#[derive(Debug, Default, Clone)]
pub struct BitWriter {
bytes: Vec<u8>,
partial: u8,
bit_count: u8,
}
impl BitWriter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
bytes: Vec::with_capacity(cap),
partial: 0,
bit_count: 0,
}
}
#[must_use]
pub fn bit_len(&self) -> usize {
self.bytes.len() * 8 + self.bit_count as usize
}
#[must_use]
pub fn is_byte_aligned(&self) -> bool {
self.bit_count == 0
}
pub fn write_bit(&mut self, bit: bool) {
self.partial = (self.partial << 1) | u8::from(bit);
self.bit_count += 1;
if self.bit_count == 8 {
self.bytes.push(self.partial);
self.partial = 0;
self.bit_count = 0;
}
}
pub fn write_bits(&mut self, value: u32, n: u8) {
let n = n.min(32);
for i in (0..n).rev() {
self.write_bit((value >> i) & 1 == 1);
}
}
pub fn align_to_byte_with_zeros(&mut self) {
while self.bit_count != 0 {
self.write_bit(false);
}
}
pub fn write_byte_aligned(&mut self, byte: u8) {
debug_assert!(
self.is_byte_aligned(),
"write_byte_aligned requires alignment"
);
self.bytes.push(byte);
}
pub fn write_start_code(&mut self, code: u8) {
self.align_to_byte_with_zeros();
self.bytes.push(0x00);
self.bytes.push(0x00);
self.bytes.push(0x01);
self.bytes.push(code);
}
#[must_use]
pub fn into_bytes(mut self) -> Vec<u8> {
self.align_to_byte_with_zeros();
self.bytes
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mpeg2::bitreader::BitReader;
#[test]
fn single_bits_round_trip_msb_first() {
let mut w = BitWriter::new();
let pattern = [true, false, true, true, false, true, false, false];
for &b in &pattern {
w.write_bit(b);
}
let bytes = w.into_bytes();
assert_eq!(bytes, vec![0xB4]);
let mut r = BitReader::new(&bytes);
for (i, &exp) in pattern.iter().enumerate() {
assert_eq!(r.read_bit().expect("bit"), exp, "bit {i}");
}
}
#[test]
fn write_bits_matches_read_bits() {
let mut w = BitWriter::new();
w.write_bits(0xABC, 12);
w.write_bits(0xD, 4);
let bytes = w.into_bytes();
assert_eq!(bytes, vec![0xAB, 0xCD]);
let mut r = BitReader::new(&bytes);
assert_eq!(r.read_bits(12).expect("12"), 0xABC);
assert_eq!(r.read_bits(4).expect("4"), 0xD);
}
#[test]
fn align_pads_with_zeros() {
let mut w = BitWriter::new();
w.write_bits(0b111, 3);
w.align_to_byte_with_zeros();
assert!(w.is_byte_aligned());
let bytes = w.into_bytes();
assert_eq!(bytes, vec![0xE0]);
}
#[test]
fn start_code_is_byte_aligned() {
let mut w = BitWriter::new();
w.write_bits(0b101, 3); w.write_start_code(0xB3);
let bytes = w.into_bytes();
assert_eq!(bytes, vec![0xA0, 0x00, 0x00, 0x01, 0xB3]);
}
#[test]
fn into_bytes_flushes_partial() {
let mut w = BitWriter::new();
w.write_bits(0b1, 1);
let bytes = w.into_bytes();
assert_eq!(bytes, vec![0x80]);
}
#[test]
fn bit_len_tracks_partial() {
let mut w = BitWriter::new();
w.write_bits(0, 10);
assert_eq!(w.bit_len(), 10);
assert!(!w.is_byte_aligned());
}
#[test]
fn write_byte_aligned_appends() {
let mut w = BitWriter::new();
w.write_byte_aligned(0x12);
w.write_byte_aligned(0x34);
assert_eq!(w.as_bytes(), &[0x12, 0x34]);
}
#[test]
fn large_field_round_trips() {
let mut w = BitWriter::new();
w.write_bits(0xDEAD_BEEF, 32);
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(r.read_bits(32).expect("32"), 0xDEAD_BEEF);
}
}