Skip to main content

adk_audio/
codec.rs

1//! Audio codec conversion between PCM16 internal format and external formats.
2
3use bytes::Bytes;
4
5use crate::error::{AudioError, AudioResult};
6use crate::frame::AudioFrame;
7
8/// Supported audio formats for encode/decode at transport edges.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum AudioFormat {
11    /// Raw PCM-16 LE (internal format, no header).
12    #[default]
13    Pcm16,
14    /// Opus codec (not yet implemented).
15    Opus,
16    /// MP3 format.
17    Mp3,
18    /// WAV (RIFF) format.
19    Wav,
20    /// FLAC lossless format.
21    Flac,
22    /// Ogg container format.
23    Ogg,
24}
25
26impl AudioFormat {
27    /// Returns `true` if this format has a working `encode()` implementation.
28    ///
29    /// Currently only `Pcm16` and `Wav` support encoding.
30    pub fn supports_encode(&self) -> bool {
31        match self {
32            AudioFormat::Pcm16 => true,
33            AudioFormat::Wav => true,
34            AudioFormat::Opus => false,
35            AudioFormat::Mp3 => false,
36            AudioFormat::Flac => false,
37            AudioFormat::Ogg => false,
38        }
39    }
40
41    /// Returns `true` if this format has a working `decode()` implementation.
42    ///
43    /// Currently only `Pcm16` and `Wav` support decoding.
44    pub fn supports_decode(&self) -> bool {
45        match self {
46            AudioFormat::Pcm16 => true,
47            AudioFormat::Wav => true,
48            AudioFormat::Opus => false,
49            AudioFormat::Mp3 => false,
50            AudioFormat::Flac => false,
51            AudioFormat::Ogg => false,
52        }
53    }
54}
55
56/// Decode encoded bytes into a PCM16 `AudioFrame`.
57///
58/// Currently supports WAV and raw PCM16. Other formats return
59/// `AudioError::Codec`.
60pub fn decode(data: &[u8], format: AudioFormat) -> AudioResult<AudioFrame> {
61    match format {
62        AudioFormat::Pcm16 => {
63            // Raw PCM16 — assume 16kHz mono (caller should know the format)
64            Ok(AudioFrame::new(Bytes::copy_from_slice(data), 16000, 1))
65        }
66        AudioFormat::Wav => decode_wav(data),
67        _ => Err(AudioError::Codec(format!("decoding {format:?} is not yet supported"))),
68    }
69}
70
71/// Encode an `AudioFrame` to the target format.
72///
73/// Currently supports WAV and raw PCM16. Other formats return
74/// `AudioError::Codec`.
75pub fn encode(frame: &AudioFrame, format: AudioFormat) -> AudioResult<Bytes> {
76    match format {
77        AudioFormat::Pcm16 => Ok(frame.data.clone()),
78        AudioFormat::Wav => encode_wav(frame),
79        _ => Err(AudioError::Codec(format!("encoding {format:?} is not yet supported"))),
80    }
81}
82
83/// Encode an `AudioFrame` as a RIFF WAV file.
84fn encode_wav(frame: &AudioFrame) -> AudioResult<Bytes> {
85    let data_len = frame.data.len() as u32;
86    let channels = frame.channels as u16;
87    let sample_rate = frame.sample_rate;
88    let bits_per_sample: u16 = 16;
89    let byte_rate = sample_rate * u32::from(channels) * u32::from(bits_per_sample) / 8;
90    let block_align = channels * bits_per_sample / 8;
91    let file_size = 36 + data_len;
92
93    let mut buf = Vec::with_capacity(44 + frame.data.len());
94    // RIFF header
95    buf.extend_from_slice(b"RIFF");
96    buf.extend_from_slice(&file_size.to_le_bytes());
97    buf.extend_from_slice(b"WAVE");
98    // fmt sub-chunk
99    buf.extend_from_slice(b"fmt ");
100    buf.extend_from_slice(&16u32.to_le_bytes()); // sub-chunk size
101    buf.extend_from_slice(&1u16.to_le_bytes()); // PCM format
102    buf.extend_from_slice(&channels.to_le_bytes());
103    buf.extend_from_slice(&sample_rate.to_le_bytes());
104    buf.extend_from_slice(&byte_rate.to_le_bytes());
105    buf.extend_from_slice(&block_align.to_le_bytes());
106    buf.extend_from_slice(&bits_per_sample.to_le_bytes());
107    // data sub-chunk
108    buf.extend_from_slice(b"data");
109    buf.extend_from_slice(&data_len.to_le_bytes());
110    buf.extend_from_slice(&frame.data);
111
112    Ok(Bytes::from(buf))
113}
114
115/// Decode a RIFF WAV file into an `AudioFrame`.
116fn decode_wav(data: &[u8]) -> AudioResult<AudioFrame> {
117    if data.len() < 44 {
118        return Err(AudioError::Codec("WAV data too short for header".into()));
119    }
120    if &data[0..4] != b"RIFF" || &data[8..12] != b"WAVE" {
121        return Err(AudioError::Codec("invalid WAV header".into()));
122    }
123    let channels = u16::from_le_bytes([data[22], data[23]]);
124    let sample_rate = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
125    let bits_per_sample = u16::from_le_bytes([data[34], data[35]]);
126    if bits_per_sample != 16 {
127        return Err(AudioError::Codec(format!(
128            "unsupported bits per sample: {bits_per_sample}, expected 16"
129        )));
130    }
131    // Find the data chunk
132    let mut offset = 12;
133    while offset + 8 <= data.len() {
134        let chunk_id = &data[offset..offset + 4];
135        let chunk_size = u32::from_le_bytes([
136            data[offset + 4],
137            data[offset + 5],
138            data[offset + 6],
139            data[offset + 7],
140        ]) as usize;
141        if chunk_id == b"data" {
142            let start = offset + 8;
143            let end = (start + chunk_size).min(data.len());
144            let pcm = Bytes::copy_from_slice(&data[start..end]);
145            return Ok(AudioFrame::new(pcm, sample_rate, channels as u8));
146        }
147        offset += 8 + chunk_size;
148    }
149    Err(AudioError::Codec("WAV data chunk not found".into()))
150}