// 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 use std::i16; use anyhow::Error; use byte_slice_cast::*; 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, } fn create_pipeline() -> Result { gst::init()?; let pipeline = gst::Pipeline::default(); let src = gst::ElementFactory::make("audiotestsrc").build()?; let appsink = gst_app::AppSink::builder() // 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. .caps( &gst_audio::AudioCapsBuilder::new_interleaved() .format(gst_audio::AUDIO_FORMAT_S16) .channels(1) .build(), ) .build(); pipeline.add_many(&[&src, appsink.upcast_ref()])?; src.link(&appsink)?; // 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::builder() // Add a handler to the "new-sample" signal. .new_sample(|appsink| { // Pull the sample in question out of the appsink's buffer. let sample = appsink.pull_sample().map_err(|_| gst::FlowError::Eos)?; let buffer = sample.buffer().ok_or_else(|| { element_error!( appsink, gst::ResourceError::Failed, ("Failed to get buffer from appsink") ); gst::FlowError::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 = buffer.map_readable().map_err(|_| { element_error!( appsink, gst::ResourceError::Failed, ("Failed to map buffer readable") ); gst::FlowError::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 = map.as_slice_of::().map_err(|_| { element_error!( appsink, gst::ResourceError::Failed, ("Failed to interprete buffer as S16 PCM") ); gst::FlowError::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}"); Ok(gst::FlowSuccess::Ok) }) .build(), ); Ok(pipeline) } fn main_loop(pipeline: gst::Pipeline) -> Result<(), Error> { 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()); } _ => (), } } pipeline.set_state(gst::State::Null)?; 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 environment on macOS // (but not necessary in normal Cocoa applications where this is set up automatically) examples_common::run(example_main); }