// This example shows how to use the appsrc element. // It operates the following pipeline: // {appsrc} - {videoconvert} - {autovideosink} // The application itself provides the video-data for the pipeline, by providing // it in the callback of the appsrc element. Videoconvert makes sure that the // format the application provides can be displayed by the autovideosink // at the end of the pipeline. // The application provides data of the following format: // Video / BGRx (4 bytes) / 2 fps use anyhow::Error; use derive_more::{Display, Error}; use gst::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, } const WIDTH: usize = 320; const HEIGHT: usize = 240; fn create_pipeline() -> Result { gst::init()?; let pipeline = gst::Pipeline::default(); // Specify the format we want to provide as application into the pipeline // by creating a video info with the given format and creating caps from it for the appsrc element. let video_info = gst_video::VideoInfo::builder(gst_video::VideoFormat::Bgrx, WIDTH as u32, HEIGHT as u32) .fps(gst::Fraction::new(2, 1)) .build() .expect("Failed to create video info"); let appsrc = gst_app::AppSrc::builder() .caps(&video_info.to_caps().unwrap()) .format(gst::Format::Time) .build(); let videoconvert = gst::ElementFactory::make("videoconvert").build()?; let sink = gst::ElementFactory::make("autovideosink").build()?; pipeline.add_many(&[appsrc.upcast_ref(), &videoconvert, &sink])?; gst::Element::link_many(&[appsrc.upcast_ref(), &videoconvert, &sink])?; // Our frame counter, that is stored in the mutable environment // of the closure of the need-data callback // // Alternatively we could also simply start a new thread that // pushes a buffer to the appsrc whenever it wants to, but this // is not really needed here. It is *not required* to use the // need-data callback. let mut i = 0; appsrc.set_callbacks( // Since our appsrc element operates in pull mode (it asks us to provide data), // we add a handler for the need-data callback and provide new data from there. // In our case, we told gstreamer that we do 2 frames per second. While the // buffers of all elements of the pipeline are still empty, this will be called // a couple of times until all of them are filled. After this initial period, // this handler will be called (on average) twice per second. gst_app::AppSrcCallbacks::builder() .need_data(move |appsrc, _| { // We only produce 100 frames if i == 100 { let _ = appsrc.end_of_stream(); return; } println!("Producing frame {i}"); let r = if i % 2 == 0 { 0 } else { 255 }; let g = if i % 3 == 0 { 0 } else { 255 }; let b = if i % 5 == 0 { 0 } else { 255 }; // Create the buffer that can hold exactly one BGRx frame. let mut buffer = gst::Buffer::with_size(video_info.size()).unwrap(); { let buffer = buffer.get_mut().unwrap(); // For each frame we produce, we set the timestamp when it should be displayed // (pts = presentation time stamp) // The autovideosink will use this information to display the frame at the right time. buffer.set_pts(i * 500 * gst::ClockTime::MSECOND); // 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). // See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html let mut vframe = gst_video::VideoFrameRef::from_buffer_ref_writable(buffer, &video_info) .unwrap(); // Remember some values from the frame for later usage let width = vframe.width() as usize; let height = vframe.height() as usize; // Each line of the first plane has this many bytes let stride = vframe.plane_stride()[0] as usize; // Iterate over each of the height many lines of length stride for line in vframe .plane_data_mut(0) .unwrap() .chunks_exact_mut(stride) .take(height) { // Iterate over each pixel of 4 bytes in that line for pixel in line[..(4 * width)].chunks_exact_mut(4) { pixel[0] = b; pixel[1] = g; pixel[2] = r; pixel[3] = 0; } } } i += 1; // appsrc already handles the error here let _ = appsrc.push_buffer(buffer); }) .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); }