1use 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 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}