#[allow(unused_imports)]
use zvidlib_core::*;
use crate::{EncodedAudioSample, Error, ErrorKind, Result};
pub const OPUS_SAMPLE_RATE: u32 = 48_000;
pub const OPUS_PREROLL_SAMPLES: u32 = 15_360;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpusHead {
pub channels: u8,
pub pre_skip: u16,
pub input_sample_rate: u32,
pub output_gain: i16,
pub channel_mapping_family: u8,
pub stream_count: u8,
pub coupled_count: u8,
pub channel_mapping: Vec<u8>,
}
impl OpusHead {
pub fn new(channels: u8, pre_skip: u16, input_sample_rate: u32) -> Result<Self> {
if channels != 1 && channels != 2 {
return Err(invalid("an Opus mapping family 0 stream is mono or stereo"));
}
Ok(Self {
channels,
pre_skip,
input_sample_rate,
output_gain: 0,
channel_mapping_family: 0,
stream_count: 1,
coupled_count: channels - 1,
channel_mapping: (0..channels).collect(),
})
}
pub fn from_dops(dops: &[u8]) -> Result<Self> {
let payload = crate::codec_config::box_payload(dops, b"dOps")?;
let declared = u32::from_be_bytes(dops[..4].try_into().expect("checked by box_payload"));
if usize::try_from(declared).ok() != Some(dops.len()) {
return Err(malformed("Opus dOps box size is inconsistent"));
}
let header = payload
.get(..11)
.ok_or_else(|| malformed("Opus dOps box is truncated"))?;
if header[0] != 0 {
return Err(unsupported(format!(
"Opus dOps version {} is unsupported",
header[0]
)));
}
let head = Self::from_fields(
header[1],
u16::from_be_bytes([header[2], header[3]]),
u32::from_be_bytes([header[4], header[5], header[6], header[7]]),
i16::from_be_bytes([header[8], header[9]]),
header[10],
&payload[11..],
)?;
Ok(head)
}
pub fn to_dops(&self) -> Vec<u8> {
let mut payload = vec![0, self.channels];
payload.extend_from_slice(&self.pre_skip.to_be_bytes());
payload.extend_from_slice(&self.input_sample_rate.to_be_bytes());
payload.extend_from_slice(&self.output_gain.to_be_bytes());
self.push_mapping(&mut payload);
let mut whole = u32::try_from(payload.len() + 8)
.expect("a dOps box is at most a few hundred bytes")
.to_be_bytes()
.to_vec();
whole.extend_from_slice(b"dOps");
whole.extend_from_slice(&payload);
whole
}
pub fn from_identification_header(packet: &[u8]) -> Result<Self> {
let header = packet
.get(..19)
.ok_or_else(|| malformed("Opus identification header is truncated"))?;
if &header[..8] != b"OpusHead" {
return Err(malformed(
"Opus identification header does not start with OpusHead",
));
}
if header[8] >> 4 != 0 {
return Err(unsupported(format!(
"Opus identification header version {} is unsupported",
header[8]
)));
}
Self::from_fields(
header[9],
u16::from_le_bytes([header[10], header[11]]),
u32::from_le_bytes([header[12], header[13], header[14], header[15]]),
i16::from_le_bytes([header[16], header[17]]),
header[18],
&packet[19..],
)
}
pub fn to_identification_header(&self) -> Vec<u8> {
let mut packet = b"OpusHead".to_vec();
packet.push(1);
packet.push(self.channels);
packet.extend_from_slice(&self.pre_skip.to_le_bytes());
packet.extend_from_slice(&self.input_sample_rate.to_le_bytes());
packet.extend_from_slice(&self.output_gain.to_le_bytes());
self.push_mapping(&mut packet);
packet
}
pub fn output_gain_factor(&self) -> f32 {
10_f32.powf(f32::from(self.output_gain) / (20.0 * 256.0))
}
fn from_fields(
channels: u8,
pre_skip: u16,
input_sample_rate: u32,
output_gain: i16,
channel_mapping_family: u8,
mapping_table: &[u8],
) -> Result<Self> {
if channels == 0 {
return Err(malformed("an Opus stream has at least one channel"));
}
if channel_mapping_family == 0 {
if channels > 2 {
return Err(malformed(
"an Opus mapping family 0 stream is mono or stereo",
));
}
return Ok(Self {
output_gain,
..Self::new(channels, pre_skip, input_sample_rate)?
});
}
let table = mapping_table
.get(..2 + usize::from(channels))
.ok_or_else(|| malformed("Opus channel mapping table is truncated"))?;
let (stream_count, coupled_count) = (table[0], table[1]);
if stream_count == 0 || coupled_count > stream_count {
return Err(malformed("Opus channel mapping has invalid stream counts"));
}
let decoded_channels = u16::from(stream_count) + u16::from(coupled_count);
let channel_mapping = table[2..].to_vec();
if channel_mapping
.iter()
.any(|&index| index != 255 && u16::from(index) >= decoded_channels)
{
return Err(malformed(
"Opus channel mapping names a channel no stream decodes",
));
}
Ok(Self {
channels,
pre_skip,
input_sample_rate,
output_gain,
channel_mapping_family,
stream_count,
coupled_count,
channel_mapping,
})
}
fn push_mapping(&self, out: &mut Vec<u8>) {
out.push(self.channel_mapping_family);
if self.channel_mapping_family != 0 {
out.push(self.stream_count);
out.push(self.coupled_count);
out.extend_from_slice(&self.channel_mapping);
}
}
}
pub fn opus_packet_samples(packet: &[u8]) -> Result<u32> {
let &toc = packet
.first()
.ok_or_else(|| invalid_packet("packet is empty"))?;
let frames = match toc & 0x03 {
0 => 1,
1 | 2 => 2,
_ => {
let count = packet
.get(1)
.ok_or_else(|| invalid_packet("code 3 packet has no frame count"))?
& 0x3F;
if count == 0 {
return Err(invalid_packet("code 3 packet declares zero frames"));
}
u32::from(count)
}
};
let samples = frames * frame_samples(toc);
if samples > MAX_PACKET_SAMPLES {
return Err(invalid_packet("packet claims more than 120 ms"));
}
Ok(samples)
}
fn frame_samples(toc: u8) -> u32 {
let size = u32::from((toc >> 3) & 0x03);
if toc & 0x80 != 0 {
(OPUS_SAMPLE_RATE << size) / 400
} else if toc & 0x60 == 0x60 {
if toc & 0x08 != 0 {
OPUS_SAMPLE_RATE / 50
} else {
OPUS_SAMPLE_RATE / 100
}
} else if size == 3 {
OPUS_SAMPLE_RATE * 60 / 1000
} else {
(OPUS_SAMPLE_RATE << size) / 100
}
}
pub fn opus_preroll_packets(packets: &[EncodedAudioSample]) -> usize {
let shortest = packets
.iter()
.map(|packet| packet.decoded_range.len())
.filter(|&length| length > 0)
.min()
.unwrap_or(u64::from(OPUS_PREROLL_SAMPLES));
usize::try_from(u64::from(OPUS_PREROLL_SAMPLES).div_ceil(shortest)).unwrap_or(usize::MAX)
}
const MAX_PACKET_SAMPLES: u32 = 5_760;
fn invalid_packet(what: &str) -> Error {
codec(format!("Opus: invalid packet: {what}"))
}
fn codec(message: impl Into<String>) -> Error {
Error::new(ErrorKind::Codec, message)
}
fn invalid(message: impl Into<String>) -> Error {
Error::new(ErrorKind::InvalidInput, message)
}
fn malformed(message: impl Into<String>) -> Error {
Error::new(ErrorKind::MalformedMedia, message)
}
fn unsupported(message: impl Into<String>) -> Error {
Error::new(ErrorKind::Unsupported, message)
}