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_core::{
23    AudioFrame, CodecCapabilities, CodecId, CodecParameters, CodecTag, Error, Frame, MediaType,
24    Packet, ProbeContext, Result, SampleFormat, TimeBase,
25};
26use oxideav_core::{CodecInfo, CodecRegistry, Decoder, Encoder};
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    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        pending: None,
208        eof: false,
209    }))
210}
211
212fn make_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
213    let format = sample_format_for(&params.codec_id)
214        .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
215    let channels = params
216        .channels
217        .ok_or_else(|| Error::invalid("PCM encoder requires channels"))?;
218    let sample_rate = params
219        .sample_rate
220        .ok_or_else(|| Error::invalid("PCM encoder requires sample_rate"))?;
221    let mut output = params.clone();
222    output.media_type = MediaType::Audio;
223    output.sample_format = Some(format);
224    Ok(Box::new(PcmEncoder {
225        format,
226        channels,
227        sample_rate,
228        output,
229        queue: std::collections::VecDeque::new(),
230    }))
231}
232
233struct PcmDecoder {
234    id: CodecId,
235    format: SampleFormat,
236    channels: u16,
237    pending: Option<Packet>,
238    eof: bool,
239}
240
241impl Decoder for PcmDecoder {
242    fn codec_id(&self) -> &CodecId {
243        &self.id
244    }
245
246    fn send_packet(&mut self, packet: &Packet) -> Result<()> {
247        if self.pending.is_some() {
248            return Err(Error::other(
249                "PCM decoder already has a buffered packet; call receive_frame first",
250            ));
251        }
252        self.pending = Some(packet.clone());
253        Ok(())
254    }
255
256    fn receive_frame(&mut self) -> Result<Frame> {
257        let Some(pkt) = self.pending.take() else {
258            return if self.eof {
259                Err(Error::Eof)
260            } else {
261                Err(Error::NeedMore)
262            };
263        };
264        let bps = self.format.bytes_per_sample();
265        let block = bps * self.channels as usize;
266        if block == 0 || pkt.data.len() % block != 0 {
267            return Err(Error::invalid("PCM packet size not a multiple of block"));
268        }
269        let samples = (pkt.data.len() / block) as u32;
270        Ok(Frame::Audio(AudioFrame {
271            samples,
272            pts: pkt.pts,
273            data: vec![pkt.data],
274        }))
275    }
276
277    fn flush(&mut self) -> Result<()> {
278        self.eof = true;
279        Ok(())
280    }
281}
282
283struct PcmEncoder {
284    format: SampleFormat,
285    channels: u16,
286    sample_rate: u32,
287    output: CodecParameters,
288    queue: std::collections::VecDeque<Packet>,
289}
290
291impl Encoder for PcmEncoder {
292    fn codec_id(&self) -> &CodecId {
293        &self.output.codec_id
294    }
295
296    fn output_params(&self) -> &CodecParameters {
297        &self.output
298    }
299
300    fn send_frame(&mut self, frame: &Frame) -> Result<()> {
301        let Frame::Audio(a) = frame else {
302            return Err(Error::invalid("PCM encoder requires audio frames"));
303        };
304        // Per-frame format/channels/sample_rate are no longer carried on
305        // AudioFrame — the encoder enforces its configured layout via the
306        // stored fields and the byte-count check below.
307        if self.format.is_planar() {
308            return Err(Error::unsupported(
309                "PCM encoder takes interleaved input; convert planar → interleaved first",
310            ));
311        }
312        let data = a
313            .data
314            .first()
315            .ok_or_else(|| Error::invalid("empty audio frame"))?
316            .clone();
317        let bps = self.format.bytes_per_sample() * self.channels as usize;
318        let expected = bps * a.samples as usize;
319        if data.len() != expected {
320            return Err(Error::invalid("audio frame data length mismatch"));
321        }
322        let mut pkt = Packet::new(
323            0,
324            oxideav_core::TimeBase::new(1, self.sample_rate as i64),
325            data,
326        );
327        pkt.pts = a.pts;
328        pkt.dts = a.pts;
329        pkt.duration = Some(a.samples as i64);
330        pkt.flags.keyframe = true;
331        self.queue.push_back(pkt);
332        Ok(())
333    }
334
335    fn receive_packet(&mut self) -> Result<Packet> {
336        self.queue.pop_front().ok_or(Error::NeedMore)
337    }
338
339    fn flush(&mut self) -> Result<()> {
340        Ok(())
341    }
342}
343
344/// Helper to build codec parameters for a PCM stream.
345pub fn params(format: SampleFormat, channels: u16, sample_rate: u32) -> Result<CodecParameters> {
346    let codec_id = codec_id_for(format)
347        .ok_or_else(|| Error::unsupported(format!("no PCM codec for {:?}", format)))?;
348    let mut p = CodecParameters::audio(codec_id);
349    p.sample_format = Some(format);
350    p.channels = Some(channels);
351    p.sample_rate = Some(sample_rate);
352    p.bit_rate =
353        Some((format.bytes_per_sample() as u64) * 8 * (channels as u64) * (sample_rate as u64));
354    Ok(p)
355}
356
357/// Default time base for a PCM audio stream: 1 / sample_rate.
358pub fn time_base_for(sample_rate: u32) -> TimeBase {
359    TimeBase::new(1, sample_rate as i64)
360}