commit 6c294fa84da7f01030df84cf2363901b96f0cc5f Author: Sanchayan Maity Date: Mon Apr 8 22:04:41 2024 +0530 Example code for testing hlssink4 https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1515 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1854039 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +hlssink/ +target +*~ +*.bk +*.swp +.vscode +builddir +.meson-subproject-wrap-hash.txt +Cargo.lock +*.mkv diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3e3e489 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "gst-hlssink4" +license = "MPL-2.0" +version = "0.1.0" +edition = "2021" + +[dependencies] +gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_22"] } +gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } +gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } +gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } +ctrlc = "3.4" +tokio = { version = "1.17", features = ["full"] } +once_cell = "1.19" +clap = { version = "4.5", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..03486f8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,689 @@ +use clap::{Parser, Subcommand}; +use gst::glib; +use gst::prelude::*; +use once_cell::sync::Lazy; +use std::str::FromStr; + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "gst-hlssink4", + gst::DebugColorFlags::empty(), + Some("gst-hlssink4"), + ) +}); + +#[derive(Parser)] +#[command(name = "gst-hlssink4")] +#[command(version = "0.1")] +#[command(about = "Code for testing hlssink4", long_about = None)] +struct Cli { + media_file_path: String, + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Commands { + MultipleAudioRenditionMultipleVideoVariant, + MultipleAudioRenditionMultipleVideoVariantWithIframe, + MultipleAudioRenditionSingleVideoVariant, + SingleAudioRenditionMultipleVideoVariant, + SingleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed, +} + +impl std::fmt::Debug for Commands { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Commands::MultipleAudioRenditionMultipleVideoVariant => { + write!(f, "Multiple Audio Rendition Multiple Video Variant") + } + Commands::MultipleAudioRenditionMultipleVideoVariantWithIframe => write!( + f, + "Multiple Audio Rendition Multiple Video Variant with I-Frame" + ), + Commands::MultipleAudioRenditionSingleVideoVariant => { + write!(f, "Multiple Audio Rendition Single Video Variant") + } + Commands::SingleAudioRenditionMultipleVideoVariant => { + write!(f, "Single Audio Rendition Multiple Video Variant") + } + Commands::SingleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed => write!( + f, + "Single Audio only Variant Multiple Video Variants with Audio Video muxed" + ), + } + } +} + +fn file_source_bin( + pipeline: &gst::Pipeline, + media_file_path: String, +) -> (gst::Element, gst::Element) { + let filesrc = gst::ElementFactory::make("filesrc").build().unwrap(); + let decodebin = gst::ElementFactory::make("decodebin").build().unwrap(); + let audiotee = gst::ElementFactory::make("tee") + .name("audio-tee") + .build() + .unwrap(); + let videotee = gst::ElementFactory::make("tee") + .name("video-tee") + .build() + .unwrap(); + + filesrc.set_property("location", media_file_path); + + let pipeline_weak = pipeline.downgrade(); + decodebin.connect("pad-added", false, move |args| { + let pipeline = match pipeline_weak.upgrade() { + Some(self_) => self_, + None => return None, + }; + + let pad = args[1] + .get::() + .expect("Second argument to decodebin pad-added must be pad"); + let caps = pad.current_caps().unwrap(); + let s = caps.structure(0).unwrap(); + let name = s.name(); + + if name.starts_with("video") { + let videotee = pipeline.by_name("video-tee").unwrap(); + let sinkpad = videotee.static_pad("sink").unwrap(); + pad.link(&sinkpad).unwrap(); + } else if name.starts_with("audio") { + let audiotee = pipeline.by_name("audio-tee").unwrap(); + let sinkpad = audiotee.static_pad("sink").unwrap(); + pad.link(&sinkpad).unwrap(); + } + + None + }); + + pipeline + .add_many([&filesrc, &decodebin, &audiotee, &videotee]) + .unwrap(); + + filesrc.link(&decodebin).unwrap(); + + (audiotee, videotee) +} + +fn video_bin(width: u32, height: u32, fps: u32, bitrate: u32, iframe_only: bool) -> gst::Bin { + let bin = gst::Bin::new(); + + let clocksync = gst::ElementFactory::make("clocksync").build().unwrap(); + let videoconvert = gst::ElementFactory::make("videoconvert").build().unwrap(); + let videoscale = gst::ElementFactory::make("videoscale").build().unwrap(); + let videorate = gst::ElementFactory::make("videorate").build().unwrap(); + let capsfilter = gst::ElementFactory::make("capsfilter").build().unwrap(); + let x264enc = gst::ElementFactory::make("x264enc").build().unwrap(); + let h264_capsfilter = gst::ElementFactory::make("capsfilter").build().unwrap(); + let h264parse = gst::ElementFactory::make("h264parse").build().unwrap(); + let queue = gst::ElementFactory::make("queue").build().unwrap(); + + let caps = gst::Caps::from_str(format!("video/x-raw,width={width},height={height},framerate={fps}/1,pixel-aspect-ratio=1/1,format=I420").as_str()).unwrap(); + capsfilter.set_property("caps", caps); + + let caps = gst::Caps::from_str("video/x-h264").unwrap(); + h264_capsfilter.set_property("caps", caps); + + x264enc.set_property("bitrate", bitrate); + x264enc.set_property_from_str("speed-preset", "ultrafast"); + x264enc.set_property_from_str("tune", "zerolatency"); + + if iframe_only { + x264enc.set_property("key-int-max", 1); + } else { + x264enc.set_property("key-int-max", fps * 2); + } + + bin.add_many([ + &clocksync, + &videoconvert, + &videoscale, + &videorate, + &capsfilter, + &x264enc, + &h264_capsfilter, + &h264parse, + &queue, + ]) + .expect("failed to add the elements"); + + gst::Element::link_many([ + &clocksync, + &videoconvert, + &videoscale, + &videorate, + &capsfilter, + &x264enc, + &h264_capsfilter, + &h264parse, + &queue, + ]) + .expect("failed to link the elements"); + + let queue_srcpad = queue.static_pad("src").unwrap(); + let srcpad = gst::GhostPad::builder(gst::PadDirection::Src) + .name("src") + .build(); + srcpad.set_target(Some(&queue_srcpad)).unwrap(); + + let clocksync_sinkpad = clocksync.static_pad("sink").unwrap(); + let sinkpad = gst::GhostPad::builder(gst::PadDirection::Sink) + .name("sink") + .build(); + sinkpad.set_target(Some(&clocksync_sinkpad)).unwrap(); + + bin.add_pad(&srcpad).unwrap(); + bin.add_pad(&sinkpad).unwrap(); + + bin +} + +fn audio_bin(bitrate: u32) -> gst::Bin { + let bin = gst::Bin::new(); + + let clocksync = gst::ElementFactory::make("clocksync").build().unwrap(); + let audioconvert = gst::ElementFactory::make("audioconvert").build().unwrap(); + let audiorate = gst::ElementFactory::make("audiorate").build().unwrap(); + let capsfilter = gst::ElementFactory::make("capsfilter").build().unwrap(); + let audioenc = gst::ElementFactory::make("avenc_aac").build().unwrap(); + let aacparse = gst::ElementFactory::make("aacparse").build().unwrap(); + + let caps = gst::Caps::from_str("audio/x-raw,channels=2,rate=48000,format=F32LE").unwrap(); + capsfilter.set_property("caps", caps); + audioenc.set_property("bitrate", bitrate as i32); + + bin.add_many([ + &clocksync, + &audioconvert, + &audiorate, + &capsfilter, + &audioenc, + &aacparse, + ]) + .expect("failed to add the elements"); + + gst::Element::link_many([ + &clocksync, + &audioconvert, + &audiorate, + &capsfilter, + &audioenc, + &aacparse, + ]) + .expect("failed to link the elements"); + + let aacparse_srcpad = aacparse.static_pad("src").unwrap(); + let srcpad = gst::GhostPad::builder(gst::PadDirection::Src) + .name("src") + .build(); + srcpad.set_target(Some(&aacparse_srcpad)).unwrap(); + + let clocksync_sinkpad = clocksync.static_pad("sink").unwrap(); + let sinkpad = gst::GhostPad::builder(gst::PadDirection::Sink) + .name("sink") + .build(); + sinkpad.set_target(Some(&clocksync_sinkpad)).unwrap(); + + bin.add_pad(&srcpad).unwrap(); + bin.add_pad(&sinkpad).unwrap(); + + bin +} + +fn multiple_audio_rendition_multiple_video_variant( + pipeline: &gst::Pipeline, + media_file_path: String, + iframe: bool, +) { + let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); + + hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); + hlssink.set_property_from_str("playlist-type", "event"); + hlssink.set_property("target-duration", 10u32); + if iframe { + hlssink.set_property_from_str("muxer-type", "mpegts") + } + + pipeline.add(&hlssink).unwrap(); + + let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); + + let audio_bin1 = audio_bin(256000); + pipeline.add(&audio_bin1).unwrap(); + let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); + let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); + let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let r = gst::Structure::builder("audio1") + .field("media_type", "AUDIO") + .field("uri", "hi-audio/audio.m3u8") + .field("group_id", "aac") + .field("language", "en") + .field("name", "English") + .field("default", true) + .field("autoselect", false) + .build(); + audio1_pad.set_property("alternate-rendition", r); + audio_bin1_pad.link(&audio1_pad).unwrap(); + + let audio_bin2 = audio_bin(128000); + pipeline.add(&audio_bin2).unwrap(); + let audio_bin2_sinkpad = audio_bin2.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin2_sinkpad).unwrap(); + let audio_bin2_pad = audio_bin2.static_pad("src").unwrap(); + let audio2_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let r = gst::Structure::builder("audio2") + .field("media_type", "AUDIO") + .field("uri", "mid-audio/audio.m3u8") + .field("group_id", "aac") + .field("language", "fr") + .field("name", "French") + .field("default", false) + .field("autoselect", true) + .build(); + audio2_pad.set_property("alternate-rendition", r); + audio_bin2_pad.link(&audio2_pad).unwrap(); + + let video_bin1 = video_bin(1920, 1080, 30, 2500, false); + pipeline.add(&video_bin1).unwrap(); + let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); + let video_bin1_pad = video_bin1.static_pad("src").unwrap(); + let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video1") + .field("uri", "hi/video.m3u8") + .field("bandwidth", 2500) + .field("audio", "aac") + .build(); + video1_pad.set_property("variant", v); + video_bin1_pad.link(&video1_pad).unwrap(); + + let video_bin2 = video_bin(1280, 720, 30, 1500, false); + pipeline.add(&video_bin2).unwrap(); + let video_bin2_sinkpad = video_bin2.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin2_sinkpad).unwrap(); + let video_bin2_pad = video_bin2.static_pad("src").unwrap(); + let video2_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video2") + .field("uri", "mid/video.m3u8") + .field("bandwidth", 1500) + .field("audio", "aac") + .build(); + video2_pad.set_property("variant", v); + video_bin2_pad.link(&video2_pad).unwrap(); + + let video_bin3 = video_bin(640, 360, 24, 700, false); + pipeline.add(&video_bin3).unwrap(); + let video_bin3_sinkpad = video_bin3.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin3_sinkpad).unwrap(); + let video_bin3_pad = video_bin3.static_pad("src").unwrap(); + let video3_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let mut v = gst::Structure::builder("video3") + .field("uri", "low/video.m3u8") + .field("bandwidth", 700) + .build(); + if !iframe { + v.set_value("audio", "aac".to_send_value()); + } else { + v.set_value("is-i-frame", true.to_send_value()); + } + video3_pad.set_property("variant", v); + video_bin3_pad.link(&video3_pad).unwrap(); +} + +fn multiple_audio_rendition_single_video_variant( + pipeline: &gst::Pipeline, + media_file_path: String, +) { + let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); + + hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); + hlssink.set_property_from_str("playlist-type", "event"); + hlssink.set_property("target-duration", 10u32); + + pipeline.add(&hlssink).unwrap(); + + let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); + + let audio_bin1 = audio_bin(256000); + pipeline.add(&audio_bin1).unwrap(); + let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); + let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); + let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let r = gst::Structure::builder("audio1") + .field("media_type", "AUDIO") + .field("uri", "hi-audio/audio.m3u8") + .field("group_id", "aac") + .field("language", "en") + .field("name", "English") + .field("default", true) + .field("autoselect", false) + .build(); + audio1_pad.set_property("alternate-rendition", r); + audio_bin1_pad.link(&audio1_pad).unwrap(); + + let audio_bin2 = audio_bin(128000); + pipeline.add(&audio_bin2).unwrap(); + let audio_bin2_sinkpad = audio_bin2.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin2_sinkpad).unwrap(); + let audio_bin2_pad = audio_bin2.static_pad("src").unwrap(); + let audio2_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let r = gst::Structure::builder("audio2") + .field("media_type", "AUDIO") + .field("uri", "mid-audio/audio.m3u8") + .field("group_id", "aac") + .field("language", "fr") + .field("name", "French") + .field("default", false) + .field("autoselect", true) + .build(); + audio2_pad.set_property("alternate-rendition", r); + audio_bin2_pad.link(&audio2_pad).unwrap(); + + let audio_bin3 = audio_bin(64000); + pipeline.add(&audio_bin3).unwrap(); + let audio_bin3_sinkpad = audio_bin3.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin3_sinkpad).unwrap(); + let audio_bin3_pad = audio_bin3.static_pad("src").unwrap(); + let audio3_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let r = gst::Structure::builder("audio3") + .field("media_type", "AUDIO") + .field("uri", "low-audio/audio.m3u8") + .field("group_id", "aac") + .field("language", "da") + .field("name", "Danish") + .field("default", false) + .field("autoselect", true) + .build(); + audio3_pad.set_property("alternate-rendition", r); + audio_bin3_pad.link(&audio3_pad).unwrap(); + + let video_bin1 = video_bin(1920, 1080, 30, 2500, false); + pipeline.add(&video_bin1).unwrap(); + let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); + let video_bin1_pad = video_bin1.static_pad("src").unwrap(); + let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video1") + .field("uri", "hi/video.m3u8") + .field("bandwidth", 2500) + .field("audio", "aac") + .build(); + video1_pad.set_property("variant", v); + video_bin1_pad.link(&video1_pad).unwrap(); +} + +fn single_audio_rendition_multiple_video_variant( + pipeline: &gst::Pipeline, + media_file_path: String, +) { + let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); + + hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); + hlssink.set_property_from_str("playlist-type", "event"); + hlssink.set_property("target-duration", 10u32); + + pipeline.add(&hlssink).unwrap(); + + let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); + + let audio_bin1 = audio_bin(256000); + pipeline.add(&audio_bin1).unwrap(); + let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); + let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); + let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let r = gst::Structure::builder("audio1") + .field("media_type", "AUDIO") + .field("uri", "hi-audio/audio.m3u8") + .field("group_id", "aac") + .field("language", "en") + .field("name", "English") + .field("default", true) + .field("autoselect", false) + .build(); + audio1_pad.set_property("alternate-rendition", r); + audio_bin1_pad.link(&audio1_pad).unwrap(); + + let video_bin1 = video_bin(1920, 1080, 30, 2500, false); + pipeline.add(&video_bin1).unwrap(); + let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); + let video_bin1_pad = video_bin1.static_pad("src").unwrap(); + let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video1") + .field("uri", "hi/video.m3u8") + .field("bandwidth", 2500) + .field("audio", "aac") + .build(); + video1_pad.set_property("variant", v); + video_bin1_pad.link(&video1_pad).unwrap(); + + let video_bin2 = video_bin(1280, 720, 30, 1500, false); + pipeline.add(&video_bin2).unwrap(); + let video_bin2_sinkpad = video_bin2.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin2_sinkpad).unwrap(); + let video_bin2_pad = video_bin2.static_pad("src").unwrap(); + let video2_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video2") + .field("uri", "mid/video.m3u8") + .field("bandwidth", 1500) + .field("audio", "aac") + .build(); + video2_pad.set_property("variant", v); + video_bin2_pad.link(&video2_pad).unwrap(); + + let video_bin3 = video_bin(640, 360, 24, 700, false); + pipeline.add(&video_bin3).unwrap(); + let video_bin3_sinkpad = video_bin3.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin3_sinkpad).unwrap(); + let video_bin3_pad = video_bin3.static_pad("src").unwrap(); + let video3_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video3") + .field("uri", "low/video.m3u8") + .field("audio", "aac") + .field("bandwidth", 700) + .build(); + video3_pad.set_property("variant", v); + video_bin3_pad.link(&video3_pad).unwrap(); +} + +fn single_audio_only_variant_multiple_video_variant_with_audio_video_muxed( + pipeline: &gst::Pipeline, + media_file_path: String, +) { + let hlssink = gst::ElementFactory::make("hlssink4").build().unwrap(); + + hlssink.set_property("master-playlist-location", "hlssink/master.m3u8"); + hlssink.set_property_from_str("playlist-type", "event"); + hlssink.set_property("target-duration", 10u32); + hlssink.set_property_from_str("muxer-type", "mpegts"); + + pipeline.add(&hlssink).unwrap(); + + let (audiotee, videotee) = file_source_bin(pipeline, media_file_path); + + let video_bin1 = video_bin(1920, 1080, 30, 2500, false); + pipeline.add(&video_bin1).unwrap(); + let video_bin1_sinkpad = video_bin1.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin1_sinkpad).unwrap(); + let video_bin1_pad = video_bin1.static_pad("src").unwrap(); + let video1_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video1") + .field("uri", "hi/video.m3u8") + .field("bandwidth", 2500) + .field("audio", "aac") + .build(); + video1_pad.set_property("variant", v); + video_bin1_pad.link(&video1_pad).unwrap(); + + let video_bin2 = video_bin(1280, 720, 30, 1500, false); + pipeline.add(&video_bin2).unwrap(); + let video_bin2_sinkpad = video_bin2.static_pad("sink").unwrap(); + let videotee_srcpad = videotee.request_pad_simple("src_%u").unwrap(); + videotee_srcpad.link(&video_bin2_sinkpad).unwrap(); + let video_bin2_pad = video_bin2.static_pad("src").unwrap(); + let video2_pad = hlssink.request_pad_simple("video_%u").unwrap(); + let v = gst::Structure::builder("video2") + .field("uri", "mid/video.m3u8") + .field("bandwidth", 1500) + .field("audio", "aac") + .build(); + video2_pad.set_property("variant", v); + video_bin2_pad.link(&video2_pad).unwrap(); + + let audio_bin1 = audio_bin(256000); + pipeline.add(&audio_bin1).unwrap(); + let audio_bin1_sinkpad = audio_bin1.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin1_sinkpad).unwrap(); + let audio_bin1_pad = audio_bin1.static_pad("src").unwrap(); + let audio1_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let v = gst::Structure::builder("audio1") + .field("uri", "hi/video.m3u8") + .field("bandwidth", 256000) + .build(); + audio1_pad.set_property("variant", v); + audio_bin1_pad.link(&audio1_pad).unwrap(); + + let audio_bin2 = audio_bin(128000); + pipeline.add(&audio_bin2).unwrap(); + let audio_bin2_sinkpad = audio_bin2.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin2_sinkpad).unwrap(); + let audio_bin2_pad = audio_bin2.static_pad("src").unwrap(); + let audio2_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let v = gst::Structure::builder("audio2") + .field("uri", "mid-audio/audio.m3u8") + .field("bandwidth", 128000) + .build(); + audio2_pad.set_property("variant", v); + audio_bin2_pad.link(&audio2_pad).unwrap(); + + let audio_bin3 = audio_bin(64000); + pipeline.add(&audio_bin3).unwrap(); + let audio_bin3_sinkpad = audio_bin3.static_pad("sink").unwrap(); + let audiotee_srcpad = audiotee.request_pad_simple("src_%u").unwrap(); + audiotee_srcpad.link(&audio_bin3_sinkpad).unwrap(); + let audio_bin3_pad = audio_bin3.static_pad("src").unwrap(); + let audio3_pad = hlssink.request_pad_simple("audio_%u").unwrap(); + let v = gst::Structure::builder("audio3") + .field("uri", "low-audio/audio-only.m3u8") + .field("bandwidth", 64000) + .build(); + audio3_pad.set_property("variant", v); + audio_bin3_pad.link(&audio3_pad).unwrap(); +} + +fn main() { + let cli = Cli::parse(); + + gst::init().unwrap(); + + let pipeline = gst::Pipeline::new(); + let context = glib::MainContext::default(); + let main_loop = glib::MainLoop::new(Some(&context), false); + + match cli.command { + Some(c @ Commands::MultipleAudioRenditionMultipleVideoVariant) => { + println!("Generating HLS playlist for: {:?}", c); + multiple_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path, false); + } + Some(c @ Commands::MultipleAudioRenditionMultipleVideoVariantWithIframe) => { + println!("Generating HLS playlist for: {:?}", c); + multiple_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path, true); + } + Some(c @ Commands::MultipleAudioRenditionSingleVideoVariant) => { + println!("Generating HLS playlist for: {:?}", c); + multiple_audio_rendition_single_video_variant(&pipeline, cli.media_file_path); + } + Some(c @ Commands::SingleAudioRenditionMultipleVideoVariant) => { + println!("Generating HLS playlist for: {:?}", c); + single_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path); + } + Some(c @ Commands::SingleAudioOnlyVariantMultipleVideoVariantWithAudioVideoMuxed) => { + println!("Generating HLS playlist for: {:?}", c); + single_audio_only_variant_multiple_video_variant_with_audio_video_muxed( + &pipeline, + cli.media_file_path, + ); + } + None => { + println!( + "Generating HLS playlist for: {:?}", + Commands::MultipleAudioRenditionMultipleVideoVariant + ); + multiple_audio_rendition_multiple_video_variant(&pipeline, cli.media_file_path, false); + } + } + + pipeline.set_state(gst::State::Playing).unwrap(); + + let weak_pipeline = pipeline.downgrade(); + // Capture pipeline graph 5 secs later to correctly capture STATE changes. + glib::timeout_add_seconds_once(5, move || { + let pipeline = match weak_pipeline.upgrade() { + Some(pipeline) => pipeline, + None => return, + }; + + pipeline.debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), "gst-hlssink4-pipeline"); + }); + + let bus = pipeline.bus().unwrap(); + let l_clone = main_loop.clone(); + let _bus_watch = bus + .add_watch(move |_, msg| { + use gst::MessageView; + match msg.view() { + MessageView::Eos(..) => { + println!("\nReceived End of Stream, quitting..."); + l_clone.quit(); + } + MessageView::Error(err) => { + gst::error!(CAT, obj: &err.src().unwrap(), + "Error from {:?}: {} ({:?})", + err.src().map(|s| s.path_string()), + err.error(), + err.debug() + ); + l_clone.quit(); + } + _ => (), + }; + glib::ControlFlow::Continue + }) + .expect("Failed to add bus watch"); + + ctrlc::set_handler({ + let pipeline_weak = pipeline.downgrade(); + move || { + let pipeline = pipeline_weak.upgrade().unwrap(); + println!("\nReceived Ctrl-c, sending EOS..."); + pipeline.send_event(gst::event::Eos::new()); + } + }) + .unwrap(); + + main_loop.run(); + + pipeline.set_state(gst::State::Null).unwrap(); +}