Skip to main content

ogg_core/
mux.rs

1//! Ogg page writer — one packet per page (simple, always spec-valid; real
2//! encoders also pack multiple small packets per page, deferred — see ADR-0001).
3
4#![forbid(unsafe_code)]
5
6use crate::crc::crc32_ogg;
7use crate::error::Error;
8
9/// Max packet payload representable in a single page's 255-entry segment table
10/// (254 full 255-byte segments + one final terminator segment of up to 254 bytes).
11pub const MAX_SINGLE_PAGE_PAYLOAD: usize = 65_024;
12
13/// Byte offset of the 4-byte CRC field within a page (after capture pattern,
14/// version, flags, `granule_position`, serial, sequence).
15const CRC_FIELD_OFFSET: usize = 4 + 1 + 1 + 8 + 4 + 4;
16
17/// Writes Ogg pages for one logical bitstream (one `serial`).
18///
19/// Unlike a real encoder, this always emits exactly one page per
20/// [`Muxer::push_packet`] call (no multi-packet-per-page batching, no
21/// continuation splitting for oversized packets) — a real, bounded v1 scope
22/// (crate-local ADR-0001), not a corner cut on correctness: every page this
23/// writes is a fully valid, independently decodable Ogg page.
24#[derive(Debug, Clone)]
25pub struct Muxer {
26    serial: u32,
27    sequence: u32,
28    bos_written: bool,
29}
30
31impl Muxer {
32    /// Start a mux session for logical bitstream `serial`.
33    #[must_use]
34    pub const fn new(serial: u32) -> Self {
35        Self {
36            serial,
37            sequence: 0,
38            bos_written: false,
39        }
40    }
41
42    /// Write one page containing exactly `packet`. The first call automatically
43    /// sets the page's `bos` (beginning-of-stream) flag; pass `eos = true` on the
44    /// last call for this stream.
45    pub fn push_packet(
46        &mut self,
47        packet: &[u8],
48        granule_position: i64,
49        eos: bool,
50        out: &mut Vec<u8>,
51    ) -> Result<(), Error> {
52        if packet.len() > MAX_SINGLE_PAGE_PAYLOAD {
53            return Err(Error::PacketTooLargeForSinglePage(packet.len()));
54        }
55        let bos = !self.bos_written;
56        self.bos_written = true;
57        let segments = lacing_values_for(packet.len());
58
59        let page_start = out.len();
60        out.extend_from_slice(b"OggS");
61        out.push(0); // version
62        let flags = (u8::from(bos) << 1) | (u8::from(eos) << 2);
63        out.push(flags);
64        out.extend_from_slice(&granule_position.to_le_bytes());
65        out.extend_from_slice(&self.serial.to_le_bytes());
66        out.extend_from_slice(&self.sequence.to_le_bytes());
67        out.extend_from_slice(&0u32.to_le_bytes()); // CRC placeholder
68        out.push(u8::try_from(segments.len()).unwrap_or(u8::MAX));
69        out.extend_from_slice(&segments);
70        out.extend_from_slice(packet);
71
72        let crc = crc32_ogg(&out[page_start..]);
73        let crc_start = page_start + CRC_FIELD_OFFSET;
74        out[crc_start..crc_start + 4].copy_from_slice(&crc.to_le_bytes());
75
76        self.sequence += 1;
77        Ok(())
78    }
79}
80
81fn lacing_values_for(len: usize) -> Vec<u8> {
82    let mut segments = Vec::new();
83    let mut remaining = len;
84    while remaining >= 255 {
85        segments.push(255);
86        remaining -= 255;
87    }
88    segments.push(u8::try_from(remaining).unwrap_or(u8::MAX));
89    segments
90}
91
92#[cfg(test)]
93#[path = "mux_tests.rs"]
94mod tests;