less_avc/
lib.rs

1// Copyright 2022-2023 Andrew D. Straw.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT
5// or http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! less Advanced Video Coding (H.264) encoding library
9//!
10//! This module contains a pure Rust implementation of an H.264 encoder. It is
11//! simple ("less advanced"), and uses a small subset of the encoder features in
12//! the H.264 specification. It was inspired by Ben Mesander's [World's Smallest
13//! H.264
14//! Encoder](https://www.cardinalpeak.com/blog/worlds-smallest-h-264-encoder).
15//! In the present implementation, all data is encoded as a lossless PCM frame.
16//! (Future updates could include other encoding possibilities.) Bit depths of 8
17//! and 12 in monochrome and YCbCr colorspaces are supported. Tests ensure that
18//! data is losslessly encoded.
19#![cfg_attr(not(feature = "std"), no_std)]
20#![cfg_attr(feature = "backtrace", feature(error_generic_member_access))]
21#![deny(unsafe_code)]
22
23#[cfg(not(feature = "std"))]
24extern crate core as std;
25
26extern crate alloc;
27use alloc::{vec, vec::Vec};
28
29#[cfg(feature = "backtrace")]
30use std::backtrace::Backtrace;
31
32use bitvec::prelude::{BitVec, Msb0};
33
34mod golomb;
35use golomb::BitVecGolomb;
36
37pub mod ycbcr_image;
38use ycbcr_image::*;
39
40pub mod nal_unit;
41use nal_unit::*;
42
43pub mod sei;
44
45#[cfg(feature = "std")]
46mod writer;
47#[cfg(feature = "std")]
48pub use writer::H264Writer;
49
50mod encoder;
51pub use encoder::LessEncoder;
52
53// Error type ----------------------
54
55/// An H.264 encoding error.
56#[derive(Debug)]
57pub enum Error {
58    DataShapeProblem {
59        msg: &'static str,
60        #[cfg(feature = "backtrace")]
61        backtrace: Backtrace,
62    },
63    UnsupportedFormat {
64        #[cfg(feature = "backtrace")]
65        backtrace: Backtrace,
66    },
67    UnsupportedImageSize {
68        #[cfg(feature = "backtrace")]
69        backtrace: Backtrace,
70    },
71    InconsistentState {
72        #[cfg(feature = "backtrace")]
73        backtrace: Backtrace,
74    },
75    #[cfg(feature = "std")]
76    IoError {
77        source: std::io::Error,
78        #[cfg(feature = "backtrace")]
79        backtrace: Backtrace,
80    },
81}
82type Result<T> = std::result::Result<T, Error>;
83
84#[cfg(feature = "std")]
85impl From<std::io::Error> for Error {
86    fn from(source: std::io::Error) -> Self {
87        Error::IoError {
88            source,
89            #[cfg(feature = "backtrace")]
90            backtrace: Backtrace::capture(),
91        }
92    }
93}
94
95#[cfg(feature = "std")]
96impl std::error::Error for Error {
97    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
98        match self {
99            Error::IoError {
100                source,
101                #[cfg(feature = "backtrace")]
102                    backtrace: _,
103            } => Some(source),
104            _ => None,
105        }
106    }
107}
108
109impl std::fmt::Display for Error {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
111        match self {
112            Error::DataShapeProblem {
113                msg,
114                #[cfg(feature = "backtrace")]
115                    backtrace: _,
116            } => {
117                write!(f, "Image data shape is problematic: {msg}")
118            }
119            Error::UnsupportedFormat {
120                #[cfg(feature = "backtrace")]
121                    backtrace: _,
122            } => {
123                write!(f, "unsupported format")
124            }
125            Error::UnsupportedImageSize {
126                #[cfg(feature = "backtrace")]
127                    backtrace: _,
128            } => {
129                write!(f, "unsupported image size: even width and height required")
130            }
131            Error::InconsistentState {
132                #[cfg(feature = "backtrace")]
133                    backtrace: _,
134            } => {
135                write!(f, "internal error: inconsistent state")
136            }
137            #[cfg(feature = "std")]
138            Error::IoError {
139                source,
140                #[cfg(feature = "backtrace")]
141                    backtrace: _,
142            } => {
143                write!(f, "IO error: {source}")
144            }
145        }
146    }
147}
148
149// Utility functions -------------------
150
151#[inline]
152fn div_ceil(a: u32, b: u32) -> u32 {
153    // See https://stackoverflow.com/a/72442854
154    (a + b - 1) / b
155}
156
157#[inline]
158fn next_multiple(a: u32, b: u32) -> u32 {
159    div_ceil(a, b) * b
160}
161
162// H.264 definitions ------------------
163
164#[derive(Debug, PartialEq, Eq)]
165#[allow(dead_code, clippy::upper_case_acronyms)]
166enum VideoFormat {
167    Component,
168    PAL,
169    NTSC,
170    SECAM,
171    MAC,
172    Unspecified,
173    Reserved,
174}
175
176#[derive(Debug, PartialEq, Eq)]
177struct TimingInfo {
178    /// The number of time units of a clock operating at the frequency
179    /// time_scale Hz that corresponds to one increment (called a clock tick) of
180    /// a clock tick counter.
181    num_units_in_tick: u32,
182
183    /// The number of time units that pass in one second.
184    time_scale: u32,
185
186    /// If true, the temporal distance between the HRD output times of any two
187    /// consecutive pictures in output order is constrained
188    fixed_frame_rate_flag: bool,
189}
190
191#[derive(Debug, PartialEq, Eq)]
192struct Vui {
193    /// Whether intensity range in encoded signal uses full luma/chroma range.
194    ///
195    /// If false, the signal is "studio swing".
196    full_range: bool,
197    video_format: VideoFormat,
198    timing_info: Option<TimingInfo>,
199}
200
201impl Vui {
202    fn new(full_range: bool) -> Self {
203        Self {
204            full_range,
205            video_format: VideoFormat::Unspecified,
206            timing_info: None,
207        }
208    }
209
210    fn append_to_rbsp(&self, bv: &mut BitVec<u8, Msb0>) {
211        // vui_parameters( )
212        // Annex E
213
214        // aspect_ratio_info_present_flag 0
215        bv.push(false);
216
217        // overscan_info_present_flag 0
218        bv.push(false);
219
220        // video_signal_type_present_flag 1
221        bv.push(true);
222
223        // video_format
224        let video_format_arr = match &self.video_format {
225            VideoFormat::Component => [false, false, false],
226            VideoFormat::PAL => [false, false, true],
227            VideoFormat::NTSC => [false, true, false],
228            VideoFormat::SECAM => [false, true, true],
229            VideoFormat::MAC => [true, false, false],
230            VideoFormat::Unspecified => [true, false, true],
231            VideoFormat::Reserved => [true, true, true],
232        };
233        bv.extend(video_format_arr);
234
235        // video_full_range_flag
236        bv.push(self.full_range);
237
238        // colour_description_present_flag 0
239        bv.push(false);
240
241        // chroma_loc_info_present_flag 0
242        bv.push(false);
243
244        // timing_info_present_flag
245        if let Some(_timing_info) = &self.timing_info {
246            todo!();
247        } else {
248            bv.push(false);
249        }
250
251        // nal_hrd_parameters_present_flag 0
252        bv.push(false);
253
254        // vcl_hrd_parameters_present_flag 0
255        bv.push(false);
256
257        // pic_struct_present_flag 0
258        bv.push(false);
259
260        // bitstream_restriction_flag 0
261        bv.push(false);
262    }
263}
264
265/// The dynamic range of the data, stored as number of bits.
266#[derive(Debug, PartialEq, Eq, Clone, Copy)]
267pub enum BitDepth {
268    /// 8 bit data
269    Depth8,
270    /// 12 bit data
271    Depth12,
272}
273
274impl BitDepth {
275    /// Return the number of bits
276    pub fn num_bits(&self) -> u8 {
277        match self {
278            Self::Depth8 => 8,
279            Self::Depth12 => 12,
280        }
281    }
282}
283
284#[derive(Debug, PartialEq, Eq)]
285enum ProfileIdc {
286    Bare(u8),
287    Extra((u8, ChromaFormatIdc)),
288}
289
290impl ProfileIdc {
291    fn baseline() -> Self {
292        Self::Bare(66)
293    }
294    fn high(chroma_format: ChromaFormatIdc) -> Self {
295        Self::Extra((100, chroma_format))
296    }
297    fn high444pp(chroma_format: ChromaFormatIdc) -> Self {
298        Self::Extra((244, chroma_format))
299    }
300    fn profile_idc_byte(&self) -> u8 {
301        match self {
302            Self::Bare(value) => *value,
303            Self::Extra((value, _)) => *value,
304        }
305    }
306    fn is_monochrome(&self) -> bool {
307        match self {
308            Self::Bare(_) | Self::Extra((_, ChromaFormatIdc::Chroma420(_))) => false,
309            Self::Extra((_, ChromaFormatIdc::Monochrome(_))) => true,
310        }
311    }
312    fn append_to_rbsp(&self, bv: &mut BitVec<u8, Msb0>) {
313        match self {
314            Self::Bare(_) => {}
315            Self::Extra((_, chroma_format_idc)) => {
316                let chroma_format_idc_value = chroma_format_idc.value();
317                bv.extend_exp_golomb(chroma_format_idc_value);
318                if chroma_format_idc_value == 3 {
319                    // separate_colour_plane_flag 0
320                    bv.push(false);
321                }
322                let bit_depth = match chroma_format_idc {
323                    ChromaFormatIdc::Monochrome(bit_depth)
324                    | ChromaFormatIdc::Chroma420(bit_depth) => bit_depth,
325                };
326
327                let bit_depth_luma_minus8 = bit_depth.num_bits() - 8;
328                let bit_depth_chroma_minus8 = bit_depth.num_bits() - 8;
329                bv.extend_exp_golomb(bit_depth_luma_minus8.try_into().unwrap());
330                bv.extend_exp_golomb(bit_depth_chroma_minus8.try_into().unwrap());
331
332                // qpprime_y_zero_transform_bypass_flag 0
333                bv.push(false);
334                // seq_scaling_matrix_present_flag 0
335                bv.push(false);
336            }
337        }
338    }
339}
340
341#[derive(Debug, PartialEq, Eq)]
342#[allow(dead_code)]
343enum ChromaFormatIdc {
344    Monochrome(BitDepth),
345    // Theoretically, luma and chroma could have different bit depths, but
346    // ffmpeg does not support this and thus it is difficult to test.
347    Chroma420(BitDepth),
348    // // Enabling these would require handling SubWidthC and SubHeightC not 2:
349    // Chroma422,
350    // Chroma444,
351    // // separate planes would be handled separately.
352}
353
354impl ChromaFormatIdc {
355    fn value(&self) -> u32 {
356        match self {
357            Self::Monochrome(_) => 0,
358            Self::Chroma420(_) => 1,
359            // Self::Chroma422 => 2,
360            // Self::Chroma444 => 3,
361        }
362    }
363}
364
365/// Sequence parameter set
366#[derive(Debug, PartialEq, Eq)]
367struct Sps {
368    profile_idc: ProfileIdc,
369    pic_width_in_mbs_minus1: u32,
370    pic_height_in_map_units_minus1: u32,
371    frame_cropping: Option<[u32; 4]>,
372    log2_max_frame_num_minus4: u32,
373    pic_order_cnt_type: u32,
374    log2_max_pic_order_cnt_lsb_minus4: u32,
375    vui: Option<Vui>,
376    // Future: expand with ability to set more parameters.
377}
378
379impl Sps {
380    fn new(
381        profile_idc: ProfileIdc,
382        pic_width_in_mbs_minus1: u32,
383        pic_height_in_map_units_minus1: u32,
384        frame_cropping: Option<[u32; 4]>,
385        vui: Option<Vui>,
386    ) -> Self {
387        Self {
388            profile_idc,
389            pic_width_in_mbs_minus1,
390            pic_height_in_map_units_minus1,
391            frame_cropping,
392            log2_max_frame_num_minus4: 0,
393            pic_order_cnt_type: 0,
394            log2_max_pic_order_cnt_lsb_minus4: 0,
395            vui,
396        }
397    }
398
399    #[allow(dead_code)]
400    fn log2_max_frame_num(&self) -> u32 {
401        self.log2_max_frame_num_minus4 + 4
402    }
403
404    #[allow(dead_code)]
405    fn log2_max_pic_order_cnt_lsb(&self) -> u32 {
406        self.log2_max_pic_order_cnt_lsb_minus4 + 4
407    }
408    fn to_rbsp(&self) -> RbspData {
409        // Payload
410        // profile_idc
411        let profile_idc = self.profile_idc.profile_idc_byte();
412
413        // constraint_set0_flag = 0
414        // constraint_set1_flag = 0
415        // constraint_set2_flag = 0
416        // constraint_set3_flag = 0
417        // constraint_set4_flag = 0
418        // constraint_set5_flag = 0
419        // reserved_zero_2bits = 0
420        let reserved = 0x00;
421
422        // level_idc = 10
423        let level_idc = 10;
424
425        let start = vec![profile_idc, reserved, level_idc];
426        let mut bv: BitVec<u8, Msb0> = BitVec::from_vec(start);
427
428        // seq_parameter_set_id = 0
429        bv.extend_exp_golomb(0);
430
431        // chroma_format_idc etc if in the correct `profile_idc`.
432        self.profile_idc.append_to_rbsp(&mut bv);
433
434        bv.extend_exp_golomb(self.log2_max_frame_num_minus4);
435
436        // pic_order_cnt_type
437        bv.extend_exp_golomb(self.pic_order_cnt_type);
438
439        // log2_max_pic_order_cnt_lsb_minus4
440        bv.extend_exp_golomb(self.log2_max_pic_order_cnt_lsb_minus4);
441
442        // max_num_ref_frames
443        bv.extend_exp_golomb(0);
444
445        // gaps_in_frame_num_value_allowed_flag = 0
446        bv.push(false);
447
448        // pic_width_in_mbs_minus1
449        bv.extend_exp_golomb(self.pic_width_in_mbs_minus1);
450
451        // pic_height_in_map_units_minus1
452        bv.extend_exp_golomb(self.pic_height_in_map_units_minus1);
453
454        // frame_mbs_only_flag = 1
455        bv.push(true);
456
457        // direct_8x8_inference_flag = 0
458        bv.push(false);
459
460        if let Some(lrtb) = &self.frame_cropping {
461            // frame_cropping_flag = 1
462            bv.push(true);
463            for frame_crop_offset in lrtb.iter() {
464                bv.extend_exp_golomb(*frame_crop_offset);
465            }
466        } else {
467            // frame_cropping_flag = 0
468            bv.push(false);
469        }
470
471        match &self.vui {
472            None => {
473                // vui_prameters_present_flag = 0
474                bv.push(false);
475            }
476            Some(vui) => {
477                bv.push(true);
478                vui.append_to_rbsp(&mut bv);
479            }
480        }
481
482        // rbsp_stop_one_bit = 1
483        bv.push(true);
484
485        RbspData::new(bv.into_vec())
486    }
487}
488
489/// Picture parameter set
490#[derive(PartialEq, Eq)]
491struct Pps {
492    pic_parameter_set_id: u32,
493    // In the future: expand with ability to set some parameters.
494}
495
496impl Pps {
497    fn new(pic_parameter_set_id: u32) -> Self {
498        Self {
499            pic_parameter_set_id,
500        }
501    }
502
503    fn to_rbsp(&self) -> RbspData {
504        // Payload
505
506        let mut bv: BitVec<u8, Msb0> = BitVec::with_capacity(20 * 8); // 20 bytes should be enough
507
508        bv.extend_exp_golomb(self.pic_parameter_set_id);
509
510        // seq_parameter_set_id = 0
511        bv.extend_exp_golomb(0);
512
513        // entropy_coding_mode_flag = 0
514        bv.push(false);
515
516        // bottom_field_pic_order_in_frame_present_flag = 0
517        bv.push(false);
518
519        // num_slice_groups_minus1 = 0
520        bv.extend_exp_golomb(0);
521
522        // num_ref_idx_l0_default_active_minus1 = 0
523        bv.extend_exp_golomb(0);
524
525        // num_ref_idx_l1_default_active_minus1 = 0
526        bv.extend_exp_golomb(0);
527
528        // weighted_pred_flag = 0
529        bv.push(false);
530
531        // weighted_bipred_idc = 0
532        bv.push(false);
533        bv.push(false);
534
535        // pic_init_qp_minus26 = 0
536        bv.extend_signed_exp_golomb(0);
537
538        // pic_init_qs_minus26 = 0
539        bv.extend_signed_exp_golomb(0);
540
541        // chroma_qp_index_offset
542        bv.extend_signed_exp_golomb(0);
543
544        // deblocking_filter_control_present_flag = 0
545        bv.push(false);
546
547        // constrained_intra_pred_flag = 0
548        bv.push(false);
549
550        // redundant_pic_cnt_present_flag = 0
551        bv.push(false);
552
553        // rbsp_trailing_bits( )
554        bv.push(true);
555
556        RbspData::new(bv.into_vec())
557    }
558}
559
560struct SliceHeader {}
561
562impl SliceHeader {
563    fn new() -> Self {
564        Self {}
565    }
566
567    fn to_rbsp(&self, sps: &Sps, pps: &Pps) -> RbspData {
568        // We are `slice_layer_without_partitioning_rbsp` because we have
569        // nal_unit_type 5 (NalUnitType::CodedSliceOfAnIDRPicture). Also
570        // `IdrPicFlag` is 1 for the same reason.
571
572        // Payload
573
574        let mut bv: BitVec<u8, Msb0> = BitVec::with_capacity(20 * 8); // 20 bytes should be enough for slice header
575
576        // first_mb_in_slice = 0
577        bv.extend_exp_golomb(0);
578
579        // slice_type = 7 (I)
580        bv.extend_exp_golomb(7);
581
582        bv.extend_exp_golomb(pps.pic_parameter_set_id);
583
584        // colour_plane: None,
585
586        // frame_num = 0
587        let n_bits = sps.log2_max_frame_num();
588        for _ in 0..n_bits {
589            bv.push(false);
590        }
591
592        // idr_pic_id = 0
593        bv.extend_exp_golomb(0);
594
595        if sps.pic_order_cnt_type == 0 {
596            // pic_order_cnt_lsb = 0
597            let n_bits = sps.log2_max_pic_order_cnt_lsb();
598            for _ in 0..n_bits {
599                bv.push(false);
600            }
601        } else {
602            todo!();
603        }
604
605        // dec_ref_pic_marking
606        //   no_output_of_prior_pics_flag u(1)
607        bv.push(true);
608
609        //   long_term_reference_flag u(1)
610        bv.push(false);
611
612        // slice_qp_delta = 0
613        bv.extend_signed_exp_golomb(0);
614
615        // For the first macroblock, the macroblock type (mb_type) is read without
616        // aligning to a byte boundary. This would explain why we must put this here
617        // rather than in the first macroblock.
618        bv.extend_exp_golomb(MacroblockType::I_PCM.mb_type());
619
620        RbspData::new(bv.into_vec())
621    }
622}
623
624#[allow(non_camel_case_types)]
625enum MacroblockType {
626    // I_NxN,
627    I_PCM,
628}
629
630impl MacroblockType {
631    #[inline]
632    fn mb_type(&self) -> u32 {
633        match self {
634            // Self::I_NxN => 0,
635            Self::I_PCM => 25,
636        }
637    }
638    /// This is an opimization to compile time.
639    #[inline]
640    const fn as_encoded_macroblock_header(&self) -> &'static [u8] {
641        match self {
642            Self::I_PCM => &[0x0D, 0x00],
643        }
644    }
645}
646
647#[test]
648fn test_macroblock_header() {
649    {
650        let typ = &MacroblockType::I_PCM;
651        let mut bv: BitVec<u8, Msb0> = BitVec::new();
652        bv.extend_exp_golomb(typ.mb_type());
653        let macroblock_header_dynamic = bv.as_raw_slice();
654        dbg!(macroblock_header_dynamic);
655        let macroblock_header_static = typ.as_encoded_macroblock_header();
656        assert_eq!(macroblock_header_static, macroblock_header_dynamic);
657    }
658}
659
660#[inline]
661fn copy_to_macroblock_8bit(
662    mbs_row: usize,
663    mbs_col: usize,
664    src_plane: &DataPlane,
665    dest: &mut Vec<u8>,
666    dest_sz: usize,
667) {
668    // `dest_sz` will be 16 when copying luma block and 8 when copying 4:2:0
669    // chroma block.
670    let src_data = src_plane.data;
671    let src_stride = src_plane.stride;
672    for src_row in (mbs_row * dest_sz)..((mbs_row + 1) * dest_sz) {
673        // This copies beyond end of source pixel data but still within source
674        // buffer.
675        let row_chunk = &src_data[src_row * src_stride..(src_row + 1) * src_stride];
676        let chunk = &row_chunk[mbs_col * dest_sz..(mbs_col + 1) * dest_sz];
677        dest.extend(chunk);
678    }
679}
680
681#[inline]
682fn copy_to_macroblock_12bit(
683    mbs_row: usize,
684    mbs_col: usize,
685    src_plane: &DataPlane,
686    dest: &mut Vec<u8>,
687    dest_sz: usize,
688) {
689    // `dest_sz` will be 24 when copying luma block and 12 when copying 4:2:0
690    // chroma block.
691    let src_data = src_plane.data;
692    let src_stride = src_plane.stride;
693    let src_sz = dest_sz / 3 * 2;
694    for src_row in (mbs_row * src_sz)..((mbs_row + 1) * src_sz) {
695        // This copies beyond end of source pixel data but still within source
696        // buffer.
697        let row_chunk = &src_data[src_row * src_stride..(src_row + 1) * src_stride];
698        let chunk = &row_chunk[mbs_col * dest_sz..(mbs_col + 1) * dest_sz];
699        dest.extend(chunk);
700    }
701}
702
703#[inline]
704fn macroblock(
705    mbs_row: usize,
706    mbs_col: usize,
707    result: &mut RbspData,
708    y4m_frame: &YCbCrImage,
709    luma_only: bool,
710) {
711    if !(mbs_row == 0 && mbs_col == 0) {
712        result
713            .data
714            .extend(MacroblockType::I_PCM.as_encoded_macroblock_header());
715    }
716
717    match &y4m_frame.planes {
718        Planes::Mono(y_plane) | Planes::YCbCr((y_plane, _, _)) => match y_plane.bit_depth {
719            BitDepth::Depth8 => {
720                copy_to_macroblock_8bit(mbs_row, mbs_col, y_plane, &mut result.data, 16);
721            }
722            BitDepth::Depth12 => {
723                copy_to_macroblock_12bit(mbs_row, mbs_col, y_plane, &mut result.data, 24);
724            }
725        },
726    }
727
728    if luma_only {
729        return;
730    }
731
732    match &y4m_frame.planes {
733        Planes::Mono(y_plane) => {
734            assert_eq!(y_plane.bit_depth, BitDepth::Depth8);
735            // 2 macroblocks of chrominance at 8x8 each
736            result.data.extend(vec![128u8; 2 * 8 * 8]);
737        }
738        Planes::YCbCr((_, u_plane, v_plane)) => {
739            assert_eq!(u_plane.bit_depth, v_plane.bit_depth);
740            match u_plane.bit_depth {
741                BitDepth::Depth8 => {
742                    copy_to_macroblock_8bit(mbs_row, mbs_col, u_plane, &mut result.data, 8);
743                    copy_to_macroblock_8bit(mbs_row, mbs_col, v_plane, &mut result.data, 8);
744                }
745                BitDepth::Depth12 => {
746                    copy_to_macroblock_12bit(mbs_row, mbs_col, u_plane, &mut result.data, 12);
747                    copy_to_macroblock_12bit(mbs_row, mbs_col, v_plane, &mut result.data, 12);
748                }
749            }
750        }
751    }
752}
753
754/// Raw byte sequence payload (RBSP) data.
755///
756/// This is merely a newtype to indicate the type of data help within the
757/// `Vec<u8>`.
758#[derive(Clone)]
759pub struct RbspData {
760    /// Raw byte sequence payload (RBSP) data.
761    pub data: Vec<u8>,
762}
763
764impl RbspData {
765    fn new(data: Vec<u8>) -> Self {
766        Self { data }
767    }
768}
769
770#[cfg(test)]
771mod tests {
772    use super::*;
773
774    // Data from https://www.cardinalpeak.com/blog/worlds-smallest-h-264-encoder
775    const HELLO_SPS: &[u8] = &[
776        0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2,
777    ];
778    const HELLO_PPS: &[u8] = &[0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80];
779    // Original slice header data had a bug in that it is not spec compliant.
780    // Here I have fixed it.
781    const FIXED_HELLO_SLICE_HEADER: &[u8] = &[0, 0, 0, 1, 37, 136, 132, 40, 104];
782    // And here is the original slice header.
783    const _HELLO_SLICE_HEADER: &[u8] = &[0x00, 0x00, 0x00, 0x01, 0x05, 0x88, 0x84, 0x21, 0xa0];
784    const HELLO_MACROBLOCK_HEADER: &[u8] = &[0x0d, 0x00];
785
786    use h264_reader::{
787        nal::{pps::PicParameterSet, sps::SeqParameterSet, Nal, RefNal},
788        rbsp::BitReader,
789        Context,
790    };
791
792    #[test]
793    fn test_div_ceil() {
794        assert_eq!(div_ceil(10, 2), 5);
795        assert_eq!(div_ceil(11, 2), 6);
796        assert_eq!(div_ceil(15, 3), 5);
797        assert_eq!(div_ceil(16, 3), 6);
798        assert_eq!(div_ceil(18, 3), 6);
799    }
800
801    #[test]
802    fn test_next_multiple() {
803        assert_eq!(next_multiple(10, 16), 16);
804        assert_eq!(next_multiple(11, 16), 16);
805        assert_eq!(next_multiple(15, 16), 16);
806        assert_eq!(next_multiple(16, 16), 16);
807        assert_eq!(next_multiple(17, 16), 32);
808    }
809
810    #[test]
811    fn test_encode() {
812        use h264_reader::rbsp::decode_nal;
813        use std::ops::Deref;
814
815        let nal_with_escape = &b"\x67\x64\x00\x0A\xAC\x72\x84\x44\x26\x84\x00\x00\x03\x00\x04\x00\x00\x03\x00\xCA\x3C\x48\x96\x11\x80"[..];
816        let rbsp = &b"\x64\x00\x0a\xac\x72\x84\x44\x26\x84\x00\x00\x00\x04\x00\x00\x00\xca\x3c\x48\x96\x11\x80"[..];
817
818        // `decode_nal` consumes the first byte as the NAL header
819        assert_eq!(decode_nal(nal_with_escape).unwrap().deref(), rbsp);
820
821        let mut nal2 = vec![0u8; rbsp.len() * 3];
822        let sz = rbsp_to_ebsp(rbsp, &mut nal2);
823        nal2.truncate(sz);
824        nal2.insert(0, 0); // `decode_nal` needs first byte
825
826        assert_eq!(decode_nal(&nal2).unwrap().deref(), rbsp);
827    }
828
829    #[test]
830    fn test_sps() {
831        let width = 128;
832        let height = 96;
833
834        let pic_width_in_mbs_minus1 = div_ceil(width, 16) - 1;
835        let pic_height_in_map_units_minus1 = div_ceil(height, 16) - 1;
836
837        let payload = Sps::new(
838            ProfileIdc::baseline(),
839            pic_width_in_mbs_minus1,
840            pic_height_in_map_units_minus1,
841            None,
842            None,
843        )
844        .to_rbsp();
845        let encoded = NalUnit::new(
846            NalRefIdc::Three,
847            NalUnitType::SequenceParameterSet,
848            payload.clone(),
849        )
850        .to_annex_b_data();
851        assert_eq!(&encoded, HELLO_SPS);
852
853        let sps = SeqParameterSet::from_bits(BitReader::new(&payload.data[..])).unwrap();
854        let sps2 = RefNal::new(&encoded[4..], &[], true);
855        let sps2 = SeqParameterSet::from_bits(sps2.rbsp_bits()).unwrap();
856        assert_eq!(format!("{sps:?}"), format!("{sps2:?}")); // compare debug representations
857
858        assert_eq!(sps.pic_width_in_mbs_minus1, pic_width_in_mbs_minus1);
859        assert_eq!(
860            sps.pic_height_in_map_units_minus1,
861            pic_height_in_map_units_minus1
862        );
863    }
864
865    #[test]
866    fn test_pps() {
867        let payload = Pps::new(0).to_rbsp();
868        let encoded = NalUnit::new(
869            NalRefIdc::Three,
870            NalUnitType::PictureParameterSet,
871            payload.clone(),
872        )
873        .to_annex_b_data();
874
875        assert_eq!(&encoded, HELLO_PPS);
876
877        let sps = SeqParameterSet::from_bits(BitReader::new(&HELLO_SPS[5..])).unwrap();
878
879        let mut ctx = Context::default();
880        ctx.put_seq_param_set(sps);
881        let _pps = PicParameterSet::from_bits(&ctx, BitReader::new(&payload.data[..])).unwrap();
882    }
883
884    #[test]
885    fn test_slice_header() {
886        let sps = Sps::new(ProfileIdc::baseline(), 5, 5, None, None);
887        let pps = Pps::new(0);
888        let payload = SliceHeader::new().to_rbsp(&sps, &pps);
889
890        let sps = SeqParameterSet::from_bits(BitReader::new(&HELLO_SPS[5..])).unwrap();
891        let mut ctx = Context::default();
892        ctx.put_seq_param_set(sps);
893        let pps = PicParameterSet::from_bits(&ctx, BitReader::new(&HELLO_PPS[5..])).unwrap();
894        ctx.put_pic_param_set(pps);
895
896        fn dbg_hex(vals: &[u8]) -> &[u8] {
897            println!();
898            for v in vals.iter() {
899                println!("{:03} 0b{:08b} 0x{:02x}", v, v, v);
900            }
901            println!();
902            vals
903        }
904
905        let encoded = NalUnit::new(
906            NalRefIdc::One,
907            NalUnitType::CodedSliceOfAnIDRPicture,
908            payload,
909        )
910        .to_annex_b_data();
911
912        let hello_slice_header = {
913            let nal = RefNal::new(&FIXED_HELLO_SLICE_HEADER[4..], &[], true);
914            let hello_slice_header = h264_reader::nal::slice::SliceHeader::from_bits(
915                &ctx,
916                &mut nal.rbsp_bits(),
917                nal.header().unwrap(),
918            )
919            .unwrap()
920            .0;
921            hello_slice_header
922        };
923
924        let nal = RefNal::new(&encoded[4..], &[], true);
925        let slice_header = h264_reader::nal::slice::SliceHeader::from_bits(
926            &ctx,
927            &mut nal.rbsp_bits(),
928            nal.header().unwrap(),
929        )
930        .unwrap()
931        .0;
932
933        assert_eq!(
934            format!("{:?}", hello_slice_header),
935            format!("{:?}", slice_header)
936        );
937        assert_eq!(dbg_hex(&encoded), dbg_hex(FIXED_HELLO_SLICE_HEADER));
938    }
939
940    #[test]
941    fn test_macroblock() {
942        let mut bv: BitVec<u8, Msb0> = BitVec::new();
943        bv.extend_exp_golomb(MacroblockType::I_PCM.mb_type());
944        let macroblock_header = bv.as_raw_slice();
945
946        assert_eq!(macroblock_header, HELLO_MACROBLOCK_HEADER);
947    }
948}