gstreamer-rs/examples/src/bin/transmux.rs

218 lines
7.8 KiB
Rust

// This sample demonstrates the use of manual typefinding, as well as
// the multiqueue element.
// The example takes a URI and an output file as input.
// The URI is used to construct a source element.
// To the beginning, a typefind element is plugged after the src to detect
// the container type:
// {src} - {typefind}
// After the typefind element reported the detected type, the example manually
// selects an appropriate demuxer (from a list of very few supported formats).
// The demuxer is plugged into the pipeline, every stream is linked to the multiqueue
// and piped into the matroskamux at the end of the pipeline.
// The result is then sent into the filesink. Running pipeline after typefind:
// /-[audio]-\
// {src} - {typefind} - {demuxer} -| {multiqueue} - {matroskamux} - {filesink}
// \-[video]-/
use std::env;
use anyhow::Error;
use derive_more::{Display, Error};
use gst::{element_error, prelude::*};
#[path = "../examples-common.rs"]
mod examples_common;
#[derive(Debug, Display, Error)]
#[display(fmt = "Received error from {src}: {error} (debug: {debug:?})")]
struct ErrorMessage {
src: glib::GString,
error: glib::Error,
debug: Option<glib::GString>,
}
fn example_main() -> Result<(), Error> {
gst::init()?;
let args: Vec<_> = env::args().collect();
let uri: &str;
let output_file: &str;
if args.len() == 3 {
uri = args[1].as_ref();
output_file = args[2].as_ref();
} else {
println!("Usage: multiqueue URI output_file");
std::process::exit(-1)
};
let pipeline = gst::Pipeline::default();
let src = gst::Element::make_from_uri(gst::URIType::Src, uri, None)
.expect("We do not seem to support this uri");
let typefinder = gst::ElementFactory::make("typefind").build()?;
let queue = gst::ElementFactory::make("multiqueue")
.property("max-size-buffers", 0u32)
.property("max-size-time", 0u64)
.property("max-size-bytes", 1024u32 * 1024 * 100)
.build()?;
let muxer = gst::ElementFactory::make("matroskamux").build()?;
let sink = gst::ElementFactory::make("filesink")
.property("location", output_file)
.build()?;
// Increase the queue capacity to 100MB to avoid a stalling pipeline
pipeline
.add_many(&[&src, &typefinder, &queue, &muxer, &sink])
.expect("failed to add elements to pipeline");
src.link(&typefinder)?;
muxer.link(&sink)?;
let pipeline_clone = pipeline.clone();
let typefinder_clone = typefinder.clone();
typefinder.connect("have-type", false, move |values| {
let (pipeline, typefinder) = (&pipeline_clone, &typefinder_clone);
// Use the detected format to select between a small set of supported demuxers
// Hint: This should probably never be done manually, for stuff like this,
// the decodebin should be used, that does this stuff automatically and handles
// much more corner-cases. This is just for the sake of being an example.
let caps = values[2]
.get::<gst::Caps>()
.expect("typefinder \"have-type\" signal values[2]");
let format_name = caps.structure(0).expect("Failed to get format name").name();
let demuxer = match format_name.as_str() {
"video/x-matroska" | "video/webm" => gst::ElementFactory::make("matroskademux")
.build()
.expect("matroskademux missing"),
"video/quicktime" => gst::ElementFactory::make("qtdemux")
.build()
.expect("qtdemux missing"),
_ => {
eprintln!("Sorry, this format is not supported by this example.");
std::process::exit(-1);
}
};
// We found a supported format and created the appropriate demuxer -> link it
pipeline
.add(&demuxer)
.expect("Failed to build remux pipeline");
// We simply keep the typefinder element and pipe the data through it.
// Removing is non-trivial since it started reading data from the pipeline
// that the next element (the format specific demuxer) would need.
typefinder
.link(&demuxer)
.expect("Failed to build remux pipeline");
let queue_clone = queue.clone();
let muxer_clone = muxer.clone();
demuxer.connect_pad_added(move |demux, src_pad| {
handle_demux_pad_added(demux, src_pad, &queue_clone, &muxer_clone)
});
demuxer
.sync_state_with_parent()
.expect("Failed to build remux pipeline");
None
});
pipeline.set_state(gst::State::Playing)?;
let bus = pipeline
.bus()
.expect("Pipeline without bus. Shouldn't happen!");
for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => break,
MessageView::Error(err) => {
pipeline.set_state(gst::State::Null)?;
return Err(ErrorMessage {
src: msg
.src()
.map(|s| s.path_string())
.unwrap_or_else(|| glib::GString::from("UNKNOWN")),
error: err.error(),
debug: err.debug(),
}
.into());
}
MessageView::StateChanged(s) => {
println!(
"State changed from {:?}: {:?} -> {:?} ({:?})",
s.src().map(|s| s.path_string()),
s.old(),
s.current(),
s.pending()
);
}
_ => (),
}
}
pipeline.set_state(gst::State::Null)?;
Ok(())
}
// This is the callback function called by the demuxer, when a new stream was detected.
fn handle_demux_pad_added(
demuxer: &gst::Element,
demux_src_pad: &gst::Pad,
queue: &gst::Element,
muxer: &gst::Element,
) {
// Pipe the detected stream through our multiqueue to the muxer.
// For that, we need to request a sink pad that fits our needs.
let link_to_muxer = || -> Result<(), Error> {
let queue_sink_pad = queue
.request_pad_simple("sink_%u")
.expect("If this happened, something is terribly wrong");
demux_src_pad.link(&queue_sink_pad)?;
// Now that we requested a sink pad fitting our needs from the multiqueue,
// the multiqueue automatically created a fitting src pad on the other side.
// sink and src pad are linked internally, so we can iterate this internal link chain
// and dependably retrieve the src pad corresponding to our requested sink pad.
let queue_src_pad = queue_sink_pad
.iterate_internal_links()
.next()?
.expect("Failed to iterate the multiqueue's internal link chain");
// Link the multiqueue's output for this stream to the matroskamuxer.
// For that, we request an appropriate pad at the muxer, that fits our needs.
let muxer_sink_pad = muxer
.compatible_pad(&queue_src_pad, None)
.expect("Aww, you found a format that matroska doesn't support!");
queue_src_pad.link(&muxer_sink_pad)?;
Ok(())
};
if let Err(err) = link_to_muxer() {
element_error!(
demuxer,
gst::LibraryError::Failed,
("Failed to insert sink"),
["{}", err]
);
}
}
fn main() {
// tutorials_common::run is only required to set up the application environment on macOS
// (but not necessary in normal Cocoa applications where this is set up automatically)
match examples_common::run(example_main) {
Ok(r) => r,
Err(e) => eprintln!("Error! {e}"),
}
}