Example code for testing hlssink4
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1515
This commit is contained in:
commit
6c294fa84d
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
hlssink/
|
||||||
|
target
|
||||||
|
*~
|
||||||
|
*.bk
|
||||||
|
*.swp
|
||||||
|
.vscode
|
||||||
|
builddir
|
||||||
|
.meson-subproject-wrap-hash.txt
|
||||||
|
Cargo.lock
|
||||||
|
*.mkv
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
@ -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"] }
|
689
src/main.rs
Normal file
689
src/main.rs
Normal file
|
@ -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<gst::DebugCategory> = 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<Commands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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::<gst::Pad>()
|
||||||
|
.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();
|
||||||
|
}
|
Loading…
Reference in a new issue