1use std::borrow::Borrow;
2use std::borrow::Cow;
3use std::ffi::CString;
4use std::mem::{align_of, size_of};
5
6use crate::ffi::*;
7#[cfg(feature = "ffmpeg_7_0")]
8use crate::Error;
9use libc::{c_int, c_uint};
10
11use super::Channel;
12use super::ChannelCustom;
13use super::ChannelLayoutIter;
14use super::ChannelLayoutMask;
15use super::ChannelOrder;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct ChannelLayout<'a>(Cow<'a, AVChannelLayout>);
19
20impl<'a> ChannelLayout<'a> {
21 pub fn unspecified(channels: u32) -> Self {
23 let mut layout = AVChannelLayout::empty();
24 layout.order = AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC;
25 layout.nb_channels = channels as c_int;
26
27 Self(Cow::Owned(layout))
28 }
29
30 pub fn custom(channels: Vec<ChannelCustom>) -> Self {
31 #[cold]
32 fn alloc_failed(channels: usize) -> ! {
33 use std::alloc::{handle_alloc_error, Layout};
34
35 let alloc_size = channels * size_of::<AVChannelCustom>();
36 let layout =
37 Layout::from_size_align(alloc_size, align_of::<AVChannelCustom>()).unwrap();
38 handle_alloc_error(layout)
39 }
40
41 let mut layout = AVChannelLayout::empty();
42 layout.order = AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM;
43 layout.nb_channels = channels.len() as c_int;
44 unsafe {
45 layout.u.map = av_malloc_array(channels.len(), size_of::<AVChannelCustom>()) as _;
46 if layout.u.map.is_null() {
47 alloc_failed(channels.len());
48 }
49
50 for (i, ch) in channels.into_iter().enumerate() {
51 std::ptr::write(layout.u.map.add(i), AVChannelCustom::from(ch));
52 }
53 }
54
55 Self(Cow::Owned(layout))
56 }
57
58 pub fn default_for_channels(channels: u32) -> Self {
63 let mut layout = AVChannelLayout::empty();
64
65 unsafe {
66 av_channel_layout_default(&mut layout as _, channels as c_int);
67 }
68
69 Self(Cow::Owned(layout))
70 }
71
72 pub fn standard_layouts() -> ChannelLayoutIter {
74 ChannelLayoutIter::new()
75 }
76
77 pub fn from_mask(layout_mask: ChannelLayoutMask) -> Option<Self> {
82 let mut layout = AVChannelLayout::empty();
83 let ret = unsafe { av_channel_layout_from_mask(&mut layout as _, layout_mask.bits()) };
84
85 match ret {
86 0 => Some(Self(Cow::Owned(layout))),
87 _ => None,
89 }
90 }
91
92 pub fn from_string<S: AsRef<str>>(description: S) -> Option<Self> {
103 let mut layout = AVChannelLayout::empty();
104 let cstr = CString::new(description.as_ref()).expect("no nul byte in description");
105 let ret = unsafe { av_channel_layout_from_string(&mut layout as _, cstr.as_ptr()) };
106
107 match ret {
108 0 => Some(Self(Cow::Owned(layout))),
109 _ => None,
111 }
112 }
113
114 pub fn order(&self) -> ChannelOrder {
116 ChannelOrder::from(self.0.order)
117 }
118
119 pub fn channels(&self) -> u32 {
121 self.0.nb_channels as u32
122 }
123
124 pub fn mask(&self) -> Option<ChannelLayoutMask> {
132 match self.order() {
133 ChannelOrder::Unspecified | ChannelOrder::Custom => None,
134 ChannelOrder::Native | ChannelOrder::Ambisonic => unsafe {
135 Some(ChannelLayoutMask::from_bits_truncate(self.0.u.mask))
136 },
137 }
138 }
139
140 pub fn map(&self) -> Option<&[ChannelCustom]> {
144 if self.order() != ChannelOrder::Custom {
145 return None;
146 }
147
148 unsafe {
149 Some(std::slice::from_raw_parts(
151 self.0.u.map as _,
152 self.0.nb_channels as usize,
153 ))
154 }
155 }
156
157 pub fn into_owned(self) -> AVChannelLayout {
161 self.0.into_owned()
162 }
163
164 pub fn as_ptr(&self) -> *const AVChannelLayout {
168 self.0.as_ref() as _
169 }
170
171 pub fn description(&self) -> String {
176 let mut buf = vec![0u8; 256];
177
178 unsafe {
179 let ret_val =
180 av_channel_layout_describe(self.as_ptr(), buf.as_mut_ptr() as _, buf.len());
181
182 match usize::try_from(ret_val) {
183 Ok(out_len) if out_len > 0 => {
184 #[cfg(feature = "ffmpeg_6_1")]
185 let out_len = out_len - 1;
187
188 buf.truncate(out_len);
189 String::from_utf8_unchecked(buf)
190 }
191 _ => String::new(),
193 }
194 }
195 }
196
197 pub fn channel_from_index(&self, idx: u32) -> Channel {
201 Channel::from(unsafe { av_channel_layout_channel_from_index(self.as_ptr(), idx as c_uint) })
202 }
203
204 pub fn index_from_channel(&self, channel: Channel) -> Option<u32> {
206 unsafe {
207 u32::try_from(av_channel_layout_index_from_channel(
208 self.as_ptr(),
209 AVChannel::from(channel),
210 ))
211 .ok()
212 }
213 }
214
215 pub fn index_from_string<S: AsRef<str>>(&self, name: S) -> Option<u32> {
219 let cstr = CString::new(name.as_ref()).expect("no nul byte in name");
220 let ret = unsafe { av_channel_layout_index_from_string(self.as_ptr(), cstr.as_ptr()) };
221
222 u32::try_from(ret).ok()
223 }
224
225 pub fn channel_from_string<S: AsRef<str>>(&self, name: S) -> Channel {
231 let cstr = CString::new(name.as_ref()).expect("no nul byte in name");
232
233 Channel::from(unsafe {
234 av_channel_layout_channel_from_string(self.as_ptr(), cstr.as_ptr())
235 })
236 }
237
238 pub fn subset(&self, mask: ChannelLayoutMask) -> ChannelLayoutMask {
240 ChannelLayoutMask::from_bits_truncate(unsafe {
241 av_channel_layout_subset(self.as_ptr(), mask.bits())
242 })
243 }
244
245 #[doc(alias = "check")]
247 pub fn is_valid(&self) -> bool {
248 unsafe { av_channel_layout_check(self.as_ptr()) != 0 }
249 }
250
251 #[cfg(feature = "ffmpeg_7_0")]
273 pub fn retype(&mut self, target: ChannelRetypeTarget) -> Result<ChannelRetypeKind, Error> {
274 use std::cmp::Ordering;
275 use ChannelRetypeTarget as Target;
276
277 let (channel_order, flags) = match target {
278 Target::Lossy(order) => (order, 0),
279 Target::Lossless(order) => (order, AV_CHANNEL_LAYOUT_RETYPE_FLAG_LOSSLESS),
280 Target::Canonical => (
281 ChannelOrder::Unspecified,
282 AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL,
283 ),
284 };
285
286 let ret = unsafe { av_channel_layout_retype(self.0.to_mut(), channel_order.into(), flags) };
287
288 match ret.cmp(&0) {
289 Ordering::Greater => Ok(ChannelRetypeKind::Lossy),
290 Ordering::Equal => Ok(ChannelRetypeKind::Lossless),
291 Ordering::Less => Err(Error::from(ret)),
292 }
293 }
294}
295
296#[cfg(feature = "ffmpeg_7_0")]
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub enum ChannelRetypeKind {
300 Lossless,
301 Lossy,
302}
303
304#[cfg(feature = "ffmpeg_7_0")]
307#[non_exhaustive]
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum ChannelRetypeTarget {
310 Lossy(ChannelOrder),
312 Lossless(ChannelOrder),
314 Canonical,
316}
317
318impl<'a> From<AVChannelLayout> for ChannelLayout<'a> {
319 fn from(value: AVChannelLayout) -> Self {
320 Self(Cow::Owned(value))
321 }
322}
323
324impl<'a> From<&'a AVChannelLayout> for ChannelLayout<'a> {
325 fn from(value: &'a AVChannelLayout) -> Self {
326 Self(Cow::Borrowed(value))
327 }
328}
329
330impl<'a> Borrow<AVChannelLayout> for ChannelLayout<'a> {
331 fn borrow(&self) -> &AVChannelLayout {
332 &self.0
333 }
334}
335
336type Scl = ChannelLayout<'static>;
338
339impl<'a> ChannelLayout<'a> {
341 pub const MONO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_MONO));
342 pub const STEREO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO));
343 pub const _2POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2POINT1));
344 pub const _2_1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_1));
345 pub const SURROUND: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_SURROUND));
346 pub const _3POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1));
347 pub const _4POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT0));
348 pub const _4POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT1));
349 pub const _2_2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_2));
350 pub const QUAD: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_QUAD));
351 pub const _5POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0));
352 pub const _5POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1));
353 pub const _5POINT0_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0_BACK));
354 pub const _5POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1_BACK));
355 pub const _6POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0));
356 pub const _6POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0_FRONT));
357 pub const _3POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1POINT2));
358 pub const HEXAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXAGONAL));
359 pub const _6POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1));
360 pub const _6POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_BACK));
361 pub const _6POINT1_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_FRONT));
362 pub const _7POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0));
363 pub const _7POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0_FRONT));
364 pub const _7POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1));
365 pub const _7POINT1_WIDE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE));
366 pub const _7POINT1_WIDE_BACK: Scl =
367 ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK));
368 pub const _5POINT1POINT2_BACK: Scl =
369 ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK));
370 pub const OCTAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_OCTAGONAL));
371 pub const CUBE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_CUBE));
372 pub const _5POINT1POINT4_BACK: Scl =
373 ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK));
374 pub const _7POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT2));
375 pub const _7POINT1POINT4_BACK: Scl =
376 ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK));
377 #[cfg(feature = "ffmpeg_7_0")]
378 pub const _7POINT2POINT3: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT2POINT3));
379 #[cfg(feature = "ffmpeg_7_0")]
380 pub const _9POINT1POINT4_BACK: Scl =
381 ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK));
382 pub const HEXADECAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXADECAGONAL));
383 pub const STEREO_DOWNMIX: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO_DOWNMIX));
384 pub const _22POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_22POINT2));
385}
386
387#[cfg(test)]
388mod test {
389 use super::*;
390
391 #[test]
392 fn unspecified() {
393 let empty = ChannelLayout::unspecified(0);
394 assert_eq!(empty.order(), ChannelOrder::Unspecified);
395 assert_eq!(empty.channels(), 0);
396 assert!(!empty.is_valid());
397
398 let unspec = ChannelLayout::unspecified(42);
399 assert_eq!(unspec.order(), ChannelOrder::Unspecified);
400 assert_eq!(unspec.channels(), 42);
401 assert!(unspec.is_valid());
402 }
403
404 #[test]
405 fn custom() {
406 let channels = vec![
407 ChannelCustom::new(Channel::FrontLeft),
408 ChannelCustom::new(Channel::FrontRight),
409 ChannelCustom::named(Channel::LowFrequency, "bass"),
410 ChannelCustom::named(Channel::BackLeft, "back left"),
411 ChannelCustom::named(Channel::BottomFrontCenter, "bottom front center"),
413 ];
414
415 let custom = ChannelLayout::custom(channels.clone());
416 assert!(custom.is_valid());
417 assert_eq!(custom.channels(), 5);
418 assert_eq!(custom.order(), ChannelOrder::Custom);
419 assert_eq!(custom.map().unwrap(), &channels);
420 }
421
422 #[test]
423 fn defaults() {
424 let unspec = ChannelLayout::default_for_channels(0);
425 assert!(unspec.order() == ChannelOrder::Unspecified);
426 assert!(!unspec.is_valid());
427
428 for i in 1..12 {
429 let layout = ChannelLayout::default_for_channels(i);
430 assert_eq!(layout.channels(), i);
431 assert!(layout.is_valid(), "default layout invalid for {i} channels");
432 assert!(!layout.description().is_empty());
433 }
434 }
435
436 #[test]
437 fn from_mask() {
438 use ChannelLayout as Layout;
439 use ChannelLayoutMask as Mask;
440
441 assert_eq!(Layout::from_mask(Mask::empty()), None);
442
443 let tests = [
444 (Mask::MONO, Layout::MONO),
445 (Mask::STEREO, Layout::STEREO),
446 (Mask::_2POINT1, Layout::_2POINT1),
447 (Mask::_2_1, Layout::_2_1),
448 (Mask::SURROUND, Layout::SURROUND),
449 (Mask::_3POINT1, Layout::_3POINT1),
450 (Mask::_4POINT0, Layout::_4POINT0),
451 (Mask::_4POINT1, Layout::_4POINT1),
452 (Mask::_2_2, Layout::_2_2),
453 (Mask::QUAD, Layout::QUAD),
454 (Mask::_5POINT0, Layout::_5POINT0),
455 (Mask::_5POINT1, Layout::_5POINT1),
456 (Mask::_5POINT0_BACK, Layout::_5POINT0_BACK),
457 (Mask::_5POINT1_BACK, Layout::_5POINT1_BACK),
458 (Mask::_6POINT0, Layout::_6POINT0),
459 (Mask::_6POINT0_FRONT, Layout::_6POINT0_FRONT),
460 (Mask::HEXAGONAL, Layout::HEXAGONAL),
461 (Mask::_3POINT1POINT2, Layout::_3POINT1POINT2),
462 (Mask::_6POINT1, Layout::_6POINT1),
463 (Mask::_6POINT1_BACK, Layout::_6POINT1_BACK),
464 (Mask::_6POINT1_FRONT, Layout::_6POINT1_FRONT),
465 (Mask::_7POINT0, Layout::_7POINT0),
466 (Mask::_7POINT0_FRONT, Layout::_7POINT0_FRONT),
467 (Mask::_7POINT1, Layout::_7POINT1),
468 (Mask::_7POINT1_WIDE, Layout::_7POINT1_WIDE),
469 (Mask::_7POINT1_WIDE_BACK, Layout::_7POINT1_WIDE_BACK),
470 (Mask::_5POINT1POINT2_BACK, Layout::_5POINT1POINT2_BACK),
471 (Mask::OCTAGONAL, Layout::OCTAGONAL),
472 (Mask::CUBE, Layout::CUBE),
473 (Mask::_5POINT1POINT4_BACK, Layout::_5POINT1POINT4_BACK),
474 (Mask::_7POINT1POINT2, Layout::_7POINT1POINT2),
475 (Mask::_7POINT1POINT4_BACK, Layout::_7POINT1POINT4_BACK),
476 (Mask::HEXADECAGONAL, Layout::HEXADECAGONAL),
477 (Mask::STEREO_DOWNMIX, Layout::STEREO_DOWNMIX),
478 (Mask::_22POINT2, Layout::_22POINT2),
479 ];
480
481 for (mask, expected) in tests {
482 let result = Layout::from_mask(mask).expect("can find layout for bitmask");
483 assert_eq!(
484 result.order(),
485 ChannelOrder::Native,
486 "layout from mask must use native order"
487 );
488 assert_eq!(result.mask(), Some(mask));
489 assert_eq!(result, expected);
490 }
491 }
492
493 #[test]
494 fn from_string() {
495 let test_strings = [
496 ("1 channels (FRC)", ChannelOrder::Native, 1),
497 ("FL@Left+FR@Right+LFE", ChannelOrder::Custom, 3),
498 ("0x4", ChannelOrder::Native, 1),
499 ("4c", ChannelOrder::Native, 4),
500 ("7 channels", ChannelOrder::Unspecified, 7),
501 ("ambisonic 2+stereo", ChannelOrder::Ambisonic, 11),
502 ];
503
504 for (s, order, channels) in test_strings {
505 let result = ChannelLayout::from_string(s).expect("can find layout for description");
506 assert!(result.is_valid());
507 assert_eq!(result.order(), order);
508 assert_eq!(result.channels(), channels);
509 }
510 }
511
512 #[test]
513 fn describe() {
514 use ChannelLayout as Layout;
515 use ChannelLayoutMask as Mask;
516
517 let tests = [
518 (Layout::MONO, "mono"),
519 (Layout::STEREO, "stereo"),
520 (Layout::_5POINT1, "5.1(side)"),
521 (
522 Layout::from_string("FL@Left+FR@Right+LFE").unwrap(),
523 "3 channels (FL@Left+FR@Right+LFE)",
524 ),
525 (
526 Layout::from_mask(Mask::FRONT_RIGHT_OF_CENTER).unwrap(),
527 "1 channels (FRC)",
528 ),
529 #[cfg(feature = "ffmpeg_6_1")]
530 (Layout::_7POINT1POINT4_BACK, "7.1.4"),
531 #[cfg(not(feature = "ffmpeg_6_1"))]
532 (
533 Layout::_7POINT1POINT4_BACK,
534 "12 channels (FL+FR+FC+LFE+BL+BR+SL+SR+TFL+TFR+TBL+TBR)",
535 ),
536 ];
537
538 for (layout, expected) in tests {
539 assert!(layout.is_valid());
540
541 let desc = layout.description();
542 assert!(!desc.is_empty());
543 assert_eq!(desc, expected);
544 }
545 }
546
547 #[cfg(feature = "ffmpeg_7_0")]
548 #[test]
549 fn retype() {
550 use ChannelLayout as Layout;
551 use ChannelOrder as Order;
552 use ChannelRetypeKind as Kind;
553 use ChannelRetypeTarget as Target;
554
555 let tests = [
556 (
557 Layout::_7POINT1POINT4_BACK,
559 Target::Lossless(Order::Native),
560 Ok(Kind::Lossless),
561 Layout::_7POINT1POINT4_BACK,
562 true,
563 ),
564 (
565 Layout::STEREO,
567 Target::Lossless(Order::Custom),
568 Ok(Kind::Lossless),
569 Layout::custom(vec![
570 ChannelCustom::new(Channel::FrontLeft),
571 ChannelCustom::new(Channel::FrontRight),
572 ]),
573 true,
574 ),
575 (
576 Layout::STEREO,
578 Target::Lossy(Order::Custom),
579 Ok(Kind::Lossless),
580 Layout::custom(vec![
581 ChannelCustom::new(Channel::FrontLeft),
582 ChannelCustom::new(Channel::FrontRight),
583 ]),
584 true,
585 ),
586 (
587 Layout::OCTAGONAL,
589 Target::Lossy(Order::Unspecified),
590 Ok(Kind::Lossy),
591 Layout::unspecified(8),
592 true,
593 ),
594 (
595 Layout::OCTAGONAL,
597 Target::Lossless(Order::Unspecified),
598 Err(Error::Other {
599 errno: libc::ENOSYS,
600 }),
601 Layout::OCTAGONAL,
602 true,
603 ),
604 (
605 Layout::custom(vec![
607 ChannelCustom::new(Channel::FrontLeft),
608 ChannelCustom::new(Channel::FrontRight),
609 ChannelCustom::new(Channel::FrontCenter),
610 ChannelCustom::new(Channel::LowFrequency),
611 ]),
612 Target::Lossy(Order::Native),
613 Ok(Kind::Lossless),
614 Layout::_3POINT1,
615 true,
616 ),
617 (
618 Layout::custom(vec![
620 ChannelCustom::new(Channel::FrontLeft),
621 ChannelCustom::new(Channel::FrontRight),
622 ChannelCustom::named(Channel::FrontCenter, "front center"),
623 ChannelCustom::new(Channel::LowFrequency),
624 ]),
625 Target::Lossy(Order::Native),
626 Ok(Kind::Lossy),
627 Layout::_3POINT1,
628 true,
629 ),
630 (
631 Layout::unspecified(0),
633 Target::Lossy(ChannelOrder::Custom),
634 Err(Error::Other {
635 errno: libc::EINVAL,
636 }),
637 Layout::unspecified(0),
638 false,
639 ),
640 ];
641
642 for (layout, target, expected_result, expected_layout, expected_valid) in tests {
643 let mut layout = layout.clone();
644 let actual_result = layout.retype(target);
645
646 assert_eq!(
647 layout.is_valid(),
648 expected_valid,
649 "is_valid should return {expected_valid} for {layout:?}, but did not."
650 );
651 assert_eq!(
652 actual_result,
653 expected_result,
654 "retype should return {expected_result:?} for {layout:?}, but returned {actual_result:?}"
655 );
656 assert_eq!(layout, expected_layout);
657 }
658 }
659}