// This example demonstrates the use of the appsink element. // It operates the following pipeline: // {audiotestsrc} - {appsink} // The application specifies what format it wants to handle. This format // is applied by calling set_caps on the appsink. Now it's the audiotestsrc's // task to provide this data format. If the element connected to the appsink's // sink-pad were not able to provide what we ask them to, this would fail. // This is the format we request: // Audio / Signed 16bit / 1 channel / arbitrary sample rate #[macro_use] extern crate gstreamer as gst; use gst::prelude::*; extern crate gstreamer_app as gst_app; extern crate gstreamer_audio as gst_audio; extern crate glib; extern crate byte_slice_cast; use byte_slice_cast::*; use std::error::Error as StdError; use std::i16; use std::i32; extern crate failure; use failure::Error; #[macro_use] extern crate failure_derive; #[path = "../examples-common.rs"] mod examples_common; #[derive(Debug, Fail)] #[fail(display = "Missing element {}", _0)] struct MissingElement(&'static str); #[derive(Debug, Fail)] #[fail( display = "Received error from {}: {} (debug: {:?})", src, error, debug )] struct ErrorMessage { src: String, error: String, debug: Option, #[cause] cause: glib::Error, } fn create_pipeline() -> Result { gst::init()?; let pipeline = gst::Pipeline::new(None); let src = gst::ElementFactory::make("audiotestsrc", None).ok_or(MissingElement("audiotestsrc"))?; let sink = gst::ElementFactory::make("appsink", None).ok_or(MissingElement("appsink"))?; pipeline.add_many(&[&src, &sink])?; src.link(&sink)?; let appsink = sink .dynamic_cast::() .expect("Sink element is expected to be an appsink!"); // Tell the appsink what format we want. It will then be the audiotestsrc's job to // provide the format we request. // This can be set after linking the two objects, because format negotiation between // both elements will happen during pre-rolling of the pipeline. appsink.set_caps(&gst::Caps::new_simple( "audio/x-raw", &[ ("format", &gst_audio::AUDIO_FORMAT_S16.to_string()), ("layout", &"interleaved"), ("channels", &(1i32)), ("rate", &gst::IntRange::::new(1, i32::MAX)), ], )); // Getting data out of the appsink is done by setting callbacks on it. // The appsink will then call those handlers, as soon as data is available. appsink.set_callbacks( gst_app::AppSinkCallbacks::new() // Add a handler to the "new-sample" signal. .new_sample(|appsink| { // Pull the sample in question out of the appsink's buffer. let sample = match appsink.pull_sample() { None => return gst::FlowReturn::Eos, Some(sample) => sample, }; let buffer = if let Some(buffer) = sample.get_buffer() { buffer } else { gst_element_error!( appsink, gst::ResourceError::Failed, ("Failed to get buffer from appsink") ); return gst::FlowReturn::Error; }; // At this point, buffer is only a reference to an existing memory region somewhere. // When we want to access its content, we have to map it while requesting the required // mode of access (read, read/write). // This type of abstraction is necessary, because the buffer in question might not be // on the machine's main memory itself, but rather in the GPU's memory. // So mapping the buffer makes the underlying memory region accessible to us. // See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html let map = if let Some(map) = buffer.map_readable() { map } else { gst_element_error!( appsink, gst::ResourceError::Failed, ("Failed to map buffer readable") ); return gst::FlowReturn::Error; }; // We know what format the data in the memory region has, since we requested // it by setting the appsink's caps. So what we do here is interpret the // memory region we mapped as an array of signed 16 bit integers. let samples = if let Ok(samples) = map.as_slice_of::() { samples } else { gst_element_error!( appsink, gst::ResourceError::Failed, ("Failed to interprete buffer as S16 PCM") ); return gst::FlowReturn::Error; }; // For buffer (= chunk of samples), we calculate the root mean square: // (https://en.wikipedia.org/wiki/Root_mean_square) let sum: f64 = samples .iter() .map(|sample| { let f = f64::from(*sample) / f64::from(i16::MAX); f * f }) .sum(); let rms = (sum / (samples.len() as f64)).sqrt(); println!("rms: {}", rms); gst::FlowReturn::Ok }) .build(), ); Ok(pipeline) } fn main_loop(pipeline: gst::Pipeline) -> Result<(), Error> { pipeline.set_state(gst::State::Playing).into_result()?; let bus = pipeline .get_bus() .expect("Pipeline without bus. Shouldn't happen!"); for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) { use gst::MessageView; match msg.view() { MessageView::Eos(..) => break, MessageView::Error(err) => { pipeline.set_state(gst::State::Null).into_result()?; Err(ErrorMessage { src: err .get_src() .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(), })?; } _ => (), } } pipeline.set_state(gst::State::Null).into_result()?; Ok(()) } fn example_main() { match create_pipeline().and_then(main_loop) { Ok(r) => r, Err(e) => eprintln!("Error! {}", e), } } 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) examples_common::run(example_main); }