Skip to main content

rusty_h264_common/
bit_writer.rs

1//! MSB-first bit writer with H.264 Exp-Golomb coding.
2//!
3//! H.264 packs syntax elements as a big-endian bitstream: the first bit written
4//! lands in the most-significant position of the first byte. This writer
5//! accumulates bits and exposes the Exp-Golomb (`ue`/`se`) and fixed-length
6//! (`u`) codings the bitstream syntax is built from.
7
8/// A growable, MSB-first bit buffer.
9///
10/// Uses a **bit cache**: bits accumulate in the low `nbits` positions of a 64-bit
11/// `cache` (the most-recently-written bit is least-significant within the valid
12/// region), and whole bytes are flushed off the top. A multi-bit write is a shift
13/// + OR + a short byte-flush loop — not one operation per bit. After every public
14/// write the invariant `nbits < 8` and "bits above `nbits` are zero" hold.
15#[derive(Debug, Default, Clone)]
16pub struct BitWriter {
17    /// Completed bytes.
18    bytes: Vec<u8>,
19    /// Pending bits, valid in the low `nbits` positions (zero above).
20    cache: u64,
21    /// Number of valid pending bits (0..=7 between writes).
22    nbits: u32,
23}
24
25impl BitWriter {
26    /// Creates an empty writer.
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Creates a writer with `bytes` pre-reserved — for the per-frame slice writer,
32    /// so the CAVLC hot loop never pays a `Vec` realloc mid-frame.
33    pub fn with_capacity(bytes: usize) -> Self {
34        Self {
35            bytes: Vec::with_capacity(bytes),
36            cache: 0,
37            nbits: 0,
38        }
39    }
40
41    /// Number of bits written so far.
42    pub fn bit_len(&self) -> usize {
43        self.bytes.len() * 8 + self.nbits as usize
44    }
45
46    /// `true` if the writer is on a byte boundary.
47    pub fn is_byte_aligned(&self) -> bool {
48        self.nbits % 8 == 0
49    }
50
51    /// Writes the low `n` bits of `value`, most-significant first (`u(n)`). `n` <= 32.
52    ///
53    /// Keeps up to 31 pending bits in `cache` and flushes a whole **u32 word** (4
54    /// bytes) at a time, like openh264's `BsWriteBits` (`WRITE_BE_32`) — a quarter
55    /// the `Vec` writes of byte-at-a-time flushing.
56    #[inline]
57    pub fn write_bits(&mut self, value: u32, n: u32) {
58        debug_assert!(n <= 32, "write_bits supports up to 32 bits");
59        if n == 0 {
60            return;
61        }
62        let mask = (1u64 << n) - 1; // n<=32 so 1<<32 fits u64
63        self.cache = (self.cache << n) | (value as u64 & mask);
64        self.nbits += n;
65        if self.nbits >= 32 {
66            self.nbits -= 32;
67            let word = (self.cache >> self.nbits) as u32;
68            self.bytes.extend_from_slice(&word.to_be_bytes());
69            self.cache &= (1u64 << self.nbits) - 1; // drop the flushed high bits
70        }
71    }
72
73    /// Writes a single bit (`true` => 1).
74    #[inline]
75    pub fn write_bit(&mut self, bit: bool) {
76        self.write_bits(bit as u32, 1);
77    }
78
79    /// Appends every bit of `other`, in order, at this writer's current position.
80    ///
81    /// Neither writer need be byte-aligned. This is what lets a macroblock be
82    /// encoded into a scratch writer *before* the syntax that must precede it is
83    /// known (`mb_skip_run` is only decided once the macroblock's own cost is), so
84    /// the encoder can commit one encode rather than trialing and repeating it.
85    /// Sound for CAVLC because a macroblock's Exp-Golomb/VLC syntax does not depend
86    /// on its bit position; an arithmetic coder (CABAC) could NOT be spliced this
87    /// way, since its state is carried across the whole slice.
88    pub fn append(&mut self, other: &BitWriter) {
89        for chunk in other.bytes.chunks(4) {
90            let mut word = 0u32;
91            for &b in chunk {
92                word = (word << 8) | b as u32;
93            }
94            self.write_bits(word, chunk.len() as u32 * 8);
95        }
96        if other.nbits > 0 {
97            // `cache` holds `nbits` (<= 31) valid bits in its low positions.
98            self.write_bits(other.cache as u32, other.nbits);
99        }
100    }
101
102    /// Emits a value already mapped to its Exp-Golomb code number: `floor(log2 x)`
103    /// leading zeros then `x` in `floor(log2 x)+1` bits, where `x = code_num + 1`.
104    #[inline]
105    fn put_golomb(&mut self, x: u64) {
106        let n = 63 - x.leading_zeros(); // floor(log2 x), 0..=32
107        self.write_bits(0, n);
108        if n < 32 {
109            self.write_bits(x as u32, n + 1);
110        } else {
111            // n == 32 (e.g. ue(u32::MAX)): 33-bit value, split across two writes.
112            self.write_bits((x >> 32) as u32, n - 31);
113            self.write_bits(x as u32, 32);
114        }
115    }
116
117    /// Unsigned Exp-Golomb code `ue(v)`.
118    pub fn write_ue(&mut self, value: u32) {
119        self.put_golomb(value as u64 + 1);
120    }
121
122    /// Signed Exp-Golomb code `se(v)`: `0->0, 1->1, -1->2, 2->3, -2->4, ...`.
123    pub fn write_se(&mut self, value: i32) {
124        let code_num = if value <= 0 {
125            (-(value as i64) as u64) * 2
126        } else {
127            (value as u64) * 2 - 1
128        };
129        self.put_golomb(code_num + 1);
130    }
131
132    /// Writes the `rbsp_trailing_bits()`: a stop bit `1` then zero-pad to a byte.
133    pub fn rbsp_trailing_bits(&mut self) {
134        self.write_bits(1, 1);
135        self.align_zero();
136    }
137
138    /// Pads with zero bits to the next byte boundary (no stop bit), then flushes
139    /// the pending whole bytes from the cache (the word-flush in `write_bits` can
140    /// leave up to 31 pending bits).
141    pub fn align_zero(&mut self) {
142        let pad = (8 - self.nbits % 8) % 8;
143        if pad != 0 {
144            self.write_bits(0, pad);
145        }
146        while self.nbits >= 8 {
147            self.nbits -= 8;
148            self.bytes.push((self.cache >> self.nbits) as u8);
149        }
150        self.cache = 0;
151    }
152
153    /// Consumes the writer, returning the byte buffer. Flushes any whole bytes
154    /// still pending in the cache; panics if a sub-byte remainder is left (call
155    /// [`rbsp_trailing_bits`](Self::rbsp_trailing_bits) or
156    /// [`align_zero`](Self::align_zero) first).
157    pub fn into_bytes(mut self) -> Vec<u8> {
158        while self.nbits >= 8 {
159            self.nbits -= 8;
160            self.bytes.push((self.cache >> self.nbits) as u8);
161        }
162        assert!(
163            self.nbits == 0,
164            "BitWriter::into_bytes called with {} dangling bits",
165            self.nbits
166        );
167        self.bytes
168    }
169
170    /// Borrows the completed bytes (excludes bits still pending in the cache).
171    pub fn as_bytes(&self) -> &[u8] {
172        &self.bytes
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn write_bits_is_msb_first() {
182        let mut w = BitWriter::new();
183        w.write_bits(0b101, 3);
184        w.write_bits(0b1, 1);
185        w.align_zero();
186        // bits 1,0,1,1 then zero-pad -> 1011_0000
187        assert_eq!(w.into_bytes(), vec![0b1011_0000]);
188    }
189
190    #[test]
191    fn ue_known_values() {
192        // From H.264 Table 9-2: code_num -> bit string.
193        let cases: &[(u32, &str)] = &[
194            (0, "1"),
195            (1, "010"),
196            (2, "011"),
197            (3, "00100"),
198            (4, "00101"),
199            (5, "00110"),
200            (6, "00111"),
201            (7, "0001000"),
202            (8, "0001001"),
203        ];
204        for &(v, bits) in cases {
205            let mut w = BitWriter::new();
206            w.write_ue(v);
207            assert_eq!(bitstring(&w), bits, "ue({v})");
208        }
209    }
210
211    #[test]
212    fn se_known_values() {
213        // H.264 Table 9-3 mapping: se -> code_num.
214        let cases: &[(i32, &str)] = &[
215            (0, "1"),    // code_num 0
216            (1, "010"),  // 1
217            (-1, "011"), // 2
218            (2, "00100"),
219            (-2, "00101"),
220            (3, "00110"),
221            (-3, "00111"),
222        ];
223        for &(v, bits) in cases {
224            let mut w = BitWriter::new();
225            w.write_se(v);
226            assert_eq!(bitstring(&w), bits, "se({v})");
227        }
228    }
229
230    #[test]
231    fn ue_max_does_not_overflow() {
232        let mut w = BitWriter::new();
233        w.write_ue(u32::MAX);
234        // value+1 = 2^32 -> 32 leading zeros then 33-bit value => 65 bits total.
235        assert_eq!(w.bit_len(), 65);
236    }
237
238    #[test]
239    fn rbsp_trailing_aligns_to_byte() {
240        let mut w = BitWriter::new();
241        w.write_bits(0b101, 3);
242        w.rbsp_trailing_bits();
243        // 101 + stop 1 + pad 0000 => 1011_0000
244        assert_eq!(w.into_bytes(), vec![0b1011_0000]);
245    }
246
247    /// Renders the bits currently buffered (including the partial byte) as a
248    /// string of '0'/'1' for assertions.
249    fn bitstring(w: &BitWriter) -> String {
250        let mut s = String::new();
251        for &b in w.as_bytes() {
252            for i in (0..8).rev() {
253                s.push(if (b >> i) & 1 == 1 { '1' } else { '0' });
254            }
255        }
256        for i in (0..w.nbits).rev() {
257            s.push(if (w.cache >> i) & 1 == 1 { '1' } else { '0' });
258        }
259        s
260    }
261}
262
263#[cfg(test)]
264mod append_tests {
265    use super::BitWriter;
266
267    /// Appending must be indistinguishable from having written the bits inline,
268    /// at every source/destination bit misalignment — this is the invariant the
269    /// encoder's encode-once-and-splice path rests on.
270    #[test]
271    fn append_matches_inline_at_every_alignment() {
272        for lead in 0..17u32 {
273            for n in 0..40u32 {
274                let mut inline = BitWriter::new();
275                inline.write_bits(0b1011_0110, lead.min(8));
276                if lead > 8 {
277                    inline.write_bits(0x5A5A, lead - 8);
278                }
279                let mut spliced = inline.clone();
280
281                // the payload, written bit-patterned so order errors show up
282                let mut scratch = BitWriter::new();
283                for i in 0..n {
284                    scratch.write_bit(i % 3 == 0);
285                }
286                for i in 0..n {
287                    inline.write_bit(i % 3 == 0);
288                }
289                spliced.append(&scratch);
290
291                assert_eq!(spliced.bit_len(), inline.bit_len(), "lead={lead} n={n}: length");
292                let (mut a, mut b) = (spliced.clone(), inline.clone());
293                a.align_zero();
294                b.align_zero();
295                assert_eq!(a.into_bytes(), b.into_bytes(), "lead={lead} n={n}: bits");
296            }
297        }
298    }
299
300    /// Exp-Golomb payloads across a byte-crossing boundary (the real shape).
301    #[test]
302    fn append_preserves_exp_golomb() {
303        for lead in 0..9u32 {
304            let mut inline = BitWriter::new();
305            inline.write_bits(1, lead);
306            let mut spliced = inline.clone();
307            let mut scratch = BitWriter::new();
308            for v in [0u32, 1, 7, 40, 1000, 65535] {
309                scratch.write_ue(v);
310                inline.write_ue(v);
311            }
312            spliced.append(&scratch);
313            assert_eq!(spliced.bit_len(), inline.bit_len(), "lead={lead}: length");
314            spliced.align_zero();
315            inline.align_zero();
316            assert_eq!(spliced.into_bytes(), inline.into_bytes(), "lead={lead}");
317        }
318    }
319}