From efb73fb38106a68b0e3ba89681c9790d4f2f7d33 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 6 Nov 2026 00:50:33 -0800 Subject: [PATCH 1/3] feat(decode)!: H.265 Main decode beside H.264 `decode::Config` gains `codec`, and `Decoder` dispatches to an H.265 decoder ported from discord/cros-codecs's stateless H.265 decoder, with its parser, DPB, and picture modules vendored beside the H.264 ones. It derives reference picture sets (8.3.2) and reference lists (8.3.4), bumps the DPB by the SPS's reorder limit (C.5.2), so a stream with no B-frames comes out as it decodes, skips RASL pictures after a CRA that starts the stream over, and handles dependent slice segments. Screen content and range extensions are left out; Main, plus Main 10 and Main Still Picture streams that hold 8-bit samples, 4:2:0, right/bottom crop. The two decoders share the VA side: a sequence is keyed on a `Format` (codec, VA profile, coded and visible size, DPB size), and submitting a picture goes through one helper. `probe` takes the codec it checks for. Co-Authored-By: Claude Opus 5.5 --- Cargo.toml | 2 +- README.md | 28 +- src/codec.rs | 13 +- src/codec/h265.rs | 7 + src/codec/h265/dpb.rs | 275 +++ src/codec/h265/parser.rs | 4047 +++++++++++++++++++++++++++++++++++++ src/codec/h265/picture.rs | 152 ++ src/decode.rs | 648 ++++-- src/decode/h265.rs | 957 +++++++++ src/lib.rs | 1 + 10 files changed, 5917 insertions(+), 213 deletions(-) create mode 100644 src/codec/h265.rs create mode 100644 src/codec/h265/dpb.rs create mode 100644 src/codec/h265/parser.rs create mode 100644 src/codec/h265/picture.rs create mode 100644 src/decode/h265.rs diff --git a/Cargo.toml b/Cargo.toml index 950508a..da59c27 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 hardware encoder and decoder, derived from discord/cros-libva + discord/cros-codecs" +description = "(AI GENERATED) VA-API H.264 hardware encoder and H.264/H.265 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 93b11e1..081f9d5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ > it. The encoder and the decoder have both been checked against ffmpeg's > software codecs on Intel hardware (see below). -A small, self-contained **VA-API H.264 hardware codec** for Linux (Intel / AMD), +A small, self-contained **VA-API H.264 / H.265 hardware codec** for Linux (Intel / AMD), derived from [discord/cros-libva @ discord-0.0.13](https://github.com/discord/cros-libva/tree/discord-0.0.13) and @@ -37,14 +37,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). - **Pinned, vendored headers.** libva's headers are vendored into [`libva/`](./libva) (see `just vendor`) and fed to bindgen, so generating the bindings needs **no system libva-dev** — only `libclang` — and the pinned @@ -73,7 +80,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 `bindgen_gen.rs`, `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 @@ -88,7 +95,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 thin encode + H.264 and H.265 bitstream layers (`bitstream_utils`, `codec::h264`, + `codec::h265`), 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 6d1d83c..ee0d812 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,2 +1,13 @@ -// 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)] +pub enum Codec { + /// H.264 (AVC). + H264, + /// H.265 (HEVC). + H265, +} 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..1ed79cc --- /dev/null +++ b/src/codec/h265/dpb.rs @@ -0,0 +1,275 @@ +// 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 num_needed_for_output = self.pictures().filter(|pic| pic.needed_for_output).count(); + + let highest_tid = sps.max_sub_layers_minus1; + let max_num_reorder_pics = sps.max_num_reorder_pics[usize::from(highest_tid)]; + let max_latency_increase_plus1 = sps.max_latency_increase_plus1[usize::from(highest_tid)]; + let pic_over_max_latency = self + .pictures() + .find(|pic| pic.needed_for_output && pic.pic_latency_cnt >= i32::from(max_latency_increase_plus1)); + let max_dec_pic_buffering = usize::from(sps.max_dec_pic_buffering_minus1[usize::from(highest_tid)]) + 1; + + num_needed_for_output > max_num_reorder_pics.into() + || (max_latency_increase_plus1 != 0 && pic_over_max_latency.is_some()) + || self.entries().len() >= max_dec_pic_buffering + } + + /// 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 { + let num_needed_for_output = self.pictures().filter(|pic| pic.needed_for_output).count(); + let highest_tid = sps.max_sub_layers_minus1; + + let max_num_reorder_pics = sps.max_num_reorder_pics[usize::from(highest_tid)]; + let max_latency_increase_plus1 = sps.max_latency_increase_plus1[usize::from(highest_tid)]; + + let pic_over_max_latency = self + .pictures() + .find(|pic| pic.needed_for_output && pic.pic_latency_cnt >= i32::from(max_latency_increase_plus1)); + + num_needed_for_output > max_num_reorder_pics.into() + || (max_latency_increase_plus1 != 0 && pic_over_max_latency.is_some()) + } + + /// 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..73db291 --- /dev/null +++ b/src/codec/h265/parser.rs @@ -0,0 +1,4047 @@ +// 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; +// 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; 19], + /// `row_height_minus1[ i ]` plus 1 specifies the height of the i-th tile row + /// in units of CTBs. + pub row_height_minus1: [u32; 21], + /// 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: [u32; 32], + /// 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 = 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_level_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)?; + 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) * (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)?; + for i in 0..hdr.num_entry_point_offsets as usize { + let num_bits = usize::from(hdr.offset_len_minus1 + 1); + hdr.entry_point_offset_minus1[i] = r.read_bits(num_bits)?; + } + } + } + + 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..53d626e 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,150 @@ 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 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 Ok(mut decoder) = Decoder::new(Config::new()) else { - eprintln!("skipping: no VA-API H.264 decoder"); + 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 units = split_access_units(&stream); 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 +2364,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 +2452,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..1aa7d60 --- /dev/null +++ b/src/decode/h265.rs @@ -0,0 +1,957 @@ +//! 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 + ); + } + + // 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, + pps.column_width_minus1.map(|width| width as u16), + pps.row_height_minus1.map(|height| height 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::*; + + #[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" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1dd7449..4165b58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ pub use bindings::_VADRMPRIMESurfaceDescriptor__bindgen_ty_1 as VADRMPRIMESurfac pub use bindings::_VADRMPRIMESurfaceDescriptor__bindgen_ty_2 as VADRMPRIMESurfaceDescriptorLayer; pub use bindings::*; pub use buffer::*; +pub use codec::Codec; pub use color::{Color, Matrix}; pub use config::*; pub use context::*; From 38dfca1d2c7c46bce8946adcd818eba7cae1683f Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 6 Nov 2026 00:58:52 -0800 Subject: [PATCH 2/3] fix(decode): H.265 parsing and output fixes from review - Keep every slice entry point: a 4K picture with wavefronts signals 33 at 64-pixel CTBs, past the 32 a fixed array held. - Read a sublayer's profile on its profile-present flag and its level on its level-present flag, rather than both on the level flag. - Size the tile arrays for the inferred last column and row, and refuse more tiles than any level allows. - Count a picture's output latency against SpsMaxLatencyPictures, the reorder limit plus sps_max_latency_increase_plus1 minus one (C.5.2.2). - Refuse a field-coded sequence instead of putting each field out as a frame. Co-Authored-By: Claude Opus 5.5 --- src/codec/h265/dpb.rs | 38 +++++++++++------------ src/codec/h265/parser.rs | 41 +++++++++++++++++-------- src/decode.rs | 29 ++++++++++++++++++ src/decode/h265.rs | 66 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 138 insertions(+), 36 deletions(-) diff --git a/src/codec/h265/dpb.rs b/src/codec/h265/dpb.rs index 1ed79cc..3ac0614 100644 --- a/src/codec/h265/dpb.rs +++ b/src/codec/h265/dpb.rs @@ -117,19 +117,26 @@ impl Dpb { /// Whether the DPB needs bumping. See C.5.2.2. pub fn needs_bumping(&mut self, sps: &Sps) -> bool { - let num_needed_for_output = self.pictures().filter(|pic| pic.needed_for_output).count(); - let highest_tid = sps.max_sub_layers_minus1; - let max_num_reorder_pics = sps.max_num_reorder_pics[usize::from(highest_tid)]; - let max_latency_increase_plus1 = sps.max_latency_increase_plus1[usize::from(highest_tid)]; + 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() - .find(|pic| pic.needed_for_output && pic.pic_latency_cnt >= i32::from(max_latency_increase_plus1)); - let max_dec_pic_buffering = usize::from(sps.max_dec_pic_buffering_minus1[usize::from(highest_tid)]) + 1; + .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.is_some()) - || self.entries().len() >= max_dec_pic_buffering + 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. @@ -176,18 +183,7 @@ impl Dpb { /// See C.5.2.3. Happens when we are done decoding the picture. pub fn needs_additional_bumping(&mut self, sps: &Sps) -> bool { - let num_needed_for_output = self.pictures().filter(|pic| pic.needed_for_output).count(); - let highest_tid = sps.max_sub_layers_minus1; - - let max_num_reorder_pics = sps.max_num_reorder_pics[usize::from(highest_tid)]; - let max_latency_increase_plus1 = sps.max_latency_increase_plus1[usize::from(highest_tid)]; - - let pic_over_max_latency = self - .pictures() - .find(|pic| pic.needed_for_output && pic.pic_latency_cnt >= i32::from(max_latency_increase_plus1)); - - num_needed_for_output > max_num_reorder_pics.into() - || (max_latency_increase_plus1 != 0 && pic_over_max_latency.is_some()) + self.output_overdue(sps) } /// Clears the DPB, dropping all the pictures. diff --git a/src/codec/h265/parser.rs b/src/codec/h265/parser.rs index 73db291..909afbe 100644 --- a/src/codec/h265/parser.rs +++ b/src/codec/h265/parser.rs @@ -24,6 +24,10 @@ const MAX_VPS_COUNT: usize = 16; 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; @@ -1236,10 +1240,10 @@ pub struct Pps { 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; 19], + 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; 21], + 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 @@ -1713,7 +1717,7 @@ pub struct SliceHeader { /// 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: [u32; 32], + pub entry_point_offset_minus1: Vec, /// Same as NumPicTotalCurr in the specification. pub num_pic_total_curr: u32, // Size of slice_header() in bits. @@ -1809,7 +1813,7 @@ impl<'a> Slice<'a> { let segment_address = self.header.segment_address; let offset_len_minus1 = self.header.offset_len_minus1; - let entry_point_offset_minus1 = self.header.entry_point_offset_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; @@ -2453,7 +2457,7 @@ impl Parser { } for i in 0..sps_max_sub_layers_minus_1 as usize { - if ptl.sub_layer_level_present_flag[i] { + 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)?; @@ -2533,11 +2537,11 @@ impl Parser { } 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)?; - } + 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(()) @@ -3431,6 +3435,15 @@ impl Parser { 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; @@ -4001,10 +4014,12 @@ impl Parser { 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)?; - for i in 0..hdr.num_entry_point_offsets as usize { - let num_bits = usize::from(hdr.offset_len_minus1 + 1); - hdr.entry_point_offset_minus1[i] = r.read_bits(num_bits)?; - } + // 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::>()?; } } diff --git a/src/decode.rs b/src/decode.rs index 53d626e..feb3661 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -2253,6 +2253,35 @@ mod tests { assert!(decoder.flush().expect("flush the decoder").is_empty()); } + /// 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 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 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] diff --git a/src/decode/h265.rs b/src/decode/h265.rs index 1aa7d60..8941e92 100644 --- a/src/decode/h265.rs +++ b/src/decode/h265.rs @@ -580,6 +580,11 @@ fn format(sps: &Sps) -> anyhow::Result { 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. @@ -740,8 +745,9 @@ fn build_pic_param( pps.log2_parallel_merge_level_minus2, pps.num_tile_columns_minus1, pps.num_tile_rows_minus1, - pps.column_width_minus1.map(|width| width as u16), - pps.row_height_minus1.map(|height| height as u16), + // 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, @@ -939,6 +945,62 @@ fn build_slice_param( 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. From d1c7c3ad48e0f11d7d0ce0fcbd3e37edef47fbe0 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 6 Nov 2026 01:00:25 -0800 Subject: [PATCH 3/3] fix(decode): widen tile counts before bounding entry points Twenty columns by twenty-two rows of tiles now parse, and their product overflowed the u8 counts it was taken in. Co-Authored-By: Claude Opus 5.5 --- src/codec/h265/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codec/h265/parser.rs b/src/codec/h265/parser.rs index 909afbe..aa93d78 100644 --- a/src/codec/h265/parser.rs +++ b/src/codec/h265/parser.rs @@ -4006,7 +4006,7 @@ impl Parser { 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) * (pps.num_tile_rows_minus1 + 1) - 1) + (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 };