diff --git a/Cargo.toml b/Cargo.toml index 935e850..5dbe640 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "moq-vaapi" version = "0.1.1" -description = "(AI GENERATED) VA-API H.264/H.265 hardware encoder and H.264 decoder, derived from discord/cros-libva + discord/cros-codecs" +description = "(AI GENERATED) VA-API H.264/H.265 hardware encoder and decoder, derived from discord/cros-libva + discord/cros-codecs" authors = ["The ChromiumOS Authors", "Discord", "Luke Curley "] repository = "https://github.com/moq-dev/vaapi" license = "BSD-3-Clause" diff --git a/README.md b/README.md index 36a0378..9076eaa 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,21 @@ pulled in as a git dependency. through `VAEntrypointVideoProc`, scaling, converting, and re-tiling on the way. `Processor::resize` scales into NV12 surfaces it recycles per size, so a stream of resizes costs no allocation per frame. -- **H.264 decode** over VA-API: one Annex-B access unit in, tightly-packed NV12 - out, with in-stream parameter sets, a conformant DPB (reference marking, - reordering, frame_num gaps), and mid-stream resolution changes. Progressive - 8-bit 4:2:0 only, with right/bottom cropping supported and left/top cropping - rejected. Verified bit-exact against ffmpeg's software decoder on Intel +- **H.264 and H.265 decode** over VA-API: one Annex-B access unit in, + tightly-packed NV12 out, with in-stream parameter sets, a conformant DPB, and + mid-stream resolution changes. H.264 covers reference marking, reordering, + and frame_num gaps; H.265 covers reference picture sets, long-term + references, open-GOP CRA pictures, and dependent slice segments, and puts a + picture out as soon as the SPS's reorder limit allows, so a stream without + B-frames comes out with no delay. H.265 is the Main profile, plus Main 10 and + Main Still Picture streams that hold 8-bit samples. Progressive 8-bit 4:2:0 + only, with right/bottom cropping supported and left/top cropping rejected. + H.264 was verified bit-exact against ffmpeg's software decoder on Intel Meteor Lake (iHD 26.1.5) for constrained baseline, main with B-frames, high at - 720p, and a cropped non-macroblock-aligned size. The main-with-B-frames case - runs as a test wherever ffmpeg and a VA-API device are both present. + 720p, and a cropped non-macroblock-aligned size. Both codecs, coded by x264 + and x265 with B-frames at 320x240 and cropped at 852x478, run bit-exact as a + test wherever ffmpeg and a VA-API device are both present, and pass on Intel + Arrow Lake (iHD 26.1.2) and AMD Radeon RX 9070 (radeonsi, Mesa 26.0.8). - **Checked-in bindings from pinned headers.** libva's headers are vendored into [`libva/`](./libva) (see `just vendor`), and `just bindgen` generates [`src/bindings/libva.rs`](./src/bindings/libva.rs) from them. The output is @@ -87,7 +94,7 @@ This run checked all five cros-libva commits and the one cros-codecs commit: | cros-libva [`89454d66`](https://github.com/chromeos/cros-libva/commit/89454d660411627a4a463116fdea00ac1ac888bc), explicit image lifetimes | Already present in `Image::create_from` and `Picture::{derive_image,create_image}`. | | cros-libva [`9f3c0d92`](https://github.com/chromeos/cros-libva/commit/9f3c0d9264ec04b4fa792764178b879341ce33b1), packed-header bindings | Already present in the `bindgen` allowlist, `buffer.rs`, and the H.264 encoder. | | cros-libva [`ed9c63b7`](https://github.com/chromeos/cros-libva/commit/ed9c63b743ce9490e1079a8b0c1a24b8e879324f), borrowed input buffers | Optional copy avoidance, not a correctness fix. Keep the existing owned-buffer API; no new public API or optimization without measurement. | -| cros-libva [`59384456`](https://github.com/chromeos/cros-libva/commit/59384456ac2ae78c0c3e5515f41ef1efd9b802cf), libva 2.23 VP9 fields | Build compatibility already covered by `..Default::default()` in the retained VP9 wrapper against pinned 2.23 headers. The codec drivers support H.264 only, so no VP9 segmentation control is needed. | +| cros-libva [`59384456`](https://github.com/chromeos/cros-libva/commit/59384456ac2ae78c0c3e5515f41ef1efd9b802cf), libva 2.23 VP9 fields | Build compatibility already covered by `..Default::default()` in the retained VP9 wrapper against pinned 2.23 headers. The codec drivers support only H.264 and H.265, so no VP9 segmentation control is needed. | | cros-codecs [`a050c6cd`](https://github.com/chromeos/cros-codecs/commit/a050c6cde530e563197ee9e3e72ab01f48a0c41f), release log filtering | Ported: remove `log/release_max_level_debug`, which conflicts with a consumer selecting another release level. `just ci` checks feature unification with `log/release_max_level_trace`. | GitHub's published security-advisory endpoints for @@ -102,8 +109,8 @@ advisories checked, not a guarantee that no undisclosed vulnerabilities exist. ## Layout - `src/` — libva bindings (`bindings`, `display`, `surface`, `buffer`, ...), the - H.264 bitstream layer (`bitstream_utils`, `codec::h264`), the H.265 header - writer (`encode/h265.rs`), the thin encode + H.264 and H.265 bitstream layers (`bitstream_utils`, `codec::h264`, + `codec::h265`), the H.265 header writer (`encode/h265.rs`), the thin encode and decode drivers (`encode`, `decode`), DMA-BUF import (`dmabuf`), video post-processing (`vpp`), and the color space both share (`color`). - `libva/` — vendored libva headers (checked in; refreshed by `just vendor`). diff --git a/src/codec.rs b/src/codec.rs index 896f40b..ee0d812 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,5 +1,7 @@ -// Vendored from discord/cros-codecs (BSD-3-Clause), trimmed to H.264. +// Vendored from discord/cros-codecs (BSD-3-Clause): H.264, and the H.265 +// parser and DPB. pub mod h264; +pub mod h265; /// A video coding format. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] diff --git a/src/codec/h265.rs b/src/codec/h265.rs new file mode 100644 index 0000000..44f3f8a --- /dev/null +++ b/src/codec/h265.rs @@ -0,0 +1,7 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +pub mod dpb; +pub mod parser; +pub mod picture; diff --git a/src/codec/h265/dpb.rs b/src/codec/h265/dpb.rs new file mode 100644 index 0000000..3ac0614 --- /dev/null +++ b/src/codec/h265/dpb.rs @@ -0,0 +1,271 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::cell::Ref; +use std::cell::RefCell; +use std::cell::RefMut; +use std::rc::Rc; + +use crate::codec::h265::parser::Sps; +use crate::codec::h265::picture::PictureData; +use crate::codec::h265::picture::Reference; + +// Shortcut to refer to a DPB entry. +// +// The first member of the tuple is the `PictureData` for the frame. +// +// The second member is the backend handle of the frame. +#[derive(Clone, Debug)] +pub struct DpbEntry(pub Rc>, pub T); + +pub struct Dpb { + /// List of `PictureData` and backend handles to decoded pictures. + entries: Vec>, + /// The maximum number of pictures that can be stored. + max_num_pics: usize, +} + +impl Dpb { + /// Returns an iterator over the underlying H265 pictures stored in the + /// DPB. + pub fn pictures(&self) -> impl Iterator> { + self.entries.iter().map(|h| h.0.borrow()) + } + + /// Returns a mutable iterator over the underlying H265 pictures stored in + /// the DPB. + pub fn pictures_mut(&mut self) -> impl Iterator> { + self.entries.iter().map(|h| h.0.borrow_mut()) + } + + /// Returns the length of the DPB. + pub fn len(&self) -> usize { + self.entries.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get a reference to the whole DPB entries. + pub fn entries(&self) -> &Vec> { + &self.entries + } + + /// Set the dpb's max num pics. + pub fn set_max_num_pics(&mut self, max_num_pics: usize) { + self.max_num_pics = max_num_pics; + } + + /// Get a reference to the dpb's max num pics. + pub fn max_num_pics(&self) -> usize { + self.max_num_pics + } + + /// Mark all pictures in the DPB as unused for reference. + pub fn mark_all_as_unused_for_ref(&mut self) { + for mut picture in self.pictures_mut() { + picture.set_reference(Reference::None); + } + } + + /// Gets the position of `needle` in the DPB, if any. + fn get_position(&self, needle: &Rc>) -> Option { + self.entries.iter().position(|handle| Rc::ptr_eq(&handle.0, needle)) + } + + /// Finds a reference picture in the DPB using `poc`. + pub fn find_ref_by_poc(&self, poc: i32) -> Option> { + let position = self.pictures().position(|p| p.is_ref() && p.pic_order_cnt_val == poc); + + log::debug!("find_ref_by_poc: {}, found position {:?}", poc, position); + Some(self.entries[position?].clone()) + } + + /// Finds a reference picture in the DPB using `poc` and `mask`. + pub fn find_ref_by_poc_masked(&self, poc: i32, mask: i32) -> Option> { + let position = self + .pictures() + .position(|p| p.is_ref() && p.pic_order_cnt_val & mask == poc); + + log::debug!("find_ref_by_poc: {}, found position {:?}", poc, position); + Some(self.entries[position?].clone()) + } + + /// Finds a short term reference picture in the DPB using `poc`. + pub fn find_short_term_ref_by_poc(&self, poc: i32) -> Option> { + let position = self + .pictures() + .position(|p| matches!(p.reference(), Reference::ShortTerm) && p.pic_order_cnt_val == poc); + + log::debug!("find_short_term_ref_by_poc: {}, found position {:?}", poc, position); + Some(self.entries[position?].clone()) + } + + /// Drains the DPB by continuously invoking the bumping process. + pub fn drain(&mut self) -> Vec> { + log::debug!("Draining the DPB."); + + let mut pics = vec![]; + while let Some(pic) = self.bump(true) { + pics.push(pic); + } + + pics + } + + /// Whether the DPB needs bumping. See C.5.2.2. + pub fn needs_bumping(&mut self, sps: &Sps) -> bool { + let highest_tid = sps.max_sub_layers_minus1; + let max_dec_pic_buffering = usize::from(sps.max_dec_pic_buffering_minus1[usize::from(highest_tid)]) + 1; + + self.output_overdue(sps) || self.entries().len() >= max_dec_pic_buffering + } + + /// Whether more pictures wait for output than the reorder limit allows, or + /// one has waited past the latency limit, SpsMaxLatencyPictures (7-9). + fn output_overdue(&self, sps: &Sps) -> bool { + let highest_tid = usize::from(sps.max_sub_layers_minus1); + let max_num_reorder_pics = sps.max_num_reorder_pics[highest_tid]; + let max_latency_increase_plus1 = sps.max_latency_increase_plus1[highest_tid]; + let max_latency_pictures = i32::from(max_num_reorder_pics) + i32::from(max_latency_increase_plus1) - 1; + + let num_needed_for_output = self.pictures().filter(|pic| pic.needed_for_output).count(); + let pic_over_max_latency = self + .pictures() + .any(|pic| pic.needed_for_output && pic.pic_latency_cnt >= max_latency_pictures); + + num_needed_for_output > max_num_reorder_pics.into() || (max_latency_increase_plus1 != 0 && pic_over_max_latency) + } + + /// Find the lowest POC in the DPB that can be bumped. + fn find_lowest_poc_for_bumping(&self) -> Option> { + let lowest = self + .pictures() + .filter(|pic| pic.needed_for_output) + .min_by_key(|pic| pic.pic_order_cnt_val)?; + + let position = self + .entries + .iter() + .position(|handle| handle.0.borrow().pic_order_cnt_val == lowest.pic_order_cnt_val) + .unwrap(); + + Some(self.entries[position].clone()) + } + + /// See C.5.2.4 "Bumping process". + pub fn bump(&mut self, flush: bool) -> Option> { + let handle = self.find_lowest_poc_for_bumping()?; + let mut pic = handle.0.borrow_mut(); + + pic.needed_for_output = false; + log::debug!("Bumping POC {} from the dpb", pic.pic_order_cnt_val); + log::trace!("{:#?}", pic); + + if !pic.is_ref() || flush { + let index = self.get_position(&handle.0).unwrap(); + + log::debug!( + "Removed POC {} from the dpb: reference: {}, flush: {}", + pic.pic_order_cnt_val, + pic.is_ref(), + flush + ); + log::trace!("{:#?}", pic); + + self.entries.remove(index); + } + + Some(handle.clone()) + } + + /// See C.5.2.3. Happens when we are done decoding the picture. + pub fn needs_additional_bumping(&mut self, sps: &Sps) -> bool { + self.output_overdue(sps) + } + + /// Clears the DPB, dropping all the pictures. + pub fn clear(&mut self) { + log::debug!("Clearing the DPB"); + + let max_num_pics = self.max_num_pics; + + *self = Default::default(); + self.max_num_pics = max_num_pics; + } + + /// Removes all pictures which are marked as "not needed for output" and + /// "unused for reference". See C.5.2.2 + pub fn remove_unused(&mut self) { + log::debug!("Removing unused pictures from DPB."); + self.entries.retain(|e| { + let pic = e.0.borrow(); + let retain = pic.needed_for_output || pic.is_ref(); + log::debug!("Retaining pic POC: {}: {}", pic.pic_order_cnt_val, retain); + retain + }) + } + + /// Store a picture and its backend handle in the DPB. + pub fn store_picture(&mut self, picture: Rc>, handle: T) -> Result<(), String> { + if self.entries.len() >= self.max_num_pics { + return Err("Can't add a picture to the DPB: DPB is full.".into()); + } + + let mut pic = picture.borrow_mut(); + log::debug!( + "Stored picture POC {:?}, the DPB length is {:?}", + pic.pic_order_cnt_val, + self.entries.len() + ); + + if pic.pic_output_flag { + pic.needed_for_output = true; + pic.pic_latency_cnt = 0; + } else { + pic.needed_for_output = false; + } + + // C.3.4. + // After all the slices of the current picture have been decoded, this + // picture is marked as "used for short-term reference". + pic.set_reference(Reference::ShortTerm); + drop(pic); + + for mut pic in self.pictures_mut() { + pic.pic_latency_cnt += 1; + } + + self.entries.push(DpbEntry(picture, handle)); + + Ok(()) + } + + /// Returns all the references in the DPB. + pub fn get_all_references(&self) -> Vec> { + self.entries.iter().filter(|e| e.0.borrow().is_ref()).cloned().collect() + } +} + +impl Default for Dpb { + fn default() -> Self { + // See https://github.com/rust-lang/rust/issues/26925 on why this can't + // be derived. + Self { + entries: Default::default(), + max_num_pics: Default::default(), + } + } +} + +impl std::fmt::Debug for Dpb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let pics = self.entries.iter().map(|h| &h.0).enumerate().collect::>(); + f.debug_struct("Dpb") + .field("pictures", &pics) + .field("max_num_pics", &self.max_num_pics) + .finish() + } +} diff --git a/src/codec/h265/parser.rs b/src/codec/h265/parser.rs new file mode 100644 index 0000000..aa93d78 --- /dev/null +++ b/src/codec/h265/parser.rs @@ -0,0 +1,4062 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! An Annex B h.265 parser. +//! +//! Parses VPSs, SPSs, PPSs and Slices from NALUs. + +use std::collections::BTreeMap; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::rc::Rc; + +use crate::bitstream_utils::BitReader; +use crate::codec::h264::nalu; +use crate::codec::h264::nalu::Header; +use crate::codec::h264::parser::Point; +use crate::codec::h264::parser::Rect; + +// Given the max VPS id. +const MAX_VPS_COUNT: usize = 16; +// Given the max SPS id. +const MAX_SPS_COUNT: usize = 16; +// Given the max PPS id. +const MAX_PPS_COUNT: usize = 64; +// The most tile columns and rows any level allows (Table A.8), including the +// last of each, which is inferred rather than signalled. +const MAX_TILE_COLUMNS: usize = 20; +const MAX_TILE_ROWS: usize = 22; +// 7.4.7.1 +const MAX_REF_IDX_ACTIVE: u32 = 15; + +// 7.4.3.2.1: +// num_short_term_ref_pic_sets specifies the number of st_ref_pic_set( ) syntax +// structures included in the SPS. The value of num_short_term_ref_pic_sets +// shall be in the range of 0 to 64, inclusive. +// NOTE 5 – A decoder should allocate memory for a total number of +// num_short_term_ref_pic_sets + 1 st_ref_pic_set( ) syntax structures since +// there may be a st_ref_pic_set( ) syntax structure directly signalled in the +// slice headers of a current picture. A st_ref_pic_set( ) syntax structure +// directly signalled in the slice headers of a current picture has an index +// equal to num_short_term_ref_pic_sets. +const MAX_SHORT_TERM_REF_PIC_SETS: usize = 65; + +// 7.4.3.2.1: +const MAX_LONG_TERM_REF_PIC_SETS: usize = 32; + +// From table 7-5. +const DEFAULT_SCALING_LIST_0: [u8; 16] = [16; 16]; + +// From Table 7-6. +const DEFAULT_SCALING_LIST_1: [u8; 64] = [ + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 17, 16, 17, 18, 17, 18, 18, 17, 18, 21, 19, 20, 21, 20, 19, 21, 24, + 22, 22, 24, 24, 22, 22, 24, 25, 25, 27, 30, 27, 25, 25, 29, 31, 35, 35, 31, 29, 36, 41, 44, 41, 36, 47, 54, 54, 47, + 65, 70, 65, 88, 88, 115, +]; + +// From Table 7-6. +const DEFAULT_SCALING_LIST_2: [u8; 64] = [ + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 20, 20, 20, 20, 20, 20, 20, 24, + 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 28, 28, 28, 28, 28, 28, 33, 33, 33, 33, 33, 41, 41, 41, 41, + 54, 54, 54, 71, 71, 91, +]; + +/// Table 7-1 – NAL unit type codes and NAL unit type classes +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub enum NaluType { + #[default] + TrailN = 0, + TrailR = 1, + TsaN = 2, + TsaR = 3, + StsaN = 4, + StsaR = 5, + RadlN = 6, + RadlR = 7, + RaslN = 8, + RaslR = 9, + RsvVclN10 = 10, + RsvVclR11 = 11, + RsvVclN12 = 12, + RsvVclR13 = 13, + RsvVclN14 = 14, + RsvVclR15 = 15, + BlaWLp = 16, + BlaWRadl = 17, + BlaNLp = 18, + IdrWRadl = 19, + IdrNLp = 20, + CraNut = 21, + RsvIrapVcl22 = 22, + RsvIrapVcl23 = 23, + RsvVcl24 = 24, + RsvVcl25 = 25, + RsvVcl26 = 26, + RsvVcl27 = 27, + RsvVcl28 = 28, + RsvVcl29 = 29, + RsvVcl30 = 30, + RsvVcl31 = 31, + VpsNut = 32, + SpsNut = 33, + PpsNut = 34, + AudNut = 35, + EosNut = 36, + EobNut = 37, + FdNut = 38, + PrefixSeiNut = 39, + SuffixSeiNut = 40, + RsvNvcl41 = 41, + RsvNvcl42 = 42, + RsvNvcl43 = 43, + RsvNvcl44 = 44, + RsvNvcl45 = 45, + RsvNvcl46 = 46, + RsvNvcl47 = 47, +} + +impl TryFrom for NaluType { + type Error = String; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(NaluType::TrailN), + 1 => Ok(NaluType::TrailR), + 2 => Ok(NaluType::TsaN), + 3 => Ok(NaluType::TsaR), + 4 => Ok(NaluType::StsaN), + 5 => Ok(NaluType::StsaR), + 6 => Ok(NaluType::RadlN), + 7 => Ok(NaluType::RadlR), + 8 => Ok(NaluType::RaslN), + 9 => Ok(NaluType::RaslR), + 10 => Ok(NaluType::RsvVclN10), + 11 => Ok(NaluType::RsvVclR11), + 12 => Ok(NaluType::RsvVclN12), + 13 => Ok(NaluType::RsvVclR13), + 14 => Ok(NaluType::RsvVclN14), + 15 => Ok(NaluType::RsvVclR15), + 16 => Ok(NaluType::BlaWLp), + 17 => Ok(NaluType::BlaWRadl), + 18 => Ok(NaluType::BlaNLp), + 19 => Ok(NaluType::IdrWRadl), + 20 => Ok(NaluType::IdrNLp), + 21 => Ok(NaluType::CraNut), + 22 => Ok(NaluType::RsvIrapVcl22), + 23 => Ok(NaluType::RsvIrapVcl23), + 24 => Ok(NaluType::RsvVcl24), + 25 => Ok(NaluType::RsvVcl25), + 26 => Ok(NaluType::RsvVcl26), + 27 => Ok(NaluType::RsvVcl27), + 28 => Ok(NaluType::RsvVcl28), + 29 => Ok(NaluType::RsvVcl29), + 30 => Ok(NaluType::RsvVcl30), + 31 => Ok(NaluType::RsvVcl31), + 32 => Ok(NaluType::VpsNut), + 33 => Ok(NaluType::SpsNut), + 34 => Ok(NaluType::PpsNut), + 35 => Ok(NaluType::AudNut), + 36 => Ok(NaluType::EosNut), + 37 => Ok(NaluType::EobNut), + 38 => Ok(NaluType::FdNut), + 39 => Ok(NaluType::PrefixSeiNut), + 40 => Ok(NaluType::SuffixSeiNut), + 41 => Ok(NaluType::RsvNvcl41), + 42 => Ok(NaluType::RsvNvcl42), + 43 => Ok(NaluType::RsvNvcl43), + 44 => Ok(NaluType::RsvNvcl44), + 45 => Ok(NaluType::RsvNvcl45), + 46 => Ok(NaluType::RsvNvcl46), + 47 => Ok(NaluType::RsvNvcl47), + _ => Err(format!("Invalid NaluType {}", value)), + } + } +} + +impl NaluType { + /// Whether this is an IDR NALU. + pub fn is_idr(&self) -> bool { + matches!(self, Self::IdrWRadl | Self::IdrNLp) + } + + /// Whether this is an IRAP NALU. + pub fn is_irap(&self) -> bool { + let type_ = *self as u32; + type_ >= Self::BlaWLp as u32 && type_ <= Self::RsvIrapVcl23 as u32 + } + + /// Whether this is a BLA NALU. + pub fn is_bla(&self) -> bool { + let type_ = *self as u32; + type_ >= Self::BlaWLp as u32 && type_ <= Self::BlaNLp as u32 + } + + /// Whether this is a CRA NALU. + pub fn is_cra(&self) -> bool { + matches!(self, Self::CraNut) + } + + /// Whether this is a RADL NALU. + pub fn is_radl(&self) -> bool { + matches!(self, Self::RadlN | Self::RadlR) + } + + /// Whether this is a RASL NALU. + pub fn is_rasl(&self) -> bool { + matches!(self, Self::RaslN | Self::RaslR) + } + + //// Whether this is a SLNR NALU. + pub fn is_slnr(&self) -> bool { + // From the specification: + // If a picture has nal_unit_type equal to TRAIL_N, TSA_N, STSA_N, + // RADL_N, RASL_N, RSV_VCL_N10, RSV_VCL_N12 or RSV_VCL_N14, the picture + // is an SLNR picture. Otherwise, the picture is a sub-layer reference + // picture. + matches!( + self, + Self::TrailN + | Self::TsaN | Self::StsaN + | Self::RadlN + | Self::RaslN + | Self::RsvVclN10 + | Self::RsvVclN12 + | Self::RsvVclN14 + ) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct NaluHeader { + /// The NALU type. + pub type_: NaluType, + /// Specifies the identifier of the layer to which a VCL NAL unit belongs or + /// the identifier of a layer to which a non-VCL NAL unit applies. + pub nuh_layer_id: u8, + /// Minus 1 specifies a temporal identifier for the NAL unit. The value of + /// nuh_temporal_id_plus1 shall not be equal to 0. + pub nuh_temporal_id_plus1: u8, +} + +impl NaluHeader { + pub fn nuh_temporal_id(&self) -> u8 { + self.nuh_temporal_id_plus1.saturating_sub(1) + } +} + +impl Header for NaluHeader { + fn parse>(cursor: &mut std::io::Cursor) -> Result { + let mut data = [0u8; 2]; + cursor.read_exact(&mut data).map_err(|_| String::from("Broken Data"))?; + let mut r = BitReader::new(&data, false); + let _ = cursor.seek(SeekFrom::Current(-1 * data.len() as i64)); + + // Skip forbidden_zero_bit + r.skip_bits(1)?; + + Ok(Self { + type_: NaluType::try_from(r.read_bits::(6)?)?, + nuh_layer_id: r.read_bits::(6)?, + nuh_temporal_id_plus1: r.read_bits::(3)?, + }) + } + + fn is_end(&self) -> bool { + matches!(self.type_, NaluType::EosNut | NaluType::EobNut) + } + + fn len(&self) -> usize { + // 7.3.1.2 + 2 + } +} + +pub type Nalu<'a> = nalu::Nalu<'a, NaluHeader>; + +/// H265 levels as defined by table A.8. +/// `general_level_idc` and `sub_layer_level_idc[ OpTid ]` shall be set equal to a +/// value of 30 times the level number specified in Table A.8 +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub enum Level { + #[default] + L1 = 30, + L2 = 60, + L2_1 = 63, + L3 = 90, + L3_1 = 93, + L4 = 120, + L4_1 = 123, + L5 = 150, + L5_1 = 153, + L5_2 = 156, + L6 = 180, + L6_1 = 183, + L6_2 = 186, +} + +impl TryFrom for Level { + type Error = String; + + fn try_from(value: u8) -> Result { + match value { + 30 => Ok(Level::L1), + 60 => Ok(Level::L2), + 63 => Ok(Level::L2_1), + 90 => Ok(Level::L3), + 93 => Ok(Level::L3_1), + 120 => Ok(Level::L4), + 123 => Ok(Level::L4_1), + 150 => Ok(Level::L5), + 153 => Ok(Level::L5_1), + 156 => Ok(Level::L5_2), + 180 => Ok(Level::L6), + 183 => Ok(Level::L6_1), + 186 => Ok(Level::L6_2), + _ => Err(format!("Invalid Level {}", value)), + } + } +} + +/// H265 profiles. See A.3. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub enum Profile { + #[default] + Main = 1, + Main10 = 2, + MainStill = 3, + RangeExtensions = 4, + HighThroughput = 5, + MultiviewMain = 6, + ScalableMain = 7, + ThreeDMain = 8, + ScreenContentCoding = 9, + ScalableRangeExtensions = 10, + HighThroughputScreenContentCoding = 11, +} + +impl TryFrom for Profile { + type Error = String; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Profile::Main), + 2 => Ok(Profile::Main10), + 3 => Ok(Profile::MainStill), + 4 => Ok(Profile::RangeExtensions), + 5 => Ok(Profile::HighThroughput), + 6 => Ok(Profile::MultiviewMain), + 7 => Ok(Profile::ScalableMain), + 8 => Ok(Profile::ThreeDMain), + 9 => Ok(Profile::ScreenContentCoding), + 10 => Ok(Profile::ScalableRangeExtensions), + 11 => Ok(Profile::HighThroughputScreenContentCoding), + _ => Err(format!("Invalid Profile {}", value)), + } + } +} + +/// A H.265 Video Parameter Set. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Vps { + /// Identifies the VPS for reference by other syntax elements. + pub video_parameter_set_id: u8, + /// If vps_base_layer_internal_flag is equal to 1 and + /// vps_base_layer_available_flag is equal to 1, the base layer is present + /// in the bitstream. + pub base_layer_internal_flag: bool, + /// See `base_layer_internal_flag`. + pub base_layer_available_flag: bool, + /// Plus 1 specifies the maximum allowed number of layers in each CVS + /// referring to the VPS. + pub max_layers_minus1: u8, + /// Plus 1 specifies the maximum number of temporal sub-layers that may be + /// present in each CVS referring to the VPS. + pub max_sub_layers_minus1: u8, + /// When vps_max_sub_layers_minus1 is greater than 0, specifies whether + /// inter prediction is additionally restricted for CVSs referring to the + /// VPS. + pub temporal_id_nesting_flag: bool, + /// ProfileTierLevel() data. + pub profile_tier_level: ProfileTierLevel, + /// When true, specifies that `vps_max_dec_pic_buffering_minus1[ i ]`, + /// `vps_max_num_reorder_pics[ i ]` and `vps_max_latency_increase_plus1[ i ]` + /// are present for vps_max_sub_layers_ minus1 + 1 sub-layers. + /// vps_sub_layer_ordering_info_present_flag equal to 0 specifies that the + /// values of `vps_max_dec_pic_buffering_minus1[ vps_max_sub_layers_minus1 ]`, + /// vps_max_num_reorder_pics[ vps_max_sub_ layers_minus1 ] and + /// `vps_max_latency_increase_plus1[ vps_max_sub_layers_minus1 ]` apply to all + /// sub-layers + pub sub_layer_ordering_info_present_flag: bool, + /// `max_dec_pic_buffering_minus1[i]` plus 1 specifies the maximum required + /// size of the decoded picture buffer for the CVS in units of picture + /// storage buffers when HighestTid is equal to i. + pub max_dec_pic_buffering_minus1: [u32; 7], + /// Indicates the maximum allowed number of pictures with PicOutputFlag + /// equal to 1 that can precede any picture with PicOutputFlag equal to 1 in + /// the CVS in decoding order and follow that picture with PicOutputFlag + /// equal to 1 in output order when HighestTid is equal to i. + pub max_num_reorder_pics: [u32; 7], + /// When true, `max_latency_increase_plus1[i]` is used to compute the value of + /// `VpsMaxLatencyPictures[ i ]`, which specifies the maximum number of + /// pictures with PicOutputFlag equal to 1 that can precede any picture with + /// PicOutputFlag equal to 1 in the CVS in output order and follow that + /// picture with PicOutputFlag equal to 1 in decoding order when HighestTid + /// is equal to i. + pub max_latency_increase_plus1: [u32; 7], + /// Specifies the maximum allowed value of nuh_layer_id of all NAL units in + /// each CVS referring to the VPS. + pub max_layer_id: u8, + /// num_layer_sets_minus1 plus 1 specifies the number of layer sets that are + /// specified by the VPS. + pub num_layer_sets_minus1: u32, + /// When true, specifies that num_units_in_tick, time_scale, + /// poc_proportional_to_timing_flag and num_hrd_parameters are present in + /// the VPS. + pub timing_info_present_flag: bool, + /// The number of time units of a clock operating at the frequency + /// vps_time_scale Hz that corresponds to one increment (called a clock + /// tick) of a clock tick counter. The value of vps_num_units_in_tick shall + /// be greater than 0. A clock tick, in units of seconds, is equal to the + /// quotient of vps_num_units_in_tick divided by vps_time_scale. For + /// example, when the picture rate of a video signal is 25 Hz, + /// vps_time_scale may be equal to 27 000 000 and vps_num_units_in_tick may + /// be equal to 1 080 000, and consequently a clock tick may be 0.04 + /// seconds. + pub num_units_in_tick: u32, + /// The number of time units that pass in one second. For example, a time + /// coordinate system that measures time using a 27 MHz clock has a + /// vps_time_scale of 27 000 000. + pub time_scale: u32, + /// When true, indicates that the picture order count value for each picture + /// in the CVS that is not the first picture in the CVS, in decoding order, + /// is proportional to the output time of the picture relative to the output + /// time of the first picture in the CVS. When false, indicates that the + /// picture order count value for each picture in the CVS that is not the + /// first picture in the CVS, in decoding order, may or may not be + /// proportional to the output time of the picture relative to the output + /// time of the first picture in the CVS. + pub poc_proportional_to_timing_flag: bool, + /// num_ticks_poc_diff_one_minus1 plus 1 specifies the number of clock ticks + /// corresponding to a difference of picture order count values equal to 1. + pub num_ticks_poc_diff_one_minus1: u32, + /// Specifies the number of hrd_parameters( ) syntax structures present in + /// the VPS RBSP before the vps_extension_flag syntax element. + pub num_hrd_parameters: u32, + /// `hrd_layer_set_idx[ i ]` specifies the index, into the list of layer sets + /// specified by the VPS, of the layer set to which the i-th hrd_parameters( + /// ) syntax structure in the VPS applies. + pub hrd_layer_set_idx: Vec, + /// `cprms_present_flag[ i ]` equal to true specifies that the HRD parameters + /// that are common for all sub-layers are present in the i-th + /// hrd_parameters( ) syntax structure in the VPS. `cprms_present_flag[ i ]` + /// equal to false specifies that the HRD parameters that are common for all + /// sub-layers are not present in the i-th hrd_parameters( ) syntax + /// structure in the VPS and are derived to be the same as the ( i − 1 )-th + /// hrd_parameters( ) syntax structure in the VPS. `cprms_present_flag[ 0 ]` + /// is inferred to be equal to true. + pub cprms_present_flag: Vec, + /// The hrd_parameters() data. + pub hrd_parameters: Vec, + /// When false, specifies that no vps_extension_data_flag syntax elements + /// are present in the VPS RBSP syntax structure. When true, specifies that + /// there are vps_extension_data_flag syntax elements present in the VPS + /// RBSP syntax structure. Decoders conforming to a profile specified in + /// Annex A but not supporting the INBLD capability specified in Annex F + /// shall ignore all data that follow the value 1 for vps_extension_flag in + /// a VPS NAL unit. + pub extension_flag: bool, +} + +impl Default for Vps { + fn default() -> Self { + Self { + video_parameter_set_id: Default::default(), + base_layer_internal_flag: Default::default(), + base_layer_available_flag: Default::default(), + max_layers_minus1: Default::default(), + max_sub_layers_minus1: Default::default(), + temporal_id_nesting_flag: Default::default(), + profile_tier_level: Default::default(), + sub_layer_ordering_info_present_flag: Default::default(), + max_dec_pic_buffering_minus1: Default::default(), + max_num_reorder_pics: Default::default(), + max_latency_increase_plus1: Default::default(), + max_layer_id: Default::default(), + num_layer_sets_minus1: Default::default(), + timing_info_present_flag: Default::default(), + num_units_in_tick: Default::default(), + time_scale: Default::default(), + poc_proportional_to_timing_flag: Default::default(), + num_ticks_poc_diff_one_minus1: Default::default(), + num_hrd_parameters: Default::default(), + hrd_layer_set_idx: Default::default(), + cprms_present_flag: vec![true], + hrd_parameters: Default::default(), + extension_flag: Default::default(), + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ProfileTierLevel { + /// Specifies the context for the interpretation of general_profile_idc and + /// `general_profile_compatibility_flag[ j ]` for all values of j in the range + /// of 0 to 31, inclusive. + pub general_profile_space: u8, + /// Specifies the tier context for the interpretation of general_level_idc + /// as specified in Annex A. + pub general_tier_flag: bool, + /// When general_profile_space is equal to 0, indicates a profile to which + /// the CVS conforms as specified in Annex A. Bitstreams shall not contain + /// values of general_profile_idc other than those specified in Annex A. + /// Other values of general_profile_idc are reserved for future use by ITU-T + /// | ISO/IEC. + pub general_profile_idc: u8, + /// `general_profile_compatibility_flag[ j ]` equal to true, when + /// general_profile_space is false, indicates that the CVS conforms to the + /// profile indicated by general_profile_idc equal to j as specified in + /// Annex A. + pub general_profile_compatibility_flag: [bool; 32], + /// general_progressive_source_flag and general_interlaced_source_flag are + /// interpreted as follows: + /// + /// –If general_progressive_source_flag is true and + /// general_interlaced_source_flag is false, the source scan type of the + /// pictures in the CVS should be interpreted as progressive only. + /// + /// –Otherwise, if general_progressive_source_flag is false and + /// general_interlaced_source_flag is true, the source scan type of the + /// pictures in the CVS should be interpreted as interlaced only. + /// + /// –Otherwise, if general_progressive_source_flag is false and + /// general_interlaced_source_flag is false, the source scan type of the + /// pictures in the CVS should be interpreted as unknown or unspecified. + /// + /// –Otherwise (general_progressive_source_flag is true and + /// general_interlaced_source_flag is true), the source scan type of each + /// picture in the CVS is indicated at the picture level using the syntax + /// element source_scan_type in a picture timing SEI message. + pub general_progressive_source_flag: bool, + /// See `general_progressive_source_flag`. + pub general_interlaced_source_flag: bool, + /// If true, specifies that there are no frame packing arrangement SEI + /// messages, segmented rectangular frame packing arrangement SEI messages, + /// equirectangular projection SEI messages, or cubemap projection SEI + /// messages present in the CVS. If false, indicates that there may or may + /// not be one or more frame packing arrangement SEI messages, segmented + /// rectangular frame packing arrangement SEI messages, equirectangular + /// projection SEI messages, or cubemap projection SEI messages present in + /// the CVS. + pub general_non_packed_constraint_flag: bool, + /// When true, specifies that field_seq_flag is false. When false, indicates + /// that field_seq_flag may or may not be false. + pub general_frame_only_constraint_flag: bool, + /// See Annex A. + pub general_max_12bit_constraint_flag: bool, + /// See Annex A. + pub general_max_10bit_constraint_flag: bool, + /// See Annex A. + pub general_max_8bit_constraint_flag: bool, + /// See Annex A. + pub general_max_422chroma_constraint_flag: bool, + /// See Annex A. + pub general_max_420chroma_constraint_flag: bool, + /// See Annex A. + pub general_max_monochrome_constraint_flag: bool, + /// See Annex A. + pub general_intra_constraint_flag: bool, + /// See Annex A. + pub general_lower_bit_rate_constraint_flag: bool, + /// See Annex A. + pub general_max_14bit_constraint_flag: bool, + /// See Annex A. + pub general_one_picture_only_constraint_flag: bool, + /// When true, specifies that the INBLD capability as specified in Annex F + /// is required for decoding of the layer to which the profile_tier_level( ) + /// syntax structure applies. When false, specifies that the INBLD + /// capability as specified in Annex F is not required for decoding of the + /// layer to which the profile_tier_level( ) syntax structure applies. + pub general_inbld_flag: bool, + /// Indicates a level to which the CVS conforms as specified in Annex A. + pub general_level_idc: Level, + /// Sub-layer syntax element. + pub sub_layer_profile_present_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_level_present_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_profile_space: [u8; 6], + /// Sub-layer syntax element. + pub sub_layer_tier_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_profile_idc: [u8; 6], + /// Sub-layer syntax element. + pub sub_layer_profile_compatibility_flag: [[bool; 32]; 6], + /// Sub-layer syntax element. + pub sub_layer_progressive_source_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_interlaced_source_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_non_packed_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_frame_only_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_max_12bit_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_max_10bit_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_max_8bit_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_max_422chroma_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_max_420chroma_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_max_monochrome_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_intra_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_one_picture_only_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_lower_bit_rate_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_max_14bit_constraint_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_inbld_flag: [bool; 6], + /// Sub-layer syntax element. + pub sub_layer_level_idc: [Level; 6], +} + +impl ProfileTierLevel { + pub fn max_luma_ps(&self) -> u32 { + // See Table A.8. + match self.general_level_idc { + Level::L1 => 36864, + Level::L2 => 122880, + Level::L2_1 => 245760, + Level::L3 => 552960, + Level::L3_1 => 983040, + Level::L4 | Level::L4_1 => 2228224, + Level::L5 | Level::L5_1 | Level::L5_2 => 8912896, + _ => 35651584, + } + } + + pub fn max_dpb_pic_buf(&self) -> u32 { + if self.general_profile_idc >= 1 && self.general_profile_idc <= 5 { + 6 + } else { + 7 + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SpsRangeExtension { + pub transform_skip_rotation_enabled_flag: bool, + pub transform_skip_context_enabled_flag: bool, + pub implicit_rdpcm_enabled_flag: bool, + pub explicit_rdpcm_enabled_flag: bool, + pub extended_precision_processing_flag: bool, + pub intra_smoothing_disabled_flag: bool, + pub high_precision_offsets_enabled_flag: bool, + pub persistent_rice_adaptation_enabled_flag: bool, + pub cabac_bypass_alignment_enabled_flag: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SpsSccExtension { + /// When set, specifies that a picture in the CVS may be included in a + /// reference picture list of a slice of the picture itself. When not set, + /// specifies that a picture in the CVS is never included in a reference + /// picture list of a slice of the picture itself. + pub curr_pic_ref_enabled_flag: bool, + /// When set, specifies that the decoding process for palette mode may be + /// used for intra blocks. When not set, specifies that the decoding process + /// for palette mode is not applied. + pub palette_mode_enabled_flag: bool, + /// Specifies the maximum allowed palette size. + pub palette_max_size: u8, + /// Specifies the difference between the maximum allowed palette predictor + /// size and the maximum allowed palette size. + pub delta_palette_max_predictor_size: u8, + /// When set, specifies that the sequence palette predictors are initialized + /// using the sps_palette_predictor_initializers. When not set, specifies + /// that the entries in the sequence palette predictor are initialized to 0. + pub palette_predictor_initializers_present_flag: bool, + /// num_palette_predictor_initializers_minus1 plus 1 specifies the number of + /// entries in the sequence palette predictor initializer. + pub num_palette_predictor_initializer_minus1: u8, + /// `palette_predictor_initializer[ comp ][ i ]` specifies the value of the + /// comp-th component of the i-th palette entry in the SPS that is used to + /// initialize the array PredictorPaletteEntries. + pub palette_predictor_initializer: [[u32; 128]; 3], + /// Controls the presence and inference of the use_integer_mv_flag that + /// specifies the resolution of motion vectors for inter prediction. + pub motion_vector_resolution_control_idc: u8, + /// When set, specifies that the intra boundary filtering process is + /// unconditionally disabled for intra prediction. If not set, specifies + /// that the intra boundary filtering process may be used. + pub intra_boundary_filtering_disabled_flag: bool, +} + +impl Default for SpsSccExtension { + fn default() -> Self { + Self { + curr_pic_ref_enabled_flag: Default::default(), + palette_mode_enabled_flag: Default::default(), + palette_max_size: Default::default(), + delta_palette_max_predictor_size: Default::default(), + palette_predictor_initializers_present_flag: Default::default(), + num_palette_predictor_initializer_minus1: Default::default(), + palette_predictor_initializer: [[0; 128]; 3], + motion_vector_resolution_control_idc: Default::default(), + intra_boundary_filtering_disabled_flag: Default::default(), + } + } +} + +/// A H.265 Sequence Parameter Set. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Sps { + /// Specifies the value of the vps_video_parameter_set_id of the active VPS. + pub video_parameter_set_id: u8, + /// `max_sub_layers_minus1` plus 1 specifies the maximum number of temporal + /// sub-layers that may be present in each CVS referring to the SPS. + pub max_sub_layers_minus1: u8, + /// When sps_max_sub_layers_minus1 is greater than 0, specifies whether + /// inter prediction is additionally restricted for CVSs referring to the + /// SPS. + pub temporal_id_nesting_flag: bool, + /// profile_tier_level() data. + pub profile_tier_level: ProfileTierLevel, + /// Provides an identifier for the SPS for reference by other syntax + /// elements. + pub seq_parameter_set_id: u8, + /// Specifies the chroma sampling relative to the luma sampling as specified + /// in clause 6.2. + pub chroma_format_idc: u8, + /// When true, specifies that the three colour components of the 4:4:4 + /// chroma format are coded separately. When false, specifies that the + /// colour components are not coded separately. + pub separate_colour_plane_flag: bool, + /// Specifies the width of each decoded picture in units of luma samples. + pub pic_width_in_luma_samples: u16, + /// Specifies the height of each decoded picture in units of luma samples. + pub pic_height_in_luma_samples: u16, + /// When true, indicates that the conformance cropping window offset + /// parameters follow next in the SPS. When false, indicates that the + /// conformance cropping window offset parameters are not present. + pub conformance_window_flag: bool, + /* if conformance_window_flag */ + /// Specify the samples of the pictures in the CVS that are output from the + /// decoding process, in terms of a rectangular region specified in picture + /// coordinates for output. + pub conf_win_left_offset: u32, + pub conf_win_right_offset: u32, + pub conf_win_top_offset: u32, + pub conf_win_bottom_offset: u32, + + /// Specifies the bit depth of the samples of the luma array BitDepthY and + /// the value of the luma quantization parameter range offset QpBdOffsetY. + pub bit_depth_luma_minus8: u8, + /// Specifies the bit depth of the samples of the chroma arrays BitDepthC + /// and the value of the chroma quantization parameter range offset + /// QpBdOffsetC. + pub bit_depth_chroma_minus8: u8, + /// Specifies the value of the variable MaxPicOrderCntLsb that is used in + /// the decoding process for picture order count. + pub log2_max_pic_order_cnt_lsb_minus4: u8, + /// When true, specifies that `max_dec_pic_buffering_minus1[ i ]`, + /// `max_num_reorder_pics[ i ]` and `max_latency_increase_plus1[ i ]` are + /// present for max_sub_layers_minus1 + 1 sub- layers. When false, specifies + /// that the values of `max_dec_pic_ buffering_minus1[ max_sub_layers_minus1 + /// ]`, `max_num_reorder_pics[ max_sub_layers_minus1 ]` and max_ + /// `latency_increase_plus1[ max_sub_layers_minus1 ]` apply to all sub-layers. + pub sub_layer_ordering_info_present_flag: bool, + /// `max_dec_pic_buffering_minus1[ i ]` plus 1 specifies the maximum required + /// size of the decoded picture buffer for the CVS in units of picture + /// storage buffers when HighestTid is equal to i. + pub max_dec_pic_buffering_minus1: [u8; 7], + /// `max_num_reorder_pics[ i ]` indicates the maximum allowed number of + /// pictures with PicOutputFlag equal to 1 that can precede any picture with + /// PicOutputFlag equal to 1 in the CVS in decoding order and follow that + /// picture with PicOutputFlag equal to 1 in output order when HighestTid is + /// equal to i. + pub max_num_reorder_pics: [u8; 7], + /// `max_latency_increase_plus1[ i ]` not equal to 0 is used to compute the + /// value of `SpsMaxLatencyPictures[ i ]`, which specifies the maximum number + /// of pictures with PicOutputFlag equal to 1 that can precede any picture + /// with PicOutputFlag equal to 1 in the CVS in output order and follow that + /// picture with PicOutputFlag equal to 1 in decoding order when HighestTid + /// is equal to i. + pub max_latency_increase_plus1: [u8; 7], + /// min_luma_coding_block_size_minus3 plus 3 specifies the minimum luma + /// coding block size. + pub log2_min_luma_coding_block_size_minus3: u8, + /// Specifies the difference between the maximum and minimum luma coding + /// block size. + pub log2_diff_max_min_luma_coding_block_size: u8, + /// min_luma_transform_block_size_minus2 plus 2 specifies the minimum luma + /// transform block size. + pub log2_min_luma_transform_block_size_minus2: u8, + /// Specifies the difference between the maximum and minimum luma transform + /// block size. + pub log2_diff_max_min_luma_transform_block_size: u8, + /// Specifies the maximum hierarchy depth for transform units of coding + /// units coded in inter prediction mode. + pub max_transform_hierarchy_depth_inter: u8, + /// Specifies the maximum hierarchy depth for transform units of coding + /// units coded in intra prediction mode. + pub max_transform_hierarchy_depth_intra: u8, + /// When true, specifies that a scaling list is used for the scaling process + /// for transform coefficients. When false, specifies that scaling list is + /// not used for the scaling process for transform coefficients. + pub scaling_list_enabled_flag: bool, + /* if scaling_list_enabled_flag */ + /// When true, specifies that the scaling_list_data( ) syntax structure is + /// present in the SPS. When false, specifies that the scaling_list_data( ) + /// syntax structure is not present in the SPS. + pub scaling_list_data_present_flag: bool, + /// The scaling_list_data() syntax data. + pub scaling_list: ScalingLists, + /// When true, specifies that asymmetric motion partitions, i.e., PartMode + /// equal to PART_2NxnU, PART_2NxnD, PART_nLx2N or PART_nRx2N, may be used + /// in CTBs. When false, specifies that asymmetric motion partitions cannot + /// be used in CTBs. + pub amp_enabled_flag: bool, + /// When true, specifies that the sample adaptive offset process is applied + /// to the reconstructed picture after the deblocking filter process. When + /// false, specifies that the sample adaptive offset process is not applied + /// to the reconstructed picture after the deblocking filter process. + pub sample_adaptive_offset_enabled_flag: bool, + /// When false, specifies that PCM-related syntax + /// (pcm_sample_bit_depth_luma_minus1, pcm_sample_ bit_depth_chroma_minus1, + /// log2_min_pcm_luma_coding_block_size_minus3, log2_diff_max_min_pcm_luma_ + /// coding_block_size, pcm_loop_filter_disabled_flag, pcm_flag, + /// pcm_alignment_zero_bit syntax elements and pcm_sample( ) syntax + /// structure) is not present in the CVS. + pub pcm_enabled_flag: bool, + + /* if pcm_enabled_flag */ + pub pcm_sample_bit_depth_luma_minus1: u8, + /// Specifies the number of bits used to represent each of PCM sample values + /// of the luma component. + pub pcm_sample_bit_depth_chroma_minus1: u8, + /// Specifies the number of bits used to represent each of PCM sample values + /// of the chroma components. + pub log2_min_pcm_luma_coding_block_size_minus3: u8, + /// Specifies the difference between the maximum and minimum size of coding + /// blocks with pcm_flag equal to true. + pub log2_diff_max_min_pcm_luma_coding_block_size: u8, + /// Specifies whether the loop filter process is disabled on reconstructed + /// samples in a coding unit with pcm_flag equal to true as follows: + /// + /// – If pcm_loop_filter_disabled_flag is set, the deblocking filter and + /// sample adaptive offset filter processes on the reconstructed samples in + /// a coding unit with pcm_flag set are disabled. + /// + /// – Otherwise (pcm_loop_filter_disabled_flag value is not set), the + /// deblocking filter and sample adaptive offset filter processes on the + /// reconstructed samples in a coding unit with pcm_flag set are not + /// disabled. + pub pcm_loop_filter_disabled_flag: bool, + /// Specifies the number of st_ref_pic_set( ) syntax structures included in + /// the SPS. + pub num_short_term_ref_pic_sets: u8, + /// the st_ref_pic_set() data. + pub short_term_ref_pic_set: Vec, + /// If unset, specifies that no long-term reference picture is used for + /// inter prediction of any coded picture in the CVS. + /// If set, specifies that long-term reference pictures may be used for + /// inter prediction of one or more coded pictures in the CVS. + pub long_term_ref_pics_present_flag: bool, + + /* if long_term_ref_pics_present_flag */ + /// Specifies the number of candidate long-term reference pictures that are + /// specified in the SPS. + pub num_long_term_ref_pics_sps: u8, + /// `lt_ref_pic_poc_lsb_sps[ i ]` specifies the picture order count modulo + /// MaxPicOrderCntLsb of the i-th candidate long-term reference picture + /// specified in the SPS. + pub lt_ref_pic_poc_lsb_sps: [u32; MAX_LONG_TERM_REF_PIC_SETS], + /// `used_by_curr_pic_lt_sps_flag[ i ]` equal to false specifies that the i-th + /// candidate long-term reference picture specified in the SPS is not used + /// for reference by a picture that includes in its long-term reference + /// picture set (RPS) the i-th candidate long-term reference picture + /// specified in the SPS. + pub used_by_curr_pic_lt_sps_flag: [bool; MAX_LONG_TERM_REF_PIC_SETS], + /// When set, specifies that slice_temporal_mvp_enabled_flag is present in + /// the slice headers of non-IDR pictures in the CVS. When not set, + /// specifies that slice_temporal_mvp_enabled_flag is not present in slice + /// headers and that temporal motion vector predictors are not used in the + /// CVS. + pub temporal_mvp_enabled_flag: bool, + /// When set, specifies that bi-linear interpolation is conditionally used + /// in the intraprediction filtering process in the CVS as specified in + /// clause 8.4.4.2.3. + pub strong_intra_smoothing_enabled_flag: bool, + /// When set, specifies that the vui_parameters( ) syntax structure as + /// specified in Annex E is present. When not set, specifies that the + /// vui_parameters( ) syntax structure as specified in Annex E is not + /// present. + pub vui_parameters_present_flag: bool, + /// The vui_parameters() data. + pub vui_parameters: VuiParams, + /// When set, specifies that the syntax elements sps_range_extension_flag, + /// sps_multilayer_extension_flag, sps_3d_extension_flag, + /// sps_scc_extension_flag, and sps_extension_4bits are present in the SPS + /// RBSP syntax structure. When not set, specifies that these syntax + /// elements are not present. + pub extension_present_flag: bool, + + pub range_extension_flag: bool, + /// The sps_range_extension() data. + pub range_extension: SpsRangeExtension, + /// When set, specifies that the sps_scc_extension( ) syntax structure is + /// present in the SPS RBSP syntax structure. When not set, specifies that + /// this syntax structure is not present + pub scc_extension_flag: bool, + /// The sps_scc_extension() data. + pub scc_extension: SpsSccExtension, + + // Internal H265 variables. Computed from the bitstream. + /// Equivalent to MinCbLog2SizeY in the specification. + pub min_cb_log2_size_y: u32, + /// Equivalent to CtbLog2SizeY in the specification. + pub ctb_log2_size_y: u32, + /// Equivalent to CtbSizeY in the specification. + pub ctb_size_y: u32, + /// Equivalent to PicHeightInCtbsY in the specification. + pub pic_height_in_ctbs_y: u32, + /// Equivalent to PicWidthInCtbsY in the specification. + pub pic_width_in_ctbs_y: u32, + /// Equivalent to PicSizeInCtbsY in the specification. + pub pic_size_in_ctbs_y: u32, + /// Equivalent to ChromaArrayType in the specification. + pub chroma_array_type: u8, + /// Equivalent to WpOffsetHalfRangeY in the specification. + pub wp_offset_half_range_y: u32, + /// Equivalent to WpOffsetHalfRangeC in the specification. + pub wp_offset_half_range_c: u32, + /// Equivalent to MaxTbLog2SizeY in the specification. + pub max_tb_log2_size_y: u32, + /// Equivalent to PicSizeInSamplesY in the specification. + pub pic_size_in_samples_y: u32, + + /// The VPS referenced by this SPS, if any. + pub vps: Option>, +} + +impl Sps { + pub fn max_dpb_size(&self) -> usize { + let max_luma_ps = self.profile_tier_level.max_luma_ps(); + let max_dpb_pic_buf = self.profile_tier_level.max_dpb_pic_buf(); + + // Equation A-2 + let max = if self.pic_size_in_samples_y <= (max_luma_ps >> 2) { + std::cmp::min(4 * max_dpb_pic_buf, 16) + } else if self.pic_size_in_samples_y <= (max_luma_ps >> 1) { + std::cmp::min(2 * max_dpb_pic_buf, 16) + } else if self.pic_size_in_samples_y <= ((3 * max_luma_ps) >> 2) { + std::cmp::min(4 * max_dpb_pic_buf / 3, 16) + } else { + max_dpb_pic_buf + }; + + max as usize + } + + pub fn width(&self) -> u16 { + self.pic_width_in_luma_samples + } + + pub fn height(&self) -> u16 { + self.pic_height_in_luma_samples + } + + pub fn visible_rectangle(&self) -> Rect { + // From the specification: + // NOTE 3 – The conformance cropping window offset parameters are + // only applied at the output. All internal decoding processes are + // applied to the uncropped picture size. + if !self.conformance_window_flag { + return Rect { + min: Point { x: 0, y: 0 }, + max: Point { + x: u32::from(self.width()), + y: u32::from(self.height()), + }, + }; + } + const SUB_HEIGHT_C: [u32; 5] = [1, 2, 1, 1, 1]; + const SUB_WIDTH_C: [u32; 5] = [1, 2, 2, 1, 1]; + + let crop_unit_y = SUB_HEIGHT_C[usize::from(self.chroma_array_type)]; + let crop_unit_x = SUB_WIDTH_C[usize::from(self.chroma_array_type)]; + let crop_left = crop_unit_x * self.conf_win_left_offset; + let crop_right = crop_unit_x * self.conf_win_right_offset; + let crop_top = crop_unit_y * self.conf_win_top_offset; + let crop_bottom = crop_unit_y * self.conf_win_bottom_offset; + + Rect { + min: Point { + x: crop_left, + y: crop_top, + }, + max: Point { + x: u32::from(self.width()) - crop_left - crop_right, + y: u32::from(self.height()) - crop_top - crop_bottom, + }, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PpsSccExtension { + /// When set, specifies that a picture referring to the PPS may be included + /// in a reference picture list of a slice of the picture itself. If not + /// set, specifies that a picture referring to the PPS is never included in + /// a reference picture list of a slice of the picture itself. + pub curr_pic_ref_enabled_flag: bool, + /// When set, specifies that an adaptive colour transform may be applied to + /// the residual in the decoding process. When not set, specifies that + /// adaptive colour transform is not applied to the residual. + pub residual_adaptive_colour_transform_enabled_flag: bool, + /// When set, specifies that slice_act_y_qp_offset, slice_act_cb_qp_offset, + /// slice_act_cr_qp_offset are present in the slice header. When not set, + /// specifies that slice_act_y_qp_offset, slice_act_cb_qp_offset, + /// slice_act_cr_qp_offset are not present in the slice header. + pub slice_act_qp_offsets_present_flag: bool, + /// See the specificartion for more details. + pub act_y_qp_offset_plus5: i8, + /// See the specificartion for more details. + pub act_cb_qp_offset_plus5: i8, + /// See the specificartion for more details. + pub act_cr_qp_offset_plus3: i8, + /// When set, specifies that the palette predictor initializers used for the + /// pictures referring to the PPS are derived based on the palette predictor + /// initializers specified by the PPS. If not set, specifies that the + /// palette predictor initializers used for the pictures referring to the + /// PPS are inferred to be equal to those specified by the active SPS. + pub palette_predictor_initializers_present_flag: bool, + /// Specifies the number of entries in the picture palette predictor + /// initializer. + pub num_palette_predictor_initializers: u8, + /// When set, specifies that the pictures that refer to this PPS are + /// monochrome. If not set, specifies that the pictures that refer to this + /// PPS have multiple components. + pub monochrome_palette_flag: bool, + /// luma_bit_depth_entry_minus8 plus 8 specifies the bit depth of the luma + /// component of the entries of the palette predictor initializer. + pub luma_bit_depth_entry_minus8: u8, + /// chroma_bit_depth_entry_minus8 plus 8 specifies the bit depth of the + /// chroma components of the entries of the palette predictor initializer. + pub chroma_bit_depth_entry_minus8: u8, + /// `pps_palette_predictor_initializer[ comp ][ i ]` specifies the value of + /// the comp-th component of the i-th palette entry in the PPS that is used + /// to initialize the array PredictorPaletteEntries. + pub palette_predictor_initializer: [[u8; 128]; 3], +} + +impl Default for PpsSccExtension { + fn default() -> Self { + Self { + curr_pic_ref_enabled_flag: Default::default(), + residual_adaptive_colour_transform_enabled_flag: Default::default(), + slice_act_qp_offsets_present_flag: Default::default(), + act_y_qp_offset_plus5: Default::default(), + act_cb_qp_offset_plus5: Default::default(), + act_cr_qp_offset_plus3: Default::default(), + palette_predictor_initializers_present_flag: Default::default(), + num_palette_predictor_initializers: Default::default(), + monochrome_palette_flag: Default::default(), + luma_bit_depth_entry_minus8: Default::default(), + chroma_bit_depth_entry_minus8: Default::default(), + palette_predictor_initializer: [[0; 128]; 3], + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PpsRangeExtension { + /// log2_max_transform_skip_block_size_minus2 plus 2 specifies the maximum + /// transform block size for which transform_skip_flag may be present in + /// coded pictures referring to the PPS. When not present, the value of + /// log2_max_transform_skip_block_size_minus2 is inferred to be equal to 0. + /// When present, the value of log2_max_transform_skip_block_size_minus2 + /// shall be less than or equal to MaxTbLog2SizeY − 2. + pub log2_max_transform_skip_block_size_minus2: u32, + /// When set, specifies that log2_res_scale_abs_plus1 and + /// res_scale_sign_flag may be present in the transform unit syntax for + /// pictures referring to the PPS. When not set, specifies that + /// log2_res_scale_abs_plus1 and res_scale_sign_flag are not present for + /// pictures referring to the PPS. + pub cross_component_prediction_enabled_flag: bool, + /// When set, specifies that the cu_chroma_qp_offset_flag may be present in + /// the transform unit syntax. When not set, specifies that the + /// cu_chroma_qp_offset_flag is not present in the transform unit syntax. + pub chroma_qp_offset_list_enabled_flag: bool, + /// Specifies the difference between the luma CTB size and the minimum luma + /// coding block size of coding units that convey cu_chroma_qp_offset_flag. + pub diff_cu_chroma_qp_offset_depth: u32, + /// chroma_qp_offset_list_len_minus1 plus 1 specifies the number of + /// `cb_qp_offset_list[ i ]` and `cr_qp_offset_list[ i ]` syntax elements that + /// are present in the PPS. + pub chroma_qp_offset_list_len_minus1: u32, + /// Specify offsets used in the derivation of Qp′Cb and Qp′Cr, respectively. + pub cb_qp_offset_list: [i32; 6], + /// Specify offsets used in the derivation of Qp′Cb and Qp′Cr, respectively. + pub cr_qp_offset_list: [i32; 6], + /// The base 2 logarithm of the scaling parameter that is used to scale + /// sample adaptive offset (SAO) offset values for luma samples. + pub log2_sao_offset_scale_luma: u32, + /// The base 2 logarithm of the scaling parameter that is used to scale SAO + /// offset values for chroma samples. + pub log2_sao_offset_scale_chroma: u32, +} + +/// A H.265 Picture Parameter Set. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Pps { + /// Identifies the PPS for reference by other syntax elements. + pub pic_parameter_set_id: u8, + /// Specifies the value of sps_seq_parameter_set_id for the active SPS. + pub seq_parameter_set_id: u8, + /// When set, specifies the presence of the syntax element + /// dependent_slice_segment_flag in the slice segment headers for coded + /// pictures referring to the PPS. When not set, specifies the absence of + /// the syntax element dependent_slice_segment_flag in the slice segment + /// headers for coded pictures referring to the PPS. + pub dependent_slice_segments_enabled_flag: bool, + /// When set, indicates that the pic_output_flag syntax element is present + /// in the associated slice headers. When not set, indicates that the + /// pic_output_flag syntax element is not present in the associated slice + /// headers. + pub output_flag_present_flag: bool, + /// Specifies the number of extra slice header bits that are present in the + /// slice header RBSP for coded pictures referring to the PPS. + pub num_extra_slice_header_bits: u8, + /// When not set, specifies that sign bit hiding is disabled. Whens set, + /// specifies that sign bit hiding is enabled. + pub sign_data_hiding_enabled_flag: bool, + /// When set, specifies that cabac_init_flag is present in slice headers + /// referring to the PPS. When not set, specifies that cabac_init_flag is + /// not present in slice headers referring to the PPS. + pub cabac_init_present_flag: bool, + /// Specifies the inferred value of num_ref_idx_l0_active_minus1 for P and B + /// slices with num_ref_idx_active_override_flag not set. + pub num_ref_idx_l0_default_active_minus1: u8, + /// Specifies the inferred value of num_ref_idx_l1_active_minus1 for B + /// slices with num_ref_idx_active_override_flag not set. + pub num_ref_idx_l1_default_active_minus1: u8, + /// init_qp_minus26 plus 26 specifies the initial value of SliceQpY for each + /// slice referring to the PPS. The initial value of SliceQpY is modified at + /// the slice segment layer when a non-zero value of slice_qp_delta is + /// decoded. + pub init_qp_minus26: i8, + /// When not set, specifies that intra prediction allows usage of residual + /// data and decoded samples of neighbouring coding blocks coded using + /// either intra or inter prediction modes. When set, specifies constrained + /// intra prediction, in which case intra prediction only uses residual data + /// and decoded samples from neighbouring coding blocks coded using intra + /// prediction modes. + pub constrained_intra_pred_flag: bool, + /// When set, specifies that transform_skip_flag may be present in the + /// residual coding syntax. When not set, specifies that transform_skip_flag + /// is not present in the residual coding syntax. + pub transform_skip_enabled_flag: bool, + /// When set, specifies that the diff_cu_qp_delta_depth syntax element is + /// present in the PPS and that cu_qp_delta_abs may be present in the + /// transform unit syntax and the palette syntax. When not set, specifies + /// that the diff_cu_qp_delta_depth syntax element is not present in the PPS + /// and that cu_qp_delta_abs is not present in the transform unit syntax and + /// the palette syntax. + pub cu_qp_delta_enabled_flag: bool, + + /*if cu_qp_delta_enabled_flag */ + /// Specifies the difference between the luma CTB size and the minimum luma + /// coding block size of coding units that convey cu_qp_delta_abs and + /// cu_qp_delta_sign_flag. + pub diff_cu_qp_delta_depth: u8, + /// Specifies the offsets to the luma quantization parameter Qp′Y used for + /// deriving Qp′Cb and Qp′Cr, respectively. + pub cb_qp_offset: i8, + /// Specifies the offsets to the luma quantization parameter Qp′Y used for + /// deriving Qp′Cb and Qp′Cr, respectively. + pub cr_qp_offset: i8, + /// When set, indicates that the slice_cb_qp_offset and slice_cr_qp_offset + /// syntax elements are present in the associated slice headers. When not + /// set, indicates that these syntax elements are not present in the + /// associated slice headers. When ChromaArrayType is equal to 0, + /// pps_slice_chroma_qp_offsets_present_flag shall be equal to 0 + pub slice_chroma_qp_offsets_present_flag: bool, + /// When not set, specifies that weighted prediction is not applied to P + /// slices. When set, specifies that weighted prediction is applied to P + /// slices. + pub weighted_pred_flag: bool, + /// When not set, specifies that the default weighted prediction is applied + /// to B slices. When set, specifies that weighted prediction is applied to + /// B slices. + pub weighted_bipred_flag: bool, + /// When set, specifies that `cu_transquant_bypass_flag` is present, When + /// not set, specifies that `cu_transquant_bypass_flag` is not present. + pub transquant_bypass_enabled_flag: bool, + /// When set, specifies that there is more than one tile in each picture + /// referring to the PPS. When not set, specifies that there is only one + /// tile in each picture referring to the PPS. + pub tiles_enabled_flag: bool, + /// When set, specifies that a specific synchronization process for context + /// variables, and when applicable, Rice parameter initialization states and + /// palette predictor variables, is invoked before decoding the CTU which + /// includes the first CTB of a row of CTBs in each tile in each picture + /// referring to the PPS, and a specific storage process for context + /// variables, and when applicable, Rice parameter initialization states and + /// palette predictor variables, is invoked after decoding the CTU which + /// includes the second CTB of a row of CTBs in each tile in each picture + /// referring to the PPS. When not set, specifies that no specific + /// synchronization process for context variables, and when applicable, Rice + /// parameter initialization states and palette predictor variables, is + /// required to be invoked before decoding the CTU which includes the first + /// CTB of a row of CTBs in each tile in each picture referring to the PPS, + /// and no specific storage process for context variables, and when + /// applicable, Rice parameter initialization states and palette predictor + /// variables, is required to be invoked after decoding the CTU which + /// includes the second CTB of a row of CTBs in each tile in each picture + /// referring to the PPS. + pub entropy_coding_sync_enabled_flag: bool, + /// num_tile_columns_minus1 plus 1 specifies the number of tile columns + /// partitioning the picture. + pub num_tile_columns_minus1: u8, + /// num_tile_rows_minus1 plus 1 specifies the number of tile rows + /// partitioning the picture. + pub num_tile_rows_minus1: u8, + /// When set, specifies that tile column boundaries and likewise tile row + /// boundaries are distributed uniformly across the picture. When not set, + /// specifies that tile column boundaries and likewise tile row boundaries + /// are not distributed uniformly across the picture but signalled + /// explicitly using the syntax elements `column_width_minus1[ i ]` and + /// `row_height_minus1[ i ]`. + pub uniform_spacing_flag: bool, + /// `column_width_minus1[ i ]` plus 1 specifies the width of the i-th tile + /// column in units of CTBs. + pub column_width_minus1: [u32; MAX_TILE_COLUMNS], + /// `row_height_minus1[ i ]` plus 1 specifies the height of the i-th tile row + /// in units of CTBs. + pub row_height_minus1: [u32; MAX_TILE_ROWS], + /// When set, specifies that in-loop filtering operations may be performed + /// across tile boundaries in pictures referring to the PPS. When not set, + /// specifies that in-loop filtering operations are not performed across + /// tile boundaries in pictures referring to the PPS. The in-loop filtering + /// operations include the deblocking filter and sample adaptive offset + /// filter operations. + pub loop_filter_across_tiles_enabled_flag: bool, + /// When set, specifies that in-loop filtering operations may be performed + /// across left and upper boundaries of slices referring to the PPS. When + /// not set, specifies that in-loop filtering operations are not performed + /// across left and upper boundaries of slices referring to the PPS. The in- + /// loop filtering operations include the deblocking filter and sample + /// adaptive offset filter operations. + pub loop_filter_across_slices_enabled_flag: bool, + /// When set, specifies the presence of deblocking filter control syntax + /// elements in the PPS. When not set, specifies the absence of deblocking + /// filter control syntax elements in the PPS. + pub deblocking_filter_control_present_flag: bool, + /// When set, specifies the presence of deblocking_filter_override_flag in + /// the slice headers for pictures referring to the PPS. When not set, + /// specifies the absence of deblocking_filter_override_flag in the slice + /// headers for pictures referring to the PPS. + pub deblocking_filter_override_enabled_flag: bool, + /// When set, specifies that the operation of deblocking filter is not + /// applied for slices referring to the PPS in which + /// slice_deblocking_filter_disabled_flag is not present. When not set, + /// specifies that the operation of the deblocking filter is applied for + /// slices referring to the PPS in which + /// slice_deblocking_filter_disabled_flag is not present. + pub deblocking_filter_disabled_flag: bool, + /// Specify the default deblocking parameter offsets for β and tC (divided + /// by 2) that are applied for slices referring to the PPS, unless the + /// default deblocking parameter offsets are overridden by the deblocking + /// parameter offsets present in the slice headers of the slices referring + /// to the PPS. + pub beta_offset_div2: i8, + /// Specify the default deblocking parameter offsets for β and tC (divided + /// by 2) that are applied for slices referring to the PPS, unless the + /// default deblocking parameter offsets are overridden by the deblocking + /// parameter offsets present in the slice headers of the slices referring + /// to the PPS. + pub tc_offset_div2: i8, + /// When set, specifies that the scaling list data used for the pictures + /// referring to the PPS are derived based on the scaling lists specified by + /// the active SPS and the scaling lists specified by the PPS. + /// pps_scaling_list_data_present_flag equal to 0 specifies that the scaling + /// list data used for the pictures referring to the PPS are inferred to be + /// equal to those specified by the active SPS. + pub scaling_list_data_present_flag: bool, + /// The scaling list data. + pub scaling_list: ScalingLists, + /// When set, specifies that the syntax structure + /// ref_pic_lists_modification( ) is present in the slice segment header. + /// When not set, specifies that the syntax structure + /// ref_pic_lists_modification( ) is not present in the slice segment header + pub lists_modification_present_flag: bool, + /// log2_parallel_merge_level_minus2 plus 2 specifies the value of the + /// variable Log2ParMrgLevel, which is used in the derivation process for + /// luma motion vectors for merge mode as specified in clause 8.5.3.2.2 and + /// the derivation process for spatial merging candidates as specified in + /// clause 8.5.3.2.3. + pub log2_parallel_merge_level_minus2: u8, + /// When not set, specifies that no slice segment header extension syntax + /// elements are present in the slice segment headers for coded pictures + /// referring to the PPS. When set, specifies that slice segment header + /// extension syntax elements are present in the slice segment headers for + /// coded pictures referring to the PPS. + pub slice_segment_header_extension_present_flag: bool, + /// When set, specifies that the syntax elements pps_range_extension_flag, + /// pps_multilayer_extension_flag, pps_3d_extension_flag, + /// pps_scc_extension_flag, and pps_extension_4bits are present in the + /// picture parameter set RBSP syntax structure. When not set, specifies + /// that these syntax elements are not present. + pub extension_present_flag: bool, + /// When setspecifies that the pps_range_extension( ) syntax structure is + /// present in the PPS RBSP syntax structure. When not set, specifies that + /// this syntax structure is not present. + pub range_extension_flag: bool, + /// The range extension data. + pub range_extension: PpsRangeExtension, + + pub scc_extension_flag: bool, + /// The SCC extension data. + pub scc_extension: PpsSccExtension, + + // Internal variables. + /// Equivalent to QpBdOffsetY in the specification. + pub qp_bd_offset_y: u32, + + /// The SPS referenced by this PPS. + pub sps: Rc, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ScalingLists { + /// plus 8 specifies the value of the variable `ScalingFactor[ 2 ][ matrixId + /// ] [ 0 ][ 0 ]` for the scaling list for the 16x16 size. + pub scaling_list_dc_coef_minus8_16x16: [i16; 6], + /// plus 8 specifies the value of the variable `ScalingFactor[ 3 ][ matrixId + /// ][ 0 ][ 0 ]` for the scaling list for the 32x32 size. + pub scaling_list_dc_coef_minus8_32x32: [i16; 6], + /// The 4x4 scaling list. + pub scaling_list_4x4: [[u8; 16]; 6], + /// The 8x8 scaling list. + pub scaling_list_8x8: [[u8; 64]; 6], + /// The 16x16 scaling list. + pub scaling_list_16x16: [[u8; 64]; 6], + /// The 32x32 scaling list. + pub scaling_list_32x32: [[u8; 64]; 6], +} + +impl Default for ScalingLists { + fn default() -> Self { + Self { + scaling_list_dc_coef_minus8_16x16: Default::default(), + scaling_list_dc_coef_minus8_32x32: Default::default(), + scaling_list_4x4: Default::default(), + scaling_list_8x8: [[0; 64]; 6], + scaling_list_16x16: [[0; 64]; 6], + scaling_list_32x32: [[0; 64]; 6], + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RefPicListModification { + /// Whenset, indicates that reference picture list 0 is specified explicitly + /// by a list of `list_entry_l0[ i ]` values. When not set, indicates that + /// reference picture list 0 is determined implicitly. + pub ref_pic_list_modification_flag_l0: bool, + /// `list_entry_l0[ i ]` specifies the index of the reference picture in + /// RefPicListTemp0 to be placed at the current position of reference + /// picture list 0. + pub list_entry_l0: Vec, + /// Whenset, indicates that reference picture list 1 is specified explicitly + /// by a list of `list_entry_l1[ i ]` values. When not set, indicates that + /// reference picture list 1 is determined implicitly. + pub ref_pic_list_modification_flag_l1: bool, + /// `list_entry_l1[ i ]` specifies the index of the reference picture in + /// RefPicListTemp1 to be placed at the current position of reference + /// picture list 1. + pub list_entry_l1: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PredWeightTable { + /// The base 2 logarithm of the denominator for all luma weighting factors. + pub luma_log2_weight_denom: u8, + /// The difference of the base 2 logarithm of the denominator for all chroma + /// weighting factors. + pub delta_chroma_log2_weight_denom: i8, + /// `luma_weight_l0_flag[ i ]` set specifies that weighting factors for the + /// luma component of list 0 prediction using `RefPicList0[ i ]` are present. + /// `luma_weight_l0_flag[ i ]` not set specifies that these weighting factors + /// are not present. + pub luma_weight_l0_flag: [bool; 15], + /// `chroma_weight_l0_flag[ i ]` set specifies that weighting factors for the + /// chroma prediction values of list 0 prediction using `RefPicList0[ i ]` are + /// present. `chroma_weight_l0_flag[ i ]` not set specifies that these + /// weighting factors are not present. + pub chroma_weight_l0_flag: [bool; 15], + /// `delta_luma_weight_l0[ i ]` is the difference of the weighting factor + /// applied to the luma prediction value for list 0 prediction using + /// `RefPicList0[ i ]`. + pub delta_luma_weight_l0: [i8; 15], + /// `luma_offset_l0[ i ]` is the additive offset applied to the luma + /// prediction value for list 0 prediction using `RefPicList0[ i ]`. + pub luma_offset_l0: [i8; 15], + /// `delta_chroma_weight_l0[ i ][ j ]` is the difference of the weighting + /// factor applied to the chroma prediction values for list 0 prediction + /// using `RefPicList0[ i ]` with j equal to 0 for Cb and j equal to 1 for Cr. + pub delta_chroma_weight_l0: [[i8; 2]; 15], + /// `delta_chroma_offset_l0[ i ][ j ]` is the difference of the additive + /// offset applied to the chroma prediction values for list 0 prediction + /// using `RefPicList0[ i ]` with j equal to 0 for Cb and j equal to 1 for Cr. + pub delta_chroma_offset_l0: [[i16; 2]; 15], + + // `luma_weight_l1_flag[ i ]`, `chroma_weight_l1_flag[ i ]`, + // `delta_luma_weight_l1[ i ]`, `luma_offset_l1[ i ]`, delta_chroma_weight_l1[ i + // `][ j ]` and `delta_chroma_offset_l1[ i ]`[ j ] have the same + // `semanticsasluma_weight_l0_flag[ i ]`, `chroma_weight_l0_flag[ i ]`, + // `delta_luma_weight_l0[ i ]`, `luma_offset_l0[ i ]`, `delta_chroma_weight_l0[ i + // ][ j ]` and `delta_chroma_offset_l0[ i ][ j ]`, respectively, with `l0`, `L0`, + // `list 0` and `List0` replaced by `l1`, `L1`, `list 1` and `List1`, respectively. + pub luma_weight_l1_flag: [bool; 15], + pub chroma_weight_l1_flag: [bool; 15], + pub delta_luma_weight_l1: [i8; 15], + pub luma_offset_l1: [i8; 15], + + pub delta_chroma_weight_l1: [[i8; 2]; 15], + pub delta_chroma_offset_l1: [[i16; 2]; 15], + + // Calculated. + /// Same as ChromaLog2WeightDenom in the specification. + pub chroma_log2_weight_denom: u8, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ShortTermRefPicSet { + /// When set, specifies that the stRpsIdx-th candidate short-term RPS is + /// predicted from another candidate short-term RPS, which is referred to as + /// the source candidate short-term RPS. + pub inter_ref_pic_set_prediction_flag: bool, + /// delta_idx_minus1 plus 1 specifies the difference between the value of + /// stRpsIdx and the index, into the list of the candidate short-term RPSs + /// specified in the SPS, of the source candidate short-term RPS. + pub delta_idx_minus1: u8, + /// delta_rps_sign and abs_delta_rps_minus1 together specify the value of + /// the variable deltaRps. + pub delta_rps_sign: bool, + /// delta_rps_sign and abs_delta_rps_minus1 together specify the value of + /// the variable deltaRps. + pub abs_delta_rps_minus1: u16, + /// specifies the number of entries in the stRpsIdx-th candidate short-term + /// RPS that have picture order count values less than the picture order + /// count value of the current picture. + pub num_negative_pics: u8, + /// specifies the number of entries in the stRpsIdx-th candidate short-term + /// RPS that have picture order count values greater than the picture order + /// count value of the current picture. + pub num_positive_pics: u8, + /// Same as UsedByCurrPicS0 in the specification. + pub used_by_curr_pic_s0: [bool; MAX_SHORT_TERM_REF_PIC_SETS], + /// Same as UsedByCurrPicS1 in the specification. + pub used_by_curr_pic_s1: [bool; MAX_SHORT_TERM_REF_PIC_SETS], + /// Same as DeltaPocS0 in the specification. + pub delta_poc_s0: [i32; MAX_SHORT_TERM_REF_PIC_SETS], + /// Same as DeltaPocS1 in the specification. + pub delta_poc_s1: [i32; MAX_SHORT_TERM_REF_PIC_SETS], + /// Same as NumDeltaPocs in the specification. + pub num_delta_pocs: u32, +} + +impl Default for ShortTermRefPicSet { + fn default() -> Self { + Self { + inter_ref_pic_set_prediction_flag: Default::default(), + delta_idx_minus1: Default::default(), + delta_rps_sign: Default::default(), + abs_delta_rps_minus1: Default::default(), + num_negative_pics: Default::default(), + num_positive_pics: Default::default(), + used_by_curr_pic_s0: [false; MAX_SHORT_TERM_REF_PIC_SETS], + used_by_curr_pic_s1: [false; MAX_SHORT_TERM_REF_PIC_SETS], + delta_poc_s0: [0; MAX_SHORT_TERM_REF_PIC_SETS], + delta_poc_s1: [0; MAX_SHORT_TERM_REF_PIC_SETS], + num_delta_pocs: Default::default(), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// See table 7-7 in the specification. +pub enum SliceType { + B = 0, + P = 1, + I = 2, +} + +impl TryFrom for SliceType { + type Error = String; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(SliceType::B), + 1 => Ok(SliceType::P), + 2 => Ok(SliceType::I), + _ => Err(format!("Invalid SliceType {}", value)), + } + } +} + +impl SliceType { + /// Whether this is a P slice. See table 7-7 in the specification. + pub fn is_p(&self) -> bool { + matches!(self, SliceType::P) + } + + /// Whether this is a B slice. See table 7-7 in the specification. + pub fn is_b(&self) -> bool { + matches!(self, SliceType::B) + } + + /// Whether this is an I slice. See table 7-7 in the specification. + pub fn is_i(&self) -> bool { + matches!(self, SliceType::I) + } +} + +impl Default for SliceType { + fn default() -> Self { + Self::P + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SliceHeader { + /// When set, specifies that the slice segment is the first slice segment of + /// the picture in decoding order. When not set, specifies that the slice + /// segment is not the first slice segment of the picture in decoding order. + pub first_slice_segment_in_pic_flag: bool, + /// Affects the output of previously-decoded pictures in the decoded picture + /// buffer after the decoding of an IDR or a BLA picture that is not the + /// first picture in the bitstream as specified in Annex C. + pub no_output_of_prior_pics_flag: bool, + /// Specifies the value of pps_pic_parameter_set_id for the PPS in use. + pub pic_parameter_set_id: u8, + /// When set, specifies that the value of each slice segment header syntax + /// element that is not present is inferred to be equal to the value of the + /// corresponding slice segment header syntax element in the slice header. + pub dependent_slice_segment_flag: bool, + /// Specifies the address of the first CTB in the slice segment, in CTB + /// raster scan of a picture. + pub segment_address: u32, + /// Specifies the coding type of the slice according to Table 7-7. + pub type_: SliceType, + /// Affects the decoded picture output and removal processes as specified in + /// Annex C. + pub pic_output_flag: bool, + /// Specifies the colour plane associated with the current slice RBSP when + /// separate_colour_plane_flag is set. The value of colour_plane_id shall be + /// in the range of 0 to 2, inclusive. colour_plane_id values 0, 1 and 2 + /// correspond to the Y, Cb and Cr planes, respectively. + pub colour_plane_id: u8, + /// Specifies the picture order count modulo MaxPicOrderCntLsb for the + /// current picture. The length of the slice_pic_order_cnt_lsb syntax + /// element is log2_max_pic_order_cnt_lsb_minus4 + 4 bits. + pub pic_order_cnt_lsb: u16, + /// When set, specifies that the short-term RPS of the current picture is + /// derived based on one of the st_ref_pic_set( ) syntax structures in the + /// active SPS that is identified by the syntax element + /// short_term_ref_pic_set_idx in the slice header. When not set, specifies + /// that the short-term RPS of the current picture is derived based on the + /// st_ref_pic_set( ) syntax structure that is directly included in the + /// slice headers of the current picture. + pub short_term_ref_pic_set_sps_flag: bool, + /// The st_ref_pic_set() data. + pub short_term_ref_pic_set: ShortTermRefPicSet, + /// Specifies the index, into the list of the st_ref_pic_set( ) syntax + /// structures included in the active SPS, of the st_ref_pic_set( ) syntax + /// structure that is used for derivation of the short-term RPS of the + /// current picture. + pub short_term_ref_pic_set_idx: u8, + /// Specifies the number of entries in the long-term RPS of the current + /// picture that are derived based on the candidate long-term reference + /// pictures specified in the active SPS. + pub num_long_term_sps: u8, + /// Specifies the number of entries in the long-term RPS of the current + /// picture that are directly signalled in the slice header. + pub num_long_term_pics: u8, + /// `lt_idx_sps[ i ]` specifies an index, into the list of candidate long-term + /// reference pictures specified in the active SPS, of the i-th entry in the + /// long-term RPS of the current picture. + pub lt_idx_sps: [u8; 16], + /// Same as PocLsbLt in the specification. + pub poc_lsb_lt: [u32; 16], + /// Same as UsedByCurrPicLt in the specification. + pub used_by_curr_pic_lt: [bool; 16], + /// When set, specifies that that `delta_poc_msb_cycle_lt[i]` is present. + pub delta_poc_msb_present_flag: [bool; 16], + /// Same as DeltaPocMsbCycleLt in the specification. + pub delta_poc_msb_cycle_lt: [u32; 16], + /// Specifies whether temporal motion vector predictors can be used for + /// inter prediction. If slice_temporal_mvp_enabled_flag is not set, the + /// syntax elements of the current picture shall be constrained such that no + /// temporal motion vector predictor is used in decoding of the current + /// picture. Otherwise (slice_temporal_mvp_enabled_flag is set), temporal + /// motion vector predictors may be used in decoding of the current picture. + pub temporal_mvp_enabled_flag: bool, + /// When set, specifies that SAO is enabled for the luma component in the + /// current slice; slice_sao_luma_flag not set specifies that SAO is + /// disabled for the luma component in the current slice. + pub sao_luma_flag: bool, + /// When set, specifies that SAO is enabled for the chroma component in the + /// current slice; When not set, specifies that SAO is disabled for the + /// chroma component in the current slice. + pub sao_chroma_flag: bool, + /// When set, specifies that the syntax element num_ref_idx_l0_active_minus1 + /// is present for P and B slices and that the syntax element + /// num_ref_idx_l1_active_minus1 is present for B slices. When not set, + /// specifies that the syntax elements num_ref_idx_l0_active_minus1 and + /// num_ref_idx_l1_active_minus1 are not present. + pub num_ref_idx_active_override_flag: bool, + /// Specifies the maximum reference index for + /// reference picture list 0 that may be used to decode the slice. + pub num_ref_idx_l0_active_minus1: u8, + /// Specifies the maximum reference index for reference picture list 1 that + /// may be used to decode the slice. + pub num_ref_idx_l1_active_minus1: u8, + /// The RefPicListModification data. + pub ref_pic_list_modification: RefPicListModification, + /// When set, indicates that the mvd_coding( x0, y0, 1 ) syntax structure is + /// not parsed and `MvdL1[ x0 ]`[ y0 `][ compIdx ]` is set equal to 0 for + /// compIdx = 0..1. When not set, indicates that the mvd_coding( x0, y0, 1 ) + /// syntax structure is parsed. + pub mvd_l1_zero_flag: bool, + /// Specifies the method for determining the initialization table used in + /// the initialization process for context variables. + pub cabac_init_flag: bool, + /// When set, specifies that the collocated picture used for temporal motion + /// vector prediction is derived from reference picture list 0. When not + /// set, specifies that the collocated picture used for temporal motion + /// vector prediction is derived from reference picture list 1. + pub collocated_from_l0_flag: bool, + /// Specifies the reference index of the collocated picture used for + /// temporal motion vector prediction. + pub collocated_ref_idx: u8, + /// The PredWeightTable data. + pub pred_weight_table: PredWeightTable, + /// Specifies the maximum number of merging motion vector prediction (MVP) + /// candidates supported in the slice subtracted from 5. + pub five_minus_max_num_merge_cand: u8, + /// Specifies that the resolution of motion vectors for inter prediction in + /// the current slice is integer. When not set, specifies + /// that the resolution of motion vectors for inter prediction in the + /// current slice that refer to pictures other than the current picture is + /// fractional with quarter-sample precision in units of luma samples. + pub use_integer_mv_flag: bool, + /// Specifies the initial value of QpY to be used for the coding blocks in + /// the slice until modified by the value of CuQpDeltaVal in the coding unit + /// layer. + pub qp_delta: i8, + /// Specifies a difference to be added to the value of pps_cb_qp_offset when + /// determining the value of the Qp′Cb quantization parameter. + pub cb_qp_offset: i8, + /// Specifies a difference to be added to the value of pps_cb_qr_offset when + /// determining the value of the Qp′Cr quantization parameter. + pub cr_qp_offset: i8, + /// Specifies offsets to the quantization parameter values qP derived in + /// clause 8.6.2 for luma, Cb, and Cr components, respectively. + pub slice_act_y_qp_offset: i8, + /// Specifies offsets to the quantization parameter values qP derived in + /// clause 8.6.2 for luma, Cb, and Cr components, respectively. + pub slice_act_cb_qp_offset: i8, + /// Specifies offsets to the quantization parameter values qP derived in + /// clause 8.6.2 for luma, Cb, and Cr components, respectively. + pub slice_act_cr_qp_offset: i8, + /// When set, specifies that the cu_chroma_qp_offset_flag may be present in + /// the transform unit syntax. When not set, specifies that the + /// cu_chroma_qp_offset_flag is not present in the transform unit syntax. + pub cu_chroma_qp_offset_enabled_flag: bool, + /// When set, specifies that deblocking parameters are present in the slice + /// header. When not set, specifies that deblocking parameters are not + /// present in the slice header. + pub deblocking_filter_override_flag: bool, + /// When set, specifies that the operation of the deblocking filter is not + /// applied for the current slice. When not set, specifies that the + /// operation of the deblocking filter is applied for the current slice. + pub deblocking_filter_disabled_flag: bool, + /// Specifies the deblocking parameter offsets for β and tC (divided by 2) + /// for the current slice. + pub beta_offset_div2: i8, + /// Specifies the deblocking parameter offsets for β and tC (divided by 2) + /// for the current slice. + pub tc_offset_div2: i8, + /// When set, specifies that in-loop filtering operations may be performed + /// across the left and upper boundaries of the current slice. When not + /// set, specifies that in-loop operations are not performed across left and + /// upper boundaries of the current slice. The in-loop filtering operations + /// include the deblocking filter and sample adaptive offset filter. + pub loop_filter_across_slices_enabled_flag: bool, + /// Specifies the number of `entry_point_offset_minus1[ i ]` syntax elements + /// in the slice header. + pub num_entry_point_offsets: u32, + /// offset_len_minus1 plus 1 specifies the length, in bits, of the + /// `entry_point_offset_minus1[ i ]` syntax elements. + pub offset_len_minus1: u8, + /// `entry_point_offset_minus1[ i ]` plus 1 specifies the i-th entry point + /// offset in bytes, and is represented by offset_len_minus1 plus 1 bits. + /// The slice segment data that follow the slice segment header consists of + /// num_entry_point_offsets + 1 subsets, with subset index values ranging + /// from 0 to num_entry_point_offsets, inclusive. See the specification for + /// more details. + pub entry_point_offset_minus1: Vec, + /// Same as NumPicTotalCurr in the specification. + pub num_pic_total_curr: u32, + // Size of slice_header() in bits. + pub header_bit_size: u32, + // Number of emulation prevention bytes (EPB) in this slice_header(). + pub n_emulation_prevention_bytes: u32, + /// Same as CurrRpsIdx in the specification. + pub curr_rps_idx: u8, + /// Number of bits taken by st_ref_pic_set minus Emulation Prevention Bytes. + pub st_rps_bits: u32, +} + +impl Default for SliceHeader { + fn default() -> Self { + Self { + first_slice_segment_in_pic_flag: Default::default(), + no_output_of_prior_pics_flag: Default::default(), + pic_parameter_set_id: Default::default(), + dependent_slice_segment_flag: Default::default(), + segment_address: Default::default(), + type_: Default::default(), + pic_output_flag: true, + colour_plane_id: Default::default(), + pic_order_cnt_lsb: Default::default(), + short_term_ref_pic_set_sps_flag: Default::default(), + short_term_ref_pic_set: Default::default(), + short_term_ref_pic_set_idx: Default::default(), + num_long_term_sps: Default::default(), + num_long_term_pics: Default::default(), + lt_idx_sps: Default::default(), + poc_lsb_lt: Default::default(), + used_by_curr_pic_lt: Default::default(), + delta_poc_msb_present_flag: Default::default(), + delta_poc_msb_cycle_lt: Default::default(), + temporal_mvp_enabled_flag: Default::default(), + sao_luma_flag: Default::default(), + sao_chroma_flag: Default::default(), + num_ref_idx_active_override_flag: Default::default(), + num_ref_idx_l0_active_minus1: Default::default(), + num_ref_idx_l1_active_minus1: Default::default(), + ref_pic_list_modification: Default::default(), + mvd_l1_zero_flag: Default::default(), + cabac_init_flag: Default::default(), + collocated_from_l0_flag: true, + collocated_ref_idx: Default::default(), + pred_weight_table: Default::default(), + five_minus_max_num_merge_cand: Default::default(), + use_integer_mv_flag: Default::default(), + qp_delta: Default::default(), + cb_qp_offset: Default::default(), + cr_qp_offset: Default::default(), + slice_act_y_qp_offset: Default::default(), + slice_act_cb_qp_offset: Default::default(), + slice_act_cr_qp_offset: Default::default(), + cu_chroma_qp_offset_enabled_flag: Default::default(), + deblocking_filter_override_flag: Default::default(), + deblocking_filter_disabled_flag: Default::default(), + beta_offset_div2: Default::default(), + tc_offset_div2: Default::default(), + loop_filter_across_slices_enabled_flag: Default::default(), + num_entry_point_offsets: Default::default(), + offset_len_minus1: Default::default(), + entry_point_offset_minus1: Default::default(), + num_pic_total_curr: Default::default(), + header_bit_size: Default::default(), + n_emulation_prevention_bytes: Default::default(), + curr_rps_idx: Default::default(), + st_rps_bits: Default::default(), + } + } +} + +/// A H265 slice. An integer number of macroblocks or macroblock pairs ordered +/// consecutively in the raster scan within a particular slice group +pub struct Slice<'a> { + /// The slice header. + pub header: SliceHeader, + /// The NAL unit backing this slice. + pub nalu: Nalu<'a>, +} + +impl<'a> Slice<'a> { + /// Sets the header for dependent slices by copying from an independent + /// slice. + pub fn replace_header(&mut self, header: SliceHeader) -> Result<(), String> { + if !self.header.dependent_slice_segment_flag { + Err("Replacing the slice header is only possible for dependent slices".into()) + } else { + let first_slice_segment_in_pic_flag = self.header.first_slice_segment_in_pic_flag; + let no_output_of_prior_pics_flag = self.header.no_output_of_prior_pics_flag; + let pic_parameter_set_id = self.header.pic_parameter_set_id; + let dependent_slice_segment_flag = self.header.dependent_slice_segment_flag; + let segment_address = self.header.segment_address; + + let offset_len_minus1 = self.header.offset_len_minus1; + let entry_point_offset_minus1 = std::mem::take(&mut self.header.entry_point_offset_minus1); + let num_pic_total_curr = self.header.num_pic_total_curr; + let header_bit_size = self.header.header_bit_size; + let n_emulation_prevention_bytes = self.header.n_emulation_prevention_bytes; + let curr_rps_idx = self.header.curr_rps_idx; + let st_rps_bits = self.header.st_rps_bits; + + self.header = header; + + self.header.first_slice_segment_in_pic_flag = first_slice_segment_in_pic_flag; + self.header.no_output_of_prior_pics_flag = no_output_of_prior_pics_flag; + self.header.pic_parameter_set_id = pic_parameter_set_id; + self.header.dependent_slice_segment_flag = dependent_slice_segment_flag; + self.header.segment_address = segment_address; + self.header.offset_len_minus1 = offset_len_minus1; + self.header.entry_point_offset_minus1 = entry_point_offset_minus1; + self.header.num_pic_total_curr = num_pic_total_curr; + self.header.header_bit_size = header_bit_size; + self.header.n_emulation_prevention_bytes = n_emulation_prevention_bytes; + self.header.curr_rps_idx = curr_rps_idx; + self.header.st_rps_bits = st_rps_bits; + + Ok(()) + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SublayerHrdParameters { + // NOTE: The value of CpbCnt is cpb_cnt_minus1[i] + 1, and cpb_cnt_minus1 + // ranges from 0..=31 + /// `bit_rate_value_minus1[ i ]` (together with bit_rate_scale) specifies the + /// maximum input bit rate for the i-th CPB when the CPB operates at the + /// access unit level + pub bit_rate_value_minus1: [u32; 32], + /// `cpb_size_value_minus1[ i ]` is used together with cpb_size_scale to + /// specify the i-th CPB size when the CPB operates at the access unit + /// level. + pub cpb_size_value_minus1: [u32; 32], + /// `cpb_size_du_value_minus1[ i ]` is used together with cpb_size_du_scale to + /// specify the i-th CPB size when the CPB operates at sub-picture level. + pub cpb_size_du_value_minus1: [u32; 32], + /// `bit_rate_du_value_minus1[ i ]` (together with bit_rate_scale) specifies + /// the maximum input bit rate for the i-th CPB when the CPB operates at the + /// sub-picture level. + pub bit_rate_du_value_minus1: [u32; 32], + /// `cbr_flag[ i ]` not set specifies that to decode this CVS by the HRD using + /// the i-th CPB specification. + pub cbr_flag: [bool; 32], +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HrdParams { + /// When set, specifies that NAL HRD parameters (pertaining to the Type II + /// bitstream conformance point) are present in the hrd_parameters( ) syntax + /// structure. When not set, specifies that NAL HRD parameters are not + /// present in the hrd_parameters( ) syntax structure. + pub nal_hrd_parameters_present_flag: bool, + /// When set, specifies that VCL HRD parameters (pertaining to the Type I + /// bitstream conformance point) are present in the hrd_parameters( ) syntax + /// structure. When not set, specifies that VCL HRD parameters are not + /// present in the hrd_parameters( ) syntax structure. + pub vcl_hrd_parameters_present_flag: bool, + /// When set, specifies that sub-picture level HRD parameters are present + /// and the HRD may operate at access unit level or sub-picture level. When + /// not set, specifies that sub-picture level HRD parameters are not present + /// and the HRD operates at access unit level. + pub sub_pic_hrd_params_present_flag: bool, + /// Used to specify the clock sub-tick. A clock sub-tick is the minimum + /// interval of time that can be represented in the coded data when + /// sub_pic_hrd_params_present_flag is set. + pub tick_divisor_minus2: u8, + /// du_cpb_removal_delay_increment_length_minus1 plus 1 specifies the + /// length, in bits, of the `du_cpb_removal_delay_increment_minus1[ i ]` and + /// du_common_cpb_removal_delay_increment_minus1 syntax elements of the + /// picture timing SEI message and the du_spt_cpb_removal_delay_increment + /// syntax element in the decoding unit information SEI message. + pub du_cpb_removal_delay_increment_length_minus1: u8, + /// When set, specifies that sub-picture level CPB removal delay parameters + /// are present in picture timing SEI messages and no decoding unit + /// information SEI message is available (in the CVS or provided through + /// external means not specified in this Specification). When not set, + /// specifies that sub-picture level CPB removal delay parameters are + /// present in decoding unit information SEI messages and picture timing SEI + /// messages do not include sub-picture level CPB removal delay parameters. + pub sub_pic_cpb_params_in_pic_timing_sei_flag: bool, + /// dpb_output_delay_du_length_minus1 plus 1 specifies the length, in bits, + /// of the pic_dpb_output_du_delay syntax element in the picture timing SEI + /// message and the pic_spt_dpb_output_du_delay syntax element in the + /// decoding unit information SEI message. + pub dpb_output_delay_du_length_minus1: u8, + /// Together with `bit_rate_value_minus1[ i ]`, specifies the maximum input + /// bit rate of the i-th CPB. + pub bit_rate_scale: u8, + /// Together with `cpb_size_du_value_minus1[ i ]`, specifies the CPB size of + /// the i-th CPB when the CPB operates at sub-picture level. + pub cpb_size_scale: u8, + /// Together with `cpb_size_du_value_minus1[ i ]`, specifies the CPB size of + /// the i-th CPB when the CPB operates at sub-picture level. + pub cpb_size_du_scale: u8, + /// initial_cpb_removal_delay_length_minus1 plus 1 specifies the length, in + /// bits, of the `nal_initial_cpb_removal_delay[ i ]`, + /// `nal_initial_cpb_removal_offset[ i ]`, `vcl_initial_cpb_removal_delay[ i ]` + /// and `vcl_initial_cpb_removal_offset[ i ]` syntax elements of the buffering + /// period SEI message. + pub initial_cpb_removal_delay_length_minus1: u8, + /// au_cpb_removal_delay_length_minus1 plus 1 specifies the length, in bits, + /// of the cpb_delay_offset syntax element in the buffering period SEI + /// message and the au_cpb_removal_delay_minus1 syntax element in the + /// picture timing SEI message. + pub au_cpb_removal_delay_length_minus1: u8, + /// dpb_output_delay_length_minus1 plus 1 specifies the length, in bits, of + /// the dpb_delay_offset syntax element in the buffering period SEI message + /// and the pic_dpb_output_delay syntax element in the picture timing SEI + /// message. + pub dpb_output_delay_length_minus1: u8, + /// `fixed_pic_rate_general_flag[ i ]` set indicates that, when HighestTid is + /// equal to i, the temporal distance between the HRD output times of + /// consecutive pictures in output order is constrained as specified in the + /// specification. `fixed_pic_rate_general_flag[ i ]` not set indicates that + /// this constraint may not apply. + pub fixed_pic_rate_general_flag: [bool; 7], + /// `fixed_pic_rate_within_cvs_flag[ i ]` set indicates that, when HighestTid + /// is equal to i, the temporal distance between the HRD output times of + /// consecutive pictures in output order is constrained as specified in the + /// specification. `fixed_pic_rate_within_cvs_flag[ i ]` not set indicates + /// that this constraint may not apply. + pub fixed_pic_rate_within_cvs_flag: [bool; 7], + /// `elemental_duration_in_tc_minus1[ i ]` plus 1 (when present) specifies, + /// when HighestTid is equal to i, the temporal distance, in clock ticks, + /// between the elemental units that specify the HRD output times of + /// consecutive pictures in output order as specified in the specification. + pub elemental_duration_in_tc_minus1: [u32; 7], + /// `low_delay_hrd_flag[ i ]` specifies the HRD operational mode, when + /// HighestTid is equal to i, as specified in Annex C or clause F.13. + pub low_delay_hrd_flag: [bool; 7], + /// `cpb_cnt_minus1[ i ]` plus 1 specifies the number of alternative CPB + /// specifications in the bitstream of the CVS when HighestTid is equal to + /// i. + pub cpb_cnt_minus1: [u32; 7], + /// The NAL HRD data. + pub nal_hrd: [SublayerHrdParameters; 7], + /// The VCL HRD data. + pub vcl_hrd: [SublayerHrdParameters; 7], +} + +impl Default for HrdParams { + fn default() -> Self { + Self { + initial_cpb_removal_delay_length_minus1: 23, + au_cpb_removal_delay_length_minus1: 23, + dpb_output_delay_du_length_minus1: 23, + nal_hrd_parameters_present_flag: Default::default(), + vcl_hrd_parameters_present_flag: Default::default(), + sub_pic_hrd_params_present_flag: Default::default(), + tick_divisor_minus2: Default::default(), + du_cpb_removal_delay_increment_length_minus1: Default::default(), + sub_pic_cpb_params_in_pic_timing_sei_flag: Default::default(), + bit_rate_scale: Default::default(), + cpb_size_scale: Default::default(), + cpb_size_du_scale: Default::default(), + dpb_output_delay_length_minus1: Default::default(), + fixed_pic_rate_general_flag: Default::default(), + fixed_pic_rate_within_cvs_flag: Default::default(), + elemental_duration_in_tc_minus1: Default::default(), + low_delay_hrd_flag: Default::default(), + cpb_cnt_minus1: Default::default(), + nal_hrd: Default::default(), + vcl_hrd: Default::default(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VuiParams { + /// When set, specifies that aspect_ratio_idc is present. When not set, + /// specifies that aspect_ratio_idc is not present. + pub aspect_ratio_info_present_flag: bool, + /// Specifies the value of the sample aspect ratio of the luma samples. + pub aspect_ratio_idc: u32, + /// Indicates the horizontal size of the sample aspect ratio (in arbitrary + /// units). + pub sar_width: u32, + /// Indicates the vertical size of the sample aspect ratio (in arbitrary + /// units). + pub sar_height: u32, + /// When set, specifies that the overscan_appropriate_flag is present. When + /// not set, the preferred display method for the video signal is + /// unspecified. + pub overscan_info_present_flag: bool, + /// When set indicates that the cropped decoded pictures output are suitable + /// for display using overscan. When not set, indicates that the cropped + /// decoded pictures output contain visually important information in the + /// entire region out to the edges of the conformance cropping window of the + /// picture, such that the cropped decoded pictures output should not be + /// displayed using overscan. + pub overscan_appropriate_flag: bool, + /// When set, specifies that video_format, video_full_range_flag and + /// colour_description_present_flag are present. When not set, specify that + /// video_format, video_full_range_flag and colour_description_present_flag + /// are not present. + pub video_signal_type_present_flag: bool, + /// Indicates the representation of the pictures as specified in Table E.2, + /// before being coded in accordance with this Specification. + pub video_format: u8, + /// Indicates the black level and range of the luma and chroma signals as + /// derived from E′Y, E′PB, and E′PR or E′R, E′G, and E′B real-valued + /// component signals. + pub video_full_range_flag: bool, + /// When set, specifies that colour_primaries, transfer_characteristics, and + /// matrix_coeffs are present. When not set, specifies that + /// colour_primaries, transfer_characteristics, and matrix_coeffs are not + /// present. + pub colour_description_present_flag: bool, + /// Indicates the chromaticity coordinates of the source primaries as + /// specified in Table E.3 in terms of the CIE 1931 definition of x and y as + /// specified in ISO 11664-1. + pub colour_primaries: u32, + /// See table E.4 in the specification. + pub transfer_characteristics: u32, + /// Describes the matrix coefficients used in deriving luma and chroma + /// signals from the green, blue, and red, or Y, Z, and X primaries, as + /// specified in Table E.5. + pub matrix_coeffs: u32, + /// When true, specifies that chroma_sample_loc_type_top_field and + /// chroma_sample_loc_type_bottom_field are present. When false, specifies + /// that chroma_sample_loc_type_top_field and + /// chroma_sample_loc_type_bottom_field are not present. + pub chroma_loc_info_present_flag: bool, + /// See the specification for more details. + pub chroma_sample_loc_type_top_field: u32, + /// See the specification for more details. + pub chroma_sample_loc_type_bottom_field: u32, + /// When true, indicates that the value of all decoded chroma samples is + /// equal to 1 << ( BitDepthC − 1 ). When false, provides no indication of + /// decoded chroma sample values. + pub neutral_chroma_indication_flag: bool, + /// When true, indicates that the CVS conveys pictures that represent + /// fields, and specifies that a picture timing SEI message shall be present + /// in every access unit of the current CVS. When false, indicates that the + /// CVS conveys pictures that represent frames and that a picture timing SEI + /// message may or may not be present in any access unit of the current CVS. + pub field_seq_flag: bool, + /// When true, specifies that picture timing SEI messages are present for + /// every picture and include the pic_struct, source_scan_type and + /// duplicate_flag syntax elements. When false, specifies that the + /// pic_struct syntax element is not present in picture timing SEI messages. + pub frame_field_info_present_flag: bool, + /// When true, indicates that the default display window parameters follow + /// next in the VUI. When false, indicates that the default display window + /// parameters are not present. + pub default_display_window_flag: bool, + /// Specifies the samples of the pictures in the CVS that are within the + /// default display window, in terms of a rectangular region specified in + /// picture coordinates for display. + pub def_disp_win_left_offset: u32, + /// Specifies the samples of the pictures in the CVS that are within the + /// default display window, in terms of a rectangular region specified in + /// picture coordinates for display. + pub def_disp_win_right_offset: u32, + /// Specifies the samples of the pictures in the CVS that are within the + /// default display window, in terms of a rectangular region specified in + /// picture coordinates for display. + pub def_disp_win_top_offset: u32, + /// Specifies the samples of the pictures in the CVS that are within the + /// default display window, in terms of a rectangular region specified in + /// picture coordinates for display. + pub def_disp_win_bottom_offset: u32, + /// When set, specifies that vui_num_units_in_tick, vui_time_scale, + /// vui_poc_proportional_to_timing_flag and vui_hrd_parameters_present_flag + /// are present in the vui_parameters( ) syntax structure. When not set, + /// specifies that vui_num_units_in_tick, vui_time_scale, + /// vui_poc_proportional_to_timing_flag and vui_hrd_parameters_present_flag + /// are not present in the vui_parameters( ) syntax structure + pub timing_info_present_flag: bool, + /// The number of time units of a clock operating at the frequency + /// vui_time_scale Hz that corresponds to one increment (called a clock + /// tick) of a clock tick counter. + pub num_units_in_tick: u32, + /// Is the number of time units that pass in one second. For example, a time + /// coordinate system that measures time using a 27 MHz clock has a + /// vui_time_scale of 27 000 000. + pub time_scale: u32, + /// When set, indicates that the picture order count value for each picture + /// in the CVS that is not the first picture in the CVS, in decoding order, + /// is proportional to the output time of the picture relative to the output + /// time of the first picture in the CVS. When not set, indicates that the + /// picture order count value for each picture in the CVS that is not the + /// first picture in the CVS, in decoding order, may or may not be + /// proportional to the output time of the picture relative to the output + /// time of the first picture in the CVS. + pub poc_proportional_to_timing_flag: bool, + /// vui_num_ticks_poc_diff_one_minus1 plus 1 specifies the number of clock + /// ticks corresponding to a difference of picture order count values equal + /// to 1. + pub num_ticks_poc_diff_one_minus1: u32, + /// When set, specifies that the syntax structure hrd_parameters( ) is + /// present in the vui_parameters( ) syntax structure. When not set, + /// specifies that the syntax structure hrd_parameters( ) is not present in + /// the vui_parameters( ) syntax structure. + pub hrd_parameters_present_flag: bool, + /// The hrd_parameters() data. + pub hrd: HrdParams, + /// When set, specifies that the bitstream restriction parameters for the + /// CVS are present. When not set, specifies that the bitstream restriction + /// parameters for the CVS are not present. + pub bitstream_restriction_flag: bool, + /// When set, indicates that each PPS that is active in the CVS has the same + /// value of the syntax elements num_tile_columns_minus1, + /// num_tile_rows_minus1, uniform_spacing_flag, `column_width_minus1[ i ]`, + /// `row_height_minus1[ i ]` and loop_filter_across_tiles_enabled_flag, when + /// present. When not set, indicates that tiles syntax elements in different + /// PPSs may or may not have the same value + pub tiles_fixed_structure_flag: bool, + /// When not set, indicates that no sample outside the picture boundaries + /// and no sample at a fractional sample position for which the sample value + /// is derived using one or more samples outside the picture boundaries is + /// used for inter prediction of any sample. When set, indicates that one + /// or more samples outside the picture boundaries may be used in inter + /// prediction. + pub motion_vectors_over_pic_boundaries_flag: bool, + /// When set, indicates that all P and B slices (when present) that belong + /// to the same picture have an identical reference picture list 0 and that + /// all B slices (when present) that belong to the same picture have an + /// identical reference picture list 1. + pub restricted_ref_pic_lists_flag: bool, + /// When not equal to 0, establishes a bound on the maximum possible size of + /// distinct coded spatial segmentation regions in the pictures of the CVS. + pub min_spatial_segmentation_idc: u32, + /// Indicates a number of bytes not exceeded by the sum of the sizes of the + /// VCL NAL units associated with any coded picture in the CVS. + pub max_bytes_per_pic_denom: u32, + /// Indicates an upper bound for the number of coded bits of coding_unit( ) + /// data for anycoding block in any picture of the CVS. + pub max_bits_per_min_cu_denom: u32, + /// Indicate the maximum absolute value of a decoded horizontal and vertical + /// motion vector component, respectively, in quarter luma sample units, for + /// all pictures in the CVS. + pub log2_max_mv_length_horizontal: u32, + /// Indicate the maximum absolute value of a decoded horizontal and vertical + /// motion vector component, respectively, in quarter luma sample units, for + /// all pictures in the CVS. + pub log2_max_mv_length_vertical: u32, +} + +impl Default for VuiParams { + fn default() -> Self { + Self { + aspect_ratio_info_present_flag: Default::default(), + aspect_ratio_idc: Default::default(), + sar_width: Default::default(), + sar_height: Default::default(), + overscan_info_present_flag: Default::default(), + overscan_appropriate_flag: Default::default(), + video_signal_type_present_flag: Default::default(), + video_format: 5, + video_full_range_flag: Default::default(), + colour_description_present_flag: Default::default(), + colour_primaries: 2, + transfer_characteristics: 2, + matrix_coeffs: 2, + chroma_loc_info_present_flag: Default::default(), + chroma_sample_loc_type_top_field: Default::default(), + chroma_sample_loc_type_bottom_field: Default::default(), + neutral_chroma_indication_flag: Default::default(), + field_seq_flag: Default::default(), + frame_field_info_present_flag: Default::default(), + default_display_window_flag: Default::default(), + def_disp_win_left_offset: Default::default(), + def_disp_win_right_offset: Default::default(), + def_disp_win_top_offset: Default::default(), + def_disp_win_bottom_offset: Default::default(), + timing_info_present_flag: Default::default(), + num_units_in_tick: Default::default(), + time_scale: Default::default(), + poc_proportional_to_timing_flag: Default::default(), + num_ticks_poc_diff_one_minus1: Default::default(), + hrd_parameters_present_flag: Default::default(), + hrd: Default::default(), + bitstream_restriction_flag: Default::default(), + tiles_fixed_structure_flag: Default::default(), + motion_vectors_over_pic_boundaries_flag: true, + restricted_ref_pic_lists_flag: Default::default(), + min_spatial_segmentation_idc: Default::default(), + max_bytes_per_pic_denom: 2, + max_bits_per_min_cu_denom: 1, + log2_max_mv_length_horizontal: 15, + log2_max_mv_length_vertical: 15, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct Parser { + active_vpses: BTreeMap>, + active_spses: BTreeMap>, + active_ppses: BTreeMap>, +} + +impl Parser { + /// Parse a VPS NALU. + pub fn parse_vps(&mut self, nalu: &Nalu) -> Result<&Vps, String> { + if !matches!(nalu.header.type_, NaluType::VpsNut) { + return Err(format!( + "Invalid NALU type, expected {:?}, got {:?}", + NaluType::VpsNut, + nalu.header.type_ + )); + } + + let data = nalu.as_ref(); + let header = &nalu.header; + let hdr_len = header.len(); + // Skip the header + let mut r = BitReader::new(&data[hdr_len..], true); + + let mut vps = Vps { + video_parameter_set_id: r.read_bits(4)?, + base_layer_internal_flag: r.read_bit()?, + base_layer_available_flag: r.read_bit()?, + max_layers_minus1: r.read_bits(6)?, + max_sub_layers_minus1: r.read_bits(3)?, + temporal_id_nesting_flag: r.read_bit()?, + ..Default::default() + }; + + r.skip_bits(16)?; // vps_reserved_0xffff_16bits + + let ptl = &mut vps.profile_tier_level; + Self::parse_profile_tier_level(ptl, &mut r, true, vps.max_sub_layers_minus1)?; + + vps.sub_layer_ordering_info_present_flag = r.read_bit()?; + + let start = if vps.sub_layer_ordering_info_present_flag { + 0 + } else { + vps.max_sub_layers_minus1 + } as usize; + + for i in start..=usize::from(vps.max_sub_layers_minus1) { + vps.max_dec_pic_buffering_minus1[i] = r.read_ue_max(15)?; + vps.max_num_reorder_pics[i] = r.read_ue_max(vps.max_dec_pic_buffering_minus1[i])?; + vps.max_latency_increase_plus1[i] = r.read_ue()?; + + if i > 0 { + if vps.max_dec_pic_buffering_minus1[i] < vps.max_dec_pic_buffering_minus1[i - 1] { + return Err(format!( + "Invalid max_dec_pic_buffering_minus1[{}]: {}", + i, vps.max_dec_pic_buffering_minus1[i] + )); + } + + if vps.max_num_reorder_pics[i] < vps.max_num_reorder_pics[i - 1] { + return Err(format!( + "Invalid max_num_reorder_pics[{}]: {}", + i, vps.max_num_reorder_pics[i] + )); + } + } + } + + // vps_sub_layer_ordering_info_present_flag equal to 0 specifies that + // the values of vps_max_dec_pic_buffering_minus1[ + // vps_max_sub_layers_minus1 ], vps_max_num_reorder_pics[ vps_max_sub_ + // layers_minus1 ] and vps_max_latency_increase_plus1[ + // vps_max_sub_layers_minus1 ] apply to all sub-layers + if !vps.sub_layer_ordering_info_present_flag { + let max_num_sublayers = usize::from(vps.max_sub_layers_minus1); + for i in 0..max_num_sublayers { + vps.max_dec_pic_buffering_minus1[i] = vps.max_dec_pic_buffering_minus1[max_num_sublayers]; + + vps.max_num_reorder_pics[i] = vps.max_num_reorder_pics[max_num_sublayers]; + + vps.max_latency_increase_plus1[i] = vps.max_latency_increase_plus1[max_num_sublayers]; + } + } + + vps.max_layer_id = r.read_bits(6)?; + if vps.max_layer_id > 62 { + return Err(format!("Invalid max_layer_id {}", vps.max_layer_id)); + } + + vps.num_layer_sets_minus1 = r.read_ue_max(1023)?; + + for _ in 1..=vps.num_layer_sets_minus1 { + for _ in 0..=vps.max_layer_id { + // Skip layer_id_included_flag[i][j] for now. + r.skip_bits(1)?; + } + } + + vps.timing_info_present_flag = r.read_bit()?; + + if vps.timing_info_present_flag { + vps.num_units_in_tick = r.read_bits::(31)? << 1; + vps.num_units_in_tick |= r.read_bits::(1)?; + + vps.time_scale = r.read_bits::(31)? << 1; + vps.time_scale |= r.read_bits::(1)?; + + vps.poc_proportional_to_timing_flag = r.read_bit()?; + if vps.poc_proportional_to_timing_flag { + vps.num_ticks_poc_diff_one_minus1 = r.read_ue()?; + } + + vps.num_hrd_parameters = r.read_ue()?; + + for i in 0..vps.num_hrd_parameters as usize { + vps.hrd_layer_set_idx.push(r.read_ue()?); + if i > 0 { + vps.cprms_present_flag.push(r.read_bit()?); + } + + let mut hrd = HrdParams::default(); + Self::parse_hrd_parameters(vps.cprms_present_flag[i], vps.max_sub_layers_minus1, &mut hrd, &mut r)?; + + vps.hrd_parameters.push(hrd); + } + } + + vps.extension_flag = r.read_bit()?; + + if self.active_vpses.keys().len() >= MAX_VPS_COUNT { + return Err("Broken data: Number of active VPSs > MAX_VPS_COUNT".into()); + } + + let key = vps.video_parameter_set_id; + let vps = Rc::new(vps); + self.active_vpses.remove(&key); + Ok(self.active_vpses.entry(key).or_insert(vps)) + } + + fn parse_profile_tier_level( + ptl: &mut ProfileTierLevel, + r: &mut BitReader, + profile_present_flag: bool, + sps_max_sub_layers_minus_1: u8, + ) -> Result<(), String> { + if profile_present_flag { + ptl.general_profile_space = r.read_bits(2)?; + ptl.general_tier_flag = r.read_bit()?; + ptl.general_profile_idc = r.read_bits(5)?; + + for i in 0..32 { + ptl.general_profile_compatibility_flag[i] = r.read_bit()?; + } + + ptl.general_progressive_source_flag = r.read_bit()?; + ptl.general_interlaced_source_flag = r.read_bit()?; + ptl.general_non_packed_constraint_flag = r.read_bit()?; + ptl.general_frame_only_constraint_flag = r.read_bit()?; + + if ptl.general_profile_idc == 4 + || ptl.general_profile_compatibility_flag[4] + || ptl.general_profile_idc == 5 + || ptl.general_profile_compatibility_flag[5] + || ptl.general_profile_idc == 6 + || ptl.general_profile_compatibility_flag[6] + || ptl.general_profile_idc == 7 + || ptl.general_profile_compatibility_flag[7] + || ptl.general_profile_idc == 8 + || ptl.general_profile_compatibility_flag[8] + || ptl.general_profile_idc == 9 + || ptl.general_profile_compatibility_flag[9] + || ptl.general_profile_idc == 10 + || ptl.general_profile_compatibility_flag[10] + || ptl.general_profile_idc == 11 + || ptl.general_profile_compatibility_flag[11] + { + ptl.general_max_12bit_constraint_flag = r.read_bit()?; + ptl.general_max_10bit_constraint_flag = r.read_bit()?; + ptl.general_max_8bit_constraint_flag = r.read_bit()?; + ptl.general_max_422chroma_constraint_flag = r.read_bit()?; + ptl.general_max_420chroma_constraint_flag = r.read_bit()?; + ptl.general_max_monochrome_constraint_flag = r.read_bit()?; + ptl.general_intra_constraint_flag = r.read_bit()?; + ptl.general_one_picture_only_constraint_flag = r.read_bit()?; + ptl.general_lower_bit_rate_constraint_flag = r.read_bit()?; + if ptl.general_profile_idc == 5 + || ptl.general_profile_compatibility_flag[5] + || ptl.general_profile_idc == 9 + || ptl.general_profile_compatibility_flag[9] + || ptl.general_profile_idc == 10 + || ptl.general_profile_compatibility_flag[10] + || ptl.general_profile_idc == 11 + || ptl.general_profile_compatibility_flag[11] + { + ptl.general_max_14bit_constraint_flag = r.read_bit()?; + // Skip general_reserved_zero_33bits + r.skip_bits(31)?; + r.skip_bits(2)?; + } else { + // Skip general_reserved_zero_34bits + r.skip_bits(31)?; + r.skip_bits(3)?; + } + } else if ptl.general_profile_idc == 2 || ptl.general_profile_compatibility_flag[2] { + // Skip general_reserved_zero_7bits + r.skip_bits(7)?; + ptl.general_one_picture_only_constraint_flag = r.read_bit()?; + // Skip general_reserved_zero_35bits + r.skip_bits(31)?; + r.skip_bits(4)?; + } else { + r.skip_bits(31)?; + r.skip_bits(12)?; + } + + if ptl.general_profile_idc == 1 + || ptl.general_profile_compatibility_flag[1] + || ptl.general_profile_idc == 2 + || ptl.general_profile_compatibility_flag[2] + || ptl.general_profile_idc == 3 + || ptl.general_profile_compatibility_flag[3] + || ptl.general_profile_idc == 4 + || ptl.general_profile_compatibility_flag[4] + || ptl.general_profile_idc == 5 + || ptl.general_profile_compatibility_flag[5] + || ptl.general_profile_idc == 9 + || ptl.general_profile_compatibility_flag[9] + || ptl.general_profile_idc == 11 + || ptl.general_profile_compatibility_flag[11] + { + ptl.general_inbld_flag = r.read_bit()?; + } else { + r.skip_bits(1)?; + } + } + + let level: u8 = r.read_bits(8)?; + ptl.general_level_idc = Level::try_from(level)?; + + for i in 0..sps_max_sub_layers_minus_1 as usize { + ptl.sub_layer_profile_present_flag[i] = r.read_bit()?; + ptl.sub_layer_level_present_flag[i] = r.read_bit()?; + } + + if sps_max_sub_layers_minus_1 > 0 { + for _ in sps_max_sub_layers_minus_1..8 { + r.skip_bits(2)?; + } + } + + for i in 0..sps_max_sub_layers_minus_1 as usize { + if ptl.sub_layer_profile_present_flag[i] { + ptl.sub_layer_profile_space[i] = r.read_bits(2)?; + ptl.sub_layer_tier_flag[i] = r.read_bit()?; + ptl.sub_layer_profile_idc[i] = r.read_bits(5)?; + for j in 0..32 { + ptl.sub_layer_profile_compatibility_flag[i][j] = r.read_bit()?; + } + ptl.sub_layer_progressive_source_flag[i] = r.read_bit()?; + ptl.sub_layer_interlaced_source_flag[i] = r.read_bit()?; + ptl.sub_layer_non_packed_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_frame_only_constraint_flag[i] = r.read_bit()?; + + if ptl.sub_layer_profile_idc[i] == 4 + || ptl.sub_layer_profile_compatibility_flag[i][4] + || ptl.sub_layer_profile_idc[i] == 5 + || ptl.sub_layer_profile_compatibility_flag[i][5] + || ptl.sub_layer_profile_idc[i] == 6 + || ptl.sub_layer_profile_compatibility_flag[i][6] + || ptl.sub_layer_profile_idc[i] == 7 + || ptl.sub_layer_profile_compatibility_flag[i][7] + || ptl.sub_layer_profile_idc[i] == 8 + || ptl.sub_layer_profile_compatibility_flag[i][8] + || ptl.sub_layer_profile_idc[i] == 9 + || ptl.sub_layer_profile_compatibility_flag[i][9] + || ptl.sub_layer_profile_idc[i] == 10 + || ptl.sub_layer_profile_compatibility_flag[i][10] + || ptl.sub_layer_profile_idc[i] == 11 + || ptl.sub_layer_profile_compatibility_flag[i][11] + { + ptl.sub_layer_max_12bit_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_max_10bit_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_max_8bit_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_max_422chroma_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_max_420chroma_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_max_monochrome_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_intra_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_one_picture_only_constraint_flag[i] = r.read_bit()?; + ptl.sub_layer_lower_bit_rate_constraint_flag[i] = r.read_bit()?; + + if ptl.sub_layer_profile_idc[i] == 5 + || ptl.sub_layer_profile_compatibility_flag[i][5] + || ptl.sub_layer_profile_idc[i] == 9 + || ptl.sub_layer_profile_compatibility_flag[i][9] + || ptl.sub_layer_profile_idc[i] == 10 + || ptl.sub_layer_profile_compatibility_flag[i][10] + || ptl.sub_layer_profile_idc[i] == 11 + || ptl.sub_layer_profile_compatibility_flag[i][11] + { + ptl.sub_layer_max_14bit_constraint_flag[i] = r.read_bit()?; + r.skip_bits(33)?; + } else { + r.skip_bits(34)?; + } + } else if ptl.sub_layer_profile_idc[i] == 2 || ptl.sub_layer_profile_compatibility_flag[i][2] { + r.skip_bits(7)?; + ptl.sub_layer_one_picture_only_constraint_flag[i] = r.read_bit()?; + r.skip_bits(35)?; + } else { + r.skip_bits(43)?; + } + + if ptl.sub_layer_profile_idc[i] == 1 + || ptl.sub_layer_profile_compatibility_flag[i][1] + || ptl.sub_layer_profile_idc[i] == 2 + || ptl.sub_layer_profile_compatibility_flag[i][2] + || ptl.sub_layer_profile_idc[i] == 3 + || ptl.sub_layer_profile_compatibility_flag[i][3] + || ptl.sub_layer_profile_idc[i] == 4 + || ptl.sub_layer_profile_compatibility_flag[i][4] + || ptl.sub_layer_profile_idc[i] == 5 + || ptl.sub_layer_profile_compatibility_flag[i][5] + || ptl.sub_layer_profile_idc[i] == 9 + || ptl.sub_layer_profile_compatibility_flag[i][9] + || ptl.sub_layer_profile_idc[i] == 11 + || ptl.sub_layer_profile_compatibility_flag[i][11] + { + ptl.sub_layer_inbld_flag[i] = r.read_bit()?; + } else { + r.skip_bits(1)?; + } + } + + if ptl.sub_layer_level_present_flag[i] { + let level: u8 = r.read_bits(8)?; + ptl.sub_layer_level_idc[i] = Level::try_from(level)?; + } + } + Ok(()) + } + + fn fill_default_scaling_list(sl: &mut ScalingLists, size_id: i32, matrix_id: i32) { + if size_id == 0 { + sl.scaling_list_4x4[matrix_id as usize] = DEFAULT_SCALING_LIST_0; + return; + } + + let dst = match size_id { + 1 => &mut sl.scaling_list_8x8[matrix_id as usize], + 2 => &mut sl.scaling_list_16x16[matrix_id as usize], + 3 => &mut sl.scaling_list_32x32[matrix_id as usize], + _ => panic!("Invalid size_id {}", size_id), + }; + + let src = if matrix_id < 3 { + &DEFAULT_SCALING_LIST_1 + } else if matrix_id <= 5 { + &DEFAULT_SCALING_LIST_2 + } else { + panic!("Invalid matrix_id {}", matrix_id); + }; + + *dst = *src; + + // When `scaling_list_pred_mode_flag[ sizeId ]`[ matrixId ] is equal to + // 0, scaling_list_pred_matrix_id_ `delta[ sizeId ]`[ matrixId ] is equal + // to 0 and sizeId is greater than 1, the value of + // scaling_list_dc_coef_minus8[ sizeId − 2 `][ matrixId ]` is inferred to + // be equal to 8. + // + // Since we are using a slightly different layout here, with two + // different field names (i.e. 16x16, and 32x32), we must differentiate + // between size_id == 2 or size_id == 3. + if size_id == 2 { + sl.scaling_list_dc_coef_minus8_16x16[matrix_id as usize] = 8; + } else if size_id == 3 { + sl.scaling_list_dc_coef_minus8_32x32[matrix_id as usize] = 8; + } + } + + fn parse_scaling_list_data(sl: &mut ScalingLists, r: &mut BitReader) -> Result<(), String> { + // 7.4.5 + for size_id in 0..4 { + let mut matrix_id = 0; + while matrix_id < 6 { + let scaling_list_pred_mode_flag = r.read_bit()?; + // If `scaling_list_pred_matrix_id_delta[ sizeId ]`[ matrixId ] is + // equal to 0, the scaling list is inferred from the default + // scaling list `ScalingList[ sizeId ]`[ matrixId `][ i ]` as specified + // in Table 7-5 and Table 7-6 for i = 0..Min( 63, ( 1 << ( 4 + ( + // sizeId << 1 ) ) ) − 1 ). + if !scaling_list_pred_mode_flag { + let scaling_list_pred_matrix_id_delta: u32 = r.read_ue()?; + if scaling_list_pred_matrix_id_delta == 0 { + Self::fill_default_scaling_list(sl, size_id, matrix_id); + } else { + // Equation 7-42 + let factor = if size_id == 3 { 3 } else { 1 }; + let ref_matrix_id = matrix_id as u32 - scaling_list_pred_matrix_id_delta * factor; + if size_id == 0 { + sl.scaling_list_4x4[matrix_id as usize] = sl.scaling_list_4x4[ref_matrix_id as usize]; + } else { + let src = match size_id { + 1 => sl.scaling_list_8x8[ref_matrix_id as usize], + 2 => sl.scaling_list_16x16[ref_matrix_id as usize], + 3 => sl.scaling_list_32x32[ref_matrix_id as usize], + _ => return Err(format!("Invalid size_id {}", size_id)), + }; + + let dst = match size_id { + 1 => &mut sl.scaling_list_8x8[matrix_id as usize], + 2 => &mut sl.scaling_list_16x16[matrix_id as usize], + 3 => &mut sl.scaling_list_32x32[matrix_id as usize], + _ => return Err(format!("Invalid size_id {}", size_id)), + }; + + *dst = src; + + if size_id == 2 { + sl.scaling_list_dc_coef_minus8_16x16[matrix_id as usize] = + sl.scaling_list_dc_coef_minus8_16x16[ref_matrix_id as usize]; + } else if size_id == 3 { + sl.scaling_list_dc_coef_minus8_32x32[matrix_id as usize] = + sl.scaling_list_dc_coef_minus8_32x32[ref_matrix_id as usize]; + } + } + } + } else { + let mut next_coef = 8i32; + let coef_num = std::cmp::min(64, 1 << (4 + (size_id << 1))); + + if size_id > 1 { + if size_id == 2 { + sl.scaling_list_dc_coef_minus8_16x16[matrix_id as usize] = r.read_se_bounded(-7, 247)?; + next_coef = i32::from(sl.scaling_list_dc_coef_minus8_16x16[matrix_id as usize]) + 8; + } else if size_id == 3 { + sl.scaling_list_dc_coef_minus8_32x32[matrix_id as usize] = r.read_se_bounded(-7, 247)?; + next_coef = i32::from(sl.scaling_list_dc_coef_minus8_32x32[matrix_id as usize]) + 8; + } + } + + for i in 0..coef_num as usize { + let scaling_list_delta_coef: i32 = r.read_se_bounded(-128, 127)?; + next_coef = (next_coef + scaling_list_delta_coef + 256) % 256; + match size_id { + 0 => sl.scaling_list_4x4[matrix_id as usize][i] = next_coef as _, + 1 => sl.scaling_list_8x8[matrix_id as usize][i] = next_coef as _, + 2 => sl.scaling_list_16x16[matrix_id as usize][i] = next_coef as _, + 3 => sl.scaling_list_32x32[matrix_id as usize][i] = next_coef as _, + _ => return Err(format!("Invalid size_id {}", size_id)), + } + } + } + let step = if size_id == 3 { 3 } else { 1 }; + matrix_id += step; + } + } + Ok(()) + } + + fn parse_short_term_ref_pic_set( + sps: &Sps, + st: &mut ShortTermRefPicSet, + r: &mut BitReader, + st_rps_idx: u8, + ) -> Result<(), String> { + if st_rps_idx != 0 { + st.inter_ref_pic_set_prediction_flag = r.read_bit()?; + } + + // (7-59) + if st.inter_ref_pic_set_prediction_flag { + if st_rps_idx == sps.num_short_term_ref_pic_sets { + st.delta_idx_minus1 = r.read_ue_max(st_rps_idx as u32 - 1)?; + } + + st.delta_rps_sign = r.read_bit()?; + // The value of abs_delta_rps_minus1 shall be in the range of 0 to + // 2^15 − 1, inclusive. + st.abs_delta_rps_minus1 = r.read_ue_max(32767)?; + + let ref_rps_idx = st_rps_idx - (st.delta_idx_minus1 + 1); + let delta_rps = (1 - 2 * st.delta_rps_sign as i32) * (st.abs_delta_rps_minus1 as i32 + 1); + + let ref_st = sps + .short_term_ref_pic_set + .get(usize::from(ref_rps_idx)) + .ok_or::("Invalid ref_rps_idx".into())?; + + let mut used_by_curr_pic_flag = [false; 64]; + + // 7.4.8 - defaults to 1 if not present + let mut use_delta_flag = [true; 64]; + + for j in 0..=ref_st.num_delta_pocs as usize { + used_by_curr_pic_flag[j] = r.read_bit()?; + if !used_by_curr_pic_flag[j] { + use_delta_flag[j] = r.read_bit()?; + } + } + + // (7-61) + let mut i = 0; + // Ranges are [a,b[, but the real loop is [b, a], i.e. + // [num_positive_pics - 1, 0]. Use ..= so that b is included when + // rev() is called. + for j in (0..=isize::from(ref_st.num_positive_pics) - 1) + .rev() + .take_while(|j| *j >= 0) + .map(|j| j as usize) + { + let d_poc = ref_st.delta_poc_s1[j] + delta_rps; + if d_poc < 0 && use_delta_flag[usize::from(ref_st.num_negative_pics) + j] { + st.delta_poc_s0[i] = d_poc; + st.used_by_curr_pic_s0[i] = used_by_curr_pic_flag[usize::from(ref_st.num_negative_pics) + j]; + + i += 1; + } + } + + if delta_rps < 0 && use_delta_flag[ref_st.num_delta_pocs as usize] { + st.delta_poc_s0[i] = delta_rps; + st.used_by_curr_pic_s0[i] = used_by_curr_pic_flag[ref_st.num_delta_pocs as usize]; + + i += 1; + } + + // Let's *not* change the original algorithm in any way. + #[allow(clippy::needless_range_loop)] + for j in 0..ref_st.num_negative_pics as usize { + let d_poc = ref_st.delta_poc_s0[j] + delta_rps; + if d_poc < 0 && use_delta_flag[j] { + st.delta_poc_s0[i] = d_poc; + st.used_by_curr_pic_s0[i] = used_by_curr_pic_flag[j]; + + i += 1; + } + } + + st.num_negative_pics = i as u8; + + // (7-62) + let mut i = 0; + // Ranges are [a,b[, but the real loop is [b, a], i.e. + // [num_negative_pics - 1, 0]. Use ..= so that b is included when + // rev() is called. + for j in (0..=isize::from(ref_st.num_negative_pics) - 1) + .rev() + .take_while(|j| *j >= 0) + .map(|j| j as usize) + { + let d_poc = ref_st.delta_poc_s0[j] + delta_rps; + if d_poc > 0 && use_delta_flag[j] { + st.delta_poc_s1[i] = d_poc; + st.used_by_curr_pic_s1[i] = used_by_curr_pic_flag[j]; + + i += 1; + } + } + + if delta_rps > 0 && use_delta_flag[ref_st.num_delta_pocs as usize] { + st.delta_poc_s1[i] = delta_rps; + st.used_by_curr_pic_s1[i] = used_by_curr_pic_flag[ref_st.num_delta_pocs as usize]; + + i += 1; + } + + for j in 0..usize::from(ref_st.num_positive_pics) { + let d_poc = ref_st.delta_poc_s1[j] + delta_rps; + if d_poc > 0 && use_delta_flag[ref_st.num_negative_pics as usize + j] { + st.delta_poc_s1[i] = d_poc; + st.used_by_curr_pic_s1[i] = used_by_curr_pic_flag[ref_st.num_negative_pics as usize + j]; + + i += 1; + } + } + + st.num_positive_pics = i as u8; + } else { + st.num_negative_pics = r.read_ue_max(u32::from( + sps.max_dec_pic_buffering_minus1[usize::from(sps.max_sub_layers_minus1)], + ))?; + + st.num_positive_pics = r.read_ue_max(u32::from( + sps.max_dec_pic_buffering_minus1[usize::from(sps.max_sub_layers_minus1)] - st.num_negative_pics, + ))?; + + for i in 0..usize::from(st.num_negative_pics) { + let delta_poc_s0_minus1: u32 = r.read_ue_max(32767)?; + + if i == 0 { + st.delta_poc_s0[i] = -(delta_poc_s0_minus1 as i32 + 1); + } else { + st.delta_poc_s0[i] = st.delta_poc_s0[i - 1] - (delta_poc_s0_minus1 as i32 + 1); + } + + st.used_by_curr_pic_s0[i] = r.read_bit()?; + } + + for i in 0..usize::from(st.num_positive_pics) { + let delta_poc_s1_minus1: u32 = r.read_ue_max(32767)?; + + if i == 0 { + st.delta_poc_s1[i] = delta_poc_s1_minus1 as i32 + 1; + } else { + st.delta_poc_s1[i] = st.delta_poc_s1[i - 1] + (delta_poc_s1_minus1 as i32 + 1); + } + + st.used_by_curr_pic_s1[i] = r.read_bit()?; + } + } + + st.num_delta_pocs = u32::from(st.num_negative_pics + st.num_positive_pics); + + Ok(()) + } + + fn parse_sublayer_hrd_parameters( + h: &mut SublayerHrdParameters, + cpb_cnt: u32, + sub_pic_hrd_params_present_flag: bool, + r: &mut BitReader, + ) -> Result<(), String> { + for i in 0..cpb_cnt as usize { + h.bit_rate_value_minus1[i] = r.read_ue_max((2u64.pow(32) - 2) as u32)?; + h.cpb_size_value_minus1[i] = r.read_ue_max((2u64.pow(32) - 2) as u32)?; + if sub_pic_hrd_params_present_flag { + h.cpb_size_du_value_minus1[i] = r.read_ue_max((2u64.pow(32) - 2) as u32)?; + h.bit_rate_du_value_minus1[i] = r.read_ue_max((2u64.pow(32) - 2) as u32)?; + } + + h.cbr_flag[i] = r.read_bit()?; + } + + Ok(()) + } + + fn parse_hrd_parameters( + common_inf_present_flag: bool, + max_num_sublayers_minus1: u8, + hrd: &mut HrdParams, + r: &mut BitReader, + ) -> Result<(), String> { + if common_inf_present_flag { + hrd.nal_hrd_parameters_present_flag = r.read_bit()?; + hrd.vcl_hrd_parameters_present_flag = r.read_bit()?; + if hrd.nal_hrd_parameters_present_flag || hrd.vcl_hrd_parameters_present_flag { + hrd.sub_pic_hrd_params_present_flag = r.read_bit()?; + if hrd.sub_pic_hrd_params_present_flag { + hrd.tick_divisor_minus2 = r.read_bits(8)?; + hrd.du_cpb_removal_delay_increment_length_minus1 = r.read_bits(5)?; + hrd.sub_pic_cpb_params_in_pic_timing_sei_flag = r.read_bit()?; + hrd.dpb_output_delay_du_length_minus1 = r.read_bits(5)?; + } + hrd.bit_rate_scale = r.read_bits(4)?; + hrd.cpb_size_scale = r.read_bits(4)?; + if hrd.sub_pic_hrd_params_present_flag { + hrd.cpb_size_du_scale = r.read_bits(4)?; + } + hrd.initial_cpb_removal_delay_length_minus1 = r.read_bits(5)?; + hrd.au_cpb_removal_delay_length_minus1 = r.read_bits(5)?; + hrd.dpb_output_delay_length_minus1 = r.read_bits(5)?; + } + } + + for i in 0..=max_num_sublayers_minus1 as usize { + hrd.fixed_pic_rate_general_flag[i] = r.read_bit()?; + if !hrd.fixed_pic_rate_general_flag[i] { + hrd.fixed_pic_rate_within_cvs_flag[i] = r.read_bit()?; + } + if hrd.fixed_pic_rate_within_cvs_flag[i] { + hrd.elemental_duration_in_tc_minus1[i] = r.read_ue_max(2047)?; + } else { + hrd.low_delay_hrd_flag[i] = r.read_bit()?; + } + + if !hrd.low_delay_hrd_flag[i] { + hrd.cpb_cnt_minus1[i] = r.read_ue_max(31)?; + } + + if hrd.nal_hrd_parameters_present_flag { + Self::parse_sublayer_hrd_parameters( + &mut hrd.nal_hrd[i], + hrd.cpb_cnt_minus1[i] + 1, + hrd.sub_pic_hrd_params_present_flag, + r, + )?; + } + + if hrd.vcl_hrd_parameters_present_flag { + Self::parse_sublayer_hrd_parameters( + &mut hrd.vcl_hrd[i], + hrd.cpb_cnt_minus1[i] + 1, + hrd.sub_pic_hrd_params_present_flag, + r, + )?; + } + } + + Ok(()) + } + + fn parse_vui_parameters(sps: &mut Sps, r: &mut BitReader) -> Result<(), String> { + let vui = &mut sps.vui_parameters; + + vui.aspect_ratio_info_present_flag = r.read_bit()?; + if vui.aspect_ratio_info_present_flag { + vui.aspect_ratio_idc = r.read_bits(8)?; + const EXTENDED_SAR: u32 = 255; + if vui.aspect_ratio_idc == EXTENDED_SAR { + vui.sar_width = r.read_bits(16)?; + vui.sar_height = r.read_bits(16)?; + } + } + + vui.overscan_info_present_flag = r.read_bit()?; + if vui.overscan_info_present_flag { + vui.overscan_appropriate_flag = r.read_bit()?; + } + + vui.video_signal_type_present_flag = r.read_bit()?; + if vui.video_signal_type_present_flag { + vui.video_format = r.read_bits(3)?; + vui.video_full_range_flag = r.read_bit()?; + vui.colour_description_present_flag = r.read_bit()?; + if vui.colour_description_present_flag { + vui.colour_primaries = r.read_bits(8)?; + vui.transfer_characteristics = r.read_bits(8)?; + vui.matrix_coeffs = r.read_bits(8)?; + } + } + + vui.chroma_loc_info_present_flag = r.read_bit()?; + if vui.chroma_loc_info_present_flag { + vui.chroma_sample_loc_type_top_field = r.read_ue_max(5)?; + vui.chroma_sample_loc_type_bottom_field = r.read_ue_max(5)?; + } + + vui.neutral_chroma_indication_flag = r.read_bit()?; + vui.field_seq_flag = r.read_bit()?; + vui.frame_field_info_present_flag = r.read_bit()?; + vui.default_display_window_flag = r.read_bit()?; + + if vui.default_display_window_flag { + vui.def_disp_win_left_offset = r.read_ue()?; + vui.def_disp_win_right_offset = r.read_ue()?; + vui.def_disp_win_top_offset = r.read_ue()?; + vui.def_disp_win_bottom_offset = r.read_ue()?; + } + + vui.timing_info_present_flag = r.read_bit()?; + if vui.timing_info_present_flag { + vui.num_units_in_tick = r.read_bits::(31)? << 1; + vui.num_units_in_tick |= r.read_bits::(1)?; + + if vui.num_units_in_tick == 0 { + log::warn!("Incompliant value for num_units_in_tick {}", vui.num_units_in_tick); + } + + vui.time_scale = r.read_bits::(31)? << 1; + vui.time_scale |= r.read_bits::(1)?; + + if vui.time_scale == 0 { + log::warn!("Incompliant value for time_scale {}", vui.time_scale); + } + + vui.poc_proportional_to_timing_flag = r.read_bit()?; + if vui.poc_proportional_to_timing_flag { + vui.num_ticks_poc_diff_one_minus1 = r.read_ue_max((2u64.pow(32) - 2) as u32)?; + } + + vui.hrd_parameters_present_flag = r.read_bit()?; + if vui.hrd_parameters_present_flag { + let sps_max_sub_layers_minus1 = sps.max_sub_layers_minus1; + Self::parse_hrd_parameters(true, sps_max_sub_layers_minus1, &mut vui.hrd, r)?; + } + } + + vui.bitstream_restriction_flag = r.read_bit()?; + if vui.bitstream_restriction_flag { + vui.tiles_fixed_structure_flag = r.read_bit()?; + vui.motion_vectors_over_pic_boundaries_flag = r.read_bit()?; + vui.restricted_ref_pic_lists_flag = r.read_bit()?; + + vui.min_spatial_segmentation_idc = r.read_ue_max(4095)?; + vui.max_bytes_per_pic_denom = r.read_ue()?; + vui.max_bits_per_min_cu_denom = r.read_ue()?; + vui.log2_max_mv_length_horizontal = r.read_ue_max(16)?; + vui.log2_max_mv_length_vertical = r.read_ue_max(15)?; + } + + Ok(()) + } + + fn parse_sps_scc_extension(sps: &mut Sps, r: &mut BitReader) -> Result<(), String> { + let scc = &mut sps.scc_extension; + + scc.curr_pic_ref_enabled_flag = r.read_bit()?; + scc.palette_mode_enabled_flag = r.read_bit()?; + if scc.palette_mode_enabled_flag { + scc.palette_max_size = r.read_ue_max(64)?; + scc.delta_palette_max_predictor_size = r.read_ue_max(128 - u32::from(scc.palette_max_size))?; + scc.palette_predictor_initializers_present_flag = r.read_bit()?; + if scc.palette_predictor_initializers_present_flag { + let max = u32::from(scc.palette_max_size + scc.delta_palette_max_predictor_size - 1); + scc.num_palette_predictor_initializer_minus1 = r.read_ue_max(max)?; + + let num_comps = if sps.chroma_format_idc == 0 { 1 } else { 3 }; + for comp in 0..num_comps { + for i in 0..=usize::from(scc.num_palette_predictor_initializer_minus1) { + let num_bits = if comp == 0 { + sps.bit_depth_luma_minus8 + 8 + } else { + sps.bit_depth_chroma_minus8 + 8 + }; + scc.palette_predictor_initializer[comp][i] = r.read_bits(usize::from(num_bits))?; + } + } + } + } + + scc.motion_vector_resolution_control_idc = r.read_bits(2)?; + scc.intra_boundary_filtering_disabled_flag = r.read_bit()?; + + Ok(()) + } + + fn parse_sps_range_extension(sps: &mut Sps, r: &mut BitReader) -> Result<(), String> { + let ext = &mut sps.range_extension; + + ext.transform_skip_rotation_enabled_flag = r.read_bit()?; + ext.transform_skip_context_enabled_flag = r.read_bit()?; + ext.implicit_rdpcm_enabled_flag = r.read_bit()?; + ext.explicit_rdpcm_enabled_flag = r.read_bit()?; + ext.extended_precision_processing_flag = r.read_bit()?; + ext.intra_smoothing_disabled_flag = r.read_bit()?; + ext.high_precision_offsets_enabled_flag = r.read_bit()?; + ext.persistent_rice_adaptation_enabled_flag = r.read_bit()?; + ext.cabac_bypass_alignment_enabled_flag = r.read_bit()?; + + Ok(()) + } + + /// Parse a SPS NALU. + pub fn parse_sps(&mut self, nalu: &Nalu) -> Result<&Sps, String> { + if !matches!(nalu.header.type_, NaluType::SpsNut) { + return Err(format!( + "Invalid NALU type, expected {:?}, got {:?}", + NaluType::SpsNut, + nalu.header.type_ + )); + } + + let data = nalu.as_ref(); + let header = &nalu.header; + let hdr_len = header.len(); + // Skip the header + let mut r = BitReader::new(&data[hdr_len..], true); + + let video_parameter_set_id = r.read_bits(4)?; + + // A non-existing VPS means the SPS is not using any VPS. + let vps = self.get_vps(video_parameter_set_id).cloned(); + + let mut sps = Sps { + video_parameter_set_id, + max_sub_layers_minus1: r.read_bits(3)?, + temporal_id_nesting_flag: r.read_bit()?, + vps, + ..Default::default() + }; + + Self::parse_profile_tier_level(&mut sps.profile_tier_level, &mut r, true, sps.max_sub_layers_minus1)?; + + sps.seq_parameter_set_id = r.read_ue_max(MAX_SPS_COUNT as u32 - 1)?; + sps.chroma_format_idc = r.read_ue_max(3)?; + + if sps.chroma_format_idc == 3 { + sps.separate_colour_plane_flag = r.read_bit()?; + } + + sps.chroma_array_type = if sps.separate_colour_plane_flag { + 0 + } else { + sps.chroma_format_idc + }; + + sps.pic_width_in_luma_samples = r.read_ue_bounded(1, 16888)?; + sps.pic_height_in_luma_samples = r.read_ue_bounded(1, 16888)?; + + sps.conformance_window_flag = r.read_bit()?; + if sps.conformance_window_flag { + sps.conf_win_left_offset = r.read_ue()?; + sps.conf_win_right_offset = r.read_ue()?; + sps.conf_win_top_offset = r.read_ue()?; + sps.conf_win_bottom_offset = r.read_ue()?; + } + + sps.bit_depth_luma_minus8 = r.read_ue_max(6)?; + sps.bit_depth_chroma_minus8 = r.read_ue_max(6)?; + sps.log2_max_pic_order_cnt_lsb_minus4 = r.read_ue_max(12)?; + sps.sub_layer_ordering_info_present_flag = r.read_bit()?; + + { + let i = if sps.sub_layer_ordering_info_present_flag { + 0 + } else { + sps.max_sub_layers_minus1 + }; + + for j in i..=sps.max_sub_layers_minus1 { + sps.max_dec_pic_buffering_minus1[j as usize] = r.read_ue_max(16)?; + sps.max_num_reorder_pics[j as usize] = + r.read_ue_max(sps.max_dec_pic_buffering_minus1[j as usize] as _)?; + sps.max_latency_increase_plus1[j as usize] = r.read_ue_max(u32::MAX - 1)?; + } + } + + sps.log2_min_luma_coding_block_size_minus3 = r.read_ue_max(3)?; + sps.log2_diff_max_min_luma_coding_block_size = r.read_ue_max(6)?; + sps.log2_min_luma_transform_block_size_minus2 = r.read_ue_max(3)?; + sps.log2_diff_max_min_luma_transform_block_size = r.read_ue_max(3)?; + + // (7-10) + sps.min_cb_log2_size_y = u32::from(sps.log2_min_luma_coding_block_size_minus3 + 3); + // (7-11) + sps.ctb_log2_size_y = sps.min_cb_log2_size_y + u32::from(sps.log2_diff_max_min_luma_coding_block_size); + // (7-12) + sps.ctb_size_y = 1 << sps.ctb_log2_size_y; + // (7-17) + sps.pic_height_in_ctbs_y = (sps.pic_height_in_luma_samples as f64 / sps.ctb_size_y as f64).ceil() as u32; + // (7-15) + sps.pic_width_in_ctbs_y = (sps.pic_width_in_luma_samples as f64 / sps.ctb_size_y as f64).ceil() as u32; + + sps.max_tb_log2_size_y = u32::from( + sps.log2_min_luma_transform_block_size_minus2 + 2 + sps.log2_diff_max_min_luma_transform_block_size, + ); + + sps.pic_size_in_samples_y = + u32::from(sps.pic_width_in_luma_samples) * u32::from(sps.pic_height_in_luma_samples); + + if sps.max_tb_log2_size_y > std::cmp::min(sps.ctb_log2_size_y, 5) { + return Err(format!("Invalid value for MaxTbLog2SizeY: {}", sps.max_tb_log2_size_y)); + } + + sps.pic_size_in_ctbs_y = sps.pic_width_in_ctbs_y * sps.pic_height_in_ctbs_y; + + sps.max_transform_hierarchy_depth_inter = r.read_ue_max(4)?; + sps.max_transform_hierarchy_depth_intra = r.read_ue_max(4)?; + + sps.scaling_list_enabled_flag = r.read_bit()?; + if sps.scaling_list_enabled_flag { + sps.scaling_list_data_present_flag = r.read_bit()?; + if sps.scaling_list_data_present_flag { + Self::parse_scaling_list_data(&mut sps.scaling_list, &mut r)?; + } + } + + sps.amp_enabled_flag = r.read_bit()?; + sps.sample_adaptive_offset_enabled_flag = r.read_bit()?; + + sps.pcm_enabled_flag = r.read_bit()?; + if sps.pcm_enabled_flag { + sps.pcm_sample_bit_depth_luma_minus1 = r.read_bits(4)?; + sps.pcm_sample_bit_depth_chroma_minus1 = r.read_bits(4)?; + sps.log2_min_pcm_luma_coding_block_size_minus3 = r.read_ue_max(2)?; + sps.log2_diff_max_min_pcm_luma_coding_block_size = r.read_ue_max(2)?; + sps.pcm_loop_filter_disabled_flag = r.read_bit()?; + } + + sps.num_short_term_ref_pic_sets = r.read_ue_max(64)?; + + for i in 0..sps.num_short_term_ref_pic_sets { + let mut st = ShortTermRefPicSet::default(); + Self::parse_short_term_ref_pic_set(&sps, &mut st, &mut r, i)?; + sps.short_term_ref_pic_set.push(st); + } + + sps.long_term_ref_pics_present_flag = r.read_bit()?; + if sps.long_term_ref_pics_present_flag { + sps.num_long_term_ref_pics_sps = r.read_ue_max(32)?; + for i in 0..usize::from(sps.num_long_term_ref_pics_sps) { + sps.lt_ref_pic_poc_lsb_sps[i] = r.read_bits(usize::from(sps.log2_max_pic_order_cnt_lsb_minus4) + 4)?; + sps.used_by_curr_pic_lt_sps_flag[i] = r.read_bit()?; + } + } + + sps.temporal_mvp_enabled_flag = r.read_bit()?; + sps.strong_intra_smoothing_enabled_flag = r.read_bit()?; + + sps.vui_parameters_present_flag = r.read_bit()?; + if sps.vui_parameters_present_flag { + Self::parse_vui_parameters(&mut sps, &mut r)?; + } + + sps.extension_present_flag = r.read_bit()?; + if sps.extension_present_flag { + sps.range_extension_flag = r.read_bit()?; + if sps.range_extension_flag { + Self::parse_sps_range_extension(&mut sps, &mut r)?; + } + + let multilayer_extension_flag = r.read_bit()?; + if multilayer_extension_flag { + return Err("Multilayer extension not supported.".into()); + } + + let three_d_extension_flag = r.read_bit()?; + if three_d_extension_flag { + return Err("3D extension not supported.".into()); + } + + sps.scc_extension_flag = r.read_bit()?; + if sps.scc_extension_flag { + Self::parse_sps_scc_extension(&mut sps, &mut r)?; + } + } + + let shift = if sps.range_extension.high_precision_offsets_enabled_flag { + sps.bit_depth_luma_minus8 + 7 + } else { + 7 + }; + + sps.wp_offset_half_range_y = 1 << shift; + + let shift = if sps.range_extension.high_precision_offsets_enabled_flag { + sps.bit_depth_chroma_minus8 + 7 + } else { + 7 + }; + + sps.wp_offset_half_range_c = 1 << shift; + + log::debug!( + "Parsed SPS({}), resolution: ({}, {}): NAL size was {}", + sps.seq_parameter_set_id, + sps.width(), + sps.height(), + nalu.size + ); + + if self.active_spses.keys().len() >= MAX_SPS_COUNT { + return Err("Broken data: Number of active SPSs > MAX_SPS_COUNT".into()); + } + + let key = sps.seq_parameter_set_id; + let sps = Rc::new(sps); + self.active_spses.remove(&key); + Ok(self.active_spses.entry(key).or_insert(sps)) + } + + fn parse_pps_scc_extension(pps: &mut Pps, sps: &Sps, r: &mut BitReader) -> Result<(), String> { + let scc = &mut pps.scc_extension; + scc.curr_pic_ref_enabled_flag = r.read_bit()?; + scc.residual_adaptive_colour_transform_enabled_flag = r.read_bit()?; + if scc.residual_adaptive_colour_transform_enabled_flag { + scc.slice_act_qp_offsets_present_flag = r.read_bit()?; + scc.act_y_qp_offset_plus5 = r.read_se_bounded(-7, 17)?; + scc.act_cb_qp_offset_plus5 = r.read_se_bounded(-7, 17)?; + scc.act_cr_qp_offset_plus3 = r.read_se_bounded(-9, 15)?; + } + + scc.palette_predictor_initializers_present_flag = r.read_bit()?; + if scc.palette_predictor_initializers_present_flag { + let max = sps.scc_extension.palette_max_size + sps.scc_extension.delta_palette_max_predictor_size; + scc.num_palette_predictor_initializers = r.read_ue_max(max.into())?; + if scc.num_palette_predictor_initializers > 0 { + scc.monochrome_palette_flag = r.read_bit()?; + scc.luma_bit_depth_entry_minus8 = + r.read_ue_bounded(sps.bit_depth_luma_minus8.into(), sps.bit_depth_luma_minus8.into())?; + if !scc.monochrome_palette_flag { + scc.chroma_bit_depth_entry_minus8 = + r.read_ue_bounded(sps.bit_depth_chroma_minus8.into(), sps.bit_depth_chroma_minus8.into())?; + } + + let num_comps = if scc.monochrome_palette_flag { 1 } else { 3 }; + for comp in 0..num_comps { + let num_bits = if comp == 0 { + scc.luma_bit_depth_entry_minus8 + 8 + } else { + scc.chroma_bit_depth_entry_minus8 + 8 + }; + for i in 0..usize::from(scc.num_palette_predictor_initializers) { + scc.palette_predictor_initializer[comp][i] = r.read_bits(num_bits.into())?; + } + } + } + } + Ok(()) + } + + fn parse_pps_range_extension(pps: &mut Pps, sps: &Sps, r: &mut BitReader) -> Result<(), String> { + let rext = &mut pps.range_extension; + + if pps.transform_skip_enabled_flag { + rext.log2_max_transform_skip_block_size_minus2 = r.read_ue_max(sps.max_tb_log2_size_y - 2)?; + } + + rext.cross_component_prediction_enabled_flag = r.read_bit()?; + rext.chroma_qp_offset_list_enabled_flag = r.read_bit()?; + if rext.chroma_qp_offset_list_enabled_flag { + rext.diff_cu_chroma_qp_offset_depth = r.read_ue()?; + rext.chroma_qp_offset_list_len_minus1 = r.read_ue_max(5)?; + for i in 0..=rext.chroma_qp_offset_list_len_minus1 as usize { + rext.cb_qp_offset_list[i] = r.read_se_bounded(-12, 12)?; + rext.cr_qp_offset_list[i] = r.read_se_bounded(-12, 12)?; + } + } + + let bit_depth_y = sps.bit_depth_luma_minus8 + 8; + let max = u32::from(std::cmp::max(0, bit_depth_y - 10)); + + rext.log2_sao_offset_scale_luma = r.read_ue_max(max)?; + rext.log2_sao_offset_scale_chroma = r.read_ue_max(max)?; + + Ok(()) + } + + /// Parse a PPS NALU. + pub fn parse_pps(&mut self, nalu: &Nalu) -> Result<&Pps, String> { + if !matches!(nalu.header.type_, NaluType::PpsNut) { + return Err(format!( + "Invalid NALU type, expected {:?}, got {:?}", + NaluType::PpsNut, + nalu.header.type_ + )); + } + + let data = nalu.as_ref(); + let header = &nalu.header; + let hdr_len = header.len(); + // Skip the header + let mut r = BitReader::new(&data[hdr_len..], true); + + let pic_parameter_set_id = r.read_ue_max(MAX_PPS_COUNT as u32 - 1)?; + let seq_parameter_set_id = r.read_ue_max(MAX_SPS_COUNT as u32 - 1)?; + + let sps = self.get_sps(seq_parameter_set_id).ok_or::(format!( + "Could not get SPS for seq_parameter_set_id {}", + seq_parameter_set_id + ))?; + + let mut pps = Pps { + pic_parameter_set_id, + seq_parameter_set_id, + dependent_slice_segments_enabled_flag: Default::default(), + output_flag_present_flag: Default::default(), + num_extra_slice_header_bits: Default::default(), + sign_data_hiding_enabled_flag: Default::default(), + cabac_init_present_flag: Default::default(), + num_ref_idx_l0_default_active_minus1: Default::default(), + num_ref_idx_l1_default_active_minus1: Default::default(), + init_qp_minus26: Default::default(), + constrained_intra_pred_flag: Default::default(), + transform_skip_enabled_flag: Default::default(), + cu_qp_delta_enabled_flag: Default::default(), + diff_cu_qp_delta_depth: Default::default(), + cb_qp_offset: Default::default(), + cr_qp_offset: Default::default(), + slice_chroma_qp_offsets_present_flag: Default::default(), + weighted_pred_flag: Default::default(), + weighted_bipred_flag: Default::default(), + transquant_bypass_enabled_flag: Default::default(), + tiles_enabled_flag: Default::default(), + entropy_coding_sync_enabled_flag: Default::default(), + num_tile_columns_minus1: Default::default(), + num_tile_rows_minus1: Default::default(), + uniform_spacing_flag: true, + column_width_minus1: Default::default(), + row_height_minus1: Default::default(), + loop_filter_across_tiles_enabled_flag: true, + loop_filter_across_slices_enabled_flag: Default::default(), + deblocking_filter_control_present_flag: Default::default(), + deblocking_filter_override_enabled_flag: Default::default(), + deblocking_filter_disabled_flag: Default::default(), + beta_offset_div2: Default::default(), + tc_offset_div2: Default::default(), + scaling_list_data_present_flag: Default::default(), + scaling_list: Default::default(), + lists_modification_present_flag: Default::default(), + log2_parallel_merge_level_minus2: Default::default(), + slice_segment_header_extension_present_flag: Default::default(), + extension_present_flag: Default::default(), + range_extension_flag: Default::default(), + range_extension: Default::default(), + qp_bd_offset_y: Default::default(), + scc_extension: Default::default(), + scc_extension_flag: Default::default(), + sps: Rc::clone(sps), + }; + + pps.dependent_slice_segments_enabled_flag = r.read_bit()?; + pps.output_flag_present_flag = r.read_bit()?; + pps.num_extra_slice_header_bits = r.read_bits(3)?; + pps.sign_data_hiding_enabled_flag = r.read_bit()?; + pps.cabac_init_present_flag = r.read_bit()?; + + // 7.4.7.1 + pps.num_ref_idx_l0_default_active_minus1 = r.read_ue_max(14)?; + pps.num_ref_idx_l1_default_active_minus1 = r.read_ue_max(14)?; + + // (7-5) + let qp_bd_offset_y = 6 * i32::from(sps.bit_depth_luma_minus8); + + pps.init_qp_minus26 = r.read_se_bounded(-(26 + qp_bd_offset_y), 25)?; + pps.qp_bd_offset_y = qp_bd_offset_y as u32; + pps.constrained_intra_pred_flag = r.read_bit()?; + pps.transform_skip_enabled_flag = r.read_bit()?; + pps.cu_qp_delta_enabled_flag = r.read_bit()?; + + if pps.cu_qp_delta_enabled_flag { + pps.diff_cu_qp_delta_depth = r.read_ue_max(u32::from(sps.log2_diff_max_min_luma_coding_block_size))?; + } + + pps.cb_qp_offset = r.read_se_bounded(-12, 12)?; + pps.cr_qp_offset = r.read_se_bounded(-12, 12)?; + + pps.slice_chroma_qp_offsets_present_flag = r.read_bit()?; + pps.weighted_pred_flag = r.read_bit()?; + pps.weighted_bipred_flag = r.read_bit()?; + pps.transquant_bypass_enabled_flag = r.read_bit()?; + pps.tiles_enabled_flag = r.read_bit()?; + pps.entropy_coding_sync_enabled_flag = r.read_bit()?; + + // A mix of the rbsp data and the algorithm in 6.5.1 + if pps.tiles_enabled_flag { + pps.num_tile_columns_minus1 = r.read_ue_max(sps.pic_width_in_ctbs_y - 1)?; + pps.num_tile_rows_minus1 = r.read_ue_max(sps.pic_height_in_ctbs_y - 1)?; + if usize::from(pps.num_tile_columns_minus1) >= MAX_TILE_COLUMNS + || usize::from(pps.num_tile_rows_minus1) >= MAX_TILE_ROWS + { + return Err(format!( + "{}x{} tiles, more than any level allows", + u32::from(pps.num_tile_columns_minus1) + 1, + u32::from(pps.num_tile_rows_minus1) + 1 + )); + } + pps.uniform_spacing_flag = r.read_bit()?; + if !pps.uniform_spacing_flag { + pps.column_width_minus1[usize::from(pps.num_tile_columns_minus1)] = sps.pic_width_in_ctbs_y - 1; + + for i in 0..usize::from(pps.num_tile_columns_minus1) { + pps.column_width_minus1[i] = + r.read_ue_max(pps.column_width_minus1[usize::from(pps.num_tile_columns_minus1)] - 1)?; + pps.column_width_minus1[usize::from(pps.num_tile_columns_minus1)] -= pps.column_width_minus1[i] + 1; + } + + pps.row_height_minus1[usize::from(pps.num_tile_rows_minus1)] = sps.pic_height_in_ctbs_y - 1; + + for i in 0..usize::from(pps.num_tile_rows_minus1) { + pps.row_height_minus1[i] = + r.read_ue_max(pps.row_height_minus1[usize::from(pps.num_tile_rows_minus1)] - 1)?; + pps.row_height_minus1[usize::from(pps.num_tile_rows_minus1)] -= pps.row_height_minus1[i] + 1; + } + } else { + let nrows = u32::from(pps.num_tile_rows_minus1) + 1; + let ncols = u32::from(pps.num_tile_columns_minus1) + 1; + + for j in 0..ncols { + pps.column_width_minus1[j as usize] = + ((j + 1) * sps.pic_width_in_ctbs_y) / ncols - j * sps.pic_width_in_ctbs_y / ncols - 1; + } + + for j in 0..nrows { + pps.row_height_minus1[j as usize] = + ((j + 1) * sps.pic_height_in_ctbs_y) / nrows - j * sps.pic_height_in_ctbs_y / nrows - 1; + } + } + + pps.loop_filter_across_tiles_enabled_flag = r.read_bit()?; + } + + pps.loop_filter_across_slices_enabled_flag = r.read_bit()?; + pps.deblocking_filter_control_present_flag = r.read_bit()?; + + if pps.deblocking_filter_control_present_flag { + pps.deblocking_filter_override_enabled_flag = r.read_bit()?; + pps.deblocking_filter_disabled_flag = r.read_bit()?; + if !pps.deblocking_filter_disabled_flag { + pps.beta_offset_div2 = r.read_se_bounded(-6, 6)?; + pps.tc_offset_div2 = r.read_se_bounded(-6, 6)?; + } + } + + pps.scaling_list_data_present_flag = r.read_bit()?; + + if pps.scaling_list_data_present_flag { + Self::parse_scaling_list_data(&mut pps.scaling_list, &mut r)?; + } else { + for size_id in 0..4 { + let mut matrix_id = 0; + while matrix_id < 6 { + Self::fill_default_scaling_list(&mut pps.scaling_list, size_id, matrix_id); + let step = if size_id == 3 { 3 } else { 1 }; + matrix_id += step; + } + } + } + + pps.lists_modification_present_flag = r.read_bit()?; + pps.log2_parallel_merge_level_minus2 = r.read_ue_max(sps.ctb_log2_size_y - 2)?; + pps.slice_segment_header_extension_present_flag = r.read_bit()?; + + pps.extension_present_flag = r.read_bit()?; + if pps.extension_present_flag { + pps.range_extension_flag = r.read_bit()?; + + if pps.range_extension_flag { + Self::parse_pps_range_extension(&mut pps, sps, &mut r)?; + } + + let multilayer_extension_flag = r.read_bit()?; + if multilayer_extension_flag { + return Err("Multilayer extension is not supported".into()); + } + + let three_d_extension_flag = r.read_bit()?; + if three_d_extension_flag { + return Err("3D extension is not supported".into()); + } + + pps.scc_extension_flag = r.read_bit()?; + if pps.scc_extension_flag { + Self::parse_pps_scc_extension(&mut pps, sps, &mut r)?; + } + + r.skip_bits(4)?; // pps_extension_4bits + } + + log::debug!("Parsed PPS({}), NAL size was {}", pps.pic_parameter_set_id, nalu.size); + + if self.active_ppses.keys().len() >= MAX_PPS_COUNT { + return Err("Broken Data: number of active PPSs > MAX_PPS_COUNT".into()); + } + + let key = pps.pic_parameter_set_id; + let pps = Rc::new(pps); + self.active_ppses.remove(&key); + Ok(self.active_ppses.entry(key).or_insert(pps)) + } + + fn parse_pred_weight_table(hdr: &mut SliceHeader, r: &mut BitReader, sps: &Sps) -> Result<(), String> { + let pwt = &mut hdr.pred_weight_table; + + pwt.luma_log2_weight_denom = r.read_ue_max(7)?; + if sps.chroma_array_type != 0 { + pwt.delta_chroma_log2_weight_denom = r.read_se()?; + pwt.chroma_log2_weight_denom = (pwt.luma_log2_weight_denom as i32 + + pwt.delta_chroma_log2_weight_denom as i32) + .try_into() + .map_err(|_| String::from("Integer overflow on chroma_log2_weight_denom calculation"))?; + } + + for i in 0..=usize::from(hdr.num_ref_idx_l0_active_minus1) { + pwt.luma_weight_l0_flag[i] = r.read_bit()?; + } + + if sps.chroma_array_type != 0 { + for i in 0..=usize::from(hdr.num_ref_idx_l0_active_minus1) { + pwt.chroma_weight_l0_flag[i] = r.read_bit()?; + } + } + + for i in 0..=usize::from(hdr.num_ref_idx_l0_active_minus1) { + if pwt.luma_weight_l0_flag[i] { + pwt.delta_luma_weight_l0[i] = r.read_se_bounded(-128, 127)?; + pwt.luma_offset_l0[i] = r.read_se_bounded(-128, 127)?; + } + + if pwt.chroma_weight_l0_flag[i] { + for j in 0..2 { + pwt.delta_chroma_weight_l0[i][j] = r.read_se_bounded(-128, 127)?; + pwt.delta_chroma_offset_l0[i][j] = r.read_se_bounded( + -4 * sps.wp_offset_half_range_c as i32, + 4 * sps.wp_offset_half_range_c as i32 - 1, + )?; + } + } + } + + if hdr.type_.is_b() { + for i in 0..=usize::from(hdr.num_ref_idx_l1_active_minus1) { + pwt.luma_weight_l1_flag[i] = r.read_bit()?; + } + + if sps.chroma_format_idc != 0 { + for i in 0..=usize::from(hdr.num_ref_idx_l1_active_minus1) { + pwt.chroma_weight_l1_flag[i] = r.read_bit()?; + } + } + + for i in 0..=usize::from(hdr.num_ref_idx_l1_active_minus1) { + if pwt.luma_weight_l1_flag[i] { + pwt.delta_luma_weight_l1[i] = r.read_se_bounded(-128, 127)?; + pwt.luma_offset_l1[i] = r.read_se_bounded(-128, 127)?; + } + + if pwt.chroma_weight_l1_flag[i] { + for j in 0..2 { + pwt.delta_chroma_weight_l1[i][j] = r.read_se_bounded(-128, 127)?; + pwt.delta_chroma_offset_l1[i][j] = r.read_se_bounded( + -4 * sps.wp_offset_half_range_c as i32, + 4 * sps.wp_offset_half_range_c as i32 - 1, + )?; + } + } + } + } + + Ok(()) + } + + fn parse_ref_pic_lists_modification(hdr: &mut SliceHeader, r: &mut BitReader) -> Result<(), String> { + let rplm = &mut hdr.ref_pic_list_modification; + + rplm.ref_pic_list_modification_flag_l0 = r.read_bit()?; + if rplm.ref_pic_list_modification_flag_l0 { + for _ in 0..=hdr.num_ref_idx_l0_active_minus1 { + let num_bits = (hdr.num_pic_total_curr as f64).log2().ceil() as _; + + let entry = r.read_bits(num_bits)?; + + if entry > hdr.num_pic_total_curr - 1 { + return Err(format!( + "Invalid list_entry_l0 {}, expected at max NumPicTotalCurr - 1: {}", + entry, + hdr.num_pic_total_curr - 1 + )); + } + + rplm.list_entry_l0.push(entry); + } + } + + if hdr.type_.is_b() { + rplm.ref_pic_list_modification_flag_l1 = r.read_bit()?; + if rplm.ref_pic_list_modification_flag_l1 { + for _ in 0..=hdr.num_ref_idx_l1_active_minus1 { + let num_bits = (hdr.num_pic_total_curr as f64).log2().ceil() as _; + + let entry = r.read_bits(num_bits)?; + + if entry > hdr.num_pic_total_curr - 1 { + return Err(format!( + "Invalid list_entry_l1 {}, expected at max NumPicTotalCurr - 1: {}", + entry, + hdr.num_pic_total_curr - 1 + )); + } + + rplm.list_entry_l1.push(entry); + } + } + } + + Ok(()) + } + + /// Further sets default values given `sps` and `pps`. + pub fn slice_header_set_defaults(hdr: &mut SliceHeader, sps: &Sps, pps: &Pps) { + // Set some defaults that can't be defined in Default::default(). + hdr.deblocking_filter_disabled_flag = pps.deblocking_filter_disabled_flag; + hdr.beta_offset_div2 = pps.beta_offset_div2; + hdr.tc_offset_div2 = pps.tc_offset_div2; + hdr.loop_filter_across_slices_enabled_flag = pps.loop_filter_across_slices_enabled_flag; + hdr.curr_rps_idx = sps.num_short_term_ref_pic_sets; + hdr.use_integer_mv_flag = sps.scc_extension.motion_vector_resolution_control_idc != 0; + } + + /// Parses a slice header from a slice NALU. + pub fn parse_slice_header<'a>(&mut self, nalu: Nalu<'a>) -> Result, String> { + if !matches!( + nalu.header.type_, + NaluType::TrailN + | NaluType::TrailR + | NaluType::TsaN + | NaluType::TsaR + | NaluType::StsaN + | NaluType::StsaR + | NaluType::RadlN + | NaluType::RadlR + | NaluType::RaslN + | NaluType::RaslR + | NaluType::BlaWLp + | NaluType::BlaWRadl + | NaluType::BlaNLp + | NaluType::IdrWRadl + | NaluType::IdrNLp + | NaluType::CraNut, + ) { + return Err(format!( + "Invalid NALU type: {:?} is not a slice NALU", + nalu.header.type_ + )); + } + + let data = nalu.as_ref(); + let nalu_header = &nalu.header; + let hdr_len = nalu_header.len(); + // Skip the header + let mut r = BitReader::new(&data[hdr_len..], true); + + let mut hdr = SliceHeader { + first_slice_segment_in_pic_flag: r.read_bit()?, + ..Default::default() + }; + + if nalu.header.type_.is_irap() { + hdr.no_output_of_prior_pics_flag = r.read_bit()?; + } + + hdr.pic_parameter_set_id = r.read_ue_max(63)?; + + let pps = self.get_pps(hdr.pic_parameter_set_id).ok_or::(format!( + "Could not get PPS for pic_parameter_set_id {}", + hdr.pic_parameter_set_id + ))?; + + let sps = &pps.sps; + + Self::slice_header_set_defaults(&mut hdr, sps, pps); + + if !hdr.first_slice_segment_in_pic_flag { + if pps.dependent_slice_segments_enabled_flag { + hdr.dependent_slice_segment_flag = r.read_bit()?; + } + + let num_bits = (sps.pic_size_in_ctbs_y as f64).log2().ceil() as _; + hdr.segment_address = r.read_bits(num_bits)?; + + if hdr.segment_address > sps.pic_size_in_ctbs_y - 1 { + return Err(format!("Invalid slice_segment_address {}", hdr.segment_address)); + } + } + + if !hdr.dependent_slice_segment_flag { + r.skip_bits(usize::from(pps.num_extra_slice_header_bits))?; + + let slice_type: u32 = r.read_ue()?; + hdr.type_ = SliceType::try_from(slice_type)?; + + if pps.output_flag_present_flag { + hdr.pic_output_flag = r.read_bit()?; + } + + if sps.separate_colour_plane_flag { + hdr.colour_plane_id = r.read_bits(2)?; + } + + if !matches!(nalu_header.type_, NaluType::IdrWRadl | NaluType::IdrNLp) { + let num_bits = usize::from(sps.log2_max_pic_order_cnt_lsb_minus4 + 4); + hdr.pic_order_cnt_lsb = r.read_bits(num_bits)?; + + if u32::from(hdr.pic_order_cnt_lsb) > 2u32.pow(u32::from(sps.log2_max_pic_order_cnt_lsb_minus4 + 4)) { + return Err(format!("Invalid pic_order_cnt_lsb {}", hdr.pic_order_cnt_lsb)); + } + + hdr.short_term_ref_pic_set_sps_flag = r.read_bit()?; + + if !hdr.short_term_ref_pic_set_sps_flag { + let epb_before = r.num_epb(); + let bits_left_before = r.num_bits_left(); + + let st_rps_idx = sps.num_short_term_ref_pic_sets; + + Self::parse_short_term_ref_pic_set(sps, &mut hdr.short_term_ref_pic_set, &mut r, st_rps_idx)?; + + hdr.st_rps_bits = ((bits_left_before - r.num_bits_left()) - 8 * (r.num_epb() - epb_before)) as u32; + } else if sps.num_short_term_ref_pic_sets > 1 { + let num_bits = (sps.num_short_term_ref_pic_sets as f64).log2().ceil() as _; + hdr.short_term_ref_pic_set_idx = r.read_bits(num_bits)?; + + if hdr.short_term_ref_pic_set_idx > sps.num_short_term_ref_pic_sets - 1 { + return Err(format!( + "Invalid short_term_ref_pic_set_idx {}", + hdr.short_term_ref_pic_set_idx + )); + } + } + + if hdr.short_term_ref_pic_set_sps_flag { + hdr.curr_rps_idx = hdr.short_term_ref_pic_set_idx; + } + + if sps.long_term_ref_pics_present_flag { + if sps.num_long_term_ref_pics_sps > 0 { + hdr.num_long_term_sps = r.read_ue_max(u32::from(sps.num_long_term_ref_pics_sps))?; + } + + hdr.num_long_term_pics = + r.read_ue_max(MAX_LONG_TERM_REF_PIC_SETS as u32 - u32::from(hdr.num_long_term_sps))?; + + let num_lt = hdr.num_long_term_sps + hdr.num_long_term_pics; + for i in 0..usize::from(num_lt) { + // The variables `PocLsbLt[ i ]` and `UsedByCurrPicLt[ i ]` are derived as follows: + // + // – If i is less than num_long_term_sps, `PocLsbLt[ i ]` is set equal to + // lt_ref_pic_poc_lsb_sps[ `lt_idx_sps[ i ]` ] and `UsedByCurrPicLt[ i ]` is set equal + // to used_by_curr_pic_lt_sps_flag[ `lt_idx_sps[ i ]` ]. + // + // – Otherwise, `PocLsbLt[ i ]` + // is set equal to `poc_lsb_lt[ i ]` and `UsedByCurrPicLt[ i ]` is set equal to + // `used_by_curr_pic_lt_flag[ i ]`. + if i < usize::from(hdr.num_long_term_sps) { + if sps.num_long_term_ref_pics_sps > 1 { + let num_bits = (sps.num_long_term_ref_pics_sps as f64).log2().ceil() as _; + + hdr.lt_idx_sps[i] = r.read_bits(num_bits)?; + + if hdr.lt_idx_sps[i] > sps.num_long_term_ref_pics_sps - 1 { + return Err(format!("Invalid lt_idx_sps[{}] {}", i, hdr.lt_idx_sps[i])); + } + } + + hdr.poc_lsb_lt[i] = sps.lt_ref_pic_poc_lsb_sps[usize::from(hdr.lt_idx_sps[i])]; + hdr.used_by_curr_pic_lt[i] = + sps.used_by_curr_pic_lt_sps_flag[usize::from(hdr.lt_idx_sps[i])]; + } else { + let num_bits = usize::from(sps.log2_max_pic_order_cnt_lsb_minus4) + 4; + hdr.poc_lsb_lt[i] = r.read_bits(num_bits)?; + hdr.used_by_curr_pic_lt[i] = r.read_bit()?; + } + + hdr.delta_poc_msb_present_flag[i] = r.read_bit()?; + if hdr.delta_poc_msb_present_flag[i] { + // The value of `delta_poc_msb_cycle_lt[ i ]` shall be + // in the range of 0 to 2(32 − + // log2_max_pic_order_cnt_lsb_minus4 − 4 ), + // inclusive. When `delta_poc_msb_cycle_lt[ i ]` is + // not present, it is inferred to be equal to 0. + let max = 2u32.pow(32 - u32::from(sps.log2_max_pic_order_cnt_lsb_minus4) - 4); + hdr.delta_poc_msb_cycle_lt[i] = r.read_ue_max(max)?; + } + // Equation 7-52 (simplified) + if i != 0 && i != usize::from(hdr.num_long_term_sps) { + hdr.delta_poc_msb_cycle_lt[i] += hdr.delta_poc_msb_cycle_lt[i - 1]; + } + } + } + + if sps.temporal_mvp_enabled_flag { + hdr.temporal_mvp_enabled_flag = r.read_bit()?; + } + } + + if sps.sample_adaptive_offset_enabled_flag { + hdr.sao_luma_flag = r.read_bit()?; + if sps.chroma_array_type != 0 { + hdr.sao_chroma_flag = r.read_bit()?; + } + } + + if hdr.type_.is_p() || hdr.type_.is_b() { + hdr.num_ref_idx_active_override_flag = r.read_bit()?; + if hdr.num_ref_idx_active_override_flag { + hdr.num_ref_idx_l0_active_minus1 = r.read_ue_max(MAX_REF_IDX_ACTIVE - 1)?; + if hdr.type_.is_b() { + hdr.num_ref_idx_l1_active_minus1 = r.read_ue_max(MAX_REF_IDX_ACTIVE - 1)?; + } + } else { + hdr.num_ref_idx_l0_active_minus1 = pps.num_ref_idx_l0_default_active_minus1; + hdr.num_ref_idx_l1_active_minus1 = pps.num_ref_idx_l1_default_active_minus1; + } + + // 7-57 + let mut num_pic_total_curr = 0; + let rps = if hdr.short_term_ref_pic_set_sps_flag { + sps.short_term_ref_pic_set + .get(usize::from(hdr.curr_rps_idx)) + .ok_or::("Invalid RPS".into())? + } else { + &hdr.short_term_ref_pic_set + }; + + for i in 0..usize::from(rps.num_negative_pics) { + if rps.used_by_curr_pic_s0[i] { + num_pic_total_curr += 1; + } + } + + for i in 0..usize::from(rps.num_positive_pics) { + if rps.used_by_curr_pic_s1[i] { + num_pic_total_curr += 1; + } + } + + for i in 0..usize::from(hdr.num_long_term_sps + hdr.num_long_term_pics) { + if hdr.used_by_curr_pic_lt[i] { + num_pic_total_curr += 1; + } + } + + if pps.scc_extension.curr_pic_ref_enabled_flag { + num_pic_total_curr += 1; + } + + hdr.num_pic_total_curr = num_pic_total_curr; + + if pps.lists_modification_present_flag && hdr.num_pic_total_curr > 1 { + Self::parse_ref_pic_lists_modification(&mut hdr, &mut r)?; + } + + if hdr.type_.is_b() { + hdr.mvd_l1_zero_flag = r.read_bit()?; + } + + if pps.cabac_init_present_flag { + hdr.cabac_init_flag = r.read_bit()?; + } + + if hdr.temporal_mvp_enabled_flag { + if hdr.type_.is_b() { + hdr.collocated_from_l0_flag = r.read_bit()?; + } + + if (hdr.collocated_from_l0_flag && hdr.num_ref_idx_l0_active_minus1 > 0) + || (!hdr.collocated_from_l0_flag && hdr.num_ref_idx_l1_active_minus1 > 0) + { + let max = if (hdr.type_.is_p() || hdr.type_.is_b()) && hdr.collocated_from_l0_flag { + hdr.num_ref_idx_l0_active_minus1 + } else if hdr.type_.is_b() && !hdr.collocated_from_l0_flag { + hdr.num_ref_idx_l1_active_minus1 + } else { + return Err("Invalid value for collocated_ref_idx".into()); + }; + + { + hdr.collocated_ref_idx = r.read_ue_max(u32::from(max))?; + } + } + } + + if (pps.weighted_pred_flag && hdr.type_.is_p()) || (pps.weighted_bipred_flag && hdr.type_.is_b()) { + Self::parse_pred_weight_table(&mut hdr, &mut r, sps)?; + } + + hdr.five_minus_max_num_merge_cand = r.read_ue()?; + + if sps.scc_extension.motion_vector_resolution_control_idc == 2 { + hdr.use_integer_mv_flag = r.read_bit()?; + } + } + + hdr.qp_delta = r.read_se_bounded(-87, 77)?; + + let slice_qp_y = (26 + pps.init_qp_minus26 + hdr.qp_delta) as i32; + if slice_qp_y < -(pps.qp_bd_offset_y as i32) || slice_qp_y > 51 { + return Err(format!("Invalid slice_qp_delta: {}", hdr.qp_delta)); + } + + if pps.slice_chroma_qp_offsets_present_flag { + hdr.cb_qp_offset = r.read_se_bounded(-12, 12)?; + + let qp_offset = pps.cb_qp_offset + hdr.cb_qp_offset; + if !(-12..=12).contains(&qp_offset) { + return Err(format!("Invalid value for slice_cb_qp_offset: {}", hdr.cb_qp_offset)); + } + + hdr.cr_qp_offset = r.read_se_bounded(-12, 12)?; + + let qp_offset = pps.cr_qp_offset + hdr.cr_qp_offset; + if !(-12..=12).contains(&qp_offset) { + return Err(format!("Invalid value for slice_cr_qp_offset: {}", hdr.cr_qp_offset)); + } + } + + if pps.scc_extension.slice_act_qp_offsets_present_flag { + hdr.slice_act_y_qp_offset = r.read_se_bounded(-12, 12)?; + hdr.slice_act_cb_qp_offset = r.read_se_bounded(-12, 12)?; + hdr.slice_act_cr_qp_offset = r.read_se_bounded(-12, 12)?; + } + + if pps.range_extension.chroma_qp_offset_list_enabled_flag { + hdr.cu_chroma_qp_offset_enabled_flag = r.read_bit()?; + } + + if pps.deblocking_filter_override_enabled_flag { + hdr.deblocking_filter_override_flag = r.read_bit()?; + } + + if hdr.deblocking_filter_override_flag { + hdr.deblocking_filter_disabled_flag = r.read_bit()?; + if !hdr.deblocking_filter_disabled_flag { + hdr.beta_offset_div2 = r.read_se_bounded(-6, 6)?; + hdr.tc_offset_div2 = r.read_se_bounded(-6, 6)?; + } + } + + if pps.loop_filter_across_slices_enabled_flag + && (hdr.sao_luma_flag || hdr.sao_chroma_flag || !hdr.deblocking_filter_disabled_flag) + { + hdr.loop_filter_across_slices_enabled_flag = r.read_bit()?; + } + } + + if pps.tiles_enabled_flag || pps.entropy_coding_sync_enabled_flag { + let max = if !pps.tiles_enabled_flag && pps.entropy_coding_sync_enabled_flag { + sps.pic_height_in_ctbs_y - 1 + } else if pps.tiles_enabled_flag && !pps.entropy_coding_sync_enabled_flag { + (u32::from(pps.num_tile_columns_minus1) + 1) * (u32::from(pps.num_tile_rows_minus1) + 1) - 1 + } else { + (u32::from(pps.num_tile_columns_minus1) + 1) * sps.pic_height_in_ctbs_y - 1 + }; + + hdr.num_entry_point_offsets = r.read_ue_max(max)?; + if hdr.num_entry_point_offsets > 0 { + hdr.offset_len_minus1 = r.read_ue_max(31)?; + // One per CTB row or tile, so a 4K picture with wavefronts has + // more than a fixed array would hold. + let num_bits = usize::from(hdr.offset_len_minus1 + 1); + hdr.entry_point_offset_minus1 = (0..hdr.num_entry_point_offsets) + .map(|_| r.read_bits(num_bits)) + .collect::>()?; + } + } + + if pps.slice_segment_header_extension_present_flag { + let segment_header_extension_length = r.read_ue_max(256)?; + for _ in 0..segment_header_extension_length { + r.skip_bits(8)?; // slice_segment_header_extension_data_byte[i] + } + } + + // byte_alignment() + r.skip_bits(1)?; // Alignment bit + let num_bits = r.num_bits_left() % 8; + r.skip_bits(num_bits)?; + + let epb = r.num_epb(); + hdr.header_bit_size = ((nalu.size - epb) * 8 - r.num_bits_left()) as u32; + + hdr.n_emulation_prevention_bytes = epb as u32; + + log::debug!("Parsed slice {:?}, NAL size was {}", nalu_header.type_, nalu.size); + + Ok(Slice { header: hdr, nalu }) + } + + /// Returns a previously parsed vps given `vps_id`, if any. + pub fn get_vps(&self, vps_id: u8) -> Option<&Rc> { + self.active_vpses.get(&vps_id) + } + + /// Returns a previously parsed sps given `sps_id`, if any. + pub fn get_sps(&self, sps_id: u8) -> Option<&Rc> { + self.active_spses.get(&sps_id) + } + + /// Returns a previously parsed pps given `pps_id`, if any. + pub fn get_pps(&self, pps_id: u8) -> Option<&Rc> { + self.active_ppses.get(&pps_id) + } +} diff --git a/src/codec/h265/picture.rs b/src/codec/h265/picture.rs new file mode 100644 index 0000000..a9ffcde --- /dev/null +++ b/src/codec/h265/picture.rs @@ -0,0 +1,152 @@ +// Copyright 2023 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use crate::codec::h265::parser::NaluType; +use crate::codec::h265::parser::Slice; + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub enum Reference { + #[default] + None, + ShortTerm, + LongTerm, +} + +/// Data associated with an h.265 picture. Most fields are extracted from the +/// slice header and kept for future processing. +#[derive(Debug, Default, Clone, Eq, PartialEq)] +pub struct PictureData { + // Fields extracted from the slice header. These are the CamelCase + // variables, unless noted otherwise. + pub nalu_type: NaluType, + pub no_rasl_output_flag: bool, + pub pic_output_flag: bool, + pub valid_for_prev_tid0_pic: bool, + pub slice_pic_order_cnt_lsb: i32, + pub pic_order_cnt_msb: i32, + pub pic_order_cnt_val: i32, + pub no_output_of_prior_pics_flag: bool, + + // Internal state. + pub first_picture_after_eos: bool, + reference: Reference, + pub pic_latency_cnt: i32, + pub needed_for_output: bool, + pub short_term_ref_pic_set_size_bits: u32, +} + +impl PictureData { + /// Instantiates a new `PictureData` from a slice. + /// + /// See 8.1.3 Decoding process for a coded picture with nuh_layer_id equal + /// to 0. + /// + /// This will also call the picture order count process (clause 8.3.1) to + /// correctly initialize the POC values. + pub fn new_from_slice( + slice: &Slice, + first_picture_in_bitstream: bool, + first_picture_after_eos: bool, + prev_tid0_pic: Option<&PictureData>, + max_pic_order_cnt_lsb: i32, + ) -> Self { + let hdr = &slice.header; + let nalu_type = slice.nalu.header.type_; + + // We assume HandleCraAsBlafFLag == 0, as it is only set through + // external means, which we do not provide. + + let mut pic_order_cnt_msb = 0; + let slice_pic_order_cnt_lsb: i32 = hdr.pic_order_cnt_lsb.into(); + + // Compute the output flags: + // The value of NoRaslOutputFlag is equal to 1 for each IDR access + // unit, each BLA access unit, and each CRA access unit that is the + // first access unit in the bitstream in decoding order, is the first + // access unit that follows an end of sequence NAL unit in decoding + // order, or has HandleCraAsBlaFlag equal to 1. + let no_rasl_output_flag = nalu_type.is_idr() + || nalu_type.is_bla() + || (nalu_type.is_cra() && first_picture_in_bitstream) + || first_picture_after_eos; + + let pic_output_flag = if slice.nalu.header.type_.is_rasl() && no_rasl_output_flag { + false + } else { + hdr.pic_output_flag + }; + + // Compute the Picture Order Count. See 8.3.1 Decoding Process for + // Picture Order Count + if !(nalu_type.is_irap() && no_rasl_output_flag) { + if let Some(prev_tid0_pic) = prev_tid0_pic { + // Equation (8-1) + let prev_pic_order_cnt_lsb = prev_tid0_pic.slice_pic_order_cnt_lsb; + let prev_pic_order_cnt_msb = prev_tid0_pic.pic_order_cnt_msb; + if (slice_pic_order_cnt_lsb < prev_pic_order_cnt_lsb) + && (prev_pic_order_cnt_lsb - slice_pic_order_cnt_lsb) >= (max_pic_order_cnt_lsb / 2) + { + pic_order_cnt_msb = prev_pic_order_cnt_msb + max_pic_order_cnt_lsb; + } else if (slice_pic_order_cnt_lsb > prev_pic_order_cnt_lsb) + && (slice_pic_order_cnt_lsb - prev_pic_order_cnt_lsb) > (max_pic_order_cnt_lsb / 2) + { + pic_order_cnt_msb = prev_pic_order_cnt_msb - max_pic_order_cnt_lsb; + } else { + pic_order_cnt_msb = prev_pic_order_cnt_msb; + } + } + } + + // Compute whether this picture will be a valid prevTid0Pic, i.e.: + // + // Let prevTid0Pic be the previous picture in decoding order that has + // TemporalId equal to 0 and that is not a RASL, RADL or SLNR picture. + // + // Use this flag to correctly set up the field in the decoder during + // `finish_picture`. + let valid_for_prev_tid0_pic = slice.nalu.header.nuh_temporal_id() == 0 + && !nalu_type.is_radl() + && !nalu_type.is_rasl() + && !nalu_type.is_slnr(); + + let no_output_of_prior_pics_flag = if nalu_type.is_irap() && no_rasl_output_flag && !first_picture_in_bitstream + { + nalu_type.is_cra() || hdr.no_output_of_prior_pics_flag + } else { + false + }; + + Self { + nalu_type, + no_rasl_output_flag, + no_output_of_prior_pics_flag, + pic_output_flag, + valid_for_prev_tid0_pic, + slice_pic_order_cnt_lsb, + pic_order_cnt_msb, + // Equation (8-2) + pic_order_cnt_val: pic_order_cnt_msb + slice_pic_order_cnt_lsb, + first_picture_after_eos, + reference: Default::default(), + pic_latency_cnt: 0, + needed_for_output: false, + short_term_ref_pic_set_size_bits: hdr.st_rps_bits, + } + } + + /// Whether the current picture is a reference, either ShortTerm or LongTerm. + pub fn is_ref(&self) -> bool { + !matches!(self.reference, Reference::None) + } + + pub fn set_reference(&mut self, reference: Reference) { + log::debug!("Set reference of POC {} to {:?}", self.pic_order_cnt_val, reference); + + self.reference = reference; + } + + pub fn reference(&self) -> &Reference { + &self.reference + } +} diff --git a/src/decode.rs b/src/decode.rs index fe25506..feb3661 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -6,31 +6,37 @@ // `decoder/stateless/h264/vaapi.rs`), itself BSD-3-Clause / Copyright The // ChromiumOS Authors. See LICENSE.cros-codecs. -//! Thin VA-API H.264 decoder built on the vendored libva binding. +//! Thin VA-API H.264 and H.265 decoder built on the vendored libva binding. //! //! The mirror of [`crate::encode`]. It reuses the backend-agnostic bitstream -//! layer ([`crate::codec::h264`]) from discord/cros-codecs (BSD-3-Clause) for -//! SPS/PPS/slice parsing, reference marking, and DPB bumping, then drives libva -//! directly rather than vendoring cros-codecs's generic multi-backend decoder -//! framework. The per-picture and per-slice VA buffer population is ported from -//! cros-codecs's `decoder/stateless/h264/vaapi.rs`. +//! layers ([`crate::codec::h264`], [`crate::codec::h265`]) from +//! discord/cros-codecs (BSD-3-Clause) for parameter set and slice parsing, +//! reference marking, and DPB bumping, then drives libva directly rather than +//! vendoring cros-codecs's generic multi-backend decoder framework. The +//! per-picture and per-slice VA buffer population is ported from cros-codecs's +//! `decoder/stateless/h264/vaapi.rs` and, in `h265.rs`, from +//! `decoder/stateless/h265.rs` and `decoder/stateless/h265/vaapi.rs`. //! //! Feed [`Decoder::decode`] one Annex-B access unit at a time. Its picture is //! submitted and completed before the call returns, rather than when the next //! unit's first slice arrives, so the hardware is never left holding a //! half-submitted picture between calls. //! -//! Output trails input, and by more than the stream's reorder depth: C.4.5.3 -//! bumps the DPB only when a new picture needs the slot, so the delay follows -//! the sequence's reference and reorder limits. A stream coded with three -//! reference frames trails by three pictures whether or not it uses B-frames. -//! [`Frame`] therefore carries the timestamp of the access unit it was coded in, -//! and [`Decoder::flush`] is what releases the tail at the end of a stream. +//! H.264 output trails input, and by more than the stream's reorder depth: +//! C.4.5.3 bumps the DPB only when a new picture needs the slot, so the delay +//! follows the sequence's reference and reorder limits. A stream coded with +//! three reference frames trails by three pictures whether or not it uses +//! B-frames. H.265 bumps by the SPS's reorder limit (C.5.2), so a stream with +//! no reordering comes out as it decodes. [`Frame`] therefore carries the +//! timestamp of the access unit it was coded in, and [`Decoder::flush`] is what +//! releases the tail at the end of a stream. //! //! Progressive 8-bit 4:2:0 only: a sequence that is interlaced, deeper than 8 //! bits, or not 4:2:0 is rejected rather than decoded wrongly. That covers every //! stream this crate's encoder, WebRTC, or a browser's `VideoEncoder` produces. -//! Left and top cropping are rejected; right and bottom cropping are supported. +//! H.265 is the Main profile, with Main 10 and Main Still Picture streams that +//! hold 8-bit samples decoded the same way. Left and top cropping are rejected; +//! right and bottom cropping are supported. use std::io::Cursor; use std::path::PathBuf; @@ -45,6 +51,7 @@ use crate::codec::h264::parser::{ SliceType, Sps, }; use crate::codec::h264::picture::{Field, IsIdr, PictureData, Reference}; +use crate::codec::Codec; use crate::pool::{PooledSurface, SurfacePool}; use crate::{ bindings, Buffer, BufferType, Config as VaConfig, Context, Display, DrmPrimeSurfaceDescriptor, H264PicFields, @@ -55,19 +62,27 @@ use crate::{ VA_SLICE_DATA_FLAG_ALL, }; -/// H.264 decoder configuration. +mod h265; + +/// Decoder configuration. #[derive(Clone, Debug)] pub struct Config { + /// The format to decode, H.264 unless set. + pub codec: Codec, /// DRM render node to open (e.g. `/dev/dri/renderD128`). /// - /// `None`, the default, opens the first node whose driver can decode H.264. + /// `None`, the default, opens the first node whose driver can decode + /// [`codec`](Self::codec). pub device: Option, } impl Config { - /// Returns a configuration that opens the first render node whose driver can decode H.264. + /// Returns an H.264 configuration that opens the first render node whose driver can decode it. pub fn new() -> Self { - Self { device: None } + Self { + codec: Codec::H264, + device: None, + } } } @@ -212,7 +227,7 @@ impl std::fmt::Debug for Frame { } } -/// A VA-API H.264 decoder. Built once, fed Annex-B access units, emits NV12. +/// A VA-API H.264 or H.265 decoder. Built once, fed Annex-B access units, emits NV12. /// /// # Examples /// @@ -234,6 +249,18 @@ impl std::fmt::Debug for Frame { /// # } /// ``` pub struct Decoder { + inner: Inner, +} + +/// The decoder for each format. They share the VA plumbing: [`Sequence`], +/// [`Handle`], and the read-back and export of a decoded picture. +enum Inner { + H264(H264), + H265(h265::Decoder), +} + +/// The H.264 decoder. +struct H264 { parser: Parser, dpb: Dpb, /// Cached variables from the previous reference picture. @@ -251,38 +278,34 @@ pub struct Decoder { } impl Decoder { - /// Opens [`Config::device`], or the first render node that decodes H.264, - /// and checks that the driver exposes an H.264 decode entrypoint. + /// Opens [`Config::device`], or the first render node that decodes + /// [`Config::codec`], and checks that the driver exposes a decode + /// entrypoint for it. /// /// The VA config, context, and surface pool are built later, from the first /// SPS, since their profile and size come from the stream. This still fails /// early enough for a caller to fall back to another decoder when libva is - /// missing, the node cannot be opened, or the driver decodes no H.264. A - /// named node is never swapped for another. + /// missing, the node cannot be opened, or the driver does not decode the + /// codec. A named node is never swapped for another. pub fn new(config: Config) -> anyhow::Result { + let codec = config.codec; let (device, display) = match config.device { Some(device) => { let display = Display::open_drm_display(&device).map_err(|e| anyhow!("open DRM display {device:?}: {e:?}"))?; - probe(&display)?; + probe(&display, codec)?; (device, display) } - None => { - crate::display::open_first(probe).map_err(|e| e.context("find a render node that decodes H.264"))? - } + None => crate::display::open_first(|display| probe(display, codec)) + .map_err(|e| e.context(format!("find a render node that decodes {codec:?}")))?, }; - log::info!("opened VA-API H.264 decoder on {device:?}"); - Ok(Self { - parser: Parser::default(), - dpb: Dpb::default(), - prev_ref_pic_info: PrevReferencePicInfo::default(), - prev_pic_info: PrevPicInfo::default(), - max_long_term_frame_idx: MaxLongTermFrameIdx::default(), - sequence: None, - ready: Vec::new(), - display, - }) + log::info!("opened VA-API {codec:?} decoder on {device:?}"); + let inner = match codec { + Codec::H264 => Inner::H264(H264::new(display)), + Codec::H265 => Inner::H265(h265::Decoder::new(display)), + }; + Ok(Self { inner }) } /// Decodes one Annex-B access unit, returning the pictures that came due, in @@ -324,6 +347,98 @@ impl Decoder { /// queue for whichever of [`Decoder::take_frames`] or /// [`Decoder::take_exported`] the caller asked for. fn submit(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result<()> { + let nalus = match &mut self.inner { + Inner::H264(decoder) => decoder.submit(access_unit, timestamp)?, + Inner::H265(decoder) => decoder.submit(access_unit, timestamp)?, + }; + + // A decoder cannot tell a truncated unit from a complete one, so a + // caller that hands us the wrong container gets silence. Say so once per + // unit rather than leaving them to wonder where their pictures went. + if nalus == 0 && !access_unit.is_empty() { + log::warn!( + "access unit of {} bytes holds no Annex-B NAL unit; length-prefixed input has to be converted first", + access_unit.len() + ); + } + Ok(()) + } + + /// Returns every picture still held in the DPB, in output order. + /// + /// The DPB lets a picture out only when a later one needs it to (C.4.5.3 + /// for H.264, C.5.2 for H.265), so a stream that simply stops leaves its + /// tail sitting there. This releases that tail and empties the DPB, so a + /// second call returns nothing. + /// + /// The parsed parameter sets survive, and nothing here waits for an IDR: a + /// stream picked up part way through decodes against an empty DPB, which + /// gives distorted pictures until its next IDR rather than an error. + pub fn flush(&mut self) -> anyhow::Result> { + self.reset(); + self.take_frames() + } + + /// [`Decoder::flush`] for a caller taking its pictures on the GPU. + pub fn flush_exported(&mut self) -> anyhow::Result> { + self.reset(); + self.take_exported() + } + + /// Moves the DPB to the ready queue and forgets the state the next picture + /// would otherwise be derived against. + fn reset(&mut self) { + match &mut self.inner { + Inner::H264(decoder) => decoder.reset(), + Inner::H265(decoder) => decoder.reset(), + } + } + + /// The pictures bumped out of the DPB, in output order, awaiting download. + fn ready(&mut self) -> &mut Vec { + match &mut self.inner { + Inner::H264(decoder) => &mut decoder.ready, + Inner::H265(decoder) => &mut decoder.ready, + } + } + + /// Downloads every picture waiting in the ready queue. + fn take_frames(&mut self) -> anyhow::Result> { + std::mem::take(self.ready()).iter().map(download).collect() + } + + /// Exports every picture waiting in the ready queue, leaving the pixels + /// where the hardware wrote them. + fn take_exported(&mut self) -> anyhow::Result> { + std::mem::take(self.ready()).iter().map(export).collect() + } + + /// The VA state of the active sequence, for tests that look at its pool. + #[cfg(test)] + fn sequence(&self) -> Option<&Sequence> { + match &self.inner { + Inner::H264(decoder) => decoder.sequence.as_ref(), + Inner::H265(decoder) => decoder.sequence.as_ref(), + } + } +} + +impl H264 { + fn new(display: Arc) -> Self { + Self { + parser: Parser::default(), + dpb: Dpb::default(), + prev_ref_pic_info: PrevReferencePicInfo::default(), + prev_pic_info: PrevPicInfo::default(), + max_long_term_frame_idx: MaxLongTermFrameIdx::default(), + sequence: None, + ready: Vec::new(), + display, + } + } + + /// Decodes one access unit into the ready queue, returning how many NAL units it held. + fn submit(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result { let mut cursor = Cursor::new(access_unit); let mut current: Option = None; let mut nalus = 0usize; @@ -369,43 +484,13 @@ impl Decoder { } } - // Since the loop cannot tell a truncated unit from a complete one, a - // caller that hands us the wrong container gets silence. Say so once per - // unit rather than leaving them to wonder where their pictures went. - if nalus == 0 && !access_unit.is_empty() { - log::warn!( - "access unit of {} bytes holds no Annex-B NAL unit; length-prefixed input has to be converted first", - access_unit.len() - ); - } - // Finishing here rather than on the next unit's first slice keeps the // hardware from holding a half-submitted picture between calls. if let Some(picture) = current.take() { self.finish_picture(picture)?; } - Ok(()) - } - - /// Returns every picture still held in the DPB, in output order. - /// - /// C.4.5.3 bumps the DPB only when a new picture needs the slot, so a stream - /// that simply stops leaves its tail sitting there. This releases that tail - /// and empties the DPB, so a second call returns nothing. - /// - /// The parsed parameter sets survive, and nothing here waits for an IDR: a - /// stream picked up part way through decodes against an empty DPB, which - /// gives distorted pictures until its next IDR rather than an error. - pub fn flush(&mut self) -> anyhow::Result> { - self.reset(); - self.take_frames() - } - - /// [`Decoder::flush`] for a caller taking its pictures on the GPU. - pub fn flush_exported(&mut self) -> anyhow::Result> { - self.reset(); - self.take_exported() + Ok(nalus) } /// Moves the DPB to the ready queue and forgets the order count state the @@ -417,17 +502,6 @@ impl Decoder { self.max_long_term_frame_idx = MaxLongTermFrameIdx::default(); } - /// Downloads every picture waiting in the ready queue. - fn take_frames(&mut self) -> anyhow::Result> { - std::mem::take(&mut self.ready).iter().map(download).collect() - } - - /// Exports every picture waiting in the ready queue, leaving the pixels - /// where the hardware wrote them. - fn take_exported(&mut self) -> anyhow::Result> { - std::mem::take(&mut self.ready).iter().map(export).collect() - } - /// Moves everything left in the DPB to the ready queue. fn drain(&mut self) { self.ready.extend(self.dpb.drain().into_iter().flatten()); @@ -448,7 +522,14 @@ impl Decoder { }; self.dpb.set_limits(max_dpb_frames, max_num_reorder_frames); - if self.sequence.as_ref().map(|sequence| &sequence.info) == Some(&info) { + let format = Format { + codec: Codec::H264, + profile: va_profile(sps)?, + coded: info.coded, + visible: info.visible, + max_dpb_frames: info.max_dpb_frames, + }; + if self.sequence.as_ref().map(|sequence| &sequence.format) == Some(&format) { return Ok(()); } @@ -457,7 +538,7 @@ impl Decoder { // through them the old pool) alive until they are downloaded. self.drain(); - self.sequence = Some(Sequence::new(&self.display, sps, info)?); + self.sequence = Some(Sequence::new(&self.display, format)?); Ok(()) } @@ -489,12 +570,7 @@ impl Decoder { // `apply_sps` above either kept the sequence or built a new one. let sequence = self.sequence.as_ref().expect("sequence built by apply_sps"); let context = Rc::clone(&sequence.context); - let handle = Handle { - surface: Arc::new(sequence.pool.alloc()?), - timestamp, - width: sequence.width, - height: sequence.height, - }; + let handle = sequence.handle(timestamp)?; let mut picture = Picture::new(timestamp, Rc::clone(&context), handle.clone()); let pic_param = build_pic_param(&slice.header, &pic, handle.surface.id(), &self.dpb, &sps, &pps); @@ -566,13 +642,7 @@ impl Decoder { .. } = current; - let picture = picture.begin::<()>().map_err(|e| anyhow!("picture begin: {e:?}"))?; - let picture = picture.render().map_err(|e| anyhow!("picture render: {e:?}"))?; - let picture = picture.end().map_err(|e| anyhow!("picture end: {e:?}"))?; - // Sync before the surface enters the DPB: it is about to be read as a - // reference by later pictures, and the caller may never ask for its - // pixels. Dropping the synced picture frees the VA buffers with it. - drop(picture.sync::<()>().map_err(|(e, _)| anyhow!("picture sync: {e:?}"))?); + run(picture)?; log::trace!("decoded picture POC {}", pic.pic_order_cnt); if pic.is_ref() { @@ -1129,13 +1199,22 @@ impl SequenceInfo { } } +/// What the VA config, context, and surface pool of a sequence are built for. +/// A change in any of it rebuilds them. +#[derive(Clone, Debug, PartialEq, Eq)] +struct Format { + codec: Codec, + profile: VAProfile::Type, + coded: (u32, u32), + /// The size after cropping, which is what a [`Frame`] reports. + visible: (u32, u32), + /// The most pictures the DPB holds, which bounds what the pool keeps free. + max_dpb_frames: usize, +} + /// The VA state for one coded video sequence. struct Sequence { - info: SequenceInfo, - /// Visible width after cropping, which is what a [`Frame`] reports. - width: u32, - /// Visible height after cropping. - height: u32, + format: Format, pool: SurfacePool, // Drop order: the context must go before the config it was created from. context: Rc, @@ -1143,48 +1222,70 @@ struct Sequence { } impl Sequence { - fn new(display: &Arc, sps: &Sps, info: SequenceInfo) -> anyhow::Result { - let (width, height) = info.visible; - + fn new(display: &Arc, format: Format) -> anyhow::Result { let attrs = vec![VAConfigAttrib { type_: VAConfigAttribType::VAConfigAttribRTFormat, value: VA_RT_FORMAT_YUV420, }]; let config = display - .create_config(attrs, va_profile(sps)?, VAEntrypoint::VAEntrypointVLD) + .create_config(attrs, format.profile, VAEntrypoint::VAEntrypointVLD) .map_err(|e| anyhow!("create VA config: {e:?}"))?; - let (coded_width, coded_height) = info.coded; + let (coded_width, coded_height) = format.coded; let context = display .create_context::<()>(&config, coded_width, coded_height, None, true) .map_err(|e| anyhow!("create VA context: {e:?}"))?; + let (width, height) = format.visible; log::info!( - "VA-API H.264 sequence: {width}x{height} visible, {coded_width}x{coded_height} coded, \ - profile_idc {}, DPB {} frames", - sps.profile_idc, - info.max_dpb_frames + "VA-API {:?} sequence: {width}x{height} visible, {coded_width}x{coded_height} coded, \ + VA profile {}, DPB {} frames", + format.codec, + format.profile, + format.max_dpb_frames ); // What the decoder itself can need at once: a full DPB plus the picture // being decoded. Anything free beyond that was let go by a consumer that // had been holding pictures, and is memory the stream will not ask for // again. - let capacity = info.max_dpb_frames + 1; + let capacity = format.max_dpb_frames + 1; Ok(Self { - info, - width, - height, pool: SurfacePool::new( display, (coded_width, coded_height), UsageHint::USAGE_HINT_DECODER, capacity, ), + format, context, _config: config, }) } + + /// Returns a surface to decode a picture coded at `timestamp` into. + fn handle(&self, timestamp: u64) -> anyhow::Result { + let (width, height) = self.format.visible; + Ok(Handle { + surface: Arc::new(self.pool.alloc()?), + timestamp, + width, + height, + }) + } +} + +/// Submits a picture with all its buffers added, and waits for it to decode. +/// +/// The wait comes before the surface enters the DPB: it is about to be read as +/// a reference by later pictures, and the caller may never ask for its pixels. +/// Dropping the synced picture frees the VA buffers with it. +fn run(picture: Picture) -> anyhow::Result<()> { + let picture = picture.begin::<()>().map_err(|e| anyhow!("picture begin: {e:?}"))?; + let picture = picture.render().map_err(|e| anyhow!("picture render: {e:?}"))?; + let picture = picture.end().map_err(|e| anyhow!("picture end: {e:?}"))?; + drop(picture.sync::<()>().map_err(|(e, _)| anyhow!("picture sync: {e:?}"))?); + Ok(()) } /// Exports a decoded surface as a DRM PRIME descriptor, copying nothing. @@ -1296,26 +1397,28 @@ fn copy_plane( Ok(()) } -/// Checks that `display`'s driver can decode H.264, which is what [`Decoder::new`] needs of a render node. +/// Checks that `display`'s driver can decode `codec`, which is what [`Decoder::new`] needs of a render node. /// /// # Errors /// /// Fails unless the driver offers `VAEntrypointVLD` for a Constrained -/// Baseline, Main, or High profile, or when it cannot be asked. -pub fn probe(display: &Display) -> anyhow::Result<()> { +/// Baseline, Main, or High profile (H.264) or for the Main profile (H.265), +/// or when it cannot be asked. +pub fn probe(display: &Display, codec: Codec) -> anyhow::Result<()> { let profiles = display .query_config_profiles() .map_err(|e| anyhow!("query VA profiles: {e:?}"))?; let supported = profiles .into_iter() - .filter(|profile| { - matches!( + .filter(|profile| match codec { + Codec::H264 => matches!( *profile, VAProfile::VAProfileH264ConstrainedBaseline | VAProfile::VAProfileH264Main | VAProfile::VAProfileH264High - ) + ), + Codec::H265 => *profile == VAProfile::VAProfileHEVCMain, }) .any(|profile| { display @@ -1324,7 +1427,7 @@ pub fn probe(display: &Display) -> anyhow::Result<()> { }); if !supported { - bail!("driver exposes no H.264 decode entrypoint"); + bail!("driver exposes no {codec:?} decode entrypoint"); } Ok(()) @@ -1826,6 +1929,7 @@ mod tests { } let named = Config { device: Some(PathBuf::from("/dev/null")), + ..Config::new() }; assert!( Decoder::new(named).is_err(), @@ -1922,7 +2026,7 @@ mod tests { } assert!(compared > 0, "the decoder produced no pictures"); - let pool = &exporting.sequence.as_ref().expect("a sequence").pool; + let pool = &exporting.sequence().expect("a sequence").pool; let allocated = pool.allocated.get(); eprintln!("{allocated} surfaces allocated for {PICTURES} exported pictures"); assert!( @@ -1960,7 +2064,7 @@ mod tests { for _ in 0..HELD { held.extend(decode(&mut decoder)); } - let pool = &decoder.sequence.as_ref().expect("a sequence").pool; + let pool = &decoder.sequence().expect("a sequence").pool; let allocated = pool.allocated.get(); assert!( allocated >= held.len(), @@ -1979,7 +2083,7 @@ mod tests { for _ in 0..10 { drop(decode(&mut decoder)); } - let pool = &decoder.sequence.as_ref().expect("a sequence").pool; + let pool = &decoder.sequence().expect("a sequence").pool; assert_eq!( pool.allocated.get(), allocated, @@ -2048,99 +2152,179 @@ mod tests { } } - /// Decoded pictures match ffmpeg's software decoder byte for byte, in the - /// order both put them out. + /// Every picture matches what ffmpeg's software decoder makes of the same + /// stream, bit for bit, in either codec. /// - /// The test that says this is a decoder rather than plumbing. The stream is - /// coded with B-frames and three reference frames, so matching the reference - /// means the reference lists, the picture order counts, and the DPB bumping - /// are all right, not just the surfaces. Skips without ffmpeg or a device. + /// The test that says this is a decoder rather than plumbing. The streams + /// are coded with B-frames and three reference frames, so matching the + /// reference means the reference lists, the picture order counts, and the + /// DPB bumping are all right, not just the surfaces. The second size crops + /// on the right and bottom. Half the pictures are taken as descriptors and + /// downloaded after, so both ways out are checked. Skips without ffmpeg, + /// its encoder, or a device. #[test] fn decoded_pictures_match_a_software_decoder() { - const WIDTH: u32 = 320; - const HEIGHT: u32 = 240; const PICTURES: usize = 30; - let Some(stream) = ffmpeg(&[ - "-f", - "lavfi", - "-i", - &format!("testsrc2=size={WIDTH}x{HEIGHT}:rate=30"), - "-frames:v", - &PICTURES.to_string(), - "-c:v", - "libx264", - "-profile:v", - "main", - "-bf", - "2", - "-refs", - "3", - "-g", - "15", - "-pix_fmt", - "yuv420p", - "-f", - "h264", - "-", - ]) else { - eprintln!("skipping: no ffmpeg with libx264"); + for codec in [Codec::H264, Codec::H265] { + for (width, height) in [(320u32, 240u32), (852, 478)] { + let Some(stream) = software_encode(codec, (width, height), PICTURES, true) else { + eprintln!("skipping: no ffmpeg that encodes {codec:?}"); + continue; + }; + let reference = software_decode(codec, &stream); + + let picture_len = (width * height * 3 / 2) as usize; + assert_eq!( + reference.len(), + PICTURES * picture_len, + "ffmpeg decoded a different count" + ); + + let Ok(mut decoder) = Decoder::new(Config { codec, ..Config::new() }) else { + eprintln!("skipping: no VA-API {codec:?} decoder"); + continue; + }; + + let units = split_access_units(codec, &stream); + let mut decoded = Vec::new(); + for (index, unit) in units.iter().enumerate() { + if index % 2 == 0 { + decoded.extend(decoder.decode(unit, index as u64).expect("decode an access unit")); + } else { + for exported in decoder + .decode_exported(unit, index as u64) + .expect("decode an access unit") + { + decoded.push(exported.download().expect("download an exported picture")); + } + } + } + decoded.extend(decoder.flush().expect("flush the decoder")); + assert_eq!(decoded.len(), PICTURES, "{codec:?} lost or invented pictures"); + + let mut timestamps: Vec = decoded.iter().map(|picture| picture.timestamp).collect(); + assert!( + timestamps.windows(2).any(|pair| pair[0] > pair[1]), + "the {codec:?} stream came out in decode order, so nothing here tests reordering" + ); + timestamps.sort_unstable(); + assert_eq!( + timestamps, + (0..PICTURES as u64).collect::>(), + "a {codec:?} picture's timestamp did not survive reordering" + ); + + for (index, picture) in decoded.iter().enumerate() { + assert_eq!((picture.width, picture.height), (width, height)); + assert!( + picture.data == reference[index * picture_len..][..picture_len], + "{codec:?} picture {index} of {PICTURES} at {width}x{height} differs from the software decoder" + ); + } + } + } + } + + /// An H.265 stream coded without reordering comes out as it decodes: C.5.2 + /// bumps by the SPS's reorder limit, which is zero, rather than holding + /// pictures until the DPB fills the way H.264's C.4.5.3 does. + #[test] + fn h265_without_reordering_comes_out_as_it_decodes() { + const PICTURES: usize = 10; + + let Some(stream) = software_encode(Codec::H265, (320, 240), PICTURES, false) else { + eprintln!("skipping: no ffmpeg that encodes H.265"); + return; + }; + let Ok(mut decoder) = Decoder::new(Config { + codec: Codec::H265, + ..Config::new() + }) else { + eprintln!("skipping: no VA-API H.265 decoder"); return; }; - // ffmpeg reads the elementary stream from a file rather than a pipe, so - // the test never has to drain its output while still writing its input. - let path = std::env::temp_dir().join(format!("moq-vaapi-decode-{}.264", std::process::id())); - std::fs::write(&path, &stream).expect("write the elementary stream"); - let reference = ffmpeg(&[ - "-i", - path.to_str().expect("a UTF-8 temporary path"), - "-pix_fmt", - "nv12", - "-f", - "rawvideo", - "-", - ]); - let _ = std::fs::remove_file(&path); - let reference = reference.expect("ffmpeg decodes what it just encoded"); + for (index, unit) in split_access_units(Codec::H265, &stream).iter().enumerate() { + let decoded = decoder.decode(unit, index as u64).expect("decode an access unit"); + let timestamps: Vec = decoded.iter().map(|picture| picture.timestamp).collect(); + assert_eq!(timestamps, [index as u64], "access unit {index} was held back"); + } + assert!(decoder.flush().expect("flush the decoder").is_empty()); + } - let picture_len = (WIDTH * HEIGHT * 3 / 2) as usize; - assert_eq!( - reference.len(), - PICTURES * picture_len, - "ffmpeg decoded a different count" - ); + /// A 4K H.265 stream coded with wavefronts, as x265 codes by default, signals + /// an entry point per CTB row: 33 at 64-pixel CTBs, past what a fixed array + /// of 32 held. + #[test] + fn a_4k_h265_wavefront_stream_decodes() { + const PICTURES: usize = 2; + let size = (3840, 2160); - let Ok(mut decoder) = Decoder::new(Config::new()) else { - eprintln!("skipping: no VA-API H.264 decoder"); + let Some(stream) = software_encode(Codec::H265, size, PICTURES, false) else { + eprintln!("skipping: no ffmpeg that encodes H.265"); + return; + }; + let Ok(mut decoder) = Decoder::new(Config { + codec: Codec::H265, + ..Config::new() + }) else { + eprintln!("skipping: no VA-API H.265 decoder"); return; }; - let units = split_access_units(&stream); let mut decoded = Vec::new(); + for (index, unit) in split_access_units(Codec::H265, &stream).iter().enumerate() { + decoded.extend(decoder.decode(unit, index as u64).expect("decode an access unit")); + } + decoded.extend(decoder.flush().expect("flush the decoder")); + assert_eq!(decoded.len(), PICTURES); + assert!(decoded.iter().all(|picture| (picture.width, picture.height) == size)); + } + + /// A second H.265 sequence at another size decodes at that size, and the + /// first sequence's tail comes out ahead of it. + #[test] + fn a_new_h265_sequence_decodes_at_its_own_size() { + const PER_SEQUENCE: usize = 6; + let sizes = [(320u32, 240u32), (192u32, 144u32)]; + + let mut stream = Vec::new(); + for &size in &sizes { + let Some(sequence) = software_encode(Codec::H265, size, PER_SEQUENCE, true) else { + eprintln!("skipping: no ffmpeg that encodes H.265"); + return; + }; + stream.extend(sequence); + } + let Ok(mut decoder) = Decoder::new(Config { + codec: Codec::H265, + ..Config::new() + }) else { + eprintln!("skipping: no VA-API H.265 decoder"); + return; + }; + + let mut decoded = Vec::new(); + let units = split_access_units(Codec::H265, &stream); for (index, unit) in units.iter().enumerate() { decoded.extend(decoder.decode(unit, index as u64).expect("decode an access unit")); } decoded.extend(decoder.flush().expect("flush the decoder")); - assert_eq!(decoded.len(), PICTURES, "the decoder lost or invented pictures"); - let mut timestamps: Vec = decoded.iter().map(|picture| picture.timestamp).collect(); - assert!( - timestamps.windows(2).any(|pair| pair[0] > pair[1]), - "the stream came out in decode order, so nothing here tests reordering" - ); - timestamps.sort_unstable(); - assert_eq!( - timestamps, - (0..PICTURES as u64).collect::>(), - "a picture's timestamp did not survive reordering" - ); + let expected: Vec<(u32, u32)> = sizes + .iter() + .flat_map(|&size| std::iter::repeat_n(size, PER_SEQUENCE)) + .collect(); + let actual: Vec<(u32, u32)> = decoded.iter().map(|picture| (picture.width, picture.height)).collect(); + assert_eq!(actual, expected, "a picture came out at the wrong size or out of order"); - for (index, picture) in decoded.iter().enumerate() { - assert_eq!((picture.width, picture.height), (WIDTH, HEIGHT)); - assert!( - picture.data == reference[index * picture_len..][..picture_len], - "picture {index} of {PICTURES} differs from the software decoder" + for picture in &decoded { + let expected_len = (picture.width * picture.height * 3 / 2) as usize; + assert_eq!( + picture.data.len(), + expected_len, + "a picture was read back at the wrong size" ); } } @@ -2209,11 +2393,84 @@ mod tests { Some(output.stdout) } + /// Encodes `pictures` of ffmpeg's test pattern at `size`, with two B-frames + /// and three reference frames when `reorder` is set and neither otherwise, + /// returning the Annex-B stream. `None` without ffmpeg or its encoder. + fn software_encode(codec: Codec, (width, height): (u32, u32), pictures: usize, reorder: bool) -> Option> { + let source = format!("testsrc2=size={width}x{height}:rate=30"); + let pictures = pictures.to_string(); + let mut args = vec![ + "-f", + "lavfi", + "-i", + &source, + "-frames:v", + &pictures, + "-pix_fmt", + "yuv420p", + ]; + + let x265; + match codec { + Codec::H264 => args.extend([ + "-c:v", + "libx264", + "-profile:v", + "main", + "-bf", + if reorder { "2" } else { "0" }, + "-refs", + if reorder { "3" } else { "1" }, + "-g", + "15", + "-f", + "h264", + ]), + Codec::H265 => { + // x265's defaults are open GOPs, so the second keyframe is a CRA + // picture with leading pictures that refer across it. + x265 = if reorder { + "bframes=2:ref=3:keyint=15:min-keyint=15:log-level=error" + } else { + "bframes=0:ref=1:keyint=15:min-keyint=15:log-level=error" + }; + args.extend(["-c:v", "libx265", "-x265-params", x265, "-f", "hevc"]); + } + } + args.push("-"); + ffmpeg(&args) + } + + /// Decodes `stream` with ffmpeg into packed NV12, one picture after another. + fn software_decode(codec: Codec, stream: &[u8]) -> Vec { + // ffmpeg reads the elementary stream from a file rather than a pipe, so + // the test never has to drain its output while still writing its input. + static COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + let count = COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let extension = match codec { + Codec::H264 => "264", + Codec::H265 => "265", + }; + let path = std::env::temp_dir().join(format!("moq-vaapi-decode-{}-{count}.{extension}", std::process::id())); + std::fs::write(&path, stream).expect("write the elementary stream"); + let decoded = ffmpeg(&[ + "-i", + path.to_str().expect("a UTF-8 temporary path"), + "-pix_fmt", + "nv12", + "-f", + "rawvideo", + "-", + ]); + let _ = std::fs::remove_file(&path); + decoded.expect("ffmpeg decodes what it just encoded") + } + /// Splits an Annex-B elementary stream into access units. /// - /// Each picture here is a single slice, so a unit ends at the next VCL NAL - /// unit, or at the next parameter set or SEI that follows one. - fn split_access_units(stream: &[u8]) -> Vec> { + /// A unit ends at the first slice of the next picture, or at a parameter + /// set, SEI, or delimiter that comes before one. + fn split_access_units(codec: Codec, stream: &[u8]) -> Vec> { let starts: Vec = (0..stream.len().saturating_sub(3)) .filter(|&i| stream[i..i + 3] == [0, 0, 1]) .collect(); @@ -2224,10 +2481,28 @@ mod tests { for (index, &start) in starts.iter().enumerate() { let end = starts.get(index + 1).copied().unwrap_or(stream.len()); - let nal_type = stream[start + 3] & 0x1f; - let is_slice = (1..=5).contains(&nal_type); + let nal = &stream[start + 3..end]; + // The first bit after the NAL header is first_mb_in_slice == 0 for + // H.264 and first_slice_segment_in_pic_flag for H.265: either way, set + // on the first slice of a picture. + let (is_slice, starts_picture, starts_unit) = match codec { + Codec::H264 => { + let nal_type = nal[0] & 0x1f; + let is_slice = (1..=5).contains(&nal_type); + (is_slice, is_slice && nal[1] & 0x80 != 0, matches!(nal_type, 6..=9)) + } + Codec::H265 => { + let nal_type = (nal[0] >> 1) & 0x3f; + let is_slice = nal_type <= 31; + ( + is_slice, + is_slice && nal[2] & 0x80 != 0, + matches!(nal_type, 32..=35 | 39), + ) + } + }; - if current_has_picture && (is_slice || matches!(nal_type, 6..=9)) { + if current_has_picture && (starts_picture || starts_unit) { units.push(std::mem::take(&mut current)); current_has_picture = false; } diff --git a/src/decode/h265.rs b/src/decode/h265.rs new file mode 100644 index 0000000..8941e92 --- /dev/null +++ b/src/decode/h265.rs @@ -0,0 +1,1019 @@ +//! The H.265 decoder. +//! +//! Ported from discord/cros-codecs's `decoder/stateless/h265.rs` and +//! `decoder/stateless/h265/vaapi.rs` (BSD-3-Clause), driving libva directly the +//! way the H.264 decoder beside it does. The reference picture set (8.3.2), +//! reference list construction (8.3.4), and DPB output (C.5.2) follow the +//! specification as cros-codecs does, with the screen content and range +//! extension paths left out: a sequence that needs them is rejected up front. +//! +//! Output follows C.5.2's bumping, which works from the SPS's reorder limit +//! rather than the DPB size, so a stream coded without reordering comes out the +//! moment each picture decodes. + +use std::cell::RefCell; +use std::io::Cursor; +use std::rc::Rc; +use std::sync::Arc; + +use anyhow::{anyhow, bail, Context as _}; + +use super::{create_buffer, run, Format, Handle, Sequence}; +use crate::codec::h265::dpb::{Dpb, DpbEntry}; +use crate::codec::h265::parser::{Nalu, NaluType, Parser, Pps, ScalingLists, Slice, SliceHeader, Sps}; +use crate::codec::h265::picture::{PictureData, Reference}; +use crate::codec::Codec; +use crate::{ + BufferType, Display, HevcLongSliceFlags, HevcPicFields, HevcSliceParsingFields, IQMatrix, IQMatrixBufferHEVC, + Picture, PictureHEVC, PictureParameter, PictureParameterBufferHEVC, SliceParameter, SliceParameterBufferHEVC, + VAProfile, VA_INVALID_ID, VA_PICTURE_HEVC_INVALID, VA_PICTURE_HEVC_LONG_TERM_REFERENCE, + VA_PICTURE_HEVC_RPS_LT_CURR, VA_PICTURE_HEVC_RPS_ST_CURR_AFTER, VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE, + VA_SLICE_DATA_FLAG_ALL, +}; + +/// The most pictures a DPB holds (A.4.2). +const MAX_DPB_SIZE: usize = 16; + +/// The most reference pictures a VA picture parameter buffer lists. +const MAX_VA_REFERENCES: usize = 15; + +/// The H.265 decoder. +pub(super) struct Decoder { + parser: Parser, + dpb: Dpb, + /// The SPS of the last picture begun, whose limits drive bumping (C.5.2). + sps: Option>, + /// PrevTid0Pic of 8.3.1, which the next picture's order count derives from. + prev_tid0_pic: Option, + first_picture_in_bitstream: bool, + first_picture_after_eos: bool, + /// NoRaslOutputFlag of the last IRAP picture, which decides whether the + /// RASL pictures after it can be decoded at all. + irap_no_rasl_output_flag: bool, + /// The header a dependent slice segment inherits. + last_independent_slice_header: Option, + /// VA state for the active sequence, rebuilt when the SPS changes something + /// the config, context, or surface pool depends on. + pub(super) sequence: Option, + /// Pictures bumped out of the DPB, in output order, awaiting download. + pub(super) ready: Vec, + /// Drop order: everything above borrows the display. + display: Arc, +} + +/// The picture being assembled, held across the slices that make it up. +struct CurrentPicture { + pic: PictureData, + /// The SPS the picture was begun with. + sps: Rc, + /// The reference picture set derived for it (8.3.2). + rps: RefPicSet, + /// The DPB's reference pictures, in the order the picture parameters list + /// them, which is what a slice's reference lists index into. + references: Vec>, + handle: Handle, + picture: Picture, + /// The last slice's parameters and data, held back one slice so the final + /// one can be marked as the last of the picture. + last_slice: Option<(SliceParameterBufferHEVC, Vec)>, +} + +/// The reference picture set of one picture (8.3.2). A missing entry is a +/// reference the stream names but the DPB does not hold. +#[derive(Default)] +struct RefPicSet { + st_curr_before: Vec>>, + st_curr_after: Vec>>, + st_foll: Vec>>, + lt_curr: Vec>>, + lt_foll: Vec>>, +} + +impl RefPicSet { + fn lists(&self) -> [&[Option>]; 5] { + [ + &self.st_curr_before, + &self.st_curr_after, + &self.st_foll, + &self.lt_curr, + &self.lt_foll, + ] + } + + /// 8.3.4: the reference picture lists of a slice, each as long as the + /// slice's active reference count. + fn ref_pic_lists(&self, hdr: &SliceHeader) -> anyhow::Result<[Vec>>; 2]> { + if !hdr.type_.is_p() && !hdr.type_.is_b() { + return Ok(Default::default()); + } + + // RefPicListTemp0 and RefPicListTemp1 (8-8, 8-10) repeat these until + // they are long enough, so an entry is picked modulo their length. + let before = || self.st_curr_before.iter(); + let after = || self.st_curr_after.iter(); + let long = || self.lt_curr.iter(); + let temp0: Vec<_> = before().chain(after()).chain(long()).collect(); + if temp0.is_empty() { + bail!("invalid stream: an inter slice whose reference picture set holds nothing to predict from"); + } + let temp1: Vec<_> = after().chain(before()).chain(long()).collect(); + + let rplm = &hdr.ref_pic_list_modification; + let build = |temp: &[&Option>], active_minus1: u8, modified: bool, entries: &[u32]| { + (0..=usize::from(active_minus1)) + .map(|index| { + // 8-9 and 8-11. + let index = if modified { + entries.get(index).map_or(index, |&entry| entry as usize) + } else { + index + }; + temp[index % temp.len()].clone() + }) + .collect::>() + }; + + let list0 = build( + &temp0, + hdr.num_ref_idx_l0_active_minus1, + rplm.ref_pic_list_modification_flag_l0, + &rplm.list_entry_l0, + ); + let list1 = if hdr.type_.is_b() { + build( + &temp1, + hdr.num_ref_idx_l1_active_minus1, + rplm.ref_pic_list_modification_flag_l1, + &rplm.list_entry_l1, + ) + } else { + Vec::new() + }; + + Ok([list0, list1]) + } +} + +impl Decoder { + pub(super) fn new(display: Arc) -> Self { + Self { + parser: Parser::default(), + dpb: Dpb::default(), + sps: None, + prev_tid0_pic: None, + first_picture_in_bitstream: true, + first_picture_after_eos: true, + irap_no_rasl_output_flag: false, + last_independent_slice_header: None, + sequence: None, + ready: Vec::new(), + display, + } + } + + /// Decodes one access unit into the ready queue, returning how many NAL units it held. + pub(super) fn submit(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result { + let mut cursor = Cursor::new(access_unit); + let mut current: Option = None; + let mut nalus = 0usize; + + // `Nalu::next` reports the end of the buffer as an error, so a truncated + // unit is indistinguishable from a complete one and simply stops us. + while let Ok(nalu) = Nalu::next(&mut cursor) { + nalus += 1; + match nalu.header.type_ { + NaluType::VpsNut => { + self.parser.parse_vps(&nalu).map_err(|e| anyhow!("parse VPS: {e}"))?; + } + NaluType::SpsNut => { + self.parser.parse_sps(&nalu).map_err(|e| anyhow!("parse SPS: {e}"))?; + } + NaluType::PpsNut => { + self.parser.parse_pps(&nalu).map_err(|e| anyhow!("parse PPS: {e}"))?; + } + NaluType::TrailN + | NaluType::TrailR + | NaluType::TsaN + | NaluType::TsaR + | NaluType::StsaN + | NaluType::StsaR + | NaluType::RadlN + | NaluType::RadlR + | NaluType::RaslN + | NaluType::RaslR + | NaluType::BlaWLp + | NaluType::BlaWRadl + | NaluType::BlaNLp + | NaluType::IdrWRadl + | NaluType::IdrNLp + | NaluType::CraNut => { + let mut slice = self + .parser + .parse_slice_header(nalu) + .map_err(|e| anyhow!("parse slice header: {e}"))?; + + if slice.header.dependent_slice_segment_flag { + let header = self + .last_independent_slice_header + .clone() + .context("invalid stream: a dependent slice segment with no independent one before it")?; + slice.replace_header(header).map_err(|e| anyhow!("{e}"))?; + } else { + self.last_independent_slice_header = Some(slice.header.clone()); + } + + // An access unit normally holds one picture, but nothing + // forces that: the first segment of a picture ends the one in + // flight. + if slice.header.first_slice_segment_in_pic_flag { + if let Some(picture) = current.take() { + self.finish_picture(picture)?; + } + } + if current.is_none() { + current = self.begin_picture(&slice, timestamp)?; + } + // `None` is a RASL picture that cannot be decoded, skipped + // one slice at a time. + if let Some(picture) = current.as_mut() { + self.decode_slice(picture, &slice)?; + } + } + NaluType::EosNut => { + if let Some(picture) = current.take() { + self.finish_picture(picture)?; + } + // What follows decodes without reference to anything before, + // so nothing before has to wait for it. + self.drain(); + self.first_picture_after_eos = true; + } + NaluType::EobNut => { + if let Some(picture) = current.take() { + self.finish_picture(picture)?; + } + self.drain(); + self.first_picture_in_bitstream = true; + } + _ => {} + } + } + + // Finishing here rather than on the next unit's first slice keeps the + // hardware from holding a half-submitted picture between calls. + if let Some(picture) = current.take() { + self.finish_picture(picture)?; + } + + Ok(nalus) + } + + /// Moves the DPB to the ready queue and forgets the state the next picture + /// would otherwise be derived against, so it starts the stream over. + pub(super) fn reset(&mut self) { + self.drain(); + self.prev_tid0_pic = None; + self.first_picture_after_eos = true; + self.last_independent_slice_header = None; + } + + /// Moves everything left in the DPB to the ready queue. + fn drain(&mut self) { + self.ready.extend(self.dpb.drain().into_iter().map(|entry| entry.1)); + // What is left is reference pictures already output. + self.dpb.clear(); + } + + /// Applies `sps`, rebuilding the VA config, context, and surface pool if it + /// changes anything they depend on. + fn apply_sps(&mut self, sps: &Rc) -> anyhow::Result<()> { + let format = format(sps)?; + self.dpb.set_max_num_pics(format.max_dpb_frames); + self.sps = Some(Rc::clone(sps)); + + if self.sequence.as_ref().map(|sequence| &sequence.format) == Some(&format) { + return Ok(()); + } + + // Surfaces are about to change shape, so let everything decoded against + // the old sequence out first. Those handles keep their own surfaces (and + // through them the old pool) alive until they are downloaded. + self.drain(); + + self.sequence = Some(Sequence::new(&self.display, format)?); + Ok(()) + } + + /// Starts a picture: derives its order count and reference picture set, + /// makes room in the DPB, and hands the driver the picture parameters. + /// + /// Returns `None` for a RASL picture whose IRAP picture started the stream + /// over, which may refer to pictures that were never decoded (8.1.3). + fn begin_picture(&mut self, slice: &Slice, timestamp: u64) -> anyhow::Result> { + let pps = Rc::clone( + self.parser + .get_pps(slice.header.pic_parameter_set_id) + .context("slice refers to an unknown PPS")?, + ); + let sps = Rc::clone(&pps.sps); + self.apply_sps(&sps)?; + + let max_pic_order_cnt_lsb = 1 << (sps.log2_max_pic_order_cnt_lsb_minus4 + 4); + let pic = PictureData::new_from_slice( + slice, + self.first_picture_in_bitstream, + self.first_picture_after_eos, + self.prev_tid0_pic.as_ref(), + max_pic_order_cnt_lsb, + ); + self.first_picture_in_bitstream = false; + self.first_picture_after_eos = false; + + if pic.nalu_type.is_irap() { + self.irap_no_rasl_output_flag = pic.no_rasl_output_flag; + } else if pic.nalu_type.is_rasl() && self.irap_no_rasl_output_flag { + log::debug!("skipping RASL picture POC {}", pic.pic_order_cnt_val); + return Ok(None); + } + + let rps = self.derive_rps(slice, &sps, &pic, max_pic_order_cnt_lsb); + self.update_dpb_before_decoding(&pic, &sps); + + let references = self.dpb.get_all_references(); + if references.len() > MAX_VA_REFERENCES { + bail!( + "invalid stream: {} reference pictures, more than the {MAX_VA_REFERENCES} a picture can use", + references.len() + ); + } + + // `apply_sps` above either kept the sequence or built a new one. + let sequence = self.sequence.as_ref().expect("sequence built by apply_sps"); + let context = Rc::clone(&sequence.context); + let handle = sequence.handle(timestamp)?; + + let mut picture = Picture::new(timestamp, Rc::clone(&context), handle.clone()); + let pic_param = build_pic_param(&pic, handle.surface.id(), &references, &rps, &sps, &pps); + picture.add_buffer(create_buffer(&context, pic_param)?); + if let Some(lists) = scaling_lists(&sps, &pps) { + picture.add_buffer(create_buffer(&context, build_iq_matrix(lists))?); + } + + log::trace!("starting picture POC {}", pic.pic_order_cnt_val); + Ok(Some(CurrentPicture { + pic, + sps, + rps, + references, + handle, + picture, + last_slice: None, + })) + } + + /// 8.3.2: derives the current picture's reference picture set and marks the + /// DPB to match it. + fn derive_rps(&mut self, slice: &Slice, sps: &Sps, pic: &PictureData, max_pic_order_cnt_lsb: i32) -> RefPicSet { + let hdr = &slice.header; + + if pic.nalu_type.is_irap() && pic.no_rasl_output_flag { + self.dpb.mark_all_as_unused_for_ref(); + } + + let mut rps = RefPicSet::default(); + if !pic.nalu_type.is_idr() { + let poc = pic.pic_order_cnt_val; + + // 8-5: the long-term pictures, found by the low bits of their order + // count unless the stream sent the high bits too. + let mut lt_curr = Vec::new(); + let mut lt_foll = Vec::new(); + for i in 0..usize::from(hdr.num_long_term_sps + hdr.num_long_term_pics) { + let mut poc_lt = hdr.poc_lsb_lt[i] as i32; + let msb = hdr.delta_poc_msb_present_flag[i]; + if msb { + poc_lt += poc + .wrapping_sub((hdr.delta_poc_msb_cycle_lt[i] as i32).wrapping_mul(max_pic_order_cnt_lsb)) + .wrapping_sub(poc & (max_pic_order_cnt_lsb - 1)); + } + if hdr.used_by_curr_pic_lt[i] { + lt_curr.push((poc_lt, msb)); + } else { + lt_foll.push((poc_lt, msb)); + } + } + + let find_long_term = |&(poc, msb): &(i32, bool)| { + let found = if msb { + self.dpb.find_ref_by_poc(poc) + } else { + self.dpb.find_ref_by_poc_masked(poc, max_pic_order_cnt_lsb - 1) + }; + if found.is_none() { + log::warn!("no long-term reference picture with POC {poc}"); + } + found + }; + rps.lt_curr = lt_curr.iter().map(find_long_term).collect(); + rps.lt_foll = lt_foll.iter().map(find_long_term).collect(); + for entry in rps.lt_curr.iter().chain(&rps.lt_foll).flatten() { + entry.0.borrow_mut().set_reference(Reference::LongTerm); + } + + // 8-6: the short-term pictures, after the long-term ones are marked + // so a picture is never both. + let st = if hdr.curr_rps_idx == sps.num_short_term_ref_pic_sets { + &hdr.short_term_ref_pic_set + } else { + &sps.short_term_ref_pic_set[usize::from(hdr.curr_rps_idx)] + }; + let find_short_term = |poc: i32| { + let found = self.dpb.find_short_term_ref_by_poc(poc); + if found.is_none() { + log::warn!("no short-term reference picture with POC {poc}"); + } + found + }; + for i in 0..usize::from(st.num_negative_pics) { + let entry = find_short_term(poc.wrapping_add(st.delta_poc_s0[i])); + if st.used_by_curr_pic_s0[i] { + rps.st_curr_before.push(entry); + } else { + rps.st_foll.push(entry); + } + } + for i in 0..usize::from(st.num_positive_pics) { + let entry = find_short_term(poc.wrapping_add(st.delta_poc_s1[i])); + if st.used_by_curr_pic_s1[i] { + rps.st_curr_after.push(entry); + } else { + rps.st_foll.push(entry); + } + } + } + + // Every reference picture the set leaves out is no longer one. + for entry in self.dpb.entries() { + let kept = rps + .lists() + .iter() + .any(|list| list.iter().flatten().any(|kept| Rc::ptr_eq(&kept.0, &entry.0))); + if !kept { + entry.0.borrow_mut().set_reference(Reference::None); + } + } + + rps + } + + /// C.5.2.2: empties the DPB at a picture that starts the stream over, and + /// otherwise outputs and removes pictures until the current one fits. + fn update_dpb_before_decoding(&mut self, pic: &PictureData, sps: &Sps) { + if pic.nalu_type.is_irap() && pic.no_rasl_output_flag { + if pic.no_output_of_prior_pics_flag { + self.dpb.clear(); + } else { + self.drain(); + } + } else { + self.dpb.remove_unused(); + while self.dpb.needs_bumping(sps) { + let Some(entry) = self.dpb.bump(false) else { + break; + }; + self.ready.push(entry.1); + } + } + } + + /// Hands the driver one slice. The slice before it goes now; this one waits + /// for the next, or for [`Decoder::finish_picture`] to mark it as the last. + fn decode_slice(&mut self, current: &mut CurrentPicture, slice: &Slice) -> anyhow::Result<()> { + // A slice may refer to a different PPS than the one that started the + // picture, as long as it names the same sequence. + let pps = Rc::clone( + self.parser + .get_pps(slice.header.pic_parameter_set_id) + .context("slice refers to an unknown PPS")?, + ); + if format(&pps.sps)? != format(¤t.sps)? { + bail!("invalid stream: the sequence changed between slices of one picture"); + } + + let [list0, list1] = current.rps.ref_pic_lists(&slice.header)?; + let slice_param = build_slice_param(slice, &pps.sps, &list0, &list1, ¤t.references)?; + + let context = &self.sequence.as_ref().expect("sequence built by apply_sps").context; + if let Some((param, data)) = current.last_slice.take() { + add_slice(&mut current.picture, context, param, data)?; + } + current.last_slice = Some((slice_param, slice.nalu.as_ref().to_vec())); + + Ok(()) + } + + /// Submits the picture to the hardware, stores it in the DPB, and outputs + /// what the store leaves over the reorder limit (C.5.2.3). + fn finish_picture(&mut self, current: CurrentPicture) -> anyhow::Result<()> { + let CurrentPicture { + pic, + handle, + mut picture, + last_slice, + .. + } = current; + + let context = &self.sequence.as_ref().expect("sequence built by apply_sps").context; + if let Some((mut param, data)) = last_slice { + param.set_as_last(); + add_slice(&mut picture, context, param, data)?; + } + run(picture)?; + log::trace!("decoded picture POC {}", pic.pic_order_cnt_val); + + // 8.3.1 + if pic.valid_for_prev_tid0_pic { + self.prev_tid0_pic = Some(pic.clone()); + } + + self.dpb + .store_picture(Rc::new(RefCell::new(pic)), handle) + .map_err(|e| anyhow!("store picture in DPB: {e}"))?; + + let sps = self.sps.as_ref().expect("SPS applied by begin_picture"); + while self.dpb.needs_additional_bumping(sps) { + let Some(entry) = self.dpb.bump(false) else { + break; + }; + self.ready.push(entry.1); + } + + Ok(()) + } +} + +/// Checks that `sps` describes something this decoder handles, and returns what +/// its VA state is built for. +fn format(sps: &Sps) -> anyhow::Result { + // A.3: Main, Main 10, and Main Still Picture, either named or claimed + // through the compatibility flags, which is how a stream marks itself + // decodable by a Main decoder. + let ptl = &sps.profile_tier_level; + let main = + matches!(ptl.general_profile_idc, 1..=3) || ptl.general_profile_compatibility_flag[1..=3].contains(&true); + if !main { + bail!( + "only Main profile H.265 is supported, got general_profile_idc {}", + ptl.general_profile_idc + ); + } + if sps.chroma_format_idc != 1 { + bail!( + "only 4:2:0 chroma is supported, got chroma_format_idc {}", + sps.chroma_format_idc + ); + } + if sps.bit_depth_luma_minus8 != 0 || sps.bit_depth_chroma_minus8 != 0 { + bail!( + "only 8-bit samples are supported, got {}-bit luma and {}-bit chroma", + sps.bit_depth_luma_minus8 + 8, + sps.bit_depth_chroma_minus8 + 8 + ); + } + // H.265 codes interlaced video as a sequence of field pictures, each of + // which would otherwise come out as a frame of half the height. + if sps.vui_parameters_present_flag && sps.vui_parameters.field_seq_flag { + bail!("interlaced H.265 is not supported"); + } + + // The conformance window is in chroma samples, two luma samples each way + // for 4:2:0. + let (crop_right, crop_bottom) = if sps.conformance_window_flag { + if sps.conf_win_left_offset != 0 || sps.conf_win_top_offset != 0 { + bail!("left and top H.265 cropping is not supported"); + } + (sps.conf_win_right_offset, sps.conf_win_bottom_offset) + } else { + (0, 0) + }; + let coded = (u32::from(sps.width()), u32::from(sps.height())); + let width = crop_right + .checked_mul(2) + .and_then(|crop| coded.0.checked_sub(crop)) + .context("invalid horizontal H.265 crop")?; + let height = crop_bottom + .checked_mul(2) + .and_then(|crop| coded.1.checked_sub(crop)) + .context("invalid vertical H.265 crop")?; + if width == 0 || height == 0 { + bail!("visible size {width}x{height} is empty"); + } + + // What the level allows, or what the stream says it needs if that is more, + // so a stream that understates its level still fits. + let highest = usize::from(sps.max_sub_layers_minus1); + let buffering = usize::from(sps.max_dec_pic_buffering_minus1[highest]) + 1; + let max_dpb_frames = sps.max_dpb_size().max(buffering).min(MAX_DPB_SIZE); + + Ok(Format { + codec: Codec::H265, + // Main 10 and Main Still Picture streams that reach here hold 8-bit + // samples, which the Main profile decodes. + profile: VAProfile::VAProfileHEVCMain, + coded, + visible: (width, height), + max_dpb_frames, + }) +} + +/// Adds a slice's parameters and data to `picture`. +fn add_slice( + picture: &mut Picture, + context: &Rc, + param: SliceParameterBufferHEVC, + data: Vec, +) -> anyhow::Result<()> { + picture.add_buffer(create_buffer( + context, + BufferType::SliceParameter(SliceParameter::HEVC(param)), + )?); + picture.add_buffer(create_buffer(context, BufferType::SliceData(data))?); + Ok(()) +} + +/// Describes a DPB picture to the driver, flagged with the part of the current +/// reference picture set it is in. +fn va_picture(entry: &DpbEntry, rps: &RefPicSet) -> PictureHEVC { + let pic = entry.0.borrow(); + let contains = + |list: &[Option>]| list.iter().flatten().any(|other| Rc::ptr_eq(&other.0, &entry.0)); + + let mut flags = 0; + if matches!(pic.reference(), Reference::LongTerm) { + flags |= VA_PICTURE_HEVC_LONG_TERM_REFERENCE; + } + if contains(&rps.st_curr_before) { + flags |= VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE; + } else if contains(&rps.st_curr_after) { + flags |= VA_PICTURE_HEVC_RPS_ST_CURR_AFTER; + } else if contains(&rps.lt_curr) { + flags |= VA_PICTURE_HEVC_RPS_LT_CURR; + } + + PictureHEVC::new(entry.1.surface.id(), pic.pic_order_cnt_val, flags) +} + +fn build_pic_param( + pic: &PictureData, + surface_id: crate::VASurfaceID, + references: &[DpbEntry], + rps: &RefPicSet, + sps: &Sps, + pps: &Pps, +) -> BufferType { + let mut reference_frames = [PictureHEVC::new(VA_INVALID_ID, 0, VA_PICTURE_HEVC_INVALID); MAX_VA_REFERENCES]; + for (slot, entry) in reference_frames.iter_mut().zip(references) { + *slot = va_picture(entry, rps); + } + + let pic_fields = HevcPicFields::new( + u32::from(sps.chroma_format_idc), + u32::from(sps.separate_colour_plane_flag), + u32::from(sps.pcm_enabled_flag), + u32::from(sps.scaling_list_enabled_flag), + u32::from(pps.transform_skip_enabled_flag), + u32::from(sps.amp_enabled_flag), + u32::from(sps.strong_intra_smoothing_enabled_flag), + u32::from(pps.sign_data_hiding_enabled_flag), + u32::from(pps.constrained_intra_pred_flag), + u32::from(pps.cu_qp_delta_enabled_flag), + u32::from(pps.weighted_pred_flag), + u32::from(pps.weighted_bipred_flag), + u32::from(pps.transquant_bypass_enabled_flag), + u32::from(pps.tiles_enabled_flag), + u32::from(pps.entropy_coding_sync_enabled_flag), + u32::from(pps.loop_filter_across_slices_enabled_flag), + u32::from(pps.loop_filter_across_tiles_enabled_flag), + u32::from(sps.pcm_loop_filter_disabled_flag), + // NoPicReorderingFlag and NoBiPredFlag are hints, which ffmpeg and + // GStreamer leave unset too. + 0, + 0, + ); + + let rap_pic_flag = (NaluType::BlaWLp..=NaluType::CraNut).contains(&pic.nalu_type); + let slice_parsing_fields = HevcSliceParsingFields::new( + u32::from(pps.lists_modification_present_flag), + u32::from(sps.long_term_ref_pics_present_flag), + u32::from(sps.temporal_mvp_enabled_flag), + u32::from(pps.cabac_init_present_flag), + u32::from(pps.output_flag_present_flag), + u32::from(pps.dependent_slice_segments_enabled_flag), + u32::from(pps.slice_chroma_qp_offsets_present_flag), + u32::from(sps.sample_adaptive_offset_enabled_flag), + u32::from(pps.deblocking_filter_override_enabled_flag), + u32::from(pps.deblocking_filter_disabled_flag), + u32::from(pps.slice_segment_header_extension_present_flag), + u32::from(rap_pic_flag), + u32::from(pic.nalu_type.is_idr()), + u32::from(pic.nalu_type.is_irap()), + ); + + let pic_param = PictureParameterBufferHEVC::new( + va_current_picture(pic, surface_id), + reference_frames, + sps.pic_width_in_luma_samples, + sps.pic_height_in_luma_samples, + &pic_fields, + sps.max_dec_pic_buffering_minus1[usize::from(sps.max_sub_layers_minus1)], + sps.bit_depth_luma_minus8, + sps.bit_depth_chroma_minus8, + sps.pcm_sample_bit_depth_luma_minus1, + sps.pcm_sample_bit_depth_chroma_minus1, + sps.log2_min_luma_coding_block_size_minus3, + sps.log2_diff_max_min_luma_coding_block_size, + sps.log2_min_luma_transform_block_size_minus2, + sps.log2_diff_max_min_luma_transform_block_size, + sps.log2_min_pcm_luma_coding_block_size_minus3, + sps.log2_diff_max_min_pcm_luma_coding_block_size, + sps.max_transform_hierarchy_depth_intra, + sps.max_transform_hierarchy_depth_inter, + pps.init_qp_minus26, + pps.diff_cu_qp_delta_depth, + pps.cb_qp_offset, + pps.cr_qp_offset, + pps.log2_parallel_merge_level_minus2, + pps.num_tile_columns_minus1, + pps.num_tile_rows_minus1, + // VA takes the signalled sizes only; the last of each is inferred. + std::array::from_fn(|i| pps.column_width_minus1[i] as u16), + std::array::from_fn(|i| pps.row_height_minus1[i] as u16), + &slice_parsing_fields, + sps.log2_max_pic_order_cnt_lsb_minus4, + sps.num_short_term_ref_pic_sets, + sps.num_long_term_ref_pics_sps, + pps.num_ref_idx_l0_default_active_minus1, + pps.num_ref_idx_l1_default_active_minus1, + pps.beta_offset_div2, + pps.tc_offset_div2, + pps.num_extra_slice_header_bits, + pic.short_term_ref_pic_set_size_bits, + ); + + BufferType::PictureParameter(PictureParameter::HEVC(pic_param)) +} + +/// Describes the picture being decoded, which is in no reference picture set +/// of its own. +fn va_current_picture(pic: &PictureData, surface_id: crate::VASurfaceID) -> PictureHEVC { + PictureHEVC::new(surface_id, pic.pic_order_cnt_val, 0) +} + +/// The scaling lists a picture is dequantized with, or `None` for the flat +/// default the driver assumes without an IQ matrix (7.3.2.2.1, 7.3.2.3.1). +fn scaling_lists<'a>(sps: &'a Sps, pps: &'a Pps) -> Option<&'a ScalingLists> { + if !sps.scaling_list_enabled_flag { + None + } else if pps.scaling_list_data_present_flag || !sps.scaling_list_data_present_flag { + // The PPS's lists, or the defaults the parser fills in when neither + // parameter set sends any. + Some(&pps.scaling_list) + } else { + Some(&sps.scaling_list) + } +} + +/// 6.5.3: the up-right diagonal scan of a `ROWS` by `ROWS` block, as raster +/// positions. +const fn up_right_diagonal() -> [usize; N] { + let mut scan = [0; N]; + let mut i = 0; + let mut x = 0i32; + let mut y = 0i32; + while i < N { + while y >= 0 { + if x < ROWS as i32 && y < ROWS as i32 { + scan[i] = (x + ROWS as i32 * y) as usize; + i += 1; + } + y -= 1; + x += 1; + } + y = x; + x = 0; + } + scan +} + +const UP_RIGHT_DIAGONAL_4X4: [usize; 16] = up_right_diagonal::<16, 4>(); +const UP_RIGHT_DIAGONAL_8X8: [usize; 64] = up_right_diagonal::<64, 8>(); + +/// Reorders a list coded in up-right diagonal scan into raster order, which is +/// how VA takes it. +fn raster(list: &[u8; N], scan: &[usize; N]) -> [u8; N] { + let mut out = [0; N]; + for (&value, &position) in list.iter().zip(scan) { + out[position] = value; + } + out +} + +fn build_iq_matrix(lists: &ScalingLists) -> BufferType { + // VA carries the 32x32 lists for luma only: matrixId 0 (intra) and 3 + // (inter) of the six the syntax indexes. + BufferType::IQMatrix(IQMatrix::HEVC(IQMatrixBufferHEVC::new( + lists.scaling_list_4x4.map(|list| raster(&list, &UP_RIGHT_DIAGONAL_4X4)), + lists.scaling_list_8x8.map(|list| raster(&list, &UP_RIGHT_DIAGONAL_8X8)), + lists + .scaling_list_16x16 + .map(|list| raster(&list, &UP_RIGHT_DIAGONAL_8X8)), + [0, 3].map(|id| raster(&lists.scaling_list_32x32[id], &UP_RIGHT_DIAGONAL_8X8)), + lists.scaling_list_dc_coef_minus8_16x16.map(|dc| (dc + 8) as u8), + [0, 3].map(|id| (lists.scaling_list_dc_coef_minus8_32x32[id] + 8) as u8), + ))) +} + +/// Maps a slice's reference list onto indices into the picture parameters' +/// reference frames, the way VA takes it. +fn va_ref_pic_list(list: &[Option>], references: &[DpbEntry]) -> [u8; MAX_VA_REFERENCES] { + let mut indices = [0xff; MAX_VA_REFERENCES]; + for (index, entry) in indices.iter_mut().zip(list) { + if let Some(entry) = entry { + if let Some(position) = references + .iter() + .position(|reference| Rc::ptr_eq(&reference.0, &entry.0)) + { + *index = position as u8; + } + } + } + indices +} + +fn build_slice_param( + slice: &Slice, + sps: &Sps, + list0: &[Option>], + list1: &[Option>], + references: &[DpbEntry], +) -> anyhow::Result { + let hdr = &slice.header; + + let long_slice_flags = HevcLongSliceFlags::new( + // Set on the last slice by `finish_picture`, once it is known to be last. + 0, + u32::from(hdr.dependent_slice_segment_flag), + hdr.type_ as u32, + u32::from(hdr.colour_plane_id), + u32::from(hdr.sao_luma_flag), + u32::from(hdr.sao_chroma_flag), + u32::from(hdr.mvd_l1_zero_flag), + u32::from(hdr.cabac_init_flag), + u32::from(hdr.temporal_mvp_enabled_flag), + u32::from(hdr.deblocking_filter_disabled_flag), + u32::from(hdr.collocated_from_l0_flag), + u32::from(hdr.loop_filter_across_slices_enabled_flag), + ); + + let collocated_ref_idx = if hdr.temporal_mvp_enabled_flag { + hdr.collocated_ref_idx + } else { + 0xff + }; + + // 7-56: VA takes the derived chroma offsets, not the deltas the slice codes. + let pwt = &hdr.pred_weight_table; + let half_range = sps.wp_offset_half_range_c as i32; + let chroma_offset = |delta_weight: i8, delta_offset: i16| { + let weight = (1 << pwt.chroma_log2_weight_denom) + i32::from(delta_weight); + let offset = half_range + i32::from(delta_offset) - ((half_range * weight) >> pwt.chroma_log2_weight_denom); + offset.clamp(-half_range, half_range - 1) as i8 + }; + let b = hdr.type_.is_b(); + let mut chroma_offset_l0 = [[0; 2]; MAX_VA_REFERENCES]; + let mut chroma_offset_l1 = [[0; 2]; MAX_VA_REFERENCES]; + for i in 0..MAX_VA_REFERENCES { + for j in 0..2 { + chroma_offset_l0[i][j] = chroma_offset(pwt.delta_chroma_weight_l0[i][j], pwt.delta_chroma_offset_l0[i][j]); + if b { + chroma_offset_l1[i][j] = + chroma_offset(pwt.delta_chroma_weight_l1[i][j], pwt.delta_chroma_offset_l1[i][j]); + } + } + } + let (delta_luma_weight_l1, luma_offset_l1, delta_chroma_weight_l1) = if b { + (pwt.delta_luma_weight_l1, pwt.luma_offset_l1, pwt.delta_chroma_weight_l1) + } else { + Default::default() + }; + + Ok(SliceParameterBufferHEVC::new( + u32::try_from(slice.nalu.size).context("slice NAL unit too large")?, + 0, + VA_SLICE_DATA_FLAG_ALL, + hdr.header_bit_size / 8, + hdr.segment_address, + [va_ref_pic_list(list0, references), va_ref_pic_list(list1, references)], + &long_slice_flags, + collocated_ref_idx, + hdr.num_ref_idx_l0_active_minus1, + hdr.num_ref_idx_l1_active_minus1, + hdr.qp_delta, + hdr.cb_qp_offset, + hdr.cr_qp_offset, + hdr.beta_offset_div2, + hdr.tc_offset_div2, + pwt.luma_log2_weight_denom, + pwt.delta_chroma_log2_weight_denom, + pwt.delta_luma_weight_l0, + pwt.luma_offset_l0, + pwt.delta_chroma_weight_l0, + chroma_offset_l0, + delta_luma_weight_l1, + luma_offset_l1, + delta_chroma_weight_l1, + chroma_offset_l1, + hdr.five_minus_max_num_merge_cand, + u16::try_from(hdr.num_entry_point_offsets).context("too many entry points for a VA slice")?, + 0, + u16::try_from(hdr.n_emulation_prevention_bytes) + .context("too many emulation prevention bytes for a VA slice")?, + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// An SPS this decoder takes: Main, 8-bit 4:2:0, 64x64. + fn main_sps() -> Sps { + let mut sps = Sps { + chroma_format_idc: 1, + pic_width_in_luma_samples: 64, + pic_height_in_luma_samples: 64, + ..Default::default() + }; + sps.profile_tier_level.general_profile_idc = 1; + sps + } + + #[test] + fn a_field_coded_sequence_is_rejected() { + assert!(format(&main_sps()).is_ok()); + + let mut sps = main_sps(); + sps.vui_parameters_present_flag = true; + sps.vui_parameters.field_seq_flag = true; + let err = format(&sps).expect_err("a field-coded sequence decoded as frames"); + assert!(err.to_string().contains("interlaced"), "{err}"); + } + + /// C.5.2.2: a picture is overdue once it has waited SpsMaxLatencyPictures, + /// the reorder limit plus sps_max_latency_increase_plus1 minus one, not + /// sps_max_latency_increase_plus1 alone. + #[test] + fn the_latency_limit_counts_from_the_reorder_limit() { + let mut sps = main_sps(); + sps.max_num_reorder_pics[0] = 2; + sps.max_latency_increase_plus1[0] = 1; + + let mut dpb = Dpb::<()>::default(); + dpb.set_max_num_pics(MAX_DPB_SIZE); + let store = |dpb: &mut Dpb<()>, poc| { + let mut pic = PictureData::default(); + pic.pic_output_flag = true; + pic.pic_order_cnt_val = poc; + dpb.store_picture(Rc::new(RefCell::new(pic)), ()).unwrap(); + }; + + // Two waiting, the oldest for one picture: within both limits. + store(&mut dpb, 0); + store(&mut dpb, 4); + assert!(!dpb.needs_additional_bumping(&sps)); + + // Output the others around POC 4 until it has waited two pictures, which + // is SpsMaxLatencyPictures, with still only two waiting. + dpb.bump(false); + store(&mut dpb, 2); + assert!(!dpb.needs_additional_bumping(&sps)); + dpb.bump(false); + store(&mut dpb, 6); + assert!(dpb.needs_additional_bumping(&sps)); + } + + #[test] + fn the_diagonal_scan_matches_the_specification() { + // 6.5.3, worked by hand for a 4x4 block. + assert_eq!( + UP_RIGHT_DIAGONAL_4X4, + [0, 4, 1, 8, 5, 2, 12, 9, 6, 3, 13, 10, 7, 14, 11, 15] + ); + let mut sorted = UP_RIGHT_DIAGONAL_8X8; + sorted.sort_unstable(); + assert_eq!( + sorted, + std::array::from_fn(|i| i), + "the 8x8 scan visits each position once" + ); + } +}