gst-plugins-rs/net/rtp/src/linear_audio/common/channel_positions.rs

284 lines
8 KiB
Rust

// GStreamer RTP audio channel positions
//
// Copyright (C) 2023-2024 Tim-Philipp Müller <tim centricular com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use byte_slice_cast::*;
use gst_audio::AudioChannelPosition;
pub(crate) const MAX_REORDER_CHANNELS: usize = 8;
// https://www.rfc-editor.org/rfc/rfc3551.html#section-4.1
const DEFAULT_1CH: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::Mono, // Mono
]
.as_slice();
const DEFAULT_2CH: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft, // Stereo
AudioChannelPosition::FrontRight,
]
.as_slice();
const DEFAULT_3CH: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft, // 3ch
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
]
.as_slice();
const DEFAULT_4CH: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft, // 4ch
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
]
.as_slice();
const DEFAULT_5CH: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft, // 5ch
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
]
.as_slice();
const DEFAULT_6CH: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::SideLeft, // 6ch
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::SideRight,
AudioChannelPosition::FrontRight,
AudioChannelPosition::Lfe1,
]
.as_slice();
const DV_4CH_1: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
]
.as_slice();
const DV_4CH_2: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
]
.as_slice();
const DV_4CH_3: &[gst_audio::AudioChannelPosition] = [
// Same as DV_4CH_2 it seems?
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
]
.as_slice();
const DV_5CH_1: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
AudioChannelPosition::FrontCenter,
]
.as_slice();
const DV_6CH_1: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
]
.as_slice();
const DV_6CH_2: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
]
.as_slice();
const DV_8CH_1: &[gst_audio::AudioChannelPosition] = [
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
AudioChannelPosition::RearLeft,
AudioChannelPosition::RearRight,
]
.as_slice();
const DV_8CH_2: &[gst_audio::AudioChannelPosition] = [
// Same as DV_8CH_1 it seems?
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
AudioChannelPosition::RearLeft,
AudioChannelPosition::RearRight,
]
.as_slice();
const DV_8CH_3: &[gst_audio::AudioChannelPosition] = [
// Same as DV_8CH_1 it seems?
AudioChannelPosition::FrontLeft,
AudioChannelPosition::FrontRight,
AudioChannelPosition::FrontCenter,
AudioChannelPosition::Lfe1,
AudioChannelPosition::SideLeft,
AudioChannelPosition::SideRight,
AudioChannelPosition::RearLeft,
AudioChannelPosition::RearRight,
]
.as_slice();
pub(crate) fn get_channel_order(
name: Option<&str>,
n_channels: i32,
) -> Option<&'static [gst_audio::AudioChannelPosition]> {
assert!(n_channels > 0);
let name = name.unwrap_or("default");
// https://www.rfc-editor.org/rfc/rfc3551.html#section-4.1
// https://www.rfc-editor.org/rfc/rfc3555.html#section-4.1.15
match (n_channels, name) {
// mono
(1, _) => Some(DEFAULT_1CH),
// stereo
(2, _) => Some(DEFAULT_2CH),
// 3ch
(3, _) => Some(DEFAULT_3CH),
// 4ch
(4, "DV.LRLsRs") => Some(DV_4CH_1),
(4, "DV.LRCS") => Some(DV_4CH_2),
(4, "DV.LRCWo") => Some(DV_4CH_3),
(4, _) => Some(DEFAULT_4CH),
// 5ch
(5, "DV.LRLsRsC") => Some(DV_5CH_1),
(5, _) => Some(DEFAULT_5CH),
// 6ch
(6, "DV.LRLsRsCS") => Some(DV_6CH_1),
(6, "DV.LmixRmixTWoQ1Q2") => Some(DV_6CH_2),
(6, _) => Some(DEFAULT_6CH),
// 7ch
(7, _) => None,
// 8ch
(8, "DV.LRCWoLsRsLmixRmix") => Some(DV_8CH_1),
(8, "DV.LRCWoLs1Rs1Ls2Rs2") => Some(DV_8CH_2),
(8, "DV.LRCWoLsRsLcRc") => Some(DV_8CH_3),
(8, _) => None,
// >8ch
(9.., _) => None,
(..=0, _) => unreachable!(),
}
}
fn positions_are_compatible(
pos1: &[gst_audio::AudioChannelPosition],
pos2: &[gst_audio::AudioChannelPosition],
) -> bool {
if pos1.len() != pos2.len() {
return false;
}
let Ok(mask1) = AudioChannelPosition::positions_to_mask(pos1, false) else {
return false;
};
let Ok(mask2) = AudioChannelPosition::positions_to_mask(pos2, false) else {
return false;
};
mask1 == mask2
}
const CHANNEL_MAPPINGS: &[(&[gst_audio::AudioChannelPosition], &str)] = &[
// 1ch
(DEFAULT_1CH, "default"),
// 2ch
(DEFAULT_2CH, "default"),
// 3ch
(DEFAULT_3CH, "default"),
// 4ch
(DV_4CH_1, "DV.LRLsRs"),
(DV_4CH_2, "DV.LRCS"),
(DV_4CH_3, "DV.LRCWo"),
(DEFAULT_4CH, "default"),
// 5ch
(DV_5CH_1, "DV.LRLsRsC"),
(DEFAULT_5CH, "default"),
// 6ch
(DV_6CH_1, "DV.LRLsRsCS"),
(DV_6CH_2, "DV.LmixRmixTWoQ1Q2"),
(DEFAULT_6CH, "default"),
// 8ch
(DV_8CH_1, "DV.LRCWoLsRsLmixRmix"),
(DV_8CH_2, "DV.LRCWoLs1Rs1Ls2Rs2"),
(DV_8CH_3, "DV.LRCWoLsRsLcRc"),
];
// Returns either one of the "DV.*" names, or "default" or None
pub(crate) fn find_channel_order_from_positions(
pos: &[gst_audio::AudioChannelPosition],
) -> Option<&'static str> {
let n_channels = pos.len();
for (map, name) in CHANNEL_MAPPINGS {
if map.len() == n_channels && positions_are_compatible(map, pos) {
return Some(name);
}
}
None
}
#[allow(clippy::manual_range_contains)]
pub(crate) fn reorder_channels<T: Default + Clone + Copy + FromByteSlice>(
buffer_ref: &mut gst::BufferRef,
reorder_map: &[usize],
) -> Result<(), gst::FlowError> {
let mut map = buffer_ref
.map_writable()
.map_err(|_| gst::FlowError::Error)?;
let n_channels = reorder_map.len();
assert!(n_channels >= 1 && n_channels <= MAX_REORDER_CHANNELS);
let mut scratch: [T; MAX_REORDER_CHANNELS] = Default::default();
let in_frame = &mut scratch[0..n_channels];
let data = map.as_mut_slice_of::<T>().unwrap();
for out_frame in data.chunks_exact_mut(n_channels) {
in_frame.copy_from_slice(out_frame);
// "The reorder_map can be used for reordering by assigning
// channel i of the input to channel reorder_map[i] of the output."
for (i, &out_idx) in reorder_map.iter().enumerate() {
out_frame[out_idx] = in_frame[i];
}
}
Ok(())
}