mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-09-27 14:30:36 +00:00
162 lines
7 KiB
Rust
162 lines
7 KiB
Rust
// This example demonstrates the use of GStreamer's ToC API. This API is used
|
|
// to manage a table of contents contained in the handled media stream.
|
|
// Chapters within a matroska file would be an example of a scenario for using
|
|
// this API. Elements that can parse ToCs from a stream (such as matroskademux)
|
|
// notify all elements in the pipeline when they encountered a ToC.
|
|
// For this, the example operates the following pipeline:
|
|
|
|
// /-{queue} - {fakesink}
|
|
// {filesrc} - {decodebin} - {queue} - {fakesink}
|
|
// \- ...
|
|
|
|
use std::env;
|
|
|
|
use gst::prelude::*;
|
|
|
|
#[path = "../examples-common.rs"]
|
|
mod examples_common;
|
|
|
|
fn example_main() {
|
|
gst::init().unwrap();
|
|
|
|
let args: Vec<_> = env::args().collect();
|
|
let uri: &str = if args.len() == 2 {
|
|
args[1].as_ref()
|
|
} else {
|
|
println!("Usage: toc file_path");
|
|
std::process::exit(-1)
|
|
};
|
|
|
|
let pipeline = gst::Pipeline::default();
|
|
let src = gst::ElementFactory::make("filesrc")
|
|
.property("location", uri)
|
|
.build()
|
|
.unwrap();
|
|
let decodebin = gst::ElementFactory::make("decodebin").build().unwrap();
|
|
|
|
pipeline.add_many([&src, &decodebin]).unwrap();
|
|
gst::Element::link_many([&src, &decodebin]).unwrap();
|
|
|
|
// Need to move a new reference into the closure.
|
|
// !!ATTENTION!!:
|
|
// It might seem appealing to use pipeline.clone() here, because that greatly
|
|
// simplifies the code within the callback. What this actually dose, however, is creating
|
|
// a memory leak. The clone of a pipeline is a new strong reference on the pipeline.
|
|
// Storing this strong reference of the pipeline within the callback (we are moving it in!),
|
|
// which is in turn stored in another strong reference on the pipeline is creating a
|
|
// reference cycle.
|
|
// DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK
|
|
let pipeline_weak = pipeline.downgrade();
|
|
// Connect to decodebin's pad-added signal, that is emitted whenever it found another stream
|
|
// from the input file and found a way to decode it to its raw format.
|
|
decodebin.connect_pad_added(move |_, src_pad| {
|
|
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
|
// we moved into this callback.
|
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
|
return;
|
|
};
|
|
|
|
// In this example, we are only interested about parsing the ToC, so
|
|
// we simply pipe every encountered stream into a fakesink, essentially
|
|
// throwing away the data.
|
|
let queue = gst::ElementFactory::make("queue").build().unwrap();
|
|
let sink = gst::ElementFactory::make("fakesink").build().unwrap();
|
|
|
|
let elements = &[&queue, &sink];
|
|
pipeline.add_many(elements).unwrap();
|
|
gst::Element::link_many(elements).unwrap();
|
|
|
|
for e in elements {
|
|
e.sync_state_with_parent().unwrap();
|
|
}
|
|
|
|
let sink_pad = queue.static_pad("sink").unwrap();
|
|
src_pad
|
|
.link(&sink_pad)
|
|
.expect("Unable to link src pad to sink pad");
|
|
});
|
|
|
|
pipeline
|
|
.set_state(gst::State::Paused)
|
|
.expect("Unable to set the pipeline to the `Paused` state");
|
|
|
|
let bus = pipeline.bus().unwrap();
|
|
|
|
// Instead of using a main loop (like GLib's), we manually iterate over
|
|
// GStreamer's bus messages in this example. We don't need any special
|
|
// functionality like timeouts or GLib socket notifications, so this is sufficient.
|
|
// The bus is manually operated by repeatedly calling timed_pop on the bus with
|
|
// the desired timeout for when to stop waiting for new messages. (None = Wait forever)
|
|
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
|
use gst::MessageView;
|
|
|
|
match msg.view() {
|
|
MessageView::Eos(_) | MessageView::AsyncDone(_) => break,
|
|
MessageView::Error(err) => {
|
|
println!(
|
|
"Error from {:?}: {} ({:?})",
|
|
err.src().map(|s| s.path_string()),
|
|
err.error(),
|
|
err.debug()
|
|
);
|
|
break;
|
|
}
|
|
MessageView::Toc(msg_toc) => {
|
|
// Some element found a ToC in the current media stream and told
|
|
// us by posting a message to GStreamer's bus.
|
|
let (toc, updated) = msg_toc.toc();
|
|
println!("\nReceived toc: {:?} - updated: {}", toc.scope(), updated);
|
|
// Get a list of tags that are ToC specific.
|
|
if let Some(tags) = toc.tags() {
|
|
println!("- tags: {tags}");
|
|
}
|
|
// ToCs do not have a fixed structure. Depending on the format that
|
|
// they were parsed from, they might have different tree-like structures,
|
|
// so applications that want to support ToCs (for example in the form
|
|
// of jumping between chapters in a video) have to try parsing and
|
|
// interpreting the ToC manually.
|
|
// In this example, we simply want to print the ToC structure, so
|
|
// we iterate everything and don't try to interpret anything.
|
|
for toc_entry in toc.entries() {
|
|
// Every entry in a ToC has its own type. One type could for
|
|
// example be Chapter.
|
|
println!("\t{:?} - {}", toc_entry.entry_type(), toc_entry.uid());
|
|
// Every ToC entry can have a set of timestamps (start, stop).
|
|
if let Some((start, stop)) = toc_entry.start_stop_times() {
|
|
println!("\t- start: {start}, stop: {stop}");
|
|
}
|
|
// Every ToC entry can have tags to it.
|
|
if let Some(tags) = toc_entry.tags() {
|
|
println!("\t- tags: {tags}");
|
|
}
|
|
// Every ToC entry can have a set of child entries.
|
|
// With this structure, you can create trees of arbitrary depth.
|
|
for toc_sub_entry in toc_entry.sub_entries() {
|
|
println!(
|
|
"\n\t\t{:?} - {}",
|
|
toc_sub_entry.entry_type(),
|
|
toc_sub_entry.uid()
|
|
);
|
|
if let Some((start, stop)) = toc_sub_entry.start_stop_times() {
|
|
println!("\t\t- start: {start}, stop: {stop}");
|
|
}
|
|
if let Some(tags) = toc_sub_entry.tags() {
|
|
println!("\t\t- tags: {tags}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
pipeline
|
|
.set_state(gst::State::Null)
|
|
.expect("Unable to set the pipeline to the `Null` state");
|
|
}
|
|
|
|
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)
|
|
examples_common::run(example_main);
|
|
}
|