Skip to main content

wedeo_core/
channel_layout.rs

1use std::fmt;
2
3/// Individual audio channel, matching FFmpeg's AVChannel.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5#[repr(i32)]
6pub enum Channel {
7    FrontLeft = 0,
8    FrontRight = 1,
9    FrontCenter = 2,
10    LowFrequency = 3,
11    BackLeft = 4,
12    BackRight = 5,
13    FrontLeftOfCenter = 6,
14    FrontRightOfCenter = 7,
15    BackCenter = 8,
16    SideLeft = 9,
17    SideRight = 10,
18    TopCenter = 11,
19    TopFrontLeft = 12,
20    TopFrontCenter = 13,
21    TopFrontRight = 14,
22    TopBackLeft = 15,
23    TopBackCenter = 16,
24    TopBackRight = 17,
25    StereoLeft = 29,
26    StereoRight = 30,
27    WideLeft = 31,
28    WideRight = 32,
29    SurroundDirectLeft = 33,
30    SurroundDirectRight = 34,
31    LowFrequency2 = 35,
32    TopSideLeft = 36,
33    TopSideRight = 37,
34    BottomFrontCenter = 38,
35    BottomFrontLeft = 39,
36    BottomFrontRight = 40,
37    BinauralLeft = 61,
38    BinauralRight = 62,
39}
40
41impl Channel {
42    pub fn name(self) -> &'static str {
43        match self {
44            Channel::FrontLeft => "FL",
45            Channel::FrontRight => "FR",
46            Channel::FrontCenter => "FC",
47            Channel::LowFrequency => "LFE",
48            Channel::BackLeft => "BL",
49            Channel::BackRight => "BR",
50            Channel::FrontLeftOfCenter => "FLC",
51            Channel::FrontRightOfCenter => "FRC",
52            Channel::BackCenter => "BC",
53            Channel::SideLeft => "SL",
54            Channel::SideRight => "SR",
55            Channel::TopCenter => "TC",
56            Channel::TopFrontLeft => "TFL",
57            Channel::TopFrontCenter => "TFC",
58            Channel::TopFrontRight => "TFR",
59            Channel::TopBackLeft => "TBL",
60            Channel::TopBackCenter => "TBC",
61            Channel::TopBackRight => "TBR",
62            Channel::StereoLeft => "DL",
63            Channel::StereoRight => "DR",
64            Channel::WideLeft => "WL",
65            Channel::WideRight => "WR",
66            Channel::SurroundDirectLeft => "SDL",
67            Channel::SurroundDirectRight => "SDR",
68            Channel::LowFrequency2 => "LFE2",
69            Channel::TopSideLeft => "TSL",
70            Channel::TopSideRight => "TSR",
71            Channel::BottomFrontCenter => "BFC",
72            Channel::BottomFrontLeft => "BFL",
73            Channel::BottomFrontRight => "BFR",
74            Channel::BinauralLeft => "BinL",
75            Channel::BinauralRight => "BinR",
76        }
77    }
78}
79
80/// Channel ordering scheme.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum ChannelOrder {
83    /// The native channel order, i.e. the channels are in the same order in
84    /// which they are defined in the Channel enum.
85    Native,
86    /// Custom order — channels are described by the map in ChannelLayout.
87    Custom,
88    /// Unspecified order — only the channel count is known.
89    Unspec,
90}
91
92/// Audio channel layout.
93#[derive(Debug, Clone)]
94pub struct ChannelLayout {
95    pub order: ChannelOrder,
96    pub nb_channels: i32,
97    pub channels: Vec<Channel>,
98}
99
100impl ChannelLayout {
101    /// Mono layout: front center.
102    pub fn mono() -> Self {
103        Self {
104            order: ChannelOrder::Native,
105            nb_channels: 1,
106            channels: vec![Channel::FrontCenter],
107        }
108    }
109
110    /// Stereo layout: front left + front right.
111    pub fn stereo() -> Self {
112        Self {
113            order: ChannelOrder::Native,
114            nb_channels: 2,
115            channels: vec![Channel::FrontLeft, Channel::FrontRight],
116        }
117    }
118
119    /// 5.1 surround layout.
120    pub fn surround_5_1() -> Self {
121        Self {
122            order: ChannelOrder::Native,
123            nb_channels: 6,
124            channels: vec![
125                Channel::FrontLeft,
126                Channel::FrontRight,
127                Channel::FrontCenter,
128                Channel::LowFrequency,
129                Channel::BackLeft,
130                Channel::BackRight,
131            ],
132        }
133    }
134
135    /// 7.1 surround layout.
136    pub fn surround_7_1() -> Self {
137        Self {
138            order: ChannelOrder::Native,
139            nb_channels: 8,
140            channels: vec![
141                Channel::FrontLeft,
142                Channel::FrontRight,
143                Channel::FrontCenter,
144                Channel::LowFrequency,
145                Channel::BackLeft,
146                Channel::BackRight,
147                Channel::SideLeft,
148                Channel::SideRight,
149            ],
150        }
151    }
152
153    /// Unspecified layout with the given channel count.
154    pub fn unspec(nb_channels: i32) -> Self {
155        Self {
156            order: ChannelOrder::Unspec,
157            nb_channels,
158            channels: Vec::new(),
159        }
160    }
161
162    /// Construct a channel layout from a WAV/WAVEFORMATEXTENSIBLE channel mask.
163    ///
164    /// The WAV channel mask bits correspond to:
165    /// - bit 0: FRONT_LEFT
166    /// - bit 1: FRONT_RIGHT
167    /// - bit 2: FRONT_CENTER
168    /// - bit 3: LOW_FREQUENCY
169    /// - bit 4: BACK_LEFT
170    /// - bit 5: BACK_RIGHT
171    /// - bit 6: FRONT_LEFT_OF_CENTER
172    /// - bit 7: FRONT_RIGHT_OF_CENTER
173    /// - bit 8: BACK_CENTER
174    /// - bit 9: SIDE_LEFT
175    /// - bit 10: SIDE_RIGHT
176    pub fn from_wav_channel_mask(mask: u32) -> Self {
177        const WAV_CHANNELS: [(u32, Channel); 18] = [
178            (1 << 0, Channel::FrontLeft),
179            (1 << 1, Channel::FrontRight),
180            (1 << 2, Channel::FrontCenter),
181            (1 << 3, Channel::LowFrequency),
182            (1 << 4, Channel::BackLeft),
183            (1 << 5, Channel::BackRight),
184            (1 << 6, Channel::FrontLeftOfCenter),
185            (1 << 7, Channel::FrontRightOfCenter),
186            (1 << 8, Channel::BackCenter),
187            (1 << 9, Channel::SideLeft),
188            (1 << 10, Channel::SideRight),
189            (1 << 11, Channel::TopCenter),
190            (1 << 12, Channel::TopFrontLeft),
191            (1 << 13, Channel::TopFrontCenter),
192            (1 << 14, Channel::TopFrontRight),
193            (1 << 15, Channel::TopBackLeft),
194            (1 << 16, Channel::TopBackCenter),
195            (1 << 17, Channel::TopBackRight),
196        ];
197
198        if mask == 0 {
199            return Self::unspec(0);
200        }
201
202        let mut channels = Vec::new();
203        for &(bit, channel) in &WAV_CHANNELS {
204            if mask & bit != 0 {
205                channels.push(channel);
206            }
207        }
208
209        let nb_channels = channels.len() as i32;
210        Self {
211            order: ChannelOrder::Native,
212            nb_channels,
213            channels,
214        }
215    }
216}
217
218impl fmt::Display for ChannelLayout {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        if self.channels.is_empty() {
221            return write!(f, "{} channels", self.nb_channels);
222        }
223        // Use FFmpeg's standard layout names for common configurations
224        if let Some(name) = self.standard_name() {
225            return write!(f, "{name}");
226        }
227        let names: Vec<&str> = self.channels.iter().map(|c| c.name()).collect();
228        write!(f, "{}", names.join("+"))
229    }
230}
231
232impl ChannelLayout {
233    /// Return the FFmpeg standard layout name if this matches a known configuration.
234    ///
235    /// The layout names and channel compositions match FFmpeg's `channel_layout_map`
236    /// table in `libavutil/channel_layout.c`. Channels are listed in native order
237    /// (ascending enum/bit-position order).
238    pub fn standard_name(&self) -> Option<&'static str> {
239        use Channel::*;
240        match self.channels.as_slice() {
241            // mono: FC
242            [FrontCenter] => Some("mono"),
243            // stereo: FL+FR
244            [FrontLeft, FrontRight] => Some("stereo"),
245            // 2.1: FL+FR+LFE
246            [FrontLeft, FrontRight, LowFrequency] => Some("2.1"),
247            // 3.0: FL+FR+FC  (AV_CH_LAYOUT_SURROUND)
248            [FrontLeft, FrontRight, FrontCenter] => Some("3.0"),
249            // 3.0(back): FL+FR+BC  (AV_CH_LAYOUT_2_1)
250            [FrontLeft, FrontRight, BackCenter] => Some("3.0(back)"),
251            // 4.0: FL+FR+FC+BC
252            [FrontLeft, FrontRight, FrontCenter, BackCenter] => Some("4.0"),
253            // quad: FL+FR+BL+BR
254            [FrontLeft, FrontRight, BackLeft, BackRight] => Some("quad"),
255            // quad(side): FL+FR+SL+SR  (AV_CH_LAYOUT_2_2)
256            [FrontLeft, FrontRight, SideLeft, SideRight] => Some("quad(side)"),
257            // 3.1: FL+FR+FC+LFE
258            [FrontLeft, FrontRight, FrontCenter, LowFrequency] => Some("3.1"),
259            // 5.0: FL+FR+FC+BL+BR  (AV_CH_LAYOUT_5POINT0_BACK)
260            [FrontLeft, FrontRight, FrontCenter, BackLeft, BackRight] => Some("5.0"),
261            // 5.0(side): FL+FR+FC+SL+SR  (AV_CH_LAYOUT_5POINT0)
262            [FrontLeft, FrontRight, FrontCenter, SideLeft, SideRight] => Some("5.0(side)"),
263            // 4.1: FL+FR+FC+LFE+BC
264            [FrontLeft, FrontRight, FrontCenter, LowFrequency, BackCenter] => Some("4.1"),
265            // 5.1: FL+FR+FC+LFE+BL+BR  (AV_CH_LAYOUT_5POINT1_BACK)
266            [
267                FrontLeft,
268                FrontRight,
269                FrontCenter,
270                LowFrequency,
271                BackLeft,
272                BackRight,
273            ] => Some("5.1"),
274            // 5.1(side): FL+FR+FC+LFE+SL+SR  (AV_CH_LAYOUT_5POINT1)
275            [
276                FrontLeft,
277                FrontRight,
278                FrontCenter,
279                LowFrequency,
280                SideLeft,
281                SideRight,
282            ] => Some("5.1(side)"),
283            // 6.0: FL+FR+FC+BC+SL+SR
284            [
285                FrontLeft,
286                FrontRight,
287                FrontCenter,
288                BackCenter,
289                SideLeft,
290                SideRight,
291            ] => Some("6.0"),
292            // 6.0(front): FL+FR+FLC+FRC+SL+SR
293            [
294                FrontLeft,
295                FrontRight,
296                FrontLeftOfCenter,
297                FrontRightOfCenter,
298                SideLeft,
299                SideRight,
300            ] => Some("6.0(front)"),
301            // 3.1.2: FL+FR+FC+LFE+TFL+TFR
302            [
303                FrontLeft,
304                FrontRight,
305                FrontCenter,
306                LowFrequency,
307                TopFrontLeft,
308                TopFrontRight,
309            ] => Some("3.1.2"),
310            // hexagonal: FL+FR+FC+BL+BR+BC
311            [
312                FrontLeft,
313                FrontRight,
314                FrontCenter,
315                BackLeft,
316                BackRight,
317                BackCenter,
318            ] => Some("hexagonal"),
319            // 6.1: FL+FR+FC+LFE+BC+SL+SR
320            [
321                FrontLeft,
322                FrontRight,
323                FrontCenter,
324                LowFrequency,
325                BackCenter,
326                SideLeft,
327                SideRight,
328            ] => Some("6.1"),
329            // 6.1(back): FL+FR+FC+LFE+BL+BR+BC
330            [
331                FrontLeft,
332                FrontRight,
333                FrontCenter,
334                LowFrequency,
335                BackLeft,
336                BackRight,
337                BackCenter,
338            ] => Some("6.1(back)"),
339            // 6.1(front): FL+FR+LFE+FLC+FRC+SL+SR
340            [
341                FrontLeft,
342                FrontRight,
343                LowFrequency,
344                FrontLeftOfCenter,
345                FrontRightOfCenter,
346                SideLeft,
347                SideRight,
348            ] => Some("6.1(front)"),
349            // 7.0: FL+FR+FC+BL+BR+SL+SR
350            [
351                FrontLeft,
352                FrontRight,
353                FrontCenter,
354                BackLeft,
355                BackRight,
356                SideLeft,
357                SideRight,
358            ] => Some("7.0"),
359            // 7.0(front): FL+FR+FC+FLC+FRC+SL+SR
360            [
361                FrontLeft,
362                FrontRight,
363                FrontCenter,
364                FrontLeftOfCenter,
365                FrontRightOfCenter,
366                SideLeft,
367                SideRight,
368            ] => Some("7.0(front)"),
369            // 7.1: FL+FR+FC+LFE+BL+BR+SL+SR
370            [
371                FrontLeft,
372                FrontRight,
373                FrontCenter,
374                LowFrequency,
375                BackLeft,
376                BackRight,
377                SideLeft,
378                SideRight,
379            ] => Some("7.1"),
380            // 7.1(wide): FL+FR+FC+LFE+BL+BR+FLC+FRC  (AV_CH_LAYOUT_7POINT1_WIDE_BACK)
381            [
382                FrontLeft,
383                FrontRight,
384                FrontCenter,
385                LowFrequency,
386                BackLeft,
387                BackRight,
388                FrontLeftOfCenter,
389                FrontRightOfCenter,
390            ] => Some("7.1(wide)"),
391            // 7.1(wide-side): FL+FR+FC+LFE+FLC+FRC+SL+SR  (AV_CH_LAYOUT_7POINT1_WIDE)
392            [
393                FrontLeft,
394                FrontRight,
395                FrontCenter,
396                LowFrequency,
397                FrontLeftOfCenter,
398                FrontRightOfCenter,
399                SideLeft,
400                SideRight,
401            ] => Some("7.1(wide-side)"),
402            // 5.1.2: FL+FR+FC+LFE+SL+SR+TFL+TFR
403            [
404                FrontLeft,
405                FrontRight,
406                FrontCenter,
407                LowFrequency,
408                SideLeft,
409                SideRight,
410                TopFrontLeft,
411                TopFrontRight,
412            ] => Some("5.1.2"),
413            // 5.1.2(back): FL+FR+FC+LFE+BL+BR+TFL+TFR
414            [
415                FrontLeft,
416                FrontRight,
417                FrontCenter,
418                LowFrequency,
419                BackLeft,
420                BackRight,
421                TopFrontLeft,
422                TopFrontRight,
423            ] => Some("5.1.2(back)"),
424            // octagonal: FL+FR+FC+BL+BR+BC+SL+SR
425            [
426                FrontLeft,
427                FrontRight,
428                FrontCenter,
429                BackLeft,
430                BackRight,
431                BackCenter,
432                SideLeft,
433                SideRight,
434            ] => Some("octagonal"),
435            // cube: FL+FR+BL+BR+TFL+TFR+TBL+TBR
436            [
437                FrontLeft,
438                FrontRight,
439                BackLeft,
440                BackRight,
441                TopFrontLeft,
442                TopFrontRight,
443                TopBackLeft,
444                TopBackRight,
445            ] => Some("cube"),
446            // 5.1.4: FL+FR+FC+LFE+SL+SR+TFL+TFR+TBL+TBR  (AV_CH_LAYOUT_5POINT1POINT4_BACK)
447            [
448                FrontLeft,
449                FrontRight,
450                FrontCenter,
451                LowFrequency,
452                SideLeft,
453                SideRight,
454                TopFrontLeft,
455                TopFrontRight,
456                TopBackLeft,
457                TopBackRight,
458            ] => Some("5.1.4"),
459            // 7.1.2: FL+FR+FC+LFE+BL+BR+SL+SR+TFL+TFR
460            [
461                FrontLeft,
462                FrontRight,
463                FrontCenter,
464                LowFrequency,
465                BackLeft,
466                BackRight,
467                SideLeft,
468                SideRight,
469                TopFrontLeft,
470                TopFrontRight,
471            ] => Some("7.1.2"),
472            // 7.1.4: FL+FR+FC+LFE+BL+BR+SL+SR+TFL+TFR+TBL+TBR  (AV_CH_LAYOUT_7POINT1POINT4_BACK)
473            [
474                FrontLeft,
475                FrontRight,
476                FrontCenter,
477                LowFrequency,
478                BackLeft,
479                BackRight,
480                SideLeft,
481                SideRight,
482                TopFrontLeft,
483                TopFrontRight,
484                TopBackLeft,
485                TopBackRight,
486            ] => Some("7.1.4"),
487            // 7.2.3: FL+FR+FC+LFE+BL+BR+SL+SR+TFL+TFR+TBC+LFE2  (AV_CH_LAYOUT_7POINT2POINT3)
488            [
489                FrontLeft,
490                FrontRight,
491                FrontCenter,
492                LowFrequency,
493                BackLeft,
494                BackRight,
495                SideLeft,
496                SideRight,
497                TopFrontLeft,
498                TopFrontRight,
499                TopBackCenter,
500                LowFrequency2,
501            ] => Some("7.2.3"),
502            // 9.1.4: FL+FR+FC+LFE+BL+BR+FLC+FRC+SL+SR+TFL+TFR+TBL+TBR
503            [
504                FrontLeft,
505                FrontRight,
506                FrontCenter,
507                LowFrequency,
508                BackLeft,
509                BackRight,
510                FrontLeftOfCenter,
511                FrontRightOfCenter,
512                SideLeft,
513                SideRight,
514                TopFrontLeft,
515                TopFrontRight,
516                TopBackLeft,
517                TopBackRight,
518            ] => Some("9.1.4"),
519            // 9.1.6: FL+FR+FC+LFE+BL+BR+FLC+FRC+SL+SR+TFL+TFR+TBL+TBR+TSL+TSR
520            [
521                FrontLeft,
522                FrontRight,
523                FrontCenter,
524                LowFrequency,
525                BackLeft,
526                BackRight,
527                FrontLeftOfCenter,
528                FrontRightOfCenter,
529                SideLeft,
530                SideRight,
531                TopFrontLeft,
532                TopFrontRight,
533                TopBackLeft,
534                TopBackRight,
535                TopSideLeft,
536                TopSideRight,
537            ] => Some("9.1.6"),
538            // hexadecagonal: FL+FR+FC+BL+BR+BC+SL+SR+TFL+TFC+TFR+TBL+TBC+TBR+WL+WR
539            [
540                FrontLeft,
541                FrontRight,
542                FrontCenter,
543                BackLeft,
544                BackRight,
545                BackCenter,
546                SideLeft,
547                SideRight,
548                TopFrontLeft,
549                TopFrontCenter,
550                TopFrontRight,
551                TopBackLeft,
552                TopBackCenter,
553                TopBackRight,
554                WideLeft,
555                WideRight,
556            ] => Some("hexadecagonal"),
557            // downmix: DL+DR  (AV_CH_LAYOUT_STEREO_DOWNMIX)
558            [StereoLeft, StereoRight] => Some("downmix"),
559            // binaural: BinL+BinR
560            [BinauralLeft, BinauralRight] => Some("binaural"),
561            // 22.2: FL+FR+FC+LFE+BL+BR+FLC+FRC+BC+SL+SR+TC+TFL+TFC+TFR+TBL+TBC+TBR+LFE2+TSL+TSR+BFC+BFL+BFR
562            [
563                FrontLeft,
564                FrontRight,
565                FrontCenter,
566                LowFrequency,
567                BackLeft,
568                BackRight,
569                FrontLeftOfCenter,
570                FrontRightOfCenter,
571                BackCenter,
572                SideLeft,
573                SideRight,
574                TopCenter,
575                TopFrontLeft,
576                TopFrontCenter,
577                TopFrontRight,
578                TopBackLeft,
579                TopBackCenter,
580                TopBackRight,
581                LowFrequency2,
582                TopSideLeft,
583                TopSideRight,
584                BottomFrontCenter,
585                BottomFrontLeft,
586                BottomFrontRight,
587            ] => Some("22.2"),
588            _ => None,
589        }
590    }
591}