2017-11-16 11:39:34 +00:00
|
|
|
#[macro_use]
|
2017-07-05 07:40:02 +00:00
|
|
|
extern crate gstreamer as gst;
|
2017-08-17 14:58:15 +00:00
|
|
|
use gst::prelude::*;
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2018-11-28 06:57:17 +00:00
|
|
|
#[macro_use]
|
2017-07-05 07:40:02 +00:00
|
|
|
extern crate glib;
|
|
|
|
|
|
|
|
use std::env;
|
2017-11-16 11:39:34 +00:00
|
|
|
use std::error::Error as StdError;
|
2017-11-16 11:58:56 +00:00
|
|
|
#[cfg(feature = "v1_10")]
|
2017-11-16 11:39:34 +00:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
|
|
|
extern crate failure;
|
|
|
|
use failure::Error;
|
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
extern crate failure_derive;
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-11-12 18:07:02 +00:00
|
|
|
#[path = "../examples-common.rs"]
|
|
|
|
mod examples_common;
|
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
#[derive(Debug, Fail)]
|
|
|
|
#[fail(display = "Missing element {}", _0)]
|
|
|
|
struct MissingElement(&'static str);
|
|
|
|
|
|
|
|
#[derive(Debug, Fail)]
|
2018-07-27 10:36:40 +00:00
|
|
|
#[fail(
|
|
|
|
display = "Received error from {}: {} (debug: {:?})",
|
2018-10-28 13:47:02 +00:00
|
|
|
src, error, debug
|
2018-07-27 10:36:40 +00:00
|
|
|
)]
|
2017-11-16 11:39:34 +00:00
|
|
|
struct ErrorMessage {
|
|
|
|
src: String,
|
|
|
|
error: String,
|
|
|
|
debug: Option<String>,
|
2018-02-22 10:18:37 +00:00
|
|
|
#[cause]
|
|
|
|
cause: glib::Error,
|
2017-11-16 11:39:34 +00:00
|
|
|
}
|
|
|
|
|
2018-11-28 06:57:17 +00:00
|
|
|
#[cfg(feature = "v1_10")]
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
struct ErrorValue(Arc<Mutex<Option<Error>>>);
|
|
|
|
|
|
|
|
#[cfg(feature = "v1_10")]
|
|
|
|
impl glib::subclass::boxed::BoxedType for ErrorValue {
|
|
|
|
const NAME: &'static str = "ErrorValue";
|
|
|
|
|
2018-11-28 22:22:43 +00:00
|
|
|
glib_boxed_type!();
|
2018-11-28 06:57:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "v1_10")]
|
|
|
|
glib_boxed_derive_traits!(ErrorValue);
|
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
fn example_main() -> Result<(), Error> {
|
|
|
|
gst::init()?;
|
2017-07-05 07:40:02 +00:00
|
|
|
|
|
|
|
let args: Vec<_> = env::args().collect();
|
|
|
|
let uri: &str = if args.len() == 2 {
|
|
|
|
args[1].as_ref()
|
|
|
|
} else {
|
2017-11-10 15:53:32 +00:00
|
|
|
println!("Usage: decodebin file_path");
|
2017-11-27 11:01:03 +00:00
|
|
|
std::process::exit(-1)
|
2017-07-05 07:40:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let pipeline = gst::Pipeline::new(None);
|
2017-11-16 11:39:34 +00:00
|
|
|
let src = gst::ElementFactory::make("filesrc", None).ok_or(MissingElement("filesrc"))?;
|
|
|
|
let decodebin =
|
|
|
|
gst::ElementFactory::make("decodebin", None).ok_or(MissingElement("decodebin"))?;
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
src.set_property("location", &uri)?;
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
pipeline.add_many(&[&src, &decodebin])?;
|
|
|
|
gst::Element::link_many(&[&src, &decodebin])?;
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2018-07-27 10:07:24 +00:00
|
|
|
let pipeline_weak = pipeline.downgrade();
|
2017-11-16 11:39:34 +00:00
|
|
|
decodebin.connect_pad_added(move |dbin, src_pad| {
|
2018-07-27 10:07:24 +00:00
|
|
|
let pipeline = match pipeline_weak.upgrade() {
|
|
|
|
Some(pipeline) => pipeline,
|
|
|
|
None => return,
|
|
|
|
};
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-07-10 21:02:08 +00:00
|
|
|
let (is_audio, is_video) = {
|
2017-11-16 11:39:34 +00:00
|
|
|
let media_type = src_pad.get_current_caps().and_then(|caps| {
|
|
|
|
caps.get_structure(0).map(|s| {
|
|
|
|
let name = s.get_name();
|
|
|
|
(name.starts_with("audio/"), name.starts_with("video/"))
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
match media_type {
|
|
|
|
None => {
|
|
|
|
gst_element_warning!(
|
|
|
|
dbin,
|
|
|
|
gst::CoreError::Negotiation,
|
|
|
|
("Failed to get media type from pad {}", src_pad.get_name())
|
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(media_type) => media_type,
|
|
|
|
}
|
2017-07-05 07:40:02 +00:00
|
|
|
};
|
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
let insert_sink = |is_audio, is_video| -> Result<(), Error> {
|
|
|
|
if is_audio {
|
|
|
|
let queue =
|
|
|
|
gst::ElementFactory::make("queue", None).ok_or(MissingElement("queue"))?;
|
|
|
|
let convert = gst::ElementFactory::make("audioconvert", None)
|
|
|
|
.ok_or(MissingElement("audioconvert"))?;
|
|
|
|
let resample = gst::ElementFactory::make("audioresample", None)
|
|
|
|
.ok_or(MissingElement("audioresample"))?;
|
|
|
|
let sink = gst::ElementFactory::make("autoaudiosink", None)
|
|
|
|
.ok_or(MissingElement("autoaudiosink"))?;
|
|
|
|
|
|
|
|
let elements = &[&queue, &convert, &resample, &sink];
|
|
|
|
pipeline.add_many(elements)?;
|
|
|
|
gst::Element::link_many(elements)?;
|
|
|
|
|
|
|
|
for e in elements {
|
|
|
|
e.sync_state_with_parent()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let sink_pad = queue.get_static_pad("sink").expect("queue has no sinkpad");
|
|
|
|
src_pad.link(&sink_pad).into_result()?;
|
|
|
|
} else if is_video {
|
|
|
|
let queue =
|
|
|
|
gst::ElementFactory::make("queue", None).ok_or(MissingElement("queue"))?;
|
|
|
|
let convert = gst::ElementFactory::make("videoconvert", None)
|
|
|
|
.ok_or(MissingElement("videoconvert"))?;
|
|
|
|
let scale = gst::ElementFactory::make("videoscale", None)
|
|
|
|
.ok_or(MissingElement("videoscale"))?;
|
|
|
|
let sink = gst::ElementFactory::make("autovideosink", None)
|
|
|
|
.ok_or(MissingElement("autovideosink"))?;
|
|
|
|
|
|
|
|
let elements = &[&queue, &convert, &scale, &sink];
|
|
|
|
pipeline.add_many(elements)?;
|
|
|
|
gst::Element::link_many(elements)?;
|
|
|
|
|
|
|
|
for e in elements {
|
|
|
|
e.sync_state_with_parent()?
|
|
|
|
}
|
|
|
|
|
|
|
|
let sink_pad = queue.get_static_pad("sink").expect("queue has no sinkpad");
|
|
|
|
src_pad.link(&sink_pad).into_result()?;
|
2017-07-05 08:09:49 +00:00
|
|
|
}
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
Ok(())
|
|
|
|
};
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
if let Err(err) = insert_sink(is_audio, is_video) {
|
|
|
|
#[cfg(feature = "v1_10")]
|
|
|
|
gst_element_error!(
|
|
|
|
dbin,
|
|
|
|
gst::LibraryError::Failed,
|
|
|
|
("Failed to insert sink"),
|
|
|
|
details: gst::Structure::builder("error-details")
|
|
|
|
.field("error",
|
2018-11-28 06:57:17 +00:00
|
|
|
&ErrorValue(Arc::new(Mutex::new(Some(err)))))
|
2017-11-16 11:39:34 +00:00
|
|
|
.build()
|
|
|
|
);
|
|
|
|
|
|
|
|
#[cfg(not(feature = "v1_10"))]
|
2017-11-16 11:58:56 +00:00
|
|
|
gst_element_error!(
|
|
|
|
dbin,
|
|
|
|
gst::LibraryError::Failed,
|
|
|
|
("Failed to insert sink"),
|
|
|
|
["{}", err]
|
|
|
|
);
|
2017-07-05 07:40:02 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
pipeline.set_state(gst::State::Playing).into_result()?;
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
let bus = pipeline
|
|
|
|
.get_bus()
|
|
|
|
.expect("Pipeline without bus. Shouldn't happen!");
|
2017-07-05 07:40:02 +00:00
|
|
|
|
2017-09-13 16:35:35 +00:00
|
|
|
while let Some(msg) = bus.timed_pop(gst::CLOCK_TIME_NONE) {
|
2017-08-17 14:58:15 +00:00
|
|
|
use gst::MessageView;
|
|
|
|
|
2017-07-05 07:40:02 +00:00
|
|
|
match msg.view() {
|
2017-07-30 14:06:44 +00:00
|
|
|
MessageView::Eos(..) => break,
|
2017-07-05 07:40:02 +00:00
|
|
|
MessageView::Error(err) => {
|
2017-11-16 11:39:34 +00:00
|
|
|
pipeline.set_state(gst::State::Null).into_result()?;
|
|
|
|
|
|
|
|
#[cfg(feature = "v1_10")]
|
|
|
|
{
|
|
|
|
match err.get_details() {
|
|
|
|
Some(details) if details.get_name() == "error-details" => details
|
2018-11-28 06:57:17 +00:00
|
|
|
.get::<&ErrorValue>("error")
|
|
|
|
.and_then(|v| v.0.lock().unwrap().take())
|
2018-10-08 12:02:23 +00:00
|
|
|
.map(Result::Err)
|
2017-11-16 11:39:34 +00:00
|
|
|
.expect("error-details message without actual error"),
|
2017-12-20 17:30:14 +00:00
|
|
|
_ => Err(ErrorMessage {
|
2018-07-27 10:36:40 +00:00
|
|
|
src: err
|
|
|
|
.get_src()
|
2017-12-20 17:30:14 +00:00
|
|
|
.map(|s| s.get_path_string())
|
|
|
|
.unwrap_or_else(|| String::from("None")),
|
|
|
|
error: err.get_error().description().into(),
|
|
|
|
debug: err.get_debug(),
|
|
|
|
cause: err.get_error(),
|
2018-10-08 12:02:23 +00:00
|
|
|
}
|
|
|
|
.into()),
|
2017-11-16 11:39:34 +00:00
|
|
|
}?;
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "v1_10"))]
|
|
|
|
{
|
|
|
|
Err(ErrorMessage {
|
2018-07-27 10:36:40 +00:00
|
|
|
src: err
|
|
|
|
.get_src()
|
2017-11-16 11:58:56 +00:00
|
|
|
.map(|s| s.get_path_string())
|
2017-11-27 11:01:03 +00:00
|
|
|
.unwrap_or_else(|| String::from("None")),
|
2017-11-16 11:39:34 +00:00
|
|
|
error: err.get_error().description().into(),
|
|
|
|
debug: err.get_debug(),
|
|
|
|
cause: err.get_error(),
|
|
|
|
})?;
|
|
|
|
}
|
2017-07-05 07:40:02 +00:00
|
|
|
break;
|
2017-07-10 21:33:24 +00:00
|
|
|
}
|
2017-07-05 07:40:02 +00:00
|
|
|
MessageView::StateChanged(s) => {
|
2017-07-10 21:33:24 +00:00
|
|
|
println!(
|
2017-11-16 11:58:56 +00:00
|
|
|
"State changed from {:?}: {:?} -> {:?} ({:?})",
|
2018-01-29 12:25:12 +00:00
|
|
|
s.get_src().map(|s| s.get_path_string()),
|
2017-07-10 21:33:24 +00:00
|
|
|
s.get_old(),
|
|
|
|
s.get_current(),
|
|
|
|
s.get_pending()
|
|
|
|
);
|
|
|
|
}
|
2017-07-05 07:40:02 +00:00
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-16 11:39:34 +00:00
|
|
|
pipeline.set_state(gst::State::Null).into_result()?;
|
|
|
|
|
|
|
|
Ok(())
|
2017-07-05 07:40:02 +00:00
|
|
|
}
|
2017-11-12 18:07:02 +00:00
|
|
|
|
|
|
|
fn main() {
|
|
|
|
// tutorials_common::run is only required to set up the application environent on macOS
|
|
|
|
// (but not necessary in normal Cocoa applications where this is set up autmatically)
|
2017-11-16 11:39:34 +00:00
|
|
|
match examples_common::run(example_main) {
|
|
|
|
Ok(r) => r,
|
|
|
|
Err(e) => eprintln!("Error! {}", e),
|
|
|
|
}
|
2017-11-12 18:07:02 +00:00
|
|
|
}
|