use std::sync::{Arc, Mutex}; use anyhow::Error; use byte_slice_cast::*; use glib::source::SourceId; use gst::prelude::*; use gst_app::AppSrc; use gst_audio::AudioInfo; #[path = "../tutorials-common.rs"] mod tutorials_common; const CHUNK_SIZE: usize = 1024; // Amount of bytes we are sending in each buffer const SAMPLE_RATE: u32 = 44_100; // Samples per second we are sending #[derive(Debug)] struct CustomData { source_id: Option, num_samples: u64, // Number of samples generated so far (for timestamp generation) // For waveform generation a: f64, b: f64, c: f64, d: f64, appsrc: AppSrc, } impl CustomData { fn new(appsrc: &AppSrc) -> CustomData { CustomData { source_id: None, num_samples: 0, a: 0.0, b: 1.0, c: 0.0, d: 1.0, appsrc: appsrc.clone(), } } } fn tutorial_main() -> Result<(), Error> { // Initialize GStreamer gst::init().unwrap(); // Create the playbin element let pipeline = gst::parse_launch("playbin uri=appsrc://").unwrap(); // This part is called when playbin has created the appsrc element, // so we have a chance to configure it. pipeline.connect("source-setup", false, |args| { println!("Source has been created. Configuring."); let _pipeline = args[0].get::().unwrap(); let source = args[1] .get::() .expect("Source element is expected to be an appsrc!"); let audio_info = AudioInfo::builder(gst_audio::AudioFormat::S16le, SAMPLE_RATE, 1) .build() .unwrap(); let audio_caps = audio_info.to_caps().unwrap(); source.set_caps(Some(&audio_caps)); source.set_format(gst::Format::Time); let data: Arc> = Arc::new(Mutex::new(CustomData::new(&source))); let data_clone = data.clone(); source.set_callbacks( gst_app::AppSrcCallbacks::builder() // This signal callback is triggered when appsrc needs data. // Here, we add an idle handler to the mainloop to start pushing data into the appsrc. .need_data(move |_, _size| { let data = &data_clone; let mut d = data.lock().unwrap(); if d.source_id.is_none() { println!("Start feeding"); let data_weak = Arc::downgrade(data); d.source_id = Some(glib::source::idle_add(move || { let data = match data_weak.upgrade() { Some(data) => data, None => return glib::Continue(false), }; let (appsrc, buffer) = { let mut data = data.lock().unwrap(); // Create a new empty buffer let mut buffer = gst::Buffer::with_size(CHUNK_SIZE).unwrap(); // Each sample is 16 bits let num_samples = CHUNK_SIZE / 2; // Calculate timestamp and duration let pts = gst::ClockTime::SECOND .mul_div_floor(data.num_samples, u64::from(SAMPLE_RATE)) .expect("u64 overflow"); let duration = gst::ClockTime::SECOND .mul_div_floor(num_samples as u64, u64::from(SAMPLE_RATE)) .expect("u64 overflow"); { let buffer = buffer.get_mut().unwrap(); { let mut samples = buffer.map_writable().unwrap(); let samples = samples.as_mut_slice_of::().unwrap(); // Generate some psychodelic waveforms data.c += data.d; data.d -= data.c / 1000.0; let freq = 1100.0 + 1000.0 * data.d; for sample in samples.iter_mut() { data.a += data.b; data.b -= data.a / freq; *sample = 500 * (data.a as i16); } } data.num_samples += num_samples as u64; buffer.set_pts(pts); buffer.set_duration(duration); } (data.appsrc.clone(), buffer) }; // Push the buffer into the appsrc glib::Continue(appsrc.push_buffer(buffer).is_ok()) })); } }) // This callback is triggered when appsrc has enough data and we can stop sending. .enough_data(move |_| { let mut d = data.lock().unwrap(); if let Some(source) = d.source_id.take() { println!("Stop feeding"); source.remove(); } }) .build(), ); None }); // Create a GLib main loop let main_loop = glib::MainLoop::new(None, false); let main_loop_clone = main_loop.clone(); let bus = pipeline.bus().unwrap(); // Instruct the bus to emit signals for each received message, and connect to the interesting signals #[allow(clippy::single_match)] bus.connect_message(Some("error"), move |_, msg| match msg.view() { gst::MessageView::Error(err) => { eprintln!( "Error received from element {:?}: {}", err.src().map(|s| s.path_string()), err.error() ); eprintln!("Debugging information: {:?}", err.debug()); main_loop_clone.quit(); } _ => unreachable!(), }); bus.add_signal_watch(); // Start playing pipeline.set_state(gst::State::Playing)?; // Run the GLib main loop main_loop.run(); // Cleanup pipeline.set_state(gst::State::Null)?; bus.remove_signal_watch(); Ok(()) } 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 tutorials_common::run(tutorial_main) { Ok(_) => {} Err(err) => eprintln!("Failed: {err}"), }; }