1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::{fmt, marker::PhantomData, mem, ptr, slice}; |
4 | |
5 | use crate::ffi; |
6 | use glib::translate::{ |
7 | from_glib, from_glib_full, from_glib_none, IntoGlib, ToGlibPtr, ToGlibPtrMut, |
8 | }; |
9 | use gst::prelude::*; |
10 | |
11 | #[doc (alias = "GstAudioInfo" )] |
12 | #[derive (Clone)] |
13 | #[repr (transparent)] |
14 | pub struct AudioInfo(ffi::GstAudioInfo); |
15 | |
16 | impl fmt::Debug for AudioInfo { |
17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
18 | f&mut DebugStruct<'_, '_>.debug_struct("AudioInfo" ) |
19 | .field("format-info" , &self.format_info()) |
20 | .field("rate" , &self.rate()) |
21 | .field("channels" , &self.channels()) |
22 | .field("positions" , &self.positions()) |
23 | .field("flags" , &self.flags()) |
24 | .field(name:"layout" , &self.layout()) |
25 | .finish() |
26 | } |
27 | } |
28 | |
29 | #[derive (Debug)] |
30 | #[must_use = "The builder must be built to be used" ] |
31 | pub struct AudioInfoBuilder<'a> { |
32 | format: crate::AudioFormat, |
33 | rate: u32, |
34 | channels: u32, |
35 | positions: Option<&'a [crate::AudioChannelPosition]>, |
36 | flags: Option<crate::AudioFlags>, |
37 | layout: Option<crate::AudioLayout>, |
38 | } |
39 | |
40 | impl<'a> AudioInfoBuilder<'a> { |
41 | #[must_use = "The built AudioInfo must be used" ] |
42 | pub fn build(self) -> Result<AudioInfo, glib::error::BoolError> { |
43 | unsafe { |
44 | let mut info = mem::MaybeUninit::uninit(); |
45 | |
46 | if let Some(p) = self.positions { |
47 | if p.len() != self.channels as usize || p.len() > 64 { |
48 | return Err(glib::bool_error!("Invalid positions length" )); |
49 | } |
50 | |
51 | let valid: bool = from_glib(ffi::gst_audio_check_valid_channel_positions( |
52 | p.as_ptr() as *mut _, |
53 | self.channels as i32, |
54 | true.into_glib(), |
55 | )); |
56 | if !valid { |
57 | return Err(glib::bool_error!("channel positions are invalid" )); |
58 | } |
59 | } |
60 | |
61 | let positions_ptr = self |
62 | .positions |
63 | .as_ref() |
64 | .map(|p| p.as_ptr()) |
65 | .unwrap_or(ptr::null()); |
66 | |
67 | ffi::gst_audio_info_set_format( |
68 | info.as_mut_ptr(), |
69 | self.format.into_glib(), |
70 | self.rate as i32, |
71 | self.channels as i32, |
72 | positions_ptr as *mut _, |
73 | ); |
74 | |
75 | let mut info = info.assume_init(); |
76 | |
77 | if info.finfo.is_null() || info.rate <= 0 || info.channels <= 0 { |
78 | return Err(glib::bool_error!("Failed to build AudioInfo" )); |
79 | } |
80 | |
81 | if let Some(flags) = self.flags { |
82 | info.flags = flags.into_glib(); |
83 | } |
84 | |
85 | if let Some(layout) = self.layout { |
86 | info.layout = layout.into_glib(); |
87 | } |
88 | |
89 | Ok(AudioInfo(info)) |
90 | } |
91 | } |
92 | |
93 | pub fn positions(self, positions: &'a [crate::AudioChannelPosition]) -> AudioInfoBuilder<'a> { |
94 | Self { |
95 | positions: Some(positions), |
96 | ..self |
97 | } |
98 | } |
99 | |
100 | pub fn positions_if( |
101 | self, |
102 | positions: &'a [crate::AudioChannelPosition], |
103 | predicate: bool, |
104 | ) -> AudioInfoBuilder<'a> { |
105 | if predicate { |
106 | self.positions(positions) |
107 | } else { |
108 | self |
109 | } |
110 | } |
111 | |
112 | pub fn positions_if_some( |
113 | self, |
114 | positions: Option<&'a [crate::AudioChannelPosition]>, |
115 | ) -> AudioInfoBuilder<'a> { |
116 | if let Some(positions) = positions { |
117 | self.positions(positions) |
118 | } else { |
119 | self |
120 | } |
121 | } |
122 | |
123 | pub fn flags(self, flags: crate::AudioFlags) -> Self { |
124 | Self { |
125 | flags: Some(flags), |
126 | ..self |
127 | } |
128 | } |
129 | |
130 | pub fn flags_if(self, flags: crate::AudioFlags, predicate: bool) -> Self { |
131 | if predicate { |
132 | self.flags(flags) |
133 | } else { |
134 | self |
135 | } |
136 | } |
137 | |
138 | pub fn flags_if_some(self, flags: Option<crate::AudioFlags>) -> Self { |
139 | if let Some(flags) = flags { |
140 | self.flags(flags) |
141 | } else { |
142 | self |
143 | } |
144 | } |
145 | |
146 | pub fn layout(self, layout: crate::AudioLayout) -> Self { |
147 | Self { |
148 | layout: Some(layout), |
149 | ..self |
150 | } |
151 | } |
152 | |
153 | pub fn layout_if(self, layout: crate::AudioLayout, predicate: bool) -> Self { |
154 | if predicate { |
155 | self.layout(layout) |
156 | } else { |
157 | self |
158 | } |
159 | } |
160 | |
161 | pub fn layout_if_some(self, layout: Option<crate::AudioLayout>) -> Self { |
162 | if let Some(layout) = layout { |
163 | self.layout(layout) |
164 | } else { |
165 | self |
166 | } |
167 | } |
168 | } |
169 | |
170 | impl AudioInfo { |
171 | pub fn builder<'a>( |
172 | format: crate::AudioFormat, |
173 | rate: u32, |
174 | channels: u32, |
175 | ) -> AudioInfoBuilder<'a> { |
176 | assert_initialized_main_thread!(); |
177 | |
178 | AudioInfoBuilder { |
179 | format, |
180 | rate, |
181 | channels, |
182 | positions: None, |
183 | flags: None, |
184 | layout: None, |
185 | } |
186 | } |
187 | |
188 | #[inline ] |
189 | pub fn is_valid(&self) -> bool { |
190 | !self.0.finfo.is_null() && self.0.channels > 0 && self.0.rate > 0 && self.0.bpf > 0 |
191 | } |
192 | |
193 | #[doc (alias = "gst_audio_info_from_caps" )] |
194 | pub fn from_caps(caps: &gst::CapsRef) -> Result<Self, glib::error::BoolError> { |
195 | skip_assert_initialized!(); |
196 | |
197 | unsafe { |
198 | let mut info = mem::MaybeUninit::uninit(); |
199 | if from_glib(ffi::gst_audio_info_from_caps( |
200 | info.as_mut_ptr(), |
201 | caps.as_ptr(), |
202 | )) { |
203 | Ok(Self(info.assume_init())) |
204 | } else { |
205 | Err(glib::bool_error!("Failed to create AudioInfo from caps" )) |
206 | } |
207 | } |
208 | } |
209 | |
210 | #[doc (alias = "gst_audio_info_to_caps" )] |
211 | pub fn to_caps(&self) -> Result<gst::Caps, glib::error::BoolError> { |
212 | unsafe { |
213 | let result = from_glib_full(ffi::gst_audio_info_to_caps(&self.0)); |
214 | match result { |
215 | Some(c) => Ok(c), |
216 | None => Err(glib::bool_error!("Failed to create caps from AudioInfo" )), |
217 | } |
218 | } |
219 | } |
220 | |
221 | #[doc (alias = "gst_audio_info_convert" )] |
222 | pub fn convert<U: gst::format::SpecificFormattedValueFullRange>( |
223 | &self, |
224 | src_val: impl gst::format::FormattedValue, |
225 | ) -> Option<U> { |
226 | assert_initialized_main_thread!(); |
227 | unsafe { |
228 | let mut dest_val = mem::MaybeUninit::uninit(); |
229 | if from_glib(ffi::gst_audio_info_convert( |
230 | &self.0, |
231 | src_val.format().into_glib(), |
232 | src_val.into_raw_value(), |
233 | U::default_format().into_glib(), |
234 | dest_val.as_mut_ptr(), |
235 | )) { |
236 | Some(U::from_raw(U::default_format(), dest_val.assume_init())) |
237 | } else { |
238 | None |
239 | } |
240 | } |
241 | } |
242 | |
243 | pub fn convert_generic( |
244 | &self, |
245 | src_val: impl gst::format::FormattedValue, |
246 | dest_fmt: gst::Format, |
247 | ) -> Option<gst::GenericFormattedValue> { |
248 | assert_initialized_main_thread!(); |
249 | unsafe { |
250 | let mut dest_val = mem::MaybeUninit::uninit(); |
251 | if from_glib(ffi::gst_audio_info_convert( |
252 | &self.0, |
253 | src_val.format().into_glib(), |
254 | src_val.into_raw_value(), |
255 | dest_fmt.into_glib(), |
256 | dest_val.as_mut_ptr(), |
257 | )) { |
258 | Some(gst::GenericFormattedValue::new( |
259 | dest_fmt, |
260 | dest_val.assume_init(), |
261 | )) |
262 | } else { |
263 | None |
264 | } |
265 | } |
266 | } |
267 | |
268 | #[inline ] |
269 | pub fn format(&self) -> crate::AudioFormat { |
270 | if self.0.finfo.is_null() { |
271 | return crate::AudioFormat::Unknown; |
272 | } |
273 | |
274 | unsafe { from_glib((*self.0.finfo).format) } |
275 | } |
276 | |
277 | #[inline ] |
278 | pub fn format_info(&self) -> crate::AudioFormatInfo { |
279 | crate::AudioFormatInfo::from_format(self.format()) |
280 | } |
281 | |
282 | #[inline ] |
283 | pub fn layout(&self) -> crate::AudioLayout { |
284 | unsafe { from_glib(self.0.layout) } |
285 | } |
286 | |
287 | #[inline ] |
288 | pub fn flags(&self) -> crate::AudioFlags { |
289 | unsafe { from_glib(self.0.flags) } |
290 | } |
291 | |
292 | #[inline ] |
293 | pub fn rate(&self) -> u32 { |
294 | self.0.rate as u32 |
295 | } |
296 | |
297 | #[inline ] |
298 | pub fn channels(&self) -> u32 { |
299 | self.0.channels as u32 |
300 | } |
301 | |
302 | #[inline ] |
303 | pub fn bpf(&self) -> u32 { |
304 | self.0.bpf as u32 |
305 | } |
306 | |
307 | #[inline ] |
308 | pub fn bps(&self) -> u32 { |
309 | self.format_info().depth() >> 3 |
310 | } |
311 | |
312 | #[inline ] |
313 | pub fn depth(&self) -> u32 { |
314 | self.format_info().depth() |
315 | } |
316 | |
317 | #[inline ] |
318 | pub fn width(&self) -> u32 { |
319 | self.format_info().width() |
320 | } |
321 | |
322 | #[inline ] |
323 | pub fn endianness(&self) -> crate::AudioEndianness { |
324 | self.format_info().endianness() |
325 | } |
326 | |
327 | #[inline ] |
328 | pub fn is_big_endian(&self) -> bool { |
329 | self.format_info().is_big_endian() |
330 | } |
331 | |
332 | #[inline ] |
333 | pub fn is_little_endian(&self) -> bool { |
334 | self.format_info().is_little_endian() |
335 | } |
336 | |
337 | #[inline ] |
338 | pub fn is_float(&self) -> bool { |
339 | self.format_info().is_float() |
340 | } |
341 | |
342 | #[inline ] |
343 | pub fn is_integer(&self) -> bool { |
344 | self.format_info().is_integer() |
345 | } |
346 | |
347 | #[inline ] |
348 | pub fn is_signed(&self) -> bool { |
349 | self.format_info().is_signed() |
350 | } |
351 | |
352 | #[inline ] |
353 | pub fn positions(&self) -> Option<&[crate::AudioChannelPosition]> { |
354 | if self.0.channels > 64 || self.is_unpositioned() { |
355 | return None; |
356 | } |
357 | |
358 | Some(unsafe { |
359 | slice::from_raw_parts( |
360 | &self.0.position as *const i32 as *const crate::AudioChannelPosition, |
361 | self.0.channels as usize, |
362 | ) |
363 | }) |
364 | } |
365 | |
366 | #[inline ] |
367 | pub fn is_unpositioned(&self) -> bool { |
368 | self.flags().contains(crate::AudioFlags::UNPOSITIONED) |
369 | } |
370 | } |
371 | |
372 | impl PartialEq for AudioInfo { |
373 | #[doc (alias = "gst_audio_info_is_equal" )] |
374 | #[inline ] |
375 | fn eq(&self, other: &Self) -> bool { |
376 | unsafe { from_glib(val:ffi::gst_audio_info_is_equal(&self.0, &other.0)) } |
377 | } |
378 | } |
379 | |
380 | impl Eq for AudioInfo {} |
381 | |
382 | unsafe impl Send for AudioInfo {} |
383 | unsafe impl Sync for AudioInfo {} |
384 | |
385 | impl glib::types::StaticType for AudioInfo { |
386 | #[inline ] |
387 | fn static_type() -> glib::types::Type { |
388 | unsafe { glib::translate::from_glib(val:ffi::gst_audio_info_get_type()) } |
389 | } |
390 | } |
391 | |
392 | impl glib::value::ValueType for AudioInfo { |
393 | type Type = Self; |
394 | } |
395 | |
396 | #[doc (hidden)] |
397 | unsafe impl<'a> glib::value::FromValue<'a> for AudioInfo { |
398 | type Checker = glib::value::GenericValueTypeOrNoneChecker<Self>; |
399 | |
400 | unsafe fn from_value(value: &'a glib::Value) -> Self { |
401 | skip_assert_initialized!(); |
402 | from_glib_none( |
403 | ptr:glib::gobject_ffi::g_value_get_boxed(value.to_glib_none().0) as *mut ffi::GstAudioInfo |
404 | ) |
405 | } |
406 | } |
407 | |
408 | #[doc (hidden)] |
409 | impl glib::value::ToValue for AudioInfo { |
410 | fn to_value(&self) -> glib::Value { |
411 | let mut value: Value = glib::Value::for_value_type::<Self>(); |
412 | unsafe { |
413 | glib::gobject_ffi::g_value_set_boxed( |
414 | value.to_glib_none_mut().0, |
415 | self.to_glib_none().0 as *mut _, |
416 | ) |
417 | } |
418 | value |
419 | } |
420 | |
421 | fn value_type(&self) -> glib::Type { |
422 | Self::static_type() |
423 | } |
424 | } |
425 | |
426 | #[doc (hidden)] |
427 | impl From<AudioInfo> for glib::Value { |
428 | fn from(v: AudioInfo) -> glib::Value { |
429 | skip_assert_initialized!(); |
430 | glib::value::ToValue::to_value(&v) |
431 | } |
432 | } |
433 | |
434 | #[doc (hidden)] |
435 | impl glib::value::ToValueOptional for AudioInfo { |
436 | fn to_value_optional(s: Option<&Self>) -> glib::Value { |
437 | skip_assert_initialized!(); |
438 | let mut value: Value = glib::Value::for_value_type::<Self>(); |
439 | unsafe { |
440 | glib::gobject_ffi::g_value_set_boxed( |
441 | value.to_glib_none_mut().0, |
442 | v_boxed:s.to_glib_none().0 as *mut _, |
443 | ) |
444 | } |
445 | value |
446 | } |
447 | } |
448 | |
449 | #[doc (hidden)] |
450 | impl glib::translate::Uninitialized for AudioInfo { |
451 | #[inline ] |
452 | unsafe fn uninitialized() -> Self { |
453 | mem::zeroed() |
454 | } |
455 | } |
456 | |
457 | #[doc (hidden)] |
458 | impl glib::translate::GlibPtrDefault for AudioInfo { |
459 | type GlibType = *mut ffi::GstAudioInfo; |
460 | } |
461 | |
462 | #[doc (hidden)] |
463 | impl<'a> glib::translate::ToGlibPtr<'a, *const ffi::GstAudioInfo> for AudioInfo { |
464 | type Storage = PhantomData<&'a Self>; |
465 | |
466 | #[inline ] |
467 | fn to_glib_none(&'a self) -> glib::translate::Stash<'a, *const ffi::GstAudioInfo, Self> { |
468 | glib::translate::Stash(&self.0, PhantomData) |
469 | } |
470 | |
471 | fn to_glib_full(&self) -> *const ffi::GstAudioInfo { |
472 | unimplemented!() |
473 | } |
474 | } |
475 | |
476 | #[doc (hidden)] |
477 | impl glib::translate::FromGlibPtrNone<*const ffi::GstAudioInfo> for AudioInfo { |
478 | #[inline ] |
479 | unsafe fn from_glib_none(ptr: *const ffi::GstAudioInfo) -> Self { |
480 | Self(ptr::read(src:ptr)) |
481 | } |
482 | } |
483 | |
484 | #[doc (hidden)] |
485 | impl glib::translate::FromGlibPtrNone<*mut ffi::GstAudioInfo> for AudioInfo { |
486 | #[inline ] |
487 | unsafe fn from_glib_none(ptr: *mut ffi::GstAudioInfo) -> Self { |
488 | Self(ptr::read(src:ptr)) |
489 | } |
490 | } |
491 | |
492 | #[doc (hidden)] |
493 | impl glib::translate::FromGlibPtrFull<*mut ffi::GstAudioInfo> for AudioInfo { |
494 | #[inline ] |
495 | unsafe fn from_glib_full(ptr: *mut ffi::GstAudioInfo) -> Self { |
496 | let info: AudioInfo = from_glib_none(ptr); |
497 | glib::ffi::g_free(mem:ptr as *mut _); |
498 | info |
499 | } |
500 | } |
501 | |
502 | #[cfg (test)] |
503 | mod tests { |
504 | use super::*; |
505 | |
506 | #[test ] |
507 | fn test_new() { |
508 | gst::init().unwrap(); |
509 | |
510 | let info = AudioInfo::builder(crate::AudioFormat::S16le, 48000, 2) |
511 | .build() |
512 | .unwrap(); |
513 | assert_eq!(info.format(), crate::AudioFormat::S16le); |
514 | assert_eq!(info.rate(), 48000); |
515 | assert_eq!(info.channels(), 2); |
516 | assert_eq!( |
517 | &info.positions().unwrap(), |
518 | &[ |
519 | crate::AudioChannelPosition::FrontLeft, |
520 | crate::AudioChannelPosition::FrontRight, |
521 | ] |
522 | ); |
523 | |
524 | let positions = [ |
525 | crate::AudioChannelPosition::RearLeft, |
526 | crate::AudioChannelPosition::RearRight, |
527 | ]; |
528 | let info = AudioInfo::builder(crate::AudioFormat::S16le, 48000, 2) |
529 | .positions(&positions) |
530 | .build() |
531 | .unwrap(); |
532 | assert_eq!(info.format(), crate::AudioFormat::S16le); |
533 | assert_eq!(info.rate(), 48000); |
534 | assert_eq!(info.channels(), 2); |
535 | assert_eq!( |
536 | &info.positions().unwrap(), |
537 | &[ |
538 | crate::AudioChannelPosition::RearLeft, |
539 | crate::AudioChannelPosition::RearRight, |
540 | ] |
541 | ); |
542 | } |
543 | |
544 | #[test ] |
545 | fn test_from_to_caps() { |
546 | gst::init().unwrap(); |
547 | |
548 | let caps = crate::AudioCapsBuilder::new_interleaved() |
549 | .format(crate::AudioFormat::S16le) |
550 | .rate(48000) |
551 | .channels(2) |
552 | .fallback_channel_mask() |
553 | .build(); |
554 | let info = AudioInfo::from_caps(&caps).unwrap(); |
555 | assert_eq!(info.format(), crate::AudioFormat::S16le); |
556 | assert_eq!(info.rate(), 48000); |
557 | assert_eq!(info.channels(), 2); |
558 | assert_eq!( |
559 | &info.positions().unwrap(), |
560 | &[ |
561 | crate::AudioChannelPosition::FrontLeft, |
562 | crate::AudioChannelPosition::FrontRight, |
563 | ] |
564 | ); |
565 | |
566 | let caps2 = info.to_caps().unwrap(); |
567 | assert_eq!(caps, caps2); |
568 | |
569 | let info2 = AudioInfo::from_caps(&caps2).unwrap(); |
570 | assert!(info == info2); |
571 | } |
572 | } |
573 | |