diff --git a/gstreamer-video/src/caps.rs b/gstreamer-video/src/caps.rs new file mode 100644 index 000000000..3066f9b2f --- /dev/null +++ b/gstreamer-video/src/caps.rs @@ -0,0 +1,258 @@ +use crate::VideoFormat; +use gst::prelude::*; +use gst::Caps; +use std::ops::Bound::*; +use std::ops::RangeBounds; + +pub struct VideoCapsBuilder { + builder: gst::caps::Builder, +} + +impl VideoCapsBuilder { + pub fn new() -> Self { + let builder = Caps::builder("video/x-raw"); + VideoCapsBuilder { builder } + } + + pub fn any_features(self) -> VideoCapsBuilder { + VideoCapsBuilder { + builder: self.builder.any_features(), + } + } + + pub fn features(self, features: &[&str]) -> VideoCapsBuilder { + VideoCapsBuilder { + builder: self.builder.features(features), + } + } +} + +impl Default for VideoCapsBuilder { + fn default() -> Self { + Self::new() + } +} + +impl VideoCapsBuilder { + pub fn format(self, format: VideoFormat) -> Self { + Self { + builder: self.builder.field("format", format.to_str()), + } + } + + pub fn format_list(self, formats: impl IntoIterator) -> Self { + Self { + builder: self.builder.field( + "format", + gst::List::new(formats.into_iter().map(|f| f.to_str())), + ), + } + } + + pub fn width(self, width: i32) -> Self { + Self { + builder: self.builder.field("width", width), + } + } + + pub fn width_range(self, widths: impl RangeBounds) -> Self { + let (start, end) = range_bounds_i32_start_end(widths); + let gst_widths: gst::IntRange = gst::IntRange::new(start, end); + Self { + builder: self.builder.field("width", gst_widths), + } + } + + pub fn width_list(self, widths: impl IntoIterator) -> Self { + Self { + builder: self.builder.field("width", gst::List::new(widths)), + } + } + + pub fn height(self, height: i32) -> Self { + Self { + builder: self.builder.field("height", height), + } + } + + pub fn height_range(self, heights: impl RangeBounds) -> Self { + let (start, end) = range_bounds_i32_start_end(heights); + let gst_heights: gst::IntRange = gst::IntRange::new(start, end); + Self { + builder: self.builder.field("height", gst_heights), + } + } + + pub fn height_list(self, heights: impl IntoIterator) -> Self { + Self { + builder: self.builder.field("height", gst::List::new(heights)), + } + } + + pub fn framerate(self, framerate: gst::Fraction) -> Self { + Self { + builder: self.builder.field("framerate", framerate), + } + } + + pub fn framerate_range(self, framerates: impl RangeBounds) -> Self { + let start = match framerates.start_bound() { + Unbounded => gst::Fraction::new(0, 1), + Excluded(n) => next_fraction(*n), + Included(n) => { + assert!(n.numer() >= 0); + *n + } + }; + let end = match framerates.end_bound() { + Unbounded => gst::Fraction::new(i32::MAX, 1), + Excluded(n) => previous_fraction(*n), + Included(n) => { + assert!(n.numer() >= 0); + *n + } + }; + assert!(start <= end); + let framerates: gst::FractionRange = gst::FractionRange::new(start, end); + Self { + builder: self.builder.field("framerate", framerates), + } + } + + pub fn framerate_list(self, framerates: impl IntoIterator) -> Self { + Self { + builder: self.builder.field("framerate", gst::List::new(framerates)), + } + } + + pub fn pixel_aspect_ratio(self, pixel_aspect_ratio: gst::Fraction) -> Self { + Self { + builder: self.builder.field("pixel-aspect-ratio", pixel_aspect_ratio), + } + } + + pub fn pixel_aspect_ratio_range( + self, + pixel_aspect_ratios: impl RangeBounds, + ) -> Self { + let start = match pixel_aspect_ratios.start_bound() { + Unbounded => gst::Fraction::new(1, i32::MAX), + Excluded(n) => next_fraction(*n), + Included(n) => { + assert!(n.numer() >= 0); + *n + } + }; + let end = match pixel_aspect_ratios.end_bound() { + Unbounded => gst::Fraction::new(i32::MAX, 1), + Excluded(n) => previous_fraction(*n), + Included(n) => { + assert!(n.numer() >= 0); + *n + } + }; + assert!(start <= end); + let pixel_aspect_ratios: gst::FractionRange = gst::FractionRange::new(start, end); + Self { + builder: self + .builder + .field("pixel-aspect-ratio", pixel_aspect_ratios), + } + } + + pub fn pixel_aspect_ratio_list( + self, + pixel_aspect_ratios: impl IntoIterator, + ) -> Self { + Self { + builder: self + .builder + .field("pixel-aspect-ratio", gst::List::new(pixel_aspect_ratios)), + } + } + + pub fn field(self, name: &str, value: V) -> Self { + Self { + builder: self.builder.field(name, value), + } + } + + pub fn build(self) -> gst::Caps { + self.builder.build() + } +} + +fn range_bounds_i32_start_end(range: impl RangeBounds) -> (i32, i32) { + skip_assert_initialized!(); + let start = match range.start_bound() { + Unbounded => 1, + Excluded(n) => n + 1, + Included(n) => *n, + }; + let end = match range.end_bound() { + Unbounded => i32::MAX, + Excluded(n) => n - 1, + Included(n) => *n, + }; + (start, end) +} + +// https://math.stackexchange.com/questions/39582/how-to-compute-next-previous-representable-rational-number/3798608#3798608 + +/* Extended Euclidean Algorithm: computes (g, x, y), + * such that a*x + b*y = g = gcd(a, b) >= 0. */ +fn xgcd(mut a: i64, mut b: i64) -> (i64, i64, i64) { + skip_assert_initialized!(); + let mut x0 = 0i64; + let mut x1 = 1i64; + let mut y0 = 1i64; + let mut y1 = 0i64; + while a != 0 { + let q; + (q, a, b) = (b / a, b % a, a); + (y0, y1) = (y1, y0 - q * y1); + (x0, x1) = (x1, x0 - q * x1); + } + if b >= 0 { + (b, x0, y0) + } else { + (-b, -x0, -y0) + } +} + +/* Computes the neighbours of p/q in the Farey sequence of order n. */ +fn farey_neighbours(p: i32, q: i32) -> (i32, i32, i32, i32) { + skip_assert_initialized!(); + let n = i32::MAX as i64; + assert!(q != 0); + let mut p = p as i64; + let mut q = q as i64; + if q < 0 { + p = -p; + q = -q; + } + let (g, r, _) = xgcd(p, q); + p /= g; + q /= g; + let b = ((n - r) / q) * q + r; + let a = (b * p - 1) / q; + let d = ((n + r) / q) * q - r; + let c = (d * p + 1) / q; + (a as i32, b as i32, c as i32, d as i32) +} + +fn previous_fraction(fraction: gst::Fraction) -> gst::Fraction { + skip_assert_initialized!(); + let num = fraction.numer(); + let den = fraction.denom(); + let (new_num, new_den, _, _) = farey_neighbours(num, den); + gst::Fraction::new(new_num, new_den) +} + +fn next_fraction(fraction: gst::Fraction) -> gst::Fraction { + skip_assert_initialized!(); + let num = fraction.numer(); + let den = fraction.denom(); + let (_, _, new_num, new_den) = farey_neighbours(num, den); + gst::Fraction::new(new_num, new_den) +} diff --git a/gstreamer-video/src/lib.rs b/gstreamer-video/src/lib.rs index 63cbdae22..a943ecb23 100644 --- a/gstreamer-video/src/lib.rs +++ b/gstreamer-video/src/lib.rs @@ -41,6 +41,9 @@ mod flag_serde; mod navigation; +mod caps; +pub use crate::caps::VideoCapsBuilder; + mod caps_features; #[cfg(any(feature = "v1_16", feature = "dox"))] #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))]