diff --git a/src/gstreamer_pipeline.rs b/src/gstreamer_pipeline.rs index c6f05c2..90d0ba4 100644 --- a/src/gstreamer_pipeline.rs +++ b/src/gstreamer_pipeline.rs @@ -1,6 +1,8 @@ -use gstreamer::prelude::*; +use gstreamer::{prelude::*, PadLinkError}; use gstreamer::{Element, ElementFactory, Pipeline}; use gstreamer_app::AppSink; +use gtk::glib::BoolError; +use snafu::prelude::*; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -19,56 +21,56 @@ pub struct WebcamPipeline { } impl WebcamPipeline { - pub fn new() -> WebcamPipeline { + pub fn new() -> Result { let pipeline = Pipeline::with_name("webcam_pipeline"); // All of the following errors are unrecoverable let source = ElementFactory::make("mfvideosrc") .build() - .expect("Could not build video source for GStreamer"); + .context(BuildSnafu { + element: "mfvideosrc", + })?; let convert = ElementFactory::make("videoconvert") .build() - .expect("Could not build video convert for GStreamer"); + .context(BuildSnafu { + element: "videoconvert", + })?; let rate = ElementFactory::make("videorate") .build() - .expect("Could not build the video rate element"); + .context(BuildSnafu { + element: "videorate", + })?; let tee = ElementFactory::make("tee") .build() - .expect("Could not create tee element"); + .context(BuildSnafu { element: "tee" })?; - let queue_app = ElementFactory::make("queue") - .build() - .expect("Could not create the queue buffer"); + let queue_app = ElementFactory::make("queue").build().context(BuildSnafu { + element: "paintable queue", + })?; let sink_paintable = ElementFactory::make("gtk4paintablesink") .name("gtk4_output") .build() - .expect("Could not build gtk sink for GStreamer"); + .context(BuildSnafu { + element: "gtkpaintablesink", + })?; - let queue = ElementFactory::make("queue") - .build() - .expect("Could not create the queue buffer"); + let queue = ElementFactory::make("queue").build().context(BuildSnafu { + element: "appsink queue", + })?; let resize = ElementFactory::make("videoscale") .build() - .expect("Could not build videoscale for GStreamer"); + .context(BuildSnafu { + element: "videoscale", + })?; let caps_string = "video/x-raw,format=RGB,width=640,height=480,max-buffers=1,drop=true"; // let caps_string = String::from("video/x-raw,format=RGB,max-buffers=1,drop=true"); - let appsrc_caps = - gstreamer::Caps::from_str(caps_string).expect("Couldn't create appsrc caps"); - - /* - // let sink_frame = ElementFactory::make("appsink") - // .name("frame_output") - // .property("sync", &false) - // .build() - // .expect("Could not build appsrc for GStreamer"); - let video_info = gstreamer_video::VideoInfo::builder(gstreamer_video::VideoFormat::Rgb, 650, 480) - .build() - .expect("Couldn't build video info!"); - */ + let appsrc_caps = gstreamer::Caps::from_str(caps_string).context(BuildSnafu { + element: "appsink caps", + })?; let sink_frame = AppSink::builder() .name("frame_output") @@ -92,49 +94,81 @@ impl WebcamPipeline { &queue, &sink_frame.upcast_ref(), ]) - .expect("Could not link the elements to the pipeline"); + .context(LinkSnafu { + from: "all", + to: "pipeline", + })?; - source - .link(&convert) - .expect("Could not link video source to converter"); - - convert.link(&rate) - .expect("Could not link rate to tee"); + source.link(&convert).context(LinkSnafu { + from: "mfvideosrc", + to: "videoconvert", + })?; - rate.link_filtered( - &tee, - &gstreamer::caps::Caps::from_str("video/x-raw,framerate=15/1").expect("Could not build framerate caps"), - ).expect("Could not link converter to rate"); + convert.link(&rate).context(LinkSnafu { + from: "videoconvert", + to: "videorate", + })?; + + let tee_caps = + gstreamer::caps::Caps::from_str("video/x-raw,framerate=15/1").context(BuildSnafu { + element: "tee caps", + })?; + + rate.link_filtered(&tee, &tee_caps).context(LinkSnafu { + from: "videorate", + to: "tee", + })?; let tee_src_1 = tee .request_pad_simple("src_%u") - .expect("Could not create src pad 1"); - let sink_paintable_sinkpad = queue_app - .static_pad("sink") - .expect("Could not get sink pad for paintablesink queue"); + .ok_or(PipelineError::PadRequestError { + element: "tee pad 1".to_string(), + })?; + let paintable_queue_sinkpad = + queue_app + .static_pad("sink") + .ok_or(PipelineError::PadRequestError { + element: "gtk4 sink".to_string(), + })?; tee_src_1 - .link(&sink_paintable_sinkpad) - .expect("Could not link tee srcpad 1 to paintablesink pad"); - queue_app - .link(&sink_paintable) - .expect("Could not link app queue to paintable sink"); + .link(&paintable_queue_sinkpad) + .context(PadLinkSnafu { + from: "tee src pad", + to: "gtk4 paintable queue", + })?; + queue_app.link(&sink_paintable).context(LinkSnafu { + from: "gtk4 paintable queue", + to: "gtk4 paintable", + })?; let tee_src_2 = tee .request_pad_simple("src_%u") - .expect("Could not create src pad 2"); - let sink_frameoutput_sinkpad = queue - .static_pad("sink") - .expect("Could not get sink pad for frameoutput sink"); + .ok_or(PipelineError::PadRequestError { + element: "tee pad 2".to_string(), + })?; + let appsink_queue_sinkpad = + queue + .static_pad("sink") + .ok_or(PipelineError::PadRequestError { + element: "appsink queue".to_string(), + })?; tee_src_2 - .link(&sink_frameoutput_sinkpad) - .expect("Could not link tee srcpad 2 to frame output sink pad"); + .link(&appsink_queue_sinkpad) + .context(PadLinkSnafu { + from: "tee src pad 2", + to: "appsink queue sinkpad", + })?; - queue.link(&resize).expect("Could not link queue to resize"); - resize - .link(&sink_frame) - .expect("Could not bind resize to appsrc"); + queue.link(&resize).context(LinkSnafu { + from: "appsink queue", + to: "videoscale", + })?; + resize.link(&sink_frame).context(LinkSnafu { + from: "videoscale", + to: "appsink", + })?; - WebcamPipeline { + Ok(WebcamPipeline { pipeline, src: source, converter: convert, @@ -144,6 +178,26 @@ impl WebcamPipeline { resize, queue, sink_frame: Arc::new(Mutex::new(sink_frame)), - } + }) } } + +#[derive(Debug, Snafu)] +pub enum PipelineError { + #[snafu(display("Error during element linking"))] + LinkError { + source: BoolError, + from: String, + to: String, + }, + #[snafu(display("Error linking pads"))] + PadLinkError { + source: PadLinkError, + from: String, + to: String, + }, + #[snafu(display("Error creating element"))] + BuildError { source: BoolError, element: String }, + #[snafu(display("Error getting pad from element"))] + PadRequestError { element: String }, +}