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}