Skip to main content

oxideav_basic/
pcm.rs

1//! PCM codec: uncompressed interleaved linear PCM.
2//!
3//! A PCM "codec" is trivial — the packet payload *is* the sample data. We
4//! still funnel it through [`Decoder`] / [`Encoder`] so that pipelines treat it
5//! uniformly.
6//!
7//! Codec IDs:
8//! - `pcm_u8`   — unsigned 8-bit
9//! - `pcm_s16le` — signed 16-bit little-endian
10//! - `pcm_s24le` — signed 24-bit little-endian, packed
11//! - `pcm_s32le` — signed 32-bit little-endian
12//! - `pcm_f32le` — 32-bit IEEE float little-endian
13//! - `pcm_f64le` — 64-bit IEEE float little-endian
14//!
15//! Asterisk-style signed-linear aliases:
16//! - `slin`, `slin8`, `slin16`, `slin24`, `slin32`, `slin44`, `slin48`,
17//!   `slin96`, `slin192` — all map onto the `pcm_s16le` implementation.
18//!   The trailing digits only indicate the implied sample rate of the
19//!   surrounding headerless `.sln*` container (see `slin.rs`); as a codec
20//!   they are indistinguishable from `pcm_s16le`.
21
22use oxideav_codec::{CodecRegistry, Decoder, Encoder};
23use oxideav_core::{
24    AudioFrame, CodecCapabilities, CodecId, CodecParameters, Error, Frame, MediaType, Packet,
25    Result, SampleFormat, TimeBase,
26};
27
28pub fn register(reg: &mut CodecRegistry) {
29    for id in CODEC_IDS {
30        let cid = CodecId::new(*id);
31        let caps = CodecCapabilities::audio(format!("{id}_sw"))
32            .with_lossless(true)
33            .with_intra_only(true);
34        reg.register_both(cid, caps, make_decoder, make_encoder);
35    }
36    for id in SLIN_ALIASES {
37        let cid = CodecId::new(*id);
38        let caps = CodecCapabilities::audio(format!("{id}_sw"))
39            .with_lossless(true)
40            .with_intra_only(true);
41        // Same factories as pcm_s16le — `sample_format_for` maps all the
42        // slin aliases to SampleFormat::S16 below.
43        reg.register_both(cid, caps, make_decoder, make_encoder);
44    }
45}
46
47const CODEC_IDS: &[&str] = &[
48    "pcm_u8",
49    "pcm_s8",
50    "pcm_s16le",
51    "pcm_s24le",
52    "pcm_s32le",
53    "pcm_f32le",
54    "pcm_f64le",
55];
56
57/// Asterisk "signed linear" codec-id aliases. All are S16LE; the trailing
58/// digits only matter at the container layer (see `slin.rs`).
59pub(crate) const SLIN_ALIASES: &[&str] = &[
60    "slin", "slin8", "slin16", "slin24", "slin32", "slin44", "slin48", "slin96", "slin192",
61];
62
63/// Return the [`SampleFormat`] implied by a PCM codec ID.
64///
65/// Also accepts the Asterisk `slin*` aliases, all of which describe 16-bit
66/// signed linear PCM.
67pub fn sample_format_for(id: &CodecId) -> Option<SampleFormat> {
68    let s = id.as_str();
69    Some(match s {
70        "pcm_u8" => SampleFormat::U8,
71        "pcm_s8" => SampleFormat::S8,
72        "pcm_s16le" => SampleFormat::S16,
73        "pcm_s24le" => SampleFormat::S24,
74        "pcm_s32le" => SampleFormat::S32,
75        "pcm_f32le" => SampleFormat::F32,
76        "pcm_f64le" => SampleFormat::F64,
77        _ if SLIN_ALIASES.contains(&s) => SampleFormat::S16,
78        _ => return None,
79    })
80}
81
82/// Return the canonical PCM codec ID for a [`SampleFormat`]. Planar formats
83/// have no direct PCM codec — the caller must convert to interleaved first.
84pub fn codec_id_for(fmt: SampleFormat) -> Option<CodecId> {
85    Some(CodecId::new(match fmt {
86        SampleFormat::U8 => "pcm_u8",
87        SampleFormat::S8 => "pcm_s8",
88        SampleFormat::S16 => "pcm_s16le",
89        SampleFormat::S24 => "pcm_s24le",
90        SampleFormat::S32 => "pcm_s32le",
91        SampleFormat::F32 => "pcm_f32le",
92        SampleFormat::F64 => "pcm_f64le",
93        _ => return None,
94    }))
95}
96
97fn make_decoder(params: &CodecParameters) -> Result<Box<dyn Decoder>> {
98    let format = sample_format_for(&params.codec_id)
99        .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
100    let channels = params
101        .channels
102        .ok_or_else(|| Error::invalid("PCM decoder requires channels"))?;
103    let sample_rate = params
104        .sample_rate
105        .ok_or_else(|| Error::invalid("PCM decoder requires sample_rate"))?;
106    Ok(Box::new(PcmDecoder {
107        id: params.codec_id.clone(),
108        format,
109        channels,
110        sample_rate,
111        pending: None,
112        eof: false,
113    }))
114}
115
116fn make_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
117    let format = sample_format_for(&params.codec_id)
118        .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
119    let channels = params
120        .channels
121        .ok_or_else(|| Error::invalid("PCM encoder requires channels"))?;
122    let sample_rate = params
123        .sample_rate
124        .ok_or_else(|| Error::invalid("PCM encoder requires sample_rate"))?;
125    let mut output = params.clone();
126    output.media_type = MediaType::Audio;
127    output.sample_format = Some(format);
128    Ok(Box::new(PcmEncoder {
129        format,
130        channels,
131        sample_rate,
132        output,
133        queue: std::collections::VecDeque::new(),
134    }))
135}
136
137struct PcmDecoder {
138    id: CodecId,
139    format: SampleFormat,
140    channels: u16,
141    sample_rate: u32,
142    pending: Option<Packet>,
143    eof: bool,
144}
145
146impl Decoder for PcmDecoder {
147    fn codec_id(&self) -> &CodecId {
148        &self.id
149    }
150
151    fn send_packet(&mut self, packet: &Packet) -> Result<()> {
152        if self.pending.is_some() {
153            return Err(Error::other(
154                "PCM decoder already has a buffered packet; call receive_frame first",
155            ));
156        }
157        self.pending = Some(packet.clone());
158        Ok(())
159    }
160
161    fn receive_frame(&mut self) -> Result<Frame> {
162        let Some(pkt) = self.pending.take() else {
163            return if self.eof {
164                Err(Error::Eof)
165            } else {
166                Err(Error::NeedMore)
167            };
168        };
169        let bps = self.format.bytes_per_sample();
170        let block = bps * self.channels as usize;
171        if block == 0 || pkt.data.len() % block != 0 {
172            return Err(Error::invalid("PCM packet size not a multiple of block"));
173        }
174        let samples = (pkt.data.len() / block) as u32;
175        Ok(Frame::Audio(AudioFrame {
176            format: self.format,
177            channels: self.channels,
178            sample_rate: self.sample_rate,
179            samples,
180            pts: pkt.pts,
181            time_base: pkt.time_base,
182            data: vec![pkt.data],
183        }))
184    }
185
186    fn flush(&mut self) -> Result<()> {
187        self.eof = true;
188        Ok(())
189    }
190}
191
192struct PcmEncoder {
193    format: SampleFormat,
194    channels: u16,
195    sample_rate: u32,
196    output: CodecParameters,
197    queue: std::collections::VecDeque<Packet>,
198}
199
200impl Encoder for PcmEncoder {
201    fn codec_id(&self) -> &CodecId {
202        &self.output.codec_id
203    }
204
205    fn output_params(&self) -> &CodecParameters {
206        &self.output
207    }
208
209    fn send_frame(&mut self, frame: &Frame) -> Result<()> {
210        let Frame::Audio(a) = frame else {
211            return Err(Error::invalid("PCM encoder requires audio frames"));
212        };
213        if a.format != self.format
214            || a.channels != self.channels
215            || a.sample_rate != self.sample_rate
216        {
217            return Err(Error::invalid(
218                "PCM encoder frame parameters do not match encoder configuration",
219            ));
220        }
221        if a.format.is_planar() {
222            return Err(Error::unsupported(
223                "PCM encoder takes interleaved input; convert planar → interleaved first",
224            ));
225        }
226        let data = a
227            .data
228            .first()
229            .ok_or_else(|| Error::invalid("empty audio frame"))?
230            .clone();
231        let bps = a.format.bytes_per_sample() * a.channels as usize;
232        let expected = bps * a.samples as usize;
233        if data.len() != expected {
234            return Err(Error::invalid("audio frame data length mismatch"));
235        }
236        let mut pkt = Packet::new(0, a.time_base, data);
237        pkt.pts = a.pts;
238        pkt.dts = a.pts;
239        pkt.duration = Some(a.samples as i64);
240        pkt.flags.keyframe = true;
241        self.queue.push_back(pkt);
242        Ok(())
243    }
244
245    fn receive_packet(&mut self) -> Result<Packet> {
246        self.queue.pop_front().ok_or(Error::NeedMore)
247    }
248
249    fn flush(&mut self) -> Result<()> {
250        Ok(())
251    }
252}
253
254/// Helper to build codec parameters for a PCM stream.
255pub fn params(format: SampleFormat, channels: u16, sample_rate: u32) -> Result<CodecParameters> {
256    let codec_id = codec_id_for(format)
257        .ok_or_else(|| Error::unsupported(format!("no PCM codec for {:?}", format)))?;
258    let mut p = CodecParameters::audio(codec_id);
259    p.sample_format = Some(format);
260    p.channels = Some(channels);
261    p.sample_rate = Some(sample_rate);
262    p.bit_rate =
263        Some((format.bytes_per_sample() as u64) * 8 * (channels as u64) * (sample_rate as u64));
264    Ok(p)
265}
266
267/// Default time base for a PCM audio stream: 1 / sample_rate.
268pub fn time_base_for(sample_rate: u32) -> TimeBase {
269    TimeBase::new(1, sample_rate as i64)
270}