vcs-controller/src/gstreamer_pipeline.rs

230 lines
6.8 KiB
Rust
Raw Normal View History

use gstreamer::{prelude::*, PadLinkError};
2024-04-26 22:11:37 -07:00
use gstreamer::{Element, ElementFactory, Pipeline};
use gstreamer_app::AppSink;
use gtk::glib::BoolError;
use snafu::prelude::*;
2024-04-26 22:11:37 -07:00
use std::str::FromStr;
use std::sync::{Arc, Mutex};
2024-04-24 21:29:59 -07:00
2024-06-22 12:54:32 -07:00
pub const JPEG_QUALITY: i32 = 40;
2024-05-21 13:36:08 -07:00
#[derive(Debug)]
2024-04-24 21:29:59 -07:00
pub struct WebcamPipeline {
pub pipeline: Pipeline,
pub sink_paintable: Element,
2024-04-26 22:11:37 -07:00
pub sink_frame: Arc<Mutex<AppSink>>,
2024-04-24 21:29:59 -07:00
}
impl WebcamPipeline {
pub fn new() -> Result<WebcamPipeline, PipelineError> {
2024-04-24 21:29:59 -07:00
let pipeline = Pipeline::with_name("webcam_pipeline");
2024-04-27 19:33:18 -07:00
// All of the following errors are unrecoverable
2024-04-24 21:29:59 -07:00
let source = ElementFactory::make("mfvideosrc")
.build()
.context(BuildSnafu {
element: "mfvideosrc",
})?;
2024-04-24 21:29:59 -07:00
let convert = ElementFactory::make("videoconvert")
.build()
.context(BuildSnafu {
element: "videoconvert",
})?;
2024-05-18 15:55:50 -07:00
let rate = ElementFactory::make("videorate")
.build()
.context(BuildSnafu {
element: "videorate",
})?;
2024-04-24 21:29:59 -07:00
let tee = ElementFactory::make("tee")
.build()
.context(BuildSnafu { element: "tee" })?;
2024-04-24 21:29:59 -07:00
2024-06-03 11:09:31 -07:00
let queue_app = ElementFactory::make("queue")
.property("max-size-time", 1u64)
.property("max-size-buffers", 0u32)
.property("max-size-bytes", 0u32)
.build()
.context(BuildSnafu {
element: "paintable queue",
})?;
2024-04-24 21:29:59 -07:00
let sink_paintable = ElementFactory::make("gtk4paintablesink")
2024-04-26 22:11:37 -07:00
.name("gtk4_output")
2024-04-24 21:29:59 -07:00
.build()
.context(BuildSnafu {
element: "gtkpaintablesink",
})?;
2024-04-24 21:29:59 -07:00
2024-06-03 11:09:31 -07:00
// queue.connect_closure("overrun", false, glib::closure!(|queue: Element| {
// println!("The queue is full!");
// }));
2024-04-24 21:29:59 -07:00
2024-06-22 12:54:32 -07:00
let appsink_queue = ElementFactory::make("queue")
.property("max-size-time", 1u64)
.property("max-size-buffers", 0u32)
.property("max-size-bytes", 0u32)
.build()
.context(BuildSnafu {
element: "appsink queue",
})?;
2024-04-26 22:11:37 -07:00
let resize = ElementFactory::make("videoscale")
2024-04-24 21:29:59 -07:00
.build()
.context(BuildSnafu {
element: "videoscale",
})?;
2024-04-24 21:29:59 -07:00
2024-06-22 12:54:32 -07:00
let jpeg_enc = ElementFactory::make("jpegenc")
.property("quality", JPEG_QUALITY)
.build()
.context(BuildSnafu { element: "jpegenc" })?;
let caps_string = "image/jpeg,width=640,height=640";
let appsrc_caps = gstreamer::Caps::from_str(caps_string).context(BuildSnafu {
element: "appsink caps",
})?;
2024-04-26 22:11:37 -07:00
let sink_frame = AppSink::builder()
2024-06-22 12:54:32 -07:00
.name("frame_appsink")
2024-04-26 22:11:37 -07:00
.sync(false)
2024-06-03 11:09:31 -07:00
.max_buffers(3u32)
2024-04-26 22:11:37 -07:00
.drop(true)
.caps(&appsrc_caps)
.build();
sink_frame.set_property("caps", &appsrc_caps.to_value());
2024-04-24 21:29:59 -07:00
2024-05-04 15:09:31 -07:00
pipeline
.add_many(&[
&source,
&convert,
2024-05-18 15:55:50 -07:00
&rate,
2024-05-04 15:09:31 -07:00
&tee,
&queue_app,
&sink_paintable,
2024-06-22 12:54:32 -07:00
&appsink_queue,
2024-05-04 15:09:31 -07:00
&resize,
2024-06-22 12:54:32 -07:00
&jpeg_enc,
2024-05-04 15:09:31 -07:00
&sink_frame.upcast_ref(),
])
.context(LinkSnafu {
from: "all",
to: "pipeline",
})?;
2024-06-22 12:54:32 -07:00
Element::link_many(&[&source, &convert, &rate]).context(LinkSnafu {
from: "source et. al.",
to: "rate",
})?;
2024-06-22 12:54:32 -07:00
// -- BEGIN PAINTABLE SINK PIPELINE
let tee_caps =
// gstreamer::caps::Caps::from_str("video/x-raw,framerate=15/1").context(BuildSnafu {
gstreamer::caps::Caps::from_str("video/x-raw").context(BuildSnafu {
element: "tee caps",
})?;
rate.link_filtered(&tee, &tee_caps).context(LinkSnafu {
from: "videorate",
to: "tee",
})?;
2024-04-24 21:29:59 -07:00
let tee_src_1 = tee
2024-04-26 22:11:37 -07:00
.request_pad_simple("src_%u")
.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(),
})?;
2024-06-22 12:54:32 -07:00
2024-04-24 21:29:59 -07:00
tee_src_1
.link(&paintable_queue_sinkpad)
.context(PadLinkSnafu {
from: "tee src pad",
to: "gtk4 paintable queue",
})?;
2024-06-22 12:54:32 -07:00
queue_app.link(&sink_paintable).context(LinkSnafu {
from: "gtk4 paintable queue",
to: "gtk4 paintable",
})?;
2024-04-24 21:29:59 -07:00
2024-06-22 12:54:32 -07:00
// -- END PAINTABLE SINK PIPELINE
// -- BEGIN APPSINK PIPELINE
2024-04-24 21:29:59 -07:00
let tee_src_2 = tee
2024-04-26 22:11:37 -07:00
.request_pad_simple("src_%u")
.ok_or(PipelineError::PadRequestError {
element: "tee pad 2".to_string(),
})?;
2024-06-22 12:54:32 -07:00
let appsink_queue_sinkpad =
appsink_queue
.static_pad("sink")
.ok_or(PipelineError::PadRequestError {
element: "appsink queue".to_string(),
})?;
2024-04-24 21:29:59 -07:00
tee_src_2
2024-06-22 12:54:32 -07:00
.link(&appsink_queue_sinkpad)
.context(PadLinkSnafu {
from: "tee src pad 2",
to: "appsink queue sinkpad",
})?;
2024-06-22 12:54:32 -07:00
appsink_queue.link(&resize).context(LinkSnafu {
from: "appsink_queue",
to: "resize",
})?;
let resize_caps =
gstreamer::caps::Caps::from_str("video/x-raw,format=RGB,width=640,height=640")
.context(BuildSnafu {
element: "resize_caps",
})?;
resize
.link_filtered(&jpeg_enc, &resize_caps)
.context(LinkSnafu {
from: "jpeg_enc",
to: "resize_caps",
})?;
Element::link_many(&[&jpeg_enc, &sink_frame.upcast_ref()]).context(LinkSnafu {
from: "jpeg_enc",
to: "appsink",
})?;
Ok(WebcamPipeline {
2024-04-24 21:29:59 -07:00
pipeline,
sink_paintable,
2024-04-26 22:11:37 -07:00
sink_frame: Arc::new(Mutex::new(sink_frame)),
})
2024-04-24 21:29:59 -07:00
}
}
#[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 },
}