use crate::{Error, Result};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum AudioCodec {
#[default]
PcmS16,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AudioEncodeProfile {
pub codec: AudioCodec,
pub sample_rate: u32,
pub channel_count: u32,
pub frames_per_packet: u32,
}
impl Default for AudioEncodeProfile {
fn default() -> Self {
Self {
codec: AudioCodec::PcmS16,
sample_rate: 48_000,
channel_count: 2,
frames_per_packet: 1_024,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncodedAudioPacket {
pub bitstream: Vec<u8>,
pub presentation_frame: i64,
pub duration_frames: u32,
}
#[derive(Debug)]
pub struct AudioEncoder {
state: Option<EncoderState>,
}
#[derive(Debug)]
struct EncoderState {
profile: AudioEncodeProfile,
channels: usize,
packet_samples: usize,
pending: Vec<f32>,
next_input_frame: i64,
}
impl AudioEncoder {
pub fn create(profile: AudioEncodeProfile) -> Result<Self> {
if profile.sample_rate == 0
|| !(1..=8).contains(&profile.channel_count)
|| profile.frames_per_packet == 0
{
return Err(Error::invalid_argument(
"audio encoder requires a sample rate, 1..=8 channels, and a packet size",
));
}
let channels = usize::try_from(profile.channel_count)
.map_err(|_| Error::invalid_argument("audio channel count exceeds usize"))?;
let packet_samples = usize::try_from(profile.frames_per_packet)
.ok()
.and_then(|frames| frames.checked_mul(channels))
.ok_or_else(|| Error::invalid_argument("audio packet shape exceeds usize"))?;
packet_samples
.checked_mul(size_of::<i16>())
.ok_or_else(|| Error::invalid_argument("audio packet byte size exceeds usize"))?;
Ok(Self {
state: Some(EncoderState {
profile,
channels,
packet_samples,
pending: Vec::new(),
next_input_frame: 0,
}),
})
}
pub fn encode(&mut self, interleaved: &[f32]) -> Result<Vec<EncodedAudioPacket>> {
let state = self
.state
.as_mut()
.ok_or_else(|| Error::failed_precondition("audio encoder is closed"))?;
if interleaved.is_empty() || !interleaved.len().is_multiple_of(state.channels) {
return Err(Error::invalid_argument(
"audio encoder input must contain complete non-empty interleaved frames",
));
}
state
.pending
.try_reserve(interleaved.len())
.map_err(|_| Error::resource_exhausted("audio encoder input allocation failed"))?;
state.pending.extend_from_slice(interleaved);
emit_complete_packets(state)
}
pub fn flush(&mut self) -> Result<Vec<EncodedAudioPacket>> {
let Some(state) = self.state.as_mut() else {
return Ok(Vec::new());
};
if state.pending.is_empty() {
return Ok(Vec::new());
}
let frames = u32::try_from(state.pending.len() / state.channels)
.map_err(|_| Error::out_of_range("audio flush frame count exceeds u32"))?;
Ok(vec![emit_packet(state, frames)?])
}
pub fn close(&mut self) {
self.state = None;
}
#[must_use]
pub const fn is_open(&self) -> bool {
self.state.is_some()
}
#[must_use]
pub fn profile(&self) -> Option<&AudioEncodeProfile> {
self.state.as_ref().map(|state| &state.profile)
}
#[must_use]
pub const fn codec_config(&self) -> &[u8] {
&[]
}
#[must_use]
pub const fn priming_frames(&self) -> u32 {
0
}
}
fn emit_complete_packets(state: &mut EncoderState) -> Result<Vec<EncodedAudioPacket>> {
let packet_count = state.pending.len() / state.packet_samples;
let mut packets = Vec::new();
packets
.try_reserve_exact(packet_count)
.map_err(|_| Error::resource_exhausted("audio packet list allocation failed"))?;
for _ in 0..packet_count {
packets.push(emit_packet(state, state.profile.frames_per_packet)?);
}
Ok(packets)
}
fn emit_packet(state: &mut EncoderState, frames: u32) -> Result<EncodedAudioPacket> {
let samples = usize::try_from(frames)
.ok()
.and_then(|frames| frames.checked_mul(state.channels))
.ok_or_else(|| Error::invalid_argument("audio packet sample count exceeds usize"))?;
let byte_count = samples
.checked_mul(size_of::<i16>())
.ok_or_else(|| Error::invalid_argument("audio packet byte size exceeds usize"))?;
let mut bitstream = Vec::new();
bitstream
.try_reserve_exact(byte_count)
.map_err(|_| Error::resource_exhausted("audio packet allocation failed"))?;
for &sample in &state.pending[..samples] {
bitstream.extend_from_slice(&quantize_pcm_s16(sample).to_le_bytes());
}
let packet = EncodedAudioPacket {
bitstream,
presentation_frame: state.next_input_frame,
duration_frames: frames,
};
state.next_input_frame = state
.next_input_frame
.checked_add(i64::from(frames))
.ok_or_else(|| Error::out_of_range("audio presentation frame exceeds i64"))?;
state.pending.drain(..samples);
Ok(packet)
}
fn quantize_pcm_s16(sample: f32) -> i16 {
if sample.is_nan() {
return 0;
}
if sample >= 1.0 {
return i16::MAX;
}
if sample <= -1.0 {
return i16::MIN;
}
let scaled = if sample < 0.0 {
sample * 32_768.0
} else {
sample * 32_767.0
};
scaled.round() as i16
}