use bytes::Bytes;
use oximedia_core::{Rational, SampleFormat, Timestamp};
#[derive(Clone, Debug)]
pub struct AudioFrame {
pub format: SampleFormat,
pub sample_rate: u32,
pub channels: ChannelLayout,
pub samples: AudioBuffer,
pub timestamp: Timestamp,
}
impl AudioFrame {
#[must_use]
pub fn new(format: SampleFormat, sample_rate: u32, channels: ChannelLayout) -> Self {
Self {
format,
sample_rate,
channels,
samples: AudioBuffer::Interleaved(Bytes::new()),
timestamp: Timestamp::new(0, Rational::new(1, i64::from(sample_rate))),
}
}
#[must_use]
pub fn sample_count(&self) -> usize {
let bytes = match &self.samples {
AudioBuffer::Interleaved(data) => data.len(),
AudioBuffer::Planar(planes) => planes.first().map_or(0, Bytes::len),
};
let bytes_per_sample = self.format.bytes_per_sample();
let channel_count = self.channels.count();
if bytes_per_sample == 0 || channel_count == 0 {
0
} else if self.samples.is_planar() {
bytes / bytes_per_sample
} else {
bytes / (bytes_per_sample * channel_count)
}
}
#[must_use]
pub fn duration_seconds(&self) -> f64 {
if self.sample_rate == 0 {
0.0
} else {
#[allow(clippy::cast_precision_loss)]
let result = self.sample_count() as f64 / f64::from(self.sample_rate);
result
}
}
#[must_use]
pub fn is_silent(&self) -> bool {
match &self.samples {
AudioBuffer::Interleaved(data) => data.iter().all(|&b| b == 0 || b == 128),
AudioBuffer::Planar(planes) => {
planes.iter().all(|p| p.iter().all(|&b| b == 0 || b == 128))
}
}
}
}
#[derive(Clone, Debug)]
pub enum AudioBuffer {
Interleaved(Bytes),
Planar(Vec<Bytes>),
}
impl AudioBuffer {
#[must_use]
pub fn is_planar(&self) -> bool {
matches!(self, Self::Planar(_))
}
#[must_use]
pub fn size(&self) -> usize {
match self {
Self::Interleaved(data) => data.len(),
Self::Planar(planes) => planes.iter().map(Bytes::len).sum(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub enum ChannelLayout {
#[default]
Mono,
Stereo,
Surround21,
Surround51,
Surround71,
Custom(Vec<Channel>),
}
impl ChannelLayout {
#[must_use]
pub fn count(&self) -> usize {
match self {
Self::Mono => 1,
Self::Stereo => 2,
Self::Surround21 => 3,
Self::Surround51 => 6,
Self::Surround71 => 8,
Self::Custom(channels) => channels.len(),
}
}
#[must_use]
pub fn from_count(count: usize) -> Self {
match count {
1 => Self::Mono,
2 => Self::Stereo,
3 => Self::Surround21,
6 => Self::Surround51,
8 => Self::Surround71,
_ => Self::Custom(vec![Channel::Unknown; count]),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Channel {
Unknown,
FrontLeft,
FrontRight,
FrontCenter,
Lfe,
SideLeft,
SideRight,
BackLeft,
BackRight,
BackCenter,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_channel_layout() {
assert_eq!(ChannelLayout::Mono.count(), 1);
assert_eq!(ChannelLayout::Stereo.count(), 2);
assert_eq!(ChannelLayout::Surround51.count(), 6);
}
#[test]
fn test_from_count() {
assert_eq!(ChannelLayout::from_count(1), ChannelLayout::Mono);
assert_eq!(ChannelLayout::from_count(2), ChannelLayout::Stereo);
}
#[test]
fn test_audio_buffer() {
let buffer = AudioBuffer::Interleaved(Bytes::from_static(&[0, 1, 2, 3]));
assert!(!buffer.is_planar());
assert_eq!(buffer.size(), 4);
}
}