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