Skip to main content

gstreamer_video/
functions.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{mem, ptr};
4
5use crate::ffi;
6use glib::translate::{IntoGlib, ToGlibPtr, from_glib, from_glib_full};
7
8#[doc(alias = "gst_video_convert_sample")]
9pub fn convert_sample(
10    sample: &gst::Sample,
11    caps: &gst::Caps,
12    timeout: Option<gst::ClockTime>,
13) -> Result<gst::Sample, glib::Error> {
14    skip_assert_initialized!();
15    unsafe {
16        let mut error = ptr::null_mut();
17        let ret = ffi::gst_video_convert_sample(
18            sample.to_glib_none().0,
19            caps.to_glib_none().0,
20            timeout.into_glib(),
21            &mut error,
22        );
23
24        if error.is_null() {
25            Ok(from_glib_full(ret))
26        } else {
27            Err(from_glib_full(error))
28        }
29    }
30}
31
32pub fn convert_sample_async<F>(
33    sample: &gst::Sample,
34    caps: &gst::Caps,
35    timeout: Option<gst::ClockTime>,
36    func: F,
37) where
38    F: FnOnce(Result<gst::Sample, glib::Error>) + Send + 'static,
39{
40    skip_assert_initialized!();
41    unsafe { convert_sample_async_unsafe(sample, caps, timeout, func) }
42}
43
44pub fn convert_sample_async_local<F>(
45    sample: &gst::Sample,
46    caps: &gst::Caps,
47    timeout: Option<gst::ClockTime>,
48    func: F,
49) where
50    F: FnOnce(Result<gst::Sample, glib::Error>) + 'static,
51{
52    skip_assert_initialized!();
53    unsafe {
54        let ctx = glib::MainContext::ref_thread_default();
55        let _acquire = ctx
56            .acquire()
57            .expect("thread default main context already acquired by another thread");
58
59        let func = glib::thread_guard::ThreadGuard::new(func);
60
61        convert_sample_async_unsafe(sample, caps, timeout, move |res| (func.into_inner())(res))
62    }
63}
64
65unsafe fn convert_sample_async_unsafe<F>(
66    sample: &gst::Sample,
67    caps: &gst::Caps,
68    timeout: Option<gst::ClockTime>,
69    func: F,
70) where
71    F: FnOnce(Result<gst::Sample, glib::Error>) + 'static,
72{
73    unsafe {
74        unsafe extern "C" fn convert_sample_async_trampoline<F>(
75            sample: *mut gst::ffi::GstSample,
76            error: *mut glib::ffi::GError,
77            user_data: glib::ffi::gpointer,
78        ) where
79            F: FnOnce(Result<gst::Sample, glib::Error>) + 'static,
80        {
81            unsafe {
82                let callback: &mut Option<F> = &mut *(user_data as *mut Option<F>);
83                let callback = callback.take().unwrap();
84
85                if error.is_null() {
86                    callback(Ok(from_glib_full(sample)))
87                } else {
88                    callback(Err(from_glib_full(error)))
89                }
90            }
91        }
92        unsafe extern "C" fn convert_sample_async_free<F>(user_data: glib::ffi::gpointer)
93        where
94            F: FnOnce(Result<gst::Sample, glib::Error>) + 'static,
95        {
96            unsafe {
97                let _: Box<Option<F>> = Box::from_raw(user_data as *mut _);
98            }
99        }
100
101        let user_data: Box<Option<F>> = Box::new(Some(func));
102
103        ffi::gst_video_convert_sample_async(
104            sample.to_glib_none().0,
105            caps.to_glib_none().0,
106            timeout.into_glib(),
107            Some(convert_sample_async_trampoline::<F>),
108            Box::into_raw(user_data) as glib::ffi::gpointer,
109            Some(convert_sample_async_free::<F>),
110        );
111    }
112}
113
114#[cfg(feature = "futures")]
115pub fn convert_sample_future(
116    sample: &gst::Sample,
117    caps: &gst::Caps,
118    timeout: Option<gst::ClockTime>,
119) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<gst::Sample, glib::Error>> + 'static>>
120{
121    skip_assert_initialized!();
122
123    use futures_channel::oneshot;
124
125    let (sender, receiver) = oneshot::channel();
126
127    let sample = sample.clone();
128    let caps = caps.clone();
129    let future = async move {
130        assert!(
131            glib::MainContext::ref_thread_default().is_owner(),
132            "Spawning futures only allowed if the thread is owning the MainContext"
133        );
134
135        convert_sample_async(&sample, &caps, timeout, move |res| {
136            let _ = sender.send(res);
137        });
138
139        receiver
140            .await
141            .expect("Sender dropped before callback was called")
142    };
143
144    Box::pin(future)
145}
146
147#[doc(alias = "gst_video_calculate_display_ratio")]
148pub fn calculate_display_ratio(
149    video_width: u32,
150    video_height: u32,
151    video_par: gst::Fraction,
152    display_par: gst::Fraction,
153) -> Option<gst::Fraction> {
154    skip_assert_initialized!();
155
156    unsafe {
157        let mut dar_n = mem::MaybeUninit::uninit();
158        let mut dar_d = mem::MaybeUninit::uninit();
159
160        let res: bool = from_glib(ffi::gst_video_calculate_display_ratio(
161            dar_n.as_mut_ptr(),
162            dar_d.as_mut_ptr(),
163            video_width,
164            video_height,
165            video_par.numer() as u32,
166            video_par.denom() as u32,
167            display_par.numer() as u32,
168            display_par.denom() as u32,
169        ));
170        if res {
171            Some(gst::Fraction::new(
172                dar_n.assume_init() as i32,
173                dar_d.assume_init() as i32,
174            ))
175        } else {
176            None
177        }
178    }
179}
180
181#[doc(alias = "gst_video_guess_framerate")]
182pub fn guess_framerate(duration: gst::ClockTime) -> Option<gst::Fraction> {
183    skip_assert_initialized!();
184
185    unsafe {
186        let mut dest_n = mem::MaybeUninit::uninit();
187        let mut dest_d = mem::MaybeUninit::uninit();
188        let res: bool = from_glib(ffi::gst_video_guess_framerate(
189            duration.into_glib(),
190            dest_n.as_mut_ptr(),
191            dest_d.as_mut_ptr(),
192        ));
193        if res {
194            Some(gst::Fraction::new(
195                dest_n.assume_init(),
196                dest_d.assume_init(),
197            ))
198        } else {
199            None
200        }
201    }
202}
203
204#[cfg(feature = "v1_22")]
205#[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
206#[doc(alias = "gst_video_is_common_aspect_ratio")]
207pub fn is_common_aspect_ratio(width: u32, height: u32, par: gst::Fraction) -> bool {
208    skip_assert_initialized!();
209
210    unsafe {
211        from_glib(ffi::gst_video_is_common_aspect_ratio(
212            width as i32,
213            height as i32,
214            par.numer(),
215            par.denom(),
216        ))
217    }
218}
219
220pub fn video_make_raw_caps(
221    formats: &[crate::VideoFormat],
222) -> crate::VideoCapsBuilder<gst::caps::NoFeature> {
223    skip_assert_initialized!();
224
225    let formats = formats.iter().copied().map(|f| match f {
226        crate::VideoFormat::Encoded => panic!("Invalid encoded format"),
227        crate::VideoFormat::Unknown => panic!("Invalid unknown format"),
228        _ => f,
229    });
230
231    crate::VideoCapsBuilder::new().format_list(formats)
232}
233
234#[cfg(test)]
235mod tests {
236    use std::sync::{Arc, Mutex};
237
238    use super::*;
239
240    #[test]
241    fn test_convert_sample_async() {
242        gst::init().unwrap();
243
244        let l = glib::MainLoop::new(None, false);
245
246        let mut in_buffer = gst::Buffer::with_size(320 * 240 * 4).unwrap();
247        {
248            let buffer = in_buffer.get_mut().unwrap();
249            let mut data = buffer.map_writable().unwrap();
250
251            for p in data.as_mut_slice().chunks_mut(4) {
252                p[0] = 63;
253                p[1] = 127;
254                p[2] = 191;
255                p[3] = 255;
256            }
257        }
258        let in_caps = crate::VideoInfo::builder(crate::VideoFormat::Rgba, 320, 240)
259            .build()
260            .unwrap()
261            .to_caps()
262            .unwrap();
263        let sample = gst::Sample::builder()
264            .buffer(&in_buffer)
265            .caps(&in_caps)
266            .build();
267
268        let out_caps = crate::VideoInfo::builder(crate::VideoFormat::Abgr, 320, 240)
269            .build()
270            .unwrap()
271            .to_caps()
272            .unwrap();
273
274        let l_clone = l.clone();
275        let res_store = Arc::new(Mutex::new(None));
276        let res_store_clone = res_store.clone();
277        convert_sample_async(&sample, &out_caps, gst::ClockTime::NONE, move |res| {
278            *res_store_clone.lock().unwrap() = Some(res);
279            l_clone.quit();
280        });
281
282        l.run();
283
284        let res = res_store.lock().unwrap().take().unwrap();
285        let res = res.unwrap();
286
287        let converted_out_caps = res.caps().unwrap();
288        assert_eq!(out_caps.as_ref(), converted_out_caps);
289        let out_buffer = res.buffer().unwrap();
290        {
291            let data = out_buffer.map_readable().unwrap();
292
293            for p in data.as_slice().chunks(4) {
294                assert_eq!(p, &[255, 191, 127, 63]);
295            }
296        }
297    }
298
299    #[test]
300    fn video_caps() {
301        gst::init().unwrap();
302
303        let caps =
304            video_make_raw_caps(&[crate::VideoFormat::Nv12, crate::VideoFormat::Nv16]).build();
305        assert_eq!(
306            caps.to_string(),
307            "video/x-raw, format=(string){ NV12, NV16 }, width=(int)[ 1, 2147483647 ], height=(int)[ 1, 2147483647 ], framerate=(fraction)[ 0/1, 2147483647/1 ]"
308        );
309
310        #[cfg(feature = "v1_18")]
311        {
312            /* video_make_raw_caps() is a re-implementation so ensure it returns the same caps as the C API */
313            let c_caps = unsafe {
314                let formats: Vec<ffi::GstVideoFormat> =
315                    [crate::VideoFormat::Nv12, crate::VideoFormat::Nv16]
316                        .iter()
317                        .map(|f| f.into_glib())
318                        .collect();
319                let caps = ffi::gst_video_make_raw_caps(formats.as_ptr(), formats.len() as u32);
320                gst::Caps::from_glib_full(caps)
321            };
322            assert_eq!(caps, c_caps);
323        }
324
325        let caps = video_make_raw_caps(&[crate::VideoFormat::Nv12, crate::VideoFormat::Nv16])
326            .width(800)
327            .height(600)
328            .framerate((30, 1).into())
329            .build();
330        assert_eq!(
331            caps.to_string(),
332            "video/x-raw, format=(string){ NV12, NV16 }, width=(int)800, height=(int)600, framerate=(fraction)30/1"
333        );
334    }
335
336    #[test]
337    #[should_panic(expected = "Invalid encoded format")]
338    fn video_caps_encoded() {
339        gst::init().unwrap();
340        let _caps = video_make_raw_caps(&[crate::VideoFormat::Encoded]);
341    }
342
343    #[test]
344    #[should_panic(expected = "Invalid unknown format")]
345    fn video_caps_unknown() {
346        gst::init().unwrap();
347        let _caps = video_make_raw_caps(&[crate::VideoFormat::Unknown]);
348    }
349}