use crate::error::{NetError, NetResult};
use crate::smpte2110::rtp::{RtpHeader, RtpPacket, MAX_RTP_PAYLOAD};
use bytes::Bytes;
use std::collections::HashMap;
pub const RTP_PAYLOAD_TYPE_AUDIO: u8 = 97;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AudioSampleRate {
Rate48kHz = 48000,
Rate96kHz = 96000,
}
impl AudioSampleRate {
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[must_use]
pub fn as_f64(self) -> f64 {
f64::from(self as u32)
}
pub fn from_u32(value: u32) -> NetResult<Self> {
match value {
48000 => Ok(Self::Rate48kHz),
96000 => Ok(Self::Rate96kHz),
_ => Err(NetError::protocol(format!(
"Unsupported sample rate: {value}"
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AudioFormat {
LinearPCM,
AES3,
}
impl AudioFormat {
#[must_use]
pub const fn sdp_format(&self, bit_depth: u8) -> &'static str {
match (self, bit_depth) {
(Self::LinearPCM, 16) => "L16",
(Self::LinearPCM, 20) => "L20",
(Self::LinearPCM, 24) => "L24",
(Self::AES3, _) => "AM824",
_ => "L24", }
}
}
#[derive(Debug, Clone)]
pub struct AudioConfig {
pub sample_rate: AudioSampleRate,
pub bit_depth: u8,
pub channels: u16,
pub format: AudioFormat,
pub packet_time_us: u32,
pub max_payload_size: usize,
}
impl Default for AudioConfig {
fn default() -> Self {
Self {
sample_rate: AudioSampleRate::Rate48kHz,
bit_depth: 24,
channels: 2,
format: AudioFormat::LinearPCM,
packet_time_us: 1000, max_payload_size: MAX_RTP_PAYLOAD,
}
}
}
impl AudioConfig {
#[must_use]
pub fn samples_per_packet(&self) -> usize {
let sample_rate = self.sample_rate.as_u32() as usize;
(sample_rate * self.packet_time_us as usize) / 1_000_000
}
#[must_use]
pub const fn bytes_per_sample(&self) -> usize {
let bytes_per_channel = match self.bit_depth {
16 => 2,
20 => 3,
24 => 3,
_ => 3,
};
bytes_per_channel * self.channels as usize
}
#[must_use]
pub fn packet_payload_size(&self) -> usize {
self.samples_per_packet() * self.bytes_per_sample()
}
#[must_use]
pub fn bitrate(&self) -> u64 {
let sample_rate = self.sample_rate.as_u32() as u64;
let bits_per_sample = u64::from(self.bit_depth) * u64::from(self.channels);
sample_rate * bits_per_sample
}
pub fn validate(&self) -> NetResult<()> {
if self.channels == 0 || self.channels > 64 {
return Err(NetError::protocol(format!(
"Invalid channel count: {} (must be 1-64)",
self.channels
)));
}
if self.bit_depth != 16 && self.bit_depth != 20 && self.bit_depth != 24 {
return Err(NetError::protocol(format!(
"Invalid bit depth: {} (must be 16, 20, or 24)",
self.bit_depth
)));
}
let payload_size = self.packet_payload_size();
if payload_size > self.max_payload_size {
return Err(NetError::protocol(format!(
"Packet payload {} exceeds max size {}",
payload_size, self.max_payload_size
)));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AudioPacket {
pub header: RtpHeader,
pub samples: Bytes,
pub num_samples: usize,
}
impl AudioPacket {
#[must_use]
pub fn new(header: RtpHeader, samples: Bytes, num_samples: usize) -> Self {
Self {
header,
samples,
num_samples,
}
}
pub fn from_rtp(rtp_packet: &RtpPacket, config: &AudioConfig) -> NetResult<Self> {
let num_samples = rtp_packet.payload.len() / config.bytes_per_sample();
Ok(Self {
header: rtp_packet.header.clone(),
samples: rtp_packet.payload.clone(),
num_samples,
})
}
#[must_use]
pub fn to_rtp(&self) -> RtpPacket {
RtpPacket {
header: self.header.clone(),
payload: self.samples.clone(),
}
}
}
pub struct AudioEncoder {
config: AudioConfig,
current_timestamp: u32,
sequence_number: u16,
ssrc: u32,
sample_buffer: Vec<u8>,
}
impl AudioEncoder {
pub fn new(config: AudioConfig, ssrc: u32) -> NetResult<Self> {
config.validate()?;
Ok(Self {
config,
current_timestamp: rand::random(),
sequence_number: rand::random(),
ssrc,
sample_buffer: Vec::new(),
})
}
pub fn encode_samples(&mut self, samples: &[u8]) -> NetResult<Vec<AudioPacket>> {
self.sample_buffer.extend_from_slice(samples);
let mut packets = Vec::new();
let samples_per_packet = self.config.samples_per_packet();
let bytes_per_sample = self.config.bytes_per_sample();
let packet_size = samples_per_packet * bytes_per_sample;
while self.sample_buffer.len() >= packet_size {
let packet_data = self.sample_buffer.drain(..packet_size).collect::<Vec<_>>();
let header = RtpHeader {
padding: false,
extension: false,
csrc_count: 0,
marker: false,
payload_type: RTP_PAYLOAD_TYPE_AUDIO,
sequence_number: self.sequence_number,
timestamp: self.current_timestamp,
ssrc: self.ssrc,
csrcs: Vec::new(),
extension_data: None,
};
packets.push(AudioPacket::new(
header,
Bytes::from(packet_data),
samples_per_packet,
));
self.sequence_number = self.sequence_number.wrapping_add(1);
self.current_timestamp = self
.current_timestamp
.wrapping_add(samples_per_packet as u32);
}
Ok(packets)
}
pub fn flush(&mut self) -> NetResult<Option<AudioPacket>> {
if self.sample_buffer.is_empty() {
return Ok(None);
}
let bytes_per_sample = self.config.bytes_per_sample();
let num_samples = self.sample_buffer.len() / bytes_per_sample;
if num_samples == 0 {
return Ok(None);
}
let packet_data = self.sample_buffer.drain(..).collect::<Vec<_>>();
let header = RtpHeader {
padding: false,
extension: false,
csrc_count: 0,
marker: true, payload_type: RTP_PAYLOAD_TYPE_AUDIO,
sequence_number: self.sequence_number,
timestamp: self.current_timestamp,
ssrc: self.ssrc,
csrcs: Vec::new(),
extension_data: None,
};
self.sequence_number = self.sequence_number.wrapping_add(1);
self.current_timestamp = self.current_timestamp.wrapping_add(num_samples as u32);
Ok(Some(AudioPacket::new(
header,
Bytes::from(packet_data),
num_samples,
)))
}
#[must_use]
pub const fn current_timestamp(&self) -> u32 {
self.current_timestamp
}
#[must_use]
pub const fn config(&self) -> &AudioConfig {
&self.config
}
}
#[derive(Debug)]
pub struct AudioDecoder {
config: AudioConfig,
packet_buffer: HashMap<u32, AudioPacket>,
next_timestamp: Option<u32>,
max_buffered_packets: usize,
}
impl AudioDecoder {
pub fn new(config: AudioConfig) -> Self {
Self {
config,
packet_buffer: HashMap::new(),
next_timestamp: None,
max_buffered_packets: 50,
}
}
pub fn process_rtp_packet(&mut self, rtp_packet: &RtpPacket) -> NetResult<()> {
let audio_packet = AudioPacket::from_rtp(rtp_packet, &self.config)?;
let timestamp = audio_packet.header.timestamp;
if self.next_timestamp.is_none() {
self.next_timestamp = Some(timestamp);
}
self.packet_buffer.insert(timestamp, audio_packet);
if self.packet_buffer.len() > self.max_buffered_packets {
if let Some(oldest_ts) = self.packet_buffer.keys().min().copied() {
self.packet_buffer.remove(&oldest_ts);
}
}
Ok(())
}
pub fn get_next_packet(&mut self) -> Option<AudioPacket> {
if let Some(ts) = self.next_timestamp {
if let Some(packet) = self.packet_buffer.remove(&ts) {
let samples_per_packet = self.config.samples_per_packet() as u32;
self.next_timestamp = Some(ts.wrapping_add(samples_per_packet));
return Some(packet);
}
}
None
}
pub fn get_available_packets(&mut self) -> Vec<AudioPacket> {
let mut packets = Vec::new();
while let Some(packet) = self.get_next_packet() {
packets.push(packet);
}
packets
}
pub fn decode_packets(&mut self) -> Vec<u8> {
let packets = self.get_available_packets();
let mut samples = Vec::new();
for packet in packets {
samples.extend_from_slice(&packet.samples);
}
samples
}
#[must_use]
pub const fn config(&self) -> &AudioConfig {
&self.config
}
}
pub mod packing {
pub fn pack_24bit(sample: i32) -> [u8; 3] {
[
((sample >> 16) & 0xFF) as u8,
((sample >> 8) & 0xFF) as u8,
(sample & 0xFF) as u8,
]
}
#[must_use]
pub fn unpack_24bit(bytes: &[u8; 3]) -> i32 {
let value = (i32::from(bytes[0]) << 16) | (i32::from(bytes[1]) << 8) | i32::from(bytes[2]);
if (value & 0x800000) != 0 {
value | 0xFF000000u32 as i32
} else {
value
}
}
pub fn pack_20bit(sample: i32) -> [u8; 3] {
let shifted = sample << 4; pack_24bit(shifted)
}
#[must_use]
pub fn unpack_20bit(bytes: &[u8; 3]) -> i32 {
let value_24 = unpack_24bit(bytes);
value_24 >> 4 }
pub fn pack_16bit(sample: i16) -> [u8; 2] {
[((sample >> 8) & 0xFF) as u8, (sample & 0xFF) as u8]
}
#[must_use]
pub fn unpack_16bit(bytes: &[u8; 2]) -> i16 {
(i16::from(bytes[0]) << 8) | i16::from(bytes[1])
}
pub fn interleave_stereo(left: &[i32], right: &[i32]) -> Vec<i32> {
let mut interleaved = Vec::with_capacity(left.len() + right.len());
for (l, r) in left.iter().zip(right.iter()) {
interleaved.push(*l);
interleaved.push(*r);
}
interleaved
}
pub fn deinterleave_stereo(interleaved: &[i32]) -> (Vec<i32>, Vec<i32>) {
let mut left = Vec::with_capacity(interleaved.len() / 2);
let mut right = Vec::with_capacity(interleaved.len() / 2);
for chunk in interleaved.chunks_exact(2) {
left.push(chunk[0]);
right.push(chunk[1]);
}
(left, right)
}
pub fn pack_multichannel(samples: &[i32], _channels: usize, bit_depth: u8) -> Vec<u8> {
let mut packed = Vec::new();
for sample in samples {
let bytes = match bit_depth {
16 => {
let s16 = (*sample as i16).to_be_bytes();
vec![s16[0], s16[1]]
}
20 => pack_20bit(*sample).to_vec(),
24 => pack_24bit(*sample).to_vec(),
_ => pack_24bit(*sample).to_vec(),
};
packed.extend_from_slice(&bytes);
}
packed
}
pub fn unpack_multichannel(data: &[u8], _channels: usize, bit_depth: u8) -> Vec<i32> {
let bytes_per_sample = match bit_depth {
16 => 2,
20 | 24 => 3,
_ => 3,
};
let mut samples = Vec::new();
for chunk in data.chunks_exact(bytes_per_sample) {
let sample = match bit_depth {
16 => {
if chunk.len() >= 2 {
i32::from(unpack_16bit(&[chunk[0], chunk[1]]))
} else {
0
}
}
20 => {
if chunk.len() >= 3 {
unpack_20bit(&[chunk[0], chunk[1], chunk[2]])
} else {
0
}
}
24 => {
if chunk.len() >= 3 {
unpack_24bit(&[chunk[0], chunk[1], chunk[2]])
} else {
0
}
}
_ => 0,
};
samples.push(sample);
}
samples
}
}
pub mod aes3 {
#[derive(Debug, Clone)]
pub struct AES3Frame {
pub subframe_a: AES3Subframe,
pub subframe_b: AES3Subframe,
}
#[derive(Debug, Clone, Copy)]
pub struct AES3Subframe {
pub preamble: u8,
pub audio_sample: i32,
pub validity: bool,
pub user_data: bool,
pub channel_status: bool,
pub parity: bool,
}
impl AES3Subframe {
#[must_use]
pub const fn new(audio_sample: i32) -> Self {
Self {
preamble: 0,
audio_sample,
validity: false,
user_data: false,
channel_status: false,
parity: false,
}
}
#[must_use]
pub fn pack(&self) -> u32 {
let mut word = 0u32;
word |= u32::from(self.preamble & 0x0F);
word |= ((self.audio_sample as u32) & 0xFFFFFF) << 4;
if self.validity {
word |= 1 << 28;
}
if self.user_data {
word |= 1 << 29;
}
if self.channel_status {
word |= 1 << 30;
}
if self.parity {
word |= 1 << 31;
}
word
}
#[must_use]
pub fn unpack(word: u32) -> Self {
let preamble = (word & 0x0F) as u8;
let audio_sample = ((word >> 4) & 0xFFFFFF) as i32;
let validity = (word & (1 << 28)) != 0;
let user_data = (word & (1 << 29)) != 0;
let channel_status = (word & (1 << 30)) != 0;
let parity = (word & (1 << 31)) != 0;
Self {
preamble,
audio_sample,
validity,
user_data,
channel_status,
parity,
}
}
}
impl AES3Frame {
#[must_use]
pub fn new(left: i32, right: i32) -> Self {
Self {
subframe_a: AES3Subframe::new(left),
subframe_b: AES3Subframe::new(right),
}
}
pub fn pack(&self) -> [u8; 8] {
let word_a = self.subframe_a.pack();
let word_b = self.subframe_b.pack();
let mut bytes = [0u8; 8];
bytes[0..4].copy_from_slice(&word_a.to_be_bytes());
bytes[4..8].copy_from_slice(&word_b.to_be_bytes());
bytes
}
#[must_use]
pub fn unpack(bytes: &[u8; 8]) -> Self {
let mut word_a_bytes = [0u8; 4];
let mut word_b_bytes = [0u8; 4];
word_a_bytes.copy_from_slice(&bytes[0..4]);
word_b_bytes.copy_from_slice(&bytes[4..8]);
let word_a = u32::from_be_bytes(word_a_bytes);
let word_b = u32::from_be_bytes(word_b_bytes);
Self {
subframe_a: AES3Subframe::unpack(word_a),
subframe_b: AES3Subframe::unpack(word_b),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audio_sample_rate() {
assert_eq!(AudioSampleRate::Rate48kHz.as_u32(), 48000);
assert_eq!(AudioSampleRate::Rate96kHz.as_u32(), 96000);
let rate = AudioSampleRate::from_u32(48000).expect("should succeed in test");
assert_eq!(rate, AudioSampleRate::Rate48kHz);
}
#[test]
fn test_audio_config() {
let config = AudioConfig {
sample_rate: AudioSampleRate::Rate48kHz,
bit_depth: 24,
channels: 2,
format: AudioFormat::LinearPCM,
packet_time_us: 1000,
max_payload_size: MAX_RTP_PAYLOAD,
};
assert_eq!(config.samples_per_packet(), 48);
assert_eq!(config.bytes_per_sample(), 6); assert!(config.bitrate() > 0);
assert!(config.validate().is_ok());
}
#[test]
fn test_24bit_packing() {
let sample = 0x123456;
let packed = packing::pack_24bit(sample);
let unpacked = packing::unpack_24bit(&packed);
assert_eq!(sample, unpacked);
}
#[test]
fn test_16bit_packing() {
let sample = 0x1234i16;
let packed = packing::pack_16bit(sample);
let unpacked = packing::unpack_16bit(&packed);
assert_eq!(sample, unpacked);
}
#[test]
fn test_stereo_interleave() {
let left = vec![100, 200, 300];
let right = vec![400, 500, 600];
let interleaved = packing::interleave_stereo(&left, &right);
assert_eq!(interleaved, vec![100, 400, 200, 500, 300, 600]);
let (left_out, right_out) = packing::deinterleave_stereo(&interleaved);
assert_eq!(left, left_out);
assert_eq!(right, right_out);
}
#[test]
fn test_audio_encoder() {
let config = AudioConfig {
sample_rate: AudioSampleRate::Rate48kHz,
bit_depth: 24,
channels: 2,
format: AudioFormat::LinearPCM,
packet_time_us: 1000,
max_payload_size: MAX_RTP_PAYLOAD,
};
let mut encoder = AudioEncoder::new(config.clone(), 12345).expect("should succeed in test");
let num_samples = config.samples_per_packet();
let bytes_per_sample = config.bytes_per_sample();
let sample_data = vec![0u8; num_samples * bytes_per_sample];
let packets = encoder
.encode_samples(&sample_data)
.expect("should succeed in test");
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].num_samples, num_samples);
}
#[test]
fn test_audio_decoder() {
let config = AudioConfig {
sample_rate: AudioSampleRate::Rate48kHz,
bit_depth: 24,
channels: 2,
format: AudioFormat::LinearPCM,
packet_time_us: 1000,
max_payload_size: MAX_RTP_PAYLOAD,
};
let mut decoder = AudioDecoder::new(config.clone());
let num_samples = config.samples_per_packet();
let bytes_per_sample = config.bytes_per_sample();
let sample_data = vec![0u8; num_samples * bytes_per_sample];
let header = RtpHeader {
padding: false,
extension: false,
csrc_count: 0,
marker: false,
payload_type: RTP_PAYLOAD_TYPE_AUDIO,
sequence_number: 1,
timestamp: 0,
ssrc: 12345,
csrcs: Vec::new(),
extension_data: None,
};
let rtp_packet = RtpPacket {
header,
payload: Bytes::from(sample_data),
};
decoder
.process_rtp_packet(&rtp_packet)
.expect("should succeed in test");
let packets = decoder.get_available_packets();
assert_eq!(packets.len(), 1);
}
#[test]
fn test_aes3_subframe() {
let sample = 0x123456;
let subframe = aes3::AES3Subframe::new(sample);
let word = subframe.pack();
let unpacked = aes3::AES3Subframe::unpack(word);
assert_eq!(unpacked.audio_sample, sample);
}
#[test]
fn test_aes3_frame() {
let left = 0x111111;
let right = 0x222222;
let frame = aes3::AES3Frame::new(left, right);
let packed = frame.pack();
let unpacked = aes3::AES3Frame::unpack(&packed);
assert_eq!(unpacked.subframe_a.audio_sample, left);
assert_eq!(unpacked.subframe_b.audio_sample, right);
}
}