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::{CodecInfo, CodecRegistry, Decoder, Encoder};
23use oxideav_core::{
24    AudioFrame, CodecCapabilities, CodecId, CodecParameters, CodecTag, Error, Frame, MediaType,
25    Packet, ProbeContext, Result, SampleFormat, TimeBase,
26};
27
28pub fn register(reg: &mut CodecRegistry) {
29    // WAVEFORMATEX tags handled by this crate:
30    //   0x0001 WAVE_FORMAT_PCM — integer PCM, bit-depth disambiguation
31    //     by bits_per_sample.
32    //   0x0003 WAVE_FORMAT_IEEE_FLOAT — float PCM, same idea.
33    let wf_int = CodecTag::wave_format(0x0001);
34    let wf_flt = CodecTag::wave_format(0x0003);
35    for (id, bits, tag, probe) in [
36        (
37            "pcm_u8",
38            8u16,
39            Some(&wf_int),
40            probe_pcm_u8 as oxideav_core::ProbeFn,
41        ),
42        ("pcm_s8", 8, None, probe_pcm_s8 as oxideav_core::ProbeFn),
43        (
44            "pcm_s16le",
45            16,
46            Some(&wf_int),
47            probe_pcm_s16le as oxideav_core::ProbeFn,
48        ),
49        (
50            "pcm_s24le",
51            24,
52            Some(&wf_int),
53            probe_pcm_s24le as oxideav_core::ProbeFn,
54        ),
55        (
56            "pcm_s32le",
57            32,
58            Some(&wf_int),
59            probe_pcm_s32le as oxideav_core::ProbeFn,
60        ),
61        (
62            "pcm_f32le",
63            32,
64            Some(&wf_flt),
65            probe_pcm_f32le as oxideav_core::ProbeFn,
66        ),
67        (
68            "pcm_f64le",
69            64,
70            Some(&wf_flt),
71            probe_pcm_f64le as oxideav_core::ProbeFn,
72        ),
73    ] {
74        let _ = bits;
75        let caps = CodecCapabilities::audio(format!("{id}_sw"))
76            .with_lossless(true)
77            .with_intra_only(true);
78        let mut info = CodecInfo::new(CodecId::new(id))
79            .capabilities(caps)
80            .decoder(make_decoder)
81            .encoder(make_encoder);
82        if let Some(t) = tag {
83            info = info.probe(probe).tag(t.clone());
84        }
85        reg.register(info);
86    }
87
88    for id in SLIN_ALIASES {
89        let caps = CodecCapabilities::audio(format!("{id}_sw"))
90            .with_lossless(true)
91            .with_intra_only(true);
92        // Same factories as pcm_s16le — `sample_format_for` maps all the
93        // slin aliases to SampleFormat::S16 below. No WAVEFORMATEX claim.
94        reg.register(
95            CodecInfo::new(CodecId::new(*id))
96                .capabilities(caps)
97                .decoder(make_decoder)
98                .encoder(make_encoder),
99        );
100    }
101}
102
103// --- Per-variant PCM probes -----------------------------------------------
104// Each probe is selected by the WAVEFORMATEX tag at the call site; here we
105// only need to disambiguate by bit depth. `bits_per_sample = None` returns
106// 0.0 so the registry resolves nothing — the AVI demuxer then falls back
107// to its static table which picks a sensible default.
108
109fn match_bits(ctx: &ProbeContext, expected: u16) -> f32 {
110    match ctx.bits_per_sample {
111        Some(b) if b == expected => 1.0,
112        _ => 0.0,
113    }
114}
115
116fn probe_pcm_u8(ctx: &ProbeContext) -> f32 {
117    match_bits(ctx, 8)
118}
119fn probe_pcm_s8(_ctx: &ProbeContext) -> f32 {
120    // pcm_s8 has no canonical WAVEFORMATEX mapping; probe is unused but
121    // required by the ProbeFn type alias in the register() table.
122    0.0
123}
124fn probe_pcm_s16le(ctx: &ProbeContext) -> f32 {
125    match ctx.bits_per_sample {
126        // bits_per_sample == 0 occasionally means "unspecified" in the
127        // wild — WAV files tagged as PCM that omit the depth. Treat
128        // that as s16le (the most common default) with middling
129        // confidence so a specific claimant (e.g. pcm_s24le with
130        // bits=24) still wins if the depth is actually set.
131        Some(0) => 0.5,
132        Some(16) => 1.0,
133        _ => 0.0,
134    }
135}
136fn probe_pcm_s24le(ctx: &ProbeContext) -> f32 {
137    match_bits(ctx, 24)
138}
139fn probe_pcm_s32le(ctx: &ProbeContext) -> f32 {
140    match_bits(ctx, 32)
141}
142fn probe_pcm_f32le(ctx: &ProbeContext) -> f32 {
143    match ctx.bits_per_sample {
144        // IEEE float defaults to f32 when depth unspecified.
145        None | Some(0) => 0.5,
146        Some(32) => 1.0,
147        _ => 0.0,
148    }
149}
150fn probe_pcm_f64le(ctx: &ProbeContext) -> f32 {
151    match_bits(ctx, 64)
152}
153
154/// Asterisk "signed linear" codec-id aliases. All are S16LE; the trailing
155/// digits only matter at the container layer (see `slin.rs`).
156pub(crate) const SLIN_ALIASES: &[&str] = &[
157    "slin", "slin8", "slin16", "slin24", "slin32", "slin44", "slin48", "slin96", "slin192",
158];
159
160/// Return the [`SampleFormat`] implied by a PCM codec ID.
161///
162/// Also accepts the Asterisk `slin*` aliases, all of which describe 16-bit
163/// signed linear PCM.
164pub fn sample_format_for(id: &CodecId) -> Option<SampleFormat> {
165    let s = id.as_str();
166    Some(match s {
167        "pcm_u8" => SampleFormat::U8,
168        "pcm_s8" => SampleFormat::S8,
169        "pcm_s16le" => SampleFormat::S16,
170        "pcm_s24le" => SampleFormat::S24,
171        "pcm_s32le" => SampleFormat::S32,
172        "pcm_f32le" => SampleFormat::F32,
173        "pcm_f64le" => SampleFormat::F64,
174        _ if SLIN_ALIASES.contains(&s) => SampleFormat::S16,
175        _ => return None,
176    })
177}
178
179/// Return the canonical PCM codec ID for a [`SampleFormat`]. Planar formats
180/// have no direct PCM codec — the caller must convert to interleaved first.
181pub fn codec_id_for(fmt: SampleFormat) -> Option<CodecId> {
182    Some(CodecId::new(match fmt {
183        SampleFormat::U8 => "pcm_u8",
184        SampleFormat::S8 => "pcm_s8",
185        SampleFormat::S16 => "pcm_s16le",
186        SampleFormat::S24 => "pcm_s24le",
187        SampleFormat::S32 => "pcm_s32le",
188        SampleFormat::F32 => "pcm_f32le",
189        SampleFormat::F64 => "pcm_f64le",
190        _ => return None,
191    }))
192}
193
194fn make_decoder(params: &CodecParameters) -> Result<Box<dyn Decoder>> {
195    let format = sample_format_for(&params.codec_id)
196        .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
197    let channels = params
198        .channels
199        .ok_or_else(|| Error::invalid("PCM decoder requires channels"))?;
200    let sample_rate = params
201        .sample_rate
202        .ok_or_else(|| Error::invalid("PCM decoder requires sample_rate"))?;
203    Ok(Box::new(PcmDecoder {
204        id: params.codec_id.clone(),
205        format,
206        channels,
207        sample_rate,
208        pending: None,
209        eof: false,
210    }))
211}
212
213fn make_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
214    let format = sample_format_for(&params.codec_id)
215        .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
216    let channels = params
217        .channels
218        .ok_or_else(|| Error::invalid("PCM encoder requires channels"))?;
219    let sample_rate = params
220        .sample_rate
221        .ok_or_else(|| Error::invalid("PCM encoder requires sample_rate"))?;
222    let mut output = params.clone();
223    output.media_type = MediaType::Audio;
224    output.sample_format = Some(format);
225    Ok(Box::new(PcmEncoder {
226        format,
227        channels,
228        sample_rate,
229        output,
230        queue: std::collections::VecDeque::new(),
231    }))
232}
233
234struct PcmDecoder {
235    id: CodecId,
236    format: SampleFormat,
237    channels: u16,
238    sample_rate: u32,
239    pending: Option<Packet>,
240    eof: bool,
241}
242
243impl Decoder for PcmDecoder {
244    fn codec_id(&self) -> &CodecId {
245        &self.id
246    }
247
248    fn send_packet(&mut self, packet: &Packet) -> Result<()> {
249        if self.pending.is_some() {
250            return Err(Error::other(
251                "PCM decoder already has a buffered packet; call receive_frame first",
252            ));
253        }
254        self.pending = Some(packet.clone());
255        Ok(())
256    }
257
258    fn receive_frame(&mut self) -> Result<Frame> {
259        let Some(pkt) = self.pending.take() else {
260            return if self.eof {
261                Err(Error::Eof)
262            } else {
263                Err(Error::NeedMore)
264            };
265        };
266        let bps = self.format.bytes_per_sample();
267        let block = bps * self.channels as usize;
268        if block == 0 || pkt.data.len() % block != 0 {
269            return Err(Error::invalid("PCM packet size not a multiple of block"));
270        }
271        let samples = (pkt.data.len() / block) as u32;
272        Ok(Frame::Audio(AudioFrame {
273            format: self.format,
274            channels: self.channels,
275            sample_rate: self.sample_rate,
276            samples,
277            pts: pkt.pts,
278            time_base: pkt.time_base,
279            data: vec![pkt.data],
280        }))
281    }
282
283    fn flush(&mut self) -> Result<()> {
284        self.eof = true;
285        Ok(())
286    }
287}
288
289struct PcmEncoder {
290    format: SampleFormat,
291    channels: u16,
292    sample_rate: u32,
293    output: CodecParameters,
294    queue: std::collections::VecDeque<Packet>,
295}
296
297impl Encoder for PcmEncoder {
298    fn codec_id(&self) -> &CodecId {
299        &self.output.codec_id
300    }
301
302    fn output_params(&self) -> &CodecParameters {
303        &self.output
304    }
305
306    fn send_frame(&mut self, frame: &Frame) -> Result<()> {
307        let Frame::Audio(a) = frame else {
308            return Err(Error::invalid("PCM encoder requires audio frames"));
309        };
310        if a.format != self.format
311            || a.channels != self.channels
312            || a.sample_rate != self.sample_rate
313        {
314            return Err(Error::invalid(
315                "PCM encoder frame parameters do not match encoder configuration",
316            ));
317        }
318        if a.format.is_planar() {
319            return Err(Error::unsupported(
320                "PCM encoder takes interleaved input; convert planar → interleaved first",
321            ));
322        }
323        let data = a
324            .data
325            .first()
326            .ok_or_else(|| Error::invalid("empty audio frame"))?
327            .clone();
328        let bps = a.format.bytes_per_sample() * a.channels as usize;
329        let expected = bps * a.samples as usize;
330        if data.len() != expected {
331            return Err(Error::invalid("audio frame data length mismatch"));
332        }
333        let mut pkt = Packet::new(0, a.time_base, data);
334        pkt.pts = a.pts;
335        pkt.dts = a.pts;
336        pkt.duration = Some(a.samples as i64);
337        pkt.flags.keyframe = true;
338        self.queue.push_back(pkt);
339        Ok(())
340    }
341
342    fn receive_packet(&mut self) -> Result<Packet> {
343        self.queue.pop_front().ok_or(Error::NeedMore)
344    }
345
346    fn flush(&mut self) -> Result<()> {
347        Ok(())
348    }
349}
350
351/// Helper to build codec parameters for a PCM stream.
352pub fn params(format: SampleFormat, channels: u16, sample_rate: u32) -> Result<CodecParameters> {
353    let codec_id = codec_id_for(format)
354        .ok_or_else(|| Error::unsupported(format!("no PCM codec for {:?}", format)))?;
355    let mut p = CodecParameters::audio(codec_id);
356    p.sample_format = Some(format);
357    p.channels = Some(channels);
358    p.sample_rate = Some(sample_rate);
359    p.bit_rate =
360        Some((format.bytes_per_sample() as u64) * 8 * (channels as u64) * (sample_rate as u64));
361    Ok(p)
362}
363
364/// Default time base for a PCM audio stream: 1 / sample_rate.
365pub fn time_base_for(sample_rate: u32) -> TimeBase {
366    TimeBase::new(1, sample_rate as i64)
367}