From d42ef3b5503683c0b4149b65f891e6bcca9f7342 Mon Sep 17 00:00:00 2001 From: uttarayan21 Date: Wed, 17 Dec 2025 23:35:05 +0530 Subject: [PATCH] feat(gst): enhance GStreamer integration with new modules and improved API This commit introduces significant enhancements to the GStreamer integration by: - Adding new modules for bins, caps, elements, pads, and plugins - Implementing a more ergonomic API with helper methods like play(), pause(), ready() - Adding support for various GStreamer plugins including app, autodetect, playback, and videoconvertscale - Improving error handling with better context attachment - Updating dependencies to latest versions including gstreamer-video 0.24.4 - Refactoring existing code to use modern Rust patterns and features --- Cargo.lock | 44 +++++- examples/hdr-gstreamer-wgpu/src/main.rs | 7 +- gst/Cargo.toml | 10 +- gst/src/bin.rs | 51 +++++++ gst/src/caps.rs | 53 ++++++++ gst/src/element.rs | 52 ++++++++ gst/src/lib.rs | 96 ++++++++------ gst/src/pad.rs | 31 +++++ gst/src/playbin3.rs | 65 --------- gst/src/plugins.rs | 4 + gst/src/plugins/app.rs | 2 + gst/src/plugins/app/appsink.rs | 108 +++++++++++++++ gst/src/plugins/autodetect.rs | 2 + gst/src/plugins/autodetect/autovideosink.rs | 30 +++++ gst/src/plugins/playback.rs | 2 + gst/src/plugins/playback/playbin3.rs | 125 ++++++++++++++++++ gst/src/plugins/videoconvertscale.rs | 2 + .../plugins/videoconvertscale/videoconvert.rs | 49 +++++++ gst/src/wgpu.rs | 3 + 19 files changed, 619 insertions(+), 117 deletions(-) create mode 100644 gst/src/bin.rs create mode 100644 gst/src/caps.rs create mode 100644 gst/src/element.rs create mode 100644 gst/src/pad.rs delete mode 100644 gst/src/playbin3.rs create mode 100644 gst/src/plugins.rs create mode 100644 gst/src/plugins/app.rs create mode 100644 gst/src/plugins/app/appsink.rs create mode 100644 gst/src/plugins/autodetect.rs create mode 100644 gst/src/plugins/autodetect/autovideosink.rs create mode 100644 gst/src/plugins/playback.rs create mode 100644 gst/src/plugins/playback/playbin3.rs create mode 100644 gst/src/plugins/videoconvertscale.rs create mode 100644 gst/src/plugins/videoconvertscale/videoconvert.rs diff --git a/Cargo.lock b/Cargo.lock index d0fa1bd..0790a0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3053,8 +3053,10 @@ dependencies = [ "glib 0.21.5", "gstreamer 0.24.4", "gstreamer-app 0.24.4", + "gstreamer-video 0.24.4", "thiserror 2.0.17", "tracing", + "tracing-subscriber", "wgpu", ] @@ -3254,12 +3256,28 @@ dependencies = [ "glib 0.20.12", "gstreamer 0.23.7", "gstreamer-base 0.23.6", - "gstreamer-video-sys", + "gstreamer-video-sys 0.23.6", "libc", "once_cell", "thiserror 2.0.17", ] +[[package]] +name = "gstreamer-video" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33987f6a6a99750a07b0341d6288bac89b9b301be4672a209935203d4608d547" +dependencies = [ + "cfg-if", + "futures-channel", + "glib 0.21.5", + "gstreamer 0.24.4", + "gstreamer-base 0.24.4", + "gstreamer-video-sys 0.24.4", + "libc", + "thiserror 2.0.17", +] + [[package]] name = "gstreamer-video-sys" version = "0.23.6" @@ -3274,6 +3292,20 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gstreamer-video-sys" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00c28faad96cd40a7b7592433051199691b131b08f622ed5d51c54e049792d3" +dependencies = [ + "glib-sys 0.21.5", + "gobject-sys 0.21.5", + "gstreamer-base-sys 0.24.4", + "gstreamer-sys 0.24.4", + "libc", + "system-deps", +] + [[package]] name = "guillotiere" version = "0.6.2" @@ -3351,10 +3383,10 @@ name = "hdr-gstreamer-wgpu" version = "0.1.0" dependencies = [ "anyhow", - "gstreamer 0.23.7", - "gstreamer-app 0.23.5", - "gstreamer-base 0.23.6", - "gstreamer-video", + "gstreamer 0.24.4", + "gstreamer-app 0.24.4", + "gstreamer-base 0.24.4", + "gstreamer-video 0.23.6", "pollster 0.4.0", "tracing", "tracing-subscriber", @@ -3735,7 +3767,7 @@ dependencies = [ "gstreamer 0.23.7", "gstreamer-app 0.23.5", "gstreamer-base 0.23.6", - "gstreamer-video", + "gstreamer-video 0.23.6", "iced", "iced_wgpu", "log", diff --git a/examples/hdr-gstreamer-wgpu/src/main.rs b/examples/hdr-gstreamer-wgpu/src/main.rs index 5d82fc7..fc6cc08 100644 --- a/examples/hdr-gstreamer-wgpu/src/main.rs +++ b/examples/hdr-gstreamer-wgpu/src/main.rs @@ -31,10 +31,8 @@ impl HdrTextureFormatExt for wgpu::TextureFormat { matches!( self, wgpu::TextureFormat::Rgba16Float - // | wgpu::TextureFormat::Rg11b10float - // | wgpu::TextureFormat::R11g11b10float - | wgpu::TextureFormat::Rgb10a2Unorm | wgpu::TextureFormat::Rgba32Float + | wgpu::TextureFormat::Rgb10a2Unorm ) } } @@ -61,7 +59,7 @@ impl State { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::LowPower, + power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), force_fallback_adapter: false, }) @@ -207,6 +205,7 @@ impl State { label: Some("Jello Texture Bind Group"), }); let gst = Video::new().context("Failed to create Video")?; + std::thread::sleep(std::time::Duration::from_secs(10)); // surface.configure(&device, &config); Ok(Self { diff --git a/gst/Cargo.toml b/gst/Cargo.toml index 0fcda14..c8b54e8 100644 --- a/gst/Cargo.toml +++ b/gst/Cargo.toml @@ -8,8 +8,12 @@ edition = "2021" [dependencies] error-stack = "0.6" glib = "0.21.5" -gstreamer = "0.24.4" -gstreamer-app = "0.24.4" +gstreamer = { version = "0.24.4", features = ["v1_18"] } +gstreamer-app = { version = "0.24.4", features = ["v1_18"] } +gstreamer-video = { version = "0.24.4", features = ["v1_18"] } thiserror = "2.0" -tracing = "0.1" +tracing = { version = "0.1", features = ["log"] } wgpu = { version = "27.0.1", default-features = false } + +[dev-dependencies] +tracing-subscriber = "0.3.22" diff --git a/gst/src/bin.rs b/gst/src/bin.rs new file mode 100644 index 0000000..a12a641 --- /dev/null +++ b/gst/src/bin.rs @@ -0,0 +1,51 @@ +use crate::*; + +pub struct Bin { + inner: gstreamer::Bin, +} + +impl IsElement for Bin { + fn as_element(&self) -> &Element { + let element = self.inner.upcast_ref::(); + unsafe { core::mem::transmute(element) } + } + + fn into_element(self) -> Element { + Element { + inner: self.inner.into(), + } + } +} +impl Bin { + pub fn new(name: impl AsRef) -> Self { + let bin = gstreamer::Bin::with_name(name.as_ref()); + Bin { inner: bin } + } + + pub fn add(&mut self, element: impl IsElement) -> Result<&mut Self> { + self.inner + .add(&element.as_element().inner) + .change_context(Error) + .attach("Failed to add element to bin")?; + Ok(self) + } + + pub fn add_many<'a, E: IsElement + 'a>( + &mut self, + elements: impl IntoIterator, + ) -> Result<&mut Self> { + self.inner + .add_many(elements.into_iter().map(|e| &e.as_element().inner)) + .change_context(Error) + .attach("Failed to add elements to bin")?; + Ok(self) + } + + pub fn add_pad(&mut self, pad: &Pad) -> Result<&mut Self> { + self.inner + .add_pad(&pad.inner) + .change_context(Error) + .attach("Failed to add pad to bin")?; + Ok(self) + } +} diff --git a/gst/src/caps.rs b/gst/src/caps.rs new file mode 100644 index 0000000..b9a8493 --- /dev/null +++ b/gst/src/caps.rs @@ -0,0 +1,53 @@ +use crate::*; +#[repr(transparent)] +pub struct Caps { + pub(crate) inner: gstreamer::caps::Caps, +} + +impl Caps { + pub fn builder(cs: CapsType) -> CapsBuilder { + CapsBuilder::new(cs) + } +} + +pub struct CapsBuilder { + inner: gstreamer::caps::Builder, +} + +impl CapsBuilder { + pub fn field + Send>(mut self, name: impl AsRef, value: V) -> Self { + use gstreamer::prelude::*; + self.inner = self.inner.field(name.as_ref(), value); + self + } + + pub fn build(self) -> Caps { + Caps { + inner: self.inner.build(), + } + } +} + +pub enum CapsType { + Video, + Audio, + Text, +} + +impl CapsType { + pub fn as_str(&self) -> &str { + match self { + CapsType::Video => "video/x-raw", + CapsType::Audio => "audio/x-raw", + CapsType::Text => "text/x-raw", + } + } +} + +impl CapsBuilder { + pub fn new(cs: CapsType) -> Self { + CapsBuilder { + inner: gstreamer::Caps::builder(cs.as_str()), + } + } +} diff --git a/gst/src/element.rs b/gst/src/element.rs new file mode 100644 index 0000000..2a66330 --- /dev/null +++ b/gst/src/element.rs @@ -0,0 +1,52 @@ +use crate::{Error, Pad, Result, ResultExt}; +#[repr(transparent)] +pub struct Element { + pub(crate) inner: gstreamer::Element, +} + +pub trait IsElement { + fn as_element(&self) -> ∈ + fn into_element(self) -> Element; + fn pad(&self, name: &str) -> Option { + use gstreamer::prelude::*; + self.as_element().inner.static_pad(name).map(Pad::from) + } +} + +impl IsElement for Element { + fn as_element(&self) -> &Element { + self + } + + fn into_element(self) -> Element { + self + } +} + +pub trait Sink: IsElement { + fn sink_pad(&self) -> Pad { + use gstreamer::prelude::*; + self.as_element() + .pad("sink") + .map(From::from) + .expect("Sink element has no sink pad") + } +} +pub trait Source: IsElement { + fn source_pad(&self) -> Pad { + use gstreamer::prelude::*; + self.as_element() + .pad("src") + .map(From::from) + .expect("Source element has no src pad") + } + + fn link(&self, sink: &S) -> Result<()> { + use gstreamer::prelude::*; + self.as_element() + .inner + .link(&sink.as_element().inner) + .change_context(Error) + .attach("Failed to link source to sink") + } +} diff --git a/gst/src/lib.rs b/gst/src/lib.rs index 1e93a79..ce2e21e 100644 --- a/gst/src/lib.rs +++ b/gst/src/lib.rs @@ -1,5 +1,20 @@ +pub mod bin; +pub mod caps; +pub mod element; pub mod errors; -pub mod playbin3; +pub mod pad; +pub mod plugins; +// pub mod playbin3; +// pub mod videoconvert; + +pub use bin::*; +pub use caps::*; +pub use element::*; +pub use pad::*; +pub use plugins::*; +// pub use playbin3::*; +// pub use videoconvert::*; + use errors::*; use gstreamer::prelude::*; use std::sync::Arc; @@ -28,25 +43,18 @@ impl Gst { Err(_e) => return Err(Error).attach("Failed to downcast to Pipeline"), Ok(p) => p, }; - Ok(Pipeline { pipeline }) + Ok(Pipeline { inner: pipeline }) } } -pub struct Bin { - bin: gstreamer::Bin, -} -pub struct Sample { - sample: gstreamer::Sample, -} - pub struct Pipeline { - pipeline: gstreamer::Pipeline, + inner: gstreamer::Pipeline, } impl core::fmt::Debug for Pipeline { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Pipeline") - .field("pipeline", &self.pipeline) + .field("pipeline", &self.inner) // .field("state", &self.pipeline.state(gstreamer::ClockTime::NONE)) .finish() } @@ -54,23 +62,50 @@ impl core::fmt::Debug for Pipeline { impl Drop for Pipeline { fn drop(&mut self) { - let _ = self.pipeline.set_state(gstreamer::State::Null); + let _ = self.inner.set_state(gstreamer::State::Null); } } impl Pipeline { pub fn bus(&self) -> Result { let bus = self - .pipeline + .inner .bus() .ok_or(Error) .attach("Failed to get bus from pipeline")?; Ok(Bus { bus }) } - pub fn set_state(&self, state: gstreamer::State) -> Result { + pub fn play(&self) -> Result<()> { + self.inner + .set_state(gstreamer::State::Playing) + .change_context(Error) + .attach("Failed to set pipeline to Playing state")?; + Ok(()) + } + + pub fn pause(&self) -> Result<()> { + self.inner + .set_state(gstreamer::State::Paused) + .change_context(Error) + .attach("Failed to set pipeline to Paused state")?; + Ok(()) + } + + pub fn ready(&self) -> Result<()> { + self.inner + .set_state(gstreamer::State::Ready) + .change_context(Error) + .attach("Failed to set pipeline to Paused state")?; + Ok(()) + } + + pub unsafe fn set_state( + &self, + state: gstreamer::State, + ) -> Result { let result = self - .pipeline + .inner .set_state(state) .change_context(Error) .attach("Failed to set pipeline state")?; @@ -93,19 +128,10 @@ impl Bus { }; self.bus.iter_timed(clocktime) } -} -/// Pads are link points between elements -pub struct Pad { - pad: gstreamer::Pad, -} - -pub struct Element { - element: gstreamer::Element, -} - -pub struct AppSink { - appsink: gstreamer_app::AppSink, + pub fn stream(&self) -> gstreamer::bus::BusStream { + self.bus.stream() + } } pub struct Playbin3Builder { @@ -140,7 +166,7 @@ fn gst_play_pipeline() { let bus = pipeline.bus().expect("Failed to get bus from pipeline"); pipeline - .set_state(gstreamer::State::Playing) + .play() .expect("Unable to set the pipeline to the `Playing` state"); for msg in bus.iter_timed(None) { @@ -160,10 +186,6 @@ fn gst_play_pipeline() { _ => (), } } - - pipeline - .set_state(gstreamer::State::Null) - .expect("Unable to set the pipeline to the `Null` state"); } #[test] @@ -212,13 +234,13 @@ fn test_appsink() { let bus = pipeline.bus().expect("Failed to get bus from pipeline"); let sink = pipeline - .pipeline + .inner .by_name("video-sink") .expect("Sink not found") .downcast::() .expect("Failed to downcast to AppSink"); let capsfilter = pipeline - .pipeline + .inner .by_name("video-filter") .expect("Capsfilter not found"); @@ -236,12 +258,8 @@ fn test_appsink() { .build(), ); - // let appsink = sink - // .dynamic_cast::() - // .expect("Failed to cast to AppSink"); - pipeline - .set_state(gstreamer::State::Playing) + .play() .expect("Unable to set the pipeline to the `Playing` state"); for msg in bus.iter_timed(None) { diff --git a/gst/src/pad.rs b/gst/src/pad.rs new file mode 100644 index 0000000..3081434 --- /dev/null +++ b/gst/src/pad.rs @@ -0,0 +1,31 @@ +use crate::*; +/// Pads are link points between elements +#[repr(transparent)] +pub struct Pad { + pub(crate) inner: gstreamer::Pad, +} + +impl From for Pad { + fn from(inner: gstreamer::Pad) -> Self { + Pad { inner } + } +} + +impl Pad { + pub fn ghost(target: &Pad) -> Result { + let ghost_pad = gstreamer::GhostPad::with_target(&target.inner) + .change_context(Error) + .attach("Failed to create ghost pad")?; + Ok(Pad { + inner: ghost_pad.upcast(), + }) + } + pub fn activate(&self, activate: bool) -> Result<()> { + use gstreamer::prelude::*; + self.inner + .set_active(activate) + .change_context(Error) + .attach("Failed to set_active pad")?; + Ok(()) + } +} diff --git a/gst/src/playbin3.rs b/gst/src/playbin3.rs deleted file mode 100644 index 71beb73..0000000 --- a/gst/src/playbin3.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::*; -pub struct Playbin3 { - inner: gstreamer::Element, -} - -impl Drop for Playbin3 { - fn drop(&mut self) { - let _ = self.inner.set_state(gstreamer::State::Null); - } -} - -impl Playbin3 { - pub fn new(name: impl AsRef) -> Result { - use gstreamer::prelude::*; - gstreamer::ElementFactory::make("playbin3") - .name(name.as_ref()) - .build() - .map(|element| Playbin3 { inner: element }) - .change_context(Error) - } - - pub fn with_uri(self, uri: impl AsRef) -> Self { - use gstreamer::prelude::*; - self.inner.set_property("uri", uri.as_ref()); - self - } - - pub fn sample(&self) -> Option { - use gstreamer::prelude::*; - self.inner - .property::>("sample") - // // .and_then(|v| v.get::().ok()) - .map(|sample| Sample { sample }) - } - - pub fn play(&self) -> Result<()> { - use gstreamer::prelude::*; - self.inner - .set_state(gstreamer::State::Playing) - .change_context(Error) - .attach("Failed to set playbin3 to Playing state")?; - Ok(()) - } - pub fn bus(&self) -> Result { - let bus = self - .inner - .bus() - .ok_or(Error) - .attach("Failed to get bus from playbin3")?; - Ok(Bus { bus }) - } -} - -#[test] -fn test_playbin3() { - use gstreamer::prelude::*; - gstreamer::init().unwrap(); - let playbin3 = Playbin3::new("test_playbin3").unwrap().with_uri( - "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", - ); - playbin3.play().unwrap(); - let bus = playbin3.bus().unwrap(); - for msg in bus.iter_timed(None) {} - // std::thread::sleep(std::time::Duration::from_secs(5)); -} diff --git a/gst/src/plugins.rs b/gst/src/plugins.rs new file mode 100644 index 0000000..3d3d3db --- /dev/null +++ b/gst/src/plugins.rs @@ -0,0 +1,4 @@ +pub mod app; +pub mod autodetect; +pub mod playback; +pub mod videoconvertscale; diff --git a/gst/src/plugins/app.rs b/gst/src/plugins/app.rs new file mode 100644 index 0000000..e3fe158 --- /dev/null +++ b/gst/src/plugins/app.rs @@ -0,0 +1,2 @@ +pub mod appsink; +pub use appsink::*; diff --git a/gst/src/plugins/app/appsink.rs b/gst/src/plugins/app/appsink.rs new file mode 100644 index 0000000..2cc5b94 --- /dev/null +++ b/gst/src/plugins/app/appsink.rs @@ -0,0 +1,108 @@ +use crate::*; + +pub struct AppSink { + inner: gstreamer::Element, +} + +impl IsElement for AppSink { + fn as_element(&self) -> &Element { + unsafe { core::mem::transmute(&self.inner) } + } + + fn into_element(self) -> Element { + Element { inner: self.inner } + } +} + +impl Sink for AppSink {} + +impl AppSink { + fn appsink(&self) -> &gstreamer_app::AppSink { + self.inner + .downcast_ref::() + .expect("Failed to downcast to AppSink") + } + pub fn new(name: impl AsRef) -> Result { + use gstreamer::prelude::*; + let inner = gstreamer::ElementFactory::make("appsink") + .name(name.as_ref()) + .build() + .change_context(Error) + .attach("Failed to create appsink element")?; + Ok(AppSink { inner }) + } + + pub fn with_caps(mut self, caps: &gstreamer::Caps) -> Self { + use gstreamer::prelude::*; + // self.inner.set_caps(Some(caps)); + self + } + + pub fn set_callbacks(&self, callbacks: gstreamer_app::AppSinkCallbacks) -> Result<()> { + self.appsink().set_callbacks(callbacks); + Ok(()) + } + + pub fn pull_sample(&self, timeout: impl Into>) -> Result { + use gstreamer::prelude::*; + self.appsink() + .pull_sample() + .change_context(Error) + .attach("Failed to pull sample from AppSink") + .map(Sample::from) + } + pub fn try_pull_sample( + &self, + timeout: impl Into>, + ) -> Result> { + use gstreamer::prelude::*; + Ok(self + .appsink() + .try_pull_sample(duration_to_clocktime(timeout)?) + .map(From::from)) + } + + pub fn pull_preroll(&self, timeout: impl Into>) -> Result { + use gstreamer::prelude::*; + self.appsink() + .pull_preroll() + .change_context(Error) + .attach("Failed to pull preroll sample from AppSink") + .map(Sample::from) + } + + pub fn try_pull_preroll( + &self, + timeout: impl Into>, + ) -> Result> { + use gstreamer::prelude::*; + Ok(self + .appsink() + .try_pull_preroll(duration_to_clocktime(timeout)?) + .map(From::from)) + } +} + +fn duration_to_clocktime( + timeout: impl Into>, +) -> Result> { + match timeout.into() { + Some(dur) => { + let clocktime = gstreamer::ClockTime::try_from(dur) + .change_context(Error) + .attach("Failed to convert duration to ClockTime")?; + Ok(Some(clocktime)) + } + None => Ok(None), + } +} + +pub struct Sample { + inner: gstreamer::Sample, +} + +impl From for Sample { + fn from(inner: gstreamer::Sample) -> Self { + Sample { inner } + } +} diff --git a/gst/src/plugins/autodetect.rs b/gst/src/plugins/autodetect.rs new file mode 100644 index 0000000..44f9d3b --- /dev/null +++ b/gst/src/plugins/autodetect.rs @@ -0,0 +1,2 @@ +pub mod autovideosink; +pub use autovideosink::*; diff --git a/gst/src/plugins/autodetect/autovideosink.rs b/gst/src/plugins/autodetect/autovideosink.rs new file mode 100644 index 0000000..43f1aee --- /dev/null +++ b/gst/src/plugins/autodetect/autovideosink.rs @@ -0,0 +1,30 @@ +use crate::*; + +#[repr(transparent)] +pub struct AutoVideoSink { + inner: gstreamer::Element, +} + +impl IsElement for AutoVideoSink { + fn as_element(&self) -> &Element { + unsafe { core::mem::transmute(&self.inner) } + } + + fn into_element(self) -> Element { + Element { inner: self.inner } + } +} + +impl Sink for AutoVideoSink {} + +impl AutoVideoSink { + pub fn new(name: impl AsRef) -> Result { + use gstreamer::prelude::*; + let element = gstreamer::ElementFactory::make("autovideosink") + .name(name.as_ref()) + .build() + .change_context(Error) + .attach("Failed to create autovideosink element")?; + Ok(AutoVideoSink { inner: element }) + } +} diff --git a/gst/src/plugins/playback.rs b/gst/src/plugins/playback.rs new file mode 100644 index 0000000..c738c1c --- /dev/null +++ b/gst/src/plugins/playback.rs @@ -0,0 +1,2 @@ +pub mod playbin3; +pub use playbin3::*; diff --git a/gst/src/plugins/playback/playbin3.rs b/gst/src/plugins/playback/playbin3.rs new file mode 100644 index 0000000..c0aef9c --- /dev/null +++ b/gst/src/plugins/playback/playbin3.rs @@ -0,0 +1,125 @@ +use crate::*; +pub struct Playbin3 { + inner: gstreamer::Element, +} + +impl Drop for Playbin3 { + fn drop(&mut self) { + let _ = self + .inner + .set_state(gstreamer::State::Null) + .inspect_err(|e| { + tracing::error!("Failed to set playbin3 to Null state on drop: {:?}", e) + }); + } +} + +impl Playbin3 { + pub fn new(name: impl AsRef) -> Result { + use gstreamer::prelude::*; + gstreamer::ElementFactory::make("playbin3") + .name(name.as_ref()) + .build() + .map(|element| Playbin3 { inner: element }) + .change_context(Error) + } + + pub fn with_uri(self, uri: impl AsRef) -> Self { + use gstreamer::prelude::*; + self.inner.set_property("uri", uri.as_ref()); + self + } + + pub fn with_video_sink(self, video_sink: &impl IsElement) -> Self { + use gstreamer::prelude::*; + self.inner + .set_property("video-sink", &video_sink.as_element().inner); + self + } + + pub fn with_text_sink(self, text_sink: &impl IsElement) -> Self { + use gstreamer::prelude::*; + self.inner + .set_property("text-sink", &text_sink.as_element().inner); + self + } + + pub fn with_audio_sink(self, audio_sink: &impl IsElement) -> Self { + use gstreamer::prelude::*; + self.inner + .set_property("audio-sink", &audio_sink.as_element().inner); + self + } + + pub fn set_volume(&self, volume: f64) { + use gstreamer::prelude::*; + self.inner.set_property("volume", volume.clamp(1.0, 100.0)) + } + + pub fn get_volume(&self) -> f64 { + use gstreamer::prelude::*; + self.inner.property::("volume") + } + + pub fn play(&self) -> Result<()> { + use gstreamer::prelude::*; + self.inner + .set_state(gstreamer::State::Playing) + .change_context(Error) + .attach("Failed to set playbin3 to Playing state")?; + Ok(()) + } + pub fn bus(&self) -> Result { + let bus = self + .inner + .bus() + .ok_or(Error) + .attach("Failed to get bus from playbin3")?; + Ok(Bus { bus }) + } +} + +#[test] +fn test_playbin3() { + use gstreamer::prelude::*; + use tracing_subscriber::prelude::*; + tracing_subscriber::registry() + .with( + tracing_subscriber::fmt::layer() + .with_thread_ids(true) + .with_file(true), + ) + .init(); + tracing::info!("Linking videoconvert to appsink"); + gstreamer::init().unwrap(); + let playbin3 = Playbin3::new("test_playbin3").unwrap().with_uri("https://jellyfin.tsuba.darksailor.dev/Items/6010382cf25273e624d305907010d773/Download?api_key=036c140222464878862231ef66a2bc9c"); + // let mut video_sink = Bin::new("wgpu_video_sink"); + // + // let video_convert = plugins::videoconvertscale::VideoConvert::new("wgpu_video_convert") + // .expect("Create videoconvert"); + // let appsink = AppSink::new("test_appsink").expect("Create appsink"); + let appsink = plugins::autodetect::AutoVideoSink::new("test_autodetect_video_sink") + .expect("Create autodetect video sink"); + // video_convert + // .link(&appsink) + // .expect("Link videoconvert to appsink"); + // + // let sink_pad = video_convert.sink_pad(); + // let sink_pad = Pad::ghost(&sink_pad).expect("Create ghost pad from videoconvert src"); + // video_sink + // .add(appsink) + // .expect("Add appsink to video sink") + // .add(video_convert) + // .expect("Add videoconvert to video sink") + // .add_pad(&sink_pad) + // .expect("Add ghost pad to video sink"); + // sink_pad.activate(true).expect("Activate ghost pad"); + + let playbin3 = playbin3.with_video_sink(&appsink); + playbin3.play().unwrap(); + let bus = playbin3.bus().unwrap(); + for msg in bus.iter_timed(None) { + tracing::info!("{:#?}", &msg.view()); + } + // std::thread::sleep(std::time::Duration::from_secs(5)); +} diff --git a/gst/src/plugins/videoconvertscale.rs b/gst/src/plugins/videoconvertscale.rs new file mode 100644 index 0000000..9fe1aff --- /dev/null +++ b/gst/src/plugins/videoconvertscale.rs @@ -0,0 +1,2 @@ +pub mod videoconvert; +pub use videoconvert::*; diff --git a/gst/src/plugins/videoconvertscale/videoconvert.rs b/gst/src/plugins/videoconvertscale/videoconvert.rs new file mode 100644 index 0000000..c754c29 --- /dev/null +++ b/gst/src/plugins/videoconvertscale/videoconvert.rs @@ -0,0 +1,49 @@ +use crate::*; +#[doc(inline)] +pub use gstreamer_video::VideoFormat; + +#[repr(transparent)] +pub struct VideoConvert { + inner: gstreamer::Element, +} + +impl IsElement for VideoConvert { + fn as_element(&self) -> &Element { + unsafe { core::mem::transmute(&self.inner) } + } + + fn into_element(self) -> Element { + Element { inner: self.inner } + } +} + +impl Sink for VideoConvert {} +impl Source for VideoConvert {} + +impl VideoConvert { + pub fn new(name: impl AsRef) -> Result { + use gstreamer::prelude::*; + let element = gstreamer::ElementFactory::make("videoconvert") + .name(name.as_ref()) + .build() + .change_context(Error) + .attach("Failed to create videoconvert element")?; + Ok(VideoConvert { inner: element }) + } + + // pub fn with_caps(mut self, caps: &gstreamer::Caps) -> Self { + // use gstreamer::prelude::*; + // self.inner.set_property("caps", caps); + // self + // } + pub fn with_output_format(self, format: VideoFormat) -> Result { + use gstreamer::prelude::*; + let caps = Caps::builder(CapsType::Video) + .field("format", format.to_str()) + .build(); + self.inner.set_property("caps", &caps.inner); + // .change_context(Error) + // .attach("Failed to set output format on videoconvert")?; + Ok(self) + } +} diff --git a/gst/src/wgpu.rs b/gst/src/wgpu.rs index e69de29..1d645d6 100644 --- a/gst/src/wgpu.rs +++ b/gst/src/wgpu.rs @@ -0,0 +1,3 @@ +// pub fn copy_sample_to_texture() { +// +// }