diff --git a/Cargo.lock b/Cargo.lock index 07348727..d0b5c8dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1164,6 +1164,13 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "effectcraft-av1enc" +version = "0.1.0" +dependencies = [ + "filmcraft-av1", +] + [[package]] name = "effectcraft-cli" version = "0.1.0" @@ -1238,7 +1245,9 @@ dependencies = [ name = "effectcraft-export" version = "0.1.0" dependencies = [ + "effectcraft-av1enc", "effectcraft-color", + "effectcraft-hevcenc", "effectcraft-keyframe", "effectcraft-media", "effectcraft-opusenc", @@ -1305,6 +1314,13 @@ dependencies = [ "wgpu", ] +[[package]] +name = "effectcraft-hevcenc" +version = "0.1.0" +dependencies = [ + "filmcraft-hevc", +] + [[package]] name = "effectcraft-host" version = "0.1.0" @@ -1393,6 +1409,9 @@ dependencies = [ [[package]] name = "effectcraft-opusenc" version = "0.1.0" +dependencies = [ + "filmcraft-opus", +] [[package]] name = "effectcraft-path" diff --git a/Cargo.toml b/Cargo.toml index 6641f2c4..d5f6101a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ effectcraft-ui-egui = { path = "crates/ui-egui" } effectcraft-automation = { path = "crates/automation" } effectcraft-opusenc = { path = "crates/opusenc" } effectcraft-script = { path = "crates/script" } +effectcraft-hevcenc = { path = "crates/hevcenc" } +effectcraft-av1enc = { path = "crates/av1enc" } effectcraft-plugin = { path = "crates/plugin" } # foundation @@ -80,6 +82,9 @@ filmcraft-isobmff = { git = "https://github.com/storytold/filmcraft", rev = "89c # decoder used as the round-trip oracle of effectcraft-opusenc (crates/export dev-dependency) filmcraft-opus = { git = "https://github.com/storytold/filmcraft", rev = "89cfbf24f698b0f0e538eb7795929f19bbfb1dbb" } filmcraft-vp9 = { git = "https://github.com/storytold/filmcraft", rev = "89cfbf24f698b0f0e538eb7795929f19bbfb1dbb" } +# decoders used as the round-trip oracles of effectcraft-hevcenc / effectcraft-av1enc (dev-dependencies only) +filmcraft-hevc = { git = "https://github.com/storytold/filmcraft", rev = "89cfbf24f698b0f0e538eb7795929f19bbfb1dbb" } +filmcraft-av1 = { git = "https://github.com/storytold/filmcraft", rev = "89cfbf24f698b0f0e538eb7795929f19bbfb1dbb" } # gpu (the same wgpu eframe / egui-wgpu use) wgpu = { version = "30.0.1", features = ["fragile-send-sync-non-atomic-wasm"] } @@ -152,6 +157,12 @@ opt-level = 3 [profile.dev.package.effectcraft-vp9enc] opt-level = 3 +[profile.dev.package.effectcraft-hevcenc] +opt-level = 3 + +[profile.dev.package.effectcraft-av1enc] +opt-level = 3 + # FilmCraft codec hot loops (same settings as FilmCraft's own workspace). [profile.dev.package.filmcraft-h264] opt-level = 3 diff --git a/apps/effectcraft-cli/src/main.rs b/apps/effectcraft-cli/src/main.rs index 8aa1381b..09a9f233 100644 --- a/apps/effectcraft-cli/src/main.rs +++ b/apps/effectcraft-cli/src/main.rs @@ -9,9 +9,11 @@ //! effectcraft-cli get [--time S] read a property //! effectcraft-cli set [--time S] [--expression E] //! effectcraft-cli render-frame [--comp C] [--time S|--frame N] [--max-side PX|--scale K] [--out F.png] -//! effectcraft-cli render [--comp C] --out FILE [--format h264|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff] [--start S] [--end S] +//! effectcraft-cli render [--comp C] --out FILE [--format h264|hevc|av1|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff] [--start S] [--end S] //! [--work-area] [--fps N] [--resolution full|half|third|quarter|K] [--quality best|draft] [--channels rgb|rgba] //! [--jpeg-quality N] [--bitrate KBPS] [--prores proxy|lt|standard|hq|4444|4444xq] [--audio auto|on|off] +//! [--profile main|main10] [--level auto|4.1] [--rate-control bitrate|quality] [--video-quality 1-100] +//! [--keyint FRAMES] [--webm-codec vp9|av1] [--audio-bitrate KBPS] [--opus-app audio|voice] //! effectcraft-cli render F.ecproj --queue render the project's Render Queue //! effectcraft-cli bench [--comp C] [--time S] [--scale K] [--n N] [--play N] [--gpu] render timings //! (--gpu: CPU vs GPU ms/frame for every comp at Full and Half) @@ -44,7 +46,11 @@ const USAGE: &str = "usage: effectcraft-cli Result<(), Failure> { if let Some(v) = args.num("--bitrate")? { p["bitrate"] = json!(v); } + for (flag, key) in [ + ("--profile", "profile"), + ("--level", "level"), + ("--rate-control", "rateControl"), + ("--webm-codec", "webmCodec"), + ("--opus-app", "opusApplication"), + ] { + if let Some(v) = args.opt(flag) { + p[key] = json!(v); + } + } + for (flag, key) in [("--video-quality", "quality"), ("--keyint", "keyframeInterval"), ("--audio-bitrate", "audioBitrate")] { + if let Some(v) = args.num(flag)? { + p[key] = json!(v); + } + } // Render exactly this item: unqueue whatever the project's queue already holds. for k in 0..s.project.render_queue.len() { let _ = s.execute("renderQueue.setRender", json!({"index": k + 1, "render": false})); diff --git a/crates/av1enc/Cargo.toml b/crates/av1enc/Cargo.toml new file mode 100644 index 00000000..2a4c7ddf --- /dev/null +++ b/crates/av1enc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "effectcraft-av1enc" +description = "Clean-room pure-Rust AV1 encoder (Main profile, key and inter frames) for MP4/WebM export, from the AV1 Bitstream & Decoding Process Specification" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] + +[dev-dependencies] +filmcraft-av1 = { workspace = true } diff --git a/crates/av1enc/README.md b/crates/av1enc/README.md new file mode 100644 index 00000000..6ced20ba --- /dev/null +++ b/crates/av1enc/README.md @@ -0,0 +1,74 @@ +# effectcraft-av1enc + +Clean-room, pure-Rust AV1 encoder for EffectCraft's MP4 / WebM export (layer L0, `std` only, +no `unsafe`, builds for `wasm32-unknown-unknown`). + +Output is decoded **bit-exactly** by libdav1d (`ffmpeg -c:v libdav1d`, checked in +`tests/ffmpeg_oracle.rs` when ffmpeg is installed) and by FilmCraft's spec-derived +`filmcraft-av1` decoder (`tests/roundtrip.rs`): every decoded frame equals the encoder's own +reconstruction, so prediction never drifts. + +## What is implemented + +- **Stream**: Main profile (`seq_profile` 0), 4:2:0, 8 or 10 bit, BT.709 colour description, + limited or full range. Sequence header with one operating point; `seq_level_idx` given or the + lowest Annex A level whose MaxPicSize / MaxH/VSize / MaxDisplayRate (and, in bitrate mode, Main + tier MaxBitrate) fit. Order hints, film grain, superres, CDEF, loop restoration, intra edge + filter, filter intra, inter-intra, masked / distance compound, warped motion and dual filter + are disabled in the sequence header. +- **Packets**: one temporal unit per frame as stored in MP4 / Matroska samples: OBUs with size + fields, no temporal delimiter; key frames start with the sequence header OBU, followed by one + `OBU_FRAME` (frame header + tile group). Every frame is shown, in input order. + `Encoder::av1c()` returns the `AV1CodecConfigurationRecord` (also the WebM `CodecPrivate`). +- **Frames**: a `KEY_FRAME` every `keyint` frames, `INTER_FRAME`s otherwise. Inter frames use + slot 0 (the previous reconstruction) for all seven references and refresh slot 0 only, so + `LAST_FRAME` is always the previous frame. CDFs adapt per symbol and are saved at the end of + every frame; inter frames load them from `LAST_FRAME` (`primary_ref_frame` = 0). One tile, + `TX_MODE_LARGEST`, `reduced_tx_set`, no segmentation, no delta q / lf. +- **Deblocking**: the normative loop filter (7.14) runs on the reconstruction; the frame level + (the same for both luma directions and both chroma planes, no deltas, sharpness 0) is searched + per frame for the lowest luma error. +- **Tiles**: the multi-symbol arithmetic encoder is the exact inverse of the spec's symbol + decoder (8.2), including the exit process padding; CDF adaptation as in 8.2.6. +- **Partitions**: 64x64 superblocks, `PARTITION_NONE` / `PARTITION_SPLIT` down to 8x8 chosen by + a rate-distortion cost (SSE + λ·estimated bits); blocks crossing the frame edge are split as + the syntax requires. 64x64 blocks are only used as inter skip blocks. +- **Intra**: DC, V, H, Paeth, smooth, smooth-V, smooth-H for luma and chroma, + ranked by SATD with a rate-distortion choice among the best three; the spec's edge preparation + and above-right / below-left availability. +- **Inter**: single reference (`LAST_FRAME`), `NEWMV` / `NEARESTMV` / `NEARMV` / `GLOBALMV` + chosen from the spec's motion vector prediction stack (7.10.2, mirrored exactly); motion search + from neighbour / co-located / parent candidates, a whole-sample diamond search and half- then + quarter-sample refinement (`allow_high_precision_mv` = 0); the normative 8-tap `EIGHTTAP` + prediction for luma and chroma; skip blocks; per-block intra / inter choice. +- **Residual**: one transform per plane and block (4x4 ... 32x32): for intra luma `DCT_DCT` or + the mode's preferred `ADST` combination (rate-distortion choice over the two best modes), the + mode-dependent type for intra chroma, `DCT_DCT` for inter blocks; dead-zone quantisation with the + spec's `Dc_Qlookup` / `Ac_Qlookup`; the normative dequantisation and inverse transforms in the + reconstruction loop; full coefficient coding (all_zero, tx type, eob_pt, eob_extra, + coeff_base_eob, coeff_base, coeff_br, dc_sign, Golomb) with the exact contexts. +- **Rate control**: `ConstantQ(base_q_idx)`; `Bitrate { kbps }` adapts `base_q_idx` per frame + towards the average target (key frames get a lower quantizer and a larger share; the first + key frame is searched in a few passes). + +## Limits + +- One tile: width and height up to 4096 and at most 2304 superblocks (4096x2304 samples). +- No CDEF or loop restoration, no compound prediction, no rectangular or 4xN blocks, no + directional intra modes other than V / H, no CfL or palette, no transform-size splits, so + compression is below mature encoders. +- 4:2:0 only (Main profile), no 12 bit. + +## Specification and provenance + +Implemented from the **AV1 Bitstream & Decoding Process Specification, version 1.0.0 with +Errata 1** (AOMediaCodec, 2019; the edition FilmCraft's decoder follows) and the **AV1 Codec +ISO Media File Format Binding** (v1.2.0) for `av1C`. + +The constant tables (default CDFs, scan orders, quantizer lookup tables, interpolation filter +taps, smooth weights, block / transform size tables, ...) and the decoder-side processes the +encoder must reproduce exactly (inverse transforms, intra and inter prediction rounding, context +derivations, motion vector prediction) are mirrored from FilmCraft's first-party, spec-derived +`filmcraft-av1` decoder (MIT OR Apache-2.0), which is also the round-trip oracle. No source of +libaom, dav1d, rav1e, SVT-AV1, ffmpeg or any other third-party encoder or decoder was consulted; +ffmpeg / libdav1d is used only as an external test oracle. diff --git a/crates/av1enc/src/bitw.rs b/crates/av1enc/src/bitw.rs new file mode 100644 index 00000000..e4c8a1e6 --- /dev/null +++ b/crates/av1enc/src/bitw.rs @@ -0,0 +1,87 @@ +//! MSB-first bit writer for the OBU headers (spec 4.10 `f(n)` descriptors) and `leb128()`. + +#[derive(Default)] +pub(crate) struct BitWriter { + pub buf: Vec, + nbits: usize, +} + +impl BitWriter { + pub fn new() -> Self { + BitWriter::default() + } + + /// `f(n)`: writes the low `n` bits of `v`, most significant first. + pub fn f(&mut self, n: u32, v: u32) { + for i in (0..n).rev() { + self.bit((v >> i) & 1 != 0); + } + } + + pub fn bit(&mut self, b: bool) { + if self.nbits.is_multiple_of(8) { + self.buf.push(0); + } + if b { + let last = self.buf.len() - 1; + self.buf[last] |= 0x80 >> (self.nbits % 8); + } + self.nbits += 1; + } + + /// `byte_alignment()`: zero bits up to the next byte boundary. + pub fn byte_align(&mut self) { + while !self.nbits.is_multiple_of(8) { + self.bit(false); + } + } + + /// `trailing_bits()`: a one bit followed by zero bits up to the byte boundary. + pub fn trailing_bits(&mut self) { + self.bit(true); + self.byte_align(); + } + + pub fn into_bytes(self) -> Vec { + self.buf + } +} + +/// `leb128()` encoding of an OBU size. +pub(crate) fn leb128(mut v: usize, out: &mut Vec) { + loop { + let mut b = (v & 0x7f) as u8; + v >>= 7; + if v != 0 { + b |= 0x80; + } + out.push(b); + if v == 0 { + break; + } + } +} + +/// An OBU with `obu_has_size_field` = 1 and no extension header. +pub(crate) fn obu(obu_type: u8, payload: &[u8], out: &mut Vec) { + out.push((obu_type << 3) | 0x02); + leb128(payload.len(), out); + out.extend_from_slice(payload); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bits_and_leb128() { + let mut w = BitWriter::new(); + w.f(3, 0b101); + w.f(6, 0b110011); + w.trailing_bits(); + assert_eq!(w.into_bytes(), vec![0b1011_1001, 0b1100_0000]); + let mut v = Vec::new(); + leb128(300, &mut v); + assert_eq!(v, vec![0xAC, 0x02]); + } +} diff --git a/crates/av1enc/src/cdf.rs b/crates/av1enc/src/cdf.rs new file mode 100644 index 00000000..0466a159 --- /dev/null +++ b/crates/av1enc/src/cdf.rs @@ -0,0 +1,164 @@ +//! The adaptive CDFs this encoder codes with (the subset of spec 6.8.2 init_non_coeff_cdfs / +//! init_coeff_cdfs that its syntax uses). Layout as in filmcraft-av1: each innermost array +//! holds N cumulative values (the last is 32768) plus the adaptation counter. + +use crate::tables::*; + +#[derive(Clone)] +pub(crate) struct Cdfs { + pub y_mode: [[u16; 14]; 4], + pub uv_mode_cfl_not_allowed: [[u16; 14]; 13], + pub uv_mode_cfl_allowed: [[u16; 15]; 13], + pub angle_delta: [[u16; 8]; 8], + pub partition_w8: [[u16; 5]; 4], + pub partition_w16: [[u16; 11]; 4], + pub partition_w32: [[u16; 11]; 4], + pub partition_w64: [[u16; 11]; 4], + pub skip: [[u16; 3]; 3], + pub is_inter: [[u16; 3]; 4], + pub single_ref: [[[u16; 3]; 6]; 3], + pub new_mv: [[u16; 3]; 6], + pub zero_mv: [[u16; 3]; 2], + pub ref_mv: [[u16; 3]; 6], + pub drl_mode: [[u16; 3]; 3], + pub mv_joint: [u16; 5], + pub mv_class: [[u16; 12]; 2], + pub mv_class0_bit: [[u16; 3]; 2], + pub mv_fr: [[u16; 5]; 2], + pub mv_class0_fr: [[[u16; 5]; 2]; 2], + pub mv_sign: [[u16; 3]; 2], + pub mv_bit: [[[u16; 3]; 10]; 2], + pub intra_tx_type_set2: [[[u16; 6]; 13]; 3], + pub inter_tx_type_set3: [[u16; 3]; 4], + pub txb_skip: [[[u16; 3]; 13]; 5], + pub eob_pt_16: [[[u16; 6]; 2]; 2], + pub eob_pt_32: [[[u16; 7]; 2]; 2], + pub eob_pt_64: [[[u16; 8]; 2]; 2], + pub eob_pt_128: [[[u16; 9]; 2]; 2], + pub eob_pt_256: [[[u16; 10]; 2]; 2], + pub eob_pt_512: [[u16; 11]; 2], + pub eob_pt_1024: [[u16; 12]; 2], + pub eob_extra: [[[[u16; 3]; 9]; 2]; 5], + pub dc_sign: [[[u16; 3]; 3]; 2], + pub coeff_base_eob: [[[[u16; 4]; 4]; 2]; 5], + pub coeff_base: [[[[u16; 5]; 42]; 2]; 5], + pub coeff_br: [[[[u16; 5]; 21]; 2]; 5], +} + +impl Cdfs { + /// init_non_coeff_cdfs( ) followed by init_coeff_cdfs( ) for `base_q_idx`. + pub fn new(base_q_idx: u32) -> Box { + let idx = match base_q_idx { + 0..=20 => 0, + 21..=60 => 1, + 61..=120 => 2, + _ => 3, + }; + Box::new(Cdfs { + y_mode: DEFAULT_Y_MODE_CDF, + uv_mode_cfl_not_allowed: DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF, + uv_mode_cfl_allowed: DEFAULT_UV_MODE_CFL_ALLOWED_CDF, + angle_delta: DEFAULT_ANGLE_DELTA_CDF, + partition_w8: DEFAULT_PARTITION_W8_CDF, + partition_w16: DEFAULT_PARTITION_W16_CDF, + partition_w32: DEFAULT_PARTITION_W32_CDF, + partition_w64: DEFAULT_PARTITION_W64_CDF, + skip: DEFAULT_SKIP_CDF, + is_inter: DEFAULT_IS_INTER_CDF, + single_ref: DEFAULT_SINGLE_REF_CDF, + new_mv: DEFAULT_NEW_MV_CDF, + zero_mv: DEFAULT_ZERO_MV_CDF, + ref_mv: DEFAULT_REF_MV_CDF, + drl_mode: DEFAULT_DRL_MODE_CDF, + mv_joint: DEFAULT_MV_JOINT_CDF, + mv_class: DEFAULT_MV_CLASS_CDF, + mv_class0_bit: [DEFAULT_MV_CLASS0_BIT_CDF; 2], + mv_fr: DEFAULT_MV_FR_CDF, + mv_class0_fr: DEFAULT_MV_CLASS0_FR_CDF, + mv_sign: [DEFAULT_MV_SIGN_CDF; 2], + mv_bit: [DEFAULT_MV_BIT_CDF; 2], + intra_tx_type_set2: DEFAULT_INTRA_TX_TYPE_SET2_CDF, + inter_tx_type_set3: DEFAULT_INTER_TX_TYPE_SET3_CDF, + txb_skip: DEFAULT_TXB_SKIP_CDF[idx], + eob_pt_16: DEFAULT_EOB_PT_16_CDF[idx], + eob_pt_32: DEFAULT_EOB_PT_32_CDF[idx], + eob_pt_64: DEFAULT_EOB_PT_64_CDF[idx], + eob_pt_128: DEFAULT_EOB_PT_128_CDF[idx], + eob_pt_256: DEFAULT_EOB_PT_256_CDF[idx], + eob_pt_512: DEFAULT_EOB_PT_512_CDF[idx], + eob_pt_1024: DEFAULT_EOB_PT_1024_CDF[idx], + eob_extra: DEFAULT_EOB_EXTRA_CDF[idx], + dc_sign: DEFAULT_DC_SIGN_CDF[idx], + coeff_base_eob: DEFAULT_COEFF_BASE_EOB_CDF[idx], + coeff_base: DEFAULT_COEFF_BASE_CDF[idx], + coeff_br: DEFAULT_COEFF_BR_CDF[idx], + }) + } +} + +impl Cdfs { + /// The counter reset of load_cdfs( ): the last entry of every CDF array is set to 0. + pub fn clear_counts(&mut self) { + macro_rules! clr { + ($($f:ident),*) => { $( self.$f.clear_count(); )* }; + } + clr!( + y_mode, + uv_mode_cfl_not_allowed, + uv_mode_cfl_allowed, + angle_delta, + partition_w8, + partition_w16, + partition_w32, + partition_w64, + skip, + is_inter, + single_ref, + new_mv, + zero_mv, + ref_mv, + drl_mode, + mv_joint, + mv_class, + mv_class0_bit, + mv_fr, + mv_class0_fr, + mv_sign, + mv_bit, + intra_tx_type_set2, + inter_tx_type_set3, + txb_skip, + eob_pt_16, + eob_pt_32, + eob_pt_64, + eob_pt_128, + eob_pt_256, + eob_pt_512, + eob_pt_1024, + eob_extra, + dc_sign, + coeff_base_eob, + coeff_base, + coeff_br + ); + } +} + +/// Zeroes the adaptation counter (last element) of every innermost CDF array. +trait ClearCount { + fn clear_count(&mut self); +} + +impl ClearCount for [u16; N] { + fn clear_count(&mut self) { + self[N - 1] = 0; + } +} + +impl ClearCount for [T; M] { + fn clear_count(&mut self) { + for e in self.iter_mut() { + e.clear_count(); + } + } +} diff --git a/crates/av1enc/src/decide.rs b/crates/av1enc/src/decide.rs new file mode 100644 index 00000000..e71ad6e9 --- /dev/null +++ b/crates/av1enc/src/decide.rs @@ -0,0 +1,874 @@ +//! Mode decision and reconstruction (not normative): partitions, intra modes, motion vectors, +//! transform coefficients. Every prediction and reconstruction step uses the normative +//! processes, so the reconstruction built here is exactly what a decoder produces; the syntax +//! is written afterwards by `writer.rs`. + +use crate::pred::*; +use crate::tables::*; +use crate::transform::{forward_2d, inverse_2d}; + +/// Decisions for one coded block. +#[derive(Clone)] +pub(crate) struct Leaf { + pub bsize: usize, + pub is_inter: bool, + pub y_mode: usize, + pub uv_mode: usize, + pub tx_type_y: usize, + /// Motion vector [row, col] in 1/8 luma samples (inter blocks). + pub mv: [i32; 2], + pub skip: bool, + /// Quantised coefficients of the single transform block of each plane (raster order). + pub coefs: [Vec; 3], +} + +pub(crate) enum Node { + Out, + Leaf(Box), + Split(Box<[Node; 4]>), +} + +/// A reconstructed frame kept as a reference. +pub(crate) struct RefFrame { + pub planes: [Plane; 3], + /// Motion vectors chosen for the frame (per 4x4), used as search candidates. + pub mvs: Vec<[i32; 2]>, + /// The frame's loop filter level. + pub lf_level: u32, + /// The CDFs saved at the end of the frame (loaded by the next frame). + pub cdfs: Box, +} + +pub(crate) struct FrameGeom { + pub width: usize, + pub height: usize, + pub mi_cols: usize, + pub mi_rows: usize, + pub sb_cols: usize, + pub sb_rows: usize, +} + +impl FrameGeom { + pub fn new(width: usize, height: usize) -> FrameGeom { + let mi_cols = 2 * width.div_ceil(8); + let mi_rows = 2 * height.div_ceil(8); + FrameGeom { width, height, mi_cols, mi_rows, sb_cols: mi_cols.div_ceil(16), sb_rows: mi_rows.div_ceil(16) } + } + /// Padded plane size (whole superblocks). + pub fn padded(&self, plane: usize) -> (usize, usize) { + let s = (plane > 0) as usize; + ((self.sb_cols * 64) >> s, (self.sb_rows * 64) >> s) + } + /// Visible plane size. + pub fn visible(&self, plane: usize) -> (usize, usize) { + if plane == 0 { (self.width, self.height) } else { (self.width.div_ceil(2), self.height.div_ceil(2)) } + } +} + +pub(crate) fn tx_size_for(log2: u32) -> usize { + match log2 { + 2 => TX_4X4, + 3 => TX_8X8, + 4 => TX_16X16, + 5 => TX_32X32, + _ => TX_64X64, + } +} + +/// get_tx_set( ) for the square sizes used here with reduced_tx_set = 1. +pub(crate) fn tx_set(tx_sz: usize, is_inter: bool) -> usize { + let sqr_up = TX_SIZE_SQR_UP[tx_sz] as usize; + if sqr_up > TX_32X32 { + return TX_SET_DCTONLY; + } + if is_inter { + TX_SET_INTER_3 + } else if sqr_up == TX_32X32 { + TX_SET_DCTONLY + } else { + TX_SET_INTRA_2 + } +} + +pub(crate) fn tx_in_set(set: usize, is_inter: bool, tx_type: usize) -> bool { + if is_inter { TX_TYPE_IN_SET_INTER[set][tx_type] == 1 } else { TX_TYPE_IN_SET_INTRA[set][tx_type] == 1 } +} + +/// The chroma transform type of an intra block (compute_tx_type for plane > 0). +pub(crate) fn intra_uv_tx_type(uv_mode: usize, uv_tx: usize) -> usize { + let t = MODE_TO_TXFM[uv_mode] as usize; + if tx_in_set(tx_set(uv_tx, false), false, t) { t } else { DCT_DCT } +} + +struct TxResult { + levels: Vec, + nonzero: bool, + dist: f64, + bits: f64, +} + +/// Saved encoder state of a block area (reconstruction, BlockDecoded flags). +struct Snapshot { + planes: [Vec; 3], + decoded: Box<[[[bool; 35]; 35]; 3]>, +} + +pub(crate) struct Decider<'a> { + pub g: &'a FrameGeom, + pub bd: u32, + pub intra_only: bool, + pub src: &'a [Plane; 3], + pub rec: [Plane; 3], + pub refr: Option<&'a RefFrame>, + decoded: Box<[[[bool; 35]; 35]; 3]>, + /// Decision motion vectors per 4x4 (search candidates). + pub mvs: Vec<[i32; 2]>, + dc_q: f32, + ac_q: f32, + dc_qi: i64, + ac_qi: i64, + lambda: f64, + lambda_sad: f64, + /// Half-sample interpolations of the reference luma (visible area): (8, 0), (0, 8), (8, 8). + half: [Vec; 3], +} + +const MODE_BITS_INTRA: f64 = 3.0; + +impl<'a> Decider<'a> { + pub fn new(g: &'a FrameGeom, bd: u32, qidx: u8, intra_only: bool, src: &'a [Plane; 3], refr: Option<&'a RefFrame>) -> Self { + let bdi = ((bd - 8) >> 1) as usize; + let dc_qi = DC_QLOOKUP[bdi][qidx as usize] as i64; + let ac_qi = AC_QLOOKUP[bdi][qidx as usize] as i64; + // Lagrangian multiplier in squared-sample units of the coded bit depth. + let step = ac_qi as f64 / 8.0; + let lambda = 0.25 * step * step; + let rec = std::array::from_fn(|p| { + let (w, h) = g.padded(p); + Plane::new(w, h) + }); + Decider { + g, + bd, + intra_only, + src, + rec, + refr, + decoded: Box::new([[[false; 35]; 35]; 3]), + mvs: vec![[0, 0]; g.mi_cols * g.mi_rows], + dc_q: dc_qi as f32, + ac_q: ac_qi as f32, + dc_qi, + ac_qi, + lambda, + lambda_sad: lambda.sqrt(), + half: match refr { + Some(rf) => [(8, 0), (0, 8), (8, 8)].map(|(fx, fy)| subpel_plane(&rf.planes[0], g.width, g.height, fx, fy, bd)), + None => Default::default(), + }, + } + } + + /// Decides one 64x64 superblock (reconstruction is written to `rec`). + pub fn superblock(&mut self, r: usize, c: usize) -> Node { + // clear_block_decoded_flags( r, c, sbSize4 ) + for plane in 0..3 { + let s = (plane > 0) as usize; + let sb_w4 = (self.g.mi_cols as isize - c as isize) >> s; + let sb_h4 = (self.g.mi_rows as isize - r as isize) >> s; + let bd = &mut self.decoded[plane]; + for y in -1..=((16 >> s) as isize) { + for x in -1..=((16 >> s) as isize) { + let v = if y < 0 && x < sb_w4 { true } else { x < 0 && y < sb_h4 }; + bd[(y + 1) as usize][(x + 1) as usize] = v; + } + } + bd[(16 >> s) + 1][0] = false; + } + self.partition(r, c, BLOCK_64X64, None).0 + } + + fn decoded_at(&self, plane: usize, y: isize, x: isize) -> bool { + let (yy, xx) = (y + 1, x + 1); + if yy < 0 || xx < 0 || yy > 34 || xx > 34 { + return false; + } + self.decoded[plane][yy as usize][xx as usize] + } + + /// Marks the transform block of `plane` covering the block at (r, c) as decoded. + fn mark_decoded(&mut self, plane: usize, r: usize, c: usize, n4: usize) { + let s = (plane > 0) as usize; + let by = (r & 15) >> s; + let bx = (c & 15) >> s; + let step = (n4 >> s).max(1); + for i in 0..step { + for j in 0..step { + if by + i + 1 < 35 && bx + j + 1 < 35 { + self.decoded[plane][by + i + 1][bx + j + 1] = true; + } + } + } + } + + fn snapshot(&self, r: usize, c: usize, n4: usize) -> Snapshot { + let planes = std::array::from_fn(|p| { + let s = (p > 0) as usize; + let (x0, y0, n) = ((c * 4) >> s, (r * 4) >> s, (n4 * 4) >> s); + let pl = &self.rec[p]; + let mut v = Vec::with_capacity(n * n); + for y in y0..y0 + n { + v.extend_from_slice(&pl.data[y * pl.stride + x0..y * pl.stride + x0 + n]); + } + v + }); + Snapshot { planes, decoded: self.decoded.clone() } + } + + fn restore(&mut self, snap: &Snapshot, r: usize, c: usize, n4: usize) { + for p in 0..3 { + let s = (p > 0) as usize; + let (x0, y0, n) = ((c * 4) >> s, (r * 4) >> s, (n4 * 4) >> s); + let pl = &mut self.rec[p]; + for (i, y) in (y0..y0 + n).enumerate() { + pl.data[y * pl.stride + x0..y * pl.stride + x0 + n].copy_from_slice(&snap.planes[p][i * n..i * n + n]); + } + } + self.decoded.clone_from(&snap.decoded); + } + + fn set_mvs(&mut self, r: usize, c: usize, n4: usize, mv: [i32; 2]) { + for y in r..(r + n4).min(self.g.mi_rows) { + for x in c..(c + n4).min(self.g.mi_cols) { + self.mvs[y * self.g.mi_cols + x] = mv; + } + } + } + + fn partition(&mut self, r: usize, c: usize, bsize: usize, parent_mv: Option<[i32; 2]>) -> (Node, f64) { + let g = self.g; + if r >= g.mi_rows || c >= g.mi_cols { + return (Node::Out, 0.0); + } + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let half = n4 >> 1; + let has_rows = r + half < g.mi_rows; + let has_cols = c + half < g.mi_cols; + let can_none = has_rows && has_cols && (bsize != BLOCK_64X64 || !self.intra_only); + let can_split = bsize > BLOCK_8X8; + let sub = PARTITION_SUBSIZE[PARTITION_SPLIT][bsize] as usize; + if !can_none { + return self.split(r, c, sub, half, parent_mv); + } + let before = self.snapshot(r, c, n4); + let (leaf, cost_none) = self.leaf(r, c, bsize, parent_mv); + let cost_none = cost_none + self.lambda * 1.0; + if !can_split { + return (Node::Leaf(Box::new(leaf)), cost_none); + } + // a cheap skip block rarely gains from splitting + if leaf.skip && cost_none < self.lambda * 8.0 { + return (Node::Leaf(Box::new(leaf)), cost_none); + } + let after_none = self.snapshot(r, c, n4); + self.restore(&before, r, c, n4); + let pm = if leaf.is_inter { Some(leaf.mv) } else { parent_mv }; + let (node, cost_split) = self.split(r, c, sub, half, pm); + let cost_split = cost_split + self.lambda * 2.0; + if cost_split < cost_none { + (node, cost_split) + } else { + self.restore(&after_none, r, c, n4); + self.set_mvs(r, c, n4, if leaf.is_inter { leaf.mv } else { [0, 0] }); + (Node::Leaf(Box::new(leaf)), cost_none) + } + } + + fn split(&mut self, r: usize, c: usize, sub: usize, half: usize, pm: Option<[i32; 2]>) -> (Node, f64) { + let (n0, c0) = self.partition(r, c, sub, pm); + let (n1, c1) = self.partition(r, c + half, sub, pm); + let (n2, c2) = self.partition(r + half, c, sub, pm); + let (n3, c3) = self.partition(r + half, c + half, sub, pm); + (Node::Split(Box::new([n0, n1, n2, n3])), c0 + c1 + c2 + c3) + } + + /// Best coding of the block as a single (NONE partition) block. + fn leaf(&mut self, r: usize, c: usize, bsize: usize, parent_mv: Option<[i32; 2]>) -> (Leaf, f64) { + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + if self.intra_only { + let res = self.intra_leaf(r, c, bsize); + self.set_mvs(r, c, n4, [0, 0]); + return res; + } + let before = self.snapshot(r, c, n4); + let (inter, cost_inter) = self.inter_leaf(r, c, bsize, parent_mv); + if bsize == BLOCK_64X64 || inter.skip { + self.set_mvs(r, c, n4, inter.mv); + return (inter, cost_inter); + } + let after_inter = self.snapshot(r, c, n4); + self.restore(&before, r, c, n4); + let (intra, cost_intra) = self.intra_leaf(r, c, bsize); + if cost_intra < cost_inter { + self.set_mvs(r, c, n4, [0, 0]); + (intra, cost_intra) + } else { + self.restore(&after_inter, r, c, n4); + self.set_mvs(r, c, n4, inter.mv); + (inter, cost_inter) + } + } + + fn intra_leaf(&mut self, r: usize, c: usize, bsize: usize) -> (Leaf, f64) { + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let log2 = (n4 * 4).trailing_zeros(); + let (sbr, sbc) = ((r & 15) as isize, (c & 15) as isize); + let bd = self.bd; + let mut pred = vec![0u16; 32 * 32]; + let mut best_pred = vec![0u16; 32 * 32]; + let n = 1usize << log2; + // luma + let e = IntraEdge { + x: c * 4, + y: r * 4, + have_left: c > 0, + have_above: r > 0, + have_above_right: self.decoded_at(0, sbr - 1, sbc + n4 as isize), + have_below_left: self.decoded_at(0, sbr + n4 as isize, sbc - 1), + log2, + max_x: (self.g.mi_cols * 4) as i32 - 1, + max_y: (self.g.mi_rows * 4) as i32 - 1, + bit_depth: bd, + }; + let ed = edges(&self.rec[0], &e); + // modes ranked by SATD, then a rate-distortion choice of mode and transform type among + // the best three + let mut ranked: Vec<(f64, usize)> = INTRA_MODES_USED + .iter() + .map(|&mode| { + predict_intra(&ed, &e, mode, &mut pred); + let sad = self.satd(0, c * 4, r * 4, n, &pred[..n * n]) as f64; + (sad + self.lambda_sad * if mode == DC_PRED { 1.0 } else { MODE_BITS_INTRA }, mode) + }) + .collect(); + ranked.sort_by(|a, b| a.0.total_cmp(&b.0)); + let tx_y = tx_size_for(log2); + let mut best: Option<(f64, usize, usize, TxResult, Option>)> = None; + for &(_, mode) in ranked.iter().take(3) { + predict_intra(&ed, &e, mode, &mut pred); + let mut types = vec![DCT_DCT]; + let alt = MODE_TO_TXFM[mode] as usize; + if alt != DCT_DCT && tx_in_set(tx_set(tx_y, false), false, alt) { + types.push(alt); + } + for &t in &types { + let (res, recon) = self.try_tx(0, c * 4, r * 4, log2, t, &pred[..n * n], true); + let type_bits = if !res.nonzero || tx_set(tx_y, false) == TX_SET_DCTONLY { + 0.0 + } else if t == DCT_DCT { + 1.0 + } else { + 2.0 + }; + let mode_bits = if mode == DC_PRED { 1.0 } else { MODE_BITS_INTRA }; + let cost = res.dist + self.lambda * (res.bits + type_bits + mode_bits); + if best.as_ref().is_none_or(|b| cost < b.0) { + best_pred[..n * n].copy_from_slice(&pred[..n * n]); + best = Some((cost, mode, t, res, recon)); + } + } + } + let (_, y_mode, tx_type_y, ty, recon) = best.expect("an intra mode"); + self.put(0, c * 4, r * 4, n, recon.as_deref().unwrap_or(&best_pred[..n * n])); + let tx_type_y = if ty.nonzero { tx_type_y } else { DCT_DCT }; + self.mark_decoded(0, r, c, n4); + // chroma (one mode for both planes) + let cn = n >> 1; + let clog2 = log2 - 1; + let (csbr, csbc) = (sbr >> 1, sbc >> 1); + let ce: [IntraEdge; 2] = std::array::from_fn(|i| IntraEdge { + x: c * 2, + y: r * 2, + have_left: c > 0, + have_above: r > 0, + have_above_right: self.decoded_at(1 + i, csbr - 1, csbc + (n4 >> 1) as isize), + have_below_left: self.decoded_at(1 + i, csbr + (n4 >> 1) as isize, csbc - 1), + log2: clog2, + max_x: (self.g.mi_cols * 2) as i32 - 1, + max_y: (self.g.mi_rows * 2) as i32 - 1, + bit_depth: bd, + }); + let ced = [edges(&self.rec[1], &ce[0]), edges(&self.rec[2], &ce[1])]; + let mut best_uv = (f64::MAX, DC_PRED); + for &mode in INTRA_MODES_USED.iter() { + let mut sad = 0u64; + for i in 0..2 { + predict_intra(&ced[i], &ce[i], mode, &mut pred); + sad += self.satd(1 + i, c * 2, r * 2, cn, &pred[..cn * cn]); + } + let cost = sad as f64 + self.lambda_sad * if mode == DC_PRED { 1.0 } else { 2.5 }; + if cost < best_uv.0 { + best_uv = (cost, mode); + } + } + let uv_mode = best_uv.1; + let uv_tx = tx_size_for(clog2); + let uv_type = intra_uv_tx_type(uv_mode, uv_tx); + let mut tuv = Vec::with_capacity(2); + for i in 0..2 { + predict_intra(&ced[i], &ce[i], uv_mode, &mut pred); + tuv.push(self.code_tx(1 + i, c * 2, r * 2, clog2, uv_type, &pred[..cn * cn], true)); + self.mark_decoded(1 + i, r, c, n4); + } + let skip = !ty.nonzero && !tuv[0].nonzero && !tuv[1].nonzero; + let dist = ty.dist + tuv[0].dist + tuv[1].dist; + let bits = if skip { 1.0 } else { ty.bits + tuv[0].bits + tuv[1].bits + 1.0 } + MODE_BITS_INTRA + 2.0; + let [u, v]: [TxResult; 2] = tuv.try_into().ok().expect("two chroma planes"); + let leaf = Leaf { bsize, is_inter: false, y_mode, uv_mode, tx_type_y, mv: [0, 0], skip, coefs: [ty.levels, u.levels, v.levels] }; + (leaf, dist + self.lambda * bits) + } + + fn inter_leaf(&mut self, r: usize, c: usize, bsize: usize, parent_mv: Option<[i32; 2]>) -> (Leaf, f64) { + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let n = n4 * 4; + let log2 = n.trailing_zeros(); + let (mv, mv_bits) = self.motion_search(r, c, n, parent_mv); + let refr = self.refr.expect("reference frame"); + let mut pred = vec![0u16; n * n]; + let mut tx = Vec::with_capacity(3); + let mut dist = 0.0; + for p in 0..3 { + let s = (p > 0) as usize; + let (vw, vh) = self.g.visible(p); + let pn = n >> s; + let (x, y) = ((c * 4) >> s, (r * 4) >> s); + predict_inter(&refr.planes[p], vw as i32 - 1, vh as i32 - 1, x, y, pn, pn, mv, s, self.bd, &mut pred[..pn * pn]); + if bsize == BLOCK_64X64 { + // coded as a skip block: the prediction is the reconstruction + dist += self.sse(p, x, y, pn, &pred[..pn * pn]); + self.put(p, x, y, pn, &pred[..pn * pn]); + } else { + let t = self.code_tx(p, x, y, log2 - s as u32, DCT_DCT, &pred[..pn * pn], false); + dist += t.dist; + tx.push(t); + } + self.mark_decoded(p, r, c, n4); + } + let mode_bits = if mv == [0, 0] { 1.5 } else { 2.0 + mv_bits }; + if bsize == BLOCK_64X64 { + let leaf = Leaf { bsize, is_inter: true, y_mode: NEWMV, uv_mode: DC_PRED, tx_type_y: DCT_DCT, mv, skip: true, coefs: Default::default() }; + return (leaf, dist + self.lambda * (mode_bits + 1.0)); + } + let skip = tx.iter().all(|t| !t.nonzero); + let bits = mode_bits + if skip { 1.0 } else { 1.0 + tx.iter().map(|t| t.bits).sum::() }; + let [y, u, v]: [TxResult; 3] = tx.try_into().ok().expect("three planes"); + let leaf = Leaf { bsize, is_inter: true, y_mode: NEWMV, uv_mode: DC_PRED, tx_type_y: DCT_DCT, mv, skip, coefs: [y.levels, u.levels, v.levels] }; + (leaf, dist + self.lambda * bits) + } + + /// Full-sample motion search: candidate vectors then a shrinking diamond search. + /// Returns the vector and an estimate of its coding cost in bits. + fn motion_search(&self, r: usize, c: usize, n: usize, parent_mv: Option<[i32; 2]>) -> ([i32; 2], f64) { + let g = self.g; + let refr = self.refr.expect("reference frame"); + let mi = |rr: usize, cc: usize| self.mvs[rr * g.mi_cols + cc]; + let mut pred_mv = [0, 0]; + let mut cands: Vec<[i32; 2]> = vec![[0, 0]]; + if c > 0 { + pred_mv = mi(r, c - 1); + cands.push(pred_mv); + } + if r > 0 { + cands.push(mi(r - 1, c)); + let cr = c + n / 4; + if cr < g.mi_cols { + cands.push(mi(r - 1, cr)); + } + } + if let Some(p) = parent_mv { + cands.push(p); + } + cands.push(refr.mvs[r * g.mi_cols + c]); + let (x0, y0) = ((c * 4) as i32, (r * 4) as i32); + let (w, h) = (g.width as i32, g.height as i32); + // keep the referenced block within 64 samples of the picture + let lim_x = (-x0 - n as i32 - 64, w - x0 + 64); + let lim_y = (-y0 - n as i32 - 64, h - y0 + 64); + let valid = |m: [i32; 2]| { + let (dy, dx) = (m[0] >> 3, m[1] >> 3); + dx >= lim_x.0 && dx <= lim_x.1 && dy >= lim_y.0 && dy <= lim_y.1 + }; + let mv_bits = |m: [i32; 2]| -> f64 { + let comp = |d: i32| if d == 0 { 0.5 } else { 2.0 + 2.0 * ((d.abs() / 8) as f64 + 1.0).log2() }; + comp(m[0] - pred_mv[0]) + comp(m[1] - pred_mv[1]) + }; + let cost = + |m: [i32; 2]| -> f64 { self.sad_ref(&refr.planes[0], x0 + (m[1] >> 3), y0 + (m[0] >> 3), c * 4, r * 4, n) as f64 + self.lambda_sad * mv_bits(m) }; + let mut best = ([0, 0], f64::MAX); + for &m in &cands { + let m = [m[0] & !7, m[1] & !7]; + if !valid(m) { + continue; + } + let cst = cost(m); + if cst < best.1 { + best = (m, cst); + } + } + let mut step = 8 * if n >= 32 { + 16 + } else if n == 16 { + 8 + } else { + 4 + }; + while step >= 8 { + let mut moved = true; + let mut iters = 0; + while moved && iters < 16 { + moved = false; + iters += 1; + let center = best.0; + for d in [[-step, 0], [step, 0], [0, -step], [0, step]] { + let m = [center[0] + d[0], center[1] + d[1]]; + if !valid(m) { + continue; + } + let cst = cost(m); + if cst < best.1 { + best = (m, cst); + moved = true; + } + } + } + step >>= 1; + } + // half- then quarter-sample refinement (plus the exact neighbour candidates) + let mut buf = vec![0u16; n * n]; + let sub_cost = |m: [i32; 2], buf: &mut Vec| -> f64 { + self.search_block(m, c * 4, r * 4, n, buf); + self.sad(0, c * 4, r * 4, n, buf) as f64 + self.lambda_sad * mv_bits(m) + }; + for m in cands { + if m[0] & 7 != 0 || m[1] & 7 != 0 { + let m = [m[0] & !1, m[1] & !1]; + if valid(m) { + let cst = sub_cost(m, &mut buf); + if cst < best.1 { + best = (m, cst); + } + } + } + } + for step in [4, 2] { + let center = best.0; + for d in [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]] { + let m = [center[0] + d[0] * step, center[1] + d[1] * step]; + if !valid(m) { + continue; + } + let cst = sub_cost(m, &mut buf); + if cst < best.1 { + best = (m, cst); + } + } + } + (best.0, mv_bits(best.0)) + } + + /// Approximate luma prediction for motion search: exact at whole and half-sample + /// positions (precomputed planes), averaged neighbours at quarter-sample positions. + fn search_block(&self, mv: [i32; 2], x: usize, y: usize, n: usize, out: &mut [u16]) { + let refr = self.refr.expect("reference frame"); + let (w, h) = (self.g.width as i32, self.g.height as i32); + // per axis: up to two (integer offset, half) taps + let axis = |v: i32| -> ([(i32, bool); 2], usize) { + let p = 2 * v; // 1/16 units + let (i, f) = (p >> 4, p & 15); + match f { + 0 => ([(i, false), (i, false)], 1), + 8 => ([(i, true), (i, true)], 1), + 4 => ([(i, false), (i, true)], 2), + _ => ([(i, true), (i + 1, false)], 2), + } + }; + let (ax, nx) = axis(mv[1]); + let (ay, ny) = axis(mv[0]); + let taps = nx.max(ny); + let fetch = |k: usize, xx: i32, yy: i32| -> u16 { + let (ox, hx) = ax[k.min(nx - 1)]; + let (oy, hy) = ay[k.min(ny - 1)]; + let sx = (xx + ox).clamp(0, w - 1) as usize; + let sy = (yy + oy).clamp(0, h - 1) as usize; + match (hx, hy) { + (false, false) => refr.planes[0].at(sx, sy), + (true, false) => self.half[0][sy * w as usize + sx], + (false, true) => self.half[1][sy * w as usize + sx], + (true, true) => self.half[2][sy * w as usize + sx], + } + }; + let vw = n.min(self.g.width.saturating_sub(x)); + let vh = n.min(self.g.height.saturating_sub(y)); + // fast path: every tap inside the picture + let (x0, y0) = (x as i32, y as i32); + let inside = (0..taps).all(|k| { + let (ox, _) = ax[k.min(nx - 1)]; + let (oy, _) = ay[k.min(ny - 1)]; + x0 + ox >= 0 && y0 + oy >= 0 && x0 + ox + vw as i32 <= w && y0 + oy + vh as i32 <= h + }); + if inside { + let src = |k: usize| -> (&[u16], usize, usize) { + let (ox, hx) = ax[k.min(nx - 1)]; + let (oy, hy) = ay[k.min(ny - 1)]; + let (sx, sy) = ((x0 + ox) as usize, (y0 + oy) as usize); + match (hx, hy) { + (false, false) => (&refr.planes[0].data, refr.planes[0].stride, sy * refr.planes[0].stride + sx), + (true, false) => (&self.half[0], w as usize, sy * w as usize + sx), + (false, true) => (&self.half[1], w as usize, sy * w as usize + sx), + (true, true) => (&self.half[2], w as usize, sy * w as usize + sx), + } + }; + let (a, sa, oa) = src(0); + if taps == 1 { + for i in 0..vh { + out[i * n..i * n + vw].copy_from_slice(&a[oa + i * sa..oa + i * sa + vw]); + } + } else { + let (b, sb, ob) = src(1); + for i in 0..vh { + let (ra, rb) = (&a[oa + i * sa..oa + i * sa + vw], &b[ob + i * sb..ob + i * sb + vw]); + for (o, (&p, &q)) in out[i * n..i * n + vw].iter_mut().zip(ra.iter().zip(rb)) { + *o = ((p as u32 + q as u32 + 1) >> 1) as u16; + } + } + } + return; + } + for i in 0..vh { + for j in 0..vw { + let (xx, yy) = (x0 + j as i32, y0 + i as i32); + out[i * n + j] = if taps == 1 { fetch(0, xx, yy) } else { ((fetch(0, xx, yy) as u32 + fetch(1, xx, yy) as u32 + 1) >> 1) as u16 }; + } + } + } + + /// SAD of the source block at (x, y) (luma) against the reference block at (rx, ry). + fn sad_ref(&self, refp: &Plane, rx: i32, ry: i32, x: usize, y: usize, n: usize) -> u64 { + let (vw, vh) = (self.g.width, self.g.height); + let w = n.min(vw.saturating_sub(x)); + let h = n.min(vh.saturating_sub(y)); + let src = &self.src[0]; + let (lx, ly) = (vw as i32 - 1, vh as i32 - 1); + let mut sad = 0u64; + let inside = rx >= 0 && ry >= 0 && rx + w as i32 - 1 <= lx && ry + h as i32 - 1 <= ly; + for i in 0..h { + let s = &src.row(y + i)[x..x + w]; + let rr = refp.row((ry + i as i32).clamp(0, ly) as usize); + if inside { + let rrow = &rr[rx as usize..rx as usize + w]; + sad += s.iter().zip(rrow).map(|(&a, &b)| (a as i32 - b as i32).unsigned_abs()).sum::() as u64; + } else { + for j in 0..w { + let b = rr[(rx + j as i32).clamp(0, lx) as usize]; + sad += (s[j] as i32 - b as i32).unsigned_abs() as u64; + } + } + } + sad + } + + /// Sum of absolute Hadamard-transformed differences (4x4 or 8x8 sub-blocks) of a prediction + /// against the source, scaled to about the size of the SAD. + fn satd(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 { + let src = &self.src[p]; + let k = n.min(8); + let mut total = 0u64; + let mut d = [0i32; 64]; + for by in (0..n).step_by(k) { + for bx in (0..n).step_by(k) { + for i in 0..k { + let row = &src.row(y + by + i)[x + bx..x + bx + k]; + for j in 0..k { + d[i * k + j] = row[j] as i32 - pred[(by + i) * n + bx + j] as i32; + } + } + for i in 0..k { + hadamard(&mut d[i * k..i * k + k]); + } + let mut col = [0i32; 8]; + let mut s = 0u64; + for j in 0..k { + for i in 0..k { + col[i] = d[i * k + j]; + } + hadamard(&mut col[..k]); + s += col[..k].iter().map(|v| v.unsigned_abs() as u64).sum::(); + } + total += s / (k as u64 / 2); + } + } + total + } + + /// SAD of a prediction against the source over the visible part of the block. + fn sad(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 { + let (vw, vh) = self.g.visible(p); + let w = n.min(vw.saturating_sub(x)); + let h = n.min(vh.saturating_sub(y)); + let src = &self.src[p]; + let mut s = 0u64; + for i in 0..h { + let row = &src.row(y + i)[x..x + w]; + s += row.iter().zip(&pred[i * n..i * n + w]).map(|(&a, &b)| (a as i32 - b as i32).unsigned_abs()).sum::() as u64; + } + s + } + + fn sse(&self, p: usize, x: usize, y: usize, n: usize, blk: &[u16]) -> f64 { + let (vw, vh) = self.g.visible(p); + let w = n.min(vw.saturating_sub(x)); + let h = n.min(vh.saturating_sub(y)); + let src = &self.src[p]; + let mut s = 0u64; + for i in 0..h { + let row = &src.row(y + i)[x..x + w]; + s += row + .iter() + .zip(&blk[i * n..i * n + w]) + .map(|(&a, &b)| { + let d = a as i64 - b as i64; + (d * d) as u64 + }) + .sum::(); + } + s as f64 + } + + fn put(&mut self, p: usize, x: usize, y: usize, n: usize, blk: &[u16]) { + let pl = &mut self.rec[p]; + for i in 0..n { + let o = (y + i) * pl.stride + x; + pl.data[o..o + n].copy_from_slice(&blk[i * n..i * n + n]); + } + } + + /// Transform, quantise and reconstruct one square transform block of `plane` at (x, y) + /// over the prediction `pred`; the reconstruction is written to `rec`. + fn code_tx(&mut self, p: usize, x: usize, y: usize, log2: u32, tx_type: usize, pred: &[u16], intra: bool) -> TxResult { + let (res, recon) = self.try_tx(p, x, y, log2, tx_type, pred, intra); + let n = 1usize << log2; + self.put(p, x, y, n, recon.as_deref().unwrap_or(pred)); + res + } + + /// Transform and quantise one block; returns the result and its reconstruction (None: the + /// prediction) without writing it. + fn try_tx(&self, p: usize, x: usize, y: usize, log2: u32, tx_type: usize, pred: &[u16], intra: bool) -> (TxResult, Option>) { + let n = 1usize << log2; + let tx_sz = tx_size_for(log2); + let bd = self.bd; + let src = &self.src[p]; + let mut res = vec![0i32; n * n]; + for i in 0..n { + let row = &src.row(y + i)[x..x + n]; + for j in 0..n { + res[i * n + j] = row[j] as i32 - pred[i * n + j] as i32; + } + } + let mut coef = vec![0f32; n * n]; + forward_2d(&res, &mut coef, n, tx_type); + // dead-zone quantiser: rounding offset below one half + let rnd = if intra { 0.38f32 } else { 0.30f32 }; + let mut levels = vec![0i32; n * n]; + let mut nonzero = false; + for (k, (&cf, l)) in coef.iter().zip(levels.iter_mut()).enumerate() { + let q = if k == 0 { self.dc_q } else { self.ac_q }; + let a = (cf.abs() / q + rnd).floor() as i32; + if a != 0 { + *l = if cf < 0.0 { -a.min(1 << 16) } else { a.min(1 << 16) }; + nonzero = true; + } + } + let zero_dist = self.sse(p, x, y, n, pred); + if !nonzero { + return (TxResult { levels, nonzero, dist: zero_dist, bits: 1.0 }, None); + } + let recon = self.reconstruct(&levels, tx_sz, tx_type, pred, n, bd); + let dist = self.sse(p, x, y, n, &recon); + let bits = estimate_bits(&levels, tx_sz); + if dist + self.lambda * bits >= zero_dist + self.lambda { + levels.iter_mut().for_each(|l| *l = 0); + return (TxResult { levels, nonzero: false, dist: zero_dist, bits: 1.0 }, None); + } + (TxResult { levels, nonzero: true, dist, bits }, Some(recon)) + } + + /// Dequantisation (7.12.3) and the inverse transform, added to the prediction. + fn reconstruct(&self, levels: &[i32], tx_sz: usize, tx_type: usize, pred: &[u16], n: usize, bd: u32) -> Vec { + let denom: i64 = if tx_sz == TX_32X32 { 2 } else { 1 }; + let lim = 1i64 << (7 + bd); + let mut deq = vec![0i32; n * n]; + for (k, (&l, d)) in levels.iter().zip(deq.iter_mut()).enumerate() { + if l == 0 { + continue; + } + let q = if k == 0 { self.dc_qi } else { self.ac_qi }; + let dq = l as i64 * q; + let mag = (dq.abs() & 0xFF_FFFF) / denom; + *d = (if dq < 0 { -mag } else { mag }).clamp(-lim, lim - 1) as i32; + } + let mut resid = vec![0i32; n * n]; + inverse_2d(&deq, &mut resid, tx_sz, tx_type, bd); + let max = (1i32 << bd) - 1; + pred.iter().zip(&resid).map(|(&p, &r)| (p as i32 + r).clamp(0, max) as u16).collect() + } +} + +/// Unnormalised Walsh-Hadamard transform of 4 or 8 values, in place. +fn hadamard(v: &mut [i32]) { + let n = v.len(); + let mut h = 1; + while h < n { + for i in (0..n).step_by(2 * h) { + for j in i..i + h { + let (a, b) = (v[j], v[j + h]); + v[j] = a + b; + v[j + h] = a - b; + } + } + h *= 2; + } +} + +/// Rough coefficient coding cost in bits. +fn estimate_bits(levels: &[i32], tx_sz: usize) -> f64 { + let scan = scan_for(tx_sz); + let mut eob = 0; + for (i, &pos) in scan.iter().enumerate() { + if levels[pos as usize] != 0 { + eob = i + 1; + } + } + if eob == 0 { + return 1.0; + } + let mut bits = 3.0 + (eob as f64).log2(); + for &pos in &scan[..eob] { + let a = levels[pos as usize].unsigned_abs(); + bits += match a { + 0 => 0.7, + 1 => 2.5, + _ => 3.5 + 1.6 * (a as f64).log2(), + }; + } + bits +} + +pub(crate) fn scan_for(tx_sz: usize) -> &'static [u16] { + match tx_sz { + TX_4X4 => &DEFAULT_SCAN_4X4, + TX_8X8 => &DEFAULT_SCAN_8X8, + TX_16X16 => &DEFAULT_SCAN_16X16, + _ => &DEFAULT_SCAN_32X32, + } +} diff --git a/crates/av1enc/src/lib.rs b/crates/av1enc/src/lib.rs new file mode 100644 index 00000000..0eeded9c --- /dev/null +++ b/crates/av1enc/src/lib.rs @@ -0,0 +1,518 @@ +//! Clean-room pure-Rust AV1 encoder (Main profile, 4:2:0, 8 and 10 bit), implemented from the +//! AV1 Bitstream & Decoding Process Specification v1.0.0 with Errata 1. +//! +//! Key frames and single-reference inter frames (LAST_FRAME = the previous frame), 64x64 +//! superblocks split down to 8x8 by rate-distortion decisions, intra modes DC / V / H / Paeth / +//! smooth, full-sample motion search with the spec's motion vector prediction, DCT / ADST +//! transforms up to 32x32, one tile per frame, in-loop filters off. See the crate README. +//! +//! The public API below is the contract `crates/export` codes against; keep it stable. + +mod bitw; +mod cdf; +mod decide; +mod loopfilter; +mod pred; +mod symbol; +mod tables; +mod transform; +mod writer; + +use bitw::{BitWriter, obu}; +use decide::{Decider, FrameGeom, RefFrame}; +use loopfilter::{LfInfo, loop_filter}; +use pred::Plane; +use writer::TileWriter; + +/// Rate control. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RateControl { + /// Constant quantizer index (`base_q_idx`, 1–255; lower is better). + ConstantQ(u8), + /// Average bitrate target in kbit/s (frame-level quantizer adaptation). + Bitrate { kbps: u32 }, +} + +/// Encoder settings. Main profile (seq_profile 0), 4:2:0. +#[derive(Clone, Debug, PartialEq)] +pub struct EncoderConfig { + pub width: u32, + pub height: u32, + pub fps_num: u32, + pub fps_den: u32, + /// 8 or 10. + pub bit_depth: u8, + /// `seq_level_idx` (e.g. 8 = level 4.0); `None` picks the lowest level that fits. + pub level_idx: Option, + pub rate: RateControl, + /// Frames between key frames (1 = all key frames). + pub keyint: u32, + /// BT.709 limited range is signalled unless this is set. + pub full_range: bool, +} + +impl EncoderConfig { + pub fn new(width: u32, height: u32, fps_num: u32, fps_den: u32) -> Self { + EncoderConfig { width, height, fps_num, fps_den, bit_depth: 8, level_idx: None, rate: RateControl::ConstantQ(100), keyint: 60, full_range: false } + } +} + +/// One input picture, 4:2:0, samples at the configured bit depth. +#[derive(Clone, Copy, Debug)] +pub struct Frame<'a> { + pub y: &'a [u16], + pub u: &'a [u16], + pub v: &'a [u16], + pub y_stride: usize, + pub uv_stride: usize, +} + +/// One temporal unit as stored in MP4 / WebM samples: OBUs with `obu_has_size_field` = 1 and +/// no temporal delimiter (key frames start with the sequence header OBU). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Packet { + pub data: Vec, + pub keyframe: bool, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + InvalidConfig(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidConfig(s) => write!(f, "invalid AV1 encoder config: {s}"), + } + } +} + +impl std::error::Error for Error {} + +const OBU_SEQUENCE_HEADER: u8 = 1; +const OBU_FRAME: u8 = 6; + +/// Annex A level limits: (seq_level_idx, MaxPicSize, MaxHSize, MaxVSize, MaxDisplayRate, +/// MaxBitrate (Main tier, Mbit/s)). +const LEVELS: [(u8, u64, u32, u32, u64, u64); 14] = [ + (0, 147_456, 2048, 1152, 4_423_680, 1_500_000), + (1, 278_784, 2816, 1584, 8_363_520, 3_000_000), + (4, 665_856, 4352, 2448, 19_975_680, 6_000_000), + (5, 1_065_024, 5504, 3096, 31_950_720, 10_000_000), + (8, 2_359_296, 6144, 3456, 70_778_880, 12_000_000), + (9, 2_359_296, 6144, 3456, 141_557_760, 20_000_000), + (12, 8_912_896, 8192, 4352, 267_386_880, 30_000_000), + (13, 8_912_896, 8192, 4352, 534_773_760, 40_000_000), + (14, 8_912_896, 8192, 4352, 1_069_547_520, 60_000_000), + (15, 8_912_896, 8192, 4352, 1_069_547_520, 60_000_000), + (16, 35_651_584, 16384, 8704, 1_069_547_520, 60_000_000), + (17, 35_651_584, 16384, 8704, 2_139_095_040, 100_000_000), + (18, 35_651_584, 16384, 8704, 4_278_190_080, 160_000_000), + (19, 35_651_584, 16384, 8704, 4_278_190_080, 160_000_000), +]; + +/// The lowest level whose limits fit the stream (31 = no level limits when none does). +fn pick_level(cfg: &EncoderConfig) -> u8 { + let pic = cfg.width as u64 * cfg.height as u64; + let rate = pic * cfg.fps_num as u64 / cfg.fps_den.max(1) as u64; + let bitrate = match cfg.rate { + RateControl::Bitrate { kbps } => kbps as u64 * 1000, + RateControl::ConstantQ(_) => 0, + }; + for &(idx, max_pic, max_h, max_v, max_rate, max_br) in LEVELS.iter() { + if pic <= max_pic && cfg.width <= max_h && cfg.height <= max_v && rate <= max_rate && bitrate <= max_br { + return idx; + } + } + 31 +} + +/// Frame-level rate control state for `RateControl::Bitrate`. +struct RateState { + /// Bits per frame at the target rate. + frame_bits: f64, + /// Quantizer index for inter frames (continuous). + q: f64, + /// Accumulated (spent - budget) bits. + debt: f64, +} + +/// AV1 encoder. Frames come out in input order (no hidden/reordered frames). +pub struct Encoder { + cfg: EncoderConfig, + geom: FrameGeom, + level: u8, + seq_obu: Vec, + frame_num: u64, + refr: Option, + rc: Option, +} + +impl Encoder { + pub fn new(cfg: EncoderConfig) -> Result { + if cfg.width == 0 || cfg.height == 0 { + return Err(Error::InvalidConfig("empty frame".into())); + } + if cfg.width > 4096 || cfg.height > 4096 { + return Err(Error::InvalidConfig(format!("{}x{} exceeds the single-tile limit (4096 samples per side)", cfg.width, cfg.height))); + } + if cfg.bit_depth != 8 && cfg.bit_depth != 10 { + return Err(Error::InvalidConfig(format!("bit depth {} (8 or 10 supported)", cfg.bit_depth))); + } + if cfg.fps_num == 0 || cfg.fps_den == 0 { + return Err(Error::InvalidConfig("frame rate".into())); + } + let geom = FrameGeom::new(cfg.width as usize, cfg.height as usize); + if geom.sb_cols * geom.sb_rows > 2304 { + return Err(Error::InvalidConfig(format!("{}x{} exceeds the single-tile area limit (MAX_TILE_AREA)", cfg.width, cfg.height))); + } + if let RateControl::Bitrate { kbps } = cfg.rate + && kbps == 0 + { + return Err(Error::InvalidConfig("zero bitrate".into())); + } + let level = cfg.level_idx.unwrap_or_else(|| pick_level(&cfg)); + if level > 31 { + return Err(Error::InvalidConfig(format!("seq_level_idx {level}"))); + } + let rc = match cfg.rate { + RateControl::ConstantQ(_) => None, + RateControl::Bitrate { kbps } => { + let frame_bits = kbps as f64 * 1000.0 * cfg.fps_den as f64 / cfg.fps_num as f64; + // initial guess from the bits per sample + let bpp = frame_bits / (cfg.width as f64 * cfg.height as f64); + let q = (110.0 - 40.0 * (bpp / 0.1).log2()).clamp(20.0, 230.0); + Some(RateState { frame_bits, q, debt: 0.0 }) + } + }; + let mut enc = Encoder { cfg, geom, level, seq_obu: Vec::new(), frame_num: 0, refr: None, rc }; + let payload = enc.sequence_header_payload(); + obu(OBU_SEQUENCE_HEADER, &payload, &mut enc.seq_obu); + Ok(enc) + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + /// The sequence header OBU (with size field). + pub fn sequence_header_obu(&self) -> Vec { + self.seq_obu.clone() + } + + /// AV1CodecConfigurationRecord (`av1C` payload, also the WebM CodecPrivate). + pub fn av1c(&self) -> Vec { + let high = (self.cfg.bit_depth > 8) as u8; + let mut v = vec![ + 0x81, // marker (1), version (1) + self.level & 0x1f, // seq_profile 0, seq_level_idx_0 + (high << 6) | (1 << 3) | (1 << 2), // tier 0, high_bitdepth, twelve_bit 0, mono 0, ss_x 1, ss_y 1, csp 0 + 0, // no initial_presentation_delay + ]; + v.extend_from_slice(&self.seq_obu); + v + } + + /// sequence_header_obu( ) payload (5.5) including trailing bits. + fn sequence_header_payload(&self) -> Vec { + let c = &self.cfg; + let mut w = BitWriter::new(); + w.f(3, 0); // seq_profile + w.f(1, 0); // still_picture + w.f(1, 0); // reduced_still_picture_header + w.f(1, 0); // timing_info_present_flag + w.f(1, 0); // initial_display_delay_present_flag + w.f(5, 0); // operating_points_cnt_minus_1 + w.f(12, 0); // operating_point_idc[ 0 ] + w.f(5, self.level as u32); // seq_level_idx[ 0 ] + if self.level > 7 { + w.f(1, 0); // seq_tier[ 0 ] + } + let wbits = 32 - (c.width - 1).max(1).leading_zeros(); + let hbits = 32 - (c.height - 1).max(1).leading_zeros(); + w.f(4, wbits - 1); + w.f(4, hbits - 1); + w.f(wbits, c.width - 1); + w.f(hbits, c.height - 1); + w.f(1, 0); // frame_id_numbers_present_flag + w.f(1, 0); // use_128x128_superblock + w.f(1, 0); // enable_filter_intra + w.f(1, 0); // enable_intra_edge_filter + w.f(1, 0); // enable_interintra_compound + w.f(1, 0); // enable_masked_compound + w.f(1, 0); // enable_warped_motion + w.f(1, 0); // enable_dual_filter + w.f(1, 0); // enable_order_hint + w.f(1, 0); // seq_choose_screen_content_tools + w.f(1, 0); // seq_force_screen_content_tools + w.f(1, 0); // enable_superres + w.f(1, 0); // enable_cdef + w.f(1, 0); // enable_restoration + // color_config( ) + w.f(1, (c.bit_depth > 8) as u32); // high_bitdepth + w.f(1, 0); // mono_chrome + w.f(1, 1); // color_description_present_flag + w.f(8, 1); // color_primaries: BT.709 + w.f(8, 1); // transfer_characteristics: BT.709 + w.f(8, 1); // matrix_coefficients: BT.709 + w.f(1, c.full_range as u32); // color_range + w.f(2, 0); // chroma_sample_position: unknown + w.f(1, 0); // separate_uv_delta_q + w.f(1, 0); // film_grain_params_present + w.trailing_bits(); + w.into_bytes() + } + + /// uncompressed_header( ) (5.9) for a shown KEY_FRAME or an INTER_FRAME referencing slot 0. + fn frame_header(&self, w: &mut BitWriter, key: bool, qidx: u8, lf_level: u32) { + w.f(1, 0); // show_existing_frame + w.f(2, if key { 0 } else { 1 }); // frame_type + w.f(1, 1); // show_frame + if !key { + w.f(1, 0); // error_resilient_mode + } + w.f(1, 0); // disable_cdf_update + w.f(1, 0); // frame_size_override_flag + if !key { + w.f(3, 0); // primary_ref_frame: LAST_FRAME's saved CDFs + w.f(8, 1); // refresh_frame_flags: slot 0 becomes LAST for the next frame + for _ in 0..7 { + w.f(3, 0); // ref_frame_idx[ i ] + } + w.f(1, 0); // render_and_frame_size_different + w.f(1, 0); // allow_high_precision_mv + w.f(1, 0); // is_filter_switchable + w.f(2, 0); // interpolation_filter = EIGHTTAP + w.f(1, 0); // is_motion_mode_switchable + } else { + w.f(1, 0); // render_and_frame_size_different + } + w.f(1, 0); // disable_frame_end_update_cdf + // tile_info( ): uniform spacing, one tile + let g = &self.geom; + let tile_log2 = |blk: usize, target: usize| { + let mut k = 0; + while (blk << k) < target { + k += 1; + } + k + }; + w.f(1, 1); // uniform_tile_spacing_flag + if tile_log2(1, g.sb_cols.min(64)) > 0 { + w.f(1, 0); // increment_tile_cols_log2 + } + if tile_log2(1, g.sb_rows.min(64)) > 0 { + w.f(1, 0); // increment_tile_rows_log2 + } + // quantization_params( ) + w.f(8, qidx as u32); + w.f(1, 0); // DeltaQYDc + w.f(1, 0); // DeltaQUDc + w.f(1, 0); // DeltaQUAc + w.f(1, 0); // using_qmatrix + w.f(1, 0); // segmentation_enabled + w.f(1, 0); // delta_q_present (base_q_idx > 0) + // loop_filter_params( ): the same level for every plane and direction + w.f(6, lf_level); + w.f(6, lf_level); + if lf_level != 0 { + w.f(6, lf_level); + w.f(6, lf_level); + } + w.f(3, 0); // loop_filter_sharpness + w.f(1, 0); // loop_filter_delta_enabled + w.f(1, 0); // tx_mode_select (TX_MODE_LARGEST) + if !key { + w.f(1, 0); // reference_select + } + w.f(1, 1); // reduced_tx_set + if !key { + for _ in 0..7 { + w.f(1, 0); // is_global + } + } + } + + /// Copies a picture into superblock-padded planes (edge replication). + fn load_source(&self, frame: &Frame) -> [Plane; 3] { + let maxv = (1u16 << self.cfg.bit_depth) - 1; + std::array::from_fn(|p| { + let (vw, vh) = self.geom.visible(p); + let (pw, ph) = self.geom.padded(p); + let (data, stride) = match p { + 0 => (frame.y, frame.y_stride), + 1 => (frame.u, frame.uv_stride), + _ => (frame.v, frame.uv_stride), + }; + assert!(stride >= vw && data.len() >= stride * (vh - 1) + vw, "plane {p} is too small for {vw}x{vh}"); + let mut pl = Plane::new(pw, ph); + for y in 0..ph { + let sy = y.min(vh - 1); + let src = &data[sy * stride..sy * stride + vw]; + let row = &mut pl.data[y * pw..y * pw + pw]; + for (x, d) in row.iter_mut().enumerate() { + *d = src[x.min(vw - 1)].min(maxv); + } + } + pl + }) + } + + /// Codes one frame at `qidx`; returns the frame OBU and the reconstruction. + fn encode_frame(&self, src: &[Plane; 3], key: bool, qidx: u8) -> (Vec, RefFrame) { + let g = &self.geom; + let refr = if key { None } else { self.refr.as_ref() }; + let mut dec = Decider::new(g, self.cfg.bit_depth as u32, qidx, key, src, refr); + let mut tw = TileWriter::new(g, key, qidx, refr.map(|r| &*r.cdfs)); + for sbr in 0..g.sb_rows { + tw.start_sb_row(); + for sbc in 0..g.sb_cols { + let node = dec.superblock(sbr * 16, sbc * 16); + tw.superblock(sbr * 16, sbc * 16, &node); + } + } + let lf_level = self.pick_filter_level(&dec.rec, &tw.lf_info(), src, qidx); + loop_filter(&mut dec.rec, &tw.lf_info(), [lf_level; 4], self.cfg.bit_depth as u32, false, 1); + let (tile, cdfs) = tw.finish(); + let mut w = BitWriter::new(); + self.frame_header(&mut w, key, qidx, lf_level); + w.byte_align(); + // tile_group_obu( ): a single tile, no tile_start_and_end_present_flag + let mut payload = w.into_bytes(); + payload.extend_from_slice(&tile); + let mut out = Vec::with_capacity(payload.len() + 8); + obu(OBU_FRAME, &payload, &mut out); + let rf = RefFrame { planes: dec.rec, mvs: dec.mvs, lf_level, cdfs }; + (out, rf) + } + + /// The loop filter level minimising the luma error (searched around a guess from the + /// quantizer, on the luma of a subset of the superblock rows). + fn pick_filter_level(&self, rec: &[Plane; 3], info: &LfInfo, src: &[Plane; 3], qidx: u8) -> u32 { + let (w, h) = (self.geom.width, self.geom.height); + let sb_row_step = if self.geom.sb_rows >= 9 { 3 } else { 1 }; + let sse = |pl: &Plane| -> u64 { + let mut s = 0u64; + for y in 0..h { + if !(y >> 6).is_multiple_of(sb_row_step) { + continue; + } + for (a, b) in pl.row(y)[..w].iter().zip(&src[0].row(y)[..w]) { + let d = *a as i64 - *b as i64; + s += (d * d) as u64; + } + } + s + }; + let bd = self.cfg.bit_depth as u32; + let mut cache: Vec<(u32, u64)> = vec![(0, sse(&rec[0]))]; + let mut eval = |lvl: u32| -> u64 { + if let Some(&(_, e)) = cache.iter().find(|(l, _)| *l == lvl) { + return e; + } + let mut planes = [rec[0].clone(), Plane::new(0, 0), Plane::new(0, 0)]; + loop_filter(&mut planes, info, [lvl; 4], bd, true, sb_row_step); + let e = sse(&planes[0]); + cache.push((lvl, e)); + e + }; + let guess = ((qidx as u32 * 3) / 16).clamp(2, 50); + let mut best = (0u32, eval(0)); + let mut step = (guess / 2).max(1); + let mut center = guess; + let e = eval(center); + if e < best.1 { + best = (center, e); + } + while step >= 2 { + for lvl in [center.saturating_sub(step), (center + step).min(63)] { + let e = eval(lvl); + if e < best.1 { + best = (lvl, e); + } + } + center = best.0.max(1); + step /= 2; + } + best.0 + } + + /// Encodes one frame. + pub fn encode(&mut self, frame: &Frame) -> Packet { + let key = self.frame_num.is_multiple_of(self.cfg.keyint.max(1) as u64); + let src = self.load_source(frame); + let (frame_obu, rf) = match self.cfg.rate { + RateControl::ConstantQ(q) => self.encode_frame(&src, key, q.max(1)), + RateControl::Bitrate { .. } => self.encode_rate_controlled(&src, key), + }; + self.refr = Some(rf); + self.frame_num += 1; + let mut data = Vec::with_capacity(frame_obu.len() + self.seq_obu.len()); + if key { + data.extend_from_slice(&self.seq_obu); + } + data.extend_from_slice(&frame_obu); + Packet { data, keyframe: key } + } + + fn encode_rate_controlled(&mut self, src: &[Plane; 3], key: bool) -> (Vec, RefFrame) { + let first = self.frame_num == 0; + let rc = self.rc.as_ref().expect("rate control state"); + let (frame_bits, debt, q_inter) = (rc.frame_bits, rc.debt, rc.q); + // key frames get a lower quantizer and a larger share of the budget + let key_boost = 1.0 + (self.cfg.keyint.clamp(1, 60) as f64 - 1.0).sqrt() * 0.75; + let budget = if key { frame_bits * key_boost } else { frame_bits }; + // pay back a fraction of the accumulated deviation + let target = (budget - debt * 0.25).max(budget * 0.25); + let mut q = if key { q_inter - 24.0 } else { q_inter }; + let mut best: Option<(Vec, RefFrame, f64)> = None; + let passes = if first { 4 } else { 1 }; + for pass in 0..passes { + let qi = q.round().clamp(1.0, 255.0) as u8; + let (obu, rf) = self.encode_frame(src, key, qi); + let bits = obu.len() as f64 * 8.0; + let err = (bits / target).log2(); + let better = best.as_ref().is_none_or(|b| err.abs() < (b.2 / target).log2().abs()); + if better { + best = Some((obu, rf, bits)); + } + if pass + 1 < passes { + q = (q + 30.0 * err).clamp(1.0, 255.0); + } + } + let (obu, rf, bits) = best.expect("encoded"); + let rc = self.rc.as_mut().expect("rate control state"); + rc.debt += bits - budget; + // adapt the inter quantizer: about 30 index steps per doubling of the rate + let err = (bits / target).log2().clamp(-1.0, 1.0); + if first && key { + rc.q = (q + 24.0).clamp(1.0, 255.0); + } else { + rc.q = (rc.q + 12.0 * err).clamp(1.0, 255.0); + } + (obu, rf) + } + + /// The loop filter level of the last encoded frame. For tests. + #[doc(hidden)] + pub fn last_loop_filter_level(&self) -> Option { + self.refr.as_ref().map(|r| r.lf_level) + } + + /// The reconstruction of the last encoded frame (visible area, planes Y, U, V), equal to + /// what a decoder outputs. For tests. + #[doc(hidden)] + pub fn last_reconstruction(&self) -> Option<[Vec; 3]> { + let rf = self.refr.as_ref()?; + Some(std::array::from_fn(|p| { + let (vw, vh) = self.geom.visible(p); + let pl = &rf.planes[p]; + let mut v = Vec::with_capacity(vw * vh); + for y in 0..vh { + v.extend_from_slice(&pl.row(y)[..vw]); + } + v + })) + } +} diff --git a/crates/av1enc/src/loopfilter.rs b/crates/av1enc/src/loopfilter.rs new file mode 100644 index 00000000..2add41ed --- /dev/null +++ b/crates/av1enc/src/loopfilter.rs @@ -0,0 +1,197 @@ +//! Deblocking loop filter (spec 7.14), mirrored from the decoder process so that the encoder's +//! references equal a decoder's. Segmentation and loop filter deltas are off and the sharpness +//! is 0 in the frames this encoder writes. + +use crate::pred::Plane; +use crate::tables::*; + +/// Mode info the loop filter reads (per 4x4 luma unit, row stride `mi_cols`), and the +/// transform size per 4x4 unit of each plane (`LoopfilterTxSizes`, row stride `mi_cols + 32`). +pub(crate) struct LfInfo<'a> { + pub mi_cols: usize, + pub mi_rows: usize, + pub width: usize, + pub height: usize, + pub mi_size: &'a [u8], + pub skip: &'a [bool], + pub is_inter: &'a [bool], + pub tx: [&'a [u8]; 3], +} + +/// Applies the loop filter with `loop_filter_level[ 0..4 ]` to `planes`. For level search +/// (`luma_only`), only plane 0 and only every `sb_row_step`-th superblock row are filtered. +pub(crate) fn loop_filter(planes: &mut [Plane; 3], info: &LfInfo, level: [u32; 4], bit_depth: u32, luma_only: bool, sb_row_step: usize) { + if level[0] == 0 && level[1] == 0 { + return; + } + let nplanes = if luma_only { 1 } else { 3 }; + for (plane, pl) in planes.iter_mut().enumerate().take(nplanes) { + if plane > 0 && level[1 + plane] == 0 { + continue; + } + for pass in 0..2 { + let step = if plane == 0 { 1 } else { 2 }; + let mut row = 0; + while row < info.mi_rows { + if !(row >> 4).is_multiple_of(sb_row_step) { + row += step; + continue; + } + let mut col = 0; + while col < info.mi_cols { + edge(pl, info, level, bit_depth, plane, pass, row, col); + col += step; + } + row += step; + } + } + } +} + +fn edge(pl: &mut Plane, info: &LfInfo, level: [u32; 4], bd: u32, plane: usize, pass: usize, row: usize, col: usize) { + let s = (plane > 0) as usize; + let (dx, dy) = if pass == 0 { (1usize, 0usize) } else { (0, 1) }; + let x = col * 4; + let y = row * 4; + let row = row | s; + let col = col | s; + let on_screen = !(x >= info.width || y >= info.height || (pass == 0 && x == 0) || (pass == 1 && y == 0)); + if !on_screen { + return; + } + let xp = x >> s; + let yp = y >> s; + let prev_row = row - (dy << s); + let prev_col = col - (dx << s); + let i = row * info.mi_cols + col; + let mi_size = info.mi_size[i] as usize; + let tx_stride = info.mi_cols + 32; + let tx_sz = info.tx[plane][(row >> s) * tx_stride + (col >> s)] as usize; + let plane_size = SUBSAMPLED_SIZE[mi_size][s][s] as usize; + let skip = info.skip[i]; + let is_intra = !info.is_inter[i]; + let prev_tx_sz = info.tx[plane][(prev_row >> s) * tx_stride + (prev_col >> s)] as usize; + let is_block_edge = if pass == 0 { + xp.is_multiple_of(4 * NUM_4X4_BLOCKS_WIDE[plane_size] as usize) + } else { + yp.is_multiple_of(4 * NUM_4X4_BLOCKS_HIGH[plane_size] as usize) + }; + let is_tx_edge = if pass == 0 { xp.is_multiple_of(TX_WIDTH[tx_sz] as usize) } else { yp.is_multiple_of(TX_HEIGHT[tx_sz] as usize) }; + let apply_filter = is_tx_edge && (is_block_edge || !skip || is_intra); + let base_size = if pass == 0 { TX_WIDTH[prev_tx_sz].min(TX_WIDTH[tx_sz]) } else { TX_HEIGHT[prev_tx_sz].min(TX_HEIGHT[tx_sz]) } as usize; + let filter_size = if plane == 0 { 16.min(base_size) } else { 8.min(base_size) }; + // 7.14.4 with no deltas, segmentation or sharpness: the same strength for every block + let lvl = level[if plane == 0 { pass } else { plane + 1 }].min(MAX_LOOP_FILTER as u32) as i32; + if !apply_filter || lvl == 0 { + return; + } + let limit = lvl.max(1); + let blimit = 2 * (lvl + 2) + limit; + let thresh = lvl >> 4; + for k in 0..4 { + sample_filter(pl, xp + dy * k, yp + dx * k, plane, limit, blimit, thresh, dx as isize, dy as isize, filter_size, bd); + } +} + +#[inline] +fn sample_filter(pl: &mut Plane, x: usize, y: usize, plane: usize, limit: i32, blimit: i32, thresh: i32, dx: isize, dy: isize, filter_size: usize, bd: u32) { + let stride = pl.stride as isize; + let step = dy * stride + dx; + let base = y as isize * stride + x as isize; + let reach: isize = if filter_size >= 16 { 7 } else { 4 }; + let mut v = [0i32; 16]; + for k in -reach..reach { + v[(k + 8) as usize] = pl.data[(base + k * step) as usize] as i32; + } + let at = |k: isize| -> i32 { v[(k + 8) as usize] }; + let (q0, q1, q2, q3) = (at(0), at(1), at(2), at(3)); + let (p0, p1, p2, p3) = (at(-1), at(-2), at(-3), at(-4)); + let d = &mut pl.data; + let sh = bd - 8; + let thresh_bd = thresh << sh; + let hev = (p1 - p0).abs() > thresh_bd || (q1 - q0).abs() > thresh_bd; + let filter_len = if filter_size == 4 { + 4 + } else if plane != 0 { + 6 + } else if filter_size == 8 { + 8 + } else { + 16 + }; + let limit_bd = limit << sh; + let blimit_bd = blimit << sh; + let mut mask = (p1 - p0).abs() > limit_bd || (q1 - q0).abs() > limit_bd || (p0 - q0).abs() * 2 + (p1 - q1).abs() / 2 > blimit_bd; + if filter_len >= 6 { + mask |= (p2 - p1).abs() > limit_bd || (q2 - q1).abs() > limit_bd; + } + if filter_len >= 8 { + mask |= (p3 - p2).abs() > limit_bd || (q3 - q2).abs() > limit_bd; + } + if mask { + return; + } + let t_bd = 1 << sh; + let flat = if filter_size >= 8 { + let mut m = (p1 - p0).abs() > t_bd || (q1 - q0).abs() > t_bd || (p2 - p0).abs() > t_bd || (q2 - q0).abs() > t_bd; + if filter_len >= 8 { + m |= (p3 - p0).abs() > t_bd || (q3 - q0).abs() > t_bd; + } + !m + } else { + false + }; + let flat2 = if filter_size >= 16 { + let (q4, q5, q6, p4, p5, p6) = (at(4), at(5), at(6), at(-5), at(-6), at(-7)); + !((p6 - p0).abs() > t_bd + || (q6 - q0).abs() > t_bd + || (p5 - p0).abs() > t_bd + || (q5 - q0).abs() > t_bd + || (p4 - p0).abs() > t_bd + || (q4 - q0).abs() > t_bd) + } else { + false + }; + if filter_size == 4 || !flat { + // narrow filter (7.14.6.3) + let half = 1i32 << (bd - 1); + let c = |v: i32| v.clamp(-half, half - 1); + let off = 0x80 << sh; + let (ps1, ps0, qs0, qs1) = (p1 - off, p0 - off, q0 - off, q1 - off); + let mut filter = if hev { c(ps1 - qs1) } else { 0 }; + filter = c(filter + 3 * (qs0 - ps0)); + let filter1 = c(filter + 4) >> 3; + let filter2 = c(filter + 3) >> 3; + d[base as usize] = (c(qs0 - filter1) + off) as u16; + d[(base - step) as usize] = (c(ps0 + filter2) + off) as u16; + if !hev { + let f = (filter1 + 1) >> 1; + d[(base + step) as usize] = (c(qs1 - f) + off) as u16; + d[(base - 2 * step) as usize] = (c(ps1 + f) + off) as u16; + } + } else { + // wide filter (7.14.6.4) + let log2 = if filter_size == 8 || !flat2 { 3 } else { 4 }; + let n: isize = if log2 == 4 { + 6 + } else if plane == 0 { + 3 + } else { + 2 + }; + let n2: isize = if log2 == 3 && plane == 0 { 0 } else { 1 }; + let mut f = [0i32; 12]; + for i in -n..n { + let mut t = 0; + for j in -n..=n { + let p = (i + j).clamp(-(n + 1), n); + let tap = if j.abs() <= n2 { 2 } else { 1 }; + t += at(p) * tap; + } + f[(i + n) as usize] = (t + (1 << (log2 - 1))) >> log2; + } + for i in -n..n { + d[(base + i * step) as usize] = f[(i + n) as usize] as u16; + } + } +} diff --git a/crates/av1enc/src/pred.rs b/crates/av1enc/src/pred.rs new file mode 100644 index 00000000..d61a6fa9 --- /dev/null +++ b/crates/av1enc/src/pred.rs @@ -0,0 +1,362 @@ +//! Prediction processes, mirrored from the spec (7.11.2 intra prediction for the non-directional +//! modes plus V_PRED / H_PRED without edge filtering, 7.11.3 block inter prediction without +//! reference scaling) so that the encoder predicts exactly like a decoder. + +use crate::tables::*; +use crate::transform::round2; + +/// A plane buffer (padded to whole superblocks). +#[derive(Clone)] +pub(crate) struct Plane { + pub data: Vec, + pub stride: usize, +} + +impl Plane { + pub fn new(w: usize, h: usize) -> Plane { + Plane { data: vec![0; w * h], stride: w } + } + #[inline(always)] + pub fn at(&self, x: usize, y: usize) -> u16 { + self.data[y * self.stride + x] + } + #[inline(always)] + pub fn row(&self, y: usize) -> &[u16] { + &self.data[y * self.stride..(y + 1) * self.stride] + } +} + +/// The intra modes this encoder uses (values are the spec's y / uv mode numbers). +pub(crate) const INTRA_MODES_USED: [usize; 7] = [DC_PRED, V_PRED, H_PRED, PAETH_PRED, SMOOTH_PRED, SMOOTH_V_PRED, SMOOTH_H_PRED]; + +/// Inputs of the intra prediction process. +pub(crate) struct IntraEdge { + pub x: usize, + pub y: usize, + pub have_left: bool, + pub have_above: bool, + pub have_above_right: bool, + pub have_below_left: bool, + pub log2: u32, + /// ((MiCols * MI_SIZE) >> subX) - 1 and the same for rows. + pub max_x: i32, + pub max_y: i32, + pub bit_depth: u32, +} + +/// Edge samples: `above[0..2n]`, `left[0..2n]` and the top-left corner. +pub(crate) struct Edges { + above: [i32; 64], + left: [i32; 64], + corner: i32, +} + +pub(crate) fn edges(pl: &Plane, e: &IntraEdge) -> Edges { + let w = 1usize << e.log2; + let h = w; + let bd = e.bit_depth; + let (x, y) = (e.x, e.y); + let n = w + h; + let mut above = [0i32; 64]; + let mut left = [0i32; 64]; + if !e.have_above && e.have_left { + let v = pl.at(x - 1, y) as i32; + above[..n].iter_mut().for_each(|a| *a = v); + } else if !e.have_above && !e.have_left { + let v = (1 << (bd - 1)) - 1; + above[..n].iter_mut().for_each(|a| *a = v); + } else { + let above_limit = e.max_x.min(x as i32 + if e.have_above_right { 2 * w as i32 } else { w as i32 } - 1); + let row = pl.row(y - 1); + for (i, a) in above[..n].iter_mut().enumerate() { + *a = row[above_limit.min(x as i32 + i as i32) as usize] as i32; + } + } + if !e.have_left && e.have_above { + let v = pl.at(x, y - 1) as i32; + left[..n].iter_mut().for_each(|a| *a = v); + } else if !e.have_left && !e.have_above { + let v = (1 << (bd - 1)) + 1; + left[..n].iter_mut().for_each(|a| *a = v); + } else { + let left_limit = e.max_y.min(y as i32 + if e.have_below_left { 2 * h as i32 } else { h as i32 } - 1); + for (i, l) in left[..n].iter_mut().enumerate() { + *l = pl.at(x - 1, left_limit.min(y as i32 + i as i32) as usize) as i32; + } + } + let corner = if e.have_above && e.have_left { + pl.at(x - 1, y - 1) as i32 + } else if e.have_above { + pl.at(x, y - 1) as i32 + } else if e.have_left { + pl.at(x - 1, y) as i32 + } else { + 1 << (bd - 1) + }; + Edges { above, left, corner } +} + +fn sm_weights(log2: u32) -> &'static [u8] { + match log2 { + 2 => &SM_WEIGHTS_TX_4X4, + 3 => &SM_WEIGHTS_TX_8X8, + 4 => &SM_WEIGHTS_TX_16X16, + _ => &SM_WEIGHTS_TX_32X32, + } +} + +/// Predicts a square `2^log2` block with `mode` into `pred` (row stride = block width). +pub(crate) fn predict_intra(ed: &Edges, e: &IntraEdge, mode: usize, pred: &mut [u16]) { + let log2 = e.log2; + let w = 1usize << log2; + let h = w; + let bd = e.bit_depth; + let (above, left) = (&ed.above, &ed.left); + match mode { + DC_PRED => { + let v = if e.have_left && e.have_above { + let sum: i32 = left[..h].iter().sum::() + above[..w].iter().sum::() + ((w + h) >> 1) as i32; + sum / (w + h) as i32 + } else if e.have_left { + (left[..h].iter().sum::() + (h >> 1) as i32) >> log2 + } else if e.have_above { + (above[..w].iter().sum::() + (w >> 1) as i32) >> log2 + } else { + 1 << (bd - 1) + }; + let v = v.clamp(0, (1 << bd) - 1) as u16; + pred[..w * h].iter_mut().for_each(|p| *p = v); + } + V_PRED => { + for i in 0..h { + for j in 0..w { + pred[i * w + j] = above[j] as u16; + } + } + } + H_PRED => { + for i in 0..h { + pred[i * w..i * w + w].iter_mut().for_each(|p| *p = left[i] as u16); + } + } + PAETH_PRED => { + let tl = ed.corner; + for i in 0..h { + for j in 0..w { + let a = above[j]; + let l = left[i]; + let base = a + l - tl; + let p_left = (base - l).abs(); + let p_top = (base - a).abs(); + let p_tl = (base - tl).abs(); + pred[i * w + j] = if p_left <= p_top && p_left <= p_tl { + l as u16 + } else if p_top <= p_tl { + a as u16 + } else { + tl as u16 + }; + } + } + } + _ => { + let wx = sm_weights(log2); + let wy = wx; + for i in 0..h { + for j in 0..w { + let v = if mode == SMOOTH_PRED { + let s = wy[i] as i32 * above[j] + (256 - wy[i] as i32) * left[h - 1] + wx[j] as i32 * left[i] + (256 - wx[j] as i32) * above[w - 1]; + round2(s, 9) + } else if mode == SMOOTH_V_PRED { + round2(wy[i] as i32 * above[j] + (256 - wy[i] as i32) * left[h - 1], 8) + } else { + round2(wx[j] as i32 * left[i] + (256 - wx[j] as i32) * above[w - 1], 8) + }; + pred[i * w + j] = v as u16; + } + } + } + } +} + +/// Block inter prediction (7.11.3.3 motion vector scaling without scaling, 7.11.3.4 block +/// inter prediction with the EIGHTTAP filter, single reference so InterRound1 = 11 and the +/// final rounding is the identity) of the `w x h` block at plane position (x, y) displaced by +/// `mv` (1/8 luma sample units, [row, col]). `last_x` / `last_y` are the largest sample +/// coordinates of the reference plane; the result goes to `pred` (row stride `w`). +pub(crate) fn predict_inter( + refp: &Plane, + last_x: i32, + last_y: i32, + x: usize, + y: usize, + w: usize, + h: usize, + mv: [i32; 2], + ss: usize, + bit_depth: u32, + pred: &mut [u16], +) { + // positions in 1/16 sample units ((startX >> 6) & 15 is the filter phase) + let px = ((x as i32) << 4) + ((2 * mv[1]) >> ss); + let py = ((y as i32) << 4) + ((2 * mv[0]) >> ss); + let (fx, fy) = ((px & 15) as usize, (py & 15) as usize); + let (ix, iy) = (px >> 4, py >> 4); + let max = (1i32 << bit_depth) - 1; + if fx == 0 && fy == 0 { + // the filters' phase-0 kernel is a unit impulse: a clamped copy + for r in 0..h { + let ry = (iy + r as i32).clamp(0, last_y) as usize; + let row = refp.row(ry); + let out = &mut pred[r * w..r * w + w]; + let x0 = ix; + if x0 >= 0 && x0 + w as i32 - 1 <= last_x { + out.copy_from_slice(&row[x0 as usize..x0 as usize + w]); + } else { + for (c, o) in out.iter_mut().enumerate() { + *o = row[(x0 + c as i32).clamp(0, last_x) as usize]; + } + } + } + return; + } + let filt_h = if w <= 4 { 4 } else { EIGHTTAP }; + let filt_v = if h <= 4 { 4 } else { EIGHTTAP }; + let kh = &SUBPEL_FILTERS[filt_h][fx]; + let kv = &SUBPEL_FILTERS[filt_v][fy]; + let bx = ix - 3; + let x_inside = bx >= 0 && bx + w as i32 + 7 <= last_x + 1; + // horizontal pass of one reference row into `out` (Round2( sum, InterRound0 = 3 )) + let hfilter = |ry: usize, out: &mut [i32]| { + let row = refp.row(ry); + if x_inside { + let seg = &row[bx as usize..bx as usize + w + 7]; + for (c, o) in out.iter_mut().enumerate() { + let mut s = 0i32; + for t in 0..8 { + s += kh[t] as i32 * seg[c + t] as i32; + } + *o = round2(s, 3); + } + } else { + for (c, o) in out.iter_mut().enumerate() { + let mut s = 0i32; + for t in 0..8 { + s += kh[t] as i32 * row[(bx + c as i32 + t as i32).clamp(0, last_x) as usize] as i32; + } + *o = round2(s, 3); + } + } + }; + let mut inter = [0i32; (64 + 7) * 64]; + if fy == 0 { + // the vertical kernel is 128 at the centre: Round2( 128 * v, 11 ) = Round2( v, 4 ) + for r in 0..h { + let ry = (iy + r as i32).clamp(0, last_y) as usize; + hfilter(ry, &mut inter[..w]); + for (p, &v) in pred[r * w..r * w + w].iter_mut().zip(&inter[..w]) { + *p = round2(v, 4).clamp(0, max) as u16; + } + } + return; + } + if fx == 0 { + // the horizontal pass is exactly 16 * sample: Round2( 16 * sum, 11 ) = Round2( sum, 7 ) + for r in 0..h { + let rows: [&[u16]; 8] = std::array::from_fn(|t| refp.row((iy + r as i32 + t as i32 - 3).clamp(0, last_y) as usize)); + for c in 0..w { + let xx = if x_inside { (ix + c as i32) as usize } else { (ix + c as i32).clamp(0, last_x) as usize }; + let mut s = 0i32; + for t in 0..8 { + s += kv[t] as i32 * rows[t][xx] as i32; + } + pred[r * w + c] = round2(s, 7).clamp(0, max) as u16; + } + } + return; + } + let inter_h = h + 7; + for r in 0..inter_h { + let ry = (iy + r as i32 - 3).clamp(0, last_y) as usize; + hfilter(ry, &mut inter[r * w..r * w + w]); + } + for r in 0..h { + for c in 0..w { + let mut s = 0i32; + for t in 0..8 { + s += kv[t] as i32 * inter[(r + t) * w + c]; + } + pred[r * w + c] = round2(s, 11).clamp(0, max) as u16; + } + } +} + +/// The visible `w x h` area of a reference plane interpolated at the sub-sample phase +/// (`fx`, `fy`) (1/16 units) with the EIGHTTAP filter, as `predict_inter` computes it for +/// blocks wider and taller than 4. Used for motion search. +pub(crate) fn subpel_plane(refp: &Plane, w: usize, h: usize, fx: usize, fy: usize, bit_depth: u32) -> Vec { + let kh = &SUBPEL_FILTERS[EIGHTTAP][fx]; + let kv = &SUBPEL_FILTERS[EIGHTTAP][fy]; + let max = (1i32 << bit_depth) - 1; + let (lx, ly) = (w as i32 - 1, h as i32 - 1); + let mut inter = vec![0i32; w * h]; + for y in 0..h { + let row = refp.row(y); + for x in 0..w { + let mut s = 0i32; + for t in 0..8 { + s += kh[t] as i32 * row[(x as i32 + t as i32 - 3).clamp(0, lx) as usize] as i32; + } + inter[y * w + x] = round2(s, 3); + } + } + let mut out = vec![0u16; w * h]; + for y in 0..h { + for x in 0..w { + let mut s = 0i32; + for t in 0..8 { + s += kv[t] as i32 * inter[(y as i32 + t as i32 - 3).clamp(0, ly) as usize * w + x]; + } + out[y * w + x] = round2(s, 11).clamp(0, max) as u16; + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Whole-plane interpolation equals block inter prediction at the same phase. + #[test] + fn subpel_plane_matches_block_prediction() { + let (w, h) = (37, 29); + let mut p = Plane::new(64, 64); + for (i, v) in p.data.iter_mut().enumerate() { + *v = ((i * 7919) % 256) as u16; + } + for (fx, fy) in [(8, 0), (0, 8), (8, 8), (4, 12)] { + let plane = subpel_plane(&p, w, h, fx, fy, 8); + for (bx, by) in [(0i32, 0i32), (8, 8), (24, 16), (-5, 20), (30, -3)] { + let mut pred = vec![0u16; 64]; + let mv = [by * 8 + fy as i32 / 2, bx * 8 + fx as i32 / 2]; + predict_inter(&p, w as i32 - 1, h as i32 - 1, 0, 0, 8, 8, mv, 0, 8, &mut pred); + for r in 0..8 { + for c in 0..8 { + let (x, y) = ((bx + c).clamp(0, w as i32 - 1), (by + r).clamp(0, h as i32 - 1)); + if bx + c == x && by + r == y { + assert_eq!(pred[(r * 8 + c) as usize], plane[y as usize * w + x as usize], "phase ({fx}, {fy}) at ({}, {})", bx + c, by + r); + } + } + } + } + } + } + + #[test] + fn phase_zero_filters_are_impulses() { + for f in SUBPEL_FILTERS.iter() { + assert_eq!(f[0], [0, 0, 0, 128, 0, 0, 0, 0]); + } + } +} diff --git a/crates/av1enc/src/symbol.rs b/crates/av1enc/src/symbol.rs new file mode 100644 index 00000000..579a51e2 --- /dev/null +++ b/crates/av1enc/src/symbol.rs @@ -0,0 +1,267 @@ +//! Multi-symbol arithmetic encoder: the exact inverse of the symbol decoder of spec 8.2. +//! +//! The decoder keeps `SymbolValue`, the bitwise complement of a 15-bit window into the tile data +//! minus the lower bounds of the symbols decoded so far, and selects symbol `k` when +//! `cur(k) <= SymbolValue < cur(k - 1)` with +//! `cur(k) = ((SymbolRange >> 8) * ((32768 - cdf[k]) >> 6) >> 1) + 4 * (N - k - 1)` and +//! `cur(-1) = SymbolRange`. The encoder therefore runs an ordinary carry-propagating range +//! coder over the complemented bit stream: coding symbol `k` adds `cur(k)` to `low` and sets the +//! range to `cur(k - 1) - cur(k)`; both are renormalised exactly like the decoder's range. +//! +//! At the end (spec 8.2.4 exit process) the decoder has consumed `S` renormalisation bits plus a +//! 15-bit window, and the bits from position `S` on must be a one followed by zeros. In the +//! complemented domain that window is `0b011_1111_1111_1111`, so the encoder picks the `S`-bit +//! prefix `D` with `D * 2^15 + 2^14 - 1` inside `[low, low + range)`, i.e. +//! `D = (low + 2^14) >> 15`, and writes `!D`, a one bit and zero padding. + +pub(crate) struct SymbolWriter { + /// Low end of the interval, holding `15 + cnt` bits not yet moved to `out` (plus carries). + low: u64, + rng: u32, + cnt: u32, + out: Vec, + pub disable_update: bool, +} + +impl SymbolWriter { + pub fn new(disable_update: bool) -> Self { + SymbolWriter { low: 0, rng: 1 << 15, cnt: 0, out: Vec::with_capacity(1 << 16), disable_update } + } + + /// Adds `k` to the bytes already output (carry propagation). + fn carry(&mut self, mut k: u64) { + let mut i = self.out.len(); + while k != 0 { + i -= 1; + let t = self.out[i] as u64 + k; + self.out[i] = t as u8; + k = t >> 8; + } + } + + #[inline(always)] + fn encode(&mut self, cdf: &[u16], n: usize, s: usize) { + let r8 = self.rng >> 8; + let cur = |k: usize| -> u32 { ((r8 * ((32768 - cdf[k] as u32) >> 6)) >> 1) + 4 * (n - k - 1) as u32 }; + let hi = if s == 0 { self.rng } else { cur(s - 1) }; + let lo = cur(s); + debug_assert!(hi > lo, "symbol {s} has an empty interval"); + self.low += lo as u64; + self.rng = hi - lo; + let d = 15 - (31 - self.rng.leading_zeros()); + if d == 0 { + return; + } + self.rng <<= d; + self.low <<= d; + self.cnt += d; + while self.cnt >= 8 { + let sh = 15 + self.cnt - 8; + let c = self.low >> sh; + self.low &= (1u64 << sh) - 1; + self.cnt -= 8; + self.carry(c >> 8); + self.out.push((c & 0xff) as u8); + } + } + + /// Codes `s` with the adaptive CDF `cdf` (N values plus the adaptation counter). + #[inline] + pub fn symbol(&mut self, s: usize, cdf: &mut [u16]) { + let n = cdf.len() - 1; + debug_assert!(s < n); + self.encode(cdf, n, s); + if !self.disable_update { + update_cdf(cdf, n, s); + } + } + + /// Codes `s` with a CDF that is not adapted. + pub fn symbol_fixed(&mut self, s: usize, cdf: &[u16]) { + let n = cdf.len() - 1; + self.encode(cdf, n, s); + } + + /// `read_bool()` counterpart. + pub fn bool(&mut self, b: bool) { + self.encode(&[1 << 14, 1 << 15, 0], 2, b as usize); + } + + /// `L(n)` counterpart (most significant bit first). + #[cfg(test)] + pub fn literal(&mut self, n: u32, v: u32) { + for i in (0..n).rev() { + self.bool((v >> i) & 1 != 0); + } + } + + /// Finishes the tile: returns its bytes (with the trailing one bit and zero padding). + pub fn finish(mut self) -> Vec { + let low = self.low + (1 << 14); + let mut tail = low >> 15; + self.carry(tail >> self.cnt); + tail &= (1u64 << self.cnt) - 1; + let mut bytes: Vec = self.out.iter().map(|b| !b).collect(); + let n = self.cnt + 1; // 1..=8 bits: !tail then the trailing one bit + let v = (((!tail) & ((1u64 << self.cnt) - 1)) << 1) | 1; + bytes.push((v << (8 - n)) as u8); + bytes + } +} + +/// CDF adaptation (spec 8.2.6, as in the decoder). +#[inline(always)] +pub(crate) fn update_cdf(cdf: &mut [u16], n: usize, symbol: usize) { + let count = cdf[n]; + let rate = 3 + (count > 15) as u32 + (count > 31) as u32 + (31 - (n as u32).leading_zeros()).min(2); + let mut tmp = 0u32; + for i in 0..n - 1 { + if i == symbol { + tmp = 1 << 15; + } + let c = cdf[i] as u32; + if tmp < c { + cdf[i] = (c - ((c - tmp) >> rate)) as u16; + } else { + cdf[i] = (c + ((tmp - c) >> rate)) as u16; + } + } + cdf[n] += (count < 32) as u16; +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The spec's symbol decoder (8.2.2 - 8.2.4), for checking the encoder. + struct Dec<'a> { + data: &'a [u8], + pos: usize, + value: u32, + range: u32, + max_bits: i64, + } + + impl<'a> Dec<'a> { + fn new(data: &'a [u8]) -> Self { + let mut d = Dec { data, pos: 0, value: 0, range: 1 << 15, max_bits: 8 * data.len() as i64 - 15 }; + let nb = (data.len() * 8).min(15) as u32; + let buf = d.bits(nb); + d.value = ((1 << 15) - 1) ^ (buf << (15 - nb)); + d + } + fn bits(&mut self, n: u32) -> u32 { + let mut x = 0; + for _ in 0..n { + let b = if self.pos < self.data.len() * 8 { (self.data[self.pos >> 3] >> (7 - (self.pos & 7))) & 1 } else { 0 }; + self.pos += 1; + x = (x << 1) | b as u32; + } + x + } + fn symbol(&mut self, cdf: &mut [u16], adapt: bool) -> usize { + let n = cdf.len() - 1; + let mut cur = self.range; + let mut s = 0; + let mut prev; + loop { + prev = cur; + let f = (1u32 << 15) - cdf[s] as u32; + cur = (((self.range >> 8) * (f >> 6)) >> 1) + 4 * (n - s - 1) as u32; + if self.value >= cur { + break; + } + s += 1; + } + self.range = prev - cur; + self.value -= cur; + let b = 15 - (31 - self.range.leading_zeros()); + if b > 0 { + self.range <<= b; + let nb = (b as i64).min(self.max_bits.max(0)) as u32; + let nd = self.bits(nb) << (b - nb); + self.value = nd ^ (((self.value + 1) << b) - 1); + self.max_bits -= b as i64; + } + if adapt { + update_cdf(cdf, n, s); + } + s + } + /// Position of the trailing one bit (8.2.4). + fn trailing_position(&self) -> i64 { + let consumed = (self.pos as i64).min(self.data.len() as i64 * 8); + consumed - (15i64).min(self.max_bits + 15) + } + } + + #[test] + fn bools_only() { + for len in 1..200 { + let bits: Vec = (0..len).map(|i| (i * 7 + len) % 3 == 0).collect(); + let mut w = SymbolWriter::new(false); + for &b in &bits { + w.bool(b); + } + let data = w.finish(); + let mut d = Dec::new(&data); + for (i, &b) in bits.iter().enumerate() { + assert_eq!(d.symbol(&mut [1 << 14, 1 << 15, 0], false) == 1, b, "len {len} bit {i} data {data:?}"); + } + } + } + + #[test] + fn round_trip_random_symbols() { + let mut seed = 0x1234_5678u32; + let mut rnd = || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + for trial in 0..300 { + let count = 1 + (rnd() % 3000) as usize; + let mut cdfs_e = vec![[4096u16, 11264, 19328, 32768, 0]; 4]; + let mut cdfs_d = cdfs_e.clone(); + let mut syms = Vec::new(); + let mut w = SymbolWriter::new(false); + for _ in 0..count { + let kind = rnd() % 3; + let ctx = (rnd() % 4) as usize; + // skewed symbols so that the range also gets small + let s = if rnd() % 4 == 0 { (rnd() % 4) as usize } else { 0 }; + match kind { + 0 => w.symbol(s, &mut cdfs_e[ctx]), + 1 => w.bool(s & 1 == 1), + _ => w.literal(5, s as u32 * 7), + } + syms.push((kind, ctx, s)); + } + let data = w.finish(); + let mut d = Dec::new(&data); + for (idx, &(kind, ctx, s)) in syms.iter().enumerate() { + let got = match kind { + 0 => d.symbol(&mut cdfs_d[ctx], true), + 1 => d.symbol(&mut [1 << 14, 1 << 15, 0], false), + _ => { + let mut v = 0; + for _ in 0..5 { + v = (v << 1) | d.symbol(&mut [1 << 14, 1 << 15, 0], false); + } + v / 7 + } + }; + let want = if kind == 1 { s & 1 } else { s }; + assert_eq!(got, want, "trial {trial} idx {idx} kind {kind} of {}", syms.len()); + } + let tp = d.trailing_position(); + assert!(tp >= 0 && (tp as usize) < data.len() * 8, "trial {trial}: trailing bit position {tp}"); + let tp = tp as usize; + assert_eq!((data[tp >> 3] >> (7 - (tp & 7))) & 1, 1, "trial {trial}: trailing one bit"); + for p in tp + 1..data.len() * 8 { + assert_eq!((data[p >> 3] >> (7 - (p & 7))) & 1, 0, "trial {trial}: padding"); + } + } + } +} diff --git a/crates/av1enc/src/tables.rs b/crates/av1enc/src/tables.rs new file mode 100644 index 00000000..3e333a51 --- /dev/null +++ b/crates/av1enc/src/tables.rs @@ -0,0 +1,4805 @@ +// Tables mirrored from filmcraft-av1's spec_tables.rs, which extracts them from the AV1 Bitstream & +// Decoding Process Specification v1.0.0 with Errata 1. Only the tables this encoder uses are kept. +#![allow(dead_code, clippy::unreadable_literal)] + +pub const ADST_ADST: usize = 3; +pub const ADST_DCT: usize = 1; +pub const ADST_FLIPADST: usize = 7; +pub const AFFINE: usize = 3; +pub const ALTREF2_FRAME: usize = 6; +pub const ALTREF_FRAME: usize = 7; +pub const ANGLE_STEP: usize = 3; +pub const BIDIR_COMP_REFERENCE: usize = 1; +pub const BILINEAR: usize = 3; +pub const BLOCK_128X128: usize = 15; +pub const BLOCK_128X64: usize = 14; +pub const BLOCK_16X16: usize = 6; +pub const BLOCK_16X32: usize = 7; +pub const BLOCK_16X4: usize = 17; +pub const BLOCK_16X64: usize = 20; +pub const BLOCK_16X8: usize = 5; +pub const BLOCK_32X16: usize = 8; +pub const BLOCK_32X32: usize = 9; +pub const BLOCK_32X64: usize = 10; +pub const BLOCK_32X8: usize = 19; +pub const BLOCK_4X16: usize = 16; +pub const BLOCK_4X4: usize = 0; +pub const BLOCK_4X8: usize = 1; +pub const BLOCK_64X128: usize = 13; +pub const BLOCK_64X16: usize = 21; +pub const BLOCK_64X32: usize = 11; +pub const BLOCK_64X64: usize = 12; +pub const BLOCK_8X16: usize = 4; +pub const BLOCK_8X32: usize = 18; +pub const BLOCK_8X4: usize = 2; +pub const BLOCK_8X8: usize = 3; +pub const BLOCK_INVALID: usize = 22; +pub const BLOCK_SIZES: usize = 22; +pub const BLOCK_SIZE_GROUPS: usize = 4; +pub const BR_CDF_SIZE: usize = 4; +pub const BUFFER_POOL_MAX_SIZE: usize = 10; +pub const BWDREF_FRAME: usize = 5; +pub const BWD_REFS: usize = 3; +pub const CFL_ALPHABET_SIZE: usize = 16; +pub const CFL_ALPHA_CONTEXTS: usize = 6; +pub const CFL_JOINT_SIGNS: usize = 8; +pub const CFL_SIGN_NEG: usize = 1; +pub const CFL_SIGN_POS: usize = 2; +pub const CFL_SIGN_ZERO: usize = 0; +pub const CLASS0_SIZE: usize = 2; +pub const COEFF_BASE_RANGE: usize = 12; +pub const COEFF_CDF_Q_CTXS: usize = 4; +pub const COMPOUND_AVERAGE: usize = 2; +pub const COMPOUND_DIFFWTD: usize = 1; +pub const COMPOUND_DISTANCE: usize = 4; +pub const COMPOUND_IDX_CONTEXTS: usize = 6; +pub const COMPOUND_INTRA: usize = 3; +pub const COMPOUND_MODES: usize = 8; +pub const COMPOUND_MODE_CONTEXTS: usize = 8; +pub const COMPOUND_REFERENCE: usize = 1; +pub const COMPOUND_TYPES: usize = 2; +pub const COMPOUND_WEDGE: usize = 0; +pub const COMP_GROUP_IDX_CONTEXTS: usize = 6; +pub const COMP_INTER_CONTEXTS: usize = 5; +pub const COMP_NEWMV_CTXS: usize = 5; +pub const COMP_REF_TYPE_CONTEXTS: usize = 5; +pub const CP_BT_2020: usize = 9; +pub const CP_BT_470_B_G: usize = 5; +pub const CP_BT_470_M: usize = 4; +pub const CP_BT_601: usize = 6; +pub const CP_BT_709: usize = 1; +pub const CP_EBU_3213: usize = 22; +pub const CP_GENERIC_FILM: usize = 8; +pub const CP_SMPTE_240: usize = 7; +pub const CP_SMPTE_431: usize = 11; +pub const CP_SMPTE_432: usize = 12; +pub const CP_UNSPECIFIED: usize = 2; +pub const CP_XYZ: usize = 10; +pub const CSP_COLOCATED: usize = 2; +pub const CSP_RESERVED: usize = 3; +pub const CSP_UNKNOWN: usize = 0; +pub const CSP_VERTICAL: usize = 1; +pub const D113_PRED: usize = 5; +pub const D135_PRED: usize = 4; +pub const D157_PRED: usize = 6; +pub const D203_PRED: usize = 7; +pub const D45_PRED: usize = 3; +pub const D67_PRED: usize = 8; +pub const DCT_ADST: usize = 2; +pub const DCT_DCT: usize = 0; +pub const DCT_FLIPADST: usize = 5; +pub const DC_PRED: usize = 0; +pub const DC_SIGN_CONTEXTS: usize = 3; +pub const DELTA_LF_SMALL: usize = 3; +pub const DELTA_Q_SMALL: usize = 3; +pub const DIRECTIONAL_MODES: usize = 8; +pub const DIV_LUT_BITS: usize = 8; +pub const DIV_LUT_NUM: usize = 257; +pub const DIV_LUT_PREC_BITS: usize = 14; +pub const DRL_MODE_CONTEXTS: usize = 3; +pub const EC_MIN_PROB: usize = 4; +pub const EC_PROB_SHIFT: usize = 6; +pub const EIGHTTAP: usize = 0; +pub const EIGHTTAP_SHARP: usize = 2; +pub const EIGHTTAP_SMOOTH: usize = 1; +pub const EOB_COEF_CONTEXTS: usize = 9; +pub const FILTER_BITS: usize = 7; +pub const FILTER_D157_PRED: usize = 3; +pub const FILTER_DC_PRED: usize = 0; +pub const FILTER_H_PRED: usize = 2; +pub const FILTER_PAETH_PRED: usize = 4; +pub const FILTER_V_PRED: usize = 1; +pub const FLIPADST_ADST: usize = 8; +pub const FLIPADST_DCT: usize = 4; +pub const FLIPADST_FLIPADST: usize = 6; +pub const FRAME_LF_COUNT: usize = 4; +pub const FWD_REFS: usize = 4; +pub const GLOBALMV: usize = 16; +pub const GLOBAL_GLOBALMV: usize = 24; +pub const GM_ABS_ALPHA_BITS: usize = 12; +pub const GM_ABS_TRANS_BITS: usize = 12; +pub const GM_ABS_TRANS_ONLY_BITS: usize = 9; +pub const GM_ALPHA_PREC_BITS: usize = 15; +pub const GM_TRANS_ONLY_PREC_BITS: usize = 3; +pub const GM_TRANS_PREC_BITS: usize = 6; +pub const GOLDEN_FRAME: usize = 4; +pub const H_ADST: usize = 13; +pub const H_DCT: usize = 11; +pub const H_FLIPADST: usize = 15; +pub const H_PRED: usize = 2; +pub const IDENTITY: usize = 0; +pub const IDTX: usize = 9; +pub const II_DC_PRED: usize = 0; +pub const II_H_PRED: usize = 2; +pub const II_SMOOTH_PRED: usize = 3; +pub const II_V_PRED: usize = 1; +pub const INTERINTRA_MODES: usize = 4; +pub const INTERP_FILTERS: usize = 3; +pub const INTERP_FILTER_CONTEXTS: usize = 16; +pub const INTER_FRAME: usize = 1; +pub const INTRABC_DELAY_PIXELS: usize = 256; +pub const INTRABC_DELAY_SB64: usize = 4; +pub const INTRA_EDGE_KERNELS: usize = 3; +pub const INTRA_EDGE_TAPS: usize = 5; +pub const INTRA_FILTER_MODES: usize = 5; +pub const INTRA_FILTER_SCALE_BITS: usize = 4; +pub const INTRA_FRAME: usize = 0; +pub const INTRA_MODES: usize = 13; +pub const INTRA_MODE_CONTEXTS: usize = 5; +pub const INTRA_ONLY_FRAME: usize = 2; +pub const IS_INTER_CONTEXTS: usize = 4; +pub const KEY_FRAME: usize = 0; +pub const LAST2_FRAME: usize = 2; +pub const LAST3_FRAME: usize = 3; +pub const LAST_FRAME: usize = 1; +pub const LEAST_SQUARES_SAMPLES_MAX: usize = 8; +pub const LEVEL_CONTEXTS: usize = 21; +pub const LOCALWARP: usize = 2; +pub const LS_MV_MAX: usize = 256; +pub const MASK_MASTER_SIZE: usize = 64; +pub const MAX_ANGLE_DELTA: usize = 3; +pub const MAX_FRAME_DISTANCE: usize = 31; +pub const MAX_LOOP_FILTER: usize = 63; +pub const MAX_OFFSET_HEIGHT: usize = 0; +pub const MAX_OFFSET_WIDTH: usize = 8; +pub const MAX_REF_MV_STACK_SIZE: usize = 8; +pub const MAX_SB_SIZE: usize = 128; +pub const MAX_SEGMENTS: usize = 8; +pub const MAX_TILE_AREA: usize = 9437184; +pub const MAX_TILE_COLS: usize = 64; +pub const MAX_TILE_ROWS: usize = 64; +pub const MAX_TILE_WIDTH: usize = 4096; +pub const MAX_TX_DEPTH: usize = 2; +pub const MAX_VARTX_DEPTH: usize = 2; +pub const MB_MODE_COUNT: usize = 17; +pub const MC_BT_2020_CL: usize = 10; +pub const MC_BT_2020_NCL: usize = 9; +pub const MC_BT_470_B_G: usize = 5; +pub const MC_BT_601: usize = 6; +pub const MC_BT_709: usize = 1; +pub const MC_CHROMAT_CL: usize = 13; +pub const MC_CHROMAT_NCL: usize = 12; +pub const MC_FCC: usize = 4; +pub const MC_ICTCP: usize = 14; +pub const MC_IDENTITY: usize = 0; +pub const MC_RESERVED_3: usize = 3; +pub const MC_SMPTE_2085: usize = 11; +pub const MC_SMPTE_240: usize = 7; +pub const MC_SMPTE_YCGCO: usize = 8; +pub const MC_UNSPECIFIED: usize = 2; +pub const METADATA_TYPE_HDR_CLL: usize = 1; +pub const METADATA_TYPE_HDR_MDCV: usize = 2; +pub const METADATA_TYPE_ITUT_T35: usize = 4; +pub const METADATA_TYPE_SCALABILITY: usize = 3; +pub const METADATA_TYPE_TIMECODE: usize = 5; +pub const MFMV_STACK_SIZE: usize = 3; +pub const MI_SIZE: usize = 4; +pub const MI_SIZE_LOG2: usize = 2; +pub const MOTION_MODES: usize = 3; +pub const MV_BORDER: usize = 128; +pub const MV_CLASSES: usize = 11; +pub const MV_CLASS_0: usize = 0; +pub const MV_CLASS_1: usize = 1; +pub const MV_CLASS_10: usize = 10; +pub const MV_CLASS_2: usize = 2; +pub const MV_CLASS_3: usize = 3; +pub const MV_CLASS_4: usize = 4; +pub const MV_CLASS_5: usize = 5; +pub const MV_CLASS_6: usize = 6; +pub const MV_CLASS_7: usize = 7; +pub const MV_CLASS_8: usize = 8; +pub const MV_CLASS_9: usize = 9; +pub const MV_CONTEXTS: usize = 2; +pub const MV_INTRABC_CONTEXT: usize = 1; +pub const MV_JOINTS: usize = 4; +pub const MV_JOINT_HNZVNZ: usize = 3; +pub const MV_JOINT_HNZVZ: usize = 1; +pub const MV_JOINT_HZVNZ: usize = 2; +pub const MV_JOINT_ZERO: usize = 0; +pub const MV_OFFSET_BITS: usize = 10; +pub const NEARESTMV: usize = 14; +pub const NEAREST_NEARESTMV: usize = 18; +pub const NEAREST_NEWMV: usize = 20; +pub const NEARMV: usize = 15; +pub const NEAR_NEARMV: usize = 19; +pub const NEAR_NEWMV: usize = 22; +pub const NEWMV: usize = 17; +pub const NEW_MV_CONTEXTS: usize = 6; +pub const NEW_NEARESTMV: usize = 21; +pub const NEW_NEARMV: usize = 23; +pub const NEW_NEWMV: usize = 25; +pub const NUM_BASE_LEVELS: usize = 2; +pub const NUM_REF_FRAMES: usize = 8; +pub const OBMC: usize = 1; +pub const OBU_FRAME: usize = 6; +pub const OBU_FRAME_HEADER: usize = 3; +pub const OBU_METADATA: usize = 5; +pub const OBU_PADDING: usize = 15; +pub const OBU_REDUNDANT_FRAME_HEADER: usize = 7; +pub const OBU_SEQUENCE_HEADER: usize = 1; +pub const OBU_TEMPORAL_DELIMITER: usize = 2; +pub const OBU_TILE_GROUP: usize = 4; +pub const OBU_TILE_LIST: usize = 8; +pub const ONLY_4X4: usize = 0; +pub const PAETH_PRED: usize = 12; +pub const PALETTE_BLOCK_SIZE_CONTEXTS: usize = 7; +pub const PALETTE_COLORS: usize = 8; +pub const PALETTE_COLOR_CONTEXTS: usize = 5; +pub const PALETTE_MAX_COLOR_CONTEXT_HASH: usize = 8; +pub const PALETTE_NUM_NEIGHBORS: usize = 3; +pub const PALETTE_SIZES: usize = 7; +pub const PALETTE_UV_MODE_CONTEXTS: usize = 2; +pub const PALETTE_Y_MODE_CONTEXTS: usize = 3; +pub const PARTITION_CONTEXTS: usize = 4; +pub const PARTITION_HORZ: usize = 1; +pub const PARTITION_HORZ_4: usize = 8; +pub const PARTITION_HORZ_A: usize = 4; +pub const PARTITION_HORZ_B: usize = 5; +pub const PARTITION_NONE: usize = 0; +pub const PARTITION_SPLIT: usize = 3; +pub const PARTITION_VERT: usize = 2; +pub const PARTITION_VERT_4: usize = 9; +pub const PARTITION_VERT_A: usize = 6; +pub const PARTITION_VERT_B: usize = 7; +pub const PLANE_TYPES: usize = 2; +pub const PRIMARY_REF_NONE: usize = 7; +pub const QM_TOTAL_SIZE: usize = 3344; +pub const REFMVS_LIMIT: usize = 4095; +pub const REFS_PER_FRAME: usize = 7; +pub const REF_CAT_LEVEL: usize = 640; +pub const REF_CONTEXTS: usize = 3; +pub const REF_MV_CONTEXTS: usize = 6; +pub const REF_SCALE_SHIFT: usize = 14; +pub const RESTORATION_TILESIZE_MAX: usize = 256; +pub const RESTORE_NONE: usize = 0; +pub const RESTORE_SGRPROJ: usize = 2; +pub const RESTORE_SWITCHABLE: usize = 3; +pub const RESTORE_WIENER: usize = 1; +pub const ROTZOOM: usize = 2; +pub const SCALABILITY_L1T2: usize = 0; +pub const SCALABILITY_L1T3: usize = 1; +pub const SCALABILITY_L2T1: usize = 2; +pub const SCALABILITY_L2T2: usize = 3; +pub const SCALABILITY_L2T3: usize = 4; +pub const SCALABILITY_L3T1: usize = 15; +pub const SCALABILITY_L3T2: usize = 16; +pub const SCALABILITY_L3T2_KEY: usize = 21; +pub const SCALABILITY_L3T2_KEY_SHIFT: usize = 25; +pub const SCALABILITY_L3T3: usize = 17; +pub const SCALABILITY_L3T3_KEY: usize = 22; +pub const SCALABILITY_L3T3_KEY_SHIFT: usize = 26; +pub const SCALABILITY_L4T5_KEY: usize = 23; +pub const SCALABILITY_L4T5_KEY_SHIFT: usize = 27; +pub const SCALABILITY_L4T7_KEY: usize = 24; +pub const SCALABILITY_L4T7_KEY_SHIFT: usize = 28; +pub const SCALABILITY_S2T1: usize = 5; +pub const SCALABILITY_S2T2: usize = 6; +pub const SCALABILITY_S2T3: usize = 7; +pub const SCALABILITY_S3T1: usize = 18; +pub const SCALABILITY_S3T2: usize = 19; +pub const SCALABILITY_S3T3: usize = 20; +pub const SCALABILITY_SS: usize = 14; +pub const SCALE_SUBPEL_BITS: usize = 10; +pub const SEGMENT_ID_CONTEXTS: usize = 3; +pub const SEGMENT_ID_PREDICTED_CONTEXTS: usize = 3; +pub const SEG_LVL_ALT_LF_Y_V: usize = 1; +pub const SEG_LVL_ALT_Q: usize = 0; +pub const SEG_LVL_GLOBALMV: usize = 7; +pub const SEG_LVL_MAX: usize = 8; +pub const SEG_LVL_REF_FRAME: usize = 5; +pub const SEG_LVL_SKIP: usize = 6; +pub const SELECT_INTEGER_MV: usize = 2; +pub const SELECT_SCREEN_CONTENT_TOOLS: usize = 2; +pub const SGRPROJ_MTABLE_BITS: usize = 20; +pub const SGRPROJ_PARAMS_BITS: usize = 4; +pub const SGRPROJ_PRJ_BITS: usize = 7; +pub const SGRPROJ_PRJ_SUBEXP_K: usize = 4; +pub const SGRPROJ_RECIP_BITS: usize = 12; +pub const SGRPROJ_RST_BITS: usize = 4; +pub const SGRPROJ_SGR_BITS: usize = 8; +pub const SIG_COEF_CONTEXTS: usize = 42; +pub const SIG_COEF_CONTEXTS_2D: usize = 26; +pub const SIG_COEF_CONTEXTS_EOB: usize = 4; +pub const SIG_REF_DIFF_OFFSET_NUM: usize = 5; +pub const SIMPLE: usize = 0; +pub const SINGLE_REFERENCE: usize = 0; +pub const SINGLE_REFS: usize = 7; +pub const SINPI_1_9: usize = 1321; +pub const SINPI_2_9: usize = 2482; +pub const SINPI_3_9: usize = 3344; +pub const SINPI_4_9: usize = 3803; +pub const SKIP_CONTEXTS: usize = 3; +pub const SKIP_MODE_CONTEXTS: usize = 3; +pub const SMOOTH_H_PRED: usize = 11; +pub const SMOOTH_PRED: usize = 9; +pub const SMOOTH_V_PRED: usize = 10; +pub const SUBPEL_BITS: usize = 4; +pub const SUBPEL_MASK: usize = 15; +pub const SUPERRES_DENOM_BITS: usize = 3; +pub const SUPERRES_DENOM_MIN: usize = 9; +pub const SUPERRES_EXTRA_BITS: usize = 8; +pub const SUPERRES_FILTER_BITS: usize = 6; +pub const SUPERRES_FILTER_OFFSET: usize = 3; +pub const SUPERRES_FILTER_SHIFTS: usize = 64; +pub const SUPERRES_FILTER_TAPS: usize = 8; +pub const SUPERRES_NUM: usize = 8; +pub const SUPERRES_SCALE_BITS: usize = 14; +pub const SUPERRES_SCALE_MASK: usize = 16383; +pub const SWITCHABLE: usize = 4; +pub const SWITCH_FRAME: usize = 3; +pub const TC_BT_1361: usize = 12; +pub const TC_BT_2020_10_BIT: usize = 14; +pub const TC_BT_2020_12_BIT: usize = 15; +pub const TC_BT_470_B_G: usize = 5; +pub const TC_BT_470_M: usize = 4; +pub const TC_BT_601: usize = 6; +pub const TC_BT_709: usize = 1; +pub const TC_HLG: usize = 18; +pub const TC_IEC_61966: usize = 11; +pub const TC_LINEAR: usize = 8; +pub const TC_LOG_100: usize = 9; +pub const TC_LOG_100_SQRT10: usize = 10; +pub const TC_RESERVED_0: usize = 0; +pub const TC_RESERVED_3: usize = 3; +pub const TC_SMPTE_2084: usize = 16; +pub const TC_SMPTE_240: usize = 7; +pub const TC_SMPTE_428: usize = 17; +pub const TC_SRGB: usize = 13; +pub const TC_UNSPECIFIED: usize = 2; +pub const TOTAL_REFS_PER_FRAME: usize = 8; +pub const TRANSLATION: usize = 1; +pub const TXB_SKIP_CONTEXTS: usize = 13; +pub const TXFM_PARTITION_CONTEXTS: usize = 21; +pub const TX_16X16: usize = 2; +pub const TX_16X32: usize = 9; +pub const TX_16X4: usize = 14; +pub const TX_16X64: usize = 17; +pub const TX_16X8: usize = 8; +pub const TX_32X16: usize = 10; +pub const TX_32X32: usize = 3; +pub const TX_32X64: usize = 11; +pub const TX_32X8: usize = 16; +pub const TX_4X16: usize = 13; +pub const TX_4X4: usize = 0; +pub const TX_4X8: usize = 5; +pub const TX_64X16: usize = 18; +pub const TX_64X32: usize = 12; +pub const TX_64X64: usize = 4; +pub const TX_8X16: usize = 7; +pub const TX_8X32: usize = 15; +pub const TX_8X4: usize = 6; +pub const TX_8X8: usize = 1; +pub const TX_CLASS_2D: usize = 0; +pub const TX_CLASS_HORIZ: usize = 1; +pub const TX_CLASS_VERT: usize = 2; +pub const TX_MODES: usize = 3; +pub const TX_MODE_LARGEST: usize = 1; +pub const TX_MODE_SELECT: usize = 2; +pub const TX_SET_DCTONLY: usize = 0; +pub const TX_SET_INTER_1: usize = 1; +pub const TX_SET_INTER_2: usize = 2; +pub const TX_SET_INTER_3: usize = 3; +pub const TX_SET_INTRA_1: usize = 1; +pub const TX_SET_INTRA_2: usize = 2; +pub const TX_SET_TYPES_INTER: usize = 4; +pub const TX_SET_TYPES_INTRA: usize = 3; +pub const TX_SIZES: usize = 5; +pub const TX_SIZES_ALL: usize = 19; +pub const TX_SIZE_CONTEXTS: usize = 3; +pub const TX_TYPES: usize = 16; +pub const UNIDIR_COMP_REFERENCE: usize = 0; +pub const UNIDIR_COMP_REFS: usize = 4; +pub const UNIFORM_45: usize = 0; +pub const UNIFORM_45_INV: usize = 1; +pub const UV_CFL_PRED: usize = 13; +pub const UV_INTRA_MODES_CFL_ALLOWED: usize = 14; +pub const UV_INTRA_MODES_CFL_NOT_ALLOWED: usize = 13; +pub const V_ADST: usize = 12; +pub const V_DCT: usize = 10; +pub const V_FLIPADST: usize = 14; +pub const V_PRED: usize = 1; +pub const WARPEDDIFF_PREC_BITS: usize = 10; +pub const WARPEDMODEL_NONDIAGAFFINE_CLAMP: usize = 8192; +pub const WARPEDMODEL_PREC_BITS: usize = 16; +pub const WARPEDMODEL_TRANS_CLAMP: usize = 8388608; +pub const WARPEDPIXEL_PREC_SHIFTS: usize = 64; +pub const WARP_PARAM_REDUCE_BITS: usize = 6; +pub const WEDGE_HORIZONTAL: usize = 0; +pub const WEDGE_OBLIQUE117: usize = 4; +pub const WEDGE_OBLIQUE153: usize = 5; +pub const WEDGE_OBLIQUE27: usize = 2; +pub const WEDGE_OBLIQUE63: usize = 3; +pub const WEDGE_TYPES: usize = 16; +pub const WEDGE_VERTICAL: usize = 1; +pub const WIENER_COEFFS: usize = 3; +pub const ZERO_MV_CONTEXTS: usize = 2; + +pub static AC_QLOOKUP: [[u16; 256]; 3] = [ + [ + 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, + 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188, 191, 194, 197, 200, 203, 207, + 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260, 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, + 359, 366, 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520, 530, 540, 550, 560, 571, 582, 593, 604, 615, + 627, 639, 651, 663, 676, 689, 702, 715, 729, 743, 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066, + 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537, 1567, 1597, 1628, 1660, 1692, 1725, + 1759, 1793, 1828, + ], + [ + 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, + 140, 145, 149, 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231, 235, 240, 244, 249, 253, 258, 262, 267, 271, + 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, 319, 324, 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396, 401, + 409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547, 555, 563, 571, 579, 588, 596, 604, 616, 628, 640, 652, 664, + 676, 688, 700, 713, 725, 737, 749, 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038, 1058, 1078, 1098, + 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463, 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, + 1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079, 2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, + 2807, 2859, 2915, 2971, 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264, 4348, 4432, + 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148, 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, + 7312, + ], + [ + 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, + 456, 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, 679, 698, 716, 735, 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939, 957, 976, 994, + 1012, 1030, 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317, 1335, 1352, 1370, 1387, 1405, 1422, 1440, + 1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595, 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, 2085, 2118, + 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750, 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, + 3234, 3298, 3362, 3426, 3491, 3555, 3619, 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791, 4871, 4967, + 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410, 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, + 8011, 8155, 8315, 8475, 8635, 8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661, 11885, 12109, + 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806, 16110, 16414, 16734, 17054, 17390, 17726, 18062, + 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486, 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, + 27599, 28143, 28687, 29247, + ], +]; + +pub static ADJUSTED_TX_SIZE: [u8; 19] = [0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 10, 3, 3, 13, 14, 15, 16, 9, 10]; + +pub static COEFF_BASE_CTX_OFFSET: [[[u8; 5]; 5]; 19] = [ + [[0, 1, 6, 6, 0], [1, 6, 6, 21, 0], [6, 6, 21, 21, 0], [6, 21, 21, 21, 0], [0, 0, 0, 0, 0]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 11, 11, 11, 0], [11, 11, 11, 11, 0], [6, 6, 21, 21, 0], [6, 21, 21, 21, 0], [21, 21, 21, 21, 0]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [0, 0, 0, 0, 0]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 0], [11, 11, 11, 11, 0], [6, 6, 21, 21, 0], [6, 21, 21, 21, 0], [21, 21, 21, 21, 0]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [0, 0, 0, 0, 0]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], +]; + +pub static COEFF_BASE_POS_CTX_OFFSET: [u8; 3] = [26, 31, 36]; + +pub static COS128_LOOKUP: [u16; 65] = [ + 4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3996, 3973, 3948, 3920, 3889, 3857, 3822, 3784, 3745, 3703, 3659, 3612, 3564, 3513, 3461, 3406, 3349, + 3290, 3229, 3166, 3102, 3035, 2967, 2896, 2824, 2751, 2675, 2598, 2520, 2440, 2359, 2276, 2191, 2106, 2019, 1931, 1842, 1751, 1660, 1567, 1474, 1380, 1285, + 1189, 1092, 995, 897, 799, 700, 601, 501, 401, 301, 201, 101, 0, +]; + +pub static DC_QLOOKUP: [[u16; 256]; 3] = [ + [ + 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, + 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 70, 70, + 71, 72, 73, 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, + 113, 114, 116, 117, 118, 120, 121, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 161, 164, 166, 169, 172, + 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220, 223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, + 269, 272, 276, 280, 284, 288, 292, 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, 369, 374, 379, 384, 389, 395, 400, + 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513, 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, + 668, 684, 700, 717, 736, 755, 775, 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336, + ], + [ + 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, + 124, 128, 132, 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200, 204, 208, 212, 215, 219, 223, 226, 230, 233, + 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, 273, 276, 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334, 337, + 343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448, 454, 460, 466, 472, 478, 484, 490, 499, 507, 516, 525, 533, + 542, 550, 559, 567, 576, 584, 592, 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782, 795, 807, 819, 831, + 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030, 1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, + 1236, 1253, 1271, 1288, 1306, 1323, 1342, 1361, 1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, + 1741, 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363, 2410, 2458, 2508, 2561, 2616, + 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823, 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347, + ], + [ + 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, + 405, 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, 596, 611, 627, 643, 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814, 829, 844, 859, + 874, 889, 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, 1094, 1108, 1122, 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, + 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342, 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668, 1692, 1717, 1741, 1765, + 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199, 2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, + 2575, 2618, 2661, 2704, 2746, 2788, 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517, 3565, 3621, 3677, + 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420, 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, + 5291, 5367, 5442, 5517, 5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867, 6966, 7064, 7163, 7269, 7376, + 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, 8492, 8635, 8788, 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482, + 11776, 12081, 12409, 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387, + ], +]; + +pub static DEFAULT_ANGLE_DELTA_CDF: [[u16; 8]; 8] = [ + [2180, 5032, 7567, 22776, 26989, 30217, 32768, 0], + [2301, 5608, 8801, 23487, 26974, 30330, 32768, 0], + [3780, 11018, 13699, 19354, 23083, 31286, 32768, 0], + [4581, 11226, 15147, 17138, 21834, 28397, 32768, 0], + [1737, 10927, 14509, 19588, 22745, 28823, 32768, 0], + [2664, 10176, 12485, 17650, 21600, 30495, 32768, 0], + [2240, 11096, 15453, 20341, 22561, 28917, 32768, 0], + [3605, 10428, 12459, 17676, 21244, 30655, 32768, 0], +]; + +pub static DEFAULT_COEFF_BASE_CDF: [[[[[u16; 5]; 42]; 2]; 5]; 4] = [ + [ + [ + [ + [4034, 8930, 12727, 32768, 0], + [18082, 29741, 31877, 32768, 0], + [12596, 26124, 30493, 32768, 0], + [9446, 21118, 27005, 32768, 0], + [6308, 15141, 21279, 32768, 0], + [2463, 6357, 9783, 32768, 0], + [20667, 30546, 31929, 32768, 0], + [13043, 26123, 30134, 32768, 0], + [8151, 18757, 24778, 32768, 0], + [5255, 12839, 18632, 32768, 0], + [2820, 7206, 11161, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [15736, 27553, 30604, 32768, 0], + [11210, 23794, 28787, 32768, 0], + [5947, 13874, 19701, 32768, 0], + [4215, 9323, 13891, 32768, 0], + [2833, 6462, 10059, 32768, 0], + [19605, 30393, 31582, 32768, 0], + [13523, 26252, 30248, 32768, 0], + [8446, 18622, 24512, 32768, 0], + [3818, 10343, 15974, 32768, 0], + [1481, 4117, 6796, 32768, 0], + [22649, 31302, 32190, 32768, 0], + [14829, 27127, 30449, 32768, 0], + [8313, 17702, 23304, 32768, 0], + [3022, 8301, 12786, 32768, 0], + [1536, 4412, 7184, 32768, 0], + [22354, 29774, 31372, 32768, 0], + [14723, 25472, 29214, 32768, 0], + [6673, 13745, 18662, 32768, 0], + [2068, 5766, 9322, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [6302, 16444, 21761, 32768, 0], + [23040, 31538, 32475, 32768, 0], + [15196, 28452, 31496, 32768, 0], + [10020, 22946, 28514, 32768, 0], + [6533, 16862, 23501, 32768, 0], + [3538, 9816, 15076, 32768, 0], + [24444, 31875, 32525, 32768, 0], + [15881, 28924, 31635, 32768, 0], + [9922, 22873, 28466, 32768, 0], + [6527, 16966, 23691, 32768, 0], + [4114, 11303, 17220, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [20201, 30770, 32209, 32768, 0], + [14754, 28071, 31258, 32768, 0], + [8378, 20186, 26517, 32768, 0], + [5916, 15299, 21978, 32768, 0], + [4268, 11583, 17901, 32768, 0], + [24361, 32025, 32581, 32768, 0], + [18673, 30105, 31943, 32768, 0], + [10196, 22244, 27576, 32768, 0], + [5495, 14349, 20417, 32768, 0], + [2676, 7415, 11498, 32768, 0], + [24678, 31958, 32585, 32768, 0], + [18629, 29906, 31831, 32768, 0], + [9364, 20724, 26315, 32768, 0], + [4641, 12318, 18094, 32768, 0], + [2758, 7387, 11579, 32768, 0], + [25433, 31842, 32469, 32768, 0], + [18795, 29289, 31411, 32768, 0], + [7644, 17584, 23592, 32768, 0], + [3408, 9014, 15047, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [4536, 10072, 14001, 32768, 0], + [25459, 31416, 32206, 32768, 0], + [16605, 28048, 30818, 32768, 0], + [11008, 22857, 27719, 32768, 0], + [6915, 16268, 22315, 32768, 0], + [2625, 6812, 10537, 32768, 0], + [24257, 31788, 32499, 32768, 0], + [16880, 29454, 31879, 32768, 0], + [11958, 25054, 29778, 32768, 0], + [7916, 18718, 25084, 32768, 0], + [3383, 8777, 13446, 32768, 0], + [22720, 31603, 32393, 32768, 0], + [14960, 28125, 31335, 32768, 0], + [9731, 22210, 27928, 32768, 0], + [6304, 15832, 22277, 32768, 0], + [2910, 7818, 12166, 32768, 0], + [20375, 30627, 32131, 32768, 0], + [13904, 27284, 30887, 32768, 0], + [9368, 21558, 27144, 32768, 0], + [5937, 14966, 21119, 32768, 0], + [2667, 7225, 11319, 32768, 0], + [23970, 31470, 32378, 32768, 0], + [17173, 29734, 32018, 32768, 0], + [12795, 25441, 29965, 32768, 0], + [8981, 19680, 25893, 32768, 0], + [4728, 11372, 16902, 32768, 0], + [24287, 31797, 32439, 32768, 0], + [16703, 29145, 31696, 32768, 0], + [10833, 23554, 28725, 32768, 0], + [6468, 16566, 23057, 32768, 0], + [2415, 6562, 10278, 32768, 0], + [26610, 32395, 32659, 32768, 0], + [18590, 30498, 32117, 32768, 0], + [12420, 25756, 29950, 32768, 0], + [7639, 18746, 24710, 32768, 0], + [3001, 8086, 12347, 32768, 0], + [25076, 32064, 32580, 32768, 0], + [17946, 30128, 32028, 32768, 0], + [12024, 24985, 29378, 32768, 0], + [7517, 18390, 24304, 32768, 0], + [3243, 8781, 13331, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [6037, 16771, 21957, 32768, 0], + [24774, 31704, 32426, 32768, 0], + [16830, 28589, 31056, 32768, 0], + [10602, 22828, 27760, 32768, 0], + [6733, 16829, 23071, 32768, 0], + [3250, 8914, 13556, 32768, 0], + [25582, 32220, 32668, 32768, 0], + [18659, 30342, 32223, 32768, 0], + [12546, 26149, 30515, 32768, 0], + [8420, 20451, 26801, 32768, 0], + [4636, 12420, 18344, 32768, 0], + [27581, 32362, 32639, 32768, 0], + [18987, 30083, 31978, 32768, 0], + [11327, 24248, 29084, 32768, 0], + [7264, 17719, 24120, 32768, 0], + [3995, 10768, 16169, 32768, 0], + [25893, 31831, 32487, 32768, 0], + [16577, 28587, 31379, 32768, 0], + [10189, 22748, 28182, 32768, 0], + [6832, 17094, 23556, 32768, 0], + [3708, 10110, 15334, 32768, 0], + [25904, 32282, 32656, 32768, 0], + [19721, 30792, 32276, 32768, 0], + [12819, 26243, 30411, 32768, 0], + [8572, 20614, 26891, 32768, 0], + [5364, 14059, 20467, 32768, 0], + [26580, 32438, 32677, 32768, 0], + [20852, 31225, 32340, 32768, 0], + [12435, 25700, 29967, 32768, 0], + [8691, 20825, 26976, 32768, 0], + [4446, 12209, 17269, 32768, 0], + [27350, 32429, 32696, 32768, 0], + [21372, 30977, 32272, 32768, 0], + [12673, 25270, 29853, 32768, 0], + [9208, 20925, 26640, 32768, 0], + [5018, 13351, 18732, 32768, 0], + [27351, 32479, 32713, 32768, 0], + [21398, 31209, 32387, 32768, 0], + [12162, 25047, 29842, 32768, 0], + [7896, 18691, 25319, 32768, 0], + [4670, 12882, 18881, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [5487, 10460, 13708, 32768, 0], + [21597, 28303, 30674, 32768, 0], + [11037, 21953, 26476, 32768, 0], + [8147, 17962, 22952, 32768, 0], + [5242, 13061, 18532, 32768, 0], + [1889, 5208, 8182, 32768, 0], + [26774, 32133, 32590, 32768, 0], + [17844, 29564, 31767, 32768, 0], + [11690, 24438, 29171, 32768, 0], + [7542, 18215, 24459, 32768, 0], + [2993, 8050, 12319, 32768, 0], + [28023, 32328, 32591, 32768, 0], + [18651, 30126, 31954, 32768, 0], + [12164, 25146, 29589, 32768, 0], + [7762, 18530, 24771, 32768, 0], + [3492, 9183, 13920, 32768, 0], + [27591, 32008, 32491, 32768, 0], + [17149, 28853, 31510, 32768, 0], + [11485, 24003, 28860, 32768, 0], + [7697, 18086, 24210, 32768, 0], + [3075, 7999, 12218, 32768, 0], + [28268, 32482, 32654, 32768, 0], + [19631, 31051, 32404, 32768, 0], + [13860, 27260, 31020, 32768, 0], + [9605, 21613, 27594, 32768, 0], + [4876, 12162, 17908, 32768, 0], + [27248, 32316, 32576, 32768, 0], + [18955, 30457, 32075, 32768, 0], + [11824, 23997, 28795, 32768, 0], + [7346, 18196, 24647, 32768, 0], + [3403, 9247, 14111, 32768, 0], + [29711, 32655, 32735, 32768, 0], + [21169, 31394, 32417, 32768, 0], + [13487, 27198, 30957, 32768, 0], + [8828, 21683, 27614, 32768, 0], + [4270, 11451, 17038, 32768, 0], + [28708, 32578, 32731, 32768, 0], + [20120, 31241, 32482, 32768, 0], + [13692, 27550, 31321, 32768, 0], + [9418, 22514, 28439, 32768, 0], + [4999, 13283, 19462, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [5673, 14302, 19711, 32768, 0], + [26251, 30701, 31834, 32768, 0], + [12782, 23783, 27803, 32768, 0], + [9127, 20657, 25808, 32768, 0], + [6368, 16208, 21462, 32768, 0], + [2465, 7177, 10822, 32768, 0], + [29961, 32563, 32719, 32768, 0], + [18318, 29891, 31949, 32768, 0], + [11361, 24514, 29357, 32768, 0], + [7900, 19603, 25607, 32768, 0], + [4002, 10590, 15546, 32768, 0], + [29637, 32310, 32595, 32768, 0], + [18296, 29913, 31809, 32768, 0], + [10144, 21515, 26871, 32768, 0], + [5358, 14322, 20394, 32768, 0], + [3067, 8362, 13346, 32768, 0], + [28652, 32470, 32676, 32768, 0], + [17538, 30771, 32209, 32768, 0], + [13924, 26882, 30494, 32768, 0], + [10496, 22837, 27869, 32768, 0], + [7236, 16396, 21621, 32768, 0], + [30743, 32687, 32746, 32768, 0], + [23006, 31676, 32489, 32768, 0], + [14494, 27828, 31120, 32768, 0], + [10174, 22801, 28352, 32768, 0], + [6242, 15281, 21043, 32768, 0], + [25817, 32243, 32720, 32768, 0], + [18618, 31367, 32325, 32768, 0], + [13997, 28318, 31878, 32768, 0], + [12255, 26534, 31383, 32768, 0], + [9561, 21588, 28450, 32768, 0], + [28188, 32635, 32724, 32768, 0], + [22060, 32365, 32728, 32768, 0], + [18102, 30690, 32528, 32768, 0], + [14196, 28864, 31999, 32768, 0], + [12262, 25792, 30865, 32768, 0], + [24176, 32109, 32628, 32768, 0], + [18280, 29681, 31963, 32768, 0], + [10205, 23703, 29664, 32768, 0], + [7889, 20025, 27676, 32768, 0], + [6060, 16743, 23970, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [5141, 7096, 8260, 32768, 0], + [27186, 29022, 29789, 32768, 0], + [6668, 12568, 15682, 32768, 0], + [2172, 6181, 8638, 32768, 0], + [1126, 3379, 4531, 32768, 0], + [443, 1361, 2254, 32768, 0], + [26083, 31153, 32436, 32768, 0], + [13486, 24603, 28483, 32768, 0], + [6508, 14840, 19910, 32768, 0], + [3386, 8800, 13286, 32768, 0], + [1530, 4322, 7054, 32768, 0], + [29639, 32080, 32548, 32768, 0], + [15897, 27552, 30290, 32768, 0], + [8588, 20047, 25383, 32768, 0], + [4889, 13339, 19269, 32768, 0], + [2240, 6871, 10498, 32768, 0], + [28165, 32197, 32517, 32768, 0], + [20735, 30427, 31568, 32768, 0], + [14325, 24671, 27692, 32768, 0], + [5119, 12554, 17805, 32768, 0], + [1810, 5441, 8261, 32768, 0], + [31212, 32724, 32748, 32768, 0], + [23352, 31766, 32545, 32768, 0], + [14669, 27570, 31059, 32768, 0], + [8492, 20894, 27272, 32768, 0], + [3644, 10194, 15204, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [2461, 7013, 9371, 32768, 0], + [24749, 29600, 30986, 32768, 0], + [9466, 19037, 22417, 32768, 0], + [3584, 9280, 14400, 32768, 0], + [1505, 3929, 5433, 32768, 0], + [677, 1500, 2736, 32768, 0], + [23987, 30702, 32117, 32768, 0], + [13554, 24571, 29263, 32768, 0], + [6211, 14556, 21155, 32768, 0], + [3135, 10972, 15625, 32768, 0], + [2435, 7127, 11427, 32768, 0], + [31300, 32532, 32550, 32768, 0], + [14757, 30365, 31954, 32768, 0], + [4405, 11612, 18553, 32768, 0], + [580, 4132, 7322, 32768, 0], + [1695, 10169, 14124, 32768, 0], + [30008, 32282, 32591, 32768, 0], + [19244, 30108, 31748, 32768, 0], + [11180, 24158, 29555, 32768, 0], + [5650, 14972, 19209, 32768, 0], + [2114, 5109, 8456, 32768, 0], + [31856, 32716, 32748, 32768, 0], + [23012, 31664, 32572, 32768, 0], + [13694, 26656, 30636, 32768, 0], + [8142, 19508, 26093, 32768, 0], + [4253, 10955, 16724, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [601, 983, 1311, 32768, 0], + [18725, 23406, 28087, 32768, 0], + [5461, 8192, 10923, 32768, 0], + [3781, 15124, 21425, 32768, 0], + [2587, 7761, 12072, 32768, 0], + [106, 458, 810, 32768, 0], + [22282, 29710, 31894, 32768, 0], + [8508, 20926, 25984, 32768, 0], + [3726, 12713, 18083, 32768, 0], + [1620, 7112, 10893, 32768, 0], + [729, 2236, 3495, 32768, 0], + [30163, 32474, 32684, 32768, 0], + [18304, 30464, 32000, 32768, 0], + [11443, 26526, 29647, 32768, 0], + [6007, 15292, 21299, 32768, 0], + [2234, 6703, 8937, 32768, 0], + [30954, 32177, 32571, 32768, 0], + [17363, 29562, 31076, 32768, 0], + [9686, 22464, 27410, 32768, 0], + [8192, 16384, 21390, 32768, 0], + [1755, 8046, 11264, 32768, 0], + [31168, 32734, 32748, 32768, 0], + [22486, 31441, 32471, 32768, 0], + [12833, 25627, 29738, 32768, 0], + [6980, 17379, 23122, 32768, 0], + [3111, 8887, 13479, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [6041, 11854, 15927, 32768, 0], + [20326, 30905, 32251, 32768, 0], + [14164, 26831, 30725, 32768, 0], + [9760, 20647, 26585, 32768, 0], + [6416, 14953, 21219, 32768, 0], + [2966, 7151, 10891, 32768, 0], + [23567, 31374, 32254, 32768, 0], + [14978, 27416, 30946, 32768, 0], + [9434, 20225, 26254, 32768, 0], + [6658, 14558, 20535, 32768, 0], + [3916, 8677, 12989, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [18088, 29545, 31587, 32768, 0], + [13062, 25843, 30073, 32768, 0], + [8940, 16827, 22251, 32768, 0], + [7654, 13220, 17973, 32768, 0], + [5733, 10316, 14456, 32768, 0], + [22879, 31388, 32114, 32768, 0], + [15215, 27993, 30955, 32768, 0], + [9397, 19445, 24978, 32768, 0], + [3442, 9813, 15344, 32768, 0], + [1368, 3936, 6532, 32768, 0], + [25494, 32033, 32406, 32768, 0], + [16772, 27963, 30718, 32768, 0], + [9419, 18165, 23260, 32768, 0], + [2677, 7501, 11797, 32768, 0], + [1516, 4344, 7170, 32768, 0], + [26556, 31454, 32101, 32768, 0], + [17128, 27035, 30108, 32768, 0], + [8324, 15344, 20249, 32768, 0], + [1903, 5696, 9469, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8455, 19003, 24368, 32768, 0], + [23563, 32021, 32604, 32768, 0], + [16237, 29446, 31935, 32768, 0], + [10724, 23999, 29358, 32768, 0], + [6725, 17528, 24416, 32768, 0], + [3927, 10927, 16825, 32768, 0], + [26313, 32288, 32634, 32768, 0], + [17430, 30095, 32095, 32768, 0], + [11116, 24606, 29679, 32768, 0], + [7195, 18384, 25269, 32768, 0], + [4726, 12852, 19315, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [22822, 31648, 32483, 32768, 0], + [16724, 29633, 31929, 32768, 0], + [10261, 23033, 28725, 32768, 0], + [7029, 17840, 24528, 32768, 0], + [4867, 13886, 21502, 32768, 0], + [25298, 31892, 32491, 32768, 0], + [17809, 29330, 31512, 32768, 0], + [9668, 21329, 26579, 32768, 0], + [4774, 12956, 18976, 32768, 0], + [2322, 7030, 11540, 32768, 0], + [25472, 31920, 32543, 32768, 0], + [17957, 29387, 31632, 32768, 0], + [9196, 20593, 26400, 32768, 0], + [4680, 12705, 19202, 32768, 0], + [2917, 8456, 13436, 32768, 0], + [26471, 32059, 32574, 32768, 0], + [18458, 29783, 31909, 32768, 0], + [8400, 19464, 25956, 32768, 0], + [3812, 10973, 17206, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [6779, 13743, 17678, 32768, 0], + [24806, 31797, 32457, 32768, 0], + [17616, 29047, 31372, 32768, 0], + [11063, 23175, 28003, 32768, 0], + [6521, 16110, 22324, 32768, 0], + [2764, 7504, 11654, 32768, 0], + [25266, 32367, 32637, 32768, 0], + [19054, 30553, 32175, 32768, 0], + [12139, 25212, 29807, 32768, 0], + [7311, 18162, 24704, 32768, 0], + [3397, 9164, 14074, 32768, 0], + [25988, 32208, 32522, 32768, 0], + [16253, 28912, 31526, 32768, 0], + [9151, 21387, 27372, 32768, 0], + [5688, 14915, 21496, 32768, 0], + [2717, 7627, 12004, 32768, 0], + [23144, 31855, 32443, 32768, 0], + [16070, 28491, 31325, 32768, 0], + [8702, 20467, 26517, 32768, 0], + [5243, 13956, 20367, 32768, 0], + [2621, 7335, 11567, 32768, 0], + [26636, 32340, 32630, 32768, 0], + [19990, 31050, 32341, 32768, 0], + [13243, 26105, 30315, 32768, 0], + [8588, 19521, 25918, 32768, 0], + [4717, 11585, 17304, 32768, 0], + [25844, 32292, 32582, 32768, 0], + [19090, 30635, 32097, 32768, 0], + [11963, 24546, 28939, 32768, 0], + [6218, 16087, 22354, 32768, 0], + [2340, 6608, 10426, 32768, 0], + [28046, 32576, 32694, 32768, 0], + [21178, 31313, 32296, 32768, 0], + [13486, 26184, 29870, 32768, 0], + [7149, 17871, 23723, 32768, 0], + [2833, 7958, 12259, 32768, 0], + [27710, 32528, 32686, 32768, 0], + [20674, 31076, 32268, 32768, 0], + [12413, 24955, 29243, 32768, 0], + [6676, 16927, 23097, 32768, 0], + [2966, 8333, 12919, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8639, 19339, 24429, 32768, 0], + [24404, 31837, 32525, 32768, 0], + [16997, 29425, 31784, 32768, 0], + [11253, 24234, 29149, 32768, 0], + [6751, 17394, 24028, 32768, 0], + [3490, 9830, 15191, 32768, 0], + [26283, 32471, 32714, 32768, 0], + [19599, 31168, 32442, 32768, 0], + [13146, 26954, 30893, 32768, 0], + [8214, 20588, 26890, 32768, 0], + [4699, 13081, 19300, 32768, 0], + [28212, 32458, 32669, 32768, 0], + [18594, 30316, 32100, 32768, 0], + [11219, 24408, 29234, 32768, 0], + [6865, 17656, 24149, 32768, 0], + [3678, 10362, 16006, 32768, 0], + [25825, 32136, 32616, 32768, 0], + [17313, 29853, 32021, 32768, 0], + [11197, 24471, 29472, 32768, 0], + [6947, 17781, 24405, 32768, 0], + [3768, 10660, 16261, 32768, 0], + [27352, 32500, 32706, 32768, 0], + [20850, 31468, 32469, 32768, 0], + [14021, 27707, 31133, 32768, 0], + [8964, 21748, 27838, 32768, 0], + [5437, 14665, 21187, 32768, 0], + [26304, 32492, 32698, 32768, 0], + [20409, 31380, 32385, 32768, 0], + [13682, 27222, 30632, 32768, 0], + [8974, 21236, 26685, 32768, 0], + [4234, 11665, 16934, 32768, 0], + [26273, 32357, 32711, 32768, 0], + [20672, 31242, 32441, 32768, 0], + [14172, 27254, 30902, 32768, 0], + [9870, 21898, 27275, 32768, 0], + [5164, 13506, 19270, 32768, 0], + [26725, 32459, 32728, 32768, 0], + [20991, 31442, 32527, 32768, 0], + [13071, 26434, 30811, 32768, 0], + [8184, 20090, 26742, 32768, 0], + [4803, 13255, 19895, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [7555, 14942, 18501, 32768, 0], + [24410, 31178, 32287, 32768, 0], + [14394, 26738, 30253, 32768, 0], + [8413, 19554, 25195, 32768, 0], + [4766, 12924, 18785, 32768, 0], + [2029, 5806, 9207, 32768, 0], + [26776, 32364, 32663, 32768, 0], + [18732, 29967, 31931, 32768, 0], + [11005, 23786, 28852, 32768, 0], + [6466, 16909, 23510, 32768, 0], + [3044, 8638, 13419, 32768, 0], + [29208, 32582, 32704, 32768, 0], + [20068, 30857, 32208, 32768, 0], + [12003, 25085, 29595, 32768, 0], + [6947, 17750, 24189, 32768, 0], + [3245, 9103, 14007, 32768, 0], + [27359, 32465, 32669, 32768, 0], + [19421, 30614, 32174, 32768, 0], + [11915, 25010, 29579, 32768, 0], + [6950, 17676, 24074, 32768, 0], + [3007, 8473, 13096, 32768, 0], + [29002, 32676, 32735, 32768, 0], + [22102, 31849, 32576, 32768, 0], + [14408, 28009, 31405, 32768, 0], + [9027, 21679, 27931, 32768, 0], + [4694, 12678, 18748, 32768, 0], + [28216, 32528, 32682, 32768, 0], + [20849, 31264, 32318, 32768, 0], + [12756, 25815, 29751, 32768, 0], + [7565, 18801, 24923, 32768, 0], + [3509, 9533, 14477, 32768, 0], + [30133, 32687, 32739, 32768, 0], + [23063, 31910, 32515, 32768, 0], + [14588, 28051, 31132, 32768, 0], + [9085, 21649, 27457, 32768, 0], + [4261, 11654, 17264, 32768, 0], + [29518, 32691, 32748, 32768, 0], + [22451, 31959, 32613, 32768, 0], + [14864, 28722, 31700, 32768, 0], + [9695, 22964, 28716, 32768, 0], + [4932, 13358, 19502, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [6465, 16958, 21688, 32768, 0], + [25199, 31514, 32360, 32768, 0], + [14774, 27149, 30607, 32768, 0], + [9257, 21438, 26972, 32768, 0], + [5723, 15183, 21882, 32768, 0], + [3150, 8879, 13731, 32768, 0], + [26989, 32262, 32682, 32768, 0], + [17396, 29937, 32085, 32768, 0], + [11387, 24901, 29784, 32768, 0], + [7289, 18821, 25548, 32768, 0], + [3734, 10577, 16086, 32768, 0], + [29728, 32501, 32695, 32768, 0], + [17431, 29701, 31903, 32768, 0], + [9921, 22826, 28300, 32768, 0], + [5896, 15434, 22068, 32768, 0], + [3430, 9646, 14757, 32768, 0], + [28614, 32511, 32705, 32768, 0], + [19364, 30638, 32263, 32768, 0], + [13129, 26254, 30402, 32768, 0], + [8754, 20484, 26440, 32768, 0], + [4378, 11607, 17110, 32768, 0], + [30292, 32671, 32744, 32768, 0], + [21780, 31603, 32501, 32768, 0], + [14314, 27829, 31291, 32768, 0], + [9611, 22327, 28263, 32768, 0], + [4890, 13087, 19065, 32768, 0], + [25862, 32567, 32733, 32768, 0], + [20794, 32050, 32567, 32768, 0], + [17243, 30625, 32254, 32768, 0], + [13283, 27628, 31474, 32768, 0], + [9669, 22532, 28918, 32768, 0], + [27435, 32697, 32748, 32768, 0], + [24922, 32390, 32714, 32768, 0], + [21449, 31504, 32536, 32768, 0], + [16392, 29729, 31832, 32768, 0], + [11692, 24884, 29076, 32768, 0], + [24193, 32290, 32735, 32768, 0], + [18909, 31104, 32563, 32768, 0], + [12236, 26841, 31403, 32768, 0], + [8171, 21840, 29082, 32768, 0], + [7224, 17280, 25275, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [3078, 6839, 9890, 32768, 0], + [13837, 20450, 24479, 32768, 0], + [5914, 14222, 19328, 32768, 0], + [3866, 10267, 14762, 32768, 0], + [2612, 7208, 11042, 32768, 0], + [1067, 2991, 4776, 32768, 0], + [25817, 31646, 32529, 32768, 0], + [13708, 26338, 30385, 32768, 0], + [7328, 18585, 24870, 32768, 0], + [4691, 13080, 19276, 32768, 0], + [1825, 5253, 8352, 32768, 0], + [29386, 32315, 32624, 32768, 0], + [17160, 29001, 31360, 32768, 0], + [9602, 21862, 27396, 32768, 0], + [5915, 15772, 22148, 32768, 0], + [2786, 7779, 12047, 32768, 0], + [29246, 32450, 32663, 32768, 0], + [18696, 29929, 31818, 32768, 0], + [10510, 23369, 28560, 32768, 0], + [6229, 16499, 23125, 32768, 0], + [2608, 7448, 11705, 32768, 0], + [30753, 32710, 32748, 32768, 0], + [21638, 31487, 32503, 32768, 0], + [12937, 26854, 30870, 32768, 0], + [8182, 20596, 26970, 32768, 0], + [3637, 10269, 15497, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [5244, 12150, 16906, 32768, 0], + [20486, 26858, 29701, 32768, 0], + [7756, 18317, 23735, 32768, 0], + [3452, 9256, 13146, 32768, 0], + [2020, 5206, 8229, 32768, 0], + [1801, 4993, 7903, 32768, 0], + [27051, 31858, 32531, 32768, 0], + [15988, 27531, 30619, 32768, 0], + [9188, 21484, 26719, 32768, 0], + [6273, 17186, 23800, 32768, 0], + [3108, 9355, 14764, 32768, 0], + [31076, 32520, 32680, 32768, 0], + [18119, 30037, 31850, 32768, 0], + [10244, 22969, 27472, 32768, 0], + [4692, 14077, 19273, 32768, 0], + [3694, 11677, 17556, 32768, 0], + [30060, 32581, 32720, 32768, 0], + [21011, 30775, 32120, 32768, 0], + [11931, 24820, 29289, 32768, 0], + [7119, 17662, 24356, 32768, 0], + [3833, 10706, 16304, 32768, 0], + [31954, 32731, 32748, 32768, 0], + [23913, 31724, 32489, 32768, 0], + [15520, 28060, 31286, 32768, 0], + [11517, 23008, 28571, 32768, 0], + [6193, 14508, 20629, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [1035, 2807, 4156, 32768, 0], + [13162, 18138, 20939, 32768, 0], + [2696, 6633, 8755, 32768, 0], + [1373, 4161, 6853, 32768, 0], + [1099, 2746, 4716, 32768, 0], + [340, 1021, 1599, 32768, 0], + [22826, 30419, 32135, 32768, 0], + [10395, 21762, 26942, 32768, 0], + [4726, 12407, 17361, 32768, 0], + [2447, 7080, 10593, 32768, 0], + [1227, 3717, 6011, 32768, 0], + [28156, 31424, 31934, 32768, 0], + [16915, 27754, 30373, 32768, 0], + [9148, 20990, 26431, 32768, 0], + [5950, 15515, 21148, 32768, 0], + [2492, 7327, 11526, 32768, 0], + [30602, 32477, 32670, 32768, 0], + [20026, 29955, 31568, 32768, 0], + [11220, 23628, 28105, 32768, 0], + [6652, 17019, 22973, 32768, 0], + [3064, 8536, 13043, 32768, 0], + [31769, 32724, 32748, 32768, 0], + [22230, 30887, 32373, 32768, 0], + [12234, 25079, 29731, 32768, 0], + [7326, 18816, 25353, 32768, 0], + [3933, 10907, 16616, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [8896, 16227, 20630, 32768, 0], + [23629, 31782, 32527, 32768, 0], + [15173, 27755, 31321, 32768, 0], + [10158, 21233, 27382, 32768, 0], + [6420, 14857, 21558, 32768, 0], + [3269, 8155, 12646, 32768, 0], + [24835, 32009, 32496, 32768, 0], + [16509, 28421, 31579, 32768, 0], + [10957, 21514, 27418, 32768, 0], + [7881, 15930, 22096, 32768, 0], + [5388, 10960, 15918, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [20745, 30773, 32093, 32768, 0], + [15200, 27221, 30861, 32768, 0], + [13032, 20873, 25667, 32768, 0], + [12285, 18663, 23494, 32768, 0], + [11563, 17481, 21489, 32768, 0], + [26260, 31982, 32320, 32768, 0], + [15397, 28083, 31100, 32768, 0], + [9742, 19217, 24824, 32768, 0], + [3261, 9629, 15362, 32768, 0], + [1480, 4322, 7499, 32768, 0], + [27599, 32256, 32460, 32768, 0], + [16857, 27659, 30774, 32768, 0], + [9551, 18290, 23748, 32768, 0], + [3052, 8933, 14103, 32768, 0], + [2021, 5910, 9787, 32768, 0], + [29005, 32015, 32392, 32768, 0], + [17677, 27694, 30863, 32768, 0], + [9204, 17356, 23219, 32768, 0], + [2403, 7516, 12814, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [10808, 22056, 26896, 32768, 0], + [25739, 32313, 32676, 32768, 0], + [17288, 30203, 32221, 32768, 0], + [11359, 24878, 29896, 32768, 0], + [6949, 17767, 24893, 32768, 0], + [4287, 11796, 18071, 32768, 0], + [27880, 32521, 32705, 32768, 0], + [19038, 31004, 32414, 32768, 0], + [12564, 26345, 30768, 32768, 0], + [8269, 19947, 26779, 32768, 0], + [5674, 14657, 21674, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [25742, 32319, 32671, 32768, 0], + [19557, 31164, 32454, 32768, 0], + [13381, 26381, 30755, 32768, 0], + [10101, 21466, 26722, 32768, 0], + [9209, 19650, 26825, 32768, 0], + [27107, 31917, 32432, 32768, 0], + [18056, 28893, 31203, 32768, 0], + [10200, 21434, 26764, 32768, 0], + [4660, 12913, 19502, 32768, 0], + [2368, 6930, 12504, 32768, 0], + [26960, 32158, 32613, 32768, 0], + [18628, 30005, 32031, 32768, 0], + [10233, 22442, 28232, 32768, 0], + [5471, 14630, 21516, 32768, 0], + [3235, 10767, 17109, 32768, 0], + [27696, 32440, 32692, 32768, 0], + [20032, 31167, 32438, 32768, 0], + [8700, 21341, 28442, 32768, 0], + [5662, 14831, 21795, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [9704, 17294, 21132, 32768, 0], + [26762, 32278, 32633, 32768, 0], + [18382, 29620, 31819, 32768, 0], + [10891, 23475, 28723, 32768, 0], + [6358, 16583, 23309, 32768, 0], + [3248, 9118, 14141, 32768, 0], + [27204, 32573, 32699, 32768, 0], + [19818, 30824, 32329, 32768, 0], + [11772, 25120, 30041, 32768, 0], + [6995, 18033, 25039, 32768, 0], + [3752, 10442, 16098, 32768, 0], + [27222, 32256, 32559, 32768, 0], + [15356, 28399, 31475, 32768, 0], + [8821, 20635, 27057, 32768, 0], + [5511, 14404, 21239, 32768, 0], + [2935, 8222, 13051, 32768, 0], + [24875, 32120, 32529, 32768, 0], + [15233, 28265, 31445, 32768, 0], + [8605, 20570, 26932, 32768, 0], + [5431, 14413, 21196, 32768, 0], + [2994, 8341, 13223, 32768, 0], + [28201, 32604, 32700, 32768, 0], + [21041, 31446, 32456, 32768, 0], + [13221, 26213, 30475, 32768, 0], + [8255, 19385, 26037, 32768, 0], + [4930, 12585, 18830, 32768, 0], + [28768, 32448, 32627, 32768, 0], + [19705, 30561, 32021, 32768, 0], + [11572, 23589, 28220, 32768, 0], + [5532, 15034, 21446, 32768, 0], + [2460, 7150, 11456, 32768, 0], + [29874, 32619, 32699, 32768, 0], + [21621, 31071, 32201, 32768, 0], + [12511, 24747, 28992, 32768, 0], + [6281, 16395, 22748, 32768, 0], + [3246, 9278, 14497, 32768, 0], + [29715, 32625, 32712, 32768, 0], + [20958, 31011, 32283, 32768, 0], + [11233, 23671, 28806, 32768, 0], + [6012, 16128, 22868, 32768, 0], + [3427, 9851, 15414, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [11016, 22111, 26794, 32768, 0], + [25946, 32357, 32677, 32768, 0], + [17890, 30452, 32252, 32768, 0], + [11678, 25142, 29816, 32768, 0], + [6720, 17534, 24584, 32768, 0], + [4230, 11665, 17820, 32768, 0], + [28400, 32623, 32747, 32768, 0], + [21164, 31668, 32575, 32768, 0], + [13572, 27388, 31182, 32768, 0], + [8234, 20750, 27358, 32768, 0], + [5065, 14055, 20897, 32768, 0], + [28981, 32547, 32705, 32768, 0], + [18681, 30543, 32239, 32768, 0], + [10919, 24075, 29286, 32768, 0], + [6431, 17199, 24077, 32768, 0], + [3819, 10464, 16618, 32768, 0], + [26870, 32467, 32693, 32768, 0], + [19041, 30831, 32347, 32768, 0], + [11794, 25211, 30016, 32768, 0], + [6888, 18019, 24970, 32768, 0], + [4370, 12363, 18992, 32768, 0], + [29578, 32670, 32744, 32768, 0], + [23159, 32007, 32613, 32768, 0], + [15315, 28669, 31676, 32768, 0], + [9298, 22607, 28782, 32768, 0], + [6144, 15913, 22968, 32768, 0], + [28110, 32499, 32669, 32768, 0], + [21574, 30937, 32015, 32768, 0], + [12759, 24818, 28727, 32768, 0], + [6545, 16761, 23042, 32768, 0], + [3649, 10597, 16833, 32768, 0], + [28163, 32552, 32728, 32768, 0], + [22101, 31469, 32464, 32768, 0], + [13160, 25472, 30143, 32768, 0], + [7303, 18684, 25468, 32768, 0], + [5241, 13975, 20955, 32768, 0], + [28400, 32631, 32744, 32768, 0], + [22104, 31793, 32603, 32768, 0], + [13557, 26571, 30846, 32768, 0], + [7749, 19861, 26675, 32768, 0], + [4873, 14030, 21234, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [9800, 17635, 21073, 32768, 0], + [26153, 31885, 32527, 32768, 0], + [15038, 27852, 31006, 32768, 0], + [8718, 20564, 26486, 32768, 0], + [5128, 14076, 20514, 32768, 0], + [2636, 7566, 11925, 32768, 0], + [27551, 32504, 32701, 32768, 0], + [18310, 30054, 32100, 32768, 0], + [10211, 23420, 29082, 32768, 0], + [6222, 16876, 23916, 32768, 0], + [3462, 9954, 15498, 32768, 0], + [29991, 32633, 32721, 32768, 0], + [19883, 30751, 32201, 32768, 0], + [11141, 24184, 29285, 32768, 0], + [6420, 16940, 23774, 32768, 0], + [3392, 9753, 15118, 32768, 0], + [28465, 32616, 32712, 32768, 0], + [19850, 30702, 32244, 32768, 0], + [10983, 24024, 29223, 32768, 0], + [6294, 16770, 23582, 32768, 0], + [3244, 9283, 14509, 32768, 0], + [30023, 32717, 32748, 32768, 0], + [22940, 32032, 32626, 32768, 0], + [14282, 27928, 31473, 32768, 0], + [8562, 21327, 27914, 32768, 0], + [4846, 13393, 19919, 32768, 0], + [29981, 32590, 32695, 32768, 0], + [20465, 30963, 32166, 32768, 0], + [11479, 23579, 28195, 32768, 0], + [5916, 15648, 22073, 32768, 0], + [3031, 8605, 13398, 32768, 0], + [31146, 32691, 32739, 32768, 0], + [23106, 31724, 32444, 32768, 0], + [13783, 26738, 30439, 32768, 0], + [7852, 19468, 25807, 32768, 0], + [3860, 11124, 16853, 32768, 0], + [31014, 32724, 32748, 32768, 0], + [23629, 32109, 32628, 32768, 0], + [14747, 28115, 31403, 32768, 0], + [8545, 21242, 27478, 32768, 0], + [4574, 12781, 19067, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [9185, 19694, 24688, 32768, 0], + [26081, 31985, 32621, 32768, 0], + [16015, 29000, 31787, 32768, 0], + [10542, 23690, 29206, 32768, 0], + [6732, 17945, 24677, 32768, 0], + [3916, 11039, 16722, 32768, 0], + [28224, 32566, 32744, 32768, 0], + [19100, 31138, 32485, 32768, 0], + [12528, 26620, 30879, 32768, 0], + [7741, 20277, 26885, 32768, 0], + [4566, 12845, 18990, 32768, 0], + [29933, 32593, 32718, 32768, 0], + [17670, 30333, 32155, 32768, 0], + [10385, 23600, 28909, 32768, 0], + [6243, 16236, 22407, 32768, 0], + [3976, 10389, 16017, 32768, 0], + [28377, 32561, 32738, 32768, 0], + [19366, 31175, 32482, 32768, 0], + [13327, 27175, 31094, 32768, 0], + [8258, 20769, 27143, 32768, 0], + [4703, 13198, 19527, 32768, 0], + [31086, 32706, 32748, 32768, 0], + [22853, 31902, 32583, 32768, 0], + [14759, 28186, 31419, 32768, 0], + [9284, 22382, 28348, 32768, 0], + [5585, 15192, 21868, 32768, 0], + [28291, 32652, 32746, 32768, 0], + [19849, 32107, 32571, 32768, 0], + [14834, 26818, 29214, 32768, 0], + [10306, 22594, 28672, 32768, 0], + [6615, 17384, 23384, 32768, 0], + [28947, 32604, 32745, 32768, 0], + [25625, 32289, 32646, 32768, 0], + [18758, 28672, 31403, 32768, 0], + [10017, 23430, 28523, 32768, 0], + [6862, 15269, 22131, 32768, 0], + [23933, 32509, 32739, 32768, 0], + [19927, 31495, 32631, 32768, 0], + [11903, 26023, 30621, 32768, 0], + [7026, 20094, 27252, 32768, 0], + [5998, 18106, 24437, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [4456, 11274, 15533, 32768, 0], + [21219, 29079, 31616, 32768, 0], + [11173, 23774, 28567, 32768, 0], + [7282, 18293, 24263, 32768, 0], + [4890, 13286, 19115, 32768, 0], + [1890, 5508, 8659, 32768, 0], + [26651, 32136, 32647, 32768, 0], + [14630, 28254, 31455, 32768, 0], + [8716, 21287, 27395, 32768, 0], + [5615, 15331, 22008, 32768, 0], + [2675, 7700, 12150, 32768, 0], + [29954, 32526, 32690, 32768, 0], + [16126, 28982, 31633, 32768, 0], + [9030, 21361, 27352, 32768, 0], + [5411, 14793, 21271, 32768, 0], + [2943, 8422, 13163, 32768, 0], + [29539, 32601, 32730, 32768, 0], + [18125, 30385, 32201, 32768, 0], + [10422, 24090, 29468, 32768, 0], + [6468, 17487, 24438, 32768, 0], + [2970, 8653, 13531, 32768, 0], + [30912, 32715, 32748, 32768, 0], + [20666, 31373, 32497, 32768, 0], + [12509, 26640, 30917, 32768, 0], + [8058, 20629, 27290, 32768, 0], + [4231, 12006, 18052, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [10202, 20633, 25484, 32768, 0], + [27336, 31445, 32352, 32768, 0], + [12420, 24384, 28552, 32768, 0], + [7648, 18115, 23856, 32768, 0], + [5662, 14341, 19902, 32768, 0], + [3611, 10328, 15390, 32768, 0], + [30945, 32616, 32736, 32768, 0], + [18682, 30505, 32253, 32768, 0], + [11513, 25336, 30203, 32768, 0], + [7449, 19452, 26148, 32768, 0], + [4482, 13051, 18886, 32768, 0], + [32022, 32690, 32747, 32768, 0], + [18578, 30501, 32146, 32768, 0], + [11249, 23368, 28631, 32768, 0], + [5645, 16958, 22158, 32768, 0], + [5009, 11444, 16637, 32768, 0], + [31357, 32710, 32748, 32768, 0], + [21552, 31494, 32504, 32768, 0], + [13891, 27677, 31340, 32768, 0], + [9051, 22098, 28172, 32768, 0], + [5190, 13377, 19486, 32768, 0], + [32364, 32740, 32748, 32768, 0], + [24839, 31907, 32551, 32768, 0], + [17160, 28779, 31696, 32768, 0], + [12452, 24137, 29602, 32768, 0], + [6165, 15389, 22477, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [2575, 7281, 11077, 32768, 0], + [14002, 20866, 25402, 32768, 0], + [6343, 15056, 19658, 32768, 0], + [4474, 11858, 17041, 32768, 0], + [2865, 8299, 12534, 32768, 0], + [1344, 3949, 6391, 32768, 0], + [24720, 31239, 32459, 32768, 0], + [12585, 25356, 29968, 32768, 0], + [7181, 18246, 24444, 32768, 0], + [5025, 13667, 19885, 32768, 0], + [2521, 7304, 11605, 32768, 0], + [29908, 32252, 32584, 32768, 0], + [17421, 29156, 31575, 32768, 0], + [9889, 22188, 27782, 32768, 0], + [5878, 15647, 22123, 32768, 0], + [2814, 8665, 13323, 32768, 0], + [30183, 32568, 32713, 32768, 0], + [18528, 30195, 32049, 32768, 0], + [10982, 24606, 29657, 32768, 0], + [6957, 18165, 25231, 32768, 0], + [3508, 10118, 15468, 32768, 0], + [31761, 32736, 32748, 32768, 0], + [21041, 31328, 32546, 32768, 0], + [12568, 26732, 31166, 32768, 0], + [8052, 20720, 27733, 32768, 0], + [4336, 12192, 18396, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [7062, 16472, 22319, 32768, 0], + [24538, 32261, 32674, 32768, 0], + [13675, 28041, 31779, 32768, 0], + [8590, 20674, 27631, 32768, 0], + [5685, 14675, 22013, 32768, 0], + [3655, 9898, 15731, 32768, 0], + [26493, 32418, 32658, 32768, 0], + [16376, 29342, 32090, 32768, 0], + [10594, 22649, 28970, 32768, 0], + [8176, 17170, 24303, 32768, 0], + [5605, 12694, 19139, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [23888, 31902, 32542, 32768, 0], + [18612, 29687, 31987, 32768, 0], + [16245, 24852, 29249, 32768, 0], + [15765, 22608, 27559, 32768, 0], + [19895, 24699, 27510, 32768, 0], + [28401, 32212, 32457, 32768, 0], + [15274, 27825, 30980, 32768, 0], + [9364, 18128, 24332, 32768, 0], + [2283, 8193, 15082, 32768, 0], + [1228, 3972, 7881, 32768, 0], + [29455, 32469, 32620, 32768, 0], + [17981, 28245, 31388, 32768, 0], + [10921, 20098, 26240, 32768, 0], + [3743, 11829, 18657, 32768, 0], + [2374, 9593, 15715, 32768, 0], + [31068, 32466, 32635, 32768, 0], + [20321, 29572, 31971, 32768, 0], + [10771, 20255, 27119, 32768, 0], + [2795, 10410, 17361, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [9320, 22102, 27840, 32768, 0], + [27057, 32464, 32724, 32768, 0], + [16331, 30268, 32309, 32768, 0], + [10319, 23935, 29720, 32768, 0], + [6189, 16448, 24106, 32768, 0], + [3589, 10884, 18808, 32768, 0], + [29026, 32624, 32748, 32768, 0], + [19226, 31507, 32587, 32768, 0], + [12692, 26921, 31203, 32768, 0], + [7049, 19532, 27635, 32768, 0], + [7727, 15669, 23252, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [28056, 32625, 32748, 32768, 0], + [22383, 32075, 32669, 32768, 0], + [15417, 27098, 31749, 32768, 0], + [18127, 26493, 27190, 32768, 0], + [5461, 16384, 21845, 32768, 0], + [27982, 32091, 32584, 32768, 0], + [19045, 29868, 31972, 32768, 0], + [10397, 22266, 27932, 32768, 0], + [5990, 13697, 21500, 32768, 0], + [1792, 6912, 15104, 32768, 0], + [28198, 32501, 32718, 32768, 0], + [21534, 31521, 32569, 32768, 0], + [11109, 25217, 30017, 32768, 0], + [5671, 15124, 26151, 32768, 0], + [4681, 14043, 18725, 32768, 0], + [28688, 32580, 32741, 32768, 0], + [22576, 32079, 32661, 32768, 0], + [10627, 22141, 28340, 32768, 0], + [9362, 14043, 28087, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [7754, 16948, 22142, 32768, 0], + [25670, 32330, 32691, 32768, 0], + [15663, 29225, 31994, 32768, 0], + [9878, 23288, 29158, 32768, 0], + [6419, 17088, 24336, 32768, 0], + [3859, 11003, 17039, 32768, 0], + [27562, 32595, 32725, 32768, 0], + [17575, 30588, 32399, 32768, 0], + [10819, 24838, 30309, 32768, 0], + [7124, 18686, 25916, 32768, 0], + [4479, 12688, 19340, 32768, 0], + [28385, 32476, 32673, 32768, 0], + [15306, 29005, 31938, 32768, 0], + [8937, 21615, 28322, 32768, 0], + [5982, 15603, 22786, 32768, 0], + [3620, 10267, 16136, 32768, 0], + [27280, 32464, 32667, 32768, 0], + [15607, 29160, 32004, 32768, 0], + [9091, 22135, 28740, 32768, 0], + [6232, 16632, 24020, 32768, 0], + [4047, 11377, 17672, 32768, 0], + [29220, 32630, 32718, 32768, 0], + [19650, 31220, 32462, 32768, 0], + [13050, 26312, 30827, 32768, 0], + [9228, 20870, 27468, 32768, 0], + [6146, 15149, 21971, 32768, 0], + [30169, 32481, 32623, 32768, 0], + [17212, 29311, 31554, 32768, 0], + [9911, 21311, 26882, 32768, 0], + [4487, 13314, 20372, 32768, 0], + [2570, 7772, 12889, 32768, 0], + [30924, 32613, 32708, 32768, 0], + [19490, 30206, 32107, 32768, 0], + [11232, 23998, 29276, 32768, 0], + [6769, 17955, 25035, 32768, 0], + [4398, 12623, 19214, 32768, 0], + [30609, 32627, 32722, 32768, 0], + [19370, 30582, 32287, 32768, 0], + [10457, 23619, 29409, 32768, 0], + [6443, 17637, 24834, 32768, 0], + [4645, 13236, 20106, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8626, 20271, 26216, 32768, 0], + [26707, 32406, 32711, 32768, 0], + [16999, 30329, 32286, 32768, 0], + [11445, 25123, 30286, 32768, 0], + [6411, 18828, 25601, 32768, 0], + [6801, 12458, 20248, 32768, 0], + [29918, 32682, 32748, 32768, 0], + [20649, 31739, 32618, 32768, 0], + [12879, 27773, 31581, 32768, 0], + [7896, 21751, 28244, 32768, 0], + [5260, 14870, 23698, 32768, 0], + [29252, 32593, 32731, 32768, 0], + [17072, 30460, 32294, 32768, 0], + [10653, 24143, 29365, 32768, 0], + [6536, 17490, 23983, 32768, 0], + [4929, 13170, 20085, 32768, 0], + [28137, 32518, 32715, 32768, 0], + [18171, 30784, 32407, 32768, 0], + [11437, 25436, 30459, 32768, 0], + [7252, 18534, 26176, 32768, 0], + [4126, 13353, 20978, 32768, 0], + [31162, 32726, 32748, 32768, 0], + [23017, 32222, 32701, 32768, 0], + [15629, 29233, 32046, 32768, 0], + [9387, 22621, 29480, 32768, 0], + [6922, 17616, 25010, 32768, 0], + [28838, 32265, 32614, 32768, 0], + [19701, 30206, 31920, 32768, 0], + [11214, 22410, 27933, 32768, 0], + [5320, 14177, 23034, 32768, 0], + [5049, 12881, 17827, 32768, 0], + [27484, 32471, 32734, 32768, 0], + [21076, 31526, 32561, 32768, 0], + [12707, 26303, 31211, 32768, 0], + [8169, 21722, 28219, 32768, 0], + [6045, 19406, 27042, 32768, 0], + [27753, 32572, 32745, 32768, 0], + [20832, 31878, 32653, 32768, 0], + [13250, 27356, 31674, 32768, 0], + [7718, 21508, 29858, 32768, 0], + [7209, 18350, 25559, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [7876, 16901, 21741, 32768, 0], + [24001, 31898, 32625, 32768, 0], + [14529, 27959, 31451, 32768, 0], + [8273, 20818, 27258, 32768, 0], + [5278, 14673, 21510, 32768, 0], + [2983, 8843, 14039, 32768, 0], + [28016, 32574, 32732, 32768, 0], + [17471, 30306, 32301, 32768, 0], + [10224, 24063, 29728, 32768, 0], + [6602, 17954, 25052, 32768, 0], + [4002, 11585, 17759, 32768, 0], + [30190, 32634, 32739, 32768, 0], + [17497, 30282, 32270, 32768, 0], + [10229, 23729, 29538, 32768, 0], + [6344, 17211, 24440, 32768, 0], + [3849, 11189, 17108, 32768, 0], + [28570, 32583, 32726, 32768, 0], + [17521, 30161, 32238, 32768, 0], + [10153, 23565, 29378, 32768, 0], + [6455, 17341, 24443, 32768, 0], + [3907, 11042, 17024, 32768, 0], + [30689, 32715, 32748, 32768, 0], + [21546, 31840, 32610, 32768, 0], + [13547, 27581, 31459, 32768, 0], + [8912, 21757, 28309, 32768, 0], + [5548, 15080, 22046, 32768, 0], + [30783, 32540, 32685, 32768, 0], + [17540, 29528, 31668, 32768, 0], + [10160, 21468, 26783, 32768, 0], + [4724, 13393, 20054, 32768, 0], + [2702, 8174, 13102, 32768, 0], + [31648, 32686, 32742, 32768, 0], + [20954, 31094, 32337, 32768, 0], + [12420, 25698, 30179, 32768, 0], + [7304, 19320, 26248, 32768, 0], + [4366, 12261, 18864, 32768, 0], + [31581, 32723, 32748, 32768, 0], + [21373, 31586, 32525, 32768, 0], + [12744, 26625, 30885, 32768, 0], + [7431, 20322, 26950, 32768, 0], + [4692, 13323, 20111, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [7833, 18369, 24095, 32768, 0], + [26650, 32273, 32702, 32768, 0], + [16371, 29961, 32191, 32768, 0], + [11055, 24082, 29629, 32768, 0], + [6892, 18644, 25400, 32768, 0], + [5006, 13057, 19240, 32768, 0], + [29834, 32666, 32748, 32768, 0], + [19577, 31335, 32570, 32768, 0], + [12253, 26509, 31122, 32768, 0], + [7991, 20772, 27711, 32768, 0], + [5677, 15910, 23059, 32768, 0], + [30109, 32532, 32720, 32768, 0], + [16747, 30166, 32252, 32768, 0], + [10134, 23542, 29184, 32768, 0], + [5791, 16176, 23556, 32768, 0], + [4362, 10414, 17284, 32768, 0], + [29492, 32626, 32748, 32768, 0], + [19894, 31402, 32525, 32768, 0], + [12942, 27071, 30869, 32768, 0], + [8346, 21216, 27405, 32768, 0], + [6572, 17087, 23859, 32768, 0], + [32035, 32735, 32748, 32768, 0], + [22957, 31838, 32618, 32768, 0], + [14724, 28572, 31772, 32768, 0], + [10364, 23999, 29553, 32768, 0], + [7004, 18433, 25655, 32768, 0], + [27528, 32277, 32681, 32768, 0], + [16959, 31171, 32096, 32768, 0], + [10486, 23593, 27962, 32768, 0], + [8192, 16384, 23211, 32768, 0], + [8937, 17873, 20852, 32768, 0], + [27715, 32002, 32615, 32768, 0], + [15073, 29491, 31676, 32768, 0], + [11264, 24576, 28672, 32768, 0], + [2341, 18725, 23406, 32768, 0], + [7282, 18204, 25486, 32768, 0], + [28547, 32213, 32657, 32768, 0], + [20788, 29773, 32239, 32768, 0], + [6780, 21469, 30508, 32768, 0], + [5958, 14895, 23831, 32768, 0], + [16384, 21845, 27307, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [5992, 14304, 19765, 32768, 0], + [22612, 31238, 32456, 32768, 0], + [13456, 27162, 31087, 32768, 0], + [8001, 20062, 26504, 32768, 0], + [5168, 14105, 20764, 32768, 0], + [2632, 7771, 12385, 32768, 0], + [27034, 32344, 32709, 32768, 0], + [15850, 29415, 31997, 32768, 0], + [9494, 22776, 28841, 32768, 0], + [6151, 16830, 23969, 32768, 0], + [3461, 10039, 15722, 32768, 0], + [30134, 32569, 32731, 32768, 0], + [15638, 29422, 31945, 32768, 0], + [9150, 21865, 28218, 32768, 0], + [5647, 15719, 22676, 32768, 0], + [3402, 9772, 15477, 32768, 0], + [28530, 32586, 32735, 32768, 0], + [17139, 30298, 32292, 32768, 0], + [10200, 24039, 29685, 32768, 0], + [6419, 17674, 24786, 32768, 0], + [3544, 10225, 15824, 32768, 0], + [31333, 32726, 32748, 32768, 0], + [20618, 31487, 32544, 32768, 0], + [12901, 27217, 31232, 32768, 0], + [8624, 21734, 28171, 32768, 0], + [5104, 14191, 20748, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [11206, 21090, 26561, 32768, 0], + [28759, 32279, 32671, 32768, 0], + [14171, 27952, 31569, 32768, 0], + [9743, 22907, 29141, 32768, 0], + [6871, 17886, 24868, 32768, 0], + [4960, 13152, 19315, 32768, 0], + [31077, 32661, 32748, 32768, 0], + [19400, 31195, 32515, 32768, 0], + [12752, 26858, 31040, 32768, 0], + [8370, 22098, 28591, 32768, 0], + [5457, 15373, 22298, 32768, 0], + [31697, 32706, 32748, 32768, 0], + [17860, 30657, 32333, 32768, 0], + [12510, 24812, 29261, 32768, 0], + [6180, 19124, 24722, 32768, 0], + [5041, 13548, 17959, 32768, 0], + [31552, 32716, 32748, 32768, 0], + [21908, 31769, 32623, 32768, 0], + [14470, 28201, 31565, 32768, 0], + [9493, 22982, 28608, 32768, 0], + [6858, 17240, 24137, 32768, 0], + [32543, 32752, 32756, 32768, 0], + [24286, 32097, 32666, 32768, 0], + [15958, 29217, 32024, 32768, 0], + [10207, 24234, 29958, 32768, 0], + [6929, 18305, 25652, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [4137, 10847, 15682, 32768, 0], + [17824, 27001, 30058, 32768, 0], + [10204, 22796, 28291, 32768, 0], + [6076, 15935, 22125, 32768, 0], + [3852, 10937, 16816, 32768, 0], + [2252, 6324, 10131, 32768, 0], + [25840, 32016, 32662, 32768, 0], + [15109, 28268, 31531, 32768, 0], + [9385, 22231, 28340, 32768, 0], + [6082, 16672, 23479, 32768, 0], + [3318, 9427, 14681, 32768, 0], + [30594, 32574, 32718, 32768, 0], + [16836, 29552, 31859, 32768, 0], + [9556, 22542, 28356, 32768, 0], + [6305, 16725, 23540, 32768, 0], + [3376, 9895, 15184, 32768, 0], + [29383, 32617, 32745, 32768, 0], + [18891, 30809, 32401, 32768, 0], + [11688, 25942, 30687, 32768, 0], + [7468, 19469, 26651, 32768, 0], + [3909, 11358, 17012, 32768, 0], + [31564, 32736, 32748, 32768, 0], + [20906, 31611, 32600, 32768, 0], + [13191, 27621, 31537, 32768, 0], + [8768, 22029, 28676, 32768, 0], + [5079, 14109, 20906, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], +]; + +pub static DEFAULT_COEFF_BASE_EOB_CDF: [[[[[u16; 4]; 4]; 2]; 5]; 4] = [ + [ + [ + [[17837, 29055, 32768, 0], [29600, 31446, 32768, 0], [30844, 31878, 32768, 0], [24926, 28948, 32768, 0]], + [[21365, 30026, 32768, 0], [30512, 32423, 32768, 0], [31658, 32621, 32768, 0], [29630, 31881, 32768, 0]], + ], + [ + [[5717, 26477, 32768, 0], [30491, 31703, 32768, 0], [31550, 32158, 32768, 0], [29648, 31491, 32768, 0]], + [[12608, 27820, 32768, 0], [30680, 32225, 32768, 0], [30809, 32335, 32768, 0], [31299, 32423, 32768, 0]], + ], + [ + [[1786, 12612, 32768, 0], [30663, 31625, 32768, 0], [32339, 32468, 32768, 0], [31148, 31833, 32768, 0]], + [[18857, 23865, 32768, 0], [31428, 32428, 32768, 0], [31744, 32373, 32768, 0], [31775, 32526, 32768, 0]], + ], + [ + [[1787, 2532, 32768, 0], [30832, 31662, 32768, 0], [31824, 32682, 32768, 0], [32133, 32569, 32768, 0]], + [[13751, 22235, 32768, 0], [32089, 32409, 32768, 0], [27084, 27920, 32768, 0], [29291, 32594, 32768, 0]], + ], + [ + [[1725, 3449, 32768, 0], [31102, 31935, 32768, 0], [32457, 32613, 32768, 0], [32412, 32649, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], + [ + [ + [[17560, 29888, 32768, 0], [29671, 31549, 32768, 0], [31007, 32056, 32768, 0], [27286, 30006, 32768, 0]], + [[26594, 31212, 32768, 0], [31208, 32582, 32768, 0], [31835, 32637, 32768, 0], [30595, 32206, 32768, 0]], + ], + [ + [[15239, 29932, 32768, 0], [31315, 32095, 32768, 0], [32130, 32434, 32768, 0], [30864, 31996, 32768, 0]], + [[26279, 30968, 32768, 0], [31142, 32495, 32768, 0], [31713, 32540, 32768, 0], [31929, 32594, 32768, 0]], + ], + [ + [[2644, 25198, 32768, 0], [32038, 32451, 32768, 0], [32639, 32695, 32768, 0], [32166, 32518, 32768, 0]], + [[17187, 27668, 32768, 0], [31714, 32550, 32768, 0], [32283, 32678, 32768, 0], [31930, 32563, 32768, 0]], + ], + [ + [[1044, 2257, 32768, 0], [30755, 31923, 32768, 0], [32208, 32693, 32768, 0], [32244, 32615, 32768, 0]], + [[21317, 26207, 32768, 0], [29133, 30868, 32768, 0], [29311, 31231, 32768, 0], [29657, 31087, 32768, 0]], + ], + [ + [[478, 1834, 32768, 0], [31005, 31987, 32768, 0], [32317, 32724, 32768, 0], [30865, 32648, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], + [ + [ + [[20092, 30774, 32768, 0], [30695, 32020, 32768, 0], [31131, 32103, 32768, 0], [28666, 30870, 32768, 0]], + [[27258, 31095, 32768, 0], [31804, 32623, 32768, 0], [31763, 32528, 32768, 0], [31438, 32506, 32768, 0]], + ], + [ + [[18049, 30489, 32768, 0], [31706, 32286, 32768, 0], [32163, 32473, 32768, 0], [31550, 32184, 32768, 0]], + [[27116, 30842, 32768, 0], [31971, 32598, 32768, 0], [32088, 32576, 32768, 0], [32067, 32664, 32768, 0]], + ], + [ + [[12854, 29093, 32768, 0], [32272, 32558, 32768, 0], [32667, 32729, 32768, 0], [32306, 32585, 32768, 0]], + [[25476, 30366, 32768, 0], [32169, 32687, 32768, 0], [32479, 32689, 32768, 0], [31673, 32634, 32768, 0]], + ], + [ + [[2809, 19301, 32768, 0], [32205, 32622, 32768, 0], [32338, 32730, 32768, 0], [31786, 32616, 32768, 0]], + [[22737, 29105, 32768, 0], [30810, 32362, 32768, 0], [30014, 32627, 32768, 0], [30528, 32574, 32768, 0]], + ], + [ + [[935, 3382, 32768, 0], [30789, 31909, 32768, 0], [32466, 32756, 32768, 0], [30860, 32513, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], + [ + [ + [[22497, 31198, 32768, 0], [31715, 32495, 32768, 0], [31606, 32337, 32768, 0], [30388, 31990, 32768, 0]], + [[27877, 31584, 32768, 0], [32170, 32728, 32768, 0], [32155, 32688, 32768, 0], [32219, 32702, 32768, 0]], + ], + [ + [[21457, 31043, 32768, 0], [31951, 32483, 32768, 0], [32153, 32562, 32768, 0], [31473, 32215, 32768, 0]], + [[27558, 31151, 32768, 0], [32020, 32640, 32768, 0], [32097, 32575, 32768, 0], [32242, 32719, 32768, 0]], + ], + [ + [[19980, 30591, 32768, 0], [32219, 32597, 32768, 0], [32581, 32706, 32768, 0], [31803, 32287, 32768, 0]], + [[26473, 30507, 32768, 0], [32431, 32723, 32768, 0], [32196, 32611, 32768, 0], [31588, 32528, 32768, 0]], + ], + [ + [[24647, 30463, 32768, 0], [32412, 32695, 32768, 0], [32468, 32720, 32768, 0], [31269, 32523, 32768, 0]], + [[28482, 31505, 32768, 0], [32152, 32701, 32768, 0], [31732, 32598, 32768, 0], [31767, 32712, 32768, 0]], + ], + [ + [[12358, 24977, 32768, 0], [31331, 32385, 32768, 0], [32634, 32756, 32768, 0], [30411, 32548, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], +]; + +pub static DEFAULT_COEFF_BR_CDF: [[[[[u16; 5]; 21]; 2]; 5]; 4] = [ + [ + [ + [ + [14298, 20718, 24174, 32768, 0], + [12536, 19601, 23789, 32768, 0], + [8712, 15051, 19503, 32768, 0], + [6170, 11327, 15434, 32768, 0], + [4742, 8926, 12538, 32768, 0], + [3803, 7317, 10546, 32768, 0], + [1696, 3317, 4871, 32768, 0], + [14392, 19951, 22756, 32768, 0], + [15978, 23218, 26818, 32768, 0], + [12187, 19474, 23889, 32768, 0], + [9176, 15640, 20259, 32768, 0], + [7068, 12655, 17028, 32768, 0], + [5656, 10442, 14472, 32768, 0], + [2580, 4992, 7244, 32768, 0], + [12136, 18049, 21426, 32768, 0], + [13784, 20721, 24481, 32768, 0], + [10836, 17621, 21900, 32768, 0], + [8372, 14444, 18847, 32768, 0], + [6523, 11779, 16000, 32768, 0], + [5337, 9898, 13760, 32768, 0], + [3034, 5860, 8462, 32768, 0], + ], + [ + [15967, 22905, 26286, 32768, 0], + [13534, 20654, 24579, 32768, 0], + [9504, 16092, 20535, 32768, 0], + [6975, 12568, 16903, 32768, 0], + [5364, 10091, 14020, 32768, 0], + [4357, 8370, 11857, 32768, 0], + [2506, 4934, 7218, 32768, 0], + [23032, 28815, 30936, 32768, 0], + [19540, 26704, 29719, 32768, 0], + [15158, 22969, 27097, 32768, 0], + [11408, 18865, 23650, 32768, 0], + [8885, 15448, 20250, 32768, 0], + [7108, 12853, 17416, 32768, 0], + [4231, 8041, 11480, 32768, 0], + [19823, 26490, 29156, 32768, 0], + [18890, 25929, 28932, 32768, 0], + [15660, 23491, 27433, 32768, 0], + [12147, 19776, 24488, 32768, 0], + [9728, 16774, 21649, 32768, 0], + [7919, 14277, 19066, 32768, 0], + [5440, 10170, 14185, 32768, 0], + ], + ], + [ + [ + [14406, 20862, 24414, 32768, 0], + [11824, 18907, 23109, 32768, 0], + [8257, 14393, 18803, 32768, 0], + [5860, 10747, 14778, 32768, 0], + [4475, 8486, 11984, 32768, 0], + [3606, 6954, 10043, 32768, 0], + [1736, 3410, 5048, 32768, 0], + [14430, 20046, 22882, 32768, 0], + [15593, 22899, 26709, 32768, 0], + [12102, 19368, 23811, 32768, 0], + [9059, 15584, 20262, 32768, 0], + [6999, 12603, 17048, 32768, 0], + [5684, 10497, 14553, 32768, 0], + [2822, 5438, 7862, 32768, 0], + [15785, 21585, 24359, 32768, 0], + [18347, 25229, 28266, 32768, 0], + [14974, 22487, 26389, 32768, 0], + [11423, 18681, 23271, 32768, 0], + [8863, 15350, 20008, 32768, 0], + [7153, 12852, 17278, 32768, 0], + [3707, 7036, 9982, 32768, 0], + ], + [ + [15460, 21696, 25469, 32768, 0], + [12170, 19249, 23191, 32768, 0], + [8723, 15027, 19332, 32768, 0], + [6428, 11704, 15874, 32768, 0], + [4922, 9292, 13052, 32768, 0], + [4139, 7695, 11010, 32768, 0], + [2291, 4508, 6598, 32768, 0], + [19856, 26920, 29828, 32768, 0], + [17923, 25289, 28792, 32768, 0], + [14278, 21968, 26297, 32768, 0], + [10910, 18136, 22950, 32768, 0], + [8423, 14815, 19627, 32768, 0], + [6771, 12283, 16774, 32768, 0], + [4074, 7750, 11081, 32768, 0], + [19852, 26074, 28672, 32768, 0], + [19371, 26110, 28989, 32768, 0], + [16265, 23873, 27663, 32768, 0], + [12758, 20378, 24952, 32768, 0], + [10095, 17098, 21961, 32768, 0], + [8250, 14628, 19451, 32768, 0], + [5205, 9745, 13622, 32768, 0], + ], + ], + [ + [ + [10563, 16233, 19763, 32768, 0], + [9794, 16022, 19804, 32768, 0], + [6750, 11945, 15759, 32768, 0], + [4963, 9186, 12752, 32768, 0], + [3845, 7435, 10627, 32768, 0], + [3051, 6085, 8834, 32768, 0], + [1311, 2596, 3830, 32768, 0], + [11246, 16404, 19689, 32768, 0], + [12315, 18911, 22731, 32768, 0], + [10557, 17095, 21289, 32768, 0], + [8136, 14006, 18249, 32768, 0], + [6348, 11474, 15565, 32768, 0], + [5196, 9655, 13400, 32768, 0], + [2349, 4526, 6587, 32768, 0], + [13337, 18730, 21569, 32768, 0], + [19306, 26071, 28882, 32768, 0], + [15952, 23540, 27254, 32768, 0], + [12409, 19934, 24430, 32768, 0], + [9760, 16706, 21389, 32768, 0], + [8004, 14220, 18818, 32768, 0], + [4138, 7794, 10961, 32768, 0], + ], + [ + [10870, 16684, 20949, 32768, 0], + [9664, 15230, 18680, 32768, 0], + [6886, 12109, 15408, 32768, 0], + [4825, 8900, 12305, 32768, 0], + [3630, 7162, 10314, 32768, 0], + [3036, 6429, 9387, 32768, 0], + [1671, 3296, 4940, 32768, 0], + [13819, 19159, 23026, 32768, 0], + [11984, 19108, 23120, 32768, 0], + [10690, 17210, 21663, 32768, 0], + [7984, 14154, 18333, 32768, 0], + [6868, 12294, 16124, 32768, 0], + [5274, 8994, 12868, 32768, 0], + [2988, 5771, 8424, 32768, 0], + [19736, 26647, 29141, 32768, 0], + [18933, 26070, 28984, 32768, 0], + [15779, 23048, 27200, 32768, 0], + [12638, 20061, 24532, 32768, 0], + [10692, 17545, 22220, 32768, 0], + [9217, 15251, 20054, 32768, 0], + [5078, 9284, 12594, 32768, 0], + ], + ], + [ + [ + [2331, 3662, 5244, 32768, 0], + [2891, 4771, 6145, 32768, 0], + [4598, 7623, 9729, 32768, 0], + [3520, 6845, 9199, 32768, 0], + [3417, 6119, 9324, 32768, 0], + [2601, 5412, 7385, 32768, 0], + [600, 1173, 1744, 32768, 0], + [7672, 13286, 17469, 32768, 0], + [4232, 7792, 10793, 32768, 0], + [2915, 5317, 7397, 32768, 0], + [2318, 4356, 6152, 32768, 0], + [2127, 4000, 5554, 32768, 0], + [1850, 3478, 5275, 32768, 0], + [977, 1933, 2843, 32768, 0], + [18280, 24387, 27989, 32768, 0], + [15852, 22671, 26185, 32768, 0], + [13845, 20951, 24789, 32768, 0], + [11055, 17966, 22129, 32768, 0], + [9138, 15422, 19801, 32768, 0], + [7454, 13145, 17456, 32768, 0], + [3370, 6393, 9013, 32768, 0], + ], + [ + [5842, 9229, 10838, 32768, 0], + [2313, 3491, 4276, 32768, 0], + [2998, 6104, 7496, 32768, 0], + [2420, 7447, 9868, 32768, 0], + [3034, 8495, 10923, 32768, 0], + [4076, 8937, 10975, 32768, 0], + [1086, 2370, 3299, 32768, 0], + [9714, 17254, 20444, 32768, 0], + [8543, 13698, 17123, 32768, 0], + [4918, 9007, 11910, 32768, 0], + [4129, 7532, 10553, 32768, 0], + [2364, 5533, 8058, 32768, 0], + [1834, 3546, 5563, 32768, 0], + [1473, 2908, 4133, 32768, 0], + [15405, 21193, 25619, 32768, 0], + [15691, 21952, 26561, 32768, 0], + [12962, 19194, 24165, 32768, 0], + [10272, 17855, 22129, 32768, 0], + [8588, 15270, 20718, 32768, 0], + [8682, 14669, 19500, 32768, 0], + [4870, 9636, 13205, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [14995, 21341, 24749, 32768, 0], + [13158, 20289, 24601, 32768, 0], + [8941, 15326, 19876, 32768, 0], + [6297, 11541, 15807, 32768, 0], + [4817, 9029, 12776, 32768, 0], + [3731, 7273, 10627, 32768, 0], + [1847, 3617, 5354, 32768, 0], + [14472, 19659, 22343, 32768, 0], + [16806, 24162, 27533, 32768, 0], + [12900, 20404, 24713, 32768, 0], + [9411, 16112, 20797, 32768, 0], + [7056, 12697, 17148, 32768, 0], + [5544, 10339, 14460, 32768, 0], + [2954, 5704, 8319, 32768, 0], + [12464, 18071, 21354, 32768, 0], + [15482, 22528, 26034, 32768, 0], + [12070, 19269, 23624, 32768, 0], + [8953, 15406, 20106, 32768, 0], + [7027, 12730, 17220, 32768, 0], + [5887, 10913, 15140, 32768, 0], + [3793, 7278, 10447, 32768, 0], + ], + [ + [15571, 22232, 25749, 32768, 0], + [14506, 21575, 25374, 32768, 0], + [10189, 17089, 21569, 32768, 0], + [7316, 13301, 17915, 32768, 0], + [5783, 10912, 15190, 32768, 0], + [4760, 9155, 13088, 32768, 0], + [2993, 5966, 8774, 32768, 0], + [23424, 28903, 30778, 32768, 0], + [20775, 27666, 30290, 32768, 0], + [16474, 24410, 28299, 32768, 0], + [12471, 20180, 24987, 32768, 0], + [9410, 16487, 21439, 32768, 0], + [7536, 13614, 18529, 32768, 0], + [5048, 9586, 13549, 32768, 0], + [21090, 27290, 29756, 32768, 0], + [20796, 27402, 30026, 32768, 0], + [17819, 25485, 28969, 32768, 0], + [13860, 21909, 26462, 32768, 0], + [11002, 18494, 23529, 32768, 0], + [8953, 15929, 20897, 32768, 0], + [6448, 11918, 16454, 32768, 0], + ], + ], + [ + [ + [15999, 22208, 25449, 32768, 0], + [13050, 19988, 24122, 32768, 0], + [8594, 14864, 19378, 32768, 0], + [6033, 11079, 15238, 32768, 0], + [4554, 8683, 12347, 32768, 0], + [3672, 7139, 10337, 32768, 0], + [1900, 3771, 5576, 32768, 0], + [15788, 21340, 23949, 32768, 0], + [16825, 24235, 27758, 32768, 0], + [12873, 20402, 24810, 32768, 0], + [9590, 16363, 21094, 32768, 0], + [7352, 13209, 17733, 32768, 0], + [5960, 10989, 15184, 32768, 0], + [3232, 6234, 9007, 32768, 0], + [15761, 20716, 23224, 32768, 0], + [19318, 25989, 28759, 32768, 0], + [15529, 23094, 26929, 32768, 0], + [11662, 18989, 23641, 32768, 0], + [8955, 15568, 20366, 32768, 0], + [7281, 13106, 17708, 32768, 0], + [4248, 8059, 11440, 32768, 0], + ], + [ + [14899, 21217, 24503, 32768, 0], + [13519, 20283, 24047, 32768, 0], + [9429, 15966, 20365, 32768, 0], + [6700, 12355, 16652, 32768, 0], + [5088, 9704, 13716, 32768, 0], + [4243, 8154, 11731, 32768, 0], + [2702, 5364, 7861, 32768, 0], + [22745, 28388, 30454, 32768, 0], + [20235, 27146, 29922, 32768, 0], + [15896, 23715, 27637, 32768, 0], + [11840, 19350, 24131, 32768, 0], + [9122, 15932, 20880, 32768, 0], + [7488, 13581, 18362, 32768, 0], + [5114, 9568, 13370, 32768, 0], + [20845, 26553, 28932, 32768, 0], + [20981, 27372, 29884, 32768, 0], + [17781, 25335, 28785, 32768, 0], + [13760, 21708, 26297, 32768, 0], + [10975, 18415, 23365, 32768, 0], + [9045, 15789, 20686, 32768, 0], + [6130, 11199, 15423, 32768, 0], + ], + ], + [ + [ + [13549, 19724, 23158, 32768, 0], + [11844, 18382, 22246, 32768, 0], + [7919, 13619, 17773, 32768, 0], + [5486, 10143, 13946, 32768, 0], + [4166, 7983, 11324, 32768, 0], + [3364, 6506, 9427, 32768, 0], + [1598, 3160, 4674, 32768, 0], + [15281, 20979, 23781, 32768, 0], + [14939, 22119, 25952, 32768, 0], + [11363, 18407, 22812, 32768, 0], + [8609, 14857, 19370, 32768, 0], + [6737, 12184, 16480, 32768, 0], + [5506, 10263, 14262, 32768, 0], + [2990, 5786, 8380, 32768, 0], + [20249, 25253, 27417, 32768, 0], + [21070, 27518, 30001, 32768, 0], + [16854, 24469, 28074, 32768, 0], + [12864, 20486, 25000, 32768, 0], + [9962, 16978, 21778, 32768, 0], + [8074, 14338, 19048, 32768, 0], + [4494, 8479, 11906, 32768, 0], + ], + [ + [13960, 19617, 22829, 32768, 0], + [11150, 17341, 21228, 32768, 0], + [7150, 12964, 17190, 32768, 0], + [5331, 10002, 13867, 32768, 0], + [4167, 7744, 11057, 32768, 0], + [3480, 6629, 9646, 32768, 0], + [1883, 3784, 5686, 32768, 0], + [18752, 25660, 28912, 32768, 0], + [16968, 24586, 28030, 32768, 0], + [13520, 21055, 25313, 32768, 0], + [10453, 17626, 22280, 32768, 0], + [8386, 14505, 19116, 32768, 0], + [6742, 12595, 17008, 32768, 0], + [4273, 8140, 11499, 32768, 0], + [22120, 27827, 30233, 32768, 0], + [20563, 27358, 29895, 32768, 0], + [17076, 24644, 28153, 32768, 0], + [13362, 20942, 25309, 32768, 0], + [10794, 17965, 22695, 32768, 0], + [9014, 15652, 20319, 32768, 0], + [5708, 10512, 14497, 32768, 0], + ], + ], + [ + [ + [5705, 10930, 15725, 32768, 0], + [7946, 12765, 16115, 32768, 0], + [6801, 12123, 16226, 32768, 0], + [5462, 10135, 14200, 32768, 0], + [4189, 8011, 11507, 32768, 0], + [3191, 6229, 9408, 32768, 0], + [1057, 2137, 3212, 32768, 0], + [10018, 17067, 21491, 32768, 0], + [7380, 12582, 16453, 32768, 0], + [6068, 10845, 14339, 32768, 0], + [5098, 9198, 12555, 32768, 0], + [4312, 8010, 11119, 32768, 0], + [3700, 6966, 9781, 32768, 0], + [1693, 3326, 4887, 32768, 0], + [18757, 24930, 27774, 32768, 0], + [17648, 24596, 27817, 32768, 0], + [14707, 22052, 26026, 32768, 0], + [11720, 18852, 23292, 32768, 0], + [9357, 15952, 20525, 32768, 0], + [7810, 13753, 18210, 32768, 0], + [3879, 7333, 10328, 32768, 0], + ], + [ + [8278, 13242, 15922, 32768, 0], + [10547, 15867, 18919, 32768, 0], + [9106, 15842, 20609, 32768, 0], + [6833, 13007, 17218, 32768, 0], + [4811, 9712, 13923, 32768, 0], + [3985, 7352, 11128, 32768, 0], + [1688, 3458, 5262, 32768, 0], + [12951, 21861, 26510, 32768, 0], + [9788, 16044, 20276, 32768, 0], + [6309, 11244, 14870, 32768, 0], + [5183, 9349, 12566, 32768, 0], + [4389, 8229, 11492, 32768, 0], + [3633, 6945, 10620, 32768, 0], + [3600, 6847, 9907, 32768, 0], + [21748, 28137, 30255, 32768, 0], + [19436, 26581, 29560, 32768, 0], + [16359, 24201, 27953, 32768, 0], + [13961, 21693, 25871, 32768, 0], + [11544, 18686, 23322, 32768, 0], + [9372, 16462, 20952, 32768, 0], + [6138, 11210, 15390, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [16138, 22223, 25509, 32768, 0], + [15347, 22430, 26332, 32768, 0], + [9614, 16736, 21332, 32768, 0], + [6600, 12275, 16907, 32768, 0], + [4811, 9424, 13547, 32768, 0], + [3748, 7809, 11420, 32768, 0], + [2254, 4587, 6890, 32768, 0], + [15196, 20284, 23177, 32768, 0], + [18317, 25469, 28451, 32768, 0], + [13918, 21651, 25842, 32768, 0], + [10052, 17150, 21995, 32768, 0], + [7499, 13630, 18587, 32768, 0], + [6158, 11417, 16003, 32768, 0], + [4014, 7785, 11252, 32768, 0], + [15048, 21067, 24384, 32768, 0], + [18202, 25346, 28553, 32768, 0], + [14302, 22019, 26356, 32768, 0], + [10839, 18139, 23166, 32768, 0], + [8715, 15744, 20806, 32768, 0], + [7536, 13576, 18544, 32768, 0], + [5413, 10335, 14498, 32768, 0], + ], + [ + [17394, 24501, 27895, 32768, 0], + [15889, 23420, 27185, 32768, 0], + [11561, 19133, 23870, 32768, 0], + [8285, 14812, 19844, 32768, 0], + [6496, 12043, 16550, 32768, 0], + [4771, 9574, 13677, 32768, 0], + [3603, 6830, 10144, 32768, 0], + [21656, 27704, 30200, 32768, 0], + [21324, 27915, 30511, 32768, 0], + [17327, 25336, 28997, 32768, 0], + [13417, 21381, 26033, 32768, 0], + [10132, 17425, 22338, 32768, 0], + [8580, 15016, 19633, 32768, 0], + [5694, 11477, 16411, 32768, 0], + [24116, 29780, 31450, 32768, 0], + [23853, 29695, 31591, 32768, 0], + [20085, 27614, 30428, 32768, 0], + [15326, 24335, 28575, 32768, 0], + [11814, 19472, 24810, 32768, 0], + [10221, 18611, 24767, 32768, 0], + [7689, 14558, 20321, 32768, 0], + ], + ], + [ + [ + [16214, 22380, 25770, 32768, 0], + [14213, 21304, 25295, 32768, 0], + [9213, 15823, 20455, 32768, 0], + [6395, 11758, 16139, 32768, 0], + [4779, 9187, 13066, 32768, 0], + [3821, 7501, 10953, 32768, 0], + [2293, 4567, 6795, 32768, 0], + [15859, 21283, 23820, 32768, 0], + [18404, 25602, 28726, 32768, 0], + [14325, 21980, 26206, 32768, 0], + [10669, 17937, 22720, 32768, 0], + [8297, 14642, 19447, 32768, 0], + [6746, 12389, 16893, 32768, 0], + [4324, 8251, 11770, 32768, 0], + [16532, 21631, 24475, 32768, 0], + [20667, 27150, 29668, 32768, 0], + [16728, 24510, 28175, 32768, 0], + [12861, 20645, 25332, 32768, 0], + [10076, 17361, 22417, 32768, 0], + [8395, 14940, 19963, 32768, 0], + [5731, 10683, 14912, 32768, 0], + ], + [ + [14433, 21155, 24938, 32768, 0], + [14658, 21716, 25545, 32768, 0], + [9923, 16824, 21557, 32768, 0], + [6982, 13052, 17721, 32768, 0], + [5419, 10503, 15050, 32768, 0], + [4852, 9162, 13014, 32768, 0], + [3271, 6395, 9630, 32768, 0], + [22210, 27833, 30109, 32768, 0], + [20750, 27368, 29821, 32768, 0], + [16894, 24828, 28573, 32768, 0], + [13247, 21276, 25757, 32768, 0], + [10038, 17265, 22563, 32768, 0], + [8587, 14947, 20327, 32768, 0], + [5645, 11371, 15252, 32768, 0], + [22027, 27526, 29714, 32768, 0], + [23098, 29146, 31221, 32768, 0], + [19886, 27341, 30272, 32768, 0], + [15609, 23747, 28046, 32768, 0], + [11993, 20065, 24939, 32768, 0], + [9637, 18267, 23671, 32768, 0], + [7625, 13801, 19144, 32768, 0], + ], + ], + [ + [ + [14438, 20798, 24089, 32768, 0], + [12621, 19203, 23097, 32768, 0], + [8177, 14125, 18402, 32768, 0], + [5674, 10501, 14456, 32768, 0], + [4236, 8239, 11733, 32768, 0], + [3447, 6750, 9806, 32768, 0], + [1986, 3950, 5864, 32768, 0], + [16208, 22099, 24930, 32768, 0], + [16537, 24025, 27585, 32768, 0], + [12780, 20381, 24867, 32768, 0], + [9767, 16612, 21416, 32768, 0], + [7686, 13738, 18398, 32768, 0], + [6333, 11614, 15964, 32768, 0], + [3941, 7571, 10836, 32768, 0], + [22819, 27422, 29202, 32768, 0], + [22224, 28514, 30721, 32768, 0], + [17660, 25433, 28913, 32768, 0], + [13574, 21482, 26002, 32768, 0], + [10629, 17977, 22938, 32768, 0], + [8612, 15298, 20265, 32768, 0], + [5607, 10491, 14596, 32768, 0], + ], + [ + [13569, 19800, 23206, 32768, 0], + [13128, 19924, 23869, 32768, 0], + [8329, 14841, 19403, 32768, 0], + [6130, 10976, 15057, 32768, 0], + [4682, 8839, 12518, 32768, 0], + [3656, 7409, 10588, 32768, 0], + [2577, 5099, 7412, 32768, 0], + [22427, 28684, 30585, 32768, 0], + [20913, 27750, 30139, 32768, 0], + [15840, 24109, 27834, 32768, 0], + [12308, 20029, 24569, 32768, 0], + [10216, 16785, 21458, 32768, 0], + [8309, 14203, 19113, 32768, 0], + [6043, 11168, 15307, 32768, 0], + [23166, 28901, 30998, 32768, 0], + [21899, 28405, 30751, 32768, 0], + [18413, 26091, 29443, 32768, 0], + [15233, 23114, 27352, 32768, 0], + [12683, 20472, 25288, 32768, 0], + [10702, 18259, 23409, 32768, 0], + [8125, 14464, 19226, 32768, 0], + ], + ], + [ + [ + [9040, 14786, 18360, 32768, 0], + [9979, 15718, 19415, 32768, 0], + [7913, 13918, 18311, 32768, 0], + [5859, 10889, 15184, 32768, 0], + [4593, 8677, 12510, 32768, 0], + [3820, 7396, 10791, 32768, 0], + [1730, 3471, 5192, 32768, 0], + [11803, 18365, 22709, 32768, 0], + [11419, 18058, 22225, 32768, 0], + [9418, 15774, 20243, 32768, 0], + [7539, 13325, 17657, 32768, 0], + [6233, 11317, 15384, 32768, 0], + [5137, 9656, 13545, 32768, 0], + [2977, 5774, 8349, 32768, 0], + [21207, 27246, 29640, 32768, 0], + [19547, 26578, 29497, 32768, 0], + [16169, 23871, 27690, 32768, 0], + [12820, 20458, 25018, 32768, 0], + [10224, 17332, 22214, 32768, 0], + [8526, 15048, 19884, 32768, 0], + [5037, 9410, 13118, 32768, 0], + ], + [ + [12339, 17329, 20140, 32768, 0], + [13505, 19895, 23225, 32768, 0], + [9847, 16944, 21564, 32768, 0], + [7280, 13256, 18348, 32768, 0], + [4712, 10009, 14454, 32768, 0], + [4361, 7914, 12477, 32768, 0], + [2870, 5628, 7995, 32768, 0], + [20061, 25504, 28526, 32768, 0], + [15235, 22878, 26145, 32768, 0], + [12985, 19958, 24155, 32768, 0], + [9782, 16641, 21403, 32768, 0], + [9456, 16360, 20760, 32768, 0], + [6855, 12940, 18557, 32768, 0], + [5661, 10564, 15002, 32768, 0], + [25656, 30602, 31894, 32768, 0], + [22570, 29107, 31092, 32768, 0], + [18917, 26423, 29541, 32768, 0], + [15940, 23649, 27754, 32768, 0], + [12803, 20581, 25219, 32768, 0], + [11082, 18695, 23376, 32768, 0], + [7939, 14373, 19005, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [18315, 24289, 27551, 32768, 0], + [16854, 24068, 27835, 32768, 0], + [10140, 17927, 23173, 32768, 0], + [6722, 12982, 18267, 32768, 0], + [4661, 9826, 14706, 32768, 0], + [3832, 8165, 12294, 32768, 0], + [2795, 6098, 9245, 32768, 0], + [17145, 23326, 26672, 32768, 0], + [20733, 27680, 30308, 32768, 0], + [16032, 24461, 28546, 32768, 0], + [11653, 20093, 25081, 32768, 0], + [9290, 16429, 22086, 32768, 0], + [7796, 14598, 19982, 32768, 0], + [6502, 12378, 17441, 32768, 0], + [21681, 27732, 30320, 32768, 0], + [22389, 29044, 31261, 32768, 0], + [19027, 26731, 30087, 32768, 0], + [14739, 23755, 28624, 32768, 0], + [11358, 20778, 25511, 32768, 0], + [10995, 18073, 24190, 32768, 0], + [9162, 14990, 20617, 32768, 0], + ], + [ + [21425, 27952, 30388, 32768, 0], + [18062, 25838, 29034, 32768, 0], + [11956, 19881, 24808, 32768, 0], + [7718, 15000, 20980, 32768, 0], + [5702, 11254, 16143, 32768, 0], + [4898, 9088, 16864, 32768, 0], + [3679, 6776, 11907, 32768, 0], + [23294, 30160, 31663, 32768, 0], + [24397, 29896, 31836, 32768, 0], + [19245, 27128, 30593, 32768, 0], + [13202, 19825, 26404, 32768, 0], + [11578, 19297, 23957, 32768, 0], + [8073, 13297, 21370, 32768, 0], + [5461, 10923, 19745, 32768, 0], + [27367, 30521, 31934, 32768, 0], + [24904, 30671, 31940, 32768, 0], + [23075, 28460, 31299, 32768, 0], + [14400, 23658, 30417, 32768, 0], + [13885, 23882, 28325, 32768, 0], + [14746, 22938, 27853, 32768, 0], + [5461, 16384, 27307, 32768, 0], + ], + ], + [ + [ + [18274, 24813, 27890, 32768, 0], + [15537, 23149, 27003, 32768, 0], + [9449, 16740, 21827, 32768, 0], + [6700, 12498, 17261, 32768, 0], + [4988, 9866, 14198, 32768, 0], + [4236, 8147, 11902, 32768, 0], + [2867, 5860, 8654, 32768, 0], + [17124, 23171, 26101, 32768, 0], + [20396, 27477, 30148, 32768, 0], + [16573, 24629, 28492, 32768, 0], + [12749, 20846, 25674, 32768, 0], + [10233, 17878, 22818, 32768, 0], + [8525, 15332, 20363, 32768, 0], + [6283, 11632, 16255, 32768, 0], + [20466, 26511, 29286, 32768, 0], + [23059, 29174, 31191, 32768, 0], + [19481, 27263, 30241, 32768, 0], + [15458, 23631, 28137, 32768, 0], + [12416, 20608, 25693, 32768, 0], + [10261, 18011, 23261, 32768, 0], + [8016, 14655, 19666, 32768, 0], + ], + [ + [17616, 24586, 28112, 32768, 0], + [15809, 23299, 27155, 32768, 0], + [10767, 18890, 23793, 32768, 0], + [7727, 14255, 18865, 32768, 0], + [6129, 11926, 16882, 32768, 0], + [4482, 9704, 14861, 32768, 0], + [3277, 7452, 11522, 32768, 0], + [22956, 28551, 30730, 32768, 0], + [22724, 28937, 30961, 32768, 0], + [18467, 26324, 29580, 32768, 0], + [13234, 20713, 25649, 32768, 0], + [11181, 17592, 22481, 32768, 0], + [8291, 18358, 24576, 32768, 0], + [7568, 11881, 14984, 32768, 0], + [24948, 29001, 31147, 32768, 0], + [25674, 30619, 32151, 32768, 0], + [20841, 26793, 29603, 32768, 0], + [14669, 24356, 28666, 32768, 0], + [11334, 23593, 28219, 32768, 0], + [8922, 14762, 22873, 32768, 0], + [8301, 13544, 20535, 32768, 0], + ], + ], + [ + [ + [17113, 23733, 27081, 32768, 0], + [14139, 21406, 25452, 32768, 0], + [8552, 15002, 19776, 32768, 0], + [5871, 11120, 15378, 32768, 0], + [4455, 8616, 12253, 32768, 0], + [3469, 6910, 10386, 32768, 0], + [2255, 4553, 6782, 32768, 0], + [18224, 24376, 27053, 32768, 0], + [19290, 26710, 29614, 32768, 0], + [14936, 22991, 27184, 32768, 0], + [11238, 18951, 23762, 32768, 0], + [8786, 15617, 20588, 32768, 0], + [7317, 13228, 18003, 32768, 0], + [5101, 9512, 13493, 32768, 0], + [22639, 28222, 30210, 32768, 0], + [23216, 29331, 31307, 32768, 0], + [19075, 26762, 29895, 32768, 0], + [15014, 23113, 27457, 32768, 0], + [11938, 19857, 24752, 32768, 0], + [9942, 17280, 22282, 32768, 0], + [7167, 13144, 17752, 32768, 0], + ], + [ + [15820, 22738, 26488, 32768, 0], + [13530, 20885, 25216, 32768, 0], + [8395, 15530, 20452, 32768, 0], + [6574, 12321, 16380, 32768, 0], + [5353, 10419, 14568, 32768, 0], + [4613, 8446, 12381, 32768, 0], + [3440, 7158, 9903, 32768, 0], + [24247, 29051, 31224, 32768, 0], + [22118, 28058, 30369, 32768, 0], + [16498, 24768, 28389, 32768, 0], + [12920, 21175, 26137, 32768, 0], + [10730, 18619, 25352, 32768, 0], + [10187, 16279, 22791, 32768, 0], + [9310, 14631, 22127, 32768, 0], + [24970, 30558, 32057, 32768, 0], + [24801, 29942, 31698, 32768, 0], + [22432, 28453, 30855, 32768, 0], + [19054, 25680, 29580, 32768, 0], + [14392, 23036, 28109, 32768, 0], + [12495, 20947, 26650, 32768, 0], + [12442, 20326, 26214, 32768, 0], + ], + ], + [ + [ + [12162, 18785, 22648, 32768, 0], + [12749, 19697, 23806, 32768, 0], + [8580, 15297, 20346, 32768, 0], + [6169, 11749, 16543, 32768, 0], + [4836, 9391, 13448, 32768, 0], + [3821, 7711, 11613, 32768, 0], + [2228, 4601, 7070, 32768, 0], + [16319, 24725, 28280, 32768, 0], + [15698, 23277, 27168, 32768, 0], + [12726, 20368, 25047, 32768, 0], + [9912, 17015, 21976, 32768, 0], + [7888, 14220, 19179, 32768, 0], + [6777, 12284, 17018, 32768, 0], + [4492, 8590, 12252, 32768, 0], + [23249, 28904, 30947, 32768, 0], + [21050, 27908, 30512, 32768, 0], + [17440, 25340, 28949, 32768, 0], + [14059, 22018, 26541, 32768, 0], + [11288, 18903, 23898, 32768, 0], + [9411, 16342, 21428, 32768, 0], + [6278, 11588, 15944, 32768, 0], + ], + [ + [13981, 20067, 23226, 32768, 0], + [16922, 23580, 26783, 32768, 0], + [11005, 19039, 24487, 32768, 0], + [7389, 14218, 19798, 32768, 0], + [5598, 11505, 17206, 32768, 0], + [6090, 11213, 15659, 32768, 0], + [3820, 7371, 10119, 32768, 0], + [21082, 26925, 29675, 32768, 0], + [21262, 28627, 31128, 32768, 0], + [18392, 26454, 30437, 32768, 0], + [14870, 22910, 27096, 32768, 0], + [12620, 19484, 24908, 32768, 0], + [9290, 16553, 22802, 32768, 0], + [6668, 14288, 20004, 32768, 0], + [27704, 31055, 31949, 32768, 0], + [24709, 29978, 31788, 32768, 0], + [21668, 29264, 31657, 32768, 0], + [18295, 26968, 30074, 32768, 0], + [16399, 24422, 29313, 32768, 0], + [14347, 23026, 28104, 32768, 0], + [12370, 19806, 24477, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], +]; + +pub static DEFAULT_DC_SIGN_CDF: [[[[u16; 3]; 3]; 2]; 4] = [ + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], +]; + +pub static DEFAULT_DRL_MODE_CDF: [[u16; 3]; 3] = [[13104, 32768, 0], [24560, 32768, 0], [18945, 32768, 0]]; + +pub static DEFAULT_EOB_EXTRA_CDF: [[[[[u16; 3]; 9]; 2]; 5]; 4] = [ + [ + [ + [ + [16961, 32768, 0], + [17223, 32768, 0], + [7621, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [19069, 32768, 0], + [22525, 32768, 0], + [13377, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [20401, 32768, 0], + [17025, 32768, 0], + [12845, 32768, 0], + [12873, 32768, 0], + [14094, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20681, 32768, 0], + [20701, 32768, 0], + [15250, 32768, 0], + [15017, 32768, 0], + [14928, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [23905, 32768, 0], + [17194, 32768, 0], + [16170, 32768, 0], + [17695, 32768, 0], + [13826, 32768, 0], + [15810, 32768, 0], + [12036, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [23959, 32768, 0], + [20799, 32768, 0], + [19021, 32768, 0], + [16203, 32768, 0], + [17886, 32768, 0], + [14144, 32768, 0], + [12010, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [27399, 32768, 0], + [16327, 32768, 0], + [18071, 32768, 0], + [19584, 32768, 0], + [20721, 32768, 0], + [18432, 32768, 0], + [19560, 32768, 0], + [10150, 32768, 0], + [8805, 32768, 0], + ], + [ + [24932, 32768, 0], + [20833, 32768, 0], + [12027, 32768, 0], + [16670, 32768, 0], + [19914, 32768, 0], + [15106, 32768, 0], + [17662, 32768, 0], + [13783, 32768, 0], + [28756, 32768, 0], + ], + ], + [ + [ + [23406, 32768, 0], + [21845, 32768, 0], + [18432, 32768, 0], + [16384, 32768, 0], + [17096, 32768, 0], + [12561, 32768, 0], + [17320, 32768, 0], + [22395, 32768, 0], + [21370, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], + [ + [ + [ + [17471, 32768, 0], + [20223, 32768, 0], + [11357, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20335, 32768, 0], + [21667, 32768, 0], + [14818, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [20430, 32768, 0], + [20662, 32768, 0], + [15367, 32768, 0], + [16970, 32768, 0], + [14657, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [22117, 32768, 0], + [22028, 32768, 0], + [18650, 32768, 0], + [16042, 32768, 0], + [15885, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [22409, 32768, 0], + [21012, 32768, 0], + [15650, 32768, 0], + [17395, 32768, 0], + [15469, 32768, 0], + [20205, 32768, 0], + [19511, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [24220, 32768, 0], + [22480, 32768, 0], + [17737, 32768, 0], + [18916, 32768, 0], + [19268, 32768, 0], + [18412, 32768, 0], + [18844, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [25991, 32768, 0], + [20314, 32768, 0], + [17731, 32768, 0], + [19678, 32768, 0], + [18649, 32768, 0], + [17307, 32768, 0], + [21798, 32768, 0], + [17549, 32768, 0], + [15630, 32768, 0], + ], + [ + [26585, 32768, 0], + [21469, 32768, 0], + [20432, 32768, 0], + [17735, 32768, 0], + [19280, 32768, 0], + [15235, 32768, 0], + [20297, 32768, 0], + [22471, 32768, 0], + [28997, 32768, 0], + ], + ], + [ + [ + [26605, 32768, 0], + [11304, 32768, 0], + [16726, 32768, 0], + [16560, 32768, 0], + [20866, 32768, 0], + [23524, 32768, 0], + [19878, 32768, 0], + [13469, 32768, 0], + [23084, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], + [ + [ + [ + [18983, 32768, 0], + [20512, 32768, 0], + [14885, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20090, 32768, 0], + [19444, 32768, 0], + [17286, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [19139, 32768, 0], + [21487, 32768, 0], + [18959, 32768, 0], + [20910, 32768, 0], + [19089, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20536, 32768, 0], + [20664, 32768, 0], + [20625, 32768, 0], + [19123, 32768, 0], + [14862, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [19833, 32768, 0], + [21502, 32768, 0], + [17485, 32768, 0], + [20267, 32768, 0], + [18353, 32768, 0], + [23329, 32768, 0], + [21478, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [22041, 32768, 0], + [23434, 32768, 0], + [20001, 32768, 0], + [20554, 32768, 0], + [20951, 32768, 0], + [20145, 32768, 0], + [15562, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [23312, 32768, 0], + [21607, 32768, 0], + [16526, 32768, 0], + [18957, 32768, 0], + [18034, 32768, 0], + [18934, 32768, 0], + [24247, 32768, 0], + [16921, 32768, 0], + [17080, 32768, 0], + ], + [ + [26579, 32768, 0], + [24910, 32768, 0], + [18637, 32768, 0], + [19800, 32768, 0], + [20388, 32768, 0], + [9887, 32768, 0], + [15642, 32768, 0], + [30198, 32768, 0], + [24721, 32768, 0], + ], + ], + [ + [ + [26998, 32768, 0], + [16737, 32768, 0], + [17838, 32768, 0], + [18922, 32768, 0], + [19515, 32768, 0], + [18636, 32768, 0], + [17333, 32768, 0], + [15776, 32768, 0], + [22658, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], + [ + [ + [ + [20177, 32768, 0], + [20789, 32768, 0], + [20262, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [21416, 32768, 0], + [20855, 32768, 0], + [23410, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [20238, 32768, 0], + [21057, 32768, 0], + [19159, 32768, 0], + [22337, 32768, 0], + [20159, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20125, 32768, 0], + [20559, 32768, 0], + [21707, 32768, 0], + [22296, 32768, 0], + [17333, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [19941, 32768, 0], + [20527, 32768, 0], + [21470, 32768, 0], + [22487, 32768, 0], + [19558, 32768, 0], + [22354, 32768, 0], + [20331, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [22752, 32768, 0], + [25006, 32768, 0], + [22075, 32768, 0], + [21576, 32768, 0], + [17740, 32768, 0], + [21690, 32768, 0], + [19211, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [21442, 32768, 0], + [22358, 32768, 0], + [18503, 32768, 0], + [20291, 32768, 0], + [19945, 32768, 0], + [21294, 32768, 0], + [21178, 32768, 0], + [19400, 32768, 0], + [10556, 32768, 0], + ], + [ + [24648, 32768, 0], + [24949, 32768, 0], + [20708, 32768, 0], + [23905, 32768, 0], + [20501, 32768, 0], + [9558, 32768, 0], + [9423, 32768, 0], + [30365, 32768, 0], + [19253, 32768, 0], + ], + ], + [ + [ + [26064, 32768, 0], + [22098, 32768, 0], + [19613, 32768, 0], + [20525, 32768, 0], + [17595, 32768, 0], + [16618, 32768, 0], + [20497, 32768, 0], + [18989, 32768, 0], + [15513, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], +]; + +pub static DEFAULT_EOB_PT_1024_CDF: [[[u16; 12]; 2]; 4] = [ + [[393, 421, 751, 1623, 3160, 6352, 13345, 18047, 22571, 25830, 32768, 0], [1865, 1988, 2930, 4242, 10533, 16538, 21354, 27255, 28546, 31784, 32768, 0]], + [[696, 948, 3145, 5702, 9706, 13217, 17851, 21856, 25692, 28034, 32768, 0], [2672, 3591, 9330, 17084, 22725, 24284, 26527, 28027, 28377, 30876, 32768, 0]], + [ + [2784, 3831, 7041, 10521, 14847, 18844, 23155, 26682, 29229, 31045, 32768, 0], + [9577, 12466, 17739, 20750, 22061, 23215, 24601, 25483, 25843, 32056, 32768, 0], + ], + [ + [6698, 8334, 11961, 15762, 20186, 23862, 27434, 29326, 31082, 32050, 32768, 0], + [20569, 22426, 25569, 26859, 28053, 28913, 29486, 29724, 29807, 32570, 32768, 0], + ], +]; + +pub static DEFAULT_EOB_PT_128_CDF: [[[[u16; 9]; 2]; 2]; 4] = [ + [ + [[219, 482, 1140, 2091, 3680, 6028, 12586, 32768, 0], [371, 699, 1254, 4830, 9479, 12562, 17497, 32768, 0]], + [[5245, 7456, 12880, 15852, 20033, 23932, 27608, 32768, 0], [2054, 3472, 5869, 14232, 18242, 20590, 26752, 32768, 0]], + ], + [ + [[685, 933, 1488, 2714, 4766, 8562, 19254, 32768, 0], [217, 352, 618, 2303, 5261, 9969, 17472, 32768, 0]], + [[8045, 11200, 15497, 19595, 23948, 27408, 30938, 32768, 0], [2310, 4160, 7471, 14997, 17931, 20768, 30240, 32768, 0]], + ], + [ + [[1366, 1738, 2527, 5016, 9355, 15797, 24643, 32768, 0], [354, 558, 944, 2760, 7287, 14037, 21779, 32768, 0]], + [[13627, 16246, 20173, 24429, 27948, 30415, 31863, 32768, 0], [6275, 9889, 14769, 23164, 27988, 30493, 32272, 32768, 0]], + ], + [ + [[3472, 4885, 7489, 12481, 18517, 24536, 29635, 32768, 0], [886, 1731, 3271, 8469, 15569, 22126, 28383, 32768, 0]], + [[24313, 26062, 28385, 30107, 31217, 31898, 32345, 32768, 0], [9165, 13282, 21150, 30286, 31894, 32571, 32712, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_16_CDF: [[[[u16; 6]; 2]; 2]; 4] = [ + [[[840, 1039, 1980, 4895, 32768, 0], [370, 671, 1883, 4471, 32768, 0]], [[3247, 4950, 9688, 14563, 32768, 0], [1904, 3354, 7763, 14647, 32768, 0]]], + [[[2125, 2551, 5165, 8946, 32768, 0], [513, 765, 1859, 6339, 32768, 0]], [[7637, 9498, 14259, 19108, 32768, 0], [2497, 4096, 8866, 16993, 32768, 0]]], + [[[4016, 4897, 8881, 14968, 32768, 0], [716, 1105, 2646, 10056, 32768, 0]], [[11139, 13270, 18241, 23566, 32768, 0], [3192, 5032, 10297, 19755, 32768, 0]]], + [ + [[6708, 8958, 14746, 22133, 32768, 0], [1222, 2074, 4783, 15410, 32768, 0]], + [[19575, 21766, 26044, 29709, 32768, 0], [7297, 10767, 19273, 28194, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_256_CDF: [[[[u16; 10]; 2]; 2]; 4] = [ + [ + [[310, 584, 1887, 3589, 6168, 8611, 11352, 15652, 32768, 0], [998, 1850, 2998, 5604, 17341, 19888, 22899, 25583, 32768, 0]], + [[2520, 3240, 5952, 8870, 12577, 17558, 19954, 24168, 32768, 0], [2203, 4130, 7435, 10739, 20652, 23681, 25609, 27261, 32768, 0]], + ], + [ + [[1448, 2109, 4151, 6263, 9329, 13260, 17944, 23300, 32768, 0], [399, 1019, 1749, 3038, 10444, 15546, 22739, 27294, 32768, 0]], + [[6402, 8148, 12623, 15072, 18728, 22847, 26447, 29377, 32768, 0], [1674, 3252, 5734, 10159, 22397, 23802, 24821, 30940, 32768, 0]], + ], + [ + [[3089, 3920, 6038, 9460, 14266, 19881, 25766, 29176, 32768, 0], [1084, 2358, 3488, 5122, 11483, 18103, 26023, 29799, 32768, 0]], + [[11514, 13794, 17480, 20754, 24361, 27378, 29492, 31277, 32768, 0], [6571, 9610, 15516, 21826, 29092, 30829, 31842, 32708, 32768, 0]], + ], + [ + [[5348, 7113, 11820, 15924, 22106, 26777, 30334, 31757, 32768, 0], [2453, 4474, 6307, 8777, 16474, 22975, 29000, 31547, 32768, 0]], + [[23110, 24597, 27140, 28894, 30167, 30927, 31392, 32094, 32768, 0], [9998, 17661, 25178, 28097, 31308, 32038, 32403, 32695, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_32_CDF: [[[[u16; 7]; 2]; 2]; 4] = [ + [ + [[400, 520, 977, 2102, 6542, 32768, 0], [210, 405, 1315, 3326, 7537, 32768, 0]], + [[2636, 4273, 7588, 11794, 20401, 32768, 0], [1786, 3179, 6902, 11357, 19054, 32768, 0]], + ], + [ + [[989, 1249, 2019, 4151, 10785, 32768, 0], [313, 441, 1099, 2917, 8562, 32768, 0]], + [[8394, 10352, 13932, 18855, 26014, 32768, 0], [2578, 4124, 8181, 13670, 24234, 32768, 0]], + ], + [ + [[2515, 3003, 4452, 8162, 16041, 32768, 0], [574, 821, 1836, 5089, 13128, 32768, 0]], + [[13468, 16303, 20361, 25105, 29281, 32768, 0], [3542, 5502, 10415, 16760, 25644, 32768, 0]], + ], + [ + [[4617, 5709, 8446, 13584, 23135, 32768, 0], [1156, 1702, 3675, 9274, 20539, 32768, 0]], + [[22086, 24282, 27010, 29770, 31743, 32768, 0], [7699, 10897, 20891, 26926, 31628, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_512_CDF: [[[u16; 11]; 2]; 4] = [ + [[641, 983, 3707, 5430, 10234, 14958, 18788, 23412, 26061, 32768, 0], [5095, 6446, 9996, 13354, 16017, 17986, 20919, 26129, 29140, 32768, 0]], + [[1230, 2278, 5035, 7776, 11871, 15346, 19590, 24584, 28749, 32768, 0], [7265, 9979, 15819, 19250, 21780, 23846, 26478, 28396, 31811, 32768, 0]], + [[2624, 3936, 6480, 9686, 13979, 17726, 23267, 28410, 31078, 32768, 0], [12015, 14769, 19588, 22052, 24222, 25812, 27300, 29219, 32114, 32768, 0]], + [[5927, 7809, 10923, 14597, 19439, 24135, 28456, 31142, 32060, 32768, 0], [21093, 23043, 25742, 27658, 29097, 29716, 30073, 30820, 31956, 32768, 0]], +]; + +pub static DEFAULT_EOB_PT_64_CDF: [[[[u16; 8]; 2]; 2]; 4] = [ + [ + [[329, 498, 1101, 1784, 3265, 7758, 32768, 0], [335, 730, 1459, 5494, 8755, 12997, 32768, 0]], + [[3505, 5304, 10086, 13814, 17684, 23370, 32768, 0], [1563, 2700, 4876, 10911, 14706, 22480, 32768, 0]], + ], + [ + [[1260, 1446, 2253, 3712, 6652, 13369, 32768, 0], [401, 605, 1029, 2563, 5845, 12626, 32768, 0]], + [[8609, 10612, 14624, 18714, 22614, 29024, 32768, 0], [1923, 3127, 5867, 9703, 14277, 27100, 32768, 0]], + ], + [ + [[2374, 2772, 4583, 7276, 12288, 19706, 32768, 0], [497, 810, 1315, 3000, 7004, 15641, 32768, 0]], + [[15050, 17126, 21410, 24886, 28156, 30726, 32768, 0], [4034, 6290, 10235, 14982, 21214, 28491, 32768, 0]], + ], + [ + [[6307, 7541, 12060, 16358, 22553, 27865, 32768, 0], [1289, 2320, 3971, 7926, 14153, 24291, 32768, 0]], + [[24212, 25708, 28268, 30035, 31307, 32049, 32768, 0], [8726, 12378, 19409, 26450, 30038, 32462, 32768, 0]], + ], +]; + +pub static DEFAULT_INTER_TX_TYPE_SET3_CDF: [[u16; 3]; 4] = [[16384, 32768, 0], [4167, 32768, 0], [1998, 32768, 0], [748, 32768, 0]]; + +pub static DEFAULT_INTRA_FRAME_Y_MODE_CDF: [[[u16; 14]; 5]; 5] = [ + [ + [15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466, 32768, 0], + [12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409, 32768, 0], + [10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567, 32768, 0], + [14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462, 32768, 0], + [12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475, 32768, 0], + ], + [ + [10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436, 32768, 0], + [5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794, 32768, 0], + [7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492, 32768, 0], + [8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210, 32768, 0], + [7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994, 32768, 0], + ], + [ + [12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077, 32768, 0], + [9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907, 32768, 0], + [6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086, 32768, 0], + [10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252, 32768, 0], + [9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463, 32768, 0], + ], + [ + [12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409, 32768, 0], + [8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342, 32768, 0], + [8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018, 32768, 0], + [7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528, 32768, 0], + [8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485, 32768, 0], + ], + [ + [12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477, 32768, 0], + [9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277, 32768, 0], + [8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261, 32768, 0], + [10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482, 32768, 0], + [7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593, 32768, 0], + ], +]; + +pub static DEFAULT_INTRA_TX_TYPE_SET2_CDF: [[[u16; 6]; 13]; 3] = [ + [ + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + ], + [ + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + ], + [ + [1127, 12814, 22772, 27483, 32768, 0], + [145, 6761, 11980, 26667, 32768, 0], + [362, 5887, 11678, 16725, 32768, 0], + [385, 15213, 18587, 30693, 32768, 0], + [25, 2914, 23134, 27903, 32768, 0], + [60, 4470, 11749, 23991, 32768, 0], + [37, 3332, 14511, 21448, 32768, 0], + [157, 6320, 13036, 17439, 32768, 0], + [119, 6719, 12906, 29396, 32768, 0], + [47, 5537, 12576, 21499, 32768, 0], + [269, 6076, 11258, 23115, 32768, 0], + [83, 5615, 12001, 17228, 32768, 0], + [1968, 5556, 12023, 18547, 32768, 0], + ], +]; + +pub static DEFAULT_IS_INTER_CDF: [[u16; 3]; 4] = [[806, 32768, 0], [16662, 32768, 0], [20186, 32768, 0], [26538, 32768, 0]]; + +pub static DEFAULT_MV_BIT_CDF: [[u16; 3]; 10] = [ + [17408, 32768, 0], + [17920, 32768, 0], + [18944, 32768, 0], + [20480, 32768, 0], + [22528, 32768, 0], + [24576, 32768, 0], + [28672, 32768, 0], + [29952, 32768, 0], + [29952, 32768, 0], + [30720, 32768, 0], +]; + +pub static DEFAULT_MV_CLASS0_BIT_CDF: [u16; 3] = [27648, 32768, 0]; + +pub static DEFAULT_MV_CLASS0_FR_CDF: [[[u16; 5]; 2]; 2] = + [[[16384, 24576, 26624, 32768, 0], [12288, 21248, 24128, 32768, 0]], [[16384, 24576, 26624, 32768, 0], [12288, 21248, 24128, 32768, 0]]]; + +pub static DEFAULT_MV_CLASS0_HP_CDF: [u16; 3] = [20480, 32768, 0]; + +pub static DEFAULT_MV_CLASS_CDF: [[u16; 12]; 2] = [ + [28672, 30976, 31858, 32320, 32551, 32656, 32740, 32757, 32762, 32767, 32768, 0], + [28672, 30976, 31858, 32320, 32551, 32656, 32740, 32757, 32762, 32767, 32768, 0], +]; + +pub static DEFAULT_MV_FR_CDF: [[u16; 5]; 2] = [[8192, 17408, 21248, 32768, 0], [8192, 17408, 21248, 32768, 0]]; + +pub static DEFAULT_MV_HP_CDF: [u16; 3] = [16384, 32768, 0]; + +pub static DEFAULT_MV_JOINT_CDF: [u16; 5] = [4096, 11264, 19328, 32768, 0]; + +pub static DEFAULT_MV_SIGN_CDF: [u16; 3] = [16384, 32768, 0]; + +pub static DEFAULT_NEW_MV_CDF: [[u16; 3]; 6] = [[24035, 32768, 0], [16630, 32768, 0], [15339, 32768, 0], [8386, 32768, 0], [12222, 32768, 0], [4676, 32768, 0]]; + +pub static DEFAULT_PARTITION_W16_CDF: [[u16; 11]; 4] = [ + [15597, 20929, 24571, 26706, 27664, 28821, 29601, 30571, 31902, 32768, 0], + [7925, 11043, 16785, 22470, 23971, 25043, 26651, 28701, 29834, 32768, 0], + [5414, 13269, 15111, 20488, 22360, 24500, 25537, 26336, 32117, 32768, 0], + [2662, 6362, 8614, 20860, 23053, 24778, 26436, 27829, 31171, 32768, 0], +]; + +pub static DEFAULT_PARTITION_W32_CDF: [[u16; 11]; 4] = [ + [18462, 20920, 23124, 27647, 28227, 29049, 29519, 30178, 31544, 32768, 0], + [7689, 9060, 12056, 24992, 25660, 26182, 26951, 28041, 29052, 32768, 0], + [6015, 9009, 10062, 24544, 25409, 26545, 27071, 27526, 32047, 32768, 0], + [1394, 2208, 2796, 28614, 29061, 29466, 29840, 30185, 31899, 32768, 0], +]; + +pub static DEFAULT_PARTITION_W64_CDF: [[u16; 11]; 4] = [ + [20137, 21547, 23078, 29566, 29837, 30261, 30524, 30892, 31724, 32768, 0], + [6732, 7490, 9497, 27944, 28250, 28515, 28969, 29630, 30104, 32768, 0], + [5945, 7663, 8348, 28683, 29117, 29749, 30064, 30298, 32238, 32768, 0], + [870, 1212, 1487, 31198, 31394, 31574, 31743, 31881, 32332, 32768, 0], +]; + +pub static DEFAULT_PARTITION_W8_CDF: [[u16; 5]; 4] = + [[19132, 25510, 30392, 32768, 0], [13928, 19855, 28540, 32768, 0], [12522, 23679, 28629, 32768, 0], [9896, 18783, 25853, 32768, 0]]; + +pub static DEFAULT_REF_MV_CDF: [[u16; 3]; 6] = + [[23974, 32768, 0], [24188, 32768, 0], [17848, 32768, 0], [28622, 32768, 0], [24312, 32768, 0], [19923, 32768, 0]]; + +pub static DEFAULT_SCAN_16X16: [u16; 256] = [ + 0, 1, 16, 32, 17, 2, 3, 18, 33, 48, 64, 49, 34, 19, 4, 5, 20, 35, 50, 65, 80, 96, 81, 66, 51, 36, 21, 6, 7, 22, 37, 52, 67, 82, 97, 112, 128, 113, 98, 83, + 68, 53, 38, 23, 8, 9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 11, 26, 41, 56, 71, 86, 101, 116, 131, 146, + 161, 176, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 224, 209, 194, 179, + 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 241, 226, 211, 196, 181, 166, + 151, 136, 121, 106, 91, 76, 61, 46, 31, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182, 197, 212, 227, 242, 243, 228, 213, 198, 183, 168, 153, 138, 123, 108, + 93, 78, 63, 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 245, 230, 215, 200, 185, 170, 155, 140, 125, 110, 95, 111, 126, 141, 156, 171, 186, + 201, 216, 231, 246, 247, 232, 217, 202, 187, 172, 157, 142, 127, 143, 158, 173, 188, 203, 218, 233, 248, 249, 234, 219, 204, 189, 174, 159, 175, 190, 205, + 220, 235, 250, 251, 236, 221, 206, 191, 207, 222, 237, 252, 253, 238, 223, 239, 254, 255, +]; + +pub static DEFAULT_SCAN_32X32: [u16; 1024] = [ + 0, 1, 32, 64, 33, 2, 3, 34, 65, 96, 128, 97, 66, 35, 4, 5, 36, 67, 98, 129, 160, 192, 161, 130, 99, 68, 37, 6, 7, 38, 69, 100, 131, 162, 193, 224, 256, + 225, 194, 163, 132, 101, 70, 39, 8, 9, 40, 71, 102, 133, 164, 195, 226, 257, 288, 320, 289, 258, 227, 196, 165, 134, 103, 72, 41, 10, 11, 42, 73, 104, 135, + 166, 197, 228, 259, 290, 321, 352, 384, 353, 322, 291, 260, 229, 198, 167, 136, 105, 74, 43, 12, 13, 44, 75, 106, 137, 168, 199, 230, 261, 292, 323, 354, + 385, 416, 448, 417, 386, 355, 324, 293, 262, 231, 200, 169, 138, 107, 76, 45, 14, 15, 46, 77, 108, 139, 170, 201, 232, 263, 294, 325, 356, 387, 418, 449, + 480, 512, 481, 450, 419, 388, 357, 326, 295, 264, 233, 202, 171, 140, 109, 78, 47, 16, 17, 48, 79, 110, 141, 172, 203, 234, 265, 296, 327, 358, 389, 420, + 451, 482, 513, 544, 576, 545, 514, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, 111, 80, 49, 18, 19, 50, 81, 112, 143, 174, 205, 236, 267, + 298, 329, 360, 391, 422, 453, 484, 515, 546, 577, 608, 640, 609, 578, 547, 516, 485, 454, 423, 392, 361, 330, 299, 268, 237, 206, 175, 144, 113, 82, 51, + 20, 21, 52, 83, 114, 145, 176, 207, 238, 269, 300, 331, 362, 393, 424, 455, 486, 517, 548, 579, 610, 641, 672, 704, 673, 642, 611, 580, 549, 518, 487, 456, + 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115, 84, 53, 22, 23, 54, 85, 116, 147, 178, 209, 240, 271, 302, 333, 364, 395, 426, 457, 488, 519, 550, + 581, 612, 643, 674, 705, 736, 768, 737, 706, 675, 644, 613, 582, 551, 520, 489, 458, 427, 396, 365, 334, 303, 272, 241, 210, 179, 148, 117, 86, 55, 24, 25, + 56, 87, 118, 149, 180, 211, 242, 273, 304, 335, 366, 397, 428, 459, 490, 521, 552, 583, 614, 645, 676, 707, 738, 769, 800, 832, 801, 770, 739, 708, 677, + 646, 615, 584, 553, 522, 491, 460, 429, 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26, 27, 58, 89, 120, 151, 182, 213, 244, 275, 306, 337, + 368, 399, 430, 461, 492, 523, 554, 585, 616, 647, 678, 709, 740, 771, 802, 833, 864, 896, 865, 834, 803, 772, 741, 710, 679, 648, 617, 586, 555, 524, 493, + 462, 431, 400, 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 29, 60, 91, 122, 153, 184, 215, 246, 277, 308, 339, 370, 401, 432, 463, 494, 525, + 556, 587, 618, 649, 680, 711, 742, 773, 804, 835, 866, 897, 928, 960, 929, 898, 867, 836, 805, 774, 743, 712, 681, 650, 619, 588, 557, 526, 495, 464, 433, + 402, 371, 340, 309, 278, 247, 216, 185, 154, 123, 92, 61, 30, 31, 62, 93, 124, 155, 186, 217, 248, 279, 310, 341, 372, 403, 434, 465, 496, 527, 558, 589, + 620, 651, 682, 713, 744, 775, 806, 837, 868, 899, 930, 961, 992, 993, 962, 931, 900, 869, 838, 807, 776, 745, 714, 683, 652, 621, 590, 559, 528, 497, 466, + 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 95, 126, 157, 188, 219, 250, 281, 312, 343, 374, 405, 436, 467, 498, 529, 560, 591, 622, + 653, 684, 715, 746, 777, 808, 839, 870, 901, 932, 963, 994, 995, 964, 933, 902, 871, 840, 809, 778, 747, 716, 685, 654, 623, 592, 561, 530, 499, 468, 437, + 406, 375, 344, 313, 282, 251, 220, 189, 158, 127, 159, 190, 221, 252, 283, 314, 345, 376, 407, 438, 469, 500, 531, 562, 593, 624, 655, 686, 717, 748, 779, + 810, 841, 872, 903, 934, 965, 996, 997, 966, 935, 904, 873, 842, 811, 780, 749, 718, 687, 656, 625, 594, 563, 532, 501, 470, 439, 408, 377, 346, 315, 284, + 253, 222, 191, 223, 254, 285, 316, 347, 378, 409, 440, 471, 502, 533, 564, 595, 626, 657, 688, 719, 750, 781, 812, 843, 874, 905, 936, 967, 998, 999, 968, + 937, 906, 875, 844, 813, 782, 751, 720, 689, 658, 627, 596, 565, 534, 503, 472, 441, 410, 379, 348, 317, 286, 255, 287, 318, 349, 380, 411, 442, 473, 504, + 535, 566, 597, 628, 659, 690, 721, 752, 783, 814, 845, 876, 907, 938, 969, 1000, 1001, 970, 939, 908, 877, 846, 815, 784, 753, 722, 691, 660, 629, 598, + 567, 536, 505, 474, 443, 412, 381, 350, 319, 351, 382, 413, 444, 475, 506, 537, 568, 599, 630, 661, 692, 723, 754, 785, 816, 847, 878, 909, 940, 971, 1002, + 1003, 972, 941, 910, 879, 848, 817, 786, 755, 724, 693, 662, 631, 600, 569, 538, 507, 476, 445, 414, 383, 415, 446, 477, 508, 539, 570, 601, 632, 663, 694, + 725, 756, 787, 818, 849, 880, 911, 942, 973, 1004, 1005, 974, 943, 912, 881, 850, 819, 788, 757, 726, 695, 664, 633, 602, 571, 540, 509, 478, 447, 479, + 510, 541, 572, 603, 634, 665, 696, 727, 758, 789, 820, 851, 882, 913, 944, 975, 1006, 1007, 976, 945, 914, 883, 852, 821, 790, 759, 728, 697, 666, 635, + 604, 573, 542, 511, 543, 574, 605, 636, 667, 698, 729, 760, 791, 822, 853, 884, 915, 946, 977, 1008, 1009, 978, 947, 916, 885, 854, 823, 792, 761, 730, + 699, 668, 637, 606, 575, 607, 638, 669, 700, 731, 762, 793, 824, 855, 886, 917, 948, 979, 1010, 1011, 980, 949, 918, 887, 856, 825, 794, 763, 732, 701, + 670, 639, 671, 702, 733, 764, 795, 826, 857, 888, 919, 950, 981, 1012, 1013, 982, 951, 920, 889, 858, 827, 796, 765, 734, 703, 735, 766, 797, 828, 859, + 890, 921, 952, 983, 1014, 1015, 984, 953, 922, 891, 860, 829, 798, 767, 799, 830, 861, 892, 923, 954, 985, 1016, 1017, 986, 955, 924, 893, 862, 831, 863, + 894, 925, 956, 987, 1018, 1019, 988, 957, 926, 895, 927, 958, 989, 1020, 1021, 990, 959, 991, 1022, 1023, +]; + +pub static DEFAULT_SCAN_4X4: [u16; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; + +pub static DEFAULT_SCAN_8X8: [u16; 64] = [ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, + 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, +]; + +pub static DEFAULT_SINGLE_REF_CDF: [[[u16; 3]; 6]; 3] = [ + [[4897, 32768, 0], [1555, 32768, 0], [4236, 32768, 0], [8650, 32768, 0], [904, 32768, 0], [1444, 32768, 0]], + [[16973, 32768, 0], [16751, 32768, 0], [19647, 32768, 0], [24773, 32768, 0], [11014, 32768, 0], [15087, 32768, 0]], + [[29744, 32768, 0], [30279, 32768, 0], [31194, 32768, 0], [31895, 32768, 0], [26875, 32768, 0], [30304, 32768, 0]], +]; + +pub static DEFAULT_SKIP_CDF: [[u16; 3]; 3] = [[31671, 32768, 0], [16515, 32768, 0], [4576, 32768, 0]]; + +pub static DEFAULT_TXB_SKIP_CDF: [[[[u16; 3]; 13]; 5]; 4] = [ + [ + [ + [31849, 32768, 0], + [5892, 32768, 0], + [12112, 32768, 0], + [21935, 32768, 0], + [20289, 32768, 0], + [27473, 32768, 0], + [32487, 32768, 0], + [7654, 32768, 0], + [19473, 32768, 0], + [29984, 32768, 0], + [9961, 32768, 0], + [30242, 32768, 0], + [32117, 32768, 0], + ], + [ + [31548, 32768, 0], + [1549, 32768, 0], + [10130, 32768, 0], + [16656, 32768, 0], + [18591, 32768, 0], + [26308, 32768, 0], + [32537, 32768, 0], + [5403, 32768, 0], + [18096, 32768, 0], + [30003, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [29957, 32768, 0], + [5391, 32768, 0], + [18039, 32768, 0], + [23566, 32768, 0], + [22431, 32768, 0], + [25822, 32768, 0], + [32197, 32768, 0], + [3778, 32768, 0], + [15336, 32768, 0], + [28981, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [17920, 32768, 0], + [1818, 32768, 0], + [7282, 32768, 0], + [25273, 32768, 0], + [10923, 32768, 0], + [31554, 32768, 0], + [32624, 32768, 0], + [1366, 32768, 0], + [15628, 32768, 0], + [30462, 32768, 0], + [146, 32768, 0], + [5132, 32768, 0], + [31657, 32768, 0], + ], + [ + [6308, 32768, 0], + [117, 32768, 0], + [1638, 32768, 0], + [2161, 32768, 0], + [16384, 32768, 0], + [10923, 32768, 0], + [30247, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [30371, 32768, 0], + [7570, 32768, 0], + [13155, 32768, 0], + [20751, 32768, 0], + [20969, 32768, 0], + [27067, 32768, 0], + [32013, 32768, 0], + [5495, 32768, 0], + [17942, 32768, 0], + [28280, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31782, 32768, 0], + [1836, 32768, 0], + [10689, 32768, 0], + [17604, 32768, 0], + [21622, 32768, 0], + [27518, 32768, 0], + [32399, 32768, 0], + [4419, 32768, 0], + [16294, 32768, 0], + [28345, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31901, 32768, 0], + [10311, 32768, 0], + [18047, 32768, 0], + [24806, 32768, 0], + [23288, 32768, 0], + [27914, 32768, 0], + [32296, 32768, 0], + [4215, 32768, 0], + [15756, 32768, 0], + [28341, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [26726, 32768, 0], + [1045, 32768, 0], + [11703, 32768, 0], + [20590, 32768, 0], + [18554, 32768, 0], + [25970, 32768, 0], + [31938, 32768, 0], + [5583, 32768, 0], + [21313, 32768, 0], + [29390, 32768, 0], + [641, 32768, 0], + [22265, 32768, 0], + [31452, 32768, 0], + ], + [ + [26584, 32768, 0], + [188, 32768, 0], + [8847, 32768, 0], + [24519, 32768, 0], + [22938, 32768, 0], + [30583, 32768, 0], + [32608, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [29614, 32768, 0], + [9068, 32768, 0], + [12924, 32768, 0], + [19538, 32768, 0], + [17737, 32768, 0], + [24619, 32768, 0], + [30642, 32768, 0], + [4119, 32768, 0], + [16026, 32768, 0], + [25657, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31957, 32768, 0], + [3230, 32768, 0], + [11153, 32768, 0], + [18123, 32768, 0], + [20143, 32768, 0], + [26536, 32768, 0], + [31986, 32768, 0], + [3050, 32768, 0], + [14603, 32768, 0], + [25155, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [32363, 32768, 0], + [10692, 32768, 0], + [19090, 32768, 0], + [24357, 32768, 0], + [24442, 32768, 0], + [28312, 32768, 0], + [32169, 32768, 0], + [3648, 32768, 0], + [15690, 32768, 0], + [26815, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [30669, 32768, 0], + [3832, 32768, 0], + [11663, 32768, 0], + [18889, 32768, 0], + [19782, 32768, 0], + [23313, 32768, 0], + [31330, 32768, 0], + [5124, 32768, 0], + [18719, 32768, 0], + [28468, 32768, 0], + [3082, 32768, 0], + [20982, 32768, 0], + [29443, 32768, 0], + ], + [ + [28573, 32768, 0], + [3183, 32768, 0], + [17802, 32768, 0], + [25977, 32768, 0], + [26677, 32768, 0], + [27832, 32768, 0], + [32387, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [26887, 32768, 0], + [6729, 32768, 0], + [10361, 32768, 0], + [17442, 32768, 0], + [15045, 32768, 0], + [22478, 32768, 0], + [29072, 32768, 0], + [2713, 32768, 0], + [11861, 32768, 0], + [20773, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31903, 32768, 0], + [2044, 32768, 0], + [7528, 32768, 0], + [14618, 32768, 0], + [16182, 32768, 0], + [24168, 32768, 0], + [31037, 32768, 0], + [2786, 32768, 0], + [11194, 32768, 0], + [20155, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [32510, 32768, 0], + [8430, 32768, 0], + [17318, 32768, 0], + [24154, 32768, 0], + [23674, 32768, 0], + [28789, 32768, 0], + [32139, 32768, 0], + [3440, 32768, 0], + [13117, 32768, 0], + [22702, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31671, 32768, 0], + [2056, 32768, 0], + [11746, 32768, 0], + [16852, 32768, 0], + [18635, 32768, 0], + [24715, 32768, 0], + [31484, 32768, 0], + [4656, 32768, 0], + [16074, 32768, 0], + [24704, 32768, 0], + [1806, 32768, 0], + [14645, 32768, 0], + [25336, 32768, 0], + ], + [ + [31539, 32768, 0], + [8433, 32768, 0], + [20576, 32768, 0], + [27904, 32768, 0], + [27852, 32768, 0], + [30026, 32768, 0], + [32441, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], +]; + +pub static DEFAULT_UV_MODE_CFL_ALLOWED_CDF: [[u16; 15]; 13] = [ + [10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455, 24212, 32768, 0], + [4532, 19780, 20057, 20215, 20428, 21071, 21199, 21451, 22099, 24228, 24693, 27032, 29472, 32768, 0], + [5273, 5379, 20177, 20270, 20385, 20439, 20949, 21695, 21774, 23138, 24256, 24703, 26679, 32768, 0], + [6740, 7167, 7662, 14152, 14536, 14785, 15034, 16741, 18371, 21520, 22206, 23389, 24182, 32768, 0], + [4987, 5368, 5928, 6068, 19114, 20315, 21857, 22253, 22411, 24911, 25380, 26027, 26376, 32768, 0], + [5370, 6889, 7247, 7393, 9498, 21114, 21402, 21753, 21981, 24780, 25386, 26517, 27176, 32768, 0], + [4816, 4961, 7204, 7326, 8765, 8930, 20169, 20682, 20803, 23188, 23763, 24455, 24940, 32768, 0], + [6608, 6740, 8529, 9049, 9257, 9356, 9735, 18827, 19059, 22336, 23204, 23964, 24793, 32768, 0], + [5998, 7419, 7781, 8933, 9255, 9549, 9753, 10417, 18898, 22494, 23139, 24764, 25989, 32768, 0], + [10660, 11298, 12550, 12957, 13322, 13624, 14040, 15004, 15534, 20714, 21789, 23443, 24861, 32768, 0], + [10522, 11530, 12552, 12963, 13378, 13779, 14245, 15235, 15902, 20102, 22696, 23774, 25838, 32768, 0], + [10099, 10691, 12639, 13049, 13386, 13665, 14125, 15163, 15636, 19676, 20474, 23519, 25208, 32768, 0], + [3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048, 32768, 0], +]; + +pub static DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF: [[u16; 14]; 13] = [ + [22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961, 32768, 0], + [9513, 26881, 26973, 27046, 27118, 27664, 27739, 27824, 28359, 29505, 29800, 31796, 32768, 0], + [9845, 9915, 28663, 28704, 28757, 28780, 29198, 29822, 29854, 30764, 31777, 32029, 32768, 0], + [13639, 13897, 14171, 25331, 25606, 25727, 25953, 27148, 28577, 30612, 31355, 32493, 32768, 0], + [9764, 9835, 9930, 9954, 25386, 27053, 27958, 28148, 28243, 31101, 31744, 32363, 32768, 0], + [11825, 13589, 13677, 13720, 15048, 29213, 29301, 29458, 29711, 31161, 31441, 32550, 32768, 0], + [14175, 14399, 16608, 16821, 17718, 17775, 28551, 30200, 30245, 31837, 32342, 32667, 32768, 0], + [12885, 13038, 14978, 15590, 15673, 15748, 16176, 29128, 29267, 30643, 31961, 32461, 32768, 0], + [12026, 13661, 13874, 15305, 15490, 15726, 15995, 16273, 28443, 30388, 30767, 32416, 32768, 0], + [19052, 19840, 20579, 20916, 21150, 21467, 21885, 22719, 23174, 28861, 30379, 32175, 32768, 0], + [18627, 19649, 20974, 21219, 21492, 21816, 22199, 23119, 23527, 27053, 31397, 32148, 32768, 0], + [17026, 19004, 19997, 20339, 20586, 21103, 21349, 21907, 22482, 25896, 26541, 31819, 32768, 0], + [12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606, 32768, 0], +]; + +pub static DEFAULT_Y_MODE_CDF: [[u16; 14]; 4] = [ + [22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349, 32768, 0], + [18673, 19845, 22631, 23318, 23950, 24649, 25527, 27364, 28152, 29701, 29984, 30852, 32768, 0], + [19770, 20979, 23396, 23939, 24241, 24654, 25136, 27073, 27830, 29360, 29730, 30659, 32768, 0], + [20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049, 32768, 0], +]; + +pub static DEFAULT_ZERO_MV_CDF: [[u16; 3]; 2] = [[2175, 32768, 0], [1054, 32768, 0]]; + +pub static INTRA_MODE_CONTEXT: [u8; 13] = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; + +pub static MAG_REF_OFFSET_WITH_TX_CLASS: [[[u8; 2]; 3]; 3] = [[[0, 1], [1, 0], [1, 1]], [[0, 1], [1, 0], [0, 2]], [[0, 1], [1, 0], [2, 0]]]; + +pub static MAX_TX_SIZE_RECT: [u8; 22] = [0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18]; + +pub static MI_HEIGHT_LOG2: [u8; 22] = [0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 2, 0, 3, 1, 4, 2]; + +pub static MI_WIDTH_LOG2: [u8; 22] = [0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 0, 2, 1, 3, 2, 4]; + +pub static MODE_TO_TXFM: [u8; 14] = [0, 1, 2, 0, 3, 1, 2, 2, 1, 3, 1, 2, 3, 0]; + +pub static NUM_4X4_BLOCKS_HIGH: [u8; 22] = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4]; + +pub static NUM_4X4_BLOCKS_WIDE: [u8; 22] = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16]; + +pub static PARTITION_SUBSIZE: [[u8; 22]; 10] = [ + [0, 22, 22, 3, 22, 22, 6, 22, 22, 9, 22, 22, 12, 22, 22, 15, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 2, 22, 22, 5, 22, 22, 8, 22, 22, 11, 22, 22, 14, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 1, 22, 22, 4, 22, 22, 7, 22, 22, 10, 22, 22, 13, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 0, 22, 22, 3, 22, 22, 6, 22, 22, 9, 22, 22, 12, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 2, 22, 22, 5, 22, 22, 8, 22, 22, 11, 22, 22, 14, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 2, 22, 22, 5, 22, 22, 8, 22, 22, 11, 22, 22, 14, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 1, 22, 22, 4, 22, 22, 7, 22, 22, 10, 22, 22, 13, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 1, 22, 22, 4, 22, 22, 7, 22, 22, 10, 22, 22, 13, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 22, 22, 22, 17, 22, 22, 19, 22, 22, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 22, 22, 22, 16, 22, 22, 18, 22, 22, 20, 22, 22, 22, 22, 22, 22, 22, 22, 22], +]; + +pub static SIG_REF_DIFF_OFFSET: [[[u8; 2]; 5]; 3] = + [[[0, 1], [1, 0], [1, 1], [0, 2], [2, 0]], [[0, 1], [1, 0], [0, 2], [0, 3], [0, 4]], [[0, 1], [1, 0], [2, 0], [3, 0], [4, 0]]]; + +pub static SIZE_GROUP: [u8; 22] = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2]; + +pub static SM_WEIGHTS_TX_16X16: [u8; 16] = [255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16]; + +pub static SM_WEIGHTS_TX_32X32: [u8; 32] = + [255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74, 66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8]; + +pub static SM_WEIGHTS_TX_4X4: [u8; 4] = [255, 149, 85, 64]; + +pub static SM_WEIGHTS_TX_8X8: [u8; 8] = [255, 197, 146, 105, 73, 50, 37, 32]; + +pub static SUBPEL_FILTERS: [[[i16; 8]; 16]; 6] = [ + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 2, -6, 126, 8, -2, 0, 0], + [0, 2, -10, 122, 18, -4, 0, 0], + [0, 2, -12, 116, 28, -8, 2, 0], + [0, 2, -14, 110, 38, -10, 2, 0], + [0, 2, -14, 102, 48, -12, 2, 0], + [0, 2, -16, 94, 58, -12, 2, 0], + [0, 2, -14, 84, 66, -12, 2, 0], + [0, 2, -14, 76, 76, -14, 2, 0], + [0, 2, -12, 66, 84, -14, 2, 0], + [0, 2, -12, 58, 94, -16, 2, 0], + [0, 2, -12, 48, 102, -14, 2, 0], + [0, 2, -10, 38, 110, -14, 2, 0], + [0, 2, -8, 28, 116, -12, 2, 0], + [0, 0, -4, 18, 122, -10, 2, 0], + [0, 0, -2, 8, 126, -6, 2, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 2, 28, 62, 34, 2, 0, 0], + [0, 0, 26, 62, 36, 4, 0, 0], + [0, 0, 22, 62, 40, 4, 0, 0], + [0, 0, 20, 60, 42, 6, 0, 0], + [0, 0, 18, 58, 44, 8, 0, 0], + [0, 0, 16, 56, 46, 10, 0, 0], + [0, -2, 16, 54, 48, 12, 0, 0], + [0, -2, 14, 52, 52, 14, -2, 0], + [0, 0, 12, 48, 54, 16, -2, 0], + [0, 0, 10, 46, 56, 16, 0, 0], + [0, 0, 8, 44, 58, 18, 0, 0], + [0, 0, 6, 42, 60, 20, 0, 0], + [0, 0, 4, 40, 62, 22, 0, 0], + [0, 0, 4, 36, 62, 26, 0, 0], + [0, 0, 2, 34, 62, 28, 2, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [-2, 2, -6, 126, 8, -2, 2, 0], + [-2, 6, -12, 124, 16, -6, 4, -2], + [-2, 8, -18, 120, 26, -10, 6, -2], + [-4, 10, -22, 116, 38, -14, 6, -2], + [-4, 10, -22, 108, 48, -18, 8, -2], + [-4, 10, -24, 100, 60, -20, 8, -2], + [-4, 10, -24, 90, 70, -22, 10, -2], + [-4, 12, -24, 80, 80, -24, 12, -4], + [-2, 10, -22, 70, 90, -24, 10, -4], + [-2, 8, -20, 60, 100, -24, 10, -4], + [-2, 8, -18, 48, 108, -22, 10, -4], + [-2, 6, -14, 38, 116, -22, 10, -4], + [-2, 6, -10, 26, 120, -18, 8, -2], + [-2, 4, -6, 16, 124, -12, 6, -2], + [0, 2, -2, 8, 126, -6, 2, -2], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 0, 0, 120, 8, 0, 0, 0], + [0, 0, 0, 112, 16, 0, 0, 0], + [0, 0, 0, 104, 24, 0, 0, 0], + [0, 0, 0, 96, 32, 0, 0, 0], + [0, 0, 0, 88, 40, 0, 0, 0], + [0, 0, 0, 80, 48, 0, 0, 0], + [0, 0, 0, 72, 56, 0, 0, 0], + [0, 0, 0, 64, 64, 0, 0, 0], + [0, 0, 0, 56, 72, 0, 0, 0], + [0, 0, 0, 48, 80, 0, 0, 0], + [0, 0, 0, 40, 88, 0, 0, 0], + [0, 0, 0, 32, 96, 0, 0, 0], + [0, 0, 0, 24, 104, 0, 0, 0], + [0, 0, 0, 16, 112, 0, 0, 0], + [0, 0, 0, 8, 120, 0, 0, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 0, -4, 126, 8, -2, 0, 0], + [0, 0, -8, 122, 18, -4, 0, 0], + [0, 0, -10, 116, 28, -6, 0, 0], + [0, 0, -12, 110, 38, -8, 0, 0], + [0, 0, -12, 102, 48, -10, 0, 0], + [0, 0, -14, 94, 58, -10, 0, 0], + [0, 0, -12, 84, 66, -10, 0, 0], + [0, 0, -12, 76, 76, -12, 0, 0], + [0, 0, -10, 66, 84, -12, 0, 0], + [0, 0, -10, 58, 94, -14, 0, 0], + [0, 0, -10, 48, 102, -12, 0, 0], + [0, 0, -8, 38, 110, -12, 0, 0], + [0, 0, -6, 28, 116, -10, 0, 0], + [0, 0, -4, 18, 122, -8, 0, 0], + [0, 0, -2, 8, 126, -4, 0, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 0, 30, 62, 34, 2, 0, 0], + [0, 0, 26, 62, 36, 4, 0, 0], + [0, 0, 22, 62, 40, 4, 0, 0], + [0, 0, 20, 60, 42, 6, 0, 0], + [0, 0, 18, 58, 44, 8, 0, 0], + [0, 0, 16, 56, 46, 10, 0, 0], + [0, 0, 14, 54, 48, 12, 0, 0], + [0, 0, 12, 52, 52, 12, 0, 0], + [0, 0, 12, 48, 54, 14, 0, 0], + [0, 0, 10, 46, 56, 16, 0, 0], + [0, 0, 8, 44, 58, 18, 0, 0], + [0, 0, 6, 42, 60, 20, 0, 0], + [0, 0, 4, 40, 62, 22, 0, 0], + [0, 0, 4, 36, 62, 26, 0, 0], + [0, 0, 2, 34, 62, 30, 0, 0], + ], +]; + +pub static SUBSAMPLED_SIZE: [[[u8; 2]; 2]; 22] = [ + [[0, 0], [0, 0]], + [[1, 0], [22, 0]], + [[2, 22], [0, 0]], + [[3, 2], [1, 0]], + [[4, 3], [22, 1]], + [[5, 22], [3, 2]], + [[6, 5], [4, 3]], + [[7, 6], [22, 4]], + [[8, 22], [6, 5]], + [[9, 8], [7, 6]], + [[10, 9], [22, 7]], + [[11, 22], [9, 8]], + [[12, 11], [10, 9]], + [[13, 12], [22, 10]], + [[14, 22], [12, 11]], + [[15, 14], [13, 12]], + [[16, 1], [22, 1]], + [[17, 22], [2, 2]], + [[18, 4], [22, 16]], + [[19, 22], [5, 17]], + [[20, 7], [22, 18]], + [[21, 22], [8, 19]], +]; + +pub static TRANSFORM_ROW_SHIFT: [u8; 19] = [0, 1, 2, 2, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2]; + +pub static TX_HEIGHT: [u8; 19] = [4, 8, 16, 32, 64, 8, 4, 16, 8, 32, 16, 64, 32, 16, 4, 32, 8, 64, 16]; + +pub static TX_HEIGHT_LOG2: [u8; 19] = [2, 3, 4, 5, 6, 3, 2, 4, 3, 5, 4, 6, 5, 4, 2, 5, 3, 6, 4]; + +pub static TX_SIZE_SQR: [u8; 19] = [0, 1, 2, 3, 4, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2]; + +pub static TX_SIZE_SQR_UP: [u8; 19] = [0, 1, 2, 3, 4, 1, 1, 2, 2, 3, 3, 4, 4, 2, 2, 3, 3, 4, 4]; + +pub static TX_TYPE_IN_SET_INTER: [[u8; 16]; 4] = [ + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], +]; + +pub static TX_TYPE_IN_SET_INTRA: [[u8; 16]; 3] = + [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]]; + +pub static TX_TYPE_INTER_INV_SET3: [u8; 2] = [9, 0]; + +pub static TX_TYPE_INTRA_INV_SET2: [u8; 5] = [9, 0, 3, 1, 2]; + +pub static TX_WIDTH: [u8; 19] = [4, 8, 16, 32, 64, 4, 8, 8, 16, 16, 32, 32, 64, 4, 16, 8, 32, 16, 64]; + +pub static TX_WIDTH_LOG2: [u8; 19] = [2, 3, 4, 5, 6, 2, 3, 3, 4, 4, 5, 5, 6, 2, 4, 3, 5, 4, 6]; diff --git a/crates/av1enc/src/transform.rs b/crates/av1enc/src/transform.rs new file mode 100644 index 00000000..386133b5 --- /dev/null +++ b/crates/av1enc/src/transform.rs @@ -0,0 +1,481 @@ +//! Transforms. The inverse transforms are the normative ones of spec 7.13 (mirrored from +//! filmcraft-av1, so that the encoder reconstructs exactly like a decoder); the forward +//! transforms are floating-point matrices scaled to match them (not normative). + +use std::sync::OnceLock; + +use crate::tables::*; + +#[inline(always)] +fn round2_64(x: i64, n: u32) -> i32 { + if n == 0 { x as i32 } else { ((x + (1i64 << (n - 1))) >> n) as i32 } +} + +#[inline(always)] +pub(crate) fn round2(x: i32, n: u32) -> i32 { + if n == 0 { x } else { (x + (1 << (n - 1))) >> n } +} + +#[inline(always)] +fn cos128(angle: i32) -> i64 { + let a = (angle & 255) as usize; + (match a { + 0..=64 => COS128_LOOKUP[a] as i32, + 65..=128 => -(COS128_LOOKUP[128 - a] as i32), + 129..=192 => -(COS128_LOOKUP[a - 128] as i32), + _ => COS128_LOOKUP[256 - a] as i32, + }) as i64 +} + +#[inline(always)] +fn sin128(angle: i32) -> i64 { + cos128(angle - 64) +} + +fn brev(num_bits: u32, x: usize) -> usize { + let mut t = 0; + for i in 0..num_bits { + let bit = (x >> i) & 1; + t += bit << (num_bits - 1 - i); + } + t +} + +#[inline(always)] +fn bf(t: &mut [i32], a: usize, b: usize, angle: i32, flip: bool) { + let x = t[a] as i64 * cos128(angle) - t[b] as i64 * sin128(angle); + let y = t[a] as i64 * sin128(angle) + t[b] as i64 * cos128(angle); + t[a] = round2_64(x, 12); + t[b] = round2_64(y, 12); + if flip { + t.swap(a, b); + } +} + +#[inline(always)] +fn hd(t: &mut [i32], a: usize, b: usize, flip: bool, r: u32) { + let (a, b) = if flip { (b, a) } else { (a, b) }; + let x = t[a]; + let y = t[b]; + let lo = -(1i32 << (r - 1)); + let hi = (1i32 << (r - 1)) - 1; + t[a] = (x + y).clamp(lo, hi); + t[b] = (x - y).clamp(lo, hi); +} + +/// Inverse DCT of length 2^n, n = 2..=5 (7.13.2.3), in place. +fn inverse_dct(t: &mut [i32], n: u32, r: u32) { + let len = 1usize << n; + let mut copy = [0i32; 32]; + copy[..len].copy_from_slice(&t[..len]); + for i in 0..len { + t[i] = copy[brev(n, i)]; + } + if n >= 5 { + for i in 0..8 { + bf(t, 16 + i, 31 - i, 6 + ((brev(3, 7 - i) as i32) << 3), false); + } + } + if n >= 4 { + for i in 0..4 { + bf(t, 8 + i, 15 - i, 12 + ((brev(2, 3 - i) as i32) << 4), false); + } + } + if n >= 5 { + for i in 0..8 { + hd(t, 16 + 2 * i, 17 + 2 * i, i & 1 == 1, r); + } + } + if n >= 3 { + for i in 0..2 { + bf(t, 4 + i, 7 - i, 56 - 32 * i as i32, false); + } + } + if n >= 4 { + for i in 0..4 { + hd(t, 8 + 2 * i, 9 + 2 * i, i & 1 == 1, r); + } + } + if n >= 5 { + for i in 0..2 { + for j in 0..2 { + bf(t, 30 - 4 * i - j, 17 + 4 * i + j, 24 + ((j as i32) << 6) + (((1 - i) as i32) << 5), true); + } + } + } + for i in 0..2 { + bf(t, 2 * i, 2 * i + 1, 32 + 16 * i as i32, i == 0); + } + if n >= 3 { + for i in 0..2 { + hd(t, 4 + 2 * i, 5 + 2 * i, i == 1, r); + } + } + if n >= 4 { + for i in 0..2 { + bf(t, 14 - i, 9 + i, 48 + 64 * i as i32, true); + } + } + if n >= 5 { + for i in 0..4 { + for j in 0..2 { + hd(t, 16 + 4 * i + j, 19 + 4 * i - j, i & 1 == 1, r); + } + } + } + for i in 0..2 { + hd(t, i, 3 - i, false, r); + } + if n >= 3 { + bf(t, 6, 5, 32, true); + } + if n >= 4 { + for i in 0..2 { + for j in 0..2 { + hd(t, 8 + 4 * i + j, 11 + 4 * i - j, i == 1, r); + } + } + } + if n >= 5 { + for i in 0..4 { + bf(t, 29 - i, 18 + i, 48 + (i as i32 >> 1) * 64, true); + } + } + if n >= 3 { + for i in 0..4 { + hd(t, i, 7 - i, false, r); + } + } + if n >= 4 { + for i in 0..2 { + bf(t, 13 - i, 10 + i, 32, true); + } + } + if n >= 5 { + for i in 0..2 { + for j in 0..4 { + hd(t, 16 + i * 8 + j, 23 + i * 8 - j, i == 1, r); + } + } + } + if n >= 4 { + for i in 0..8 { + hd(t, i, 15 - i, false, r); + } + } + if n >= 5 { + for i in 0..4 { + bf(t, 27 - i, 20 + i, 32, true); + } + } + if n >= 5 { + for i in 0..16 { + hd(t, i, 31 - i, false, r); + } + } +} + +const SINPI_1_9: i64 = 1321; +const SINPI_2_9: i64 = 2482; +const SINPI_3_9: i64 = 3344; +const SINPI_4_9: i64 = 3803; + +fn inverse_adst4(t: &mut [i32]) { + let (t0, t1, t2, t3) = (t[0] as i64, t[1] as i64, t[2] as i64, t[3] as i64); + let mut s = [SINPI_1_9 * t0, SINPI_2_9 * t0, SINPI_3_9 * t1, SINPI_4_9 * t2, SINPI_1_9 * t2, SINPI_2_9 * t3, SINPI_4_9 * t3]; + let a7 = t0 - t2; + let b7 = a7 + t3; + s[0] += s[3]; + s[1] -= s[4]; + s[3] = s[2]; + s[2] = SINPI_3_9 * b7; + s[0] += s[5]; + s[1] -= s[6]; + let x = [s[0] + s[3], s[1] + s[3], s[2], s[0] + s[1] - s[3]]; + for i in 0..4 { + t[i] = round2_64(x[i], 12); + } +} + +fn adst_in_permute(t: &mut [i32], n: u32) { + let n0 = 1usize << n; + let mut copy = [0i32; 16]; + copy[..n0].copy_from_slice(&t[..n0]); + for i in 0..n0 { + let idx = if i & 1 == 1 { i - 1 } else { n0 - i - 1 }; + t[i] = copy[idx]; + } +} + +fn adst_out_permute(t: &mut [i32], n: u32) { + let n0 = 1usize << n; + let mut copy = [0i32; 16]; + copy[..n0].copy_from_slice(&t[..n0]); + for i in 0..n0 { + let a = (i >> 3) & 1; + let b = ((i >> 2) & 1) ^ ((i >> 3) & 1); + let c = ((i >> 1) & 1) ^ ((i >> 2) & 1); + let d = (i & 1) ^ ((i >> 1) & 1); + let idx = ((d << 3) | (c << 2) | (b << 1) | a) >> (4 - n); + t[i] = if i & 1 == 1 { -copy[idx] } else { copy[idx] }; + } +} + +fn inverse_adst8(t: &mut [i32], r: u32) { + adst_in_permute(t, 3); + for i in 0..4 { + bf(t, 2 * i, 2 * i + 1, 60 - 16 * i as i32, true); + } + for i in 0..4 { + hd(t, i, 4 + i, false, r); + } + for i in 0..2 { + bf(t, 4 + 3 * i, 5 + i, 48 - 32 * i as i32, true); + } + for i in 0..2 { + for j in 0..2 { + hd(t, 4 * j + i, 2 + 4 * j + i, false, r); + } + } + for i in 0..2 { + bf(t, 2 + 4 * i, 3 + 4 * i, 32, true); + } + adst_out_permute(t, 3); +} + +fn inverse_adst16(t: &mut [i32], r: u32) { + adst_in_permute(t, 4); + for i in 0..8 { + bf(t, 2 * i, 2 * i + 1, 62 - 8 * i as i32, true); + } + for i in 0..8 { + hd(t, i, 8 + i, false, r); + } + for i in 0..2 { + bf(t, 8 + 2 * i, 9 + 2 * i, 56 - 32 * i as i32, true); + bf(t, 13 + 2 * i, 12 + 2 * i, 8 + 32 * i as i32, true); + } + for i in 0..4 { + for j in 0..2 { + hd(t, 8 * j + i, 4 + 8 * j + i, false, r); + } + } + for i in 0..2 { + for j in 0..2 { + bf(t, 4 + 8 * j + 3 * i, 5 + 8 * j + i, 48 - 32 * i as i32, true); + } + } + for i in 0..2 { + for j in 0..4 { + hd(t, 4 * j + i, 2 + 4 * j + i, false, r); + } + } + for i in 0..4 { + bf(t, 2 + 4 * i, 3 + 4 * i, 32, true); + } + adst_out_permute(t, 4); +} + +/// 1-D transform kind. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(crate) enum Kind { + Dct, + Adst, +} + +/// (vertical / column kind, horizontal / row kind) of a transform type. +pub(crate) fn kinds(tx_type: usize) -> (Kind, Kind) { + match tx_type { + DCT_DCT => (Kind::Dct, Kind::Dct), + ADST_DCT => (Kind::Adst, Kind::Dct), + DCT_ADST => (Kind::Dct, Kind::Adst), + ADST_ADST => (Kind::Adst, Kind::Adst), + _ => unreachable!("unsupported transform type {tx_type}"), + } +} + +fn inverse_1d(t: &mut [i32], kind: Kind, n: u32, r: u32) { + match kind { + Kind::Dct => inverse_dct(t, n, r), + Kind::Adst => match n { + 2 => inverse_adst4(t), + 3 => inverse_adst8(t, r), + _ => inverse_adst16(t, r), + }, + } +} + +/// 2-D inverse transform (7.13.3) of a square `n x n` block (n = 4..=32) of dequantised +/// coefficients (row stride `n`) into `residual` (row stride `n`). +pub(crate) fn inverse_2d(dequant: &[i32], residual: &mut [i32], tx_sz: usize, tx_type: usize, bit_depth: u32) { + let log2 = TX_WIDTH_LOG2[tx_sz] as u32; + let n = 1usize << log2; + let row_shift = TRANSFORM_ROW_SHIFT[tx_sz] as u32; + let row_clamp = bit_depth + 8; + let col_clamp = (bit_depth + 6).max(16); + let (ck, rk) = kinds(tx_type); + let lo = -(1i32 << (col_clamp - 1)); + let hi = (1i32 << (col_clamp - 1)) - 1; + let mut t = [0i32; 32]; + for i in 0..n { + let row = &dequant[i * n..i * n + n]; + if row.iter().all(|&v| v == 0) { + residual[i * n..i * n + n].iter_mut().for_each(|v| *v = 0); + continue; + } + t[..n].copy_from_slice(row); + inverse_1d(&mut t, rk, log2, row_clamp); + for j in 0..n { + residual[i * n + j] = round2(t[j], row_shift).clamp(lo, hi); + } + } + for j in 0..n { + for i in 0..n { + t[i] = residual[i * n + j]; + } + inverse_1d(&mut t, ck, log2, col_clamp); + for i in 0..n { + residual[i * n + j] = round2(t[i], 4); + } + } +} + +/// Orthonormal forward basis matrices `[k][i]` (row k = basis function k), sizes 4..=32. +struct Bases { + dct: [Vec; 4], + adst: [Vec; 3], + /// The same matrices transposed (`[i][k]`). + dct_t: [Vec; 4], + adst_t: [Vec; 3], +} + +fn transpose(m: &[f32]) -> Vec { + let n = (m.len() as f64).sqrt() as usize; + let mut t = vec![0f32; n * n]; + for k in 0..n { + for i in 0..n { + t[i * n + k] = m[k * n + i]; + } + } + t +} + +fn bases() -> &'static Bases { + static B: OnceLock = OnceLock::new(); + B.get_or_init(|| { + let pi = std::f64::consts::PI; + let dct = std::array::from_fn(|l| { + let n = 4usize << l; + let mut m = vec![0f32; n * n]; + for k in 0..n { + let c = if k == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 }; + for i in 0..n { + m[k * n + i] = (c * (2.0 / n as f64).sqrt() * (pi * (2 * i + 1) as f64 * k as f64 / (2 * n) as f64).cos()) as f32; + } + } + m + }); + let adst = std::array::from_fn(|l| { + let n = 4usize << l; + let mut m = vec![0f32; n * n]; + for k in 0..n { + for i in 0..n { + m[k * n + i] = if n == 4 { + // the AV1 4-point ADST (sin(pi (i + 1)(2k + 1) / 9) basis) + (2.0 / 3.0 * (pi * ((i + 1) * (2 * k + 1)) as f64 / 9.0).sin()) as f32 + } else { + ((2.0 / n as f64).sqrt() * (pi * ((2 * i + 1) * (2 * k + 1)) as f64 / (4 * n) as f64).sin()) as f32 + }; + } + } + m + }); + let dct_t = std::array::from_fn(|l| transpose(&dct[l])); + let adst_t = std::array::from_fn(|l| transpose(&adst[l])); + Bases { dct, adst, dct_t, adst_t } + }) +} + +fn basis_t(kind: Kind, n: usize) -> &'static [f32] { + let l = n.trailing_zeros() as usize - 2; + match kind { + Kind::Dct => &bases().dct_t[l], + Kind::Adst => &bases().adst_t[l], + } +} + +fn basis(kind: Kind, n: usize) -> &'static [f32] { + let l = n.trailing_zeros() as usize - 2; + match kind { + Kind::Dct => &bases().dct[l], + Kind::Adst => &bases().adst[l], + } +} + +/// Forward 2-D transform of the `n x n` residual (row stride `n`) into orthonormal-domain +/// coefficients scaled by 8 (so that `level = out / q` with the spec's dequantiser). +pub(crate) fn forward_2d(residual: &[i32], out: &mut [f32], n: usize, tx_type: usize) { + let (ck, rk) = kinds(tx_type); + let bc = basis(ck, n); + let brt = basis_t(rk, n); + // tmp[i][k] = sum_j residual[i][j] * br[k][j] (horizontal; written as row updates so that + // the inner loops vectorise) + let mut tmp = [0f32; 32 * 32]; + for i in 0..n { + let t = &mut tmp[i * n..i * n + n]; + for j in 0..n { + let r = residual[i * n + j]; + if r == 0 { + continue; + } + let r = r as f32; + for (o, &b) in t.iter_mut().zip(&brt[j * n..j * n + n]) { + *o += r * b; + } + } + } + // out[k][j] = 8 * sum_i bc[k][i] * tmp[i][j] (vertical) + out[..n * n].iter_mut().for_each(|v| *v = 0.0); + for k in 0..n { + let o = &mut out[k * n..k * n + n]; + for i in 0..n { + let b = 8.0 * bc[k * n + i]; + for (v, &t) in o.iter_mut().zip(&tmp[i * n..i * n + n]) { + *v += b * t; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// forward (float) then the normative inverse reproduces the residual: the forward basis + /// and scale match the decoder's inverse transforms for every supported size and type. + #[test] + fn forward_matches_inverse() { + let mut seed = 99u32; + let mut rnd = || { + seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12345); + ((seed >> 16) % 201) as i32 - 100 + }; + for (tx, n) in [(TX_4X4, 4), (TX_8X8, 8), (TX_16X16, 16), (TX_32X32, 32)] { + let denom = if tx == TX_32X32 { 2.0 } else { 1.0 }; + for tx_type in [DCT_DCT, ADST_DCT, DCT_ADST, ADST_ADST] { + if n == 32 && tx_type != DCT_DCT { + continue; + } + let res: Vec = (0..n * n).map(|_| rnd()).collect(); + let mut coef = vec![0f32; n * n]; + forward_2d(&res, &mut coef, n, tx_type); + // q = 8 / denom keeps full precision: dequant = level * q / denom = coef + let deq: Vec = coef.iter().map(|&c| (c / denom).round() as i32).collect(); + let mut out = vec![0i32; n * n]; + inverse_2d(&deq, &mut out, tx, tx_type, 8); + let err: i32 = res.iter().zip(&out).map(|(a, b)| (a - b).abs()).max().unwrap(); + assert!(err <= 1, "tx {tx} type {tx_type}: max error {err}"); + } + } + } +} diff --git a/crates/av1enc/src/writer.rs b/crates/av1enc/src/writer.rs new file mode 100644 index 00000000..903492b1 --- /dev/null +++ b/crates/av1enc/src/writer.rs @@ -0,0 +1,1020 @@ +//! Tile syntax writer (spec 5.11 with the 8.3 CDF selection). It keeps the same per-4x4 mode +//! info, coefficient contexts and adaptive CDFs as a decoder and writes the decisions taken by +//! `decide.rs` as symbols. The context derivations and the motion vector prediction process +//! (7.10.2) mirror the decoder exactly, since symbols and coded motion vector differences +//! depend on them. + +use crate::cdf::Cdfs; +use crate::decide::{FrameGeom, Leaf, Node, intra_uv_tx_type, scan_for, tx_in_set, tx_set, tx_size_for}; +use crate::loopfilter::LfInfo; +use crate::symbol::SymbolWriter; +use crate::tables::*; + +type MvP = [i32; 2]; + +/// Mode info per 4x4 (MiSizes, YModes, RefFrames, Mvs, ...). +struct Mi { + cols: usize, + mi_size: Vec, + y_mode: Vec, + ref_frame: Vec<[i8; 2]>, + mv: Vec, + skip: Vec, + is_inter: Vec, + tx_type: Vec, + written: Vec, +} + +impl Mi { + #[inline(always)] + fn idx(&self, r: usize, c: usize) -> usize { + r * self.cols + c + } +} + +/// Output of find_mv_stack. +#[derive(Default)] +struct MvStack { + ref_stack_mv: [MvP; MAX_REF_MV_STACK_SIZE + 1], + weight_stack: [u32; MAX_REF_MV_STACK_SIZE + 1], + num_mv_found: usize, + new_mv_count: usize, + found_match: bool, + close_matches: usize, + total_matches: usize, + new_mv_context: usize, + ref_mv_context: usize, + zero_mv_context: usize, + drl_ctx_stack: [usize; MAX_REF_MV_STACK_SIZE + 1], +} + +pub(crate) struct TileWriter<'a> { + g: &'a FrameGeom, + intra_only: bool, + qidx: u8, + sw: SymbolWriter, + cdf: Box, + kf_y_mode: [[[u16; 14]; 5]; 5], + mi: Mi, + above_level: [Vec; 3], + above_dc: [Vec; 3], + left_level: [Vec; 3], + left_dc: [Vec; 3], + /// LoopfilterTxSizes per plane (row stride mi_cols + 32). + lf_tx: [Vec; 3], + // current block + r: usize, + c: usize, + bsize: usize, + avail_u: bool, + avail_l: bool, + mvs: MvStack, +} + +fn block_size_px(bs: usize) -> usize { + 4 * NUM_4X4_BLOCKS_WIDE[bs] as usize +} + +fn is_directional(mode: usize) -> bool { + (V_PRED..=D67_PRED).contains(&mode) +} + +impl<'a> TileWriter<'a> { + /// `init`: the CDFs saved by the primary reference frame (load_cdfs), or None for the + /// defaults (init_non_coeff_cdfs / init_coeff_cdfs). + pub fn new(g: &'a FrameGeom, intra_only: bool, qidx: u8, init: Option<&Cdfs>) -> Self { + let n = g.mi_cols * g.mi_rows; + TileWriter { + g, + intra_only, + qidx, + sw: SymbolWriter::new(false), + cdf: match init { + Some(c) => { + let mut c = Box::new(c.clone()); + c.clear_counts(); + c + } + None => Cdfs::new(qidx as u32), + }, + kf_y_mode: DEFAULT_INTRA_FRAME_Y_MODE_CDF, + mi: Mi { + cols: g.mi_cols, + mi_size: vec![0; n], + y_mode: vec![0; n], + ref_frame: vec![[0, -1]; n], + mv: vec![[0, 0]; n], + skip: vec![false; n], + is_inter: vec![false; n], + tx_type: vec![0; n], + written: vec![false; n], + }, + above_level: std::array::from_fn(|_| vec![0; g.mi_cols + 64]), + above_dc: std::array::from_fn(|_| vec![0; g.mi_cols + 64]), + left_level: std::array::from_fn(|_| vec![0; g.mi_rows + 64]), + left_dc: std::array::from_fn(|_| vec![0; g.mi_rows + 64]), + lf_tx: std::array::from_fn(|_| vec![0; (g.mi_cols + 32) * (g.mi_rows + 32)]), + r: 0, + c: 0, + bsize: 0, + avail_u: false, + avail_l: false, + mvs: MvStack::default(), + } + } + + /// The tile data and the final CDFs (saved for the frame-end CDF update). + pub fn finish(self) -> (Vec, Box) { + (self.sw.finish(), self.cdf) + } + + /// The mode info the loop filter needs. + pub fn lf_info(&self) -> LfInfo<'_> { + LfInfo { + mi_cols: self.g.mi_cols, + mi_rows: self.g.mi_rows, + width: self.g.width, + height: self.g.height, + mi_size: &self.mi.mi_size, + skip: &self.mi.skip, + is_inter: &self.mi.is_inter, + tx: [&self.lf_tx[0], &self.lf_tx[1], &self.lf_tx[2]], + } + } + + /// Start of a superblock row (clear_left_context). + pub fn start_sb_row(&mut self) { + for p in 0..3 { + self.left_level[p].iter_mut().for_each(|v| *v = 0); + self.left_dc[p].iter_mut().for_each(|v| *v = 0); + } + } + + pub fn superblock(&mut self, r: usize, c: usize, node: &Node) { + self.partition(r, c, BLOCK_64X64, node); + } + + fn partition(&mut self, r: usize, c: usize, bsize: usize, node: &Node) { + let (mi_rows, mi_cols) = (self.g.mi_rows, self.g.mi_cols); + if r >= mi_rows || c >= mi_cols { + return; + } + let avail_u = r > 0; + let avail_l = c > 0; + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let half = n4 >> 1; + let has_rows = r + half < mi_rows; + let has_cols = c + half < mi_cols; + let split = matches!(node, Node::Split(_)); + let bsl = MI_WIDTH_LOG2[bsize] as usize; + let above = avail_u && (MI_WIDTH_LOG2[self.mi.mi_size[self.mi.idx(r - 1, c)] as usize] as usize) < bsl; + let left = avail_l && (MI_HEIGHT_LOG2[self.mi.mi_size[self.mi.idx(r, c - 1)] as usize] as usize) < bsl; + let ctx = left as usize * 2 + above as usize; + let partition = if split { PARTITION_SPLIT } else { PARTITION_NONE }; + if has_rows && has_cols { + let cdf: &mut [u16] = match bsl { + 1 => &mut self.cdf.partition_w8[ctx], + 2 => &mut self.cdf.partition_w16[ctx], + 3 => &mut self.cdf.partition_w32[ctx], + _ => &mut self.cdf.partition_w64[ctx], + }; + self.sw.symbol(partition, cdf); + } else if has_cols || has_rows { + assert!(split, "blocks crossing the frame edge are split"); + let pcdf: Vec = match bsl { + 2 => self.cdf.partition_w16[ctx].to_vec(), + 3 => self.cdf.partition_w32[ctx].to_vec(), + _ => self.cdf.partition_w64[ctx].to_vec(), + }; + let pr = |k: usize| pcdf[k] as i32 - if k == 0 { 0 } else { pcdf[k - 1] as i32 }; + let psum = if has_cols { + // split_or_horz + pr(PARTITION_VERT) + pr(PARTITION_SPLIT) + pr(PARTITION_HORZ_A) + pr(PARTITION_VERT_A) + pr(PARTITION_VERT_B) + pr(PARTITION_VERT_4) + } else { + // split_or_vert + pr(PARTITION_HORZ) + pr(PARTITION_SPLIT) + pr(PARTITION_HORZ_A) + pr(PARTITION_HORZ_B) + pr(PARTITION_VERT_A) + pr(PARTITION_HORZ_4) + }; + let cdf = [((1i32 << 15) - psum) as u16, 1 << 15, 0]; + self.sw.symbol_fixed(1, &cdf); + } else { + assert!(split); + } + match node { + Node::Split(ch) => { + let sub = PARTITION_SUBSIZE[PARTITION_SPLIT][bsize] as usize; + self.partition(r, c, sub, &ch[0]); + self.partition(r, c + half, sub, &ch[1]); + self.partition(r + half, c, sub, &ch[2]); + self.partition(r + half, c + half, sub, &ch[3]); + } + Node::Leaf(leaf) => { + debug_assert_eq!(leaf.bsize, bsize); + self.block(r, c, leaf); + } + Node::Out => unreachable!("decided block inside the frame"), + } + } + + fn block(&mut self, r: usize, c: usize, b: &Leaf) { + self.r = r; + self.c = c; + self.bsize = b.bsize; + self.avail_u = r > 0; + self.avail_l = c > 0; + let bsize = b.bsize; + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + // skip + let mut ctx = 0; + if self.avail_u { + ctx += self.mi.skip[self.mi.idx(r - 1, c)] as usize; + } + if self.avail_l { + ctx += self.mi.skip[self.mi.idx(r, c - 1)] as usize; + } + let mut y_mode = b.y_mode; + if self.intra_only { + self.sw.symbol(b.skip as usize, &mut self.cdf.skip[ctx]); + let above = if self.avail_u { self.mi.y_mode[self.mi.idx(r - 1, c)] as usize } else { DC_PRED }; + let left = if self.avail_l { self.mi.y_mode[self.mi.idx(r, c - 1)] as usize } else { DC_PRED }; + let actx = INTRA_MODE_CONTEXT[above] as usize; + let lctx = INTRA_MODE_CONTEXT[left] as usize; + self.sw.symbol(b.y_mode, &mut self.kf_y_mode[actx][lctx]); + self.intra_angle_and_uv(b); + } else { + self.sw.symbol(b.skip as usize, &mut self.cdf.skip[ctx]); + // is_inter + let left = if self.avail_l { self.mi.ref_frame[self.mi.idx(r, c - 1)] } else { [INTRA_FRAME as i8, -1] }; + let above = if self.avail_u { self.mi.ref_frame[self.mi.idx(r - 1, c)] } else { [INTRA_FRAME as i8, -1] }; + let left_intra = left[0] <= INTRA_FRAME as i8; + let above_intra = above[0] <= INTRA_FRAME as i8; + let ctx = if self.avail_u && self.avail_l { + if left_intra && above_intra { 3 } else { (left_intra || above_intra) as usize } + } else if self.avail_u || self.avail_l { + 2 * (if self.avail_u { above_intra } else { left_intra }) as usize + } else { + 0 + }; + self.sw.symbol(b.is_inter as usize, &mut self.cdf.is_inter[ctx]); + if b.is_inter { + y_mode = self.inter_block_mode_info(b, left, above); + } else { + let ctx = SIZE_GROUP[bsize] as usize; + self.sw.symbol(b.y_mode, &mut self.cdf.y_mode[ctx]); + self.intra_angle_and_uv(b); + } + } + // mode info stored before the residual + let (rows, cols) = (n4.min(self.g.mi_rows - r), n4.min(self.g.mi_cols - c)); + for y in 0..rows { + for x in 0..cols { + let i = self.mi.idx(r + y, c + x); + self.mi.y_mode[i] = y_mode as u8; + self.mi.ref_frame[i] = if b.is_inter { [LAST_FRAME as i8, -1] } else { [INTRA_FRAME as i8, -1] }; + self.mi.written[i] = true; + if b.is_inter { + self.mi.mv[i] = b.mv; + } + } + } + if b.skip { + // reset_block_context( ) + for plane in 0..3 { + let s = (plane > 0) as usize; + for i in (c >> s)..((c + n4) >> s) { + self.above_level[plane][i] = 0; + self.above_dc[plane][i] = 0; + } + for i in (r >> s)..((r + n4) >> s) { + self.left_level[plane][i] = 0; + self.left_dc[plane][i] = 0; + } + } + } else { + let log2 = (n4 * 4).trailing_zeros(); + assert!(log2 <= 5, "64x64 blocks are coded as skip blocks"); + for plane in 0..3 { + let s = (plane > 0) as u32; + self.coeffs(plane, (c * 4) >> s, (r * 4) >> s, tx_size_for(log2 - s), b, &b.coefs[plane]); + } + } + for y in 0..rows { + for x in 0..cols { + let i = self.mi.idx(r + y, c + x); + self.mi.is_inter[i] = b.is_inter; + self.mi.skip[i] = b.skip; + self.mi.mi_size[i] = bsize as u8; + } + } + // LoopfilterTxSizes: one transform block per plane + let log2 = (n4 * 4).trailing_zeros(); + let stride = self.g.mi_cols + 32; + for plane in 0..3 { + let s = (plane > 0) as usize; + let tx = tx_size_for(log2 - s as u32) as u8; + let (r0, c0, m) = (r >> s, c >> s, n4 >> s); + for y in r0..r0 + m { + self.lf_tx[plane][y * stride + c0..y * stride + c0 + m].iter_mut().for_each(|v| *v = tx); + } + } + } + + fn intra_angle_and_uv(&mut self, b: &Leaf) { + if self.bsize >= BLOCK_8X8 && is_directional(b.y_mode) { + self.sw.symbol(MAX_ANGLE_DELTA, &mut self.cdf.angle_delta[b.y_mode - V_PRED]); + } + let bs = block_size_px(self.bsize); + if bs <= 32 { + self.sw.symbol(b.uv_mode, &mut self.cdf.uv_mode_cfl_allowed[b.y_mode]); + } else { + self.sw.symbol(b.uv_mode, &mut self.cdf.uv_mode_cfl_not_allowed[b.y_mode]); + } + if self.bsize >= BLOCK_8X8 && is_directional(b.uv_mode) { + self.sw.symbol(MAX_ANGLE_DELTA, &mut self.cdf.angle_delta[b.uv_mode - V_PRED]); + } + } + + /// read_ref_frames (LAST_FRAME), find_mv_stack, the inter mode, DRL and motion vector. + /// Returns the inter mode (YMode). + fn inter_block_mode_info(&mut self, b: &Leaf, left: [i8; 2], above: [i8; 2]) -> usize { + let count = |ft: i8| -> usize { + let mut n = 0; + if self.avail_u { + n += (above[0] == ft) as usize + (above[1] == ft) as usize; + } + if self.avail_l { + n += (left[0] == ft) as usize + (left[1] == ft) as usize; + } + n + }; + let cmp = |a: usize, b: usize| match a.cmp(&b) { + std::cmp::Ordering::Less => 0, + std::cmp::Ordering::Equal => 1, + std::cmp::Ordering::Greater => 2, + }; + let (last, last2, last3, gold) = (count(LAST_FRAME as i8), count(LAST2_FRAME as i8), count(LAST3_FRAME as i8), count(GOLDEN_FRAME as i8)); + let (bwd, alt2, alt) = (count(BWDREF_FRAME as i8), count(ALTREF2_FRAME as i8), count(ALTREF_FRAME as i8)); + let ctx_p1 = cmp(last + last2 + last3 + gold, bwd + alt2 + alt); + let ctx_comp_ref = cmp(last + last2, last3 + gold); + let ctx_comp_ref_p1 = cmp(last, last2); + self.sw.symbol(0, &mut self.cdf.single_ref[ctx_p1][0]); + self.sw.symbol(0, &mut self.cdf.single_ref[ctx_comp_ref][2]); + self.sw.symbol(0, &mut self.cdf.single_ref[ctx_comp_ref_p1][3]); + self.find_mv_stack(); + let mv = b.mv; + let s = &self.mvs; + let mode = if mv == [0, 0] { + GLOBALMV + } else if mv == s.ref_stack_mv[0] { + NEARESTMV + } else if mv == s.ref_stack_mv[1] { + NEARMV + } else { + NEWMV + }; + let (nctx, zctx, rctx) = (s.new_mv_context, s.zero_mv_context, s.ref_mv_context); + self.sw.symbol((mode != NEWMV) as usize, &mut self.cdf.new_mv[nctx]); + if mode != NEWMV { + self.sw.symbol((mode != GLOBALMV) as usize, &mut self.cdf.zero_mv[zctx]); + if mode != GLOBALMV { + self.sw.symbol((mode == NEARMV) as usize, &mut self.cdf.ref_mv[rctx]); + } + } + // DRL: ref_mv_idx 0 for NEWMV, 1 for NEARMV + if mode == NEWMV { + if self.mvs.num_mv_found > 1 { + let ctx = self.mvs.drl_ctx_stack[0]; + self.sw.symbol(0, &mut self.cdf.drl_mode[ctx]); + } + } else if mode == NEARMV && self.mvs.num_mv_found > 2 { + let ctx = self.mvs.drl_ctx_stack[1]; + self.sw.symbol(0, &mut self.cdf.drl_mode[ctx]); + } + if mode == NEWMV { + let pred = self.mvs.ref_stack_mv[0]; + self.write_mv([mv[0] - pred[0], mv[1] - pred[1]]); + } + mode + } + + fn write_mv(&mut self, diff: MvP) { + let joint = ((diff[0] != 0) as usize) << 1 | (diff[1] != 0) as usize; + // MV_JOINT_ZERO, MV_JOINT_HNZVZ (col only), MV_JOINT_HZVNZ (row only), MV_JOINT_HNZVNZ + self.sw.symbol(joint, &mut self.cdf.mv_joint); + for (comp, &d) in diff.iter().enumerate() { + if d != 0 { + self.write_mv_component(comp, d); + } + } + } + + fn write_mv_component(&mut self, comp: usize, v: i32) { + debug_assert!(v % 2 == 0, "quarter-sample precision"); + self.sw.symbol((v < 0) as usize, &mut self.cdf.mv_sign[comp]); + let m = v.unsigned_abs() - 1; // odd: the implied hp bit is 1 + let fr = ((m >> 1) & 3) as usize; + let int = m >> 3; + if int < CLASS0_SIZE as u32 { + self.sw.symbol(MV_CLASS_0, &mut self.cdf.mv_class[comp]); + self.sw.symbol(int as usize, &mut self.cdf.mv_class0_bit[comp]); + self.sw.symbol(fr, &mut self.cdf.mv_class0_fr[comp][int as usize]); + } else { + let class = 31 - int.leading_zeros(); + let d = int - (1 << class); + self.sw.symbol(class as usize, &mut self.cdf.mv_class[comp]); + for i in 0..class as usize { + self.sw.symbol(((d >> i) & 1) as usize, &mut self.cdf.mv_bit[comp][i]); + } + self.sw.symbol(fr, &mut self.cdf.mv_fr[comp]); + } + } + + // ---- coefficients ---------------------------------------------------------------------- + + fn coeffs(&mut self, plane: usize, start_x: usize, start_y: usize, tx_sz: usize, b: &Leaf, levels: &[i32]) { + let x4 = start_x >> 2; + let y4 = start_y >> 2; + let w4 = TX_WIDTH[tx_sz] as usize >> 2; + let h4 = TX_HEIGHT[tx_sz] as usize >> 2; + let tx_sz_ctx = (TX_SIZE_SQR[tx_sz] as usize + TX_SIZE_SQR_UP[tx_sz] as usize + 1) >> 1; + let ptype = (plane > 0) as usize; + let scan = scan_for(tx_sz); + let mut eob = 0; + for (i, &pos) in scan.iter().enumerate() { + if levels[pos as usize] != 0 { + eob = i + 1; + } + } + let ctx = self.all_zero_ctx(plane, tx_sz, x4, y4, w4, h4); + self.sw.symbol((eob == 0) as usize, &mut self.cdf.txb_skip[tx_sz_ctx][ctx]); + let mut cul_level = 0u32; + let mut dc_category = 0u8; + if eob == 0 { + if plane == 0 { + self.set_tx_types(x4, y4, tx_sz, DCT_DCT); + } + } else { + if plane == 0 { + // transform_type( ) + let set = tx_set(tx_sz, b.is_inter); + let t = b.tx_type_y; + if set > 0 && self.qidx > 0 { + let sqr = TX_SIZE_SQR[tx_sz] as usize; + if b.is_inter { + let s = TX_TYPE_INTER_INV_SET3.iter().position(|&v| v as usize == t).expect("inter tx type in set"); + self.sw.symbol(s, &mut self.cdf.inter_tx_type_set3[sqr]); + } else { + let s = TX_TYPE_INTRA_INV_SET2.iter().position(|&v| v as usize == t).expect("intra tx type in set"); + self.sw.symbol(s, &mut self.cdf.intra_tx_type_set2[sqr][b.y_mode]); + } + } else { + assert_eq!(t, DCT_DCT); + } + self.set_tx_types(x4, y4, tx_sz, t); + } + let plane_tx_type = self.compute_tx_type(plane, tx_sz, x4, y4, b); + debug_assert!(plane_tx_type == DCT_DCT || matches!(plane_tx_type, ADST_DCT | DCT_ADST | ADST_ADST)); + let eob_multisize = (TX_WIDTH_LOG2[tx_sz] as usize).min(5) + (TX_HEIGHT_LOG2[tx_sz] as usize).min(5) - 4; + let eob_pt = if eob < 2 { eob } else { (32 - ((eob - 1) as u32).leading_zeros()) as usize + 1 }; + let ectx = 0; // TX_CLASS_2D + let s = eob_pt - 1; + match eob_multisize { + 0 => self.sw.symbol(s, &mut self.cdf.eob_pt_16[ptype][ectx]), + 1 => self.sw.symbol(s, &mut self.cdf.eob_pt_32[ptype][ectx]), + 2 => self.sw.symbol(s, &mut self.cdf.eob_pt_64[ptype][ectx]), + 3 => self.sw.symbol(s, &mut self.cdf.eob_pt_128[ptype][ectx]), + 4 => self.sw.symbol(s, &mut self.cdf.eob_pt_256[ptype][ectx]), + 5 => self.sw.symbol(s, &mut self.cdf.eob_pt_512[ptype]), + _ => self.sw.symbol(s, &mut self.cdf.eob_pt_1024[ptype]), + } + if eob_pt >= 3 { + let eob_shift = eob_pt - 3; + let offset = eob - ((1 << (eob_pt - 2)) + 1); + self.sw.symbol((offset >> eob_shift) & 1, &mut self.cdf.eob_extra[tx_sz_ctx][ptype][eob_pt - 3]); + for i in 1..eob_pt - 2 { + let sh = eob_pt - 2 - 1 - i; + self.sw.bool((offset >> sh) & 1 == 1); + } + } + let bwl = TX_WIDTH_LOG2[tx_sz] as usize; + let height = TX_HEIGHT[tx_sz] as usize; + let width = 1usize << bwl; + // Quant[] as the decoder sees it while parsing: the levels coded so far (capped) + let mut quant = vec![0i32; width * height]; + for c in (0..eob).rev() { + let pos = scan[c] as usize; + let a = levels[pos].unsigned_abs().min(15) as i32; + if c == eob - 1 { + let ctx = coeff_base_eob_ctx(c, bwl, height); + self.sw.symbol((a.min(3) - 1) as usize, &mut self.cdf.coeff_base_eob[tx_sz_ctx][ptype][ctx]); + } else { + let ctx = coeff_base_ctx(&quant, tx_sz, bwl, width, height, pos); + self.sw.symbol(a.min(3) as usize, &mut self.cdf.coeff_base[tx_sz_ctx][ptype][ctx]); + } + if a > NUM_BASE_LEVELS as i32 { + let ctx = coeff_br_ctx(&quant, bwl, height, pos); + let mut rem = a - 3; + for _ in 0..(COEFF_BASE_RANGE / (BR_CDF_SIZE - 1)) { + let br = rem.min(3); + self.sw.symbol(br as usize, &mut self.cdf.coeff_br[tx_sz_ctx.min(TX_32X32)][ptype][ctx]); + rem -= br; + if br < 3 { + break; + } + } + } + quant[pos] = a; + } + for c in 0..eob { + let pos = scan[c] as usize; + let v = levels[pos]; + if v != 0 { + if c == 0 { + let ctx = self.dc_sign_ctx(plane, x4, y4, w4, h4); + self.sw.symbol((v < 0) as usize, &mut self.cdf.dc_sign[ptype][ctx]); + } else { + self.sw.bool(v < 0); + } + } + let a = v.unsigned_abs(); + if a > (NUM_BASE_LEVELS + COEFF_BASE_RANGE) as u32 { + // Golomb code of a - 15 + let x = a - 15 + 1; + let length = 32 - x.leading_zeros(); + for _ in 0..length - 1 { + self.sw.bool(false); + } + self.sw.bool(true); + for i in (0..length - 1).rev() { + self.sw.bool((x >> i) & 1 == 1); + } + } + if pos == 0 && a > 0 { + dc_category = if v < 0 { 1 } else { 2 }; + } + cul_level += a & 0xFFFFF; + } + cul_level = cul_level.min(63); + } + for i in 0..w4 { + if x4 + i < self.above_level[plane].len() { + self.above_level[plane][x4 + i] = cul_level as u8; + self.above_dc[plane][x4 + i] = dc_category; + } + } + for i in 0..h4 { + if y4 + i < self.left_level[plane].len() { + self.left_level[plane][y4 + i] = cul_level as u8; + self.left_dc[plane][y4 + i] = dc_category; + } + } + } + + fn set_tx_types(&mut self, x4: usize, y4: usize, tx_sz: usize, t: usize) { + for i in 0..(TX_WIDTH[tx_sz] as usize >> 2) { + for j in 0..(TX_HEIGHT[tx_sz] as usize >> 2) { + if y4 + j < self.g.mi_rows && x4 + i < self.g.mi_cols { + let k = self.mi.idx(y4 + j, x4 + i); + self.mi.tx_type[k] = t as u8; + } + } + } + } + + fn compute_tx_type(&self, plane: usize, tx_sz: usize, x4: usize, y4: usize, b: &Leaf) -> usize { + if TX_SIZE_SQR_UP[tx_sz] as usize > TX_32X32 { + return DCT_DCT; + } + if plane == 0 { + return self.mi.tx_type[self.mi.idx(y4, x4)] as usize; + } + if b.is_inter { + let set = tx_set(tx_sz, true); + let xx = self.c.max(x4 << 1); + let yy = self.r.max(y4 << 1); + let t = self.mi.tx_type[self.mi.idx(yy, xx)] as usize; + return if tx_in_set(set, true, t) { t } else { DCT_DCT }; + } + intra_uv_tx_type(b.uv_mode, tx_sz) + } + + fn all_zero_ctx(&self, plane: usize, tx_sz: usize, x4: usize, y4: usize, w4: usize, h4: usize) -> usize { + let s = (plane > 0) as usize; + let max_x4 = self.g.mi_cols >> s; + let max_y4 = self.g.mi_rows >> s; + let w = TX_WIDTH[tx_sz] as usize; + let h = TX_HEIGHT[tx_sz] as usize; + let bsize = SUBSAMPLED_SIZE[self.bsize][s][s] as usize; + let bw = block_size_px(bsize); + let bh = 4 * NUM_4X4_BLOCKS_HIGH[bsize] as usize; + let al = &self.above_level[plane]; + let ll = &self.left_level[plane]; + if plane == 0 { + let mut top = 0u32; + let mut left = 0u32; + for k in 0..w4 { + if x4 + k < max_x4 { + top = top.max(al[x4 + k] as u32); + } + } + for k in 0..h4 { + if y4 + k < max_y4 { + left = left.max(ll[y4 + k] as u32); + } + } + top = top.min(255); + left = left.min(255); + if bw == w && bh == h { + 0 + } else if top == 0 && left == 0 { + 1 + } else if top == 0 || left == 0 { + 2 + (top.max(left) > 3) as usize + } else if top.max(left) <= 3 { + 4 + } else if top.min(left) <= 3 { + 5 + } else { + 6 + } + } else { + let ad = &self.above_dc[plane]; + let ld = &self.left_dc[plane]; + let mut above = 0u8; + let mut left = 0u8; + for i in 0..w4 { + if x4 + i < max_x4 { + above |= al[x4 + i] | ad[x4 + i]; + } + } + for i in 0..h4 { + if y4 + i < max_y4 { + left |= ll[y4 + i] | ld[y4 + i]; + } + } + let mut ctx = (above != 0) as usize + (left != 0) as usize + 7; + if bw * bh > w * h { + ctx += 3; + } + ctx + } + } + + fn dc_sign_ctx(&self, plane: usize, x4: usize, y4: usize, w4: usize, h4: usize) -> usize { + let s = (plane > 0) as usize; + let max_x4 = self.g.mi_cols >> s; + let max_y4 = self.g.mi_rows >> s; + let mut dc_sign = 0i32; + for k in 0..w4 { + if x4 + k < max_x4 { + match self.above_dc[plane][x4 + k] { + 1 => dc_sign -= 1, + 2 => dc_sign += 1, + _ => {} + } + } + } + for k in 0..h4 { + if y4 + k < max_y4 { + match self.left_dc[plane][y4 + k] { + 1 => dc_sign -= 1, + 2 => dc_sign += 1, + _ => {} + } + } + } + match dc_sign.signum() { + -1 => 1, + 1 => 2, + _ => 0, + } + } + + // ---- motion vector prediction (7.10.2), single reference LAST_FRAME -------------------- + + fn inside(&self, r: isize, c: isize) -> bool { + c >= 0 && c < self.g.mi_cols as isize && r >= 0 && r < self.g.mi_rows as isize + } + + fn find_mv_stack(&mut self) { + let n4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as isize; + let (bw4, bh4) = (n4, n4); + self.mvs = MvStack::default(); + self.scan_row(-1); + let mut found_above = self.mvs.found_match; + self.mvs.found_match = false; + self.scan_col(-1); + let mut found_left = self.mvs.found_match; + self.mvs.found_match = false; + if bw4.max(bh4) <= 16 { + self.scan_point(-1, bw4); + } + if self.mvs.found_match { + found_above = true; + } + self.mvs.close_matches = found_above as usize + found_left as usize; + let num_nearest = self.mvs.num_mv_found; + let num_new = self.mvs.new_mv_count; + for idx in 0..num_nearest { + self.mvs.weight_stack[idx] += REF_CAT_LEVEL as u32; + } + self.mvs.zero_mv_context = 0; + // use_ref_frame_mvs = 0: no temporal candidates + self.scan_point(-1, -1); + if self.mvs.found_match { + found_above = true; + } + self.mvs.found_match = false; + self.scan_row(-3); + if self.mvs.found_match { + found_above = true; + } + self.mvs.found_match = false; + self.scan_col(-3); + if self.mvs.found_match { + found_left = true; + } + self.mvs.found_match = false; + if bh4 > 1 { + self.scan_row(-5); + } + if self.mvs.found_match { + found_above = true; + } + self.mvs.found_match = false; + if bw4 > 1 { + self.scan_col(-5); + } + if self.mvs.found_match { + found_left = true; + } + self.mvs.total_matches = found_above as usize + found_left as usize; + let n = self.mvs.num_mv_found; + self.sort_stack(0, num_nearest); + self.sort_stack(num_nearest, n); + if self.mvs.num_mv_found < 2 { + self.extra_search(); + } + self.context_and_clamping(num_new); + } + + fn scan_row(&mut self, delta_row: isize) { + let bw4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as isize; + let (mi_row, mi_col) = (self.r as isize, self.c as isize); + let end4 = bw4.min(self.g.mi_cols as isize - mi_col).min(16); + let mut delta_col = 0; + let use_step16 = bw4 >= 16; + let mut delta_row = delta_row; + if delta_row.abs() > 1 { + delta_row += mi_row & 1; + delta_col = 1 - (mi_col & 1); + } + let mut i = 0; + while i < end4 { + let mv_row = mi_row + delta_row; + let mv_col = mi_col + delta_col + i; + if !self.inside(mv_row, mv_col) { + break; + } + let k = self.mi.idx(mv_row as usize, mv_col as usize); + let mut len = bw4.min(NUM_4X4_BLOCKS_WIDE[self.mi.mi_size[k] as usize] as isize); + if delta_row.abs() > 1 { + len = len.max(2); + } + if use_step16 { + len = len.max(4); + } + self.add_ref_mv_candidate(mv_row as usize, mv_col as usize, len as u32 * 2); + i += len; + } + } + + fn scan_col(&mut self, delta_col: isize) { + let bh4 = NUM_4X4_BLOCKS_HIGH[self.bsize] as isize; + let (mi_row, mi_col) = (self.r as isize, self.c as isize); + let end4 = bh4.min(self.g.mi_rows as isize - mi_row).min(16); + let mut delta_row = 0; + let use_step16 = bh4 >= 16; + let mut delta_col = delta_col; + if delta_col.abs() > 1 { + delta_row = 1 - (mi_row & 1); + delta_col += mi_col & 1; + } + let mut i = 0; + while i < end4 { + let mv_row = mi_row + delta_row + i; + let mv_col = mi_col + delta_col; + if !self.inside(mv_row, mv_col) { + break; + } + let k = self.mi.idx(mv_row as usize, mv_col as usize); + let mut len = bh4.min(NUM_4X4_BLOCKS_HIGH[self.mi.mi_size[k] as usize] as isize); + if delta_col.abs() > 1 { + len = len.max(2); + } + if use_step16 { + len = len.max(4); + } + self.add_ref_mv_candidate(mv_row as usize, mv_col as usize, len as u32 * 2); + i += len; + } + } + + fn scan_point(&mut self, delta_row: isize, delta_col: isize) { + let mv_row = self.r as isize + delta_row; + let mv_col = self.c as isize + delta_col; + if self.inside(mv_row, mv_col) && self.mi.written[self.mi.idx(mv_row as usize, mv_col as usize)] { + self.add_ref_mv_candidate(mv_row as usize, mv_col as usize, 4); + } + } + + fn add_ref_mv_candidate(&mut self, mv_row: usize, mv_col: usize, weight: u32) { + let i = self.mi.idx(mv_row, mv_col); + if !self.mi.is_inter[i] { + return; + } + let rf = self.mi.ref_frame[i]; + for cand_list in 0..2 { + if rf[cand_list] == LAST_FRAME as i8 { + self.search_stack(i, cand_list, weight); + } + } + } + + fn search_stack(&mut self, i: usize, cand_list: usize, weight: u32) { + debug_assert_eq!(cand_list, 0); + let cand_mode = self.mi.y_mode[i] as usize; + // gm_type is IDENTITY: the stored motion vector is used + let mut cand = self.mi.mv[i]; + lower_mv_precision(&mut cand); + if cand_mode == NEWMV { + self.mvs.new_mv_count += 1; + } + self.mvs.found_match = true; + let n = self.mvs.num_mv_found; + if let Some(idx) = (0..n).find(|&k| self.mvs.ref_stack_mv[k] == cand) { + self.mvs.weight_stack[idx] += weight; + } else if n < MAX_REF_MV_STACK_SIZE { + self.mvs.ref_stack_mv[n] = cand; + self.mvs.weight_stack[n] = weight; + self.mvs.num_mv_found += 1; + } + } + + fn sort_stack(&mut self, start: usize, end: usize) { + let mut end = end; + let s = &mut self.mvs; + while end > start { + let mut new_end = start; + for idx in start + 1..end { + if s.weight_stack[idx - 1] < s.weight_stack[idx] { + s.weight_stack.swap(idx - 1, idx); + s.ref_stack_mv.swap(idx - 1, idx); + new_end = idx; + } + } + end = new_end; + } + } + + fn extra_search(&mut self) { + let (mi_row, mi_col) = (self.r as isize, self.c as isize); + let n4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as isize; + let w4 = n4.min(16).min(self.g.mi_cols as isize - mi_col); + let h4 = n4.min(16).min(self.g.mi_rows as isize - mi_row); + let num4x4 = w4.min(h4); + for pass in 0..2 { + let mut idx = 0; + while idx < num4x4 && self.mvs.num_mv_found < 2 { + let (mv_row, mv_col) = if pass == 0 { (mi_row - 1, mi_col + idx) } else { (mi_row + idx, mi_col - 1) }; + if !self.inside(mv_row, mv_col) { + break; + } + let k = self.mi.idx(mv_row as usize, mv_col as usize); + // add_extra_mv_candidate( ) (no sign bias: a single forward reference) + let rfs = self.mi.ref_frame[k]; + for &cand_ref in rfs.iter().take(2) { + if cand_ref > INTRA_FRAME as i8 { + let cand = self.mi.mv[k]; + let n = self.mvs.num_mv_found; + if !(0..n).any(|j| self.mvs.ref_stack_mv[j] == cand) { + self.mvs.ref_stack_mv[n] = cand; + self.mvs.weight_stack[n] = 2; + self.mvs.num_mv_found += 1; + } + } + } + let sz = self.mi.mi_size[k] as usize; + idx += if pass == 0 { NUM_4X4_BLOCKS_WIDE[sz] } else { NUM_4X4_BLOCKS_HIGH[sz] } as isize; + } + } + for idx in self.mvs.num_mv_found..2 { + self.mvs.ref_stack_mv[idx] = [0, 0]; // GlobalMvs[ 0 ] + } + } + + fn context_and_clamping(&mut self, num_new: usize) { + let bs = block_size_px(self.bsize) as i32; + let n = self.mvs.num_mv_found; + for idx in 0..n { + let mut z = 0; + if idx + 1 < n { + let w0 = self.mvs.weight_stack[idx]; + let w1 = self.mvs.weight_stack[idx + 1]; + if w0 >= REF_CAT_LEVEL as u32 { + if w1 < REF_CAT_LEVEL as u32 { + z = 1; + } + } else { + z = 2; + } + } + self.mvs.drl_ctx_stack[idx] = z; + } + let n4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as i32; + for idx in 0..n { + let mut mv = self.mvs.ref_stack_mv[idx]; + let border_r = MV_BORDER as i32 + bs * 8; + let to_top = -((self.r as i32 * 4) * 8); + let to_bottom = ((self.g.mi_rows as i32 - n4 - self.r as i32) * 4) * 8; + mv[0] = mv[0].clamp(to_top - border_r, to_bottom + border_r); + let to_left = -((self.c as i32 * 4) * 8); + let to_right = ((self.g.mi_cols as i32 - n4 - self.c as i32) * 4) * 8; + mv[1] = mv[1].clamp(to_left - border_r, to_right + border_r); + self.mvs.ref_stack_mv[idx] = mv; + } + let (close, total) = (self.mvs.close_matches, self.mvs.total_matches); + if close == 0 { + self.mvs.new_mv_context = total.min(1); + self.mvs.ref_mv_context = total; + } else if close == 1 { + self.mvs.new_mv_context = 3 - num_new.min(1); + self.mvs.ref_mv_context = 2 + total; + } else { + self.mvs.new_mv_context = 5 - num_new.min(1); + self.mvs.ref_mv_context = 5; + } + } +} + +/// lower_mv_precision( ) with allow_high_precision_mv = 0 and force_integer_mv = 0. +fn lower_mv_precision(mv: &mut MvP) { + for c in mv.iter_mut() { + if *c & 1 != 0 { + *c += if *c > 0 { -1 } else { 1 }; + } + } +} + +fn coeff_base_eob_ctx(c: usize, bwl: usize, height: usize) -> usize { + if c == 0 { + 0 + } else if c <= (height << bwl) / 8 { + 1 + } else if c <= (height << bwl) / 4 { + 2 + } else { + 3 + } +} + +/// Context of coeff_base for TX_CLASS_2D (8.3.2). +fn coeff_base_ctx(quant: &[i32], tx_sz: usize, bwl: usize, width: usize, height: usize, pos: usize) -> usize { + let row = pos >> bwl; + let col = pos - (row << bwl); + let mut mag = 0i32; + for off in SIG_REF_DIFF_OFFSET[TX_CLASS_2D].iter() { + let ref_row = row + off[0] as usize; + let ref_col = col + off[1] as usize; + if ref_row < height && ref_col < width { + mag += quant[(ref_row << bwl) + ref_col].abs().min(3); + } + } + let ctx = ((mag + 1) >> 1).min(4) as usize; + if row == 0 && col == 0 { + return 0; + } + ctx + COEFF_BASE_CTX_OFFSET[tx_sz][row.min(4)][col.min(4)] as usize +} + +/// Context of coeff_br for TX_CLASS_2D. +fn coeff_br_ctx(quant: &[i32], bwl: usize, txh: usize, pos: usize) -> usize { + let txw = 1usize << bwl; + let row = pos >> bwl; + let col = pos - (row << bwl); + let mut mag = 0i32; + for off in MAG_REF_OFFSET_WITH_TX_CLASS[TX_CLASS_2D].iter() { + let ref_row = row + off[0] as usize; + let ref_col = col + off[1] as usize; + if ref_row < txh && ref_col < txw { + mag += quant[ref_row * txw + ref_col].min((COEFF_BASE_RANGE + NUM_BASE_LEVELS + 1) as i32); + } + } + let mag = ((mag + 1) >> 1).min(6) as usize; + if pos == 0 { + mag + } else if row < 2 && col < 2 { + mag + 7 + } else { + mag + 14 + } +} diff --git a/crates/av1enc/tests/bench.rs b/crates/av1enc/tests/bench.rs new file mode 100644 index 00000000..6577f8f9 --- /dev/null +++ b/crates/av1enc/tests/bench.rs @@ -0,0 +1,136 @@ +//! Speed / efficiency measurements on ffmpeg-generated test sources (ignored by default): +//! `cargo test -p effectcraft-av1enc --test bench -- --ignored --nocapture`. + +mod common; + +use std::process::Command; +use std::time::Instant; + +use common::*; +use effectcraft_av1enc::{Encoder, EncoderConfig, RateControl}; + +/// Raw 8-bit 4:2:0 frames of an ffmpeg lavfi source, or None without ffmpeg. +fn lavfi(src: &str, w: usize, h: usize, frames: usize) -> Option> { + let out = std::env::temp_dir().join(format!("effectcraft-av1enc-bench-{}-{src}-{w}x{h}.yuv", std::process::id())); + let st = Command::new("ffmpeg") + .args(["-v", "error", "-y", "-f", "lavfi", "-i"]) + .arg(format!("{src}=size={w}x{h}:rate=30")) + .args(["-frames:v", &frames.to_string(), "-pix_fmt", "yuv420p", "-f", "rawvideo"]) + .arg(&out) + .status() + .ok()?; + if !st.success() { + return None; + } + let raw = std::fs::read(&out).ok()?; + let _ = std::fs::remove_file(&out); + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let fs = w * h + 2 * cw * ch; + Some( + raw.chunks_exact(fs) + .map(|f| Pic { + w, + h, + y: f[..w * h].iter().map(|&b| b as u16).collect(), + u: f[w * h..w * h + cw * ch].iter().map(|&b| b as u16).collect(), + v: f[w * h + cw * ch..].iter().map(|&b| b as u16).collect(), + }) + .collect(), + ) +} + +fn run(name: &str, pics: &[Pic], rate: RateControl, keyint: u32) { + let (w, h) = (pics[0].w, pics[0].h); + let mut cfg = EncoderConfig::new(w as u32, h as u32, 30, 1); + cfg.rate = rate; + cfg.keyint = keyint; + let mut enc = Encoder::new(cfg).unwrap(); + let mut bytes = 0; + let mut psnr_sum = 0.0; + let mut t_key = 0.0; + let mut t_inter = 0.0; + let mut sizes = Vec::new(); + for p in pics { + let t = Instant::now(); + let pkt = enc.encode(&p.frame()); + let dt = t.elapsed().as_secs_f64(); + if pkt.keyframe { + t_key += dt; + } else { + t_inter += dt; + } + bytes += pkt.data.len(); + sizes.push(pkt.data.len()); + let rec = enc.last_reconstruction().unwrap(); + psnr_sum += psnr(&p.y, &rec[0], 8); + } + let n = pics.len() as f64; + let n_key = pics.len().div_ceil(keyint as usize) as f64; + eprintln!( + "{name} {w}x{h} {rate:?}: {:.0} kbit/s, PSNR-Y {:.2} dB, key {:.2} s/frame, inter {:.2} s/frame, sizes {:?}", + bytes as f64 * 8.0 * 30.0 / n / 1000.0, + psnr_sum / n, + t_key / n_key, + if n > n_key { t_inter / (n - n_key) } else { 0.0 }, + &sizes[..sizes.len().min(6)] + ); +} + +#[test] +#[ignore] +fn bench_lavfi() { + if std::env::var("AV1ENC_BENCH_QUICK").is_err() { + let Some(pics) = lavfi("testsrc2", 1920, 1080, 6) else { + eprintln!("ffmpeg not available"); + return; + }; + run("testsrc2", &pics, RateControl::ConstantQ(100), 30); + } + if let Some(pics) = lavfi("mandelbrot", 640, 360, 20) { + for q in [40, 80, 120, 160] { + run("mandelbrot", &pics, RateControl::ConstantQ(q), 30); + } + run("mandelbrot", &pics, RateControl::Bitrate { kbps: 1000 }, 30); + } +} + +#[test] +#[ignore] +fn bench_intra() { + for src in ["mandelbrot", "sierpinski", "gradients"] { + let Some(pics) = lavfi(src, 640, 360, 4) else { + return; + }; + for q in [40, 80, 120, 160] { + run(&format!("{src}-intra"), &pics, RateControl::ConstantQ(q), 1); + } + } +} + +#[test] +#[ignore] +fn bench_1080p_loop() { + let Some(pics) = lavfi("mandelbrot", 1920, 1080, 4) else { + return; + }; + run("mandelbrot", &pics, RateControl::ConstantQ(120), 30); + run("mandelbrot", &pics, RateControl::ConstantQ(120), 30); +} + +#[test] +#[ignore] +fn bench_rate_control() { + let Some(pics) = lavfi("mandelbrot", 480, 272, 60) else { + eprintln!("ffmpeg not available"); + return; + }; + for kbps in [200, 600, 2000] { + run("mandelbrot", &pics, RateControl::Bitrate { kbps }, 30); + } + let Some(pics) = lavfi("testsrc2", 480, 272, 60) else { + return; + }; + for kbps in [300, 3000] { + run("testsrc2", &pics, RateControl::Bitrate { kbps }, 60); + } +} diff --git a/crates/av1enc/tests/common/mod.rs b/crates/av1enc/tests/common/mod.rs new file mode 100644 index 00000000..cf5dba73 --- /dev/null +++ b/crates/av1enc/tests/common/mod.rs @@ -0,0 +1,116 @@ +#![allow(dead_code)] + +use effectcraft_av1enc::{Encoder, EncoderConfig, Frame, Packet}; + +/// A synthetic 4:2:0 picture. +pub struct Pic { + pub w: usize, + pub h: usize, + pub y: Vec, + pub u: Vec, + pub v: Vec, +} + +impl Pic { + pub fn frame(&self) -> Frame<'_> { + Frame { y: &self.y, u: &self.u, v: &self.v, y_stride: self.w, uv_stride: self.w.div_ceil(2) } + } +} + +/// Smooth gradients with a soft blob, moved by (dx, dy) samples per frame index `t`, plus a +/// textured rectangle that moves differently. +pub fn synth(w: usize, h: usize, bd: u32, t: usize) -> Pic { + let max = ((1u32 << bd) - 1) as f64; + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let sample = |x: f64, y: f64| -> f64 { + let fx = x - 2.0 * t as f64; + let fy = y - 1.0 * t as f64; + let base = 0.25 + 0.5 * (fx / 97.0).sin() * (fy / 61.0).cos() * 0.5 + 0.2 * ((fx + fy) / (w + h) as f64); + let d = ((fx - w as f64 / 2.0).powi(2) + (fy - h as f64 / 2.0).powi(2)).sqrt(); + let blob = 0.3 * (-d * d / (2.0 * (w.min(h) as f64 / 5.0).powi(2))).exp(); + // a checkered patch moving right by 4 per frame + let px = x - 4.0 * t as f64 - w as f64 / 4.0; + let py = y - h as f64 / 3.0; + let patch = + if (0.0..24.0).contains(&px) && (0.0..16.0).contains(&py) { if ((px as i32 / 4) + (py as i32 / 4)) % 2 == 0 { 0.25 } else { -0.25 } } else { 0.0 }; + (base + blob + patch).clamp(0.0, 1.0) + }; + let mut y = vec![0u16; w * h]; + for j in 0..h { + for i in 0..w { + y[j * w + i] = (sample(i as f64, j as f64) * max).round() as u16; + } + } + let mut u = vec![0u16; cw * ch]; + let mut v = vec![0u16; cw * ch]; + for j in 0..ch { + for i in 0..cw { + let s = sample(2.0 * i as f64, 2.0 * j as f64); + u[j * cw + i] = ((0.5 + 0.3 * (s - 0.5) + 0.1 * (i as f64 / 13.0).sin()) * max).round() as u16; + v[j * cw + i] = ((0.5 - 0.25 * (s - 0.5) + 0.1 * (j as f64 / 7.0).cos()) * max).round() as u16; + } + } + Pic { w, h, y, u, v } +} + +/// Deterministic noise picture (hard case for prediction). +pub fn noise(w: usize, h: usize, bd: u32, seed: u32) -> Pic { + let mut s = seed.wrapping_mul(2_654_435_761).max(1); + let mut rnd = || { + s ^= s << 13; + s ^= s >> 17; + s ^= s << 5; + s + }; + let mask = (1u32 << bd) - 1; + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + Pic { + w, + h, + y: (0..w * h).map(|_| (rnd() & mask) as u16).collect(), + u: (0..cw * ch).map(|_| (rnd() & mask) as u16).collect(), + v: (0..cw * ch).map(|_| (rnd() & mask) as u16).collect(), + } +} + +pub fn psnr(a: &[u16], b: &[u16], bd: u32) -> f64 { + let max = ((1u32 << bd) - 1) as f64; + let mse = a.iter().zip(b).map(|(&x, &y)| (x as f64 - y as f64).powi(2)).sum::() / a.len() as f64; + if mse == 0.0 { 99.0 } else { 10.0 * (max * max / mse).log10() } +} + +pub struct Run { + pub packets: Vec, + pub recons: Vec<[Vec; 3]>, + pub psnr_y: Vec, + pub lf_levels: Vec, +} + +/// Encodes `pics`, checks every packet decodes (filmcraft-av1) to exactly the encoder's +/// reconstruction and returns packets, reconstructions and luma PSNRs. +pub fn encode_and_check(cfg: EncoderConfig, pics: &[Pic]) -> Run { + let bd = cfg.bit_depth as u32; + let mut enc = Encoder::new(cfg).expect("config"); + let mut dec = filmcraft_av1::Decoder::new(); + let mut run = Run { packets: Vec::new(), recons: Vec::new(), psnr_y: Vec::new(), lf_levels: Vec::new() }; + for (i, p) in pics.iter().enumerate() { + let pkt = enc.encode(&p.frame()); + let rec = enc.last_reconstruction().expect("reconstruction"); + let out = dec.decode(&pkt.data).unwrap_or_else(|e| panic!("frame {i}: decode error {e:?}")); + assert_eq!(out.len(), 1, "frame {i}: one picture per packet"); + let d = &out[0]; + assert_eq!((d.width as usize, d.height as usize), (p.w, p.h)); + for pl in 0..3 { + if d.planes[pl] != rec[pl] { + let first = d.planes[pl].iter().zip(&rec[pl]).position(|(a, b)| a != b).unwrap_or(usize::MAX); + let pw = if pl == 0 { p.w } else { p.w.div_ceil(2) }; + panic!("frame {i} plane {pl}: decoder output differs from the reconstruction (first at x {} y {})", first % pw, first / pw); + } + } + run.psnr_y.push(psnr(&p.y, &rec[0], bd)); + run.lf_levels.push(enc.last_loop_filter_level().expect("level")); + run.recons.push(rec); + run.packets.push(pkt); + } + run +} diff --git a/crates/av1enc/tests/ffmpeg_oracle.rs b/crates/av1enc/tests/ffmpeg_oracle.rs new file mode 100644 index 00000000..c1a087d2 --- /dev/null +++ b/crates/av1enc/tests/ffmpeg_oracle.rs @@ -0,0 +1,156 @@ +//! External oracle: streams decoded by ffmpeg's libdav1d must equal the encoder's reconstruction +//! bit for bit. Skipped when `ffmpeg` (with libdav1d) is not installed. ffmpeg's native `av1` +//! decoder only drives hardware decoders, so it is not used. + +mod common; + +use std::process::Command; + +use common::*; +use effectcraft_av1enc::{Encoder, EncoderConfig, Packet, RateControl}; + +fn have_libdav1d() -> bool { + let Ok(out) = Command::new("ffmpeg").args(["-hide_banner", "-decoders"]).output() else { + eprintln!("ffmpeg not found: skipping the external oracle"); + return false; + }; + let ok = out.status.success() && String::from_utf8_lossy(&out.stdout).contains("libdav1d"); + if !ok { + eprintln!("ffmpeg without libdav1d: skipping the external oracle"); + } + ok +} + +/// IVF container with one temporal unit (temporal delimiter + packet) per frame. +fn ivf(w: usize, h: usize, packets: &[Packet]) -> Vec { + let mut v = Vec::new(); + v.extend_from_slice(b"DKIF"); + v.extend_from_slice(&0u16.to_le_bytes()); + v.extend_from_slice(&32u16.to_le_bytes()); + v.extend_from_slice(b"AV01"); + v.extend_from_slice(&(w as u16).to_le_bytes()); + v.extend_from_slice(&(h as u16).to_le_bytes()); + v.extend_from_slice(&30u32.to_le_bytes()); + v.extend_from_slice(&1u32.to_le_bytes()); + v.extend_from_slice(&(packets.len() as u32).to_le_bytes()); + v.extend_from_slice(&0u32.to_le_bytes()); + for (i, p) in packets.iter().enumerate() { + v.extend_from_slice(&((p.data.len() + 2) as u32).to_le_bytes()); + v.extend_from_slice(&(i as u64).to_le_bytes()); + v.extend_from_slice(&[0x12, 0x00]); // OBU_TEMPORAL_DELIMITER + v.extend_from_slice(&p.data); + } + v +} + +/// Encodes `pics`, decodes the IVF stream with `ffmpeg -c:v libdav1d` and compares every +/// decoded frame with the encoder's reconstruction. +/// Returns the loop filter levels used. +fn check(name: &str, cfg: EncoderConfig, pics: &[Pic]) -> Vec { + let (w, h, bd) = (cfg.width as usize, cfg.height as usize, cfg.bit_depth); + let mut enc = Encoder::new(cfg).expect("config"); + let mut packets = Vec::new(); + let mut recons = Vec::new(); + let mut levels = Vec::new(); + for p in pics { + packets.push(enc.encode(&p.frame())); + recons.push(enc.last_reconstruction().expect("reconstruction")); + levels.push(enc.last_loop_filter_level().expect("level")); + } + let dir = std::env::temp_dir().join(format!("effectcraft-av1enc-{}-{name}", std::process::id())); + std::fs::create_dir_all(&dir).expect("temp dir"); + let input = dir.join("in.ivf"); + let output = dir.join("out.yuv"); + std::fs::write(&input, ivf(w, h, &packets)).expect("write ivf"); + let pix_fmt = if bd == 8 { "yuv420p" } else { "yuv420p10le" }; + let res = Command::new("ffmpeg") + .args(["-v", "error", "-y", "-c:v", "libdav1d", "-i"]) + .arg(&input) + .args(["-f", "rawvideo", "-pix_fmt", pix_fmt]) + .arg(&output) + .output() + .expect("run ffmpeg"); + let stderr = String::from_utf8_lossy(&res.stderr); + assert!(res.status.success() && stderr.trim().is_empty(), "{name}: ffmpeg -c:v libdav1d failed: {stderr}"); + let raw = std::fs::read(&output).expect("decoded yuv"); + let _ = std::fs::remove_dir_all(&dir); + let samples: Vec = + if bd == 8 { raw.iter().map(|&b| b as u16).collect() } else { raw.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect() }; + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let fsize = w * h + 2 * cw * ch; + assert_eq!(samples.len(), fsize * pics.len(), "{name}: decoded frame count"); + for (i, rec) in recons.iter().enumerate() { + let f = &samples[i * fsize..(i + 1) * fsize]; + let planes = [&f[..w * h], &f[w * h..w * h + cw * ch], &f[w * h + cw * ch..]]; + for p in 0..3 { + if planes[p] != &rec[p][..] { + let pw = if p == 0 { w } else { cw }; + let k = planes[p].iter().zip(&rec[p]).position(|(a, b)| a != b).unwrap_or(0); + panic!("{name}: frame {i} plane {p} differs from the reconstruction at ({}, {})", k % pw, k / pw); + } + } + } + eprintln!( + "{name}: libdav1d bit-exact over {} frames ({} bytes, loop filter levels {levels:?})", + pics.len(), + packets.iter().map(|p| p.data.len()).sum::() + ); + levels +} + +fn cfg(w: usize, h: usize, bd: u8, rate: RateControl, keyint: u32) -> EncoderConfig { + let mut c = EncoderConfig::new(w as u32, h as u32, 30, 1); + c.bit_depth = bd; + c.rate = rate; + c.keyint = keyint; + c +} + +#[test] +fn libdav1d_decodes_bit_exact() { + if !have_libdav1d() { + return; + } + // a moving pattern with inter frames + let pics: Vec = (0..8).map(|t| synth(176, 144, 8, t)).collect(); + check("inter8", cfg(176, 144, 8, RateControl::ConstantQ(90), 5), &pics); + // all-intra at a fine quantizer (large coefficients, Golomb codes) + let pics: Vec = (0..2).map(|t| noise(64, 48, 8, t as u32 + 3)).collect(); + check("noise-intra", cfg(64, 48, 8, RateControl::ConstantQ(4), 1), &pics); + // the finest quantizer: the largest coefficients, 8 and 10 bit, intra and inter + let pics: Vec = (0..3).map(|t| noise(72, 40, 8, t as u32 + 11)).collect(); + check("noise-q1", cfg(72, 40, 8, RateControl::ConstantQ(1), 2), &pics); + let pics: Vec = (0..3).map(|t| noise(40, 72, 10, t as u32 + 21)).collect(); + check("noise-q1-10", cfg(40, 72, 10, RateControl::ConstantQ(1), 2), &pics); + // odd sizes, 8 and 10 bit + let pics: Vec = (0..4).map(|t| synth(33, 17, 8, t)).collect(); + check("odd8", cfg(33, 17, 8, RateControl::ConstantQ(60), 3), &pics); + let pics: Vec = (0..5).map(|t| synth(101, 75, 10, t)).collect(); + check("odd10", cfg(101, 75, 10, RateControl::ConstantQ(110), 4), &pics); + // a noisy moving picture, bitrate mode + let pics: Vec = (0..6).map(|t| noisy(128, 80, t)).collect(); + let levels = check("bitrate", cfg(128, 80, 8, RateControl::Bitrate { kbps: 300 }, 10), &pics); + assert!(levels.iter().any(|&l| l > 0), "the deblocking filter is exercised"); + // a coarse quantizer: strong deblocking, 10 bit + let pics: Vec = (0..4).map(|t| synth(120, 88, 10, t)).collect(); + let levels = check("deblock10", cfg(120, 88, 10, RateControl::ConstantQ(220), 2), &pics); + assert!(levels.iter().any(|&l| l > 0), "the deblocking filter is exercised"); +} + +#[test] +fn libdav1d_decodes_1080p() { + if !have_libdav1d() { + return; + } + let pics: Vec = (0..3).map(|t| noisy(1920, 1080, t)).collect(); + check("1080p", cfg(1920, 1080, 8, RateControl::ConstantQ(120), 30), &pics); +} + +fn noisy(w: usize, h: usize, t: usize) -> Pic { + let mut p = synth(w, h, 8, t); + let n = noise(w, h, 8, 7 + t as u32); + for (a, b) in p.y.iter_mut().zip(&n.y) { + *a = (*a as i32 + (*b as i32 - 128) / 16).clamp(0, 255) as u16; + } + p +} diff --git a/crates/av1enc/tests/roundtrip.rs b/crates/av1enc/tests/roundtrip.rs new file mode 100644 index 00000000..1e777e50 --- /dev/null +++ b/crates/av1enc/tests/roundtrip.rs @@ -0,0 +1,92 @@ +//! Round trips through FilmCraft's spec-derived AV1 decoder: decoded frames must equal the +//! encoder's own reconstruction bit for bit. + +mod common; + +use common::*; +use effectcraft_av1enc::{EncoderConfig, RateControl}; + +fn cfg(w: u32, h: u32, bd: u8, q: u8, keyint: u32) -> EncoderConfig { + let mut c = EncoderConfig::new(w, h, 30, 1); + c.bit_depth = bd; + c.rate = RateControl::ConstantQ(q); + c.keyint = keyint; + c +} + +#[test] +fn intra_8bit_smooth_high_quality() { + let pics = vec![synth(128, 96, 8, 0)]; + let run = encode_and_check(cfg(128, 96, 8, 40, 1), &pics); + eprintln!("intra 8-bit q40: {} bytes, PSNR-Y {:.2} dB", run.packets[0].data.len(), run.psnr_y[0]); + assert!(run.packets[0].keyframe); + assert!(run.psnr_y[0] > 35.0, "PSNR {:.2}", run.psnr_y[0]); +} + +#[test] +fn odd_sizes_all_intra() { + for (w, h) in [(33, 17), (1, 1), (7, 9), (65, 63), (100, 30)] { + let pics: Vec = (0..2).map(|t| synth(w, h, 8, t)).collect(); + let run = encode_and_check(cfg(w as u32, h as u32, 8, 90, 1), &pics); + assert!(run.packets.iter().all(|p| p.keyframe)); + } +} + +#[test] +fn noise_low_q_exact() { + let pics = vec![noise(72, 40, 8, 1)]; + let run = encode_and_check(cfg(72, 40, 8, 1, 1), &pics); + assert!(run.psnr_y[0] > 40.0, "PSNR {:.2}", run.psnr_y[0]); +} + +#[test] +fn inter_moving_pattern() { + let pics: Vec = (0..6).map(|t| synth(160, 112, 8, t)).collect(); + let run = encode_and_check(cfg(160, 112, 8, 80, 30), &pics); + let sizes: Vec = run.packets.iter().map(|p| p.data.len()).collect(); + eprintln!("inter sizes {sizes:?} psnr {:?}", run.psnr_y); + assert!(run.packets[0].keyframe && run.packets[1..].iter().all(|p| !p.keyframe)); + let inter_avg = sizes[1..].iter().sum::() as f64 / (sizes.len() - 1) as f64; + assert!(inter_avg < sizes[0] as f64 * 0.5, "inter frames {inter_avg} vs key {}", sizes[0]); + assert!(run.psnr_y.iter().all(|&p| p > 33.0)); +} + +#[test] +fn ten_bit_inter() { + let pics: Vec = (0..4).map(|t| synth(96, 72, 10, t)).collect(); + let run = encode_and_check(cfg(96, 72, 10, 60, 3), &pics); + eprintln!("10-bit psnr {:?}", run.psnr_y); + assert!(run.psnr_y.iter().all(|&p| p > 35.0)); +} + +#[test] +fn odd_size_inter() { + let pics: Vec = (0..5).map(|t| synth(57, 43, 8, t)).collect(); + encode_and_check(cfg(57, 43, 8, 120, 10), &pics); + let pics: Vec = (0..3).map(|t| synth(33, 17, 10, t)).collect(); + encode_and_check(cfg(33, 17, 10, 120, 10), &pics); +} + +#[test] +fn bitrate_mode_tracks_target() { + let n = 12; + let pics: Vec = (0..n).map(|t| synth(192, 128, 8, t)).collect(); + let mut c = cfg(192, 128, 8, 0, 12); + c.rate = RateControl::Bitrate { kbps: 400 }; + let run = encode_and_check(c, &pics); + let bits: usize = run.packets.iter().map(|p| p.data.len() * 8).sum(); + let kbps = bits as f64 / (n as f64 / 30.0) / 1000.0; + eprintln!("bitrate mode: {kbps:.1} kbit/s for a 400 kbit/s target, psnr {:?}", run.psnr_y); + assert!(kbps > 400.0 / 2.0 && kbps < 400.0 * 2.0, "{kbps} kbit/s"); +} + +#[test] +fn headers() { + let enc = effectcraft_av1enc::Encoder::new(cfg(1920, 1080, 10, 100, 60)).unwrap(); + let c = enc.av1c(); + assert_eq!(c[0], 0x81); + assert_eq!(c[1] & 0x1f, 8, "1080p30 is level 4.0"); + assert_eq!(c[2], 0b0100_1100); + assert_eq!(&c[4..], &enc.sequence_header_obu()[..]); + assert_eq!(enc.sequence_header_obu()[0], 0x0A); +} diff --git a/crates/engine/src/commands/render_queue.rs b/crates/engine/src/commands/render_queue.rs index 365d7e55..c0eedaec 100644 --- a/crates/engine/src/commands/render_queue.rs +++ b/crates/engine/src/commands/render_queue.rs @@ -213,7 +213,7 @@ fn apply_output(om: &mut OutputModule, templates: &RenderTemplates, roi: Option< } let before = om.clone(); if let Some(f) = str_p(p, "format") { - let f = OutputFormat::from_name(f).ok_or_else(|| bad(cmd, "format: h264|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff"))?; + let f = OutputFormat::from_name(f).ok_or_else(|| bad(cmd, "format: h264|hevc|av1|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff"))?; om.set_format(f); } if let Some(c) = str_p(p, "channels") { @@ -223,7 +223,7 @@ fn apply_output(om: &mut OutputModule, templates: &RenderTemplates, roi: Option< "alpha" | "alphaonly" => Channels::Alpha, _ => return Err(bad(cmd, "channels: rgb|rgba|alpha")), }; - if om.channels == Channels::Rgba && !om.format.supports_alpha() { + if om.channels == Channels::Rgba && !om.supports_alpha() { return Err(bad(cmd, format!("{} has no alpha channel", om.format.label()))); } } @@ -234,6 +234,7 @@ fn apply_output(om: &mut OutputModule, templates: &RenderTemplates, roi: Option< _ => return Err(bad(cmd, "color: straight|premultiplied")), }; } + any |= apply_codec_options(om, p, cmd)?; if let Some(q) = f_p(p, "quality") { om.quality = q.clamp(1.0, 100.0) as u8; } @@ -377,6 +378,62 @@ fn apply_output(om: &mut OutputModule, templates: &RenderTemplates, roi: Option< Ok(any) } +/// HEVC / AV1 codec options, the WebM video codec and the Opus audio options. +fn apply_codec_options(om: &mut OutputModule, p: &Value, cmd: &str) -> Result { + use effectcraft_project::render_queue::{CodecProfile, OpusApplication, RateControlMode, VideoCodecOptions, WebmVideoCodec}; + let mut any = false; + if let Some(c) = str_p(p, "webmCodec") { + om.webm_codec = match c.to_ascii_lowercase().as_str() { + "vp9" => WebmVideoCodec::Vp9, + "av1" => WebmVideoCodec::Av1, + _ => return Err(bad(cmd, "webmCodec: vp9|av1")), + }; + if !om.supports_alpha() { + om.channels = Channels::Rgb; + } + any = true; + } + if let Some(v) = str_p(p, "profile") { + om.codec.profile = CodecProfile::from_name(v).ok_or_else(|| bad(cmd, "profile: main|main10"))?; + any = true; + } + match p.get("level") { + Some(Value::Null) => { + om.codec.level = None; + any = true; + } + Some(v) => { + let s = match v { + Value::String(s) => s.clone(), + other => other.to_string(), + }; + om.codec.level = VideoCodecOptions::parse_level(&s).ok_or_else(|| bad(cmd, "level: auto|null or a level like 4.1"))?; + any = true; + } + None => {} + } + if let Some(v) = str_p(p, "rateControl") { + om.codec.rate_control = RateControlMode::from_name(v).ok_or_else(|| bad(cmd, "rateControl: bitrate|quality"))?; + any = true; + } + if let Some(q) = f_p(p, "quality") { + om.codec.quality = q.clamp(1.0, 100.0) as u8; + } + if let Some(b) = f_p(p, "audioBitrate") { + om.opus_bitrate_kbps = b.clamp(6.0, 510.0) as u32; + any = true; + } + if let Some(a) = str_p(p, "opusApplication") { + om.opus_application = match a.to_ascii_lowercase().as_str() { + "audio" | "music" => OpusApplication::Audio, + "voip" | "voice" | "speech" => OpusApplication::Voip, + _ => return Err(bad(cmd, "opusApplication: audio|voice")), + }; + any = true; + } + Ok(any) +} + /// `module` (1-based; 1 = the first output module). fn module_p(s: &Session, i: usize, p: &Value, cmd: &str) -> Result { let n = p.get("module").and_then(Value::as_u64).unwrap_or(1) as usize; @@ -618,7 +675,17 @@ fn formats(s: &mut Session, _: &Value) -> Result { }) }) .collect(); - Ok(json!({"formats": v, "proresProfiles": ProResProfile::ALL.iter().map(|p| p.label()).collect::>()})) + use effectcraft_project::render_queue::{CodecProfile, VideoCodecOptions}; + let level = |l: &u8| format!("{}.{}", l / 10, l % 10); + Ok(json!({ + "formats": v, + "proresProfiles": ProResProfile::ALL.iter().map(|p| p.label()).collect::>(), + "codecProfiles": CodecProfile::ALL.iter().map(|p| p.label()).collect::>(), + "hevcLevels": VideoCodecOptions::HEVC_LEVELS.iter().map(level).collect::>(), + "av1Levels": VideoCodecOptions::AV1_LEVELS.iter().map(level).collect::>(), + "webmCodecs": ["vp9", "av1"], + "opusApplications": ["audio", "voice"], + })) } /// `item?|index?`, else the last item (the queue's newest entry). @@ -902,7 +969,7 @@ pub fn specs() -> Vec { "Add to Render Queue", ["Composition"], Some("Cmd+M"), - "{comp?: id|name, template?: Render Settings template, format?: h264|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff, output?: path|template, log?: errorsOnly|plusSettings|plusPerFrameInfo, quality?: best|draft|1-100 (jpeg, webm), resolution?: full|half|third|quarter|scale, proxyUse?, effects?, solo?, guideLayers?, colorDepth?, frameBlending?, fieldRender?, pulldown?, motionBlur?, timeSpan?: workArea|comp|custom, start?: s, end?: s, duration?: s, frameRate?: fps|null, skipExisting?: bool, storageOverflow?: bool, channels?: rgb|rgba|alpha, color?, bitrate?: kbps, webmBitrate?, keyframeInterval?, proresProfile?, crop?, resize?, includeProjectLink?, audio?: auto|on|off, sampleRate?, audioChannels?, audioFormat?, loop?: bool (values as in setRenderSettings / setOutputModule)}", + "{comp?: id|name, template?: Render Settings template, format?: h264|hevc|av1|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff, output?: path|template, log?: errorsOnly|plusSettings|plusPerFrameInfo, quality?: best|draft|1-100 (jpeg, webm), resolution?: full|half|third|quarter|scale, proxyUse?, effects?, solo?, guideLayers?, colorDepth?, frameBlending?, fieldRender?, pulldown?, motionBlur?, timeSpan?: workArea|comp|custom, start?: s, end?: s, duration?: s, frameRate?: fps|null, skipExisting?: bool, storageOverflow?: bool, channels?: rgb|rgba|alpha, color?, bitrate?: kbps, webmBitrate?, keyframeInterval?, proresProfile?, profile?, level?, rateControl?, webmCodec?, audioBitrate?, opusApplication?, crop?, resize?, includeProjectLink?, audio?: auto|on|off, sampleRate?, audioChannels?, audioFormat?, loop?: bool (values as in setRenderSettings / setOutputModule)}", can_add, add ), @@ -922,7 +989,7 @@ pub fn specs() -> Vec { "Output Module Settings...", [], None, - "{item?|index?, module?: n (1 = first), template?: name, format?, channels?: rgb|rgba|alpha, color?: straight|premultiplied, quality?: 1-100, bitrate?: kbps, webmBitrate?: bool, keyframeInterval?: frames, proresProfile?: proxy|lt|standard|hq|4444|4444xq, crop?: bool|{useRoi?, roi?, top?, left?, bottom?, right?}, cropTop?, cropLeft?, cropBottom?, cropRight?, resize?: bool|{preset?, width?, height?, lockAspect?, quality?: low|high}, resizeWidth?, resizeHeight?, postRenderAction?: none|import|importAndReplace|setProxy, includeProjectLink?: bool, audio?: auto|on|off, sampleRate?, audioChannels?: mono|stereo, audioFormat?: 16|24|32, loop?: bool, output?}", + "{item?|index?, module?: n (1 = first), template?: name, format?: h264|hevc|av1|prores|webm|png|jpeg|tiff|exr|gif|wav|aiff, channels?: rgb|rgba|alpha, color?: straight|premultiplied, quality?: 1-100, bitrate?: kbps, webmBitrate?: bool, keyframeInterval?: frames (0 = auto), proresProfile?: proxy|lt|standard|hq|4444|4444xq, profile?: main|main10 (HEVC/AV1), level?: auto|4.1, rateControl?: bitrate|quality, webmCodec?: vp9|av1, audioBitrate?: Opus kbps, opusApplication?: audio|voice, crop?: bool|{useRoi?, roi?, top?, left?, bottom?, right?}, cropTop?, cropLeft?, cropBottom?, cropRight?, resize?: bool|{preset?, width?, height?, lockAspect?, quality?: low|high}, resizeWidth?, resizeHeight?, postRenderAction?: none|import|importAndReplace|setProxy, includeProjectLink?: bool, audio?: auto|on|off, sampleRate?, audioChannels?: mono|stereo, audioFormat?: 16|24|32, loop?: bool, output?}", has_items, set_output_module ), diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 9213ff8d..5190ba82 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -790,6 +790,8 @@ mod tests_anim_tools; #[cfg(test)] mod tests_camera_track; #[cfg(test)] +mod tests_codec_options; +#[cfg(test)] mod tests_color_view; #[cfg(test)] mod tests_disk_cache; diff --git a/crates/engine/src/render_queue.rs b/crates/engine/src/render_queue.rs index 5e77131c..9756b183 100644 --- a/crates/engine/src/render_queue.rs +++ b/crates/engine/src/render_queue.rs @@ -173,7 +173,7 @@ pub fn bytes_per_frame(om: &OutputModule, w: u32, h: u32, fps: f64) -> f64 { let px = (w as f64 * h as f64) / (1920.0 * 1080.0); let mbps = |m: f64| m * 1_000_000.0 / 8.0; match om.format { - OutputFormat::H264 => om.bitrate_kbps as f64 * 1000.0 / 8.0 / fps, + OutputFormat::H264 | OutputFormat::Hevc | OutputFormat::Av1 => om.bitrate_kbps as f64 * 1000.0 / 8.0 / fps, OutputFormat::ProRes => { let rate = match om.prores_profile { Proxy => 45.0, diff --git a/crates/engine/src/rq_tests.rs b/crates/engine/src/rq_tests.rs index d4c127d0..8ffa8dcb 100644 --- a/crates/engine/src/rq_tests.rs +++ b/crates/engine/src/rq_tests.rs @@ -159,6 +159,6 @@ fn without_exporter() { s.execute("renderQueue.add", json!({})).unwrap(); assert!(!s.is_enabled("renderQueue.render")); let f = s.execute("renderQueue.formats", json!({})).unwrap(); - assert_eq!(f["formats"].as_array().unwrap().len(), 10); + assert_eq!(f["formats"].as_array().unwrap().len(), 12); assert_eq!(f["formats"][0]["available"], false); } diff --git a/crates/engine/src/tests_codec_options.rs b/crates/engine/src/tests_codec_options.rs new file mode 100644 index 00000000..21dc7e45 --- /dev/null +++ b/crates/engine/src/tests_codec_options.rs @@ -0,0 +1,53 @@ +//! M13.4: HEVC / AV1 output modules and the WebM codec / Opus options over the command API. + +use effectcraft_project::render_queue::{Channels, CodecProfile, OpusApplication, OutputFormat, RateControlMode, VideoCodecOptions, WebmVideoCodec}; +use serde_json::json; + +use crate::Session; + +fn session() -> Session { + let mut s = Session::default(); + s.execute("file.openDemoProject", json!({})).unwrap(); + s +} + +#[test] +fn hevc_and_av1_output_module_options() { + let mut s = session(); + let a = s + .execute( + "renderQueue.add", + json!({"format": "hevc", "profile": "main10", "level": "4.1", "rateControl": "quality", "quality": 85, "keyframeInterval": 30}), + ) + .unwrap(); + assert!(a["outputPath"].as_str().unwrap().ends_with(".mp4"), "{a}"); + let om = &s.project.render_queue[0].output; + assert_eq!(om.format, OutputFormat::Hevc); + assert_eq!(om.codec, VideoCodecOptions { profile: CodecProfile::Main10, level: Some(41), rate_control: RateControlMode::Quality, quality: 85 }); + assert_eq!(om.keyframe_interval, 30); + assert_eq!(a["output"]["codec"]["profile"], "Main10", "{a}"); + let id = a["item"].clone(); + s.execute("renderQueue.setOutputModule", json!({"item": id, "format": "av1", "level": null, "rateControl": "bitrate", "bitrate": 3000})).unwrap(); + let om = &s.project.render_queue[0].output; + assert_eq!((om.format, om.codec.level, om.codec.rate_control, om.bitrate_kbps), (OutputFormat::Av1, None, RateControlMode::Bitrate, 3000)); + assert!(s.execute("renderQueue.setOutputModule", json!({"item": id, "channels": "rgba"})).is_err(), "AV1 MP4 has no alpha"); + assert!(s.execute("renderQueue.setOutputModule", json!({"item": id, "profile": "high"})).is_err()); + assert!(s.execute("renderQueue.setOutputModule", json!({"item": id, "level": "0.5"})).is_err()); + assert!(s.execute("renderQueue.setOutputModule", json!({"item": id, "rateControl": "cbr2"})).is_err()); + + // WebM: VP9 with alpha, then AV1 (alpha dropped) with voice-tuned Opus. + s.execute("renderQueue.setOutputModule", json!({"item": id, "format": "webm", "channels": "rgba"})).unwrap(); + assert_eq!(s.project.render_queue[0].output.channels, Channels::Rgba); + s.execute("renderQueue.setOutputModule", json!({"item": id, "webmCodec": "av1", "audioBitrate": 24, "opusApplication": "voice"})).unwrap(); + let om = &s.project.render_queue[0].output; + assert_eq!((om.webm_codec, om.channels, om.opus_bitrate_kbps, om.opus_application), (WebmVideoCodec::Av1, Channels::Rgb, 24, OpusApplication::Voip)); + assert!(s.execute("renderQueue.setOutputModule", json!({"item": id, "webmCodec": "theora"})).is_err()); + + // The formats query lists the new formats and option values. + let f = s.execute("renderQueue.formats", json!({})).unwrap(); + let ids: Vec<&str> = f["formats"].as_array().unwrap().iter().filter_map(|v| v["id"].as_str()).collect(); + assert!(ids.contains(&"Hevc") && ids.contains(&"Av1"), "{ids:?}"); + assert_eq!(f["codecProfiles"], json!(["Main", "Main 10"])); + assert!(f["hevcLevels"].as_array().unwrap().contains(&json!("4.1"))); + assert_eq!(f["webmCodecs"], json!(["vp9", "av1"])); +} diff --git a/crates/export/Cargo.toml b/crates/export/Cargo.toml index 6900c204..11f398cf 100644 --- a/crates/export/Cargo.toml +++ b/crates/export/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "effectcraft-export" -description = "Render Queue jobs and output modules: H.264 MP4 (+AAC) and ProRes MOV (+PCM) via FilmCraft's pure-Rust encoders, WebM (VP9 + Opus), PNG/JPEG/TIFF/EXR sequences, animated GIF, WAV/AIFF" +description = "Render Queue jobs and output modules: H.264 MP4 (+AAC) and ProRes MOV (+PCM) via FilmCraft, HEVC / AV1 MP4 (+AAC) and WebM (VP9 or AV1 + Opus) via EffectCraft encoders, PNG/JPEG/TIFF/EXR sequences, animated GIF, WAV/AIFF" version.workspace = true edition.workspace = true license.workspace = true @@ -20,6 +20,8 @@ filmcraft-aac = { workspace = true } filmcraft-isobmff = { workspace = true } effectcraft-opusenc = { workspace = true } effectcraft-vp9enc = { workspace = true } +effectcraft-hevcenc = { workspace = true } +effectcraft-av1enc = { workspace = true } image = { workspace = true, features = ["png", "jpeg", "tiff", "exr"] } gif = { workspace = true } rayon = { workspace = true } diff --git a/crates/export/src/encode.rs b/crates/export/src/encode.rs index b0566d9b..167b5f33 100644 --- a/crates/export/src/encode.rs +++ b/crates/export/src/encode.rs @@ -1,4 +1,5 @@ -//! Movie export: FilmCraft H.264 → MP4 (+ AAC) and ProRes → MOV (+ PCM), muxed by +//! Movie export: FilmCraft H.264, HEVC (`effectcraft-hevcenc`) and AV1 (`effectcraft-av1enc`) +//! → MP4 (+ AAC) and ProRes → MOV (+ PCM), muxed by //! FilmCraft's ISO BMFF / QuickTime writer. use std::io::Write; @@ -18,14 +19,14 @@ fn mux_err(e: impl std::fmt::Display) -> ExportError { ExportError::Io(e.to_string()) } -struct Packet { - data: Vec, - key: bool, +pub(crate) struct Packet { + pub(crate) data: Vec, + pub(crate) key: bool, /// pts − dts in the track timescale. - cto: i32, + pub(crate) cto: i32, } -trait VideoEncoder { +pub(crate) trait VideoEncoder { fn sample_entry(&self) -> SampleEntry; fn encode(&mut self, rgba: &[u8], index: u64) -> Result>; fn flush(&mut self) -> Result>; @@ -257,9 +258,11 @@ pub(crate) fn movie(job: &Cx, comp: &Comp, w: u32, h: u32, st: &mut State) -> Re }; let mut venc: Box = match fmt { OutputFormat::H264 => Box::new(H264::new(w, h, rate, job.output.bitrate_kbps)?), + OutputFormat::Hevc => Box::new(crate::hevc_av1::Hevc::new(w, h, rate, job.output)?), + OutputFormat::Av1 => Box::new(crate::hevc_av1::Av1::new(w, h, rate, job.output)?), _ => Box::new(ProRes::new(w, h, job.output.prores_profile, if channels == Channels::Alpha { Channels::Rgb } else { channels })), }; - let brand = if fmt == OutputFormat::H264 { Brand::Mp4 } else { Brand::Mov }; + let brand = if fmt == OutputFormat::ProRes { Brand::Mov } else { Brand::Mp4 }; let batch = batch_size(); let render = |k: u64| -> Vec { job.pixels(&job.frame(comp, k), comp, channels, w, h) }; diff --git a/crates/export/src/hevc_av1.rs b/crates/export/src/hevc_av1.rs new file mode 100644 index 00000000..f42ccda2 --- /dev/null +++ b/crates/export/src/hevc_av1.rs @@ -0,0 +1,203 @@ +//! HEVC (`effectcraft-hevcenc`) and AV1 (`effectcraft-av1enc`) video encoders for the MP4 +//! movie writer (`hvc1` / `av01` sample entries) and AV1 in WebM, configured from the Output +//! Module's [`VideoCodecOptions`]. + +use effectcraft_project::render_queue::{CodecProfile, OutputModule, RateControlMode, VideoCodecOptions}; +use effectcraft_time::FrameRate; +use filmcraft_isobmff::{Av1Config, CodecConfig, FourCc, HevcConfig, SampleEntry}; +use rayon::prelude::*; + +use crate::encode::{Packet, VideoEncoder}; +use crate::{ExportError, Result}; + +fn enc_err(e: impl std::fmt::Display) -> ExportError { + ExportError::Encode(e.to_string()) +} + +/// BT.709 limited-range 4:2:0 planes at `bits` (8 or 10) from RGBA8 (2×2 chroma average; +/// alpha ignored). Chroma planes are `ceil(w/2)` × `ceil(h/2)`. +pub(crate) fn rgba_to_yuv420_bits(rgba: &[u8], w: usize, h: usize, bits: u8, y: &mut Vec, u: &mut Vec, v: &mut Vec) { + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let scale = (1u32 << (bits - 8)) as f32; + let (lo, hi) = (scale, 254.0 * scale + (scale - 1.0)); + y.resize(w * h, 0); + u.resize(cw * ch, 0); + v.resize(cw * ch, 0); + y.par_chunks_mut(w * 2).zip(u.par_chunks_mut(cw).zip(v.par_chunks_mut(cw))).enumerate().for_each(|(cy, (yr, (ur, vr)))| { + let rows = yr.len() / w; + let mut acc = vec![(0f32, 0f32, 0f32); cw]; + for dy in 0..rows { + let src = &rgba[(cy * 2 + dy) * w * 4..][..w * 4]; + for x in 0..w { + let (r, g, b) = (src[x * 4] as f32 / 255.0, src[x * 4 + 1] as f32 / 255.0, src[x * 4 + 2] as f32 / 255.0); + let l = 0.2126 * r + 0.7152 * g + 0.0722 * b; + yr[dy * w + x] = ((16.0 + 219.0 * l) * scale).round().clamp(lo, hi) as u16; + let a = &mut acc[x / 2]; + a.0 += (b - l) / 1.8556; + a.1 += (r - l) / 1.5748; + a.2 += 1.0; + } + } + for (cx, a) in acc.iter().enumerate() { + ur[cx] = ((128.0 + 224.0 * a.0 / a.2) * scale).round().clamp(lo, hi) as u16; + vr[cx] = ((128.0 + 224.0 * a.1 / a.2) * scale).round().clamp(lo, hi) as u16; + } + }); +} + +#[derive(Default)] +struct Planes { + y: Vec, + u: Vec, + v: Vec, +} + +// ---------------------------------------------------------------- HEVC + +pub(crate) struct Hevc { + enc: effectcraft_hevcenc::Encoder, + w: u32, + h: u32, + bits: u8, + planes: Planes, +} + +/// The HEVC encoder settings for an output module. +pub(crate) fn hevc_config(w: u32, h: u32, rate: FrameRate, om: &OutputModule) -> effectcraft_hevcenc::EncoderConfig { + use effectcraft_hevcenc as he; + let o: &VideoCodecOptions = &om.codec; + let mut cfg = he::EncoderConfig::new(w, h, rate.num as u32, rate.den as u32); + cfg.profile = match o.profile { + CodecProfile::Main => he::Profile::Main, + CodecProfile::Main10 => he::Profile::Main10, + }; + cfg.level_idc = o.hevc_level_idc(); + cfg.rate = match o.rate_control { + RateControlMode::Bitrate => he::RateControl::Bitrate { kbps: om.bitrate_kbps.max(50) }, + RateControlMode::Quality => he::RateControl::ConstantQp(o.hevc_qp()), + }; + cfg.keyint = om.keyint(rate.as_f64()); + cfg +} + +impl Hevc { + pub(crate) fn new(w: u32, h: u32, rate: FrameRate, om: &OutputModule) -> Result { + let cfg = hevc_config(w, h, rate, om); + let bits = cfg.profile.bit_depth(); + let enc = effectcraft_hevcenc::Encoder::new(cfg).map_err(enc_err)?; + Ok(Hevc { enc, w, h, bits, planes: Planes::default() }) + } +} + +impl VideoEncoder for Hevc { + fn sample_entry(&self) -> SampleEntry { + let cfg = HevcConfig::parse(&self.enc.hvcc()).unwrap_or_default(); + SampleEntry::hevc(cfg, self.w as u16, self.h as u16) + } + fn encode(&mut self, rgba: &[u8], _index: u64) -> Result> { + let (w, h) = (self.w as usize, self.h as usize); + let p = &mut self.planes; + rgba_to_yuv420_bits(rgba, w, h, self.bits, &mut p.y, &mut p.u, &mut p.v); + let frame = effectcraft_hevcenc::Frame { y: &p.y, u: &p.u, v: &p.v, y_stride: w, uv_stride: w.div_ceil(2) }; + let pk = self.enc.encode(&frame); + Ok(vec![Packet { data: pk.data, key: pk.keyframe, cto: 0 }]) + } + fn flush(&mut self) -> Result> { + Ok(vec![]) + } +} + +// ---------------------------------------------------------------- AV1 + +pub(crate) struct Av1 { + enc: effectcraft_av1enc::Encoder, + w: u32, + h: u32, + bits: u8, + planes: Planes, +} + +/// The AV1 encoder settings for an output module. +pub(crate) fn av1_config(w: u32, h: u32, rate: FrameRate, om: &OutputModule) -> effectcraft_av1enc::EncoderConfig { + use effectcraft_av1enc as ae; + let o: &VideoCodecOptions = &om.codec; + let mut cfg = ae::EncoderConfig::new(w, h, rate.num as u32, rate.den as u32); + cfg.bit_depth = o.profile.bit_depth(); + cfg.level_idx = o.av1_level_idx(); + cfg.rate = match o.rate_control { + RateControlMode::Bitrate => ae::RateControl::Bitrate { kbps: om.bitrate_kbps.max(50) }, + RateControlMode::Quality => ae::RateControl::ConstantQ(o.av1_qindex()), + }; + cfg.keyint = om.keyint(rate.as_f64()); + cfg +} + +impl Av1 { + pub(crate) fn new(w: u32, h: u32, rate: FrameRate, om: &OutputModule) -> Result { + let cfg = av1_config(w, h, rate, om); + let bits = cfg.bit_depth; + let enc = effectcraft_av1enc::Encoder::new(cfg).map_err(enc_err)?; + Ok(Av1 { enc, w, h, bits, planes: Planes::default() }) + } + /// The `av1C` record (also the WebM CodecPrivate). + pub(crate) fn av1c(&self) -> Vec { + self.enc.av1c() + } +} + +impl VideoEncoder for Av1 { + fn sample_entry(&self) -> SampleEntry { + let cfg = Av1Config::parse(&self.enc.av1c()).unwrap_or_default(); + SampleEntry::video(FourCc(*b"av01"), CodecConfig::Av1(cfg), self.w as u16, self.h as u16) + } + fn encode(&mut self, rgba: &[u8], _index: u64) -> Result> { + let (w, h) = (self.w as usize, self.h as usize); + let p = &mut self.planes; + rgba_to_yuv420_bits(rgba, w, h, self.bits, &mut p.y, &mut p.u, &mut p.v); + let frame = effectcraft_av1enc::Frame { y: &p.y, u: &p.u, v: &p.v, y_stride: w, uv_stride: w.div_ceil(2) }; + let pk = self.enc.encode(&frame); + Ok(vec![Packet { data: pk.data, key: pk.keyframe, cto: 0 }]) + } + fn flush(&mut self) -> Result> { + Ok(vec![]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ten_bit_planes_scale_eight_bit_ones() { + let rgba = [255u8, 255, 255, 255, 0, 0, 0, 255, 255, 0, 0, 255, 0, 255, 0, 255]; + let (mut y, mut u, mut v) = (vec![], vec![], vec![]); + rgba_to_yuv420_bits(&rgba, 2, 2, 8, &mut y, &mut u, &mut v); + assert_eq!((y[0], y[1]), (235, 16)); + rgba_to_yuv420_bits(&rgba, 2, 2, 10, &mut y, &mut u, &mut v); + assert_eq!((y[0], y[1]), (940, 64)); + assert_eq!(u.len(), 1); + } + + #[test] + fn codec_options_map_to_encoder_settings() { + let mut om = OutputModule::for_format(effectcraft_project::render_queue::OutputFormat::Hevc); + om.codec.profile = CodecProfile::Main10; + om.codec.level = Some(41); + om.codec.rate_control = RateControlMode::Quality; + om.codec.quality = 100; + om.keyframe_interval = 12; + let rate = FrameRate::new(30, 1); + let h = hevc_config(64, 32, rate, &om); + assert_eq!(h.profile, effectcraft_hevcenc::Profile::Main10); + assert_eq!(h.level_idc, Some(123)); + assert_eq!(h.rate, effectcraft_hevcenc::RateControl::ConstantQp(4)); + assert_eq!(h.keyint, 12); + let a = av1_config(64, 32, rate, &om); + assert_eq!((a.bit_depth, a.level_idx, a.keyint), (10, Some(9), 12)); + om.codec.rate_control = RateControlMode::Bitrate; + om.keyframe_interval = 0; + om.bitrate_kbps = 3000; + let a = av1_config(64, 32, rate, &om); + assert_eq!((a.rate, a.keyint), (effectcraft_av1enc::RateControl::Bitrate { kbps: 3000 }, 60)); + } +} diff --git a/crates/export/src/lib.rs b/crates/export/src/lib.rs index f1e3269f..ae889742 100644 --- a/crates/export/src/lib.rs +++ b/crates/export/src/lib.rs @@ -7,7 +7,9 @@ //! | PNG / JPEG / TIFF sequence | one file per frame | 8-bit | PNG, TIFF | — | //! | OpenEXR sequence | one file per frame | 32-bit float, linear light, premultiplied | yes | — | //! | Animated GIF | GIF89a | 256-colour palette per frame (NeuQuant) | 1-bit | — | -//! | WebM | WebM (Matroska) | VP9 profile 0 (`effectcraft-vp9enc`: key + inter frames, a key frame every 2 s, loop filter), 8-bit 4:2:0 | VP9 alpha (BlockAdditional) | Opus (`effectcraft-opusenc`), 48 kHz stereo | +//! | WebM | WebM (Matroska) | VP9 profile 0 (`effectcraft-vp9enc`: key + inter frames, a key frame every 2 s, loop filter), 8-bit 4:2:0; or AV1 (`effectcraft-av1enc`) | VP9 alpha (BlockAdditional) | Opus (`effectcraft-opusenc`: SILK / hybrid / CELT by bitrate and application), 48 kHz | +//! | HEVC | MP4 (`hvc1`) | `effectcraft-hevcenc` Main / Main 10 4:2:0, I + P slices, bitrate or constant quality, level, key-frame interval | no | AAC-LC (FilmCraft) | +//! | AV1 | MP4 (`av01`) | `effectcraft-av1enc` Main 8/10-bit 4:2:0, key + inter frames | no | AAC-LC (FilmCraft) | //! | WAV / AIFF | RIFF WAVE / AIFF | — | — | PCM 16/24-bit (WAV also 32-bit float), mono/stereo (audio only) | //! //! Every frame goes through [`pipeline`]: the Render Settings overrides (Effects, Solo Switches, @@ -27,9 +29,11 @@ //! output is a single pixel wide/high). mod encode; +mod hevc_av1; mod out; mod pipeline; mod webm; +mod webm_av1; use web_time::Instant; @@ -173,7 +177,7 @@ fn export_with(cx: &Cx, progress: &mut dyn FnMut(&Progress) -> bool, t0: Instant st.advance(0)?; let (w, h) = output_size(comp, cx.settings, cx.output); let mut report = match cx.output.format { - OutputFormat::H264 | OutputFormat::ProRes => encode::movie(cx, comp, w, h, &mut st)?, + OutputFormat::H264 | OutputFormat::Hevc | OutputFormat::Av1 | OutputFormat::ProRes => encode::movie(cx, comp, w, h, &mut st)?, OutputFormat::Gif => gif_export(cx, comp, w, h, &mut st)?, OutputFormat::WebM => webm::webm(cx, comp, w, h, &mut st)?, OutputFormat::Wav => webm::audio_file(cx, comp, false, &mut st)?, diff --git a/crates/export/src/webm.rs b/crates/export/src/webm.rs index af38eaeb..de7c8684 100644 --- a/crates/export/src/webm.rs +++ b/crates/export/src/webm.rs @@ -22,25 +22,24 @@ use rayon::prelude::*; use crate::encode::{mix, pcm_bytes}; use crate::{Cx, Report, Result, State, batch_size, io, wants_audio}; -const OPUS_RATE: u32 = 48_000; -const OPUS_BITRATE: u32 = 192_000; +pub(crate) const OPUS_RATE: u32 = 48_000; // Element ids (with their length markers). -const EBML: u32 = 0x1A45_DFA3; -const SEGMENT: u32 = 0x1853_8067; -const INFO: u32 = 0x1549_A966; -const TRACKS: u32 = 0x1654_AE6B; -const TRACK_ENTRY: u32 = 0xAE; -const CLUSTER: u32 = 0x1F43_B675; +pub(crate) const EBML: u32 = 0x1A45_DFA3; +pub(crate) const SEGMENT: u32 = 0x1853_8067; +pub(crate) const INFO: u32 = 0x1549_A966; +pub(crate) const TRACKS: u32 = 0x1654_AE6B; +pub(crate) const TRACK_ENTRY: u32 = 0xAE; +pub(crate) const CLUSTER: u32 = 0x1F43_B675; -fn id_bytes(id: u32) -> Vec { +pub(crate) fn id_bytes(id: u32) -> Vec { let b = id.to_be_bytes(); let skip = b.iter().position(|x| *x != 0).unwrap_or(3); b[skip..].to_vec() } /// EBML variable-size integer for an element data size (shortest form). -fn vint(n: u64) -> Vec { +pub(crate) fn vint(n: u64) -> Vec { for len in 1..=8u32 { let max = (1u64 << (7 * len)) - 2; if n <= max { @@ -53,23 +52,23 @@ fn vint(n: u64) -> Vec { v } -fn el(out: &mut Vec, id: u32, data: &[u8]) { +pub(crate) fn el(out: &mut Vec, id: u32, data: &[u8]) { out.extend(id_bytes(id)); out.extend(vint(data.len() as u64)); out.extend_from_slice(data); } -fn el_uint(out: &mut Vec, id: u32, v: u64) { +pub(crate) fn el_uint(out: &mut Vec, id: u32, v: u64) { let b = v.to_be_bytes(); let skip = b.iter().position(|x| *x != 0).unwrap_or(7); el(out, id, &b[skip..]); } -fn el_float(out: &mut Vec, id: u32, v: f64) { +pub(crate) fn el_float(out: &mut Vec, id: u32, v: f64) { el(out, id, &v.to_be_bytes()); } -fn el_str(out: &mut Vec, id: u32, s: &str) { +pub(crate) fn el_str(out: &mut Vec, id: u32, s: &str) { el(out, id, s.as_bytes()); } @@ -125,7 +124,20 @@ fn cluster(frames: &mut [Frame]) -> Vec { out } +/// The Opus encoder of a WebM output: the module's channel count, bitrate and application. +pub(crate) fn opus_encoder(job: &Cx) -> effectcraft_opusenc::OpusEncoder { + let channels = if job.output.audio_channels == 1 { 1 } else { 2 }; + let app = match job.output.opus_application { + effectcraft_project::render_queue::OpusApplication::Audio => effectcraft_opusenc::Application::Audio, + effectcraft_project::render_queue::OpusApplication::Voip => effectcraft_opusenc::Application::Voip, + }; + effectcraft_opusenc::OpusEncoder::with_application(channels, job.output.opus_bitrate_kbps.clamp(6, 510) * 1000, app) +} + pub(crate) fn webm(job: &Cx, comp: &Comp, w: u32, h: u32, st: &mut State) -> Result { + if job.output.webm_codec == effectcraft_project::render_queue::WebmVideoCodec::Av1 { + return crate::webm_av1::webm_av1(job, comp, w, h, st); + } let rate = job.settings.rate(comp); let alpha = job.output.channels == Channels::Rgba; let colour_channels = if job.output.channels == Channels::Alpha { Channels::Alpha } else { Channels::Rgb }; @@ -145,7 +157,7 @@ pub(crate) fn webm(job: &Cx, comp: &Comp, w: u32, h: u32, st: &mut State) -> Res let mut alpha_enc = alpha.then(|| effectcraft_vp9enc::Vp9Encoder::new(cfg.clone())); let with_audio = wants_audio(job); let opus_channels = if job.output.audio_channels == 1 { 1usize } else { 2 }; - let mut opus = with_audio.then(|| effectcraft_opusenc::OpusEncoder::new(opus_channels as _, OPUS_BITRATE)); + let mut opus = with_audio.then(|| opus_encoder(job)); let total = st.total; let frame_ms = |k: u64| -> i64 { ((k as i128 * 1000 * rate.den as i128 + rate.num as i128 / 2) / rate.num as i128) as i64 }; let duration_ms = frame_ms(total) as f64; diff --git a/crates/export/src/webm_av1.rs b/crates/export/src/webm_av1.rs new file mode 100644 index 00000000..69378e4f --- /dev/null +++ b/crates/export/src/webm_av1.rs @@ -0,0 +1,177 @@ +//! WebM with AV1 video (`V_AV1`, CodecPrivate = the `av1C` record, per the AV1-in-Matroska +//! codec mapping) and Opus audio. Frames are rendered in parallel batches and encoded in order +//! (AV1 inter frames reference the previous frame); each batch becomes one Cluster whose +//! SimpleBlocks carry the key-frame flag only on AV1 key frames (the Cues-less layout of the +//! VP9 writer: players seek by scanning clusters). + +use std::io::{Seek, SeekFrom, Write}; + +use effectcraft_project::Comp; +use effectcraft_project::render_queue::Channels; +use effectcraft_time::{TICKS_PER_SECOND, Tick}; +use rayon::prelude::*; + +use crate::encode::VideoEncoder; +use crate::encode::mix; +use crate::webm::{CLUSTER, EBML, INFO, OPUS_RATE, SEGMENT, TRACK_ENTRY, TRACKS, el, el_float, el_str, el_uint, id_bytes, opus_encoder, vint}; +use crate::{Cx, Report, Result, State, batch_size, io, wants_audio}; + +struct Block { + ms: i64, + track: u64, + key: bool, + data: Vec, +} + +fn cluster(blocks: &mut [Block]) -> Vec { + blocks.sort_by_key(|b| (b.ms, b.track)); + let base = blocks.first().map_or(0, |b| b.ms); + let mut body = vec![]; + el_uint(&mut body, 0xE7, base.max(0) as u64); + for b in blocks.iter() { + let rel = (b.ms - base).clamp(i16::MIN as i64, i16::MAX as i64) as i16; + let mut p = vint(b.track); + p.extend_from_slice(&rel.to_be_bytes()); + p.push(if b.key { 0x80 } else { 0x00 }); + p.extend_from_slice(&b.data); + el(&mut body, 0xA3, &p); + } + let mut out = vec![]; + el(&mut out, CLUSTER, &body); + out +} + +pub(crate) fn webm_av1(job: &Cx, comp: &Comp, w: u32, h: u32, st: &mut State) -> Result { + let rate = job.settings.rate(comp); + let mut venc = crate::hevc_av1::Av1::new(w, h, rate, job.output)?; + let with_audio = wants_audio(job); + let opus_channels = if job.output.audio_channels == 1 { 1usize } else { 2 }; + let mut opus = with_audio.then(|| opus_encoder(job)); + let total = st.total; + let frame_ms = |k: u64| -> i64 { ((k as i128 * 1000 * rate.den as i128 + rate.num as i128 / 2) / rate.num as i128) as i64 }; + let duration_ms = frame_ms(total) as f64; + + let path = job.place(job.path, 1); + let mut file = crate::out::create(job.sink, &path)?; + let mut head = vec![]; + let mut eb = vec![]; + el_uint(&mut eb, 0x4286, 1); + el_uint(&mut eb, 0x42F7, 1); + el_uint(&mut eb, 0x42F2, 4); + el_uint(&mut eb, 0x42F3, 8); + el_str(&mut eb, 0x4282, "webm"); + el_uint(&mut eb, 0x4287, 4); + el_uint(&mut eb, 0x4285, 2); + el(&mut head, EBML, &eb); + head.extend(id_bytes(SEGMENT)); + let size_pos = head.len() as u64; + head.extend_from_slice(&[0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); + let seg_start = head.len() as u64; + let mut info = vec![]; + el_uint(&mut info, 0x2AD7B1, 1_000_000); + el_float(&mut info, 0x4489, duration_ms); + el_str(&mut info, 0x4D80, "EffectCraft"); + el_str(&mut info, 0x5741, "EffectCraft"); + el(&mut head, INFO, &info); + let mut tracks = vec![]; + let mut v = vec![]; + el_uint(&mut v, 0xD7, 1); + el_uint(&mut v, 0x73C5, 1); + el_uint(&mut v, 0x83, 1); + el_uint(&mut v, 0x9C, 0); + el_str(&mut v, 0x86, "V_AV1"); + el(&mut v, 0x63A2, &venc.av1c()); + el_uint(&mut v, 0x23E383, (1_000_000_000u128 * rate.den as u128 / rate.num.max(1) as u128) as u64); + let mut video = vec![]; + el_uint(&mut video, 0xB0, w as u64); + el_uint(&mut video, 0xBA, h as u64); + el(&mut v, 0xE0, &video); + el(&mut tracks, TRACK_ENTRY, &v); + if let Some(o) = &opus { + let mut a = vec![]; + el_uint(&mut a, 0xD7, 2); + el_uint(&mut a, 0x73C5, 2); + el_uint(&mut a, 0x83, 2); + el_uint(&mut a, 0x9C, 0); + el_str(&mut a, 0x86, "A_OPUS"); + el(&mut a, 0x63A2, &o.opus_head()); + el_uint(&mut a, 0x56AA, o.pre_skip() as u64 * 1_000_000_000 / OPUS_RATE as u64); + el_uint(&mut a, 0x56BB, 80_000_000); + let mut au = vec![]; + el_float(&mut au, 0xB5, OPUS_RATE as f64); + el_uint(&mut au, 0x9F, opus_channels as u64); + el(&mut a, 0xE1, &au); + el(&mut tracks, TRACK_ENTRY, &a); + } + el(&mut head, TRACKS, &tracks); + file.write_all(&head).map_err(io)?; + let mut seg_len = head.len() as u64 - seg_start; + + let (span_start, span_end) = job.settings.span(comp); + let mut cursor = span_start.to_units_floor(OPUS_RATE as i64); + let mut pcm_left: Vec = vec![]; + let mut packets = 0u64; + let mut audio_until = |until: Tick, blocks: &mut Vec, finish: bool| { + let Some(enc) = opus.as_mut() else { return }; + let end = until.to_units_floor(OPUS_RATE as i64); + if end > cursor { + let n = (end - cursor) as usize; + let start = Tick(((cursor as i128 * TICKS_PER_SECOND as i128) / OPUS_RATE as i128) as i64); + pcm_left.extend(mix(job, start, n, OPUS_RATE)); + cursor = end; + } + let fs = effectcraft_opusenc::OpusEncoder::FRAME_SIZE * opus_channels; + if finish && !pcm_left.is_empty() { + let pad = (fs - pcm_left.len() % fs) % fs + fs; + pcm_left.extend(std::iter::repeat_n(0.0, pad)); + } + while pcm_left.len() >= fs { + let pkt = enc.encode_float(&pcm_left[..fs]); + pcm_left.drain(..fs); + blocks.push(Block { ms: packets as i64 * 20, track: 2, key: true, data: pkt }); + packets += 1; + } + }; + + let batch = batch_size(); + let mut i = 0; + let flush_blocks = |blocks: &mut Vec, file: &mut crate::out::Out, seg_len: &mut u64| -> Result<()> { + if !blocks.is_empty() { + let c = cluster(blocks); + file.write_all(&c).map_err(io)?; + *seg_len += c.len() as u64; + blocks.clear(); + } + Ok(()) + }; + let mut blocks = vec![]; + while i < total { + let end = (i + batch).min(total); + // RGB, or the alpha matte alone (Channels: Alpha); AV1 in WebM carries no alpha track. + let channels = if job.output.channels == Channels::Alpha { Channels::Alpha } else { Channels::Rgb }; + let frames: Vec> = (i..end).into_par_iter().map(|k| job.pixels(&job.frame(comp, k), comp, channels, w, h)).collect(); + for (j, px) in frames.iter().enumerate() { + let k = i + j as u64; + for p in venc.encode(px, k)? { + blocks.push(Block { ms: frame_ms(k), track: 1, key: p.key, data: p.data }); + } + } + let t_end = if end >= total { span_end } else { job.settings.frame_time(comp, end) }; + audio_until(t_end, &mut blocks, end >= total); + flush_blocks(&mut blocks, &mut file, &mut seg_len)?; + st.advance(end - i)?; + i = end; + } + if total == 0 { + audio_until(span_end, &mut blocks, true); + flush_blocks(&mut blocks, &mut file, &mut seg_len)?; + } + let mut size = [0u8; 8]; + size[0] = 0x01; + size[1..].copy_from_slice(&seg_len.to_be_bytes()[1..]); + file.seek(SeekFrom::Start(size_pos)).map_err(io)?; + file.write_all(&size).map_err(io)?; + file.seek(SeekFrom::End(0)).map_err(io)?; + let bytes = file.finish()?; + Ok(Report { path, frames: 0, width: w, height: h, seconds: 0.0, bytes, audio: with_audio, log: None, overflow: vec![] }) +} diff --git a/crates/export/tests/hevc_av1_roundtrip.rs b/crates/export/tests/hevc_av1_roundtrip.rs new file mode 100644 index 00000000..93a82719 --- /dev/null +++ b/crates/export/tests/hevc_av1_roundtrip.rs @@ -0,0 +1,313 @@ +//! HEVC (MP4 `hvc1`) and AV1 (MP4 `av01`, WebM `V_AV1`) export, decoded back through +//! EffectCraft's own import path (FilmCraft's demuxers and HEVC / AV1 decoders) and — when +//! installed — ffprobe / ffmpeg as external oracles (skipped otherwise). +//! +//! The comp: 64×48 @ 10 fps, 1.2 s (12 frames), dark-blue background, a red solid of 32×48 +//! centred, starting at frame 5 (inter frames code both static and changing blocks). + +use std::path::{Path, PathBuf}; +use std::process::Command; + +use effectcraft_color::Label; +use effectcraft_export::render_queue::{ + AudioOutput, CodecProfile, OpusApplication, OutputFormat, OutputModule, RateControlMode, RenderSettings, TimeSpan, WebmVideoCodec, +}; +use effectcraft_export::{Job, Progress, export}; +use effectcraft_media::MediaPool; +use effectcraft_project::{Comp, ItemId, ItemKind, LayerSource, Project, Solid, build}; +use effectcraft_raster::Image; +use effectcraft_render::{FootageSource, NoFootage}; +use effectcraft_time::{FrameRate, Tick}; + +const W: u32 = 64; +const H: u32 = 48; +const BG: [f32; 3] = [0.1, 0.1, 0.4]; +const FRAMES: u64 = 12; + +fn out_dir(name: &str) -> PathBuf { + let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/test-out/export-hevc-av1").join(name); + let _ = std::fs::remove_dir_all(&d); + std::fs::create_dir_all(&d).expect("mkdir"); + d +} + +fn project(audio: Option<&Path>) -> (Project, ItemId) { + let mut p = Project::default(); + let mut comp = Comp::new(W, H, FrameRate::new(10, 1), Tick::from_seconds_f64(1.2)); + comp.background = BG; + let sid = p.add_item("Red", Label::Red, None, ItemKind::Solid(Solid { color: [1.0, 0.0, 0.0], width: 32, height: H, pixel_aspect: 1.0 })); + let mut l = build::layer(&mut p, &comp, "Red", LayerSource::Solid { item: sid }, (32, H), None); + l.in_point = comp.frame_rate.tick_of(5); + comp.layers.push(l); + if let Some(path) = audio { + let f = effectcraft_media::probe(path).expect("probe wav"); + let fid = p.add_item("tone.wav", Label::SeaFoam, None, ItemKind::Footage(f)); + let l = build::layer(&mut p, &comp, "tone", LayerSource::Footage { item: fid }, (0, 0), None); + comp.layers.push(l); + } + let cid = p.add_item("Comp 1", Label::Sandstone, None, ItemKind::Comp(comp.into())); + (p, cid) +} + +fn run(p: &Project, cid: ItemId, footage: &dyn FootageSource, om: &OutputModule, path: &Path) -> effectcraft_export::Report { + let s = RenderSettings { time_span: TimeSpan::LengthOfComp, ..Default::default() }; + let path = path.to_string_lossy().to_string(); + let job = Job { + project: p, + footage, + expr: None, + accel: None, + comp: cid, + settings: &s, + output: om, + path: &path, + sink: None, + nested_switches: true, + options: Default::default(), + }; + let mut last = Progress::default(); + let r = export(&job, &mut |pr| { + last = *pr; + true + }) + .expect("export"); + assert_eq!((last.done, r.frames), (FRAMES, FRAMES)); + r +} + +fn px(img: &Image, x: u32, y: u32) -> [f32; 4] { + img.data[img.idx(x, y)] +} + +fn near(a: [f32; 3], b: [f32; 3], tol: f32) -> bool { + a.iter().zip(b).all(|(x, y)| (x - y).abs() <= tol) +} + +fn check_frame(img: &Image, k: u64, tol: f32) { + let corner = px(img, 2, 2); + assert!(near([corner[0], corner[1], corner[2]], BG, tol), "frame {k} corner {corner:?}"); + let c = px(img, img.width / 2, img.height / 2); + let want = if k >= 5 { [1.0, 0.0, 0.0] } else { BG }; + assert!(near([c[0], c[1], c[2]], want, tol), "frame {k} centre {c:?} want {want:?}"); +} + +/// Import our file through the media layer and check every frame. +fn decode_movie(path: &Path, tol: f32) -> effectcraft_project::Footage { + let f = effectcraft_media::probe(path).expect("probe our output"); + assert_eq!((f.width, f.height), (W, H)); + assert!(f.has_video); + let n = f.frame_rate.frame_at(f.duration - Tick(1)) + 1; + assert_eq!(n as u64, FRAMES, "frame count from duration {:?} @ {:?}", f.duration, f.frame_rate); + let pool = MediaPool::new(); + for k in 0..FRAMES { + let img = pool.frame_at(&f, f.frame_rate.tick_of(k as i64)).expect("decode"); + check_frame(&img, k, tol); + } + f +} + +fn have(tool: &str) -> bool { + Command::new(tool).arg("-version").output().map(|o| o.status.success()).unwrap_or(false) +} + +/// `codec|w|h|pix_fmt|frames` of the video stream, or `None` without ffprobe. +fn ffprobe_video(path: &Path) -> Option { + if !have("ffprobe") { + eprintln!("ffprobe not found: skipping the oracle"); + return None; + } + let out = Command::new("ffprobe") + .args(["-v", "error", "-select_streams", "v:0", "-count_frames", "-show_entries", "stream=codec_name,width,height,pix_fmt,nb_read_frames"]) + .args(["-of", "compact=p=0:nk=1"]) + .arg(path) + .output() + .expect("ffprobe"); + assert!(out.status.success() && out.stderr.is_empty(), "ffprobe rejected {}: {}", path.display(), String::from_utf8_lossy(&out.stderr)); + Some(String::from_utf8_lossy(&out.stdout).trim().to_string()) +} + +/// Decode every frame with ffmpeg (no errors allowed) and check frames 0, 4, 5 and the last. +fn ffmpeg_check(path: &Path, decoder: Option<&str>, tol: f32) { + if !have("ffmpeg") { + eprintln!("ffmpeg not found: skipping the oracle decode"); + return; + } + let dir = path.with_extension("ffmpeg"); + let _ = std::fs::create_dir_all(&dir); + let mut cmd = Command::new("ffmpeg"); + cmd.args(["-hide_banner", "-loglevel", "error", "-xerror", "-y"]); + if let Some(d) = decoder { + cmd.args(["-c:v", d]); + } + let out = cmd.arg("-i").arg(path).args(["-pix_fmt", "rgba"]).arg(dir.join("f%03d.png")).output().expect("ffmpeg"); + assert!(out.status.success() && out.stderr.is_empty(), "ffmpeg ({decoder:?}) failed on {}: {}", path.display(), String::from_utf8_lossy(&out.stderr)); + for k in [0, 4, 5, FRAMES - 1] { + let img = image::open(dir.join(format!("f{:03}.png", k + 1))).expect("ffmpeg frame").to_rgba8(); + check_frame(&Image::from_rgba8(img.width(), img.height(), img.as_raw()), k, tol); + } +} + +fn module(format: OutputFormat, keyint: u32) -> OutputModule { + let mut om = OutputModule::for_format(format); + om.keyframe_interval = keyint; + om.codec.rate_control = RateControlMode::Quality; + om.codec.quality = 80; + om.audio = AudioOutput::Off; + om +} + +/// Key-frame flags of the MP4 video samples (via ffprobe), or `None` without it. +fn ffprobe_keyframes(path: &Path) -> Option> { + if !have("ffprobe") { + return None; + } + let out = Command::new("ffprobe") + .args(["-v", "error", "-select_streams", "v:0", "-show_entries", "packet=flags", "-of", "csv=p=0"]) + .arg(path) + .output() + .expect("ffprobe"); + Some(String::from_utf8_lossy(&out.stdout).lines().map(|l| l.starts_with('K')).collect()) +} + +#[test] +fn hevc_mp4_roundtrip() { + let d = out_dir("hevc"); + let (p, cid) = project(None); + let om = module(OutputFormat::Hevc, 4); + let path = d.join("comp.mp4"); + let r = run(&p, cid, &NoFootage, &om, &path); + assert!(r.bytes > 100 && !r.audio); + let bytes = std::fs::read(&path).unwrap(); + assert!(bytes.windows(4).any(|w| w == b"hvc1") && bytes.windows(4).any(|w| w == b"hvcC")); + decode_movie(&path, 0.08); + if let Some(s) = ffprobe_video(&path) { + assert_eq!(s, "hevc|64|48|yuv420p|12"); + } + if let Some(k) = ffprobe_keyframes(&path) { + assert_eq!(k, (0..FRAMES).map(|i| i % 4 == 0).collect::>(), "IDR every 4 frames"); + } + ffmpeg_check(&path, None, 0.08); +} + +#[test] +fn hevc_main10_bitrate_mode() { + let d = out_dir("hevc10"); + let (p, cid) = project(None); + let mut om = module(OutputFormat::Hevc, 0); + om.codec.profile = CodecProfile::Main10; + om.codec.rate_control = RateControlMode::Bitrate; + om.bitrate_kbps = 2_000; + om.codec.level = Some(31); + let path = d.join("comp10.mp4"); + run(&p, cid, &NoFootage, &om, &path); + decode_movie(&path, 0.08); + if let Some(s) = ffprobe_video(&path) { + assert_eq!(s, "hevc|64|48|yuv420p10le|12"); + } + ffmpeg_check(&path, None, 0.08); +} + +#[test] +fn av1_mp4_roundtrip() { + let d = out_dir("av1"); + let (p, cid) = project(None); + let om = module(OutputFormat::Av1, 6); + let path = d.join("comp.mp4"); + run(&p, cid, &NoFootage, &om, &path); + let bytes = std::fs::read(&path).unwrap(); + assert!(bytes.windows(4).any(|w| w == b"av01") && bytes.windows(4).any(|w| w == b"av1C")); + decode_movie(&path, 0.08); + if let Some(s) = ffprobe_video(&path) { + assert_eq!(s, "av1|64|48|yuv420p|12"); + } + if let Some(k) = ffprobe_keyframes(&path) { + assert_eq!(k, (0..FRAMES).map(|i| i % 6 == 0).collect::>(), "key frame every 6 frames"); + } + ffmpeg_check(&path, Some("libdav1d"), 0.08); +} + +#[test] +fn av1_webm_with_opus() { + let d = out_dir("av1-webm"); + let wav = d.join("tone.wav"); + write_tone(&wav, 2.0); + let (p, cid) = project(Some(&wav)); + let pool = MediaPool::new(); + let mut om = module(OutputFormat::WebM, 0); + om.webm_codec = WebmVideoCodec::Av1; + om.codec.profile = CodecProfile::Main10; + om.audio = AudioOutput::Auto; + om.opus_bitrate_kbps = 96; + let path = d.join("comp.webm"); + let r = run(&p, cid, &pool, &om, &path); + assert!(r.audio); + let f = decode_movie(&path, 0.08); + assert!(f.has_audio); + let s = MediaPool::new().audio_samples(&f, Tick::from_seconds_f64(0.3), 24_000, 48_000); + let l = rms(s.iter().step_by(2).copied()); + assert!((l - 0.3536).abs() < 0.06, "left RMS {l}"); + if let Some(s) = ffprobe_video(&path) { + assert_eq!(s, "av1|64|48|yuv420p10le|12"); + } + ffmpeg_check(&path, Some("libdav1d"), 0.08); +} + +/// Low-bitrate WebM audio goes through Opus SILK (voice) or hybrid (audio) coding and still +/// decodes to the tone (FilmCraft's decoder, and ffmpeg when installed). +#[test] +fn webm_low_bitrate_opus_modes() { + let d = out_dir("opus-modes"); + let wav = d.join("tone.wav"); + write_tone(&wav, 2.0); + let (p, cid) = project(Some(&wav)); + let pool = MediaPool::new(); + for (kbps, app, name) in [(24u32, OpusApplication::Voip, "voice24.webm"), (40, OpusApplication::Audio, "audio40.webm")] { + let mut om = OutputModule::for_format(OutputFormat::WebM); + om.audio = AudioOutput::On; + om.opus_bitrate_kbps = kbps; + om.opus_application = app; + let path = d.join(name); + assert!(run(&p, cid, &pool, &om, &path).audio); + let f = effectcraft_media::probe(&path).expect("probe"); + let s = MediaPool::new().audio_samples(&f, Tick::from_seconds_f64(0.3), 24_000, 48_000); + let l = rms(s.iter().step_by(2).copied()); + assert!((l - 0.3536).abs() < 0.08, "{name}: left RMS {l}"); + if have("ffmpeg") { + let out = + Command::new("ffmpeg").args(["-v", "error", "-xerror", "-i"]).arg(&path).args(["-map", "0:a", "-f", "null", "-"]).output().expect("ffmpeg"); + assert!(out.status.success() && out.stderr.is_empty(), "{name}: {}", String::from_utf8_lossy(&out.stderr)); + } + } +} + +fn rms(v: impl Iterator) -> f32 { + let (s, n) = v.fold((0.0f64, 0usize), |(s, n), x| (s + (x as f64) * (x as f64), n + 1)); + (s / n.max(1) as f64).sqrt() as f32 +} + +/// 16-bit stereo WAV with a 440 Hz sine at amplitude 0.5 (left) and silence (right). +fn write_tone(path: &Path, secs: f64) { + let sr = 48_000u32; + let n = (secs * sr as f64) as usize; + let mut data = Vec::with_capacity(n * 4); + for i in 0..n { + let s = (0.5 * (2.0 * std::f64::consts::PI * 440.0 * i as f64 / sr as f64).sin() * 32767.0).round() as i16; + data.extend_from_slice(&s.to_le_bytes()); + data.extend_from_slice(&0i16.to_le_bytes()); + } + let mut w = Vec::new(); + w.extend_from_slice(b"RIFF"); + w.extend_from_slice(&(36 + data.len() as u32).to_le_bytes()); + w.extend_from_slice(b"WAVEfmt "); + w.extend_from_slice(&16u32.to_le_bytes()); + w.extend_from_slice(&1u16.to_le_bytes()); + w.extend_from_slice(&2u16.to_le_bytes()); + w.extend_from_slice(&sr.to_le_bytes()); + w.extend_from_slice(&(sr * 4).to_le_bytes()); + w.extend_from_slice(&4u16.to_le_bytes()); + w.extend_from_slice(&16u16.to_le_bytes()); + w.extend_from_slice(b"data"); + w.extend_from_slice(&(data.len() as u32).to_le_bytes()); + w.extend_from_slice(&data); + std::fs::write(path, w).expect("write wav"); +} diff --git a/crates/hevcenc/Cargo.toml b/crates/hevcenc/Cargo.toml new file mode 100644 index 00000000..136611ff --- /dev/null +++ b/crates/hevcenc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "effectcraft-hevcenc" +description = "Clean-room pure-Rust HEVC (H.265) encoder (Main / Main 10, I and P slices, CABAC) for MP4 export, from ITU-T H.265" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] + +[dev-dependencies] +filmcraft-hevc = { workspace = true } diff --git a/crates/hevcenc/README.md b/crates/hevcenc/README.md new file mode 100644 index 00000000..5c4e5deb --- /dev/null +++ b/crates/hevcenc/README.md @@ -0,0 +1,88 @@ +# effectcraft-hevcenc + +Clean-room, pure-Rust HEVC / H.265 encoder for EffectCraft's MP4 export (layer L0, `std` only, +no `unsafe`, builds for `wasm32-unknown-unknown`). + +## What it produces + +- **Profiles:** Main (8-bit) and Main 10 (10-bit), 4:2:0, Main tier. `general_level_idc` is taken + from the config or chosen as the lowest level whose MaxLumaPs, MaxLumaSr (and, in bitrate mode, + MaxBR) fit (Table A.8). +- **Parameter sets:** VPS, SPS (with VUI: BT.709 colour description, `video_full_range_flag`, + timing info from the frame rate) and PPS, as raw NAL units (`Encoder::parameter_sets`) and as an + `hvcC` record (ISO/IEC 14496-15 §8.3.3.1, `lengthSizeMinusOne` 3, complete VPS / SPS / PPS + arrays, `Encoder::hvcc`). +- **Pictures:** one slice per picture; `IDR_N_LP` every `keyint` pictures (`1` = all intra, `0` = + only the first), `TRAIL_R` P pictures otherwise, each referencing the previous picture (one + short-term RPS in the SPS, `num_ref_idx_l0_active` 1, no reordering, pts = dts). Packets are + length-prefixed (4-byte big-endian) NAL units without parameter sets. +- **Picture size:** any even width / height; the coded size is padded to a multiple of 8 by edge + replication and cropped with the conformance window. Odd sizes are rejected with + `Error::InvalidConfig` (4:2:0 conformance-window offsets are in units of two luma samples). + +## Coding tools + +- 64x64 CTUs, coding units 64x64 .. 8x8 chosen by rate-distortion optimisation: every candidate is + actually predicted, transformed, quantised and reconstructed, and its rate is measured with a + CABAC bit estimator that evolves the same context states as the real coder. Split decisions + compare the best unsplit candidate with the recursively coded children (early exit after a skip). +- **Intra:** all 35 modes (SATD search on a coarse grid plus refinement and the MPMs), 2Nx2N at + 8..32 and NxN (four 4x4 PUs) at 8x8, chroma in DM mode, MPM / remaining-mode coding, reference + substitution, [1 2 1] and strong (32x32) smoothing, DC / angular boundary filters. +- **Inter (P):** skip and merge (spatial candidates + zero candidates, `MaxNumMergeCand` 5) and AMVP + with mvd coding; integer motion search (predictor starts + square pattern, steps 16..1) and + half / quarter-sample refinement on pre-interpolated half-sample planes; the coded prediction uses + the normative 8-tap / 4-tap interpolation filters and reference-picture edge clamping. +- **Transforms:** DST-VII 4x4 (intra luma) and DCT 4..32 (inverse by even/odd decomposition, + bit-identical to the matrix form), dead-zone quantisation (1/3 intra, 1/6 inter), flat scaling, + sign data hiding, inferred transform splits only (`max_transform_hierarchy_depth_*` 0, so 64x64 + CUs use four 32x32 TUs). +- **Entropy coding:** CABAC (context initialisation by slice QP and init type, decision / bypass / + terminate bins, flush), `end_of_slice_segment_flag`, byte alignment, emulation prevention. +- **In-loop filters:** the deblocking filter is on (the encoder deblocks its own reconstruction, so + references match the decoder); SAO is disabled. +- **Rate control:** `ConstantQp(qp)` or `Bitrate { kbps }`, a per-picture QP from a bits-x-Qstep + model per picture type (I pictures get roughly 4x the bits of a P picture) with a buffer that + spreads over- and undershoot over the following pictures. + +Not implemented: B pictures / reordering, multiple references, TMVP, AMP and other inter partition +shapes, residual quadtree splits, transform skip, scaling lists, PCM, SAO, RDOQ, cu_qp_delta / +adaptive quantisation, tiles / WPP / multiple slices, range extensions (4:2:2, 4:4:4, > 10 bits). + +## Measurements + +On a shared, heavily loaded machine, opt-level 3: about 0.45 s of CPU per 1920x1080 picture +(ffmpeg `testsrc2`, QP 22 / 32). On ffmpeg's `mandelbrot` 1080p source (8 pictures, IPPP, QP 32) +the stream is about 12 % larger at about 0.2 dB lower mean PSNR-Y than x265 `--preset medium` at +the same QP without psy tuning (110.5 kB / 42.3 dB vs 98.3 kB / 42.5 dB). The synthetic test image +reaches 45.8 dB PSNR-Y at QP 22. + +## Tests + +`cargo test -p effectcraft-hevcenc`: + +- every stream is decoded with the first-party `filmcraft-hevc` decoder and compared bit-exactly + with the encoder's reconstruction (`Encoder::last_reconstruction`, hidden API); +- when `ffmpeg` is on `PATH`, the Annex-B elementary stream is decoded with + `ffmpeg -v error -xerror` (no stderr output allowed) and compared bit-exactly as well (skipped + otherwise). ffmpeg is used only as an external test oracle; +- covered: 8-bit and 10-bit, all-intra and IP, QP 0 noise (escape codes), QP 0..51, strong + deblocking at high QP, sizes 8x8 .. 200x136 with cropping, POC wrap (300 pictures without IDR), + bitrate mode within 2x of the target, `hvcC` layout; +- `bench_1080p` (ignored) reports speed, size and PSNR on 1080p content generated by ffmpeg. + +## Specifications + +- ITU-T H.265 (V8, 08/2021) | ISO/IEC 23008-2, *High efficiency video coding*: 7 (syntax and + semantics), 8.4 - 8.7 (intra / inter prediction, scaling and transforms, deblocking), 9.3 (CABAC, + including the informative encoder description of 9.3.4), Annex A (profiles, tiers and levels), + Annex E (VUI). +- ISO/IEC 14496-15 (2019), §8.3.3.1 HEVCDecoderConfigurationRecord. + +Implemented from the specification text. Constant tables (CABAC context layout and initValues, +rangeTabLPS / transIdx, scan orders, the DCT / DST matrices, intra angles, interpolation filters, +the chroma QP table, the deblocking beta / tC tables) and the decoder-side reconstruction +arithmetic (intra reference substitution / filtering / prediction, fractional interpolation, +deblocking) are mirrored from FilmCraft's first-party, spec-derived `filmcraft-hevc` decoder (same +organisation, MIT OR Apache-2.0), so that encoder and decoder reconstruct identically. No +third-party encoder or decoder source (x265, libde265, ffmpeg, HM, ...) was consulted. diff --git a/crates/hevcenc/src/bitstream.rs b/crates/hevcenc/src/bitstream.rs new file mode 100644 index 00000000..27ed3063 --- /dev/null +++ b/crates/hevcenc/src/bitstream.rs @@ -0,0 +1,135 @@ +//! RBSP bit writer (7.2, 9.2 Exp-Golomb) and NAL unit encapsulation with emulation prevention (7.3.1). + +/// MSB-first bit writer. +#[derive(Default)] +pub struct BitWriter { + buf: Vec, + acc: u64, + nbits: u32, +} + +impl BitWriter { + pub fn new() -> Self { + Self::default() + } + + /// Write the `n` (<= 32) low bits of `v`. + #[inline] + pub fn put(&mut self, v: u32, n: u32) { + debug_assert!(n <= 32); + if n == 0 { + return; + } + let v = (v as u64) & ((1u64 << n) - 1); + self.acc = (self.acc << n) | v; + self.nbits += n; + while self.nbits >= 8 { + self.nbits -= 8; + self.buf.push((self.acc >> self.nbits) as u8); + } + self.acc &= (1u64 << self.nbits) - 1; + } + + #[inline] + pub fn bit(&mut self, b: u32) { + self.put(b & 1, 1); + } + + pub fn flag(&mut self, b: bool) { + self.put(b as u32, 1); + } + + /// ue(v). + pub fn ue(&mut self, v: u32) { + let x = v as u64 + 1; + let len = 64 - x.leading_zeros(); // bits of x + // len - 1 leading zeros, then x in len bits + self.put(0, len - 1); + if len > 32 { + self.put((x >> 32) as u32, len - 32); + self.put(x as u32, 32); + } else { + self.put(x as u32, len); + } + } + + /// se(v). + pub fn se(&mut self, v: i32) { + let k = if v > 0 { 2 * v as u32 - 1 } else { (-(v as i64) as u32) * 2 }; + self.ue(k); + } + + pub fn is_aligned(&self) -> bool { + self.nbits == 0 + } + + /// rbsp_trailing_bits(): stop bit and zero alignment. + pub fn trailing(&mut self) { + self.bit(1); + self.align_zero(); + } + + pub fn align_zero(&mut self) { + if self.nbits > 0 { + self.put(0, 8 - self.nbits); + } + } + + pub fn into_bytes(mut self) -> Vec { + self.align_zero(); + self.buf + } +} + +/// Build a NAL unit (2-byte header, `nuh_layer_id` 0, `TemporalId` 0) from an RBSP, inserting +/// emulation prevention bytes. +pub fn nal_unit(nal_type: u8, rbsp: &[u8]) -> Vec { + let mut out = Vec::with_capacity(rbsp.len() + rbsp.len() / 64 + 4); + out.push(nal_type << 1); + out.push(1); + let mut zeros = 0; + for &b in rbsp { + if zeros >= 2 && b <= 3 { + out.push(3); + zeros = 0; + } + out.push(b); + if b == 0 { + zeros += 1; + } else { + zeros = 0; + } + } + // cabac_zero_words / trailing zero bytes never occur: every RBSP ends with the stop bit. + out +} + +pub const NAL_TRAIL_R: u8 = 1; +pub const NAL_IDR_N_LP: u8 = 20; +pub const NAL_VPS: u8 = 32; +pub const NAL_SPS: u8 = 33; +pub const NAL_PPS: u8 = 34; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn exp_golomb() { + let mut w = BitWriter::new(); + w.ue(0); // 1 + w.ue(1); // 010 + w.ue(2); // 011 + w.ue(3); // 00100 + w.se(-1); // ue(2) = 011 + w.trailing(); + // 1 010 011 00100 011 + stop bit: 1010 0110 0100 0111 + assert_eq!(w.into_bytes(), vec![0xa6, 0x47]); + } + + #[test] + fn emulation_prevention() { + let n = nal_unit(1, &[0, 0, 1, 0, 0, 0, 0, 0, 4]); + assert_eq!(&n[2..], &[0, 0, 3, 1, 0, 0, 3, 0, 0, 3, 0, 4]); + } +} diff --git a/crates/hevcenc/src/cabac.rs b/crates/hevcenc/src/cabac.rs new file mode 100644 index 00000000..220b999c --- /dev/null +++ b/crates/hevcenc/src/cabac.rs @@ -0,0 +1,220 @@ +//! CABAC: context initialisation (9.3.2.2), the arithmetic encoder (9.3.4.x, informative encoder +//! description: EncodeDecision, EncodeBypass, EncodeTerminate, EncodeFlush) and a fractional-bit +//! estimator with the same context-state evolution, used for rate-distortion decisions. + +use crate::bitstream::BitWriter; +use crate::tables::{CABAC_INIT, NEXT_STATE, NUM_CTX, RANGE_TAB_LPS}; +use std::sync::OnceLock; + +/// Context states, each `pStateIdx << 1 | valMps`. +pub type Contexts = [u8; NUM_CTX]; + +/// Initialise all context variables for `slice_qp` and `init_type` (9.3.2.2). +pub fn init_contexts(slice_qp: i32, init_type: usize) -> Contexts { + let mut ctx = [0u8; NUM_CTX]; + let qp = slice_qp.clamp(0, 51); + for (c, &v) in ctx.iter_mut().zip(CABAC_INIT[init_type].iter()) { + let slope = (v >> 4) as i32; + let offset = (v & 15) as i32; + let m = slope * 5 - 45; + let n = (offset << 3) - 16; + let pre = (((m * qp) >> 4) + n).clamp(1, 126); + *c = if pre <= 63 { ((63 - pre) << 1) as u8 } else { (((pre - 64) << 1) | 1) as u8 }; + } + ctx +} + +/// Destination of binarised syntax elements: the real arithmetic coder or the bit estimator. +pub trait Sink { + fn decision(&mut self, ctx: usize, bin: u32); + fn bypass(&mut self, bin: u32); + /// `n` bypass bins carrying the low `n` bits of `v`, MSB first. + fn bypass_bits(&mut self, v: u32, n: u32) { + for i in (0..n).rev() { + self.bypass((v >> i) & 1); + } + } + fn terminate(&mut self, bin: u32); +} + +/// Arithmetic encoder writing into a bit writer. +pub struct CabacEncoder { + pub ctx: Contexts, + low: u32, + range: u32, + outstanding: u32, + first: bool, + pub w: BitWriter, +} + +impl CabacEncoder { + /// Start encoding slice data into `w` (which must be byte aligned). + pub fn new(w: BitWriter, ctx: Contexts) -> Self { + debug_assert!(w.is_aligned()); + CabacEncoder { ctx, low: 0, range: 510, outstanding: 0, first: true, w } + } + + #[inline] + fn put_bit(&mut self, b: u32) { + if self.first { + self.first = false; + } else { + self.w.bit(b); + } + while self.outstanding > 0 { + self.w.bit(1 - b); + self.outstanding -= 1; + } + } + + #[inline] + fn renorm(&mut self) { + while self.range < 256 { + if self.low < 256 { + self.put_bit(0); + } else if self.low >= 512 { + self.low -= 512; + self.put_bit(1); + } else { + self.low -= 256; + self.outstanding += 1; + } + self.range <<= 1; + self.low <<= 1; + } + } + + /// EncodeFlush after a terminating bin equal to 1; the final bit written is the + /// rbsp_stop_one_bit. Returns the writer (byte aligned with zero bits). + pub fn finish(mut self) -> BitWriter { + self.range = 2; + self.renorm(); + let b = (self.low >> 9) & 1; + self.put_bit(b); + self.w.put(((self.low >> 7) & 3) | 1, 2); + self.w.align_zero(); + self.w + } +} + +impl Sink for CabacEncoder { + #[inline] + fn decision(&mut self, ctx_idx: usize, bin: u32) { + let s = self.ctx[ctx_idx] as usize; + let mps = (s & 1) as u32; + let q = ((self.range >> 6) & 3) as usize; + let lps = RANGE_TAB_LPS[s >> 1][q] as u32; + self.range -= lps; + if bin != mps { + self.low += self.range; + self.range = lps; + self.ctx[ctx_idx] = NEXT_STATE[s][1]; + } else { + self.ctx[ctx_idx] = NEXT_STATE[s][0]; + } + self.renorm(); + } + + #[inline] + fn bypass(&mut self, bin: u32) { + self.low <<= 1; + if bin != 0 { + self.low += self.range; + } + if self.low >= 1024 { + self.put_bit(1); + self.low -= 1024; + } else if self.low < 512 { + self.put_bit(0); + } else { + self.low -= 512; + self.outstanding += 1; + } + } + + fn terminate(&mut self, bin: u32) { + self.range -= 2; + if bin != 0 { + self.low += self.range; + // the caller flushes with finish() + } else { + self.renorm(); + } + } +} + +/// Fractional bit cost (1/32768 bit units) of coding the MPS / LPS in each state. +fn cost_table() -> &'static [[u32; 2]; 64] { + static T: OnceLock<[[u32; 2]; 64]> = OnceLock::new(); + T.get_or_init(|| { + let mut t = [[0u32; 2]; 64]; + // pLPS(s) = 0.5 * alpha^s with alpha = (0.01875 / 0.5)^(1/63) (9.3.4.3 design) + let alpha = (0.01875f64 / 0.5).powf(1.0 / 63.0); + for (s, e) in t.iter_mut().enumerate() { + let p = 0.5 * alpha.powi(s as i32); + e[0] = (-(1.0 - p).log2() * 32768.0).round() as u32; + e[1] = (-p.log2() * 32768.0).round() as u32; + } + t + }) +} + +pub const BIT: u64 = 32768; + +/// Bit estimator: tracks context states like the encoder and accumulates the ideal code length. +#[derive(Clone)] +pub struct Estimator { + pub ctx: Contexts, + /// Accumulated cost in 1/32768 bits. + pub bits: u64, + table: &'static [[u32; 2]; 64], +} + +impl Estimator { + pub fn new(ctx: Contexts) -> Self { + Estimator { ctx, bits: 0, table: cost_table() } + } +} + +impl Sink for Estimator { + #[inline] + fn decision(&mut self, ctx_idx: usize, bin: u32) { + let s = self.ctx[ctx_idx] as usize; + let lps = (bin != (s & 1) as u32) as usize; + self.bits += self.table[s >> 1][lps] as u64; + self.ctx[ctx_idx] = NEXT_STATE[s][lps]; + } + #[inline] + fn bypass(&mut self, _bin: u32) { + self.bits += BIT; + } + #[inline] + fn bypass_bits(&mut self, _v: u32, n: u32) { + self.bits += BIT * n as u64; + } + fn terminate(&mut self, _bin: u32) { + self.bits += 7; // ~0 bits for a 0 bin + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn context_init_formula() { + let ctx = init_contexts(30, 0); + // initValue 154: m = 0, n = 64 -> pStateIdx 0, valMps 1 + assert_eq!(ctx[crate::tables::CU_TRANSQUANT_BYPASS], 1); + // split_cu_flag ctx 0 (initValue 139) at QP 30: preCtxState 62 + assert_eq!(ctx[crate::tables::SPLIT_CU], (63 - 62) << 1); + } + + #[test] + fn costs_are_sane() { + let t = cost_table(); + assert_eq!(t[0][0], 32768); + assert_eq!(t[0][1], 32768); + assert!(t[62][0] < 1000 && t[62][1] > 5 * 32768); + } +} diff --git a/crates/hevcenc/src/deblock.rs b/crates/hevcenc/src/deblock.rs new file mode 100644 index 00000000..6f5d17b8 --- /dev/null +++ b/crates/hevcenc/src/deblock.rs @@ -0,0 +1,149 @@ +//! Deblocking filter (8.7.2) over a whole reconstructed picture, mirrored from the filmcraft-hevc +//! decoder: all vertical edges on the 8x8 grid first, then all horizontal edges. One slice, one QP, +//! uni-directional prediction from a single reference picture, offsets 0. + +use crate::frame::{Blk, E_EDGE_H, E_EDGE_V, F_CBF, F_INTRA}; +use crate::tables::{BETA_TABLE, TC_TABLE, qpc_420}; + +#[inline] +fn tc_beta(q: i32, bs: i32, bd: u32) -> (i32, i32) { + let qb = q.clamp(0, 51); + let qt = (q + 2 * (bs - 1)).clamp(0, 53); + ((TC_TABLE[qt as usize] as i32) << (bd - 8), (BETA_TABLE[qb as usize] as i32) << (bd - 8)) +} + +/// Boundary strength (8.7.2.4) between 4x4 blocks p and q on a transform / prediction edge. +fn boundary_strength(p: &Blk, q: &Blk) -> i32 { + if (p.flags | q.flags) & F_INTRA != 0 { + return 2; + } + if (p.flags | q.flags) & F_CBF != 0 { + return 1; + } + let far = (p.mv[0] as i32 - q.mv[0] as i32).abs() >= 4 || (p.mv[1] as i32 - q.mv[1] as i32).abs() >= 4; + far as i32 +} + +/// Deblock the planes in place. `qp` is the slice QpY (without QpBdOffset). +pub fn deblock(planes: &mut [Vec; 3], blk: &[Blk], w: usize, h: usize, qp: i32, bd: u32) { + let (w4, h4) = (w / 4, h / 4); + let cw = w / 2; + // chroma tc (bS 2 only; QpC from the averaged luma QP, no offsets) + let qt = (qpc_420(qp) + 2).clamp(0, 53); + let tc_c = (TC_TABLE[qt as usize] as i32) << (bd - 8); + // vertical edges + for by in 0..h4 { + for bx in (2..w4).step_by(2) { + let qi = by * w4 + bx; + if blk[qi].flags & E_EDGE_V == 0 { + continue; + } + let bs = boundary_strength(&blk[qi - 1], &blk[qi]); + if bs == 0 { + continue; + } + let (x, y) = (bx * 4, by * 4); + let (tc, beta) = tc_beta(qp, bs, bd); + filter_luma(&mut planes[0], y * w + x, 1, w, tc, beta, bd); + // chroma: 8x8 chroma grid (16 luma samples), one 4-sample chroma segment per 8 luma rows + if bs == 2 && bx % 4 == 0 && by % 2 == 0 { + for c in 1..3 { + filter_chroma(&mut planes[c], (y / 2) * cw + x / 2, 1, cw, tc_c, bd); + } + } + } + } + // horizontal edges + for by in (2..h4).step_by(2) { + for bx in 0..w4 { + let qi = by * w4 + bx; + if blk[qi].flags & E_EDGE_H == 0 { + continue; + } + let bs = boundary_strength(&blk[qi - w4], &blk[qi]); + if bs == 0 { + continue; + } + let (x, y) = (bx * 4, by * 4); + let (tc, beta) = tc_beta(qp, bs, bd); + filter_luma(&mut planes[0], y * w + x, w, 1, tc, beta, bd); + if bs == 2 && by % 4 == 0 && bx % 2 == 0 { + for c in 1..3 { + filter_chroma(&mut planes[c], (y / 2) * cw + x / 2, cw, 1, tc_c, bd); + } + } + } + } +} + +/// Filter one 4-sample luma edge segment. `o` = offset of q0 of the first line, `step` = distance +/// between samples across the edge, `along` = distance between lines. +#[allow(clippy::too_many_arguments)] +fn filter_luma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, beta: i32, bd: u32) { + let px = |s: &[u16], k: usize, i: usize| s[o + k * along - (i + 1) * step] as i32; + let qx = |s: &[u16], k: usize, i: usize| s[o + k * along + i * step] as i32; + let dp0 = (px(s, 0, 2) - 2 * px(s, 0, 1) + px(s, 0, 0)).abs(); + let dp3 = (px(s, 3, 2) - 2 * px(s, 3, 1) + px(s, 3, 0)).abs(); + let dq0 = (qx(s, 0, 2) - 2 * qx(s, 0, 1) + qx(s, 0, 0)).abs(); + let dq3 = (qx(s, 3, 2) - 2 * qx(s, 3, 1) + qx(s, 3, 0)).abs(); + let (dpq0, dpq3) = (dp0 + dq0, dp3 + dq3); + let (dp, dq) = (dp0 + dp3, dq0 + dq3); + let d = dpq0 + dpq3; + if d >= beta { + return; + } + let dsam = |s: &[u16], k: usize, dpq: i32| -> bool { + dpq < (beta >> 2) + && (px(s, k, 3) - px(s, k, 0)).abs() + (qx(s, k, 0) - qx(s, k, 3)).abs() < (beta >> 3) + && (px(s, k, 0) - qx(s, k, 0)).abs() < ((5 * tc + 1) >> 1) + }; + let strong = dsam(s, 0, 2 * dpq0) && dsam(s, 3, 2 * dpq3); + let dep = dp < ((beta + (beta >> 1)) >> 3); + let deq = dq < ((beta + (beta >> 1)) >> 3); + let max = (1i32 << bd) - 1; + for k in 0..4 { + let base = o + k * along; + let p = |i: usize| s[base - (i + 1) * step] as i32; + let q = |i: usize| s[base + i * step] as i32; + let (p0, p1, p2, p3) = (p(0), p(1), p(2), p(3)); + let (q0, q1, q2, q3) = (q(0), q(1), q(2), q(3)); + if strong { + let t2 = 2 * tc; + s[base - step] = ((p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3).clamp(p0 - t2, p0 + t2) as u16; + s[base - 2 * step] = ((p2 + p1 + p0 + q0 + 2) >> 2).clamp(p1 - t2, p1 + t2) as u16; + s[base - 3 * step] = ((2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3).clamp(p2 - t2, p2 + t2) as u16; + s[base] = ((p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3).clamp(q0 - t2, q0 + t2) as u16; + s[base + step] = ((p0 + q0 + q1 + q2 + 2) >> 2).clamp(q1 - t2, q1 + t2) as u16; + s[base + 2 * step] = ((p0 + q0 + q1 + 3 * q2 + 2 * q3 + 4) >> 3).clamp(q2 - t2, q2 + t2) as u16; + } else { + let mut delta = (9 * (q0 - p0) - 3 * (q1 - p1) + 8) >> 4; + if delta.abs() >= tc * 10 { + continue; + } + delta = delta.clamp(-tc, tc); + s[base - step] = (p0 + delta).clamp(0, max) as u16; + s[base] = (q0 - delta).clamp(0, max) as u16; + if dep { + let dp = ((((p2 + p0 + 1) >> 1) - p1 + delta) >> 1).clamp(-(tc >> 1), tc >> 1); + s[base - 2 * step] = (p1 + dp).clamp(0, max) as u16; + } + if deq { + let dq = ((((q2 + q0 + 1) >> 1) - q1 - delta) >> 1).clamp(-(tc >> 1), tc >> 1); + s[base + step] = (q1 + dq).clamp(0, max) as u16; + } + } + } +} + +/// Filter one 4-sample chroma edge segment. +fn filter_chroma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, bd: u32) { + let max = (1i32 << bd) - 1; + for k in 0..4 { + let base = o + k * along; + let (p0, p1) = (s[base - step] as i32, s[base - 2 * step] as i32); + let (q0, q1) = (s[base] as i32, s[base + step] as i32); + let delta = ((((q0 - p0) << 2) + p1 - q1 + 4) >> 3).clamp(-tc, tc); + s[base - step] = (p0 + delta).clamp(0, max) as u16; + s[base] = (q0 - delta).clamp(0, max) as u16; + } +} diff --git a/crates/hevcenc/src/frame.rs b/crates/hevcenc/src/frame.rs new file mode 100644 index 00000000..2cbeb4f3 --- /dev/null +++ b/crates/hevcenc/src/frame.rs @@ -0,0 +1,1438 @@ +//! Picture coding: CTU loop, rate-distortion mode decisions (with a CABAC bit estimator) and the +//! slice_segment_data() syntax (7.3.8): coding quadtree, coding unit, prediction unit, transform tree +//! and residual coding. Reconstruction mirrors the decoding process exactly (no in-loop filters). + +use crate::cabac::{BIT, CabacEncoder, Estimator, Sink}; +use crate::inter::{self, PlaneRef}; +use crate::intra::{self, Refs}; +use crate::me::MeRef; +use crate::params::{LOG2_CTB, LOG2_MAX_TB, LOG2_MIN_CB, MAX_MERGE_CAND}; +use crate::tables::*; +use crate::transform; + +pub const F_INTRA: u8 = 1; +pub const F_SKIP: u8 = 2; +/// The luma transform block covering this 4x4 block has non-zero coefficients. +pub const F_CBF: u8 = 4; +/// The left / top edge of this 4x4 block is a transform (and prediction) block edge. +pub const E_EDGE_V: u8 = 8; +pub const E_EDGE_H: u8 = 16; + +/// Per 4x4 luma block state of the current picture. +#[derive(Clone, Copy, Default)] +pub struct Blk { + pub depth: u8, + pub flags: u8, + /// IntraPredModeY (DC for inter blocks). + pub ipm: u8, + /// L0 motion vector (quarter samples) of inter blocks. + pub mv: [i16; 2], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CuMode { + Intra { nxn: bool, modes: [u8; 4] }, + Skip { idx: u8 }, + Merge { idx: u8 }, + Amvp { mv: [i16; 2], mvp: u8 }, +} + +#[derive(Clone, Copy, Debug)] +struct CuDec { + x: u32, + y: u32, + log2: u32, + mode: CuMode, +} + +/// One coded transform block of the current CU. +#[derive(Clone, Copy)] +struct Tb { + c: u8, + /// Luma-coordinate region the block belongs to (for cbf derivation). + lx: u32, + ly: u32, + log2: u32, + nz: bool, + off: usize, + scan: u8, +} + +/// Saved samples and block state of a square region. +struct Snap { + x: usize, + y: usize, + n: usize, + planes: [Vec; 3], + blk: Vec, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum SliceKind { + I, + P, +} + +pub struct FrameCoder<'a> { + pub bd: u32, + pub w: usize, + pub h: usize, + pub cw: usize, + pub ch: usize, + w4: usize, + wctb: usize, + hctb: usize, + pub kind: SliceKind, + /// QP' (with QpBdOffset) for luma and chroma. + slice_qp: i32, + qp_y: i32, + qp_c: i32, + lambda: f64, + lambda_sad: f64, + src: &'a [Vec; 3], + pub rec: [Vec; 3], + refp: Option<&'a [Vec; 3]>, + me: Option<&'a MeRef>, + blk: Vec, + tbs: Vec, + coefs: Vec, + pred: Vec, + scratch: [Vec; 2], +} + +#[inline] +fn morton(x: u32, y: u32) -> u32 { + let mut r = 0; + for i in 0..5 { + r |= ((x >> i) & 1) << (2 * i); + r |= ((y >> i) & 1) << (2 * i + 1); + } + r +} + +/// Intra modes tried in the coarse pass of the SATD search. +const COARSE_MODES: [u32; 11] = [0, 1, 2, 6, 10, 14, 18, 22, 26, 30, 34]; + +impl<'a> FrameCoder<'a> { + #[allow(clippy::too_many_arguments)] + pub fn new( + bd: u32, + w: usize, + h: usize, + kind: SliceKind, + slice_qp: i32, + src: &'a [Vec; 3], + refp: Option<&'a [Vec; 3]>, + me: Option<&'a MeRef>, + ) -> Self { + let off = 6 * (bd as i32 - 8); + let qpi = slice_qp.clamp(-off, 57); + let qp_c = qpc_420(qpi) + off; + let base = if kind == SliceKind::I { 0.57 } else { 0.62 }; + let lambda = base * 2f64.powf((slice_qp as f64 - 12.0) / 3.0) * (1u64 << (2 * (bd - 8))) as f64; + let ctb = 1usize << LOG2_CTB; + FrameCoder { + bd, + w, + h, + cw: w / 2, + ch: h / 2, + w4: w / 4, + wctb: w.div_ceil(ctb), + hctb: h.div_ceil(ctb), + kind, + slice_qp, + qp_y: slice_qp + off, + qp_c, + lambda, + lambda_sad: lambda.sqrt(), + src, + rec: [vec![0; w * h], vec![0; w * h / 4], vec![0; w * h / 4]], + refp, + me, + blk: vec![Blk::default(); (w / 4) * (h / 4)], + tbs: Vec::with_capacity(16), + coefs: Vec::with_capacity(64 * 64 * 2), + pred: vec![0; 64 * 64], + scratch: [vec![0; 32 * 32], vec![0; 32 * 32]], + } + } + + // --------------------------------------------------------------------------------------- + // neighbourhood + + /// z-scan order availability (6.4.1) of (xn, yn) for the block at (xc, yc): single slice, no tiles. + #[inline] + fn zavail(&self, xc: i32, yc: i32, xn: i32, yn: i32) -> bool { + if xn < 0 || yn < 0 || xn >= self.w as i32 || yn >= self.h as i32 { + return false; + } + let s = LOG2_CTB; + let ctb_n = (yn >> s) as usize * self.wctb + (xn >> s) as usize; + let ctb_c = (yc >> s) as usize * self.wctb + (xc >> s) as usize; + if ctb_n != ctb_c { + ctb_n < ctb_c + } else { + let m = (1 << s) - 1; + morton(((xn & m) >> 2) as u32, ((yn & m) >> 2) as u32) <= morton(((xc & m) >> 2) as u32, ((yc & m) >> 2) as u32) + } + } + + #[inline] + fn blk_at(&self, x: i32, y: i32) -> &Blk { + &self.blk[(y as usize >> 2) * self.w4 + (x as usize >> 2)] + } + + fn fill(&mut self, x: usize, y: usize, n: usize, f: impl Fn(&mut Blk)) { + for by in (y >> 2)..((y + n) >> 2) { + for b in &mut self.blk[by * self.w4 + (x >> 2)..by * self.w4 + ((x + n) >> 2)] { + f(b); + } + } + } + + /// Mark the CU's left / top edges and, for CUs larger than the maximum transform size, the + /// inner transform edges (deblocking). + fn mark_edges(&mut self, x: usize, y: usize, log2: u32) { + let n = 1usize << log2; + let t = 1usize << log2.min(LOG2_MAX_TB); + for ty in (y..y + n).step_by(t) { + for tx in (x..x + n).step_by(t) { + for by in ty / 4..(ty + t) / 4 { + self.blk[by * self.w4 + tx / 4].flags |= E_EDGE_V; + } + for b in &mut self.blk[(ty / 4) * self.w4 + tx / 4..(ty / 4) * self.w4 + (tx + t) / 4] { + b.flags |= E_EDGE_H; + } + } + } + } + + /// In-loop deblocking of the finished picture. + pub fn deblock(&mut self) { + crate::deblock::deblock(&mut self.rec, &self.blk, self.w, self.h, self.slice_qp, self.bd); + } + + fn snap(&self, x: usize, y: usize, n: usize) -> Snap { + let mut planes: [Vec; 3] = Default::default(); + for (c, p) in planes.iter_mut().enumerate() { + let (px, py, pn, stride) = if c == 0 { (x, y, n, self.w) } else { (x / 2, y / 2, n / 2, self.cw) }; + p.reserve(pn * pn); + for r in 0..pn { + p.extend_from_slice(&self.rec[c][(py + r) * stride + px..][..pn]); + } + } + let mut blk = Vec::with_capacity((n / 4) * (n / 4)); + for by in (y >> 2)..((y + n) >> 2) { + blk.extend_from_slice(&self.blk[by * self.w4 + (x >> 2)..by * self.w4 + ((x + n) >> 2)]); + } + Snap { x, y, n, planes, blk } + } + + fn restore(&mut self, s: &Snap) { + let (x, y, n) = (s.x, s.y, s.n); + for c in 0..3 { + let (px, py, pn, stride) = if c == 0 { (x, y, n, self.w) } else { (x / 2, y / 2, n / 2, self.cw) }; + for r in 0..pn { + self.rec[c][(py + r) * stride + px..][..pn].copy_from_slice(&s.planes[c][r * pn..r * pn + pn]); + } + } + let bw = n / 4; + for (i, by) in ((y >> 2)..((y + n) >> 2)).enumerate() { + self.blk[by * self.w4 + (x >> 2)..by * self.w4 + (x >> 2) + bw].copy_from_slice(&s.blk[i * bw..i * bw + bw]); + } + } + + // --------------------------------------------------------------------------------------- + // picture loop + + /// Code all CTUs of the picture into `enc` (one slice); the caller flushes the encoder. + pub fn encode(&mut self, enc: &mut CabacEncoder) { + let ctb = 1usize << LOG2_CTB; + let total = self.wctb * self.hctb; + for i in 0..total { + let (x, y) = ((i % self.wctb) * ctb, (i / self.wctb) * ctb); + let mut est = Estimator::new(enc.ctx); + let mut decs = Vec::with_capacity(64); + self.decide(x, y, LOG2_CTB, 0, &mut est, &mut decs); + let mut idx = 0; + self.write_cq(enc, x, y, LOG2_CTB, 0, &decs, &mut idx); + debug_assert_eq!(idx, decs.len()); + debug_assert_eq!(est.ctx, enc.ctx); + enc.terminate((i + 1 == total) as u32); // end_of_slice_segment_flag + } + } + + fn write_split(&self, s: &mut S, x: usize, y: usize, depth: u32, split: bool) { + let (x, y) = (x as i32, y as i32); + let mut inc = 0; + if self.zavail(x, y, x - 1, y) && self.blk_at(x - 1, y).depth as u32 > depth { + inc += 1; + } + if self.zavail(x, y, x, y - 1) && self.blk_at(x, y - 1).depth as u32 > depth { + inc += 1; + } + s.decision(SPLIT_CU + inc, split as u32); + } + + /// Final pass: re-code the decided quadtree with the real arithmetic coder. + fn write_cq(&mut self, enc: &mut CabacEncoder, x: usize, y: usize, log2: u32, depth: u32, decs: &[CuDec], idx: &mut usize) { + if x >= self.w || y >= self.h { + return; + } + let n = 1usize << log2; + let inside = x + n <= self.w && y + n <= self.h; + let d = decs[*idx]; + let leaf = d.x as usize == x && d.y as usize == y && d.log2 == log2; + if inside && log2 > LOG2_MIN_CB { + self.write_split(enc, x, y, depth, !leaf); + } else { + debug_assert_eq!(leaf, log2 == LOG2_MIN_CB); + } + if leaf { + *idx += 1; + let (eff, _) = self.encode_cu(enc, x, y, log2, depth, d.mode); + debug_assert_eq!(eff, d.mode); + } else { + let h = n / 2; + for (dx, dy) in [(0, 0), (h, 0), (0, h), (h, h)] { + self.write_cq(enc, x + dx, y + dy, log2 - 1, depth + 1, decs, idx); + } + } + } + + #[inline] + fn cost(&self, dist: u64, bits: u64) -> f64 { + dist as f64 + self.lambda * bits as f64 / BIT as f64 + } + + /// Rate-distortion decision for the quadtree node at (x, y); leaves the chosen reconstruction + /// and block state in place, advances `est` and appends the decided CUs. Returns the RD cost. + fn decide(&mut self, x: usize, y: usize, log2: u32, depth: u32, est: &mut Estimator, decs: &mut Vec) -> f64 { + if x >= self.w || y >= self.h { + return 0.0; + } + let n = 1usize << log2; + let inside = x + n <= self.w && y + n <= self.h; + let can_split = log2 > LOG2_MIN_CB; + let base = est.bits; + let leaf_ok = inside && (self.kind == SliceKind::P || log2 <= LOG2_MAX_TB); + let mut best: Option<(f64, Estimator, Snap, CuMode)> = None; + if leaf_ok { + let mut e0 = est.clone(); + if can_split { + self.write_split(&mut e0, x, y, depth, false); + } + let try_mode = |me: &mut Self, mode: CuMode, best: &mut Option<(f64, Estimator, Snap, CuMode)>| { + let mut e = e0.clone(); + let (eff, dist) = me.encode_cu(&mut e, x, y, log2, depth, mode); + let cost = me.cost(dist, e.bits - base); + if best.as_ref().is_none_or(|b| cost < b.0) { + *best = Some((cost, e, me.snap(x, y, n), eff)); + } + }; + let mut inter_satd = u64::MAX; + if self.kind == SliceKind::P { + let merge = self.merge_list(x, y, n); + let (midx, msatd) = self.best_merge(x, y, n, &merge); + try_mode(self, CuMode::Skip { idx: midx as u8 }, &mut best); + try_mode(self, CuMode::Merge { idx: midx as u8 }, &mut best); + let amvp = self.amvp(x, y, n); + let (mv, mvp, asatd) = self.motion_search(x, y, n, &amvp, &merge); + if mv != merge[midx] { + try_mode(self, CuMode::Amvp { mv, mvp }, &mut best); + } + inter_satd = msatd.min(asatd); + } + if log2 <= LOG2_MAX_TB && !matches!(best, Some((_, _, _, CuMode::Skip { .. }))) { + let (mode, satd) = self.search_intra_mode(x, y, log2); + if satd < inter_satd { + try_mode(self, CuMode::Intra { nxn: false, modes: [mode as u8; 4] }, &mut best); + if log2 == LOG2_MIN_CB { + let modes = self.search_nxn_modes(x, y); + try_mode(self, CuMode::Intra { nxn: true, modes }, &mut best); + } + } + } + if let Some(b) = &best { + self.restore(&b.2); + } + } + let early = matches!(best, Some((_, _, _, CuMode::Skip { .. }))); + if can_split && (!leaf_ok || !early) { + let mut e = est.clone(); + if inside { + self.write_split(&mut e, x, y, depth, true); + } + let limit = best.as_ref().map_or(f64::INFINITY, |b| b.0); + let mut cost = self.cost(0, e.bits - base); + let mut d2 = Vec::new(); + let h = n / 2; + for (dx, dy) in [(0, 0), (h, 0), (0, h), (h, h)] { + cost += self.decide(x + dx, y + dy, log2 - 1, depth + 1, &mut e, &mut d2); + if cost >= limit { + break; + } + } + if cost < limit { + *est = e; + decs.extend(d2); + return cost; + } + } + let (cost, e, snap, mode) = best.expect("a coding unit decision"); + self.restore(&snap); + *est = e; + decs.push(CuDec { x: x as u32, y: y as u32, log2, mode }); + cost + } + + // --------------------------------------------------------------------------------------- + // coding unit + + fn skip_ctx(&self, x: usize, y: usize) -> usize { + let (x, y) = (x as i32, y as i32); + let mut inc = 0; + if self.zavail(x, y, x - 1, y) && self.blk_at(x - 1, y).flags & F_SKIP != 0 { + inc += 1; + } + if self.zavail(x, y, x, y - 1) && self.blk_at(x, y - 1).flags & F_SKIP != 0 { + inc += 1; + } + inc + } + + /// Predict, transform, quantise and reconstruct one CU and write its syntax into `s`. + /// Returns the effective mode (a residual-free merge becomes a skip) and the SSE. + fn encode_cu(&mut self, s: &mut S, x: usize, y: usize, log2: u32, depth: u32, mode: CuMode) -> (CuMode, u64) { + let n = 1usize << log2; + self.tbs.clear(); + self.coefs.clear(); + let p_slice = self.kind == SliceKind::P; + let skip_ctx = if p_slice { self.skip_ctx(x, y) } else { 0 }; + let d8 = depth as u8; + match mode { + CuMode::Intra { nxn, modes } => { + self.fill(x, y, n, |b| { + b.depth = d8; + b.flags = F_INTRA; + b.ipm = modes[0]; + }); + self.mark_edges(x, y, log2); + if nxn { + let h = n / 2; + for k in 1..4 { + let m = modes[k]; + self.fill(x + (k & 1) * h, y + (k >> 1) * h, h, |b| b.ipm = m); + } + for k in 0..4 { + let (px, py) = (x + (k & 1) * h, y + (k >> 1) * h); + self.code_tb(0, px, py, 2, Some(modes[k] as u32), (x, y)); + } + for c in 1..3 { + self.code_tb(c, x / 2, y / 2, 2, Some(modes[0] as u32), (x, y)); + } + } else { + self.code_tus(x, y, log2, Some(modes[0] as u32)); + } + if p_slice { + s.decision(CU_SKIP + skip_ctx, 0); + s.decision(PRED_MODE, 1); + } + if log2 == LOG2_MIN_CB { + s.decision(PART_MODE, (!nxn) as u32); + } + self.write_intra_modes(s, x, y, n, nxn, &modes); + s.decision(INTRA_CHROMA, 0); // intra_chroma_pred_mode 4 (DM) + let mut ti = 0; + self.write_tt(s, x, y, x, y, log2, 0, 0, [true, true], true, nxn, &mut ti); + (mode, self.cu_dist(x, y, n)) + } + _ => { + let mv = match mode { + CuMode::Skip { idx } | CuMode::Merge { idx } => self.merge_list(x, y, n)[idx as usize], + CuMode::Amvp { mv, .. } => mv, + CuMode::Intra { .. } => unreachable!(), + }; + let amvp = if let CuMode::Amvp { .. } = mode { self.amvp(x, y, n) } else { [[0; 2]; 2] }; + let skip = matches!(mode, CuMode::Skip { .. }); + self.fill(x, y, n, |b| { + b.depth = d8; + b.flags = if skip { F_SKIP } else { 0 }; + b.ipm = 1; + b.mv = mv; + }); + self.mark_edges(x, y, log2); + self.predict_inter(x, y, n, mv); + let mut eff = mode; + let mut root = false; + if !skip { + self.code_tus(x, y, log2, None); + root = self.tbs.iter().any(|t| t.nz); + if !root && let CuMode::Merge { idx } = mode { + // rqt_root_cbf would be inferred: code a skipped CU instead + eff = CuMode::Skip { idx }; + self.fill(x, y, n, |b| b.flags |= F_SKIP); + } + } + match eff { + CuMode::Skip { idx } => { + s.decision(CU_SKIP + skip_ctx, 1); + write_merge_idx(s, idx as usize); + } + CuMode::Merge { idx } => { + s.decision(CU_SKIP + skip_ctx, 0); + s.decision(PRED_MODE, 0); + s.decision(PART_MODE, 1); // PART_2Nx2N + s.decision(MERGE_FLAG, 1); + write_merge_idx(s, idx as usize); + let mut ti = 0; + self.write_tt(s, x, y, x, y, log2, 0, 0, [true, true], false, false, &mut ti); + } + CuMode::Amvp { mv, mvp } => { + s.decision(CU_SKIP + skip_ctx, 0); + s.decision(PRED_MODE, 0); + s.decision(PART_MODE, 1); + s.decision(MERGE_FLAG, 0); + let p = amvp[mvp as usize]; + write_mvd(s, [mv[0] as i32 - p[0] as i32, mv[1] as i32 - p[1] as i32]); + s.decision(MVP_FLAG, mvp as u32); + s.decision(RQT_ROOT_CBF, root as u32); + if root { + let mut ti = 0; + self.write_tt(s, x, y, x, y, log2, 0, 0, [true, true], false, false, &mut ti); + } + } + CuMode::Intra { .. } => unreachable!(), + } + (eff, self.cu_dist(x, y, n)) + } + } + } + + /// Transform units of a 2Nx2N CU (split to the maximum TB size), luma then Cb, Cr per TU. + fn code_tus(&mut self, x: usize, y: usize, log2: u32, intra_mode: Option) { + let tl = log2.min(LOG2_MAX_TB); + let t = 1usize << tl; + let k = 1usize << (log2 - tl); + for i in 0..k * k { + // z-order of at most 2x2 TUs + let (tx, ty) = (x + (i & 1) * t, y + (i >> 1) * t); + self.code_tb(0, tx, ty, tl, intra_mode, (tx, ty)); + self.code_tb(1, tx / 2, ty / 2, tl - 1, intra_mode, (tx, ty)); + self.code_tb(2, tx / 2, ty / 2, tl - 1, intra_mode, (tx, ty)); + } + } + + fn cu_dist(&self, x: usize, y: usize, n: usize) -> u64 { + let mut d = 0u64; + for c in 0..3 { + let (px, py, pn, stride) = if c == 0 { (x, y, n, self.w) } else { (x / 2, y / 2, n / 2, self.cw) }; + for r in 0..pn { + let o = (py + r) * stride + px; + let a = &self.src[c][o..o + pn]; + let b = &self.rec[c][o..o + pn]; + d += a.iter().zip(b).map(|(&p, &q)| (p as i64 - q as i64).pow(2) as u64).sum::(); + } + } + d + } + + /// Code one transform block of component `c` at component position (x, y): optional intra + /// prediction, residual, transform, quantisation and reconstruction. + fn code_tb(&mut self, c: usize, x: usize, y: usize, log2: u32, intra_mode: Option, lpos: (usize, usize)) { + let n = 1usize << log2; + let stride = if c == 0 { self.w } else { self.cw }; + if let Some(m) = intra_mode { + let refs = self.build_refs(c, x, y, log2); + let bd = self.bd; + predict_from_refs(&refs, c, m, n, bd, &mut self.rec[c][y * stride + x..], stride); + } + let mut scr = std::mem::take(&mut self.scratch); + let [r, coef] = &mut scr; + for yy in 0..n { + let o = (y + yy) * stride + x; + for xx in 0..n { + r[yy * n + xx] = self.src[c][o + xx] as i32 - self.rec[c][o + xx] as i32; + } + } + let intra = intra_mode.is_some(); + let dst = intra && c == 0 && n == 4; + transform::forward(&r[..n * n], &mut coef[..n * n], n, dst, self.bd); + let qp = if c == 0 { self.qp_y } else { self.qp_c }; + let off = self.coefs.len(); + self.coefs.resize(off + n * n, 0); + let lv = &mut self.coefs[off..off + n * n]; + let scan = match intra_mode { + Some(m) if log2 == 2 || (log2 == 3 && c == 0) => scan_idx_for_mode(m), + _ => 0, + }; + let nz = transform::quantize(&coef[..n * n], lv, n, qp, intra, self.bd) > 0; + if nz { + transform::sign_hiding(&coef[..n * n], lv, log2, scan as usize, qp, self.bd); + } + if nz { + let d = &mut r[..n * n]; + transform::dequantize(lv, d, n, qp, self.bd); + transform::inverse(d, n, dst, self.bd); + let max = (1i32 << self.bd) - 1; + for yy in 0..n { + let o = (y + yy) * stride + x; + for xx in 0..n { + let v = &mut self.rec[c][o + xx]; + *v = (*v as i32 + d[yy * n + xx]).clamp(0, max) as u16; + } + } + } + self.scratch = scr; + if nz && c == 0 { + self.fill(x, y, n, |b| b.flags |= F_CBF); + } + self.tbs.push(Tb { c: c as u8, lx: lpos.0 as u32, ly: lpos.1 as u32, log2, nz, off, scan }); + } + + /// Reference samples (substituted, unfiltered) of an intra block, as in 8.4.4.2.1 / 8.4.4.2.2. + fn build_refs(&self, c: usize, x0: usize, y0: usize, log2: u32) -> Refs { + let n = 1usize << log2; + let sc = if c == 0 { 1 } else { 2 }; + let (x0, y0) = (x0 as i32, y0 as i32); + let (xl, yl) = (x0 * sc, y0 * sc); + let unit = 4 / sc as usize; + let stride = if c == 0 { self.w } else { self.cw }; + let mut al = [false; 129]; + let mut at = [false; 129]; + let plane = &self.rec[c]; + let mut r = Refs::default(); + if self.zavail(xl, yl, xl - 1, yl - 1) { + al[0] = true; + at[0] = true; + let v = plane[(y0 - 1) as usize * stride + (x0 - 1) as usize]; + r.left[0] = v; + r.top[0] = v; + } + let mut k = 0; + while k < 2 * n { + let yn = y0 + k as i32; + if self.zavail(xl, yl, xl - 1, yn * sc) { + for i in 0..unit { + al[1 + k + i] = true; + r.left[1 + k + i] = plane[(yn as usize + i) * stride + (x0 - 1) as usize]; + } + } + let xn = x0 + k as i32; + if self.zavail(xl, yl, xn * sc, yl - 1) { + let o = (y0 - 1) as usize * stride + xn as usize; + for i in 0..unit { + at[1 + k + i] = true; + r.top[1 + k + i] = plane[o + i]; + } + } + k += unit; + } + intra::substitute(&mut r, &al, &at, n, self.bd); + r + } + + // --------------------------------------------------------------------------------------- + // syntax writers + + /// Candidate modes candModeList (8.4.2) of the prediction block at (xp, yp). + fn mpm(&self, xp: usize, yp: usize) -> [u32; 3] { + let (xp, yp) = (xp as i32, yp as i32); + let cand_a = if self.zavail(xp, yp, xp - 1, yp) { + let b = self.blk_at(xp - 1, yp); + if b.flags & F_INTRA != 0 { b.ipm as u32 } else { 1 } + } else { + 1 + }; + let cand_b = if self.zavail(xp, yp, xp, yp - 1) && yp > ((yp >> LOG2_CTB) << LOG2_CTB) { + let b = self.blk_at(xp, yp - 1); + if b.flags & F_INTRA != 0 { b.ipm as u32 } else { 1 } + } else { + 1 + }; + if cand_a == cand_b { + if cand_a < 2 { [0, 1, 26] } else { [cand_a, 2 + ((cand_a + 29) % 32), 2 + ((cand_a - 2 + 1) % 32)] } + } else { + let c = if cand_a != 0 && cand_b != 0 { + 0 + } else if cand_a != 1 && cand_b != 1 { + 1 + } else { + 26 + }; + [cand_a, cand_b, c] + } + } + + fn write_intra_modes(&self, s: &mut S, x: usize, y: usize, n: usize, nxn: bool, modes: &[u8; 4]) { + let nb = if nxn { 2 } else { 1 }; + let pb = n / nb; + let mut codes = [(false, 0u32); 4]; + for j in 0..nb { + for i in 0..nb { + let k = j * nb + i; + let list = self.mpm(x + i * pb, y + j * pb); + let m = modes[k] as u32; + codes[k] = match list.iter().position(|&c| c == m) { + Some(idx) => (true, idx as u32), + None => { + let mut sorted = list; + sorted.sort_unstable(); + let mut r = m; + for &c in sorted.iter().rev() { + if r > c { + r -= 1; + } + } + (false, r) + } + }; + } + } + for code in codes.iter().take(nb * nb) { + s.decision(PREV_INTRA_LUMA, code.0 as u32); + } + for &(prev, v) in codes.iter().take(nb * nb) { + if prev { + s.bypass((v > 0) as u32); + if v > 0 { + s.bypass((v > 1) as u32); + } + } else { + s.bypass_bits(v, 5); + } + } + } + + fn chroma_cbf(&self, c: u8, x0: usize, y0: usize, n: usize) -> bool { + self.tbs.iter().any(|t| t.c == c && t.nz && (t.lx as usize) >= x0 && (t.lx as usize) < x0 + n && (t.ly as usize) >= y0 && (t.ly as usize) < y0 + n) + } + + /// transform_tree() (7.3.8.8) over the coded transform blocks of the CU; no split flag is ever + /// coded (max_transform_hierarchy_depth_* = 0), splits are inferred. + #[allow(clippy::too_many_arguments)] + fn write_tt( + &self, + s: &mut S, + x0: usize, + y0: usize, + xb: usize, + yb: usize, + log2: u32, + depth: u32, + blk_idx: u32, + parent: [bool; 2], + intra: bool, + nxn: bool, + ti: &mut usize, + ) { + let n = 1usize << log2; + let split = log2 > LOG2_MAX_TB || (nxn && depth == 0); + let mut cbf_c = [false; 2]; + if log2 > 2 { + for c in 0..2 { + if depth == 0 || parent[c] { + cbf_c[c] = self.chroma_cbf(c as u8 + 1, x0, y0, n); + s.decision(CBF_CHROMA + depth as usize, cbf_c[c] as u32); + } + } + } else if depth > 0 { + cbf_c = parent; + } + if split { + let h = n / 2; + for (i, (dx, dy)) in [(0, 0), (h, 0), (0, h), (h, h)].into_iter().enumerate() { + self.write_tt(s, x0 + dx, y0 + dy, x0, y0, log2 - 1, depth + 1, i as u32, cbf_c, intra, nxn, ti); + } + return; + } + let _ = (xb, yb); + let tb = self.tbs[*ti]; + *ti += 1; + debug_assert_eq!(tb.c, 0); + if intra || depth != 0 || cbf_c[0] || cbf_c[1] { + s.decision(CBF_LUMA + (depth == 0) as usize, tb.nz as u32); + } else { + debug_assert!(tb.nz, "inferred cbf_luma needs coefficients"); + } + if tb.nz { + let n2 = 1usize << (2 * tb.log2); + residual_coding(s, &self.coefs[tb.off..tb.off + n2], tb.log2, 0, tb.scan as usize); + } + if log2 > 2 || blk_idx == 3 { + for c in 0..2 { + let tb = self.tbs[*ti]; + *ti += 1; + debug_assert_eq!(tb.c as usize, c + 1); + debug_assert_eq!(tb.nz, cbf_c[c]); + if cbf_c[c] { + let n2 = 1usize << (2 * tb.log2); + residual_coding(s, &self.coefs[tb.off..tb.off + n2], tb.log2, c + 1, tb.scan as usize); + } + } + } + } + + // --------------------------------------------------------------------------------------- + // inter prediction + + /// Prediction block availability (6.4.2) for a 2Nx2N CU: available and not intra. + #[inline] + fn pb_avail(&self, xp: i32, yp: i32, xn: i32, yn: i32) -> bool { + self.zavail(xp, yp, xn, yn) && self.blk_at(xn, yn).flags & F_INTRA == 0 + } + + /// Merge candidate list (8.5.3.2.2 - 8.5.3.2.5) of a 2Nx2N P-slice CU: spatial candidates and + /// zero candidates (no temporal candidate, one reference picture). + pub fn merge_list(&self, x: usize, y: usize, n: usize) -> [[i16; 2]; MAX_MERGE_CAND] { + let (xp, yp, w, h) = (x as i32, y as i32, n as i32, n as i32); + let mv = |p: (i32, i32)| self.blk_at(p.0, p.1).mv; + let mut list = [[0i16; 2]; MAX_MERGE_CAND]; + let mut k = 0; + let pa1 = (xp - 1, yp + h - 1); + let av_a1 = self.pb_avail(xp, yp, pa1.0, pa1.1); + if av_a1 { + list[k] = mv(pa1); + k += 1; + } + let pb1 = (xp + w - 1, yp - 1); + let av_b1 = self.pb_avail(xp, yp, pb1.0, pb1.1); + let fl_b1 = av_b1 && !(av_a1 && mv(pa1) == mv(pb1)); + if fl_b1 { + list[k] = mv(pb1); + k += 1; + } + let pb0 = (xp + w, yp - 1); + let fl_b0 = self.pb_avail(xp, yp, pb0.0, pb0.1) && !(av_b1 && mv(pb1) == mv(pb0)); + if fl_b0 { + list[k] = mv(pb0); + k += 1; + } + let pa0 = (xp - 1, yp + h); + let fl_a0 = self.pb_avail(xp, yp, pa0.0, pa0.1) && !(av_a1 && mv(pa1) == mv(pa0)); + if fl_a0 { + list[k] = mv(pa0); + k += 1; + } + let pb2 = (xp - 1, yp - 1); + let fl_b2 = self.pb_avail(xp, yp, pb2.0, pb2.1) + && !((av_a1 && mv(pa1) == mv(pb2)) || (av_b1 && mv(pb1) == mv(pb2))) + && (fl_a0 as u32 + av_a1 as u32 + fl_b0 as u32 + fl_b1 as u32) != 4; + if fl_b2 { + list[k] = mv(pb2); + } + // remaining entries are zero candidates with refIdx 0 + list + } + + /// AMVP candidates (8.5.3.2.6, 8.5.3.2.7) of a 2Nx2N CU with one reference picture. + pub fn amvp(&self, x: usize, y: usize, n: usize) -> [[i16; 2]; 2] { + let (xp, yp, w, h) = (x as i32, y as i32, n as i32, n as i32); + let pa = [(xp - 1, yp + h), (xp - 1, yp + h - 1)]; + let av_a = [self.pb_avail(xp, yp, pa[0].0, pa[0].1), self.pb_avail(xp, yp, pa[1].0, pa[1].1)]; + let is_scaled = av_a[0] || av_a[1]; + let mut a = None; + for k in 0..2 { + if av_a[k] && a.is_none() { + a = Some(self.blk_at(pa[k].0, pa[k].1).mv); + } + } + let pb = [(xp + w, yp - 1), (xp + w - 1, yp - 1), (xp - 1, yp - 1)]; + let mut b = None; + for p in pb { + if b.is_none() && self.pb_avail(xp, yp, p.0, p.1) { + b = Some(self.blk_at(p.0, p.1).mv); + } + } + if !is_scaled && b.is_some() { + a = b; + } + let mut list = [[0i16; 2]; 2]; + match (a, b) { + (Some(a), Some(b)) => { + list[0] = a; + if b != a { + list[1] = b; + } + } + (Some(a), None) => list[0] = a, + (None, Some(b)) => list[0] = b, + (None, None) => {} + } + list + } + + fn predict_inter(&mut self, x: usize, y: usize, n: usize, mv: [i16; 2]) { + let refp = self.refp.expect("reference picture"); + for c in 0..3 { + let (px, py, pn, pw, ph) = if c == 0 { (x, y, n, self.w, self.h) } else { (x / 2, y / 2, n / 2, self.cw, self.ch) }; + let pr = PlaneRef { data: &refp[c], w: pw, h: ph }; + if c == 0 { + inter::mc_luma(pr, self.bd, px as i32, py as i32, mv, pn, pn, &mut self.pred); + } else { + inter::mc_chroma(pr, self.bd, px as i32, py as i32, mv, pn, pn, &mut self.pred); + } + inter::put_uni(&self.pred, pn, pn, self.bd, &mut self.rec[c][py * pw + px..], pw); + } + } + + // --------------------------------------------------------------------------------------- + // estimation helpers + + /// Best merge index by SATD of the (approximate) luma prediction. + fn best_merge(&self, x: usize, y: usize, n: usize, list: &[[i16; 2]; MAX_MERGE_CAND]) -> (usize, u64) { + let me = self.me.expect("motion search reference"); + let mut buf = vec![0u16; n * n]; + let mut best = (0, u64::MAX); + for (i, &mv) in list.iter().enumerate() { + if list[..i].contains(&mv) { + continue; + } + me.approx_pred(x as i32, y as i32, n, mv, &mut buf); + let satd = satd(&self.src[0][y * self.w + x..], self.w, &buf, n, n) as u64; + let cost = satd + (self.lambda_sad * (i + 1) as f64) as u64; + if cost < best.1 { + best = (i, cost); + } + } + best + } + + /// Integer then fractional motion search. Returns (mv, mvp index, SATD cost). + fn motion_search(&self, x: usize, y: usize, n: usize, amvp: &[[i16; 2]; 2], merge: &[[i16; 2]; MAX_MERGE_CAND]) -> ([i16; 2], u8, u64) { + let me = self.me.expect("motion search reference"); + let (xi, yi) = (x as i32, y as i32); + let src = &self.src[0][y * self.w + x..]; + let ls = self.lambda_sad; + let mv_bits = |mv: [i32; 2]| -> f64 { amvp.iter().map(|p| mvd_bits(mv[0] - p[0] as i32) + mvd_bits(mv[1] - p[1] as i32)).fold(f64::MAX, f64::min) }; + let (lo_x, hi_x, lo_y, hi_y) = me.int_range(xi, yi, n); + let clamp_i = |v: [i32; 2]| [v[0].clamp(lo_x, hi_x), v[1].clamp(lo_y, hi_y)]; + let sad_cost = |v: [i32; 2]| -> f64 { me.sad(src, self.w, xi + v[0], yi + v[1], n) as f64 + ls * mv_bits([v[0] * 4, v[1] * 4]) }; + // starting points + let mut best = [0i32; 2]; + let mut best_cost = sad_cost(best); + for mv in amvp.iter().chain(merge.iter()) { + let v = clamp_i([(mv[0] as i32 + 2) >> 2, (mv[1] as i32 + 2) >> 2]); + let c = sad_cost(v); + if c < best_cost { + best_cost = c; + best = v; + } + } + // square pattern search with decreasing steps + let mut step = 16; + while step >= 1 { + let mut moved = true; + let mut iters = 0; + while moved && iters < 4 { + moved = false; + iters += 1; + let center = best; + for (dx, dy) in [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] { + let v = clamp_i([center[0] + dx * step, center[1] + dy * step]); + if v == center { + continue; + } + let c = sad_cost(v); + if c < best_cost { + best_cost = c; + best = v; + moved = true; + } + } + } + step /= 2; + } + // fractional refinement with SATD on the approximate interpolated planes + let mut buf = vec![0u16; n * n]; + let mut satd_cost = |mv: [i32; 2]| -> f64 { + me.approx_pred(xi, yi, n, [mv[0] as i16, mv[1] as i16], &mut buf); + satd(src, self.w, &buf, n, n) as f64 + ls * mv_bits(mv) + }; + let mut bq = [best[0] * 4, best[1] * 4]; + let mut bc = satd_cost(bq); + for step in [2, 1] { + let center = bq; + for (dx, dy) in [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] { + let v = [center[0] + dx * step, center[1] + dy * step]; + let c = satd_cost(v); + if c < bc { + bc = c; + bq = v; + } + } + } + let mv = [bq[0] as i16, bq[1] as i16]; + let bits = |p: &[i16; 2]| mvd_bits(bq[0] - p[0] as i32) + mvd_bits(bq[1] - p[1] as i32); + let mvp = if bits(&amvp[1]) < bits(&amvp[0]) { 1 } else { 0 }; + (mv, mvp, bc as u64) + } + + /// Best 2Nx2N intra luma mode by SATD (coarse grid, then refinement around the best angle). + fn search_intra_mode(&self, x: usize, y: usize, log2: u32) -> (u32, u64) { + let n = 1usize << log2; + let refs = self.build_refs(0, x, y, log2); + let mpm = self.mpm(x, y); + let mut buf = vec![0u16; n * n]; + let src = &self.src[0][y * self.w + x..]; + let mut eval = |m: u32| -> u64 { + predict_from_refs(&refs, 0, m, n, self.bd, &mut buf, n); + let bits = match mpm.iter().position(|&c| c == m) { + Some(0) => 2.0, + Some(_) => 3.0, + None => 6.0, + }; + satd(src, self.w, &buf, n, n) as u64 + (self.lambda_sad * bits) as u64 + }; + let mut best = (1u32, u64::MAX); + let mut tried = [false; 35]; + for &m in COARSE_MODES.iter().chain(mpm.iter()) { + if !tried[m as usize] { + tried[m as usize] = true; + let c = eval(m); + if c < best.1 { + best = (m, c); + } + } + } + if best.0 >= 2 { + for d in [2i32, 1] { + let center = best.0 as i32; + for m in [center - d, center + d] { + if (2..=34).contains(&m) && !tried[m as usize] { + tried[m as usize] = true; + let c = eval(m as u32); + if c < best.1 { + best = (m as u32, c); + } + } + } + } + } + best + } + + /// Modes of the four 4x4 PUs of an NxN intra CU, chosen sequentially (each PU reconstructed + /// before the next one is searched, as in the real coding order). + fn search_nxn_modes(&mut self, x: usize, y: usize) -> [u8; 4] { + let mut modes = [1u8; 4]; + self.fill(x, y, 8, |b| { + b.flags = F_INTRA; + b.ipm = 1; + }); + for k in 0..4 { + let (px, py) = (x + (k & 1) * 4, y + (k >> 1) * 4); + let (m, _) = self.search_intra_mode(px, py, 2); + modes[k] = m as u8; + self.fill(px, py, 4, |b| b.ipm = m as u8); + self.tbs.clear(); + self.coefs.clear(); + self.code_tb(0, px, py, 2, Some(m), (px, py)); + } + modes + } +} + +/// Filter (luma) and predict an intra block from substituted references. +fn predict_from_refs(refs: &Refs, c: usize, mode: u32, n: usize, bd: u32, out: &mut [u16], os: usize) { + if c == 0 { + let mut r = refs.clone(); + intra::filter(&mut r, mode, n, true, true, bd); + intra::predict(&r, mode, n, true, bd, out, os); + } else { + intra::predict(refs, mode, n, false, bd, out, os); + } +} + +/// scanIdx (7.4.9.11) for an intra prediction mode. +fn scan_idx_for_mode(m: u32) -> u8 { + if (6..=14).contains(&m) { + 2 + } else if (22..=30).contains(&m) { + 1 + } else { + 0 + } +} + +/// Approximate bits of one motion vector difference component. +fn mvd_bits(v: i32) -> f64 { + if v == 0 { 1.0 } else { (2 * (32 - (v.unsigned_abs()).leading_zeros()) + 1) as f64 } +} + +fn write_merge_idx(s: &mut S, idx: usize) { + if MAX_MERGE_CAND > 1 { + s.decision(MERGE_IDX, (idx > 0) as u32); + if idx > 0 { + let mut i = 1; + while i < MAX_MERGE_CAND - 1 { + let b = idx > i; + s.bypass(b as u32); + if !b { + break; + } + i += 1; + } + } + } +} + +/// mvd_coding() (7.3.8.9). +fn write_mvd(s: &mut S, d: [i32; 2]) { + let g0 = [d[0] != 0, d[1] != 0]; + s.decision(ABS_MVD_GT0, g0[0] as u32); + s.decision(ABS_MVD_GT0, g0[1] as u32); + for k in 0..2 { + if g0[k] { + s.decision(ABS_MVD_GT1, (d[k].abs() > 1) as u32); + } + } + for k in 0..2 { + if g0[k] { + let a = d[k].unsigned_abs(); + if a > 1 { + // abs_mvd_minus2: EG1 + let mut v = a - 2; + let mut kk = 1; + while v >= (1 << kk) { + s.bypass(1); + v -= 1 << kk; + kk += 1; + } + s.bypass(0); + s.bypass_bits(v, kk); + } + s.bypass((d[k] < 0) as u32); + } + } +} + +/// coeff_abs_level_remaining binarisation (9.3.3.11): TR prefix up to 3 with Rice suffix, then +/// an escape with an Exp-Golomb-like suffix. +fn write_remaining(s: &mut S, v: u32, rice: u32) { + if v < (4 << rice) { + let p = v >> rice; + for _ in 0..p { + s.bypass(1); + } + s.bypass(0); + s.bypass_bits(v & ((1 << rice) - 1), rice); + } else { + let u = v - (2 << rice); + let kr = 31 - u.leading_zeros(); // k + rice + let k = kr - rice; + for _ in 0..k + 3 { + s.bypass(1); + } + s.bypass(0); + s.bypass_bits(u - (1 << kr), kr); + } +} + +/// last_sig_coeff_{x,y}_prefix value and suffix for a position. +fn last_prefix(v: u32) -> (u32, u32, u32) { + if v < 4 { + return (v, 0, 0); + } + let mut p = 4; + loop { + let nb = (p >> 1) - 1; + let min = (1 << nb) * (2 + (p & 1)); + let next_nb = ((p + 1) >> 1) - 1; + let next = (1 << next_nb) * (2 + ((p + 1) & 1)); + if v < next { + return (p, v - min, nb); + } + p += 1; + } +} + +/// residual_coding() (7.3.8.11) of one transform block of levels (row-major, n x n). No transform +/// skip, no sign data hiding. +fn residual_coding(s: &mut S, lv: &[i32], log2: u32, c: usize, scan_idx: usize) { + let n = 1usize << log2; + let log2sb = log2 - 2; + let sb_w = 1usize << log2sb; + let sb_scan = scan_order(log2sb, scan_idx); + let pos_scan = &SCAN_4[scan_idx]; + let at = |xs: usize, ys: usize, p: usize| lv[(ys * 4 + pos_scan[p].1 as usize) * n + xs * 4 + pos_scan[p].0 as usize]; + // last significant coefficient in scan order + let mut last = None; + 'outer: for i in (0..sb_scan.len()).rev() { + let (xs, ys) = (sb_scan[i].0 as usize, sb_scan[i].1 as usize); + for p in (0..16).rev() { + if at(xs, ys, p) != 0 { + last = Some((i, p)); + break 'outer; + } + } + } + let (last_sb, last_pos) = last.expect("residual_coding of an all-zero block"); + let lx = sb_scan[last_sb].0 as u32 * 4 + pos_scan[last_pos].0 as u32; + let ly = sb_scan[last_sb].1 as u32 * 4 + pos_scan[last_pos].1 as u32; + let (cx, cy) = if scan_idx == 2 { (ly, lx) } else { (lx, ly) }; + let cmax = (log2 << 1) - 1; + let (off, shift) = if c == 0 { (3 * (log2 - 2) + ((log2 - 1) >> 2), (log2 + 1) >> 2) } else { (15, log2 - 2) }; + let (px, sx, nbx) = last_prefix(cx); + let (py, sy, nby) = last_prefix(cy); + for (p, base) in [(px, LAST_X_PREFIX), (py, LAST_Y_PREFIX)] { + for i in 0..p { + s.decision(base + (off + (i >> shift)) as usize, 1); + } + if p < cmax { + s.decision(base + (off + (p >> shift)) as usize, 0); + } + } + if px > 3 { + s.bypass_bits(sx, nbx); + } + if py > 3 { + s.bypass_bits(sy, nby); + } + let mut csbf = [0u8; 64]; + let mut prev_g1: Option = None; + let c_off_sig = if c == 0 { 0 } else { 27 }; + for i in (0..=last_sb).rev() { + let (xs, ys) = (sb_scan[i].0 as usize, sb_scan[i].1 as usize); + let right = if xs + 1 < sb_w { csbf[ys * 8 + xs + 1] } else { 0 }; + let below = if ys + 1 < sb_w { csbf[(ys + 1) * 8 + xs] } else { 0 }; + let mut infer_dc = false; + if i < last_sb && i > 0 { + let has = (0..16).any(|p| at(xs, ys, p) != 0); + let inc = (right | below).min(1) as usize + if c > 0 { 2 } else { 0 }; + s.decision(CODED_SUB_BLOCK + inc, has as u32); + csbf[ys * 8 + xs] = has as u8; + infer_dc = true; + } else { + csbf[ys * 8 + xs] = 1; + } + if csbf[ys * 8 + xs] == 0 { + continue; + } + let mut sig = [0u8; 16]; + let mut nsig = 0; + let start: i32 = if i == last_sb { + sig[0] = last_pos as u8; + nsig = 1; + last_pos as i32 - 1 + } else { + 15 + }; + let prev_csbf = right + (below << 1); + let mut np = start; + while np >= 0 { + let p = np as usize; + let (xp, yp) = (pos_scan[p].0 as usize, pos_scan[p].1 as usize); + let (xc, yc) = (xs * 4 + xp, ys * 4 + yp); + let v = lv[yc * n + xc] != 0; + if np > 0 || !infer_dc { + let sig_ctx = if log2 == 2 { + CTX_IDX_MAP[(yc << 2) + xc] as usize + } else if xc + yc == 0 { + 0 + } else { + let mut sc = match prev_csbf { + 0 => { + if xp + yp == 0 { + 2 + } else if xp + yp < 3 { + 1 + } else { + 0 + } + } + 1 => { + if yp == 0 { + 2 + } else if yp == 1 { + 1 + } else { + 0 + } + } + 2 => { + if xp == 0 { + 2 + } else if xp == 1 { + 1 + } else { + 0 + } + } + _ => 2, + }; + if c == 0 { + if xs + ys > 0 { + sc += 3; + } + sc += if log2 == 3 { if scan_idx == 0 { 9 } else { 15 } } else { 21 }; + } else { + sc += if log2 == 3 { 9 } else { 12 }; + } + sc + }; + s.decision(SIG_COEFF + c_off_sig + sig_ctx, v as u32); + if v { + sig[nsig] = p as u8; + nsig += 1; + infer_dc = false; + } + } else { + debug_assert!(v, "inferred DC coefficient must be non-zero"); + sig[nsig] = 0; + nsig += 1; + } + np -= 1; + } + if nsig == 0 { + continue; + } + let abs_at = |k: usize| at(xs, ys, sig[k] as usize).unsigned_abs(); + let mut ctx_set = if i == 0 || c > 0 { 0 } else { 2 }; + if prev_g1 == Some(0) { + ctx_set += 1; + } + let mut g1ctx = 1u32; + let mut gt1 = [0u8; 16]; + let mut first_g1: Option = None; + let c_off_g1 = if c > 0 { 16 } else { 0 }; + for k in 0..nsig.min(8) { + let f = abs_at(k) > 1; + s.decision(GT1 + ctx_set * 4 + g1ctx.min(3) as usize + c_off_g1, f as u32); + gt1[k] = f as u8; + if f { + g1ctx = 0; + if first_g1.is_none() { + first_g1 = Some(k); + } + } else if g1ctx > 0 { + g1ctx += 1; + } + } + prev_g1 = Some(g1ctx); + let mut gt2 = 0u32; + if let Some(k) = first_g1 { + gt2 = (abs_at(k) > 2) as u32; + s.decision(GT2 + ctx_set + if c > 0 { 4 } else { 0 }, gt2); + } + // sign data hiding: the sign of the first coefficient in scan order (sig[nsig - 1]) is + // inferred from the parity of the sum of absolute levels + let hidden = sig[0] as i32 - sig[nsig - 1] as i32 > 3; + let nsigns = if hidden { nsig - 1 } else { nsig }; + for k in 0..nsigns { + s.bypass((at(xs, ys, sig[k] as usize) < 0) as u32); + } + if hidden { + let sum: u32 = (0..nsig).map(abs_at).sum(); + debug_assert_eq!(at(xs, ys, sig[nsig - 1] as usize) < 0, sum & 1 == 1, "sign hiding parity"); + } + let mut rice = 0u32; + for k in 0..nsig { + let mut base = 1 + gt1[k] as u32; + if Some(k) == first_g1 { + base += gt2; + } + let thresh = if k < 8 { if Some(k) == first_g1 { 3 } else { 2 } } else { 1 }; + if base == thresh { + let level = abs_at(k); + write_remaining(s, level - base, rice); + if level > 3 * (1 << rice) { + rice = (rice + 1).min(4); + } + } + } + } +} + +/// Sum of absolute Hadamard-transformed differences (8x8 blocks, 4x4 for 4-sample blocks). +pub fn satd(src: &[u16], ss: usize, pred: &[u16], ps: usize, n: usize) -> u32 { + if n == 4 { + return satd4(src, ss, pred, ps); + } + let mut total = 0; + for by in (0..n).step_by(8) { + for bx in (0..n).step_by(8) { + total += satd8(&src[by * ss + bx..], ss, &pred[by * ps + bx..], ps); + } + } + total +} + +fn satd4(src: &[u16], ss: usize, pred: &[u16], ps: usize) -> u32 { + let mut d = [0i32; 16]; + for y in 0..4 { + for x in 0..4 { + d[y * 4 + x] = src[y * ss + x] as i32 - pred[y * ps + x] as i32; + } + } + let mut m = [0i32; 16]; + for y in 0..4 { + let r = &d[y * 4..y * 4 + 4]; + let (a, b, c, e) = (r[0] + r[3], r[1] + r[2], r[1] - r[2], r[0] - r[3]); + m[y * 4] = a + b; + m[y * 4 + 1] = e + c; + m[y * 4 + 2] = a - b; + m[y * 4 + 3] = e - c; + } + let mut sum = 0; + for x in 0..4 { + let (r0, r1, r2, r3) = (m[x], m[4 + x], m[8 + x], m[12 + x]); + let (a, b, c, e) = (r0 + r3, r1 + r2, r1 - r2, r0 - r3); + sum += (a + b).abs() + (e + c).abs() + (a - b).abs() + (e - c).abs(); + } + ((sum + 1) >> 1) as u32 +} + +fn hadamard8(v: &mut [i32; 8]) { + let mut t = *v; + let mut h = 1; + while h < 8 { + for i in (0..8).step_by(h * 2) { + for j in i..i + h { + let (a, b) = (t[j], t[j + h]); + t[j] = a + b; + t[j + h] = a - b; + } + } + h *= 2; + } + *v = t; +} + +fn satd8(src: &[u16], ss: usize, pred: &[u16], ps: usize) -> u32 { + let mut m = [[0i32; 8]; 8]; + for y in 0..8 { + for x in 0..8 { + m[y][x] = src[y * ss + x] as i32 - pred[y * ps + x] as i32; + } + hadamard8(&mut m[y]); + } + let mut sum = 0; + for x in 0..8 { + let mut col = [0i32; 8]; + for y in 0..8 { + col[y] = m[y][x]; + } + hadamard8(&mut col); + sum += col.iter().map(|v| v.abs()).sum::(); + } + ((sum + 2) >> 2) as u32 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn last_prefix_groups() { + assert_eq!(last_prefix(3), (3, 0, 0)); + assert_eq!(last_prefix(4), (4, 0, 1)); + assert_eq!(last_prefix(5), (4, 1, 1)); + assert_eq!(last_prefix(7), (5, 1, 1)); + assert_eq!(last_prefix(8), (6, 0, 2)); + assert_eq!(last_prefix(15), (7, 3, 2)); + assert_eq!(last_prefix(16), (8, 0, 3)); + assert_eq!(last_prefix(31), (9, 7, 3)); + } +} diff --git a/crates/hevcenc/src/inter.rs b/crates/hevcenc/src/inter.rs new file mode 100644 index 00000000..e363871b --- /dev/null +++ b/crates/hevcenc/src/inter.rs @@ -0,0 +1,155 @@ +//! Fractional sample interpolation (8.5.3.3.3) and default weighted prediction (8-262), mirrored +//! from the filmcraft-hevc decoder. Reference sample fetches clamp to the picture (8-228, 8-229). + +use crate::tables::{CHROMA_FILTER, LUMA_FILTER}; + +/// One plane of a reference picture. +#[derive(Clone, Copy)] +pub struct PlaneRef<'a> { + pub data: &'a [u16], + pub w: usize, + pub h: usize, +} + +impl PlaneRef<'_> { + /// Copy a w x h window at (x0, y0) with edge replication. + fn window(&self, x0: i32, y0: i32, w: usize, h: usize, out: &mut [i16], os: usize) { + let (pw, ph) = (self.w as i32, self.h as i32); + let inside_x = x0 >= 0 && x0 + w as i32 <= pw; + for r in 0..h { + let yy = (y0 + r as i32).clamp(0, ph - 1) as usize; + let row = &self.data[yy * self.w..yy * self.w + self.w]; + let dst = &mut out[r * os..r * os + w]; + if inside_x { + for (d, &s) in dst.iter_mut().zip(&row[x0 as usize..x0 as usize + w]) { + *d = s as i16; + } + } else { + for (c, d) in dst.iter_mut().enumerate() { + *d = row[(x0 + c as i32).clamp(0, pw - 1) as usize] as i16; + } + } + } + } +} + +/// Luma prediction samples (14-bit intermediate) of a w x h block at (x, y) displaced by the +/// quarter-sample vector `mv`, into `out` (stride w). +pub fn mc_luma(f: PlaneRef, bd: u32, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16]) { + let (fx, fy) = ((mv[0] & 3) as usize, (mv[1] & 3) as usize); + let xi = x + (mv[0] as i32 >> 2); + let yi = y + (mv[1] as i32 >> 2); + let shift1 = bd.min(12) - 8; + let shift3 = 14 - bd; + let ws = w + 7; + let mut win = vec![0i16; ws * (h + 7)]; + if fx == 0 && fy == 0 { + f.window(xi, yi, w, h, &mut win, ws); + for r in 0..h { + for c in 0..w { + out[r * w + c] = win[r * ws + c] << shift3; + } + } + return; + } + f.window(xi - 3, yi - 3, w + 7, h + 7, &mut win, ws); + let hf = &LUMA_FILTER[fx]; + let vf = &LUMA_FILTER[fy]; + if fy == 0 { + for r in 0..h { + let row = &win[(r + 3) * ws..]; + for c in 0..w { + let s: i32 = (0..8).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else if fx == 0 { + for r in 0..h { + for c in 0..w { + let s: i32 = (0..8).map(|k| vf[k] as i32 * win[(r + k) * ws + c + 3] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else { + let mut tmp = vec![0i16; ws * (h + 7)]; + for r in 0..h + 7 { + let row = &win[r * ws..]; + for c in 0..w { + let s: i32 = (0..8).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + tmp[r * ws + c] = (s >> shift1) as i16; + } + } + for r in 0..h { + for c in 0..w { + let s: i32 = (0..8).map(|k| vf[k] as i32 * tmp[(r + k) * ws + c] as i32).sum(); + out[r * w + c] = (s >> 6) as i16; + } + } + } +} + +/// Chroma prediction samples of a w x h block at chroma position (x, y) with a 1/8-sample vector. +pub fn mc_chroma(f: PlaneRef, bd: u32, x: i32, y: i32, mv: [i16; 2], w: usize, h: usize, out: &mut [i16]) { + let (fx, fy) = ((mv[0] & 7) as usize, (mv[1] & 7) as usize); + let xi = x + (mv[0] as i32 >> 3); + let yi = y + (mv[1] as i32 >> 3); + let shift1 = bd.min(12) - 8; + let shift3 = 14 - bd; + let ws = w + 7; + let mut win = vec![0i16; ws * (h + 7)]; + if fx == 0 && fy == 0 { + f.window(xi, yi, w, h, &mut win, ws); + for r in 0..h { + for c in 0..w { + out[r * w + c] = win[r * ws + c] << shift3; + } + } + return; + } + f.window(xi - 1, yi - 1, w + 3, h + 3, &mut win, ws); + let hf = &CHROMA_FILTER[fx]; + let vf = &CHROMA_FILTER[fy]; + if fy == 0 { + for r in 0..h { + let row = &win[(r + 1) * ws..]; + for c in 0..w { + let s: i32 = (0..4).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else if fx == 0 { + for r in 0..h { + for c in 0..w { + let s: i32 = (0..4).map(|k| vf[k] as i32 * win[(r + k) * ws + c + 1] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else { + let mut tmp = vec![0i16; ws * (h + 7)]; + for r in 0..h + 3 { + let row = &win[r * ws..]; + for c in 0..w { + let s: i32 = (0..4).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + tmp[r * ws + c] = (s >> shift1) as i16; + } + } + for r in 0..h { + for c in 0..w { + let s: i32 = (0..4).map(|k| vf[k] as i32 * tmp[(r + k) * ws + c] as i32).sum(); + out[r * w + c] = (s >> 6) as i16; + } + } + } +} + +/// Default weighted prediction, one list (8-262): 14-bit samples to `bd`-bit samples. +pub fn put_uni(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) { + let shift = 14 - bd; + let off = if shift > 0 { 1 << (shift - 1) } else { 0 }; + let max = (1i32 << bd) - 1; + for r in 0..h { + for c in 0..w { + dst[r * ds + c] = ((p[r * w + c] as i32 + off) >> shift).clamp(0, max) as u16; + } + } +} diff --git a/crates/hevcenc/src/intra.rs b/crates/hevcenc/src/intra.rs new file mode 100644 index 00000000..f1fe9e4c --- /dev/null +++ b/crates/hevcenc/src/intra.rs @@ -0,0 +1,254 @@ +//! Intra sample prediction (8.4.4.2), mirrored from the filmcraft-hevc decoder so the encoder +//! reconstructs exactly like a decoder: reference substitution, filtering (incl. strong intra +//! smoothing), planar, DC and the 33 angular modes. + +use crate::tables::{INTRA_PRED_ANGLE, INV_ANGLE}; + +/// Reference samples of an nTbS block: `left[0]` = `top[0]` = p[-1][-1], `left[1 + y]` = p[-1][y], +/// `top[1 + x]` = p[x][-1] for x, y = 0..2*nTbS-1. +#[derive(Clone)] +pub struct Refs { + pub left: [u16; 129], + pub top: [u16; 129], +} + +impl Default for Refs { + fn default() -> Self { + Refs { left: [0; 129], top: [0; 129] } + } +} + +/// Substitution process (8.4.4.2.2). `avail_left[1 + y]` / `avail_top[1 + x]` (index 0 = corner) tell +/// which samples are available; unavailable ones are replaced. +pub fn substitute(r: &mut Refs, avail_left: &[bool; 129], avail_top: &[bool; 129], n: usize, bit_depth: u32) { + let n2 = 2 * n; + // Sequence order: p[-1][2n-1] .. p[-1][-1], p[0][-1] .. p[2n-1][-1] + let any = avail_left[..=n2].iter().any(|&a| a) || avail_top[1..=n2].iter().any(|&a| a); + if !any { + let v = 1u16 << (bit_depth - 1); + r.left[..=n2].fill(v); + r.top[..=n2].fill(v); + return; + } + if !avail_left[n2] { + // search upwards then along the top + let mut found = None; + for y in (0..n2).rev() { + if avail_left[y] { + found = Some(r.left[y]); + break; + } + } + if found.is_none() { + for x in 1..=n2 { + if avail_top[x] { + found = Some(r.top[x]); + break; + } + } + } + r.left[n2] = found.unwrap_or(0); + } + for y in (0..n2).rev() { + if !avail_left[y] { + r.left[y] = r.left[y + 1]; + } + } + r.top[0] = r.left[0]; + for x in 1..=n2 { + if !avail_top[x] { + r.top[x] = r.top[x - 1]; + } + } +} + +/// Filtering process of neighbouring samples (8.4.4.2.3) for luma (and 4:4:4 chroma). +pub fn filter(r: &mut Refs, mode: u32, n: usize, strong_enabled: bool, is_luma: bool, bit_depth: u32) { + if mode == 1 || n == 4 { + return; + } + let min_dist = (mode as i32 - 26).abs().min((mode as i32 - 10).abs()); + let thres = match n { + 8 => 7, + 16 => 1, + _ => 0, + }; + if min_dist <= thres { + return; + } + let n2 = 2 * n; + let c = r.left[0] as i32; + if strong_enabled && is_luma && n == 32 { + let (bl, tr) = (r.left[n2] as i32, r.top[n2] as i32); + let thr = 1 << (bit_depth - 5); + if (c + tr - 2 * r.top[n] as i32).abs() < thr && (c + bl - 2 * r.left[n] as i32).abs() < thr { + for i in 0..63 { + r.left[1 + i] = (((63 - i as i32) * c + (i as i32 + 1) * bl + 32) >> 6) as u16; + r.top[1 + i] = (((63 - i as i32) * c + (i as i32 + 1) * tr + 32) >> 6) as u16; + } + return; + } + } + let (l, t) = (r.left, r.top); + let corner = ((l[1] as u32 + 2 * c as u32 + t[1] as u32 + 2) >> 2) as u16; + for i in 1..n2 { + r.left[i] = ((l[i - 1] as u32 + 2 * l[i] as u32 + l[i + 1] as u32 + 2) >> 2) as u16; + r.top[i] = ((t[i - 1] as u32 + 2 * t[i] as u32 + t[i + 1] as u32 + 2) >> 2) as u16; + } + r.left[0] = corner; + r.top[0] = corner; +} + +/// Predict an n x n block with `mode` into `out` (stride `os`). `edge_filters`: DC / horizontal / +/// vertical boundary smoothing (luma blocks smaller than 32x32). +pub fn predict(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32, out: &mut [u16], os: usize) { + let max = (1i32 << bit_depth) - 1; + match mode { + 0 => { + // planar + let log2 = n.trailing_zeros(); + let tr = r.top[1 + n] as i32; + let bl = r.left[1 + n] as i32; + for y in 0..n { + let l = r.left[1 + y] as i32; + for x in 0..n { + let t = r.top[1 + x] as i32; + let v = ((n - 1 - x) as i32 * l + (x as i32 + 1) * tr + (n - 1 - y) as i32 * t + (y as i32 + 1) * bl + n as i32) >> (log2 + 1); + out[y * os + x] = v as u16; + } + } + } + 1 => { + let log2 = n.trailing_zeros(); + let sum: u32 = r.top[1..=n].iter().map(|&v| v as u32).sum::() + r.left[1..=n].iter().map(|&v| v as u32).sum::(); + let dc = ((sum + n as u32) >> (log2 + 1)) as u16; + for y in 0..n { + out[y * os..y * os + n].fill(dc); + } + if edge_filters && n < 32 { + let d = dc as u32; + out[0] = ((r.left[1] as u32 + 2 * d + r.top[1] as u32 + 2) >> 2) as u16; + for x in 1..n { + out[x] = ((r.top[1 + x] as u32 + 3 * d + 2) >> 2) as u16; + } + for y in 1..n { + out[y * os] = ((r.left[1 + y] as u32 + 3 * d + 2) >> 2) as u16; + } + } + } + _ => { + let angle = INTRA_PRED_ANGLE[mode as usize] as i32; + // reference array ref[-n..=2n] stored at offset n + let mut refa = [0i32; 3 * 64 + 1]; + let base = n as i32; + let (main, side) = if mode >= 18 { (&r.top, &r.left) } else { (&r.left, &r.top) }; + for x in 0..=n { + refa[(base + x as i32) as usize] = main[x] as i32; // ref[x] = p[-1+x][-1] + } + if angle < 0 { + let lim = (n as i32 * angle) >> 5; + if lim < -1 { + let inv = INV_ANGLE[(mode - 11) as usize] as i32; + for x in lim..=-1 { + let idx = -1 + ((x * inv + 128) >> 8); + refa[(base + x) as usize] = side[(idx + 1) as usize] as i32; + } + } + } else { + for x in n + 1..=2 * n { + refa[(base + x as i32) as usize] = main[x] as i32; + } + } + let horizontal = mode < 18; + for j in 0..n { + let pos = (j as i32 + 1) * angle; + let idx = pos >> 5; + let fact = pos & 31; + for i in 0..n { + let k = (base + i as i32 + idx + 1) as usize; + let v = if fact != 0 { ((32 - fact) * refa[k] + fact * refa[k + 1] + 16) >> 5 } else { refa[k] }; + if horizontal { + out[i * os + j] = v as u16; + } else { + out[j * os + i] = v as u16; + } + } + } + if edge_filters && n < 32 { + if mode == 26 { + for y in 0..n { + let v = r.top[1] as i32 + ((r.left[1 + y] as i32 - r.left[0] as i32) >> 1); + out[y * os] = v.clamp(0, max) as u16; + } + } else if mode == 10 { + for x in 0..n { + let v = r.left[1] as i32 + ((r.top[1 + x] as i32 - r.top[0] as i32) >> 1); + out[x] = v.clamp(0, max) as u16; + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn refs(f: impl Fn(i32) -> u16) -> Refs { + let mut r = Refs::default(); + r.left[0] = f(0); + r.top[0] = f(0); + for i in 1..129 { + r.left[i] = f(-(i as i32)); + r.top[i] = f(i as i32); + } + r + } + + #[test] + fn dc_and_planar_flat() { + let r = refs(|_| 100); + let mut out = [0u16; 64]; + predict(&r, 1, 8, true, 8, &mut out, 8); + assert!(out.iter().all(|&v| v == 100)); + predict(&r, 0, 8, true, 8, &mut out, 8); + assert!(out.iter().all(|&v| v == 100)); + } + + #[test] + fn pure_vertical_and_horizontal() { + let r = refs(|i| (128 + i) as u16); + let mut out = [0u16; 16]; + predict(&r, 26, 4, false, 8, &mut out, 4); + for y in 0..4 { + for x in 0..4 { + assert_eq!(out[y * 4 + x], r.top[1 + x]); + } + } + predict(&r, 10, 4, false, 8, &mut out, 4); + for y in 0..4 { + for x in 0..4 { + assert_eq!(out[y * 4 + x], r.left[1 + y]); + } + } + // diagonal mode 34 reads top[x + y + 2] + predict(&r, 34, 4, false, 8, &mut out, 4); + assert_eq!(out[4 + 1], r.top[1 + 1 + 1 + 1]); + } + + #[test] + fn substitution() { + let mut r = Refs::default(); + let mut al = [false; 129]; + let at = [false; 129]; + r.left[3] = 77; + al[3] = true; + substitute(&mut r, &al, &at, 4, 8); + assert!(r.left[..=8].iter().all(|&v| v == 77)); + assert!(r.top[..=8].iter().all(|&v| v == 77)); + let mut r = Refs::default(); + substitute(&mut r, &[false; 129], &[false; 129], 4, 10); + assert_eq!(r.top[5], 512); + } +} diff --git a/crates/hevcenc/src/lib.rs b/crates/hevcenc/src/lib.rs new file mode 100644 index 00000000..3a5b9b47 --- /dev/null +++ b/crates/hevcenc/src/lib.rs @@ -0,0 +1,356 @@ +//! Clean-room pure-Rust HEVC (H.265) encoder, implemented from ITU-T Rec. H.265 (ISO/IEC 23008-2). +//! +//! Main and Main 10 profiles, 4:2:0, one slice per picture: IDR pictures and single-reference P +//! pictures (no reordering), 64x64 CTUs with rate-distortion coding-unit decisions (8x8 .. 64x64), +//! intra (35 modes, NxN at 8x8), merge / skip and AMVP inter prediction with quarter-sample motion, +//! DCT / DST transforms and CABAC. The deblocking filter is on; SAO is not used. +//! +//! ``` +//! use effectcraft_hevcenc::{Encoder, EncoderConfig, Frame}; +//! let mut enc = Encoder::new(EncoderConfig::new(64, 48, 30, 1)).unwrap(); +//! let (y, c) = (vec![128u16; 64 * 48], vec![128u16; 32 * 24]); +//! let packet = enc.encode(&Frame { y: &y, u: &c, v: &c, y_stride: 64, uv_stride: 32 }); +//! assert!(packet.keyframe && !packet.data.is_empty()); +//! let _hvcc = enc.hvcc(); +//! ``` + +// Index loops over fixed-size blocks read more clearly than iterator chains in codec code. +#![allow(clippy::needless_range_loop, clippy::too_many_arguments)] + +mod bitstream; +mod cabac; +mod deblock; +mod frame; +mod inter; +mod intra; +mod me; +mod params; +#[rustfmt::skip] +mod tables; +mod transform; + +use bitstream::{BitWriter, NAL_IDR_N_LP, NAL_TRAIL_R, nal_unit}; +use frame::{FrameCoder, SliceKind}; +use params::{LOG2_MAX_POC_LSB, LOG2_MIN_CB, MAX_MERGE_CAND, SeqParams}; + +/// Profile written in the parameter sets. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum Profile { + /// Main: 8-bit 4:2:0. + #[default] + Main, + /// Main 10: 10-bit 4:2:0. + Main10, +} + +impl Profile { + pub fn bit_depth(self) -> u8 { + match self { + Profile::Main => 8, + Profile::Main10 => 10, + } + } + fn idc(self) -> u8 { + match self { + Profile::Main => 1, + Profile::Main10 => 2, + } + } +} + +/// Rate control. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RateControl { + /// Constant QP (0–51; lower is better). + ConstantQp(u8), + /// Average bitrate target in kbit/s (frame-level QP adaptation). + Bitrate { kbps: u32 }, +} + +/// Encoder settings. +#[derive(Clone, Debug, PartialEq)] +pub struct EncoderConfig { + /// Picture size; must be even (4:2:0 cropping is in units of two luma samples). + pub width: u32, + pub height: u32, + /// Frame rate numerator / denominator (VUI timing and rate control). + pub fps_num: u32, + pub fps_den: u32, + pub profile: Profile, + /// `general_level_idc` (30 × level, e.g. 93 = level 3.1); `None` picks the lowest level that fits. + pub level_idc: Option, + pub rate: RateControl, + /// Frames between IDR pictures (1 = all intra, 0 = only the first picture). + pub keyint: u32, + /// BT.709 limited range is signalled in the VUI unless this is set. + pub full_range: bool, +} + +impl EncoderConfig { + pub fn new(width: u32, height: u32, fps_num: u32, fps_den: u32) -> Self { + EncoderConfig { + width, + height, + fps_num, + fps_den, + profile: Profile::Main, + level_idc: None, + rate: RateControl::ConstantQp(28), + keyint: 60, + full_range: false, + } + } +} + +/// One input picture, 4:2:0, samples at the profile's bit depth (8-bit values 0–255 for Main). +/// Luma is `width` x `height`, chroma `width / 2` x `height / 2`. +#[derive(Clone, Copy, Debug)] +pub struct Frame<'a> { + pub y: &'a [u16], + pub u: &'a [u16], + pub v: &'a [u16], + pub y_stride: usize, + pub uv_stride: usize, +} + +/// One coded picture (an access unit): NAL units, each prefixed by a 4-byte big-endian length +/// (ISO/IEC 14496-15 sample format; parameter sets are only in [`Encoder::hvcc`]). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Packet { + pub data: Vec, + /// IDR picture. + pub keyframe: bool, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + InvalidConfig(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidConfig(s) => write!(f, "invalid HEVC encoder config: {s}"), + } + } +} + +impl std::error::Error for Error {} + +/// Frame-level rate control for [`RateControl::Bitrate`]: a bits ≈ X / Qstep model per picture type +/// and a buffer that spreads over- / undershoot over the following pictures. +struct RateCtl { + target: f64, + /// sum(actual) - sum(target), bits. + buffer: f64, + /// Complexity X = bits * Qstep for I and P pictures. + x: [Option; 2], + last_qp: [i32; 2], + init_qp: i32, +} + +fn qstep(qp: i32) -> f64 { + 2f64.powf((qp as f64 - 4.0) / 6.0) +} + +impl RateCtl { + fn new(kbps: u32, cfg: &EncoderConfig) -> Self { + let fps = cfg.fps_num as f64 / cfg.fps_den.max(1) as f64; + let target = kbps as f64 * 1000.0 / fps.max(0.001); + let bpp = target / (cfg.width as f64 * cfg.height as f64); + let init_qp = (28.0 - 6.0 * (bpp / 0.08).log2()).round().clamp(10.0, 46.0) as i32; + RateCtl { target, buffer: 0.0, x: [None, None], last_qp: [init_qp - 3, init_qp], init_qp } + } + + fn qp(&self, idr: bool) -> i32 { + let t = idr as usize ^ 1; // 0 = I, 1 = P + let tp = (self.target - self.buffer / 12.0).clamp(0.3 * self.target, 2.0 * self.target); + let want = if idr { 4.0 * tp } else { tp }; + let x = match (self.x[t], self.x[0]) { + (Some(x), _) => x, + (None, Some(xi)) => xi / 5.0, + (None, None) => return if idr { self.init_qp - 3 } else { self.init_qp }, + }; + let qp = (4.0 + 6.0 * (x / want).log2()).round() as i32; + qp.clamp(self.last_qp[t] - 6, self.last_qp[t] + 6).clamp(1, 51) + } + + fn update(&mut self, idr: bool, qp: i32, bits: f64) { + let t = idr as usize ^ 1; + let x = bits * qstep(qp); + self.x[t] = Some(match self.x[t] { + Some(old) => 0.5 * old + 0.5 * x, + None => x, + }); + self.last_qp[t] = qp; + self.buffer += bits - self.target; + } +} + +/// HEVC encoder. Pictures come out in input order (no reordering: I and P only). +pub struct Encoder { + cfg: EncoderConfig, + seq: SeqParams, + sets: [Vec; 3], + frames: u64, + /// POC of the previous picture (relative to the last IDR). + poc: u32, + reference: Option<[Vec; 3]>, + recon: Option<[Vec; 3]>, + rc: Option, +} + +impl Encoder { + pub fn new(cfg: EncoderConfig) -> Result { + if cfg.width == 0 || cfg.height == 0 { + return Err(Error::InvalidConfig("empty frame".into())); + } + if !cfg.width.is_multiple_of(2) || !cfg.height.is_multiple_of(2) { + return Err(Error::InvalidConfig(format!("{}x{}: 4:2:0 HEVC needs even dimensions", cfg.width, cfg.height))); + } + if cfg.width > 16384 || cfg.height > 16384 { + return Err(Error::InvalidConfig("picture larger than 16384 samples".into())); + } + if cfg.fps_num == 0 || cfg.fps_den == 0 { + return Err(Error::InvalidConfig("zero frame rate".into())); + } + match cfg.rate { + RateControl::ConstantQp(q) if q > 51 => return Err(Error::InvalidConfig(format!("QP {q} > 51"))), + RateControl::Bitrate { kbps: 0 } => return Err(Error::InvalidConfig("zero bitrate".into())), + _ => {} + } + let min_cb = 1u32 << LOG2_MIN_CB; + let (coded_width, coded_height) = (cfg.width.div_ceil(min_cb) * min_cb, cfg.height.div_ceil(min_cb) * min_cb); + let kbps = match cfg.rate { + RateControl::Bitrate { kbps } => Some(kbps), + RateControl::ConstantQp(_) => None, + }; + let level_idc = cfg.level_idc.unwrap_or_else(|| params::choose_level(coded_width, coded_height, cfg.fps_num, cfg.fps_den, kbps)); + let seq = SeqParams { + coded_width, + coded_height, + width: cfg.width, + height: cfg.height, + bit_depth: cfg.profile.bit_depth() as u32, + profile_idc: cfg.profile.idc(), + level_idc, + full_range: cfg.full_range, + fps_num: cfg.fps_num, + fps_den: cfg.fps_den, + }; + let sets = [params::vps(&seq), params::sps(&seq), params::pps()]; + let rc = kbps.map(|k| RateCtl::new(k, &cfg)); + Ok(Encoder { cfg, seq, sets, frames: 0, poc: 0, reference: None, recon: None, rc }) + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + /// VPS, SPS, PPS NAL units (2-byte NAL header, emulation prevention, no start codes or length + /// prefixes). + pub fn parameter_sets(&self) -> [Vec; 3] { + self.sets.clone() + } + + /// HEVCDecoderConfigurationRecord (`hvcC` payload) with the parameter sets. + pub fn hvcc(&self) -> Vec { + params::hvcc(&self.seq, &self.sets) + } + + /// `general_level_idc` written in the parameter sets. + pub fn level_idc(&self) -> u8 { + self.seq.level_idc + } + + /// Reconstruction of the last encoded picture (what a decoder outputs), cropped planes + /// `[Y, Cb, Cr]` with strides `width` and `width / 2`. For tests. + #[doc(hidden)] + pub fn last_reconstruction(&self) -> Option<&[Vec; 3]> { + self.recon.as_ref() + } + + /// Copy the input into coded-size planes, replicating the right / bottom edges. + fn pad_input(&self, f: &Frame) -> [Vec; 3] { + let max = (1u32 << self.seq.bit_depth) - 1; + let mut out: [Vec; 3] = Default::default(); + for (c, plane) in out.iter_mut().enumerate() { + let (src, stride, w, h, cw, ch) = if c == 0 { + (f.y, f.y_stride, self.seq.width, self.seq.height, self.seq.coded_width, self.seq.coded_height) + } else { + let s = if c == 1 { f.u } else { f.v }; + (s, f.uv_stride, self.seq.width / 2, self.seq.height / 2, self.seq.coded_width / 2, self.seq.coded_height / 2) + }; + let (w, h, cw, ch) = (w as usize, h as usize, cw as usize, ch as usize); + assert!(stride >= w && src.len() >= (h - 1) * stride + w, "HEVC encoder: plane {c} smaller than {w}x{h} (stride {stride})"); + plane.reserve(cw * ch); + for y in 0..ch { + let row = &src[y.min(h - 1) * stride..][..w]; + plane.extend(row.iter().map(|&v| (v as u32).min(max) as u16)); + let last = *plane.last().unwrap(); + plane.extend(std::iter::repeat_n(last, cw - w)); + } + } + out + } + + /// Encodes one picture. + pub fn encode(&mut self, frame: &Frame) -> Packet { + let src = self.pad_input(frame); + let keyint = self.cfg.keyint as u64; + let idr = self.reference.is_none() || (keyint > 0 && self.frames.is_multiple_of(keyint)); + self.poc = if idr { 0 } else { self.poc + 1 }; + let qp = match (&self.rc, self.cfg.rate) { + (Some(rc), _) => rc.qp(idr), + (None, RateControl::ConstantQp(q)) => q as i32, + (None, RateControl::Bitrate { .. }) => 28, + }; + let (w, h) = (self.seq.coded_width as usize, self.seq.coded_height as usize); + let bd = self.seq.bit_depth; + let kind = if idr { SliceKind::I } else { SliceKind::P }; + let me = if idr { None } else { self.reference.as_ref().map(|r| me::MeRef::new(&r[0], w, h, bd)) }; + let refp = if idr { None } else { self.reference.as_ref() }; + let mut fc = FrameCoder::new(bd, w, h, kind, qp, &src, refp, me.as_ref()); + + // slice_segment_header() (7.3.6.1) + let mut bw = BitWriter::new(); + bw.put(1, 1); // first_slice_segment_in_pic_flag + if idr { + bw.put(0, 1); // no_output_of_prior_pics_flag + } + bw.ue(0); // slice_pic_parameter_set_id + bw.ue(if idr { 2 } else { 1 }); // slice_type I / P + if !idr { + bw.put(self.poc & ((1 << LOG2_MAX_POC_LSB) - 1), LOG2_MAX_POC_LSB); // slice_pic_order_cnt_lsb + bw.put(1, 1); // short_term_ref_pic_set_sps_flag (the only SPS set, no index coded) + bw.put(0, 1); // num_ref_idx_active_override_flag + bw.ue(5 - MAX_MERGE_CAND as u32); // five_minus_max_num_merge_cand + } + bw.se(qp - 26); // slice_qp_delta (init_qp = 26) + // byte_alignment() + bw.put(1, 1); + bw.align_zero(); + + let ctx = cabac::init_contexts(qp, if idr { 0 } else { 1 }); + let mut enc = cabac::CabacEncoder::new(bw, ctx); + fc.encode(&mut enc); + fc.deblock(); + let rbsp = enc.finish().into_bytes(); + let nal = nal_unit(if idr { NAL_IDR_N_LP } else { NAL_TRAIL_R }, &rbsp); + let mut data = Vec::with_capacity(nal.len() + 4); + data.extend_from_slice(&(nal.len() as u32).to_be_bytes()); + data.extend_from_slice(&nal); + + if let Some(rc) = &mut self.rc { + rc.update(idr, qp, data.len() as f64 * 8.0); + } + // output picture: cropped reconstruction + let rec = std::mem::take(&mut fc.rec); + let (ow, oh) = (self.seq.width as usize, self.seq.height as usize); + let crop = |p: &[u16], stride: usize, w: usize, h: usize| -> Vec { (0..h).flat_map(|y| p[y * stride..y * stride + w].iter().copied()).collect() }; + self.recon = Some([crop(&rec[0], w, ow, oh), crop(&rec[1], w / 2, ow / 2, oh / 2), crop(&rec[2], w / 2, ow / 2, oh / 2)]); + self.reference = Some(rec); + self.frames += 1; + Packet { data, keyframe: idr } + } +} diff --git a/crates/hevcenc/src/me.rs b/crates/hevcenc/src/me.rs new file mode 100644 index 00000000..5f4cb2ce --- /dev/null +++ b/crates/hevcenc/src/me.rs @@ -0,0 +1,144 @@ +//! Motion search reference: the previous reconstructed luma plane padded by edge replication, plus +//! half-sample planes interpolated with the 8-tap half-sample filter. Quarter-sample positions are +//! approximated by averaging neighbouring half-grid samples. Used for estimation only; the coded +//! prediction always uses the normative interpolation in `inter`. + +use crate::tables::LUMA_FILTER; + +const MARGIN: i32 = 80; + +pub struct MeRef { + /// [integer, horizontal half, vertical half, diagonal half] samples, each `stride` x `rows`. + planes: [Vec; 4], + stride: usize, + rows: usize, + w: i32, + h: i32, +} + +impl MeRef { + pub fn new(y: &[u16], w: usize, h: usize, bd: u32) -> MeRef { + let m = MARGIN as usize; + let (stride, rows) = (w + 2 * m, h + 2 * m); + let mut pad = vec![0u16; stride * rows]; + for r in 0..rows { + let sy = (r as i32 - MARGIN).clamp(0, h as i32 - 1) as usize; + let src = &y[sy * w..sy * w + w]; + let row = &mut pad[r * stride..r * stride + stride]; + row[..m].fill(src[0]); + row[m..m + w].copy_from_slice(src); + row[m + w..].fill(src[w - 1]); + } + let max = (1i32 << bd) - 1; + let f = &LUMA_FILTER[2]; + let filt_h = |p: &[u16]| -> Vec { + let mut o = vec![0u16; stride * rows]; + for r in 0..rows { + let row = &p[r * stride..r * stride + stride]; + for x in 0..stride { + let mut s = 0i32; + for k in 0..8 { + let xx = (x as i32 - 3 + k as i32).clamp(0, stride as i32 - 1) as usize; + s += f[k] as i32 * row[xx] as i32; + } + o[r * stride + x] = ((s + 32) >> 6).clamp(0, max) as u16; + } + } + o + }; + let filt_v = |p: &[u16]| -> Vec { + let mut o = vec![0u16; stride * rows]; + let mut acc = vec![0i32; stride]; + for r in 0..rows { + acc.fill(0); + for k in 0..8 { + let rr = (r as i32 - 3 + k as i32).clamp(0, rows as i32 - 1) as usize; + let src = &p[rr * stride..rr * stride + stride]; + let coef = f[k] as i32; + for (a, &s) in acc.iter_mut().zip(src) { + *a += coef * s as i32; + } + } + for (d, &a) in o[r * stride..r * stride + stride].iter_mut().zip(&acc) { + *d = ((a + 32) >> 6).clamp(0, max) as u16; + } + } + o + }; + let hp = filt_h(&pad); + let vp = filt_v(&pad); + let dp = filt_v(&hp); + MeRef { planes: [pad, hp, vp, dp], stride, rows, w: w as i32, h: h as i32 } + } + + /// Integer motion vector range keeping an n x n block (plus filter margin) inside the padding. + pub fn int_range(&self, x: i32, y: i32, n: usize) -> (i32, i32, i32, i32) { + let n = n as i32; + let lo_x = -MARGIN + 4 - x; + let hi_x = self.w + MARGIN - n - 4 - x; + let lo_y = -MARGIN + 4 - y; + let hi_y = self.h + MARGIN - n - 4 - y; + (lo_x.max(-256), hi_x.min(256), lo_y.max(-256), hi_y.min(256)) + } + + /// SAD of the source block against the integer-position reference block at (rx, ry). + pub fn sad(&self, src: &[u16], ss: usize, rx: i32, ry: i32, n: usize) -> u32 { + let p = &self.planes[0]; + let base = (ry + MARGIN) as usize * self.stride + (rx + MARGIN) as usize; + let mut s = 0u32; + for r in 0..n { + let a = &src[r * ss..r * ss + n]; + let b = &p[base + r * self.stride..base + r * self.stride + n]; + s += a.iter().zip(b).map(|(&x, &y)| (x as i32 - y as i32).unsigned_abs()).sum::(); + } + s + } + + /// Approximate luma prediction of an n x n block at (x, y) with quarter-sample vector `mv`. + pub fn approx_pred(&self, x: i32, y: i32, n: usize, mv: [i16; 2], out: &mut [u16]) { + // per axis: one or two (half-sample fraction, integer offset) taps + let axis = |v: i32| -> [(usize, i32); 2] { + if v & 1 == 0 { + let hv = v >> 1; + [((hv & 1) as usize, hv >> 1); 2] + } else { + let (a, b) = ((v - 1) >> 1, (v + 1) >> 1); + [((a & 1) as usize, a >> 1), ((b & 1) as usize, b >> 1)] + } + }; + let (mx, my) = (mv[0] as i32, mv[1] as i32); + let ax = axis(mx); + let ay = axis(my); + let two = mx & 1 != 0 || my & 1 != 0; + let taps = [(ax[0], ay[0]), (ax[1], ay[1])]; + let ntaps = if two { 2 } else { 1 }; + let mut first = true; + for &((fx, ox), (fy, oy)) in taps.iter().take(ntaps) { + let plane = &self.planes[fx + 2 * fy]; + let (bx, by) = (x + ox + MARGIN, y + oy + MARGIN); + let inside = bx >= 0 && by >= 0 && bx as usize + n <= self.stride && by as usize + n <= self.rows; + for r in 0..n { + let dst = &mut out[r * n..r * n + n]; + if inside { + let o = (by as usize + r) * self.stride + bx as usize; + let src = &plane[o..o + n]; + if first { + dst.copy_from_slice(src); + } else { + for (d, &s) in dst.iter_mut().zip(src) { + *d = ((*d as u32 + s as u32 + 1) >> 1) as u16; + } + } + } else { + let yy = (by + r as i32).clamp(0, self.rows as i32 - 1) as usize; + for (c, d) in dst.iter_mut().enumerate() { + let xx = (bx + c as i32).clamp(0, self.stride as i32 - 1) as usize; + let s = plane[yy * self.stride + xx]; + *d = if first { s } else { ((*d as u32 + s as u32 + 1) >> 1) as u16 }; + } + } + } + first = false; + } + } +} diff --git a/crates/hevcenc/src/params.rs b/crates/hevcenc/src/params.rs new file mode 100644 index 00000000..f1b89f73 --- /dev/null +++ b/crates/hevcenc/src/params.rs @@ -0,0 +1,262 @@ +//! Parameter sets (7.3.2.1 VPS, 7.3.2.2 SPS with E.2.1 VUI, 7.3.2.3 PPS), profile_tier_level (7.3.3), +//! level selection (Annex A, Table A.8) and the `hvcC` record (ISO/IEC 14496-15 §8.3.3.1). + +use crate::bitstream::{BitWriter, NAL_PPS, NAL_SPS, NAL_VPS, nal_unit}; + +/// Fixed coding structure of the encoder. +pub const LOG2_CTB: u32 = 6; +pub const LOG2_MIN_CB: u32 = 3; +pub const LOG2_MIN_TB: u32 = 2; +pub const LOG2_MAX_TB: u32 = 5; +pub const LOG2_MAX_POC_LSB: u32 = 8; +/// MaxNumMergeCand. +pub const MAX_MERGE_CAND: usize = 5; + +/// Sequence-level settings shared by the parameter-set writers. +#[derive(Clone, Debug)] +pub struct SeqParams { + /// Coded (padded) size, multiples of the minimum CB size. + pub coded_width: u32, + pub coded_height: u32, + /// Output (cropped) size. + pub width: u32, + pub height: u32, + pub bit_depth: u32, + pub profile_idc: u8, + pub level_idc: u8, + pub full_range: bool, + pub fps_num: u32, + pub fps_den: u32, +} + +/// (level_idc, MaxLumaPs, MaxLumaSr, MaxBR main tier in kbit/s) of Table A.8 (and A.9 for luma rates). +const LEVELS: [(u8, u64, u64, u64); 13] = [ + (30, 36_864, 552_960, 128), + (60, 122_880, 3_686_400, 1_500), + (63, 245_760, 7_372_800, 3_000), + (90, 552_960, 16_588_800, 6_000), + (93, 983_040, 33_177_600, 10_000), + (120, 2_228_224, 66_846_720, 12_000), + (123, 2_228_224, 133_693_440, 20_000), + (150, 8_912_896, 267_386_880, 25_000), + (153, 8_912_896, 534_773_760, 40_000), + (156, 8_912_896, 1_069_547_520, 60_000), + (180, 35_651_584, 1_069_547_520, 60_000), + (183, 35_651_584, 2_139_095_040, 120_000), + (186, 35_651_584, 4_278_190_080, 240_000), +]; + +/// Lowest level whose picture size, sample rate and (when known) bitrate limits fit. +pub fn choose_level(width: u32, height: u32, fps_num: u32, fps_den: u32, kbps: Option) -> u8 { + let ps = width as u64 * height as u64; + let sr = (ps as f64 * fps_num as f64 / fps_den.max(1) as f64).ceil() as u64; + for &(idc, max_ps, max_sr, max_br) in &LEVELS { + let max_dim = ((max_ps * 8) as f64).sqrt() as u64; + if ps <= max_ps && (width as u64) <= max_dim && (height as u64) <= max_dim && sr <= max_sr && kbps.is_none_or(|k| k as u64 <= max_br) { + return idc; + } + } + 186 +} + +fn profile_tier_level(w: &mut BitWriter, p: &SeqParams) { + w.put(0, 2); // general_profile_space + w.put(0, 1); // general_tier_flag (Main tier) + w.put(p.profile_idc as u32, 5); + w.put(compat_flags(p.profile_idc), 32); + w.put(1, 1); // general_progressive_source_flag + w.put(0, 1); // general_interlaced_source_flag + w.put(0, 1); // general_non_packed_constraint_flag + w.put(1, 1); // general_frame_only_constraint_flag + w.put(0, 32); // general_reserved_zero_43bits ... + w.put(0, 11); + w.put(0, 1); // general_inbld_flag / reserved + w.put(p.level_idc as u32, 8); + // sps_max_sub_layers_minus1 == 0: no sub-layer flags +} + +/// general_profile_compatibility_flag[j] as a 32-bit word (flag 0 is the MSB). A Main stream is also +/// Main 10 compatible. +fn compat_flags(profile_idc: u8) -> u32 { + let mut f = 1u32 << (31 - profile_idc as u32); + if profile_idc == 1 { + f |= 1 << (31 - 2); + } + f +} + +pub fn vps(p: &SeqParams) -> Vec { + let mut w = BitWriter::new(); + w.put(0, 4); // vps_video_parameter_set_id + w.put(1, 1); // vps_base_layer_internal_flag + w.put(1, 1); // vps_base_layer_available_flag + w.put(0, 6); // vps_max_layers_minus1 + w.put(0, 3); // vps_max_sub_layers_minus1 + w.put(1, 1); // vps_temporal_id_nesting_flag + w.put(0xffff, 16); + profile_tier_level(&mut w, p); + w.put(1, 1); // vps_sub_layer_ordering_info_present_flag + w.ue(1); // vps_max_dec_pic_buffering_minus1 + w.ue(0); // vps_max_num_reorder_pics + w.ue(0); // vps_max_latency_increase_plus1 + w.put(0, 6); // vps_max_layer_id + w.ue(0); // vps_num_layer_sets_minus1 + w.put(0, 1); // vps_timing_info_present_flag + w.put(0, 1); // vps_extension_flag + w.trailing(); + nal_unit(NAL_VPS, &w.into_bytes()) +} + +pub fn sps(p: &SeqParams) -> Vec { + let mut w = BitWriter::new(); + w.put(0, 4); // sps_video_parameter_set_id + w.put(0, 3); // sps_max_sub_layers_minus1 + w.put(1, 1); // sps_temporal_id_nesting_flag + profile_tier_level(&mut w, p); + w.ue(0); // sps_seq_parameter_set_id + w.ue(1); // chroma_format_idc 4:2:0 + w.ue(p.coded_width); + w.ue(p.coded_height); + let crop = p.coded_width != p.width || p.coded_height != p.height; + w.flag(crop); // conformance_window_flag + if crop { + // offsets in chroma sample units (SubWidthC = SubHeightC = 2) + w.ue(0); + w.ue((p.coded_width - p.width) / 2); + w.ue(0); + w.ue((p.coded_height - p.height) / 2); + } + w.ue(p.bit_depth - 8); // bit_depth_luma_minus8 + w.ue(p.bit_depth - 8); // bit_depth_chroma_minus8 + w.ue(LOG2_MAX_POC_LSB - 4); + w.put(1, 1); // sps_sub_layer_ordering_info_present_flag + w.ue(1); // sps_max_dec_pic_buffering_minus1 + w.ue(0); // sps_max_num_reorder_pics + w.ue(0); // sps_max_latency_increase_plus1 + w.ue(LOG2_MIN_CB - 3); + w.ue(LOG2_CTB - LOG2_MIN_CB); + w.ue(LOG2_MIN_TB - 2); + w.ue(LOG2_MAX_TB - LOG2_MIN_TB); + w.ue(0); // max_transform_hierarchy_depth_inter + w.ue(0); // max_transform_hierarchy_depth_intra + w.put(0, 1); // scaling_list_enabled_flag + w.put(0, 1); // amp_enabled_flag + w.put(0, 1); // sample_adaptive_offset_enabled_flag + w.put(0, 1); // pcm_enabled_flag + w.ue(1); // num_short_term_ref_pic_sets + // st_ref_pic_set(0): one negative picture at delta POC -1, used by the current picture + w.ue(1); // num_negative_pics + w.ue(0); // num_positive_pics + w.ue(0); // delta_poc_s0_minus1 + w.put(1, 1); // used_by_curr_pic_s0_flag + w.put(0, 1); // long_term_ref_pics_present_flag + w.put(0, 1); // sps_temporal_mvp_enabled_flag + w.put(1, 1); // strong_intra_smoothing_enabled_flag + w.put(1, 1); // vui_parameters_present_flag + vui(&mut w, p); + w.put(0, 1); // sps_extension_present_flag + w.trailing(); + nal_unit(NAL_SPS, &w.into_bytes()) +} + +fn vui(w: &mut BitWriter, p: &SeqParams) { + w.put(0, 1); // aspect_ratio_info_present_flag + w.put(0, 1); // overscan_info_present_flag + w.put(1, 1); // video_signal_type_present_flag + w.put(5, 3); // video_format: unspecified + w.flag(p.full_range); + w.put(1, 1); // colour_description_present_flag + w.put(1, 8); // colour_primaries BT.709 + w.put(1, 8); // transfer_characteristics BT.709 + w.put(1, 8); // matrix_coeffs BT.709 + w.put(0, 1); // chroma_loc_info_present_flag + w.put(0, 1); // neutral_chroma_indication_flag + w.put(0, 1); // field_seq_flag + w.put(0, 1); // frame_field_info_present_flag + w.put(0, 1); // default_display_window_flag + w.put(1, 1); // vui_timing_info_present_flag + w.put(p.fps_den, 32); // vui_num_units_in_tick + w.put(p.fps_num, 32); // vui_time_scale + w.put(0, 1); // vui_poc_proportional_to_timing_flag + w.put(0, 1); // vui_hrd_parameters_present_flag + w.put(0, 1); // bitstream_restriction_flag +} + +pub fn pps() -> Vec { + let mut w = BitWriter::new(); + w.ue(0); // pps_pic_parameter_set_id + w.ue(0); // pps_seq_parameter_set_id + w.put(0, 1); // dependent_slice_segments_enabled_flag + w.put(0, 1); // output_flag_present_flag + w.put(0, 3); // num_extra_slice_header_bits + w.put(1, 1); // sign_data_hiding_enabled_flag + w.put(0, 1); // cabac_init_present_flag + w.ue(0); // num_ref_idx_l0_default_active_minus1 + w.ue(0); // num_ref_idx_l1_default_active_minus1 + w.se(0); // init_qp_minus26 + w.put(0, 1); // constrained_intra_pred_flag + w.put(0, 1); // transform_skip_enabled_flag + w.put(0, 1); // cu_qp_delta_enabled_flag + w.se(0); // pps_cb_qp_offset + w.se(0); // pps_cr_qp_offset + w.put(0, 1); // pps_slice_chroma_qp_offsets_present_flag + w.put(0, 1); // weighted_pred_flag + w.put(0, 1); // weighted_bipred_flag + w.put(0, 1); // transquant_bypass_enabled_flag + w.put(0, 1); // tiles_enabled_flag + w.put(0, 1); // entropy_coding_sync_enabled_flag + w.put(0, 1); // pps_loop_filter_across_slices_enabled_flag + w.put(1, 1); // deblocking_filter_control_present_flag + w.put(0, 1); // deblocking_filter_override_enabled_flag + w.put(0, 1); // pps_deblocking_filter_disabled_flag + w.se(0); // pps_beta_offset_div2 + w.se(0); // pps_tc_offset_div2 + w.put(0, 1); // pps_scaling_list_data_present_flag + w.put(0, 1); // lists_modification_present_flag + w.ue(0); // log2_parallel_merge_level_minus2 + w.put(0, 1); // slice_segment_header_extension_present_flag + w.put(0, 1); // pps_extension_present_flag + w.trailing(); + nal_unit(NAL_PPS, &w.into_bytes()) +} + +/// HEVCDecoderConfigurationRecord with one VPS, SPS and PPS (lengthSizeMinusOne = 3). +pub fn hvcc(p: &SeqParams, sets: &[Vec; 3]) -> Vec { + let mut o = vec![1u8]; // configurationVersion + o.push(p.profile_idc); // profile_space 0, tier 0, profile_idc + o.extend_from_slice(&compat_flags(p.profile_idc).to_be_bytes()); + // general_constraint_indicator_flags (48 bits): progressive, interlaced, non_packed, frame_only + o.extend_from_slice(&[0x90, 0, 0, 0, 0, 0]); + o.push(p.level_idc); + o.extend_from_slice(&[0xf0, 0x00]); // reserved + min_spatial_segmentation_idc 0 + o.push(0xfc); // reserved + parallelismType 0 + o.push(0xfc | 1); // reserved + chroma_format_idc 1 + o.push(0xf8 | (p.bit_depth - 8) as u8); + o.push(0xf8 | (p.bit_depth - 8) as u8); + o.extend_from_slice(&[0, 0]); // avgFrameRate unspecified + // constantFrameRate 0, numTemporalLayers 1, temporalIdNested 1, lengthSizeMinusOne 3 + o.push((1 << 3) | (1 << 2) | 3); + o.push(3); // numOfArrays + for (set, ty) in sets.iter().zip([NAL_VPS, NAL_SPS, NAL_PPS]) { + o.push(0x80 | ty); // array_completeness 1 + o.extend_from_slice(&1u16.to_be_bytes()); + o.extend_from_slice(&(set.len() as u16).to_be_bytes()); + o.extend_from_slice(set); + } + o +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn levels() { + assert_eq!(choose_level(1920, 1080, 30, 1, None), 120); + assert_eq!(choose_level(1920, 1080, 60, 1, None), 123); + assert_eq!(choose_level(3840, 2160, 30, 1, None), 150); + assert_eq!(choose_level(1280, 720, 30, 1, None), 93); + assert_eq!(choose_level(64, 64, 30, 1, None), 30); + assert_eq!(choose_level(1920, 1080, 30, 1, Some(15_000)), 123); + } +} diff --git a/crates/hevcenc/src/tables.rs b/crates/hevcenc/src/tables.rs new file mode 100644 index 00000000..7c2fc447 --- /dev/null +++ b/crates/hevcenc/src/tables.rs @@ -0,0 +1,236 @@ +//! Constant tables of ITU-T H.265, mirrored from the first-party, spec-derived `filmcraft-hevc` +//! decoder (same organisation, MIT OR Apache-2.0): CABAC context layout and initValues (9.3.2.2), +//! rangeTabLPS / transIdx (Tables 9-52, 9-53), scans (6.5.3 - 6.5.5), transform matrices (8.6.4.2), +//! intra angles (Tables 8-5, 8-6), interpolation filters (8.5.3.3.3) and the chroma QP mapping (Table 8-10). + +#![allow(dead_code)] + +/// Table 9-52 (same values as H.264) rangeTabLPS[pStateIdx][qCodIRangeIdx]. +#[rustfmt::skip] +pub static RANGE_TAB_LPS: [[u8; 4]; 64] = [ + [128, 176, 208, 240], [128, 167, 197, 227], [128, 158, 187, 216], [123, 150, 178, 205], + [116, 142, 169, 195], [111, 135, 160, 185], [105, 128, 152, 175], [100, 122, 144, 166], + [95, 116, 137, 158], [90, 110, 130, 150], [85, 104, 123, 142], [81, 99, 117, 135], + [77, 94, 111, 128], [73, 89, 105, 122], [69, 85, 100, 116], [66, 80, 95, 110], + [62, 76, 90, 104], [59, 72, 86, 99], [56, 69, 81, 94], [53, 65, 77, 89], + [51, 62, 73, 85], [48, 59, 69, 80], [46, 56, 66, 76], [43, 53, 63, 72], + [41, 50, 59, 69], [39, 48, 56, 65], [37, 45, 54, 62], [35, 43, 51, 59], + [33, 41, 48, 56], [32, 39, 46, 53], [30, 37, 43, 50], [29, 35, 41, 48], + [27, 33, 39, 45], [26, 31, 37, 43], [24, 30, 35, 41], [23, 28, 33, 39], + [22, 27, 32, 37], [21, 26, 30, 35], [20, 24, 29, 33], [19, 23, 27, 31], + [18, 22, 26, 30], [17, 21, 25, 28], [16, 20, 23, 27], [15, 19, 22, 25], + [14, 18, 21, 24], [14, 17, 20, 23], [13, 16, 19, 22], [12, 15, 18, 21], + [12, 14, 17, 20], [11, 14, 16, 19], [11, 13, 15, 18], [10, 12, 15, 17], + [10, 12, 14, 16], [9, 11, 13, 15], [9, 11, 12, 14], [8, 10, 12, 14], + [8, 9, 11, 13], [7, 9, 11, 12], [7, 9, 10, 12], [7, 8, 10, 11], + [6, 8, 9, 11], [6, 7, 9, 10], [6, 7, 8, 9], [2, 2, 2, 2], +]; + +/// Table 9-53 transIdxLPS. +#[rustfmt::skip] +static TRANS_IDX_LPS: [u8; 64] = [ + 0, 0, 1, 2, 2, 4, 4, 5, 6, 7, 8, 9, 9, 11, 11, 12, + 13, 13, 15, 15, 16, 16, 18, 18, 19, 19, 21, 21, 22, 22, 23, 24, + 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 30, 30, 31, 32, 32, 33, + 33, 33, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 63, +]; + +/// Table 9-53 transIdxMPS. +#[rustfmt::skip] +static TRANS_IDX_MPS: [u8; 64] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 63, +]; + +/// Combined state transition: index = (pStateIdx << 1 | valMPS) * 2 + bin_is_lps. +/// Each context is stored as `pStateIdx << 1 | valMPS`. +pub static NEXT_STATE: [[u8; 2]; 128] = { + let mut t = [[0u8; 2]; 128]; + let mut s = 0; + while s < 128 { + let p = s >> 1; + let mps = s & 1; + // MPS path + t[s][0] = (TRANS_IDX_MPS[p] << 1) | mps as u8; + // LPS path + let new_mps = if p == 0 { 1 - mps } else { mps }; + t[s][1] = (TRANS_IDX_LPS[p] << 1) | new_mps as u8; + s += 1; + } + t +}; + +/// Up-right diagonal scan (6.5.3) of a `N x N` block as (x, y) pairs. +const fn diag(n: usize) -> [(u8, u8); L] { + let mut out = [(0u8, 0u8); L]; + let mut i = 0; + let mut x: i32 = 0; + let mut y: i32 = 0; + while i < L { + while y >= 0 { + if (x as usize) < n && (y as usize) < n { + out[i] = (x as u8, y as u8); + i += 1; + } + y -= 1; + x += 1; + } + y = x; + x = 0; + } + out +} + +/// Horizontal (6.5.4, `vertical == false`) or vertical (6.5.5) scan. +const fn hv(n: usize, vertical: bool) -> [(u8, u8); L] { + let mut out = [(0u8, 0u8); L]; + let mut i = 0; + while i < L { + let (a, b) = ((i % n) as u8, (i / n) as u8); + out[i] = if vertical { (b, a) } else { (a, b) }; + i += 1; + } + out +} + +pub static SCAN_1: [[(u8, u8); 1]; 3] = [[(0, 0)], [(0, 0)], [(0, 0)]]; +pub static SCAN_2: [[(u8, u8); 4]; 3] = [diag::<4>(2), hv::<4>(2, false), hv::<4>(2, true)]; +pub static SCAN_4: [[(u8, u8); 16]; 3] = [diag::<16>(4), hv::<16>(4, false), hv::<16>(4, true)]; +pub static SCAN_8: [[(u8, u8); 64]; 3] = [diag::<64>(8), hv::<64>(8, false), hv::<64>(8, true)]; + +/// ScanOrder[log2BlockSize][scanIdx] (log2BlockSize 0..=3). +#[inline] +pub fn scan_order(log2: u32, scan_idx: usize) -> &'static [(u8, u8)] { + match log2 { + 0 => &SCAN_1[scan_idx], + 1 => &SCAN_2[scan_idx], + 2 => &SCAN_4[scan_idx], + _ => &SCAN_8[scan_idx], + } +} + +/// Up-right diagonal scan for log2 size 2 or 3. +pub fn scan_diag(log2: u32) -> &'static [(u8, u8)] { + scan_order(log2, 0) +} + +/// Table 8-10: QpC as a function of qPi (ChromaArrayType 1). +#[inline] +pub fn qpc_420(qpi: i32) -> i32 { + if qpi < 30 { + qpi + } else if qpi > 43 { + qpi - 6 + } else { + QPC_30_43[(qpi - 30) as usize] as i32 + } +} + +/// Table 9-50 ctxIdxMap for 4x4 sig_coeff_flag contexts. +pub static CTX_IDX_MAP: [u8; 16] = [0, 1, 4, 5, 2, 3, 4, 5, 6, 6, 8, 8, 7, 7, 8, 8]; + +/// levelScale (8-309). +pub static LEVEL_SCALE: [i32; 6] = [40, 45, 51, 57, 64, 72]; + +/// DST-VII 4x4 matrix (8.6.4.2, trType 1). +pub static DST_MATRIX: [[i8; 4]; 4] = [[29, 55, 74, 84], [74, 74, 0, -74], [84, -29, -74, 55], [55, -84, 74, -29]]; + + +// CABAC context layout: offset of each syntax element in the context array (Table 9-4). +pub const SAO_MERGE: usize = 0; +pub const SAO_TYPE: usize = 1; +pub const SPLIT_CU: usize = 2; +pub const CU_TRANSQUANT_BYPASS: usize = 5; +pub const CU_SKIP: usize = 6; +pub const PRED_MODE: usize = 9; +pub const PART_MODE: usize = 10; +pub const PREV_INTRA_LUMA: usize = 14; +pub const INTRA_CHROMA: usize = 15; +pub const RQT_ROOT_CBF: usize = 16; +pub const MERGE_FLAG: usize = 17; +pub const MERGE_IDX: usize = 18; +pub const INTER_PRED_IDC: usize = 19; +pub const REF_IDX: usize = 24; +pub const MVP_FLAG: usize = 26; +pub const SPLIT_TRANSFORM: usize = 27; +pub const CBF_LUMA: usize = 30; +pub const CBF_CHROMA: usize = 32; +pub const ABS_MVD_GT0: usize = 37; +pub const ABS_MVD_GT1: usize = 38; +pub const CU_QP_DELTA_ABS: usize = 39; +pub const TRANSFORM_SKIP: usize = 41; +pub const LAST_X_PREFIX: usize = 43; +pub const LAST_Y_PREFIX: usize = 61; +pub const CODED_SUB_BLOCK: usize = 79; +pub const SIG_COEFF: usize = 83; +pub const GT1: usize = 127; +pub const GT2: usize = 151; +pub const EXPLICIT_RDPCM: usize = 157; +pub const EXPLICIT_RDPCM_DIR: usize = 159; +pub const CU_CHROMA_QP_OFFSET_FLAG: usize = 161; +pub const CU_CHROMA_QP_OFFSET_IDX: usize = 162; +pub const NUM_CTX: usize = 163; + +/// initValue per initType (9.3.2.2). +pub static CABAC_INIT: [[u8; NUM_CTX]; 3] = [ + [153, 200, 139, 141, 157, 154, 154, 154, 154, 154, 184, 154, 154, 154, 184, 63, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 153, 138, 138, 111, 141, 94, 138, 182, 154, 154, 154, 154, 154, 154, 139, 139, 110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, 108, 123, 63, 110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, 108, 123, 63, 91, 171, 134, 141, 111, 111, 125, 110, 110, 94, 124, 108, 124, 107, 125, 141, 179, 153, 125, 107, 125, 141, 179, 153, 125, 107, 125, 141, 179, 153, 125, 140, 139, 182, 182, 152, 136, 152, 136, 153, 136, 139, 111, 136, 139, 111, 141, 111, 140, 92, 137, 138, 140, 152, 138, 139, 153, 74, 149, 92, 139, 107, 122, 152, 140, 179, 166, 182, 140, 227, 122, 197, 138, 153, 136, 167, 152, 152, 154, 154, 154, 154, 154, 154], + [153, 185, 107, 139, 126, 154, 197, 185, 201, 149, 154, 139, 154, 154, 154, 152, 79, 110, 122, 95, 79, 63, 31, 31, 153, 153, 168, 124, 138, 94, 153, 111, 149, 107, 167, 154, 154, 140, 198, 154, 154, 139, 139, 125, 110, 94, 110, 95, 79, 125, 111, 110, 78, 110, 111, 111, 95, 94, 108, 123, 108, 125, 110, 94, 110, 95, 79, 125, 111, 110, 78, 110, 111, 111, 95, 94, 108, 123, 108, 121, 140, 61, 154, 155, 154, 139, 153, 139, 123, 123, 63, 153, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 123, 123, 107, 121, 107, 121, 167, 151, 183, 140, 151, 183, 140, 140, 140, 154, 196, 196, 167, 154, 152, 167, 182, 182, 134, 149, 136, 153, 121, 136, 137, 169, 194, 166, 167, 154, 167, 137, 182, 107, 167, 91, 122, 107, 167, 139, 139, 139, 139, 154, 154], + [153, 160, 107, 139, 126, 154, 197, 185, 201, 134, 154, 139, 154, 154, 183, 152, 79, 154, 137, 95, 79, 63, 31, 31, 153, 153, 168, 224, 167, 122, 153, 111, 149, 92, 167, 154, 154, 169, 198, 154, 154, 139, 139, 125, 110, 124, 110, 95, 94, 125, 111, 111, 79, 125, 126, 111, 111, 79, 108, 123, 93, 125, 110, 124, 110, 95, 94, 125, 111, 111, 79, 125, 126, 111, 111, 79, 108, 123, 93, 121, 140, 61, 154, 170, 154, 139, 153, 139, 123, 123, 63, 124, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 138, 138, 122, 121, 122, 121, 167, 151, 183, 140, 151, 183, 140, 140, 140, 154, 196, 167, 167, 154, 152, 167, 182, 182, 134, 149, 136, 153, 121, 136, 122, 169, 208, 166, 167, 154, 152, 167, 182, 107, 167, 91, 107, 107, 167, 139, 139, 139, 139, 154, 154], +]; + + +/// Table 8-5: intraPredAngle for predModeIntra 0..34 (0 for planar / DC). +pub static INTRA_PRED_ANGLE: [i8; 35] = [0, 0, 32, 26, 21, 17, 13, 9, 5, 2, 0, -2, -5, -9, -13, -17, -21, -26, -32, -26, -21, -17, -13, -9, -5, -2, 0, 2, 5, 9, 13, 17, 21, 26, 32]; +/// Table 8-6: invAngle for predModeIntra 11..25 (index = mode - 11). +pub static INV_ANGLE: [i16; 15] = [-4096, -1638, -910, -630, -482, -390, -315, -256, -315, -390, -482, -630, -910, -1638, -4096]; + +/// Table 8-10: QpC for qPi = 30..43 (qPi < 30: QpC = qPi; qPi > 43: QpC = qPi - 6). +pub static QPC_30_43: [u8; 14] = [29, 30, 31, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37]; + + +/// Luma 8-tap interpolation filter fL[xFrac][k] (8-236..8-238), taps at offsets -3..4. +pub static LUMA_FILTER: [[i8; 8]; 4] = [[0, 0, 0, 64, 0, 0, 0, 0], [-1, 4, -10, 58, 17, -5, 1, 0], [-1, 4, -11, 40, 40, -11, 4, -1], [0, 1, -5, 17, 58, -10, 4, -1]]; +/// Chroma 4-tap interpolation filter fC[xFrac][k] (8-255..8-261), taps at offsets -1..2. +pub static CHROMA_FILTER: [[i8; 4]; 8] = [[0, 64, 0, 0], [-2, 58, 10, -2], [-4, 54, 16, -2], [-6, 46, 28, -4], [-4, 36, 36, -4], [-4, 28, 46, -6], [-2, 16, 54, -4], [-2, 10, 58, -2]]; + +/// DCT matrix (8-319, 8-321): TRANS_MATRIX[n][m] = transMatrix[m][n] (row n = basis function). +pub static TRANS_MATRIX: [[i8; 32]; 32] = [ + [64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], + [90, 90, 88, 85, 82, 78, 73, 67, 61, 54, 46, 38, 31, 22, 13, 4, -4, -13, -22, -31, -38, -46, -54, -61, -67, -73, -78, -82, -85, -88, -90, -90], + [90, 87, 80, 70, 57, 43, 25, 9, -9, -25, -43, -57, -70, -80, -87, -90, -90, -87, -80, -70, -57, -43, -25, -9, 9, 25, 43, 57, 70, 80, 87, 90], + [90, 82, 67, 46, 22, -4, -31, -54, -73, -85, -90, -88, -78, -61, -38, -13, 13, 38, 61, 78, 88, 90, 85, 73, 54, 31, 4, -22, -46, -67, -82, -90], + [89, 75, 50, 18, -18, -50, -75, -89, -89, -75, -50, -18, 18, 50, 75, 89, 89, 75, 50, 18, -18, -50, -75, -89, -89, -75, -50, -18, 18, 50, 75, 89], + [88, 67, 31, -13, -54, -82, -90, -78, -46, -4, 38, 73, 90, 85, 61, 22, -22, -61, -85, -90, -73, -38, 4, 46, 78, 90, 82, 54, 13, -31, -67, -88], + [87, 57, 9, -43, -80, -90, -70, -25, 25, 70, 90, 80, 43, -9, -57, -87, -87, -57, -9, 43, 80, 90, 70, 25, -25, -70, -90, -80, -43, 9, 57, 87], + [85, 46, -13, -67, -90, -73, -22, 38, 82, 88, 54, -4, -61, -90, -78, -31, 31, 78, 90, 61, 4, -54, -88, -82, -38, 22, 73, 90, 67, 13, -46, -85], + [83, 36, -36, -83, -83, -36, 36, 83, 83, 36, -36, -83, -83, -36, 36, 83, 83, 36, -36, -83, -83, -36, 36, 83, 83, 36, -36, -83, -83, -36, 36, 83], + [82, 22, -54, -90, -61, 13, 78, 85, 31, -46, -90, -67, 4, 73, 88, 38, -38, -88, -73, -4, 67, 90, 46, -31, -85, -78, -13, 61, 90, 54, -22, -82], + [80, 9, -70, -87, -25, 57, 90, 43, -43, -90, -57, 25, 87, 70, -9, -80, -80, -9, 70, 87, 25, -57, -90, -43, 43, 90, 57, -25, -87, -70, 9, 80], + [78, -4, -82, -73, 13, 85, 67, -22, -88, -61, 31, 90, 54, -38, -90, -46, 46, 90, 38, -54, -90, -31, 61, 88, 22, -67, -85, -13, 73, 82, 4, -78], + [75, -18, -89, -50, 50, 89, 18, -75, -75, 18, 89, 50, -50, -89, -18, 75, 75, -18, -89, -50, 50, 89, 18, -75, -75, 18, 89, 50, -50, -89, -18, 75], + [73, -31, -90, -22, 78, 67, -38, -90, -13, 82, 61, -46, -88, -4, 85, 54, -54, -85, 4, 88, 46, -61, -82, 13, 90, 38, -67, -78, 22, 90, 31, -73], + [70, -43, -87, 9, 90, 25, -80, -57, 57, 80, -25, -90, -9, 87, 43, -70, -70, 43, 87, -9, -90, -25, 80, 57, -57, -80, 25, 90, 9, -87, -43, 70], + [67, -54, -78, 38, 85, -22, -90, 4, 90, 13, -88, -31, 82, 46, -73, -61, 61, 73, -46, -82, 31, 88, -13, -90, -4, 90, 22, -85, -38, 78, 54, -67], + [64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64], + [61, -73, -46, 82, 31, -88, -13, 90, -4, -90, 22, 85, -38, -78, 54, 67, -67, -54, 78, 38, -85, -22, 90, 4, -90, 13, 88, -31, -82, 46, 73, -61], + [57, -80, -25, 90, -9, -87, 43, 70, -70, -43, 87, 9, -90, 25, 80, -57, -57, 80, 25, -90, 9, 87, -43, -70, 70, 43, -87, -9, 90, -25, -80, 57], + [54, -85, -4, 88, -46, -61, 82, 13, -90, 38, 67, -78, -22, 90, -31, -73, 73, 31, -90, 22, 78, -67, -38, 90, -13, -82, 61, 46, -88, 4, 85, -54], + [50, -89, 18, 75, -75, -18, 89, -50, -50, 89, -18, -75, 75, 18, -89, 50, 50, -89, 18, 75, -75, -18, 89, -50, -50, 89, -18, -75, 75, 18, -89, 50], + [46, -90, 38, 54, -90, 31, 61, -88, 22, 67, -85, 13, 73, -82, 4, 78, -78, -4, 82, -73, -13, 85, -67, -22, 88, -61, -31, 90, -54, -38, 90, -46], + [43, -90, 57, 25, -87, 70, 9, -80, 80, -9, -70, 87, -25, -57, 90, -43, -43, 90, -57, -25, 87, -70, -9, 80, -80, 9, 70, -87, 25, 57, -90, 43], + [38, -88, 73, -4, -67, 90, -46, -31, 85, -78, 13, 61, -90, 54, 22, -82, 82, -22, -54, 90, -61, -13, 78, -85, 31, 46, -90, 67, 4, -73, 88, -38], + [36, -83, 83, -36, -36, 83, -83, 36, 36, -83, 83, -36, -36, 83, -83, 36, 36, -83, 83, -36, -36, 83, -83, 36, 36, -83, 83, -36, -36, 83, -83, 36], + [31, -78, 90, -61, 4, 54, -88, 82, -38, -22, 73, -90, 67, -13, -46, 85, -85, 46, 13, -67, 90, -73, 22, 38, -82, 88, -54, -4, 61, -90, 78, -31], + [25, -70, 90, -80, 43, 9, -57, 87, -87, 57, -9, -43, 80, -90, 70, -25, -25, 70, -90, 80, -43, -9, 57, -87, 87, -57, 9, 43, -80, 90, -70, 25], + [22, -61, 85, -90, 73, -38, -4, 46, -78, 90, -82, 54, -13, -31, 67, -88, 88, -67, 31, 13, -54, 82, -90, 78, -46, 4, 38, -73, 90, -85, 61, -22], + [18, -50, 75, -89, 89, -75, 50, -18, -18, 50, -75, 89, -89, 75, -50, 18, 18, -50, 75, -89, 89, -75, 50, -18, -18, 50, -75, 89, -89, 75, -50, 18], + [13, -38, 61, -78, 88, -90, 85, -73, 54, -31, 4, 22, -46, 67, -82, 90, -90, 82, -67, 46, -22, -4, 31, -54, 73, -85, 90, -88, 78, -61, 38, -13], + [9, -25, 43, -57, 70, -80, 87, -90, 90, -87, 80, -70, 57, -43, 25, -9, -9, 25, -43, 57, -70, 80, -87, 90, -90, 87, -80, 70, -57, 43, -25, 9], + [4, -13, 22, -31, 38, -46, 54, -61, 67, -73, 78, -82, 85, -88, 90, -90, 90, -90, 88, -85, 82, -78, 73, -67, 61, -54, 46, -38, 31, -22, 13, -4], +]; +/// Table 8-12: beta' for Q = 0..51. +pub static BETA_TABLE: [u8; 52] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64]; +/// Table 8-12: tC' for Q = 0..53. +pub static TC_TABLE: [u8; 54] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11, 13, 14, 16, 18, 20, 22, 24]; diff --git a/crates/hevcenc/src/transform.rs b/crates/hevcenc/src/transform.rs new file mode 100644 index 00000000..cffea75c --- /dev/null +++ b/crates/hevcenc/src/transform.rs @@ -0,0 +1,328 @@ +//! Forward transforms (encoder side, any integer approximation is allowed), quantisation, and the +//! normative scaling (8.6.3) and inverse transforms (8.6.4.2) the decoder applies. The inverse path +//! is exact integer arithmetic: the even/odd decomposition computes the same sums as the matrix +//! products of the specification. + +use crate::tables::{DST_MATRIX, LEVEL_SCALE, TRANS_MATRIX}; + +#[inline] +fn m(n: usize, k: usize, i: usize) -> i32 { + // k-th basis function of the n-point DCT, sample i + TRANS_MATRIX[k * (32 / n)][i] as i32 +} + +/// 1-D forward DCT of `x` (length n) into `out`. +fn fdct1d(x: &[i32], out: &mut [i32], n: usize) { + if n <= 4 { + for k in 0..n { + let mut s = 0; + for i in 0..n { + s += m(n, k, i) * x[i]; + } + out[k] = s; + } + return; + } + let h = n / 2; + let mut e = [0i32; 16]; + let mut o = [0i32; 16]; + for i in 0..h { + e[i] = x[i] + x[n - 1 - i]; + o[i] = x[i] - x[n - 1 - i]; + } + let mut ev = [0i32; 16]; + fdct1d(&e[..h], &mut ev[..h], h); + for k in 0..h { + out[2 * k] = ev[k]; + let kk = 2 * k + 1; + let mut s = 0; + for i in 0..h { + s += m(n, kk, i) * o[i]; + } + out[kk] = s; + } +} + +/// 1-D inverse DCT: x[i] = sum_k M[k][i] * c[k]. +fn idct1d(c: &[i32], out: &mut [i32], n: usize) { + if n <= 4 { + for i in 0..n { + let mut s = 0; + for k in 0..n { + s += m(n, k, i) * c[k]; + } + out[i] = s; + } + return; + } + let h = n / 2; + let mut ce = [0i32; 16]; + for k in 0..h { + ce[k] = c[2 * k]; + } + let mut e = [0i32; 16]; + idct1d(&ce[..h], &mut e[..h], h); + for i in 0..h { + let mut o = 0; + let mut k = 1; + while k < n { + let v = c[k]; + if v != 0 { + o += m(n, k, i) * v; + } + k += 2; + } + out[i] = e[i] + o; + out[n - 1 - i] = e[i] - o; + } +} + +fn fdst1d(x: &[i32], out: &mut [i32]) { + for k in 0..4 { + out[k] = (0..4).map(|i| DST_MATRIX[k][i] as i32 * x[i]).sum(); + } +} + +fn idst1d(c: &[i32], out: &mut [i32]) { + for i in 0..4 { + out[i] = (0..4).map(|k| DST_MATRIX[k][i] as i32 * c[k]).sum(); + } +} + +/// Forward 2-D transform of the n x n residual `r` (row-major) into `c`. +pub fn forward(r: &[i32], c: &mut [i32], n: usize, dst: bool, bit_depth: u32) { + let log2 = n.trailing_zeros(); + let shift1 = log2 + bit_depth - 9; + let shift2 = log2 + 6; + let (r1, r2) = (1i32 << (shift1 - 1), 1i32 << (shift2 - 1)); + let mut tmp = vec![0i32; n * n]; + let mut out = [0i32; 32]; + // rows (horizontal frequencies) + for y in 0..n { + let row = &r[y * n..y * n + n]; + if dst { + fdst1d(row, &mut out); + } else { + fdct1d(row, &mut out, n); + } + for k in 0..n { + tmp[k * n + y] = (out[k] + r1) >> shift1; // transposed: tmp[kx][y] + } + } + // columns + let mut col = [0i32; 32]; + for kx in 0..n { + col[..n].copy_from_slice(&tmp[kx * n..kx * n + n]); + if dst { + fdst1d(&col, &mut out); + } else { + fdct1d(&col, &mut out, n); + } + for ky in 0..n { + c[ky * n + kx] = (out[ky] + r2) >> shift2; + } + } +} + +/// Inverse 2-D transform (8.6.4.2) of scaled coefficients `d` in place, producing residuals. +pub fn inverse(d: &mut [i32], n: usize, dst: bool, bit_depth: u32) { + let bd_shift = 20 - bit_depth; + let mut tmp = vec![0i32; n * n]; + let mut col = [0i32; 32]; + let mut out = [0i32; 32]; + // max non-zero row, to skip empty work + let mut nz_cols = [false; 32]; + for y in 0..n { + for x in 0..n { + if d[y * n + x] != 0 { + nz_cols[x] = true; + } + } + } + // 1. columns + for x in 0..n { + if !nz_cols[x] { + continue; // tmp column stays zero + } + for j in 0..n { + col[j] = d[j * n + x]; + } + if dst { + idst1d(&col, &mut out); + } else { + idct1d(&col, &mut out, n); + } + for y in 0..n { + tmp[y * n + x] = ((out[y] + 64) >> 7).clamp(-32768, 32767); + } + } + // 2. rows + let round = 1 << (bd_shift - 1); + for y in 0..n { + let row = &tmp[y * n..y * n + n]; + if dst { + idst1d(row, &mut out); + } else { + idct1d(row, &mut out, n); + } + for x in 0..n { + d[y * n + x] = (out[x] + round) >> bd_shift; + } + } +} + +const QUANT_SCALE: [i64; 6] = [26214, 23302, 20560, 18396, 16384, 14564]; + +/// Quantise transform coefficients `c` into levels `l` with a dead-zone rounding offset +/// (`intra`: 1/3, otherwise 1/6). `qp` includes QpBdOffset. Returns the number of non-zero levels. +pub fn quantize(c: &[i32], l: &mut [i32], n: usize, qp: i32, intra: bool, bit_depth: u32) -> usize { + let log2 = n.trailing_zeros() as i32; + let tshift = 15 - bit_depth as i32 - log2; + let qbits = 14 + qp / 6 + tshift; + let scale = QUANT_SCALE[(qp % 6) as usize]; + let offset = (if intra { 171i64 } else { 85 }) << (qbits - 9); + let mut nz = 0; + for i in 0..n * n { + let v = c[i] as i64; + let a = ((v.abs() * scale + offset) >> qbits).min(32767); + if a != 0 { + nz += 1; + } + l[i] = if v < 0 { -(a as i32) } else { a as i32 }; + } + nz +} + +/// Sign data hiding (7.3.8.11 signHidden): in every 4x4 sub-block whose first and last non-zero +/// coefficients (in scan order) are more than 3 positions apart, the sign of the first one is not +/// coded but inferred from the parity of the sum of absolute levels. Fix the parity where needed by +/// the +-1 level change that adds the least quantisation error, without moving the first / last +/// non-zero positions. +pub fn sign_hiding(c: &[i32], l: &mut [i32], log2: u32, scan_idx: usize, qp: i32, bit_depth: u32) { + use crate::tables::{SCAN_4, scan_order}; + let n = 1usize << log2; + let tshift = 15 - bit_depth as i32 - log2 as i32; + let qbits = 14 + qp / 6 + tshift; + let scale = QUANT_SCALE[(qp % 6) as usize]; + let sb_scan = scan_order(log2 - 2, scan_idx); + let pos = &SCAN_4[scan_idx]; + for &(xs, ys) in sb_scan { + let idx = |p: usize| (ys as usize * 4 + pos[p].1 as usize) * n + xs as usize * 4 + pos[p].0 as usize; + let (Some(first), Some(last)) = ((0..16).find(|&p| l[idx(p)] != 0), (0..16).rev().find(|&p| l[idx(p)] != 0)) else { continue }; + if last - first <= 3 { + continue; + } + let sum: i32 = (0..16).map(|p| l[idx(p)].abs()).sum(); + let negative = l[idx(first)] < 0; + if negative == (sum & 1 == 1) { + continue; + } + // cost of +-1 changes in 1/256 units of quantisation error + let mut best: Option<(i64, usize, i32)> = None; + for p in first..=last { + let i = idx(p); + let q = ((c[i] as i64).abs() * scale) >> (qbits - 8); + let a = l[i].abs() as i64; + let err = |lv: i64| (q - (lv << 8)).abs(); + let mut consider = |cost: i64, d: i32| { + if best.is_none_or(|b| cost < b.0) { + best = Some((cost, i, d)); + } + }; + if a < 32767 && (a > 0 || (p > first && p < last)) { + consider(err(a + 1) - err(a), 1); + } + if a > 1 || (a == 1 && p > first && p < last) { + consider(err(a - 1) - err(a), -1); + } + } + if let Some((_, i, d)) = best { + let a = l[i].abs() + d; + l[i] = if c[i] < 0 { -a } else { a }; + } + } +} + +/// Scaling process (8.6.2 / 8.6.3, flat scaling matrix): levels to scaled coefficients. +pub fn dequantize(l: &[i32], d: &mut [i32], n: usize, qp: i32, bit_depth: u32) { + let log2 = n.trailing_zeros(); + let bd_shift = bit_depth + log2 - 5; + let round = 1i64 << (bd_shift - 1); + let scale = (LEVEL_SCALE[(qp % 6) as usize] as i64) << (qp / 6); + for i in 0..n * n { + let v = l[i]; + d[i] = if v == 0 { 0 } else { ((v as i64 * 16 * scale + round) >> bd_shift).clamp(-32768, 32767) as i32 }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Direct matrix form of the inverse transform, as written in the specification. + fn inverse_ref(c: &mut [i32], n: usize, dst: bool, bd: u32) { + let mut tmp = vec![0i32; n * n]; + let step = 32 / n; + for x in 0..n { + for y in 0..n { + let mut s = 0i32; + for j in 0..n { + let m = if dst { DST_MATRIX[j][y] as i32 } else { TRANS_MATRIX[j * step][y] as i32 }; + s += m * c[j * n + x]; + } + tmp[y * n + x] = ((s + 64) >> 7).clamp(-32768, 32767); + } + } + let bd_shift = 20 - bd; + for y in 0..n { + for x in 0..n { + let mut s = 0i32; + for j in 0..n { + let m = if dst { DST_MATRIX[j][x] as i32 } else { TRANS_MATRIX[j * step][x] as i32 }; + s += m * tmp[y * n + j]; + } + c[y * n + x] = (s + (1 << (bd_shift - 1))) >> bd_shift; + } + } + } + + #[test] + fn inverse_matches_matrix_form() { + let mut seed = 7u32; + for &(n, dst) in &[(4usize, true), (4, false), (8, false), (16, false), (32, false)] { + for _ in 0..20 { + let mut c = vec![0i32; n * n]; + for v in c.iter_mut() { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + if (seed >> 24).is_multiple_of(3) { + *v = ((seed >> 8) % 2001) as i32 - 1000; + } + } + let mut a = c.clone(); + let mut b = c.clone(); + inverse(&mut a, n, dst, 8); + inverse_ref(&mut b, n, dst, 8); + assert_eq!(a, b, "n {n} dst {dst}"); + } + } + } + + #[test] + fn round_trip_is_close() { + for &(n, dst) in &[(4usize, true), (8, false), (32, false)] { + for bd in [8u32, 10] { + let r: Vec = (0..n * n).map(|i| ((i * 37 + i / n * 11) % 61) as i32 - 30).collect(); + let mut c = vec![0; n * n]; + forward(&r, &mut c, n, dst, bd); + let mut l = vec![0; n * n]; + let qp = 4; + quantize(&c, &mut l, n, qp, true, bd); + let mut d = vec![0; n * n]; + dequantize(&l, &mut d, n, qp, bd); + inverse(&mut d, n, dst, bd); + let err: i32 = r.iter().zip(&d).map(|(a, b)| (a - b).abs()).max().unwrap(); + assert!(err <= 2, "n {n} bd {bd} err {err}"); + } + } + } +} diff --git a/crates/hevcenc/target/agent-codec-hevc/.rustc_info.json b/crates/hevcenc/target/agent-codec-hevc/.rustc_info.json new file mode 100644 index 00000000..5d9c147e --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":9559367331539902066,"outputs":{"4233666815994275565":{"success":true,"status":"","code":0,"stdout":"rustc 1.96.0 (ac68faa20 2026-05-25)\nbinary: rustc\ncommit-hash: ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96\ncommit-date: 2026-05-25\nhost: aarch64-apple-darwin\nrelease: 1.96.0\nLLVM version: 22.1.2\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/bt/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/CACHEDIR.TAG b/crates/hevcenc/target/agent-codec-hevc/CACHEDIR.TAG new file mode 100644 index 00000000..20d7c319 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/CACHEDIR.TAG @@ -0,0 +1,3 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by cargo. +# For information about cache directory tags see https://bford.info/cachedir/ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.cargo-artifact-lock b/crates/hevcenc/target/agent-codec-hevc/debug/.cargo-artifact-lock new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.cargo-build-lock b/crates/hevcenc/target/agent-codec-hevc/debug/.cargo-build-lock new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.cargo-lock b/crates/hevcenc/target/agent-codec-hevc/debug/.cargo-lock new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/build-script-build-script-build new file mode 100644 index 00000000..427290bf --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/build-script-build-script-build @@ -0,0 +1 @@ +096ac002c23a6667 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/build-script-build-script-build.json new file mode 100644 index 00000000..a72a5bb4 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":5408242616063297496,"profile":3185167420583542146,"path":6080947815111469725,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/dep-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/dep-build-script-build-script-build new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/dep-build-script-build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-5b3c4336788f7174/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/dep-lib-crossbeam_deque b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/dep-lib-crossbeam_deque new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/dep-lib-crossbeam_deque differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/lib-crossbeam_deque b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/lib-crossbeam_deque new file mode 100644 index 00000000..9162efb3 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/lib-crossbeam_deque @@ -0,0 +1 @@ +a673fdef9376ddb1 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/lib-crossbeam_deque.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/lib-crossbeam_deque.json new file mode 100644 index 00000000..0e08d299 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-d9775930f3a59590/lib-crossbeam_deque.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":15353977948366730291,"profile":3185167420583542146,"path":2714696676876033032,"deps":[[2543204310390312751,"crossbeam_epoch",false,15863831904650651516],[11050506297539643678,"crossbeam_utils",false,11092378776446818952],[15481973119957668846,"build_script_build",false,7352081084529830974]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-deque-d9775930f3a59590/dep-lib-crossbeam_deque","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-f51f1776f2c514b1/run-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-f51f1776f2c514b1/run-build-script-build-script-build new file mode 100644 index 00000000..dc01c242 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-f51f1776f2c514b1/run-build-script-build-script-build @@ -0,0 +1 @@ +3eec441bcbd60766 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-f51f1776f2c514b1/run-build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-f51f1776f2c514b1/run-build-script-build-script-build.json new file mode 100644 index 00000000..b86b2c74 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-deque-f51f1776f2c514b1/run-build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[15481973119957668846,"build_script_build",false,7450707238475295241]],"local":[{"RerunIfChanged":{"output":"debug/build/crossbeam-deque-f51f1776f2c514b1/output","paths":["build.rs"]}}],"rustflags":[],"config":0,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/build-script-build-script-build new file mode 100644 index 00000000..89173559 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/build-script-build-script-build @@ -0,0 +1 @@ +16d4cd1f76c06772 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/build-script-build-script-build.json new file mode 100644 index 00000000..aed09422 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"alloc\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"loom\", \"loom-crate\", \"nightly\", \"std\"]","target":5408242616063297496,"profile":3185167420583542146,"path":5028293854929153649,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/dep-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/dep-build-script-build-script-build new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/dep-build-script-build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-1347d2207ea061ce/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-2092bf031b9d9a01/run-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-2092bf031b9d9a01/run-build-script-build-script-build new file mode 100644 index 00000000..3a26abad --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-2092bf031b9d9a01/run-build-script-build-script-build @@ -0,0 +1 @@ +c36dd00ce8039b4d \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-2092bf031b9d9a01/run-build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-2092bf031b9d9a01/run-build-script-build-script-build.json new file mode 100644 index 00000000..714a1d1e --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-2092bf031b9d9a01/run-build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[2543204310390312751,"build_script_build",false,8243769256497239062]],"local":[{"RerunIfChanged":{"output":"debug/build/crossbeam-epoch-2092bf031b9d9a01/output","paths":["build.rs"]}}],"rustflags":[],"config":0,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/dep-lib-crossbeam_epoch b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/dep-lib-crossbeam_epoch new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/dep-lib-crossbeam_epoch differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/lib-crossbeam_epoch b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/lib-crossbeam_epoch new file mode 100644 index 00000000..7cf5e27b --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/lib-crossbeam_epoch @@ -0,0 +1 @@ +7ce7352d11a727dc \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/lib-crossbeam_epoch.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/lib-crossbeam_epoch.json new file mode 100644 index 00000000..c133fb7a --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/lib-crossbeam_epoch.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"alloc\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"loom\", \"loom-crate\", \"nightly\", \"std\"]","target":16242420667881341737,"profile":3185167420583542146,"path":7266900391393874633,"deps":[[2543204310390312751,"build_script_build",false,5592067657492884931],[11050506297539643678,"crossbeam_utils",false,11092378776446818952]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-epoch-72c18a120600cf42/dep-lib-crossbeam_epoch","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/build-script-build-script-build new file mode 100644 index 00000000..1790b224 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/build-script-build-script-build @@ -0,0 +1 @@ +bdec41fc81c92fa2 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/build-script-build-script-build.json new file mode 100644 index 00000000..69147ada --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"loom\", \"nightly\", \"std\"]","target":5408242616063297496,"profile":3185167420583542146,"path":14844284517995534797,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/dep-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/dep-build-script-build-script-build new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/dep-build-script-build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-5b2ff6828cdce4c3/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/dep-lib-crossbeam_utils b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/dep-lib-crossbeam_utils new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/dep-lib-crossbeam_utils differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/lib-crossbeam_utils b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/lib-crossbeam_utils new file mode 100644 index 00000000..e425ee19 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/lib-crossbeam_utils @@ -0,0 +1 @@ +88da312cba0bf099 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/lib-crossbeam_utils.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/lib-crossbeam_utils.json new file mode 100644 index 00000000..bc49c405 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-799e464d44773a7d/lib-crossbeam_utils.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"loom\", \"nightly\", \"std\"]","target":9626079250877207070,"profile":3185167420583542146,"path":16937405451120399764,"deps":[[11050506297539643678,"build_script_build",false,8108134710804475658]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crossbeam-utils-799e464d44773a7d/dep-lib-crossbeam_utils","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-99f1d686cf22430f/run-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-99f1d686cf22430f/run-build-script-build-script-build new file mode 100644 index 00000000..c31310a1 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-99f1d686cf22430f/run-build-script-build-script-build @@ -0,0 +1 @@ +0aef838c8fe18570 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-99f1d686cf22430f/run-build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-99f1d686cf22430f/run-build-script-build-script-build.json new file mode 100644 index 00000000..2b02b78b --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/crossbeam-utils-99f1d686cf22430f/run-build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[11050506297539643678,"build_script_build",false,11686781118169869501]],"local":[{"RerunIfChanged":{"output":"debug/build/crossbeam-utils-99f1d686cf22430f/output","paths":["no_atomic.rs"]}}],"rustflags":[],"config":0,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/dep-test-integration-test-roundtrip b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/dep-test-integration-test-roundtrip new file mode 100644 index 00000000..73f42b52 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/dep-test-integration-test-roundtrip differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/test-integration-test-roundtrip b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/test-integration-test-roundtrip new file mode 100644 index 00000000..5f22e76e --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/test-integration-test-roundtrip @@ -0,0 +1 @@ +e64536e1e71b0acd \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/test-integration-test-roundtrip.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/test-integration-test-roundtrip.json new file mode 100644 index 00000000..e2674a32 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/test-integration-test-roundtrip.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[]","target":13741281541045977916,"profile":14861435456621666076,"path":1524816848624354599,"deps":[[5706705931987278984,"filmcraft_hevc",false,1065779467293311985],[10103226027885832861,"effectcraft_hevcenc",false,3919018516886026431]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/effectcraft-hevcenc-335f1f822197d831/dep-test-integration-test-roundtrip","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/dep-lib-effectcraft_hevcenc b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/dep-lib-effectcraft_hevcenc new file mode 100644 index 00000000..13bf633d Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/dep-lib-effectcraft_hevcenc differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/lib-effectcraft_hevcenc b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/lib-effectcraft_hevcenc new file mode 100644 index 00000000..14901e19 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/lib-effectcraft_hevcenc @@ -0,0 +1 @@ +bf782bc793266336 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/lib-effectcraft_hevcenc.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/lib-effectcraft_hevcenc.json new file mode 100644 index 00000000..461b185a --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/lib-effectcraft_hevcenc.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[]","target":11125424694902762418,"profile":15209061013077869454,"path":6858098780819335099,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/effectcraft-hevcenc-955aee250b2c2a4e/dep-lib-effectcraft_hevcenc","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/dep-lib-either b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/dep-lib-either new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/dep-lib-either differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/lib-either b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/lib-either new file mode 100644 index 00000000..464bb799 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/lib-either @@ -0,0 +1 @@ +a449c4ea4d7d8d17 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/lib-either.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/lib-either.json new file mode 100644 index 00000000..51e6f5f4 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/either-72952a75ef45a16d/lib-either.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[\"default\", \"serde\", \"std\", \"use_std\"]","target":17124342308084364240,"profile":2785734998891497023,"path":5546998507963859579,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/either-72952a75ef45a16d/dep-lib-either","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/dep-lib-filmcraft_bitstream b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/dep-lib-filmcraft_bitstream new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/dep-lib-filmcraft_bitstream differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/lib-filmcraft_bitstream b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/lib-filmcraft_bitstream new file mode 100644 index 00000000..51b08d80 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/lib-filmcraft_bitstream @@ -0,0 +1 @@ +059b6b4655b5b2c5 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/lib-filmcraft_bitstream.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/lib-filmcraft_bitstream.json new file mode 100644 index 00000000..8f431364 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/lib-filmcraft_bitstream.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[]","target":18127681992696119206,"profile":12938959104297781942,"path":12854803809928081657,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/filmcraft-bitstream-2ad770771b023a77/dep-lib-filmcraft_bitstream","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/dep-lib-filmcraft_hevc b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/dep-lib-filmcraft_hevc new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/dep-lib-filmcraft_hevc differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/lib-filmcraft_hevc b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/lib-filmcraft_hevc new file mode 100644 index 00000000..05871b3f --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/lib-filmcraft_hevc @@ -0,0 +1 @@ +f143ec5cc768ca0e \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/lib-filmcraft_hevc.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/lib-filmcraft_hevc.json new file mode 100644 index 00000000..75da585b --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/lib-filmcraft_hevc.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"threads\"]","declared_features":"[\"default\", \"threads\"]","target":9847609124140232008,"profile":15209061013077869454,"path":2590569095805419776,"deps":[[1957009224993739128,"thiserror",false,17538703420373504327],[4943083844709074312,"filmcraft_bitstream",false,14245647949184604933],[11910974697091955563,"rayon",false,13044872296819726823]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/filmcraft-hevc-71cf1ac5fd5debba/dep-lib-filmcraft_hevc","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/dep-lib-proc_macro2 b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/dep-lib-proc_macro2 new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/dep-lib-proc_macro2 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/lib-proc_macro2 b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/lib-proc_macro2 new file mode 100644 index 00000000..7f0dfbf3 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/lib-proc_macro2 @@ -0,0 +1 @@ +3d6cecd1d5ecd76f \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/lib-proc_macro2.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/lib-proc_macro2.json new file mode 100644 index 00000000..269e2805 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-67c058b976f66fb4/lib-proc_macro2.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"proc-macro\"]","declared_features":"[\"default\", \"nightly\", \"proc-macro\", \"span-locations\"]","target":369203346396300798,"profile":2785734998891497023,"path":2809009335364014821,"deps":[[12333832803962989937,"unicode_ident",false,16793811267078206154],[16346726298725429545,"build_script_build",false,12224304350208236698]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/proc-macro2-67c058b976f66fb4/dep-lib-proc_macro2","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-73f2303f90bf5dd8/run-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-73f2303f90bf5dd8/run-build-script-build-script-build new file mode 100644 index 00000000..6a76c040 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-73f2303f90bf5dd8/run-build-script-build-script-build @@ -0,0 +1 @@ +9a9c6c140a74a5a9 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-73f2303f90bf5dd8/run-build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-73f2303f90bf5dd8/run-build-script-build-script-build.json new file mode 100644 index 00000000..01850cf9 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-73f2303f90bf5dd8/run-build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[16346726298725429545,"build_script_build",false,11266185680237871916]],"local":[{"RerunIfChanged":{"output":"debug/build/proc-macro2-73f2303f90bf5dd8/output","paths":["src/probe/proc_macro_span.rs","src/probe/proc_macro_span_location.rs","src/probe/proc_macro_span_file.rs"]}},{"RerunIfEnvChanged":{"var":"RUSTC_BOOTSTRAP","val":null}}],"rustflags":[],"config":0,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/build-script-build-script-build new file mode 100644 index 00000000..20c7065d --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/build-script-build-script-build @@ -0,0 +1 @@ +2cc32b742f88599c \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/build-script-build-script-build.json new file mode 100644 index 00000000..0a8e349c --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"proc-macro\"]","declared_features":"[\"default\", \"nightly\", \"proc-macro\", \"span-locations\"]","target":5408242616063297496,"profile":2785734998891497023,"path":15930204402656880288,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/proc-macro2-8b5400504772b527/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/dep-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/dep-build-script-build-script-build new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/dep-build-script-build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/proc-macro2-8b5400504772b527/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/dep-lib-quote b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/dep-lib-quote new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/dep-lib-quote differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/lib-quote b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/lib-quote new file mode 100644 index 00000000..b4b6ba8b --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/lib-quote @@ -0,0 +1 @@ +618b025593b80093 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/lib-quote.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/lib-quote.json new file mode 100644 index 00000000..631fa766 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-0f85966d6f134e44/lib-quote.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"proc-macro\"]","declared_features":"[\"default\", \"proc-macro\"]","target":8313845041260779044,"profile":2785734998891497023,"path":8077693039016294807,"deps":[[8949245912927223590,"build_script_build",false,2599661241357163625],[16346726298725429545,"proc_macro2",false,8059170461296913469]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/quote-0f85966d6f134e44/dep-lib-quote","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/build-script-build-script-build new file mode 100644 index 00000000..01fcc6df --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/build-script-build-script-build @@ -0,0 +1 @@ +14dc0fcc1148625e \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/build-script-build-script-build.json new file mode 100644 index 00000000..4e1f4926 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"proc-macro\"]","declared_features":"[\"default\", \"proc-macro\"]","target":5408242616063297496,"profile":2785734998891497023,"path":2672923304195493942,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/quote-4b484c95cbb303f6/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/dep-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/dep-build-script-build-script-build new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/dep-build-script-build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-4b484c95cbb303f6/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-d3fa970117425bd7/run-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-d3fa970117425bd7/run-build-script-build-script-build new file mode 100644 index 00000000..33392f0e --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-d3fa970117425bd7/run-build-script-build-script-build @@ -0,0 +1 @@ +69dc711b20da1324 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-d3fa970117425bd7/run-build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-d3fa970117425bd7/run-build-script-build-script-build.json new file mode 100644 index 00000000..2c5faf03 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/quote-d3fa970117425bd7/run-build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[8949245912927223590,"build_script_build",false,6801077628558105620]],"local":[{"RerunIfChanged":{"output":"debug/build/quote-d3fa970117425bd7/output","paths":["build.rs"]}}],"rustflags":[],"config":0,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/dep-lib-rayon b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/dep-lib-rayon new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/dep-lib-rayon differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/lib-rayon b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/lib-rayon new file mode 100644 index 00000000..5108ffda --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/lib-rayon @@ -0,0 +1 @@ +e71946403db208b5 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/lib-rayon.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/lib-rayon.json new file mode 100644 index 00000000..dd274fe6 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-6e2dc17155d9fd34/lib-rayon.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[\"web_spin_lock\"]","target":4732152328429177609,"profile":2785734998891497023,"path":17431103954642750794,"deps":[[3746573929696391749,"rayon_core",false,11362863269611761524],[13370710369771896710,"either",false,1697150408193231268]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rayon-6e2dc17155d9fd34/dep-lib-rayon","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-1b6fdb81fee9810e/run-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-1b6fdb81fee9810e/run-build-script-build-script-build new file mode 100644 index 00000000..60eca530 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-1b6fdb81fee9810e/run-build-script-build-script-build @@ -0,0 +1 @@ +0e9aa1a7bb8410a4 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-1b6fdb81fee9810e/run-build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-1b6fdb81fee9810e/run-build-script-build-script-build.json new file mode 100644 index 00000000..ae4bfa9a --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-1b6fdb81fee9810e/run-build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[3746573929696391749,"build_script_build",false,5512398614385402082]],"local":[{"RerunIfChanged":{"output":"debug/build/rayon-core-1b6fdb81fee9810e/output","paths":["build.rs"]}}],"rustflags":[],"config":0,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/build-script-build-script-build new file mode 100644 index 00000000..4958ae8b --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/build-script-build-script-build @@ -0,0 +1 @@ +e220cc7655f97f4c \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/build-script-build-script-build.json new file mode 100644 index 00000000..c2e1f0f8 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[\"web_spin_lock\"]","target":5408242616063297496,"profile":2785734998891497023,"path":10372009197711997420,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rayon-core-b409a671b98b8570/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/dep-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/dep-build-script-build-script-build new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/dep-build-script-build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-b409a671b98b8570/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/dep-lib-rayon_core b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/dep-lib-rayon_core new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/dep-lib-rayon_core differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/lib-rayon_core b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/lib-rayon_core new file mode 100644 index 00000000..c20ab603 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/lib-rayon_core @@ -0,0 +1 @@ +74ef8ad0f0ffb09d \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/lib-rayon_core.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/lib-rayon_core.json new file mode 100644 index 00000000..33f3e03a --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/lib-rayon_core.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[\"web_spin_lock\"]","target":12465439074827573786,"profile":2785734998891497023,"path":7184092575742886946,"deps":[[3746573929696391749,"build_script_build",false,11822094963353688590],[11050506297539643678,"crossbeam_utils",false,11092378776446818952],[15481973119957668846,"crossbeam_deque",false,12816530492324934566]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rayon-core-d9d479d6cc7ea71c/dep-lib-rayon_core","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/dep-lib-syn b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/dep-lib-syn new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/dep-lib-syn differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/lib-syn b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/lib-syn new file mode 100644 index 00000000..fe54c7c4 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/lib-syn @@ -0,0 +1 @@ +dfcabe0f565d5911 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/lib-syn.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/lib-syn.json new file mode 100644 index 00000000..6c15c911 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/syn-86f5aa6c6ace4240/lib-syn.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"clone-impls\", \"default\", \"derive\", \"parsing\", \"printing\", \"proc-macro\"]","declared_features":"[\"clone-impls\", \"default\", \"derive\", \"extra-traits\", \"fold\", \"full\", \"parsing\", \"printing\", \"proc-macro\", \"test\", \"visit\", \"visit-mut\"]","target":9442126953582868550,"profile":2785734998891497023,"path":8687046859460428135,"deps":[[8949245912927223590,"quote",false,10592669266501340001],[12333832803962989937,"unicode_ident",false,16793811267078206154],[16346726298725429545,"proc_macro2",false,8059170461296913469]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/syn-86f5aa6c6ace4240/dep-lib-syn","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/dep-lib-thiserror b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/dep-lib-thiserror new file mode 100644 index 00000000..e6c51cac Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/dep-lib-thiserror differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/lib-thiserror b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/lib-thiserror new file mode 100644 index 00000000..a75b31b5 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/lib-thiserror @@ -0,0 +1 @@ +47914a38d6fd65f3 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/lib-thiserror.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/lib-thiserror.json new file mode 100644 index 00000000..02c09d11 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-572b221996b5f3ee/lib-thiserror.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":13586076721141200315,"profile":2785734998891497023,"path":7642888677821054070,"deps":[[1957009224993739128,"build_script_build",false,9625043333480330270],[7717134694032034306,"thiserror_impl",false,12308587709611057643]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/thiserror-572b221996b5f3ee/dep-lib-thiserror","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/build-script-build-script-build new file mode 100644 index 00000000..b5966d71 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/build-script-build-script-build @@ -0,0 +1 @@ +7a920618d44fdd59 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/build-script-build-script-build.json new file mode 100644 index 00000000..366699bc --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":5408242616063297496,"profile":2785734998891497023,"path":11777605584080991800,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/thiserror-6b4c49fb40990cd8/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/dep-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/dep-build-script-build-script-build new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/dep-build-script-build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-6b4c49fb40990cd8/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-ea6c25606e27ac64/run-build-script-build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-ea6c25606e27ac64/run-build-script-build-script-build new file mode 100644 index 00000000..47d0a4fa --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-ea6c25606e27ac64/run-build-script-build-script-build @@ -0,0 +1 @@ +1e34d687ea059385 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-ea6c25606e27ac64/run-build-script-build-script-build.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-ea6c25606e27ac64/run-build-script-build-script-build.json new file mode 100644 index 00000000..28a46706 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-ea6c25606e27ac64/run-build-script-build-script-build.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[1957009224993739128,"build_script_build",false,6475419611583386234]],"local":[{"RerunIfChanged":{"output":"debug/build/thiserror-ea6c25606e27ac64/output","paths":["build/probe.rs"]}},{"RerunIfEnvChanged":{"var":"RUSTC_BOOTSTRAP","val":null}}],"rustflags":[],"config":0,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/dep-lib-thiserror_impl b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/dep-lib-thiserror_impl new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/dep-lib-thiserror_impl differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/lib-thiserror_impl b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/lib-thiserror_impl new file mode 100644 index 00000000..c9a73312 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/lib-thiserror_impl @@ -0,0 +1 @@ +eb211be64ee3d0aa \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/lib-thiserror_impl.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/lib-thiserror_impl.json new file mode 100644 index 00000000..d013eade --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/thiserror-impl-40d32b56c8547825/lib-thiserror_impl.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[]","target":6216210811039475267,"profile":2785734998891497023,"path":17504287886612828789,"deps":[[8949245912927223590,"quote",false,10592669266501340001],[16346726298725429545,"proc_macro2",false,8059170461296913469],[16468994220052032074,"syn",false,1250132995784755935]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/thiserror-impl-40d32b56c8547825/dep-lib-thiserror_impl","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/dep-lib-unicode_ident b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/dep-lib-unicode_ident new file mode 100644 index 00000000..ec3cb8bf Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/dep-lib-unicode_ident differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/lib-unicode_ident b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/lib-unicode_ident new file mode 100644 index 00000000..aece5c19 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/lib-unicode_ident @@ -0,0 +1 @@ +ca3226b75e9a0fe9 \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/lib-unicode_ident.json b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/lib-unicode_ident.json new file mode 100644 index 00000000..08cf61b2 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/lib-unicode_ident.json @@ -0,0 +1 @@ +{"rustc":17467972547956852243,"features":"[]","declared_features":"[]","target":14045917370260632744,"profile":2785734998891497023,"path":13952268360258147237,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/unicode-ident-fb577f96b63f8d5a/dep-lib-unicode_ident","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0} \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build-script-build new file mode 100755 index 00000000..eb48decd Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174 b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174 new file mode 100755 index 00000000..eb48decd Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174.d b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174.d new file mode 100644 index 00000000..01540bc1 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174.d @@ -0,0 +1,5 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/build.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-5b3c4336788f7174/build_script_build-5b3c4336788f7174: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/build.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/build.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/output b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/output new file mode 100644 index 00000000..57fc2854 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/output @@ -0,0 +1,2 @@ +cargo:rerun-if-changed=build.rs +cargo:rustc-check-cfg=cfg(crossbeam_sanitize_thread) diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/root-output b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/root-output new file mode 100644 index 00000000..91782756 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/root-output @@ -0,0 +1 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/out \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/stderr b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-deque-f51f1776f2c514b1/stderr new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build-script-build new file mode 100755 index 00000000..986aecc0 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce new file mode 100755 index 00000000..986aecc0 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce.d b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce.d new file mode 100644 index 00000000..e2181c8f --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce.d @@ -0,0 +1,5 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/build.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-1347d2207ea061ce/build_script_build-1347d2207ea061ce: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/build.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/build.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/output b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/output new file mode 100644 index 00000000..57fc2854 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/output @@ -0,0 +1,2 @@ +cargo:rerun-if-changed=build.rs +cargo:rustc-check-cfg=cfg(crossbeam_sanitize_thread) diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/root-output b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/root-output new file mode 100644 index 00000000..bb48420d --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/root-output @@ -0,0 +1 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/out \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/stderr b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-epoch-2092bf031b9d9a01/stderr new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build-script-build new file mode 100755 index 00000000..c1b1362d Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3 b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3 new file mode 100755 index 00000000..c1b1362d Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3.d b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3.d new file mode 100644 index 00000000..55e49280 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3.d @@ -0,0 +1,9 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/build.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/no_atomic.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/build-common.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-5b2ff6828cdce4c3/build_script_build-5b2ff6828cdce4c3: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/build.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/no_atomic.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/build-common.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/build.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/no_atomic.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/build-common.rs: + +# env-dep:CARGO_PKG_NAME=crossbeam-utils diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/output b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/output new file mode 100644 index 00000000..d0bad9fd --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/output @@ -0,0 +1,2 @@ +cargo:rerun-if-changed=no_atomic.rs +cargo:rustc-check-cfg=cfg(crossbeam_no_atomic,crossbeam_sanitize_thread) diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/root-output b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/root-output new file mode 100644 index 00000000..27a89239 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/root-output @@ -0,0 +1 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/out \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/stderr b/crates/hevcenc/target/agent-codec-hevc/debug/build/crossbeam-utils-99f1d686cf22430f/stderr new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/output b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/output new file mode 100644 index 00000000..d3d235a5 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/output @@ -0,0 +1,23 @@ +cargo:rustc-check-cfg=cfg(fuzzing) +cargo:rustc-check-cfg=cfg(no_is_available) +cargo:rustc-check-cfg=cfg(no_literal_byte_character) +cargo:rustc-check-cfg=cfg(no_literal_c_string) +cargo:rustc-check-cfg=cfg(no_source_text) +cargo:rustc-check-cfg=cfg(proc_macro_span) +cargo:rustc-check-cfg=cfg(proc_macro_span_file) +cargo:rustc-check-cfg=cfg(proc_macro_span_location) +cargo:rustc-check-cfg=cfg(procmacro2_backtrace) +cargo:rustc-check-cfg=cfg(procmacro2_build_probe) +cargo:rustc-check-cfg=cfg(procmacro2_nightly_testing) +cargo:rustc-check-cfg=cfg(procmacro2_semver_exempt) +cargo:rustc-check-cfg=cfg(randomize_layout) +cargo:rustc-check-cfg=cfg(span_locations) +cargo:rustc-check-cfg=cfg(super_unstable) +cargo:rustc-check-cfg=cfg(wrap_proc_macro) +cargo:rerun-if-changed=src/probe/proc_macro_span.rs +cargo:rustc-cfg=wrap_proc_macro +cargo:rerun-if-changed=src/probe/proc_macro_span_location.rs +cargo:rustc-cfg=proc_macro_span_location +cargo:rerun-if-changed=src/probe/proc_macro_span_file.rs +cargo:rustc-cfg=proc_macro_span_file +cargo:rerun-if-env-changed=RUSTC_BOOTSTRAP diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/root-output b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/root-output new file mode 100644 index 00000000..545de268 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/root-output @@ -0,0 +1 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/out \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/stderr b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-73f2303f90bf5dd8/stderr new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build-script-build new file mode 100755 index 00000000..0b58654c Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527 b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527 new file mode 100755 index 00000000..0b58654c Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527.d b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527.d new file mode 100644 index 00000000..2a84a739 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527.d @@ -0,0 +1,5 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/build.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/proc-macro2-8b5400504772b527/build_script_build-8b5400504772b527: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/build.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/build.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build-script-build new file mode 100755 index 00000000..8ff96310 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6 b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6 new file mode 100755 index 00000000..8ff96310 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6.d b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6.d new file mode 100644 index 00000000..5e8a8fd8 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6.d @@ -0,0 +1,5 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/build.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-4b484c95cbb303f6/build_script_build-4b484c95cbb303f6: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/build.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/build.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/output b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/output new file mode 100644 index 00000000..6d81eca2 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/output @@ -0,0 +1,2 @@ +cargo:rerun-if-changed=build.rs +cargo:rustc-check-cfg=cfg(no_diagnostic_namespace) diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/root-output b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/root-output new file mode 100644 index 00000000..323737b3 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/root-output @@ -0,0 +1 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/out \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/stderr b/crates/hevcenc/target/agent-codec-hevc/debug/build/quote-d3fa970117425bd7/stderr new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/output b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/output new file mode 100644 index 00000000..d15ba9ab --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/output @@ -0,0 +1 @@ +cargo:rerun-if-changed=build.rs diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/root-output b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/root-output new file mode 100644 index 00000000..4de1f8ae --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/root-output @@ -0,0 +1 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/out \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/stderr b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-1b6fdb81fee9810e/stderr new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build-script-build new file mode 100755 index 00000000..73a8ef6f Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570 b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570 new file mode 100755 index 00000000..73a8ef6f Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570.d b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570.d new file mode 100644 index 00000000..ff779b97 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570.d @@ -0,0 +1,5 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/build.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/rayon-core-b409a671b98b8570/build_script_build-b409a671b98b8570: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/build.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/build.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build-script-build b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build-script-build new file mode 100755 index 00000000..3ff8ed41 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build-script-build differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8 b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8 new file mode 100755 index 00000000..3ff8ed41 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8.d b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8.d new file mode 100644 index 00000000..3171e69c --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8.d @@ -0,0 +1,5 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/build.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-6b4c49fb40990cd8/build_script_build-6b4c49fb40990cd8: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/build.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/build.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/invoked.timestamp b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/invoked.timestamp new file mode 100644 index 00000000..e00328da --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/invoked.timestamp @@ -0,0 +1 @@ +This file has an mtime of when this was started. \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out/private.rs b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out/private.rs new file mode 100644 index 00000000..036d1e64 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out/private.rs @@ -0,0 +1,5 @@ +#[doc(hidden)] +pub mod __private21 { + #[doc(hidden)] + pub use crate::private::*; +} diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/output b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/output new file mode 100644 index 00000000..f62a8d10 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/output @@ -0,0 +1,5 @@ +cargo:rerun-if-changed=build/probe.rs +cargo:rustc-check-cfg=cfg(error_generic_member_access) +cargo:rustc-check-cfg=cfg(thiserror_nightly_testing) +cargo:rustc-check-cfg=cfg(thiserror_no_backtrace_type) +cargo:rerun-if-env-changed=RUSTC_BOOTSTRAP diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/root-output b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/root-output new file mode 100644 index 00000000..374068c0 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/root-output @@ -0,0 +1 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out \ No newline at end of file diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/stderr b/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/stderr new file mode 100644 index 00000000..e69de29b diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_deque-d9775930f3a59590.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_deque-d9775930f3a59590.d new file mode 100644 index 00000000..7b261062 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_deque-d9775930f3a59590.d @@ -0,0 +1,8 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_deque-d9775930f3a59590.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/deque.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/deque.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/deque.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.8/src/deque.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_epoch-72c18a120600cf42.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_epoch-72c18a120600cf42.d new file mode 100644 index 00000000..93d1b139 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_epoch-72c18a120600cf42.d @@ -0,0 +1,18 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_epoch-72c18a120600cf42.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/atomic.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/collector.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/deferred.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/epoch.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/guard.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/internal.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/list.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/once_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/queue.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/default.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/atomic.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/collector.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/deferred.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/epoch.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/guard.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/internal.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/list.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/once_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/queue.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/default.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/atomic.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/collector.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/deferred.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/epoch.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/guard.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/internal.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/list.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/once_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/queue.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/default.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/atomic.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/collector.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/deferred.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/epoch.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/guard.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/internal.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/list.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/once_lock.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/sync/queue.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.21/src/default.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_utils-799e464d44773a7d.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_utils-799e464d44773a7d.d new file mode 100644 index 00000000..2574a612 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_utils-799e464d44773a7d.d @@ -0,0 +1,19 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/crossbeam_utils-799e464d44773a7d.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/seq_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/atomic_cell.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/consume.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/cache_padded.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/backoff.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/once_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/parker.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/sharded_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/wait_group.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/thread.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/seq_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/atomic_cell.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/consume.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/cache_padded.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/backoff.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/once_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/parker.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/sharded_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/wait_group.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/thread.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/seq_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/atomic_cell.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/consume.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/cache_padded.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/backoff.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/once_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/parker.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/sharded_lock.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/wait_group.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/thread.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/seq_lock.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/atomic_cell.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/atomic/consume.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/cache_padded.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/backoff.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/once_lock.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/parker.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/sharded_lock.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/sync/wait_group.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.23/src/thread.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/effectcraft_hevcenc-955aee250b2c2a4e.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/effectcraft_hevcenc-955aee250b2c2a4e.d new file mode 100644 index 00000000..f618c9ca --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/effectcraft_hevcenc-955aee250b2c2a4e.d @@ -0,0 +1,16 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/effectcraft_hevcenc-955aee250b2c2a4e.d: crates/hevcenc/src/lib.rs crates/hevcenc/src/bitstream.rs crates/hevcenc/src/cabac.rs crates/hevcenc/src/frame.rs crates/hevcenc/src/inter.rs crates/hevcenc/src/intra.rs crates/hevcenc/src/me.rs crates/hevcenc/src/params.rs crates/hevcenc/src/transform.rs crates/hevcenc/src/tables.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rlib: crates/hevcenc/src/lib.rs crates/hevcenc/src/bitstream.rs crates/hevcenc/src/cabac.rs crates/hevcenc/src/frame.rs crates/hevcenc/src/inter.rs crates/hevcenc/src/intra.rs crates/hevcenc/src/me.rs crates/hevcenc/src/params.rs crates/hevcenc/src/transform.rs crates/hevcenc/src/tables.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rmeta: crates/hevcenc/src/lib.rs crates/hevcenc/src/bitstream.rs crates/hevcenc/src/cabac.rs crates/hevcenc/src/frame.rs crates/hevcenc/src/inter.rs crates/hevcenc/src/intra.rs crates/hevcenc/src/me.rs crates/hevcenc/src/params.rs crates/hevcenc/src/transform.rs crates/hevcenc/src/tables.rs + +crates/hevcenc/src/lib.rs: +crates/hevcenc/src/bitstream.rs: +crates/hevcenc/src/cabac.rs: +crates/hevcenc/src/frame.rs: +crates/hevcenc/src/inter.rs: +crates/hevcenc/src/intra.rs: +crates/hevcenc/src/me.rs: +crates/hevcenc/src/params.rs: +crates/hevcenc/src/transform.rs: +crates/hevcenc/src/tables.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/either-72952a75ef45a16d.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/either-72952a75ef45a16d.d new file mode 100644 index 00000000..20801a0d --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/either-72952a75ef45a16d.d @@ -0,0 +1,9 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/either-72952a75ef45a16d.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/iterator.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/into_either.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/iterator.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/into_either.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/iterator.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/into_either.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/iterator.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.18.0/src/into_either.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_bitstream-2ad770771b023a77.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_bitstream-2ad770771b023a77.d new file mode 100644 index 00000000..63971aef --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_bitstream-2ad770771b023a77.d @@ -0,0 +1,7 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_bitstream-2ad770771b023a77.d: /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/bitstream/src/lib.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rlib: /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/bitstream/src/lib.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rmeta: /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/bitstream/src/lib.rs + +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/bitstream/src/lib.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_hevc-71cf1ac5fd5debba.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_hevc-71cf1ac5fd5debba.d new file mode 100644 index 00000000..e6fcfb02 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_hevc-71cf1ac5fd5debba.d @@ -0,0 +1,22 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/filmcraft_hevc-71cf1ac5fd5debba.d: /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/lib.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/cabac.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/decoder.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/dpb.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/error.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/filter.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/inter.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/intra.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/mvpred.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/params.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/picture.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slice.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slicedec.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/tables.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/transform.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/spec_tables.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rlib: /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/lib.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/cabac.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/decoder.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/dpb.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/error.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/filter.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/inter.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/intra.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/mvpred.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/params.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/picture.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slice.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slicedec.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/tables.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/transform.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/spec_tables.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rmeta: /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/lib.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/cabac.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/decoder.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/dpb.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/error.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/filter.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/inter.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/intra.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/mvpred.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/params.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/picture.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slice.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slicedec.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/tables.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/transform.rs /Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/spec_tables.rs + +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/lib.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/cabac.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/decoder.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/dpb.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/error.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/filter.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/inter.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/intra.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/mvpred.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/params.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/picture.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slice.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/slicedec.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/tables.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/transform.rs: +/Users/bt/.cargo/git/checkouts/filmcraft-58288263de9b27d9/89cfbf2/crates/hevc/src/spec_tables.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rlib new file mode 100644 index 00000000..0b2cd1e6 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rmeta new file mode 100644 index 00000000..9f91813c Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_deque-d9775930f3a59590.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rlib new file mode 100644 index 00000000..1deb140e Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rmeta new file mode 100644 index 00000000..f969e16e Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_epoch-72c18a120600cf42.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rlib new file mode 100644 index 00000000..f1da5d81 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rmeta new file mode 100644 index 00000000..ca423860 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libcrossbeam_utils-799e464d44773a7d.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rlib new file mode 100644 index 00000000..5e5e2f50 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rmeta new file mode 100644 index 00000000..03d053ff Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeffectcraft_hevcenc-955aee250b2c2a4e.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rlib new file mode 100644 index 00000000..c8e72a48 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rmeta new file mode 100644 index 00000000..6430cb84 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libeither-72952a75ef45a16d.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rlib new file mode 100644 index 00000000..495842e6 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rmeta new file mode 100644 index 00000000..3185389e Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_bitstream-2ad770771b023a77.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rlib new file mode 100644 index 00000000..cb28bbc2 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rmeta new file mode 100644 index 00000000..39c9f55a Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libfilmcraft_hevc-71cf1ac5fd5debba.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rlib new file mode 100644 index 00000000..01bf2210 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rmeta new file mode 100644 index 00000000..adb9c960 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rlib new file mode 100644 index 00000000..247a152c Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rmeta new file mode 100644 index 00000000..91bcca08 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rlib new file mode 100644 index 00000000..a84d8dc5 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rmeta new file mode 100644 index 00000000..3775073c Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rlib new file mode 100644 index 00000000..23605122 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rmeta new file mode 100644 index 00000000..a9861567 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rlib new file mode 100644 index 00000000..50a6d640 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rmeta new file mode 100644 index 00000000..082563d9 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rlib new file mode 100644 index 00000000..68125455 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rmeta new file mode 100644 index 00000000..35caf1b2 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror_impl-40d32b56c8547825.dylib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror_impl-40d32b56c8547825.dylib new file mode 100755 index 00000000..ccb90c50 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror_impl-40d32b56c8547825.dylib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rlib b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rlib new file mode 100644 index 00000000..386642d7 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rlib differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rmeta b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rmeta new file mode 100644 index 00000000..e816378f Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rmeta differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/proc_macro2-67c058b976f66fb4.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/proc_macro2-67c058b976f66fb4.d new file mode 100644 index 00000000..ddbe1a4f --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/proc_macro2-67c058b976f66fb4.d @@ -0,0 +1,17 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/proc_macro2-67c058b976f66fb4.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/marker.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/parse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_file.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_location.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/rcvec.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/detection.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/fallback.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/extra.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/wrapper.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/marker.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/parse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_file.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_location.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/rcvec.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/detection.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/fallback.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/extra.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/wrapper.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libproc_macro2-67c058b976f66fb4.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/marker.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/parse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_file.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_location.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/rcvec.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/detection.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/fallback.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/extra.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/wrapper.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/marker.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/parse.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_file.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/probe/proc_macro_span_location.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/rcvec.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/detection.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/fallback.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/extra.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.107/src/wrapper.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/quote-0f85966d6f134e44.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/quote-0f85966d6f134e44.d new file mode 100644 index 00000000..abc8bb75 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/quote-0f85966d6f134e44.d @@ -0,0 +1,13 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/quote-0f85966d6f134e44.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ext.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/format.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ident_fragment.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/to_tokens.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/runtime.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/spanned.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ext.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/format.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ident_fragment.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/to_tokens.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/runtime.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/spanned.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libquote-0f85966d6f134e44.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ext.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/format.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ident_fragment.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/to_tokens.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/runtime.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/spanned.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ext.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/format.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/ident_fragment.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/to_tokens.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/runtime.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.47/src/spanned.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon-6e2dc17155d9fd34.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon-6e2dc17155d9fd34.d new file mode 100644 index 00000000..899eea5c --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon-6e2dc17155d9fd34.d @@ -0,0 +1,104 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon-6e2dc17155d9fd34.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/delegate.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/private.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/split_producer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/array.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/binary_heap.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_set.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_set.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/linked_list.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/vec_deque.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/plumbing/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/blocks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chain.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/cloned.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/consumer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/copied.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/empty.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/enumerate.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/extend.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find_first_last/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/for_each.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/from_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/inspect.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave_shortest.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/intersperse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/len.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/multizip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/noop.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/once.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/panic_fuse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/par_bridge.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/positions.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/product.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/reduce.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/repeat.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/rev.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any_while.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/splitter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/step_by.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/sum.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any_while.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_fold.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/unzip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/update.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/walk_tree.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/while_some.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip_eq.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/option.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/prelude.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range_inclusive.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/result.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunk_by.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/rchunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/sort.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/windows.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/str.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/string.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/vec.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/math.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/par_either.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_collect_filtermap_data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_zip_filtered_data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cell_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/must_use.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/no_send_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/rc_par_iter.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/delegate.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/private.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/split_producer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/array.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/binary_heap.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_set.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_set.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/linked_list.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/vec_deque.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/plumbing/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/blocks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chain.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/cloned.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/consumer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/copied.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/empty.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/enumerate.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/extend.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find_first_last/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/for_each.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/from_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/inspect.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave_shortest.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/intersperse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/len.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/multizip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/noop.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/once.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/panic_fuse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/par_bridge.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/positions.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/product.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/reduce.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/repeat.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/rev.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any_while.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/splitter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/step_by.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/sum.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any_while.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_fold.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/unzip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/update.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/walk_tree.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/while_some.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip_eq.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/option.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/prelude.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range_inclusive.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/result.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunk_by.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/rchunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/sort.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/windows.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/str.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/string.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/vec.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/math.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/par_either.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_collect_filtermap_data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_zip_filtered_data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cell_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/must_use.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/no_send_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/rc_par_iter.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon-6e2dc17155d9fd34.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/delegate.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/private.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/split_producer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/array.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/binary_heap.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_set.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_set.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/linked_list.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/vec_deque.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/plumbing/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/blocks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chain.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/cloned.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/consumer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/copied.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/empty.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/enumerate.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/extend.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find_first_last/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/for_each.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/from_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/inspect.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave_shortest.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/intersperse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/len.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/multizip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/noop.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/once.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/panic_fuse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/par_bridge.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/positions.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/product.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/reduce.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/repeat.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/rev.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any_while.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/splitter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/step_by.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/sum.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any_while.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_fold.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce_with.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/unzip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/update.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/walk_tree.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/while_some.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip_eq.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/option.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/prelude.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range_inclusive.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/result.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunk_by.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/rchunks.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/sort.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/windows.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/str.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/string.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/vec.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/math.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/par_either.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_collect_filtermap_data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_zip_filtered_data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cell_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/must_use.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/no_send_par_iter.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/rc_par_iter.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/delegate.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/private.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/split_producer.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/array.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/binary_heap.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_map.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/btree_set.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_map.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/hash_set.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/linked_list.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/collections/vec_deque.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/plumbing/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/blocks.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chain.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/chunks.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/cloned.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/consumer.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/collect/test.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/copied.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/empty.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/enumerate.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/extend.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/filter_map.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/find_first_last/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flat_map_iter.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/flatten_iter.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/fold_chunks_with.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/for_each.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/from_par_iter.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/inspect.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/interleave_shortest.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/intersperse.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/len.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/map_with.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/multizip.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/noop.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/once.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/panic_fuse.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/par_bridge.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/positions.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/product.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/reduce.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/repeat.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/rev.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/skip_any_while.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/splitter.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/step_by.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/sum.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/take_any_while.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_fold.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/try_reduce_with.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/unzip.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/update.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/walk_tree.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/while_some.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/iter/zip_eq.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/option.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/prelude.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/range_inclusive.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/result.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunk_by.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/chunks.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/rchunks.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/sort.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/windows.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/slice/test.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/str.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/string.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/vec.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/math.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/par_either.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_collect_filtermap_data.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cannot_zip_filtered_data.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/cell_par_iter.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/must_use.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/no_send_par_iter.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.12.0/src/compile_fail/rc_par_iter.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon_core-d9d479d6cc7ea71c.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon_core-d9d479d6cc7ea71c.d new file mode 100644 index 00000000..dbdcd0ae --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon_core-d9d479d6cc7ea71c.d @@ -0,0 +1,29 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/rayon_core-d9d479d6cc7ea71c.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/private.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/job.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/join/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/latch.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/registry.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/scope/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/counters.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/spawn/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/unwind.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race1.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race2.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race3.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_return.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_upvar.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/scope_join_bad.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/test.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/private.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/job.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/join/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/latch.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/registry.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/scope/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/counters.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/spawn/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/unwind.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race1.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race2.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race3.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_return.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_upvar.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/scope_join_bad.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/test.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/librayon_core-d9d479d6cc7ea71c.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/private.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/job.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/join/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/latch.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/registry.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/scope/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/counters.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/spawn/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/test.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/unwind.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/mod.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race1.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race2.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race3.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_return.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_upvar.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/scope_join_bad.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/test.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/private.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/broadcast/test.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/job.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/join/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/latch.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/registry.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/scope/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/sleep/counters.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/spawn/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/thread_pool/test.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/unwind.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/mod.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race1.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race2.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/quicksort_race3.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_return.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/rc_upvar.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/compile_fail/scope_join_bad.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/test.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831 b/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831 new file mode 100755 index 00000000..93b321a3 Binary files /dev/null and b/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831 differ diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831.d new file mode 100644 index 00000000..f0407a53 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831.d @@ -0,0 +1,5 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831.d: crates/hevcenc/tests/roundtrip.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/roundtrip-335f1f822197d831: crates/hevcenc/tests/roundtrip.rs + +crates/hevcenc/tests/roundtrip.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/syn-86f5aa6c6ace4240.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/syn-86f5aa6c6ace4240.d new file mode 100644 index 00000000..f4d68481 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/syn-86f5aa6c6ace4240.d @@ -0,0 +1,49 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/syn-86f5aa6c6ace4240.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/macros.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/group.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/token.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/attr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/bigint.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/buffer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/classify.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_keyword.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_punctuation.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/derive.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/drops.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/error.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ext.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/fixup.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/generics.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ident.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lifetime.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lit.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lookahead.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/mac.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/meta.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/op.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/discouraged.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_macro_input.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_quote.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/path.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/precedence.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/print.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/punctuated.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/restriction.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/sealed.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/scan_expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/span.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/spanned.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/thread.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ty.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/verbatim.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/export.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/gen/clone.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/macros.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/group.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/token.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/attr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/bigint.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/buffer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/classify.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_keyword.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_punctuation.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/derive.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/drops.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/error.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ext.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/fixup.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/generics.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ident.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lifetime.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lit.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lookahead.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/mac.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/meta.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/op.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/discouraged.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_macro_input.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_quote.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/path.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/precedence.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/print.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/punctuated.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/restriction.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/sealed.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/scan_expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/span.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/spanned.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/thread.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ty.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/verbatim.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/export.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/gen/clone.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libsyn-86f5aa6c6ace4240.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/macros.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/group.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/token.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/attr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/bigint.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/buffer.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/classify.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_keyword.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_punctuation.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/data.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/derive.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/drops.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/error.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ext.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/fixup.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/generics.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ident.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lifetime.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lit.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lookahead.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/mac.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/meta.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/op.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/discouraged.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_macro_input.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_quote.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/path.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/precedence.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/print.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/punctuated.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/restriction.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/sealed.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/scan_expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/span.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/spanned.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/thread.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ty.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/verbatim.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/export.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/gen/clone.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/macros.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/group.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/token.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/attr.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/bigint.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/buffer.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/classify.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_keyword.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/custom_punctuation.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/data.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/derive.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/drops.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/error.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/expr.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ext.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/fixup.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/generics.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ident.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lifetime.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lit.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/lookahead.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/mac.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/meta.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/op.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/discouraged.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_macro_input.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/parse_quote.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/path.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/precedence.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/print.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/punctuated.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/restriction.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/sealed.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/scan_expr.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/span.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/spanned.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/thread.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/ty.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/verbatim.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/export.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-3.0.6/src/gen/clone.rs: diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror-572b221996b5f3ee.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror-572b221996b5f3ee.d new file mode 100644 index 00000000..a8d25a5c --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror-572b221996b5f3ee.d @@ -0,0 +1,14 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror-572b221996b5f3ee.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/aserror.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/display.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/var.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/private.rs /Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out/private.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/aserror.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/display.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/var.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/private.rs /Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out/private.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror-572b221996b5f3ee.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/aserror.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/display.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/var.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/private.rs /Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out/private.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/aserror.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/display.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/var.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.21/src/private.rs: +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out/private.rs: + +# env-dep:OUT_DIR=/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/build/thiserror-ea6c25606e27ac64/out diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror_impl-40d32b56c8547825.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror_impl-40d32b56c8547825.d new file mode 100644 index 00000000..781aff08 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror_impl-40d32b56c8547825.d @@ -0,0 +1,17 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/thiserror_impl-40d32b56c8547825.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/ast.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/attr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/expand.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/fallback.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/fmt.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/generics.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/prop.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/scan_expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/unraw.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/valid.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libthiserror_impl-40d32b56c8547825.dylib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/ast.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/attr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/expand.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/fallback.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/fmt.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/generics.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/prop.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/scan_expr.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/unraw.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/valid.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/ast.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/attr.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/expand.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/fallback.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/fmt.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/generics.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/prop.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/scan_expr.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/unraw.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.21/src/valid.rs: + +# env-dep:CARGO_PKG_VERSION_PATCH=21 diff --git a/crates/hevcenc/target/agent-codec-hevc/debug/deps/unicode_ident-fb577f96b63f8d5a.d b/crates/hevcenc/target/agent-codec-hevc/debug/deps/unicode_ident-fb577f96b63f8d5a.d new file mode 100644 index 00000000..b07086d8 --- /dev/null +++ b/crates/hevcenc/target/agent-codec-hevc/debug/deps/unicode_ident-fb577f96b63f8d5a.d @@ -0,0 +1,8 @@ +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/unicode_ident-fb577f96b63f8d5a.d: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/tables.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rlib: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/tables.rs + +/Users/bt/dev/storyteller/craft-apps/effectcraft/.claude/worktrees/agent-a5ec24c82e394a621/crates/hevcenc/target/agent-codec-hevc/debug/deps/libunicode_ident-fb577f96b63f8d5a.rmeta: /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/lib.rs /Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/tables.rs + +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/lib.rs: +/Users/bt/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.26/src/tables.rs: diff --git a/crates/hevcenc/tests/roundtrip.rs b/crates/hevcenc/tests/roundtrip.rs new file mode 100644 index 00000000..777bc455 --- /dev/null +++ b/crates/hevcenc/tests/roundtrip.rs @@ -0,0 +1,413 @@ +//! Round trips through the filmcraft-hevc decoder and (when installed) ffmpeg: decoded pictures must +//! equal the encoder's own reconstruction bit-exactly. + +use effectcraft_hevcenc::{Encoder, EncoderConfig, Frame, Profile, RateControl}; +use std::process::Command; + +struct Pic { + w: usize, + y: Vec, + u: Vec, + v: Vec, +} + +impl Pic { + fn frame(&self) -> Frame<'_> { + Frame { y: &self.y, u: &self.u, v: &self.v, y_stride: self.w, uv_stride: self.w / 2 } + } +} + +/// Smooth gradients with a few hard-edged shapes, translated by (dx, dy) samples. +fn synth(w: usize, h: usize, bd: u32, dx: i32, dy: i32) -> Pic { + let max = ((1u32 << bd) - 1) as f64; + let mut y = vec![0u16; w * h]; + let mut u = vec![0u16; w * h / 4]; + let mut v = vec![0u16; w * h / 4]; + let luma = |x: i32, yy: i32| -> f64 { + let (fx, fy) = ((x - dx) as f64, (yy - dy) as f64); + let mut l = 0.25 + 0.5 * (fx / 97.0).sin().abs() * 0.6 + 0.3 * (fy / 61.0).cos().powi(2); + // a disc and a rectangle + if (fx - 40.0).powi(2) + (fy - 30.0).powi(2) < 18.0f64.powi(2) { + l = 0.85; + } + if (70.0..110.0).contains(&fx) && (50.0..64.0).contains(&fy) { + l = 0.12; + } + // fine texture + l += 0.03 * ((fx * 0.9).sin() * (fy * 0.7).cos()); + l.clamp(0.0, 1.0) + }; + for yy in 0..h { + for x in 0..w { + y[yy * w + x] = (luma(x as i32, yy as i32) * max).round() as u16; + } + } + for yy in 0..h / 2 { + for x in 0..w / 2 { + let (fx, fy) = ((2 * x) as i32 - dx, (2 * yy) as i32 - dy); + let cu = 0.5 + 0.2 * ((fx as f64) / 53.0).sin(); + let cv = 0.5 + 0.2 * ((fy as f64) / 37.0).cos() * if (fx / 16 + fy / 16) % 2 == 0 { 1.0 } else { -0.5 }; + u[yy * w / 2 + x] = (cu * max).round() as u16; + v[yy * w / 2 + x] = (cv * max).round() as u16; + } + } + Pic { w, y, u, v } +} + +fn psnr(a: &[u16], b: &[u16], bd: u32) -> f64 { + let max = ((1u32 << bd) - 1) as f64; + let mse = a.iter().zip(b).map(|(&x, &y)| (x as f64 - y as f64).powi(2)).sum::() / a.len() as f64; + if mse == 0.0 { 99.0 } else { 10.0 * (max * max / mse).log10() } +} + +struct Run { + packets: Vec>, + keys: Vec, + recons: Vec<[Vec; 3]>, + hvcc: Vec, + sets: [Vec; 3], + psnr_y: Vec, +} + +fn encode(cfg: EncoderConfig, pics: &[Pic]) -> Run { + let bd = cfg.profile.bit_depth() as u32; + let mut enc = Encoder::new(cfg).unwrap(); + let mut run = Run { packets: vec![], keys: vec![], recons: vec![], hvcc: enc.hvcc(), sets: enc.parameter_sets(), psnr_y: vec![] }; + for p in pics { + let pkt = enc.encode(&p.frame()); + let rec = enc.last_reconstruction().unwrap().clone(); + run.psnr_y.push(psnr(&p.y, &rec[0], bd)); + run.packets.push(pkt.data); + run.keys.push(pkt.keyframe); + run.recons.push(rec); + } + run +} + +/// Decode with filmcraft-hevc and compare with the reconstructions. +fn check_filmcraft(run: &Run, w: usize, h: usize) { + let mut dec = filmcraft_hevc::Decoder::from_hvcc(&run.hvcc).unwrap(); + let mut pics = Vec::new(); + for (i, p) in run.packets.iter().enumerate() { + pics.extend(dec.decode(p, i as i64).unwrap()); + } + pics.extend(dec.flush()); + assert_eq!(pics.len(), run.packets.len(), "decoded picture count"); + for (i, pic) in pics.iter().enumerate() { + assert_eq!((pic.width as usize, pic.height as usize), (w, h)); + assert_eq!(pic.pts, i as i64); + let rec = &run.recons[i]; + for (c, plane) in [&pic.y, &pic.u, &pic.v].into_iter().enumerate() { + let (pw, ph, stride) = if c == 0 { (w, h, pic.y_stride) } else { (w / 2, h / 2, pic.uv_stride) }; + for yy in 0..ph { + for x in 0..pw { + let got = plane.get(yy * stride + x); + let want = rec[c][yy * pw + x]; + assert_eq!(got, want, "picture {i} plane {c} at ({x}, {yy})"); + } + } + } + } +} + +fn annexb(run: &Run) -> Vec { + let mut out = Vec::new(); + for s in &run.sets { + out.extend_from_slice(&[0, 0, 0, 1]); + out.extend_from_slice(s); + } + for p in &run.packets { + let mut i = 0; + while i < p.len() { + let n = u32::from_be_bytes([p[i], p[i + 1], p[i + 2], p[i + 3]]) as usize; + out.extend_from_slice(&[0, 0, 0, 1]); + out.extend_from_slice(&p[i + 4..i + 4 + n]); + i += 4 + n; + } + } + out +} + +fn ffmpeg_available() -> bool { + Command::new("ffmpeg").arg("-version").output().map(|o| o.status.success()).unwrap_or(false) +} + +/// Decode the Annex-B stream with ffmpeg and require bit-exact equality and an empty stderr. +fn check_ffmpeg(run: &Run, w: usize, h: usize, bd: u32, tag: &str) { + if !ffmpeg_available() { + eprintln!("ffmpeg not found; skipping the ffmpeg oracle"); + return; + } + let dir = std::env::temp_dir(); + let uniq = format!("{}-{}-{}", tag, std::process::id(), std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()); + let inp = dir.join(format!("hevcenc-{uniq}.hevc")); + let out = dir.join(format!("hevcenc-{uniq}.yuv")); + std::fs::write(&inp, annexb(run)).unwrap(); + let pix = if bd == 8 { "yuv420p" } else { "yuv420p10le" }; + let o = Command::new("ffmpeg") + .args(["-v", "error", "-xerror", "-y", "-f", "hevc", "-i"]) + .arg(&inp) + .args(["-f", "rawvideo", "-pix_fmt", pix]) + .arg(&out) + .output() + .unwrap(); + let stderr = String::from_utf8_lossy(&o.stderr).to_string(); + let data = std::fs::read(&out).unwrap_or_default(); + let _ = std::fs::remove_file(&inp); + let _ = std::fs::remove_file(&out); + assert!(o.status.success() && stderr.trim().is_empty(), "ffmpeg failed: {stderr}"); + let bps = if bd == 8 { 1 } else { 2 }; + let frame_bytes = (w * h + 2 * (w / 2) * (h / 2)) * bps; + assert_eq!(data.len(), frame_bytes * run.packets.len(), "ffmpeg output size"); + for (i, rec) in run.recons.iter().enumerate() { + let mut off = i * frame_bytes; + for (c, plane) in rec.iter().enumerate() { + let pw = if c == 0 { w } else { w / 2 }; + for (k, &want) in plane.iter().enumerate() { + let got = if bd == 8 { data[off] as u16 } else { u16::from_le_bytes([data[off], data[off + 1]]) }; + assert_eq!(got, want, "ffmpeg: picture {i} plane {c} at ({}, {})", k % pw, k / pw); + off += bps; + } + } + } +} + +fn cfg(w: u32, h: u32, qp: u8, keyint: u32, profile: Profile) -> EncoderConfig { + let mut c = EncoderConfig::new(w, h, 30, 1); + c.rate = RateControl::ConstantQp(qp); + c.keyint = keyint; + c.profile = profile; + c +} + +#[test] +fn intra_8bit_quality_and_decoders() { + let (w, h) = (192, 128); + let pics = vec![synth(w, h, 8, 0, 0)]; + let run = encode(cfg(w as u32, h as u32, 22, 1, Profile::Main), &pics); + eprintln!("intra QP22: {} bytes, PSNR-Y {:.2}", run.packets[0].len(), run.psnr_y[0]); + assert!(run.psnr_y[0] > 35.0, "PSNR {}", run.psnr_y[0]); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, 8, "intra8"); +} + +/// ffprobe reads the profile, level, colour description and timing from the parameter sets. +#[test] +fn ffprobe_stream_info() { + if !ffmpeg_available() { + return; + } + let (w, h) = (130, 72); + let pics: Vec = (0..2).map(|i| synth(w, h, 10, i, 0)).collect(); + let mut c = cfg(w as u32, h as u32, 30, 30, Profile::Main10); + c.fps_num = 30000; + c.fps_den = 1001; + c.full_range = true; + let run = encode(c, &pics); + let path = std::env::temp_dir().join(format!("hevcenc-probe-{}.hevc", std::process::id())); + std::fs::write(&path, annexb(&run)).unwrap(); + let o = Command::new("ffprobe").args(["-v", "error", "-show_streams", "-of", "flat"]).arg(&path).output().unwrap(); + let _ = std::fs::remove_file(&path); + let s = String::from_utf8_lossy(&o.stdout).to_string(); + for want in [ + "codec_name=\"hevc\"", + "profile=\"Main 10\"", + "width=130", + "height=72", + "coded_width=136", + "coded_height=72", + "pix_fmt=\"yuv420p10le\"", + "level=30", + "color_range=\"pc\"", + "color_space=\"bt709\"", + "color_transfer=\"bt709\"", + "color_primaries=\"bt709\"", + "r_frame_rate=\"30000/1001\"", + ] { + assert!(s.contains(want), "ffprobe output lacks {want}:\n{s}"); + } +} + +#[test] +fn inter_8bit_moving_pattern() { + let (w, h) = (160, 96); + let pics: Vec = (0..6).map(|i| synth(w, h, 8, 3 * i, i)).collect(); + let run = encode(cfg(w as u32, h as u32, 27, 30, Profile::Main), &pics); + let sizes: Vec = run.packets.iter().map(|p| p.len()).collect(); + eprintln!("inter sizes {sizes:?} psnr {:?}", run.psnr_y); + assert_eq!(run.keys, vec![true, false, false, false, false, false]); + let p_avg = sizes[1..].iter().sum::() as f64 / 5.0; + assert!(p_avg < sizes[0] as f64 * 0.6, "P frames {p_avg} vs I {}", sizes[0]); + assert!(run.psnr_y.iter().all(|&p| p > 33.0)); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, 8, "inter8"); +} + +#[test] +fn odd_multiple_sizes_and_cropping() { + for (w, h) in [(34usize, 18usize), (66, 38), (8, 8), (130, 72), (2, 2), (2, 70), (264, 6)] { + let pics: Vec = (0..3).map(|i| synth(w, h, 8, i, 2 * i)).collect(); + let run = encode(cfg(w as u32, h as u32, 30, 2, Profile::Main), &pics); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, 8, &format!("size{w}x{h}")); + } +} + +/// White noise at QP 0: large levels, escape codes with the maximum Rice parameter. +#[test] +fn noise_low_qp() { + let (w, h) = (48, 40); + let mut seed = 0x1234_5678u32; + let mut rnd = move || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + for (profile, bd) in [(Profile::Main, 8u32), (Profile::Main10, 10)] { + let max = (1u32 << bd) - 1; + let pics: Vec = (0..3) + .map(|_| { + let mut p = synth(w, h, bd, 0, 0); + for v in p.y.iter_mut().chain(p.u.iter_mut()).chain(p.v.iter_mut()) { + *v = (rnd() % (max + 1)) as u16; + } + p + }) + .collect(); + let run = encode(cfg(w as u32, h as u32, 0, 2, profile), &pics); + assert!(run.psnr_y.iter().all(|&p| p > 45.0), "{:?}", run.psnr_y); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, bd, &format!("noise{bd}")); + } +} + +/// High QPs: strong deblocking, large skipped CUs. +#[test] +fn high_qp_deblocking() { + let (w, h) = (200, 136); + for (qp, profile, bd) in [(38u8, Profile::Main, 8u32), (44, Profile::Main, 8), (37, Profile::Main10, 10)] { + let pics: Vec = (0..5).map(|i| synth(w, h, bd, 2 * i, i)).collect(); + let run = encode(cfg(w as u32, h as u32, qp, 4, profile), &pics); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, bd, &format!("hq{qp}")); + } +} + +/// More pictures than MaxPicOrderCntLsb (256) without an IDR, then a periodic IDR: POC wrap. +#[test] +fn long_gop_poc_wrap() { + let (w, h) = (16, 16); + let pics: Vec = (0..300).map(|i| synth(w, h, 8, i % 7, i % 5)).collect(); + let run = encode(cfg(w as u32, h as u32, 30, 0, Profile::Main), &pics); + assert_eq!(run.keys.iter().filter(|&&k| k).count(), 1); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, 8, "pocwrap"); + let run = encode(cfg(w as u32, h as u32, 30, 260, Profile::Main), &pics); + assert_eq!(run.keys.iter().filter(|&&k| k).count(), 2); + check_ffmpeg(&run, w, h, 8, "pocwrap2"); +} + +#[test] +fn odd_dimensions_are_rejected() { + assert!(Encoder::new(EncoderConfig::new(33, 17, 30, 1)).is_err()); +} + +#[test] +fn main10_round_trip() { + let (w, h) = (96, 64); + let pics: Vec = (0..4).map(|i| synth(w, h, 10, 2 * i, -i)).collect(); + let run = encode(cfg(w as u32, h as u32, 24, 3, Profile::Main10), &pics); + eprintln!("main10 psnr {:?}", run.psnr_y); + assert!(run.psnr_y[0] > 35.0); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, 10, "main10"); +} + +#[test] +fn qp_extremes() { + let (w, h) = (64, 64); + for qp in [0u8, 51] { + let pics: Vec = (0..2).map(|i| synth(w, h, 8, i, i)).collect(); + let run = encode(cfg(w as u32, h as u32, qp, 30, Profile::Main), &pics); + check_filmcraft(&run, w, h); + check_ffmpeg(&run, w, h, 8, &format!("qp{qp}")); + } +} + +#[test] +fn bitrate_mode_hits_target() { + let (w, h) = (192, 128); + let pics: Vec = (0..30).map(|i| synth(w, h, 8, i, i / 2)).collect(); + let mut c = EncoderConfig::new(w as u32, h as u32, 30, 1); + c.rate = RateControl::Bitrate { kbps: 400 }; + c.keyint = 15; + let run = encode(c, &pics); + let bits: usize = run.packets.iter().map(|p| p.len() * 8).sum(); + let kbps = bits as f64 / (pics.len() as f64 / 30.0) / 1000.0; + eprintln!("bitrate mode: {kbps:.1} kbit/s for a 400 kbit/s target"); + assert!(kbps > 400.0 / 2.0 && kbps < 400.0 * 2.0, "{kbps}"); + check_filmcraft(&run, w, h); +} + +#[test] +fn hvcc_layout() { + let enc = Encoder::new(cfg(64, 48, 28, 30, Profile::Main10)).unwrap(); + let h = enc.hvcc(); + assert_eq!(h[0], 1); + assert_eq!(h[1] & 31, 2); // Main 10 + assert_eq!(h[21] & 3, 3); // lengthSizeMinusOne + assert_eq!(h[22], 3); // arrays + let sets = enc.parameter_sets(); + assert_eq!(sets[0][0] >> 1, 32); + assert_eq!(sets[1][0] >> 1, 33); + assert_eq!(sets[2][0] >> 1, 34); +} + +/// Speed / size / quality report on ffmpeg-generated 1080p test content: +/// `cargo test -p effectcraft-hevcenc --test roundtrip bench_1080p -- --ignored --nocapture` +/// (`HEVCENC_BENCH_SRC` selects another lavfi source). +#[test] +#[ignore] +fn bench_1080p() { + if !ffmpeg_available() { + return; + } + let (w, h, n) = (1920usize, 1080usize, 8usize); + let src = std::env::var("HEVCENC_BENCH_SRC").unwrap_or_else(|_| "testsrc2=size=1920x1080:rate=30".into()); + let out = std::env::temp_dir().join(format!("hevcenc-bench-{}.yuv", std::process::id())); + let st = Command::new("ffmpeg") + .args(["-v", "error", "-y", "-f", "lavfi", "-i", &src, "-frames:v", &n.to_string(), "-pix_fmt", "yuv420p", "-f", "rawvideo"]) + .arg(&out) + .status() + .unwrap(); + assert!(st.success()); + let raw = std::fs::read(&out).unwrap(); + let _ = std::fs::remove_file(&out); + let fsz = w * h * 3 / 2; + let to16 = |s: &[u8]| s.iter().map(|&v| v as u16).collect::>(); + let pics: Vec = (0..n) + .map(|i| { + let f = &raw[i * fsz..(i + 1) * fsz]; + Pic { w, y: to16(&f[..w * h]), u: to16(&f[w * h..w * h * 5 / 4]), v: to16(&f[w * h * 5 / 4..]) } + }) + .collect(); + for qp in [22u8, 32] { + let mut enc = Encoder::new(cfg(w as u32, h as u32, qp, 30, Profile::Main)).unwrap(); + for (i, p) in pics.iter().enumerate() { + let t = std::time::Instant::now(); + let pkt = enc.encode(&p.frame()); + let dt = t.elapsed(); + let rec = enc.last_reconstruction().unwrap(); + eprintln!( + "QP {qp} frame {i} ({}): {:7} bytes, {:6.1} ms, PSNR Y {:.2} U {:.2} V {:.2}", + if pkt.keyframe { "I" } else { "P" }, + pkt.data.len(), + dt.as_secs_f64() * 1000.0, + psnr(&p.y, &rec[0], 8), + psnr(&p.u, &rec[1], 8), + psnr(&p.v, &rec[2], 8) + ); + } + } +} diff --git a/crates/opusenc/Cargo.toml b/crates/opusenc/Cargo.toml index 90b0fd02..489c648e 100644 --- a/crates/opusenc/Cargo.toml +++ b/crates/opusenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "effectcraft-opusenc" -description = "Clean-room pure-Rust Opus encoder (RFC 6716 CELT-only, fullband 48 kHz, 20 ms; RFC 7845 OpusHead) for WebM export audio" +description = "Clean-room pure-Rust Opus encoder (RFC 6716 SILK NB/MB/WB, hybrid SWB/FB and CELT FB modes, 48 kHz, 20 ms; RFC 7845 OpusHead) for WebM export audio" version.workspace = true edition.workspace = true license.workspace = true @@ -10,3 +10,6 @@ rust-version.workspace = true workspace = true [dependencies] + +[dev-dependencies] +filmcraft-opus = { workspace = true } diff --git a/crates/opusenc/README.md b/crates/opusenc/README.md index 0c76d49f..999d8a7e 100644 --- a/crates/opusenc/README.md +++ b/crates/opusenc/README.md @@ -1,24 +1,119 @@ # effectcraft-opusenc Clean-room, pure-Rust Opus encoder for EffectCraft's WebM export audio (layer L0, `std` only, -no `unsafe`). +no `unsafe`, builds for `wasm32-unknown-unknown`). -- **CELT-only** packets (TOC configuration 31: fullband 20 kHz, 20 ms, one frame per packet), - 48 kHz input, mono or stereo (stereo coded as dual L/R stereo), constant packet size from the - requested bitrate; all-zero frames become 3-byte silence packets. -- Forward MDCT with the low-overlap window, band energies with inter/intra coarse prediction - (cheaper of the two per frame), fine and final energy bits, the decoder's exact bit allocation, - spreading and recursive band splitting, greedy PVQ search and codeword indexing. -- Not implemented: SILK/hybrid modes, transient (short-block) frames, TF changes, pitch - post-filter, intensity stereo, dynamic allocation boosts, VBR rate control. -- `OpusEncoder::pre_skip()` is 120 samples (the MDCT overlap); `opus_head()` returns the RFC 7845 - identification header. +All three Opus modes, 20 ms packets (one frame per packet), 48 kHz float input, mono or stereo: + +- **CELT** (fullband): MDCT transform coding, constant packet size. +- **SILK** (narrowband 8 kHz, mediumband 12 kHz, wideband 16 kHz internal rate): linear + prediction with long-term (pitch) prediction; packet size varies around the target bitrate. +- **Hybrid** (super-wideband, fullband): SILK wideband codes 0–8 kHz and CELT codes bands 17 and + up (8 kHz and above) in the same range-coded frame; constant packet size. ```rust -let mut enc = effectcraft_opusenc::OpusEncoder::new(2, 128_000); -let packet = enc.encode_float(&vec![0.0; effectcraft_opusenc::OpusEncoder::FRAME_SIZE * 2]); +use effectcraft_opusenc::{Application, OpusEncoder}; +let mut enc = OpusEncoder::with_application(2, 48_000, Application::Voip); +let packet = enc.encode_float(&vec![0.0; OpusEncoder::FRAME_SIZE * 2]); +// enc.mode(), enc.bandwidth(), enc.pre_skip(), enc.opus_head() ... ``` +`OpusEncoder::new(channels, bitrate)` is `with_application(.., Application::Audio)`. +`OpusEncoder::with_mode(channels, bitrate, mode, bandwidth)` forces a mode/bandwidth pair +(SILK: NB/MB/WB; hybrid: SWB/FB; CELT: FB). + +## Mode table + +Chosen once per stream (constant-bitrate export, no in-stream switching) from the bitrate per +channel (bitrate / channels; the total is clamped to 6 kb/s per channel … 510 kb/s): + +| per-channel bitrate | `Application::Voip` | `Application::Audio` (default) | +|---------------------|-------------------------|--------------------------------| +| < 10 kb/s | SILK narrowband (8 kHz) | SILK wideband | +| 10–12 kb/s | SILK mediumband (12 kHz)| SILK wideband | +| 12–16 kb/s | SILK wideband (16 kHz) | SILK wideband | +| 16–18 kb/s | SILK wideband | hybrid fullband | +| 18–28 kb/s | hybrid super-wideband | hybrid fullband | +| 28–36 kb/s | hybrid fullband | hybrid fullband | +| 36–40 kb/s | hybrid fullband | CELT fullband | +| ≥ 40 kb/s | CELT fullband | CELT fullband | + +TOC configurations (RFC 6716 §3.1, Table 2): SILK NB/MB/WB 20 ms = 1/5/9, hybrid SWB/FB 20 ms += 13/15, CELT FB 20 ms = 31; the stereo bit is set for two channels; frame-count code 0. + +`pre_skip()` is 120 samples in every mode: the CELT MDCT overlap, and for SILK/hybrid the +decimator delay (83 samples at 48 kHz, 88 for narrowband) plus the decoder's SILK delay (one +internal-rate sample and the resampler delay of RFC 6716 Table 54). The SILK and CELT halves of a +hybrid frame are therefore aligned without extra buffering. + +## What is implemented + +**SILK** (RFC 6716 §4.2, encoder direction; own analysis and search design): + +- 48 kHz → 8/12/16 kHz decimation with a linear-phase Kaiser-windowed-sinc FIR. +- Per frame: energy VAD (inactive / unvoiced / voiced), open-loop pitch search (normalised + correlation with octave checks, joint lag + contour codebook choice, §4.2.7.6.1), windowed + autocorrelation LPC (Levinson, lag window, bandwidth expansion), LPC → NLSF by root search, + NLSF quantisation (stage-1 preselection by weighted distance, backwards-predictive stage-2 + scalar quantiser reproducing the decoder's dequantisation exactly, rate-distortion choice), + LTP codebook search over the three periodicity codebooks, independent + delta gain coding. +- Analysis-by-synthesis excitation: every sample is chosen by running the decoder's exact + fixed-point reconstruction (LTP from the decoder's own past output, LPC synthesis with the Q12 + coefficients of the *dequantised* NLSFs, gain rescaling between subframes, quantisation + offsets, the LCG sign sequence), so encoder and decoder never drift. A noise-feedback loop + shapes the coding noise by `1/A(z/γ)` (γ 0.75–0.9, coarser quantisation → more shaping), and a + small rate penalty biases pulses towards zero. +- Excitation coding: rate level, per-block pulse counts with LSB escapes, shell coding, LSBs and + signs (§4.2.7.8). +- Rate control: per-frame search over a gain offset (integer bracket, then bisection) to the + frame budget; SILK-only streams carry a bit reservoir across frames (packet sizes vary). +- Stereo: mid/side with the decoder's stereo prediction (§4.2.7.1, §4.2.8; ridge-regularised + least-squares weights, quantised to the decoder grid, interpolation mirrored), mid-only frames + when the predicted side is negligible, side gains floored at the mid gains. +- Not implemented: LBRR (in-band FEC), NLSF interpolation, 10/40/60 ms frames, DTX. + +**Hybrid** (§4.3 with start band 17): the SILK frame is written first, then the redundancy flag +(always 0) when there is room, then a CELT frame coding bands 17–18 (SWB) or 17–20 (FB) in the +same range coder with the remaining packet bytes (no silence flag or post-filter in hybrid +frames, decoder band-energy reset for uncoded bands mirrored). SILK gets 72 % (SWB) / 62 % (FB) +of the per-channel bitrate, at most the bitrate minus 6 kb/s, and never more than leaves CELT a +fifth of the packet (at least 80 bits). + +**CELT** (unchanged): forward MDCT with the low-overlap window, band energies with inter/intra +coarse prediction, fine and final energy bits, the decoder's exact bit allocation, spreading, +recursive band splitting, greedy PVQ search. Not implemented: transient frames, TF changes, +pitch post-filter, intensity stereo, dynamic allocation, VBR. All-zero CELT frames become 3-byte +silence packets. + +## Measured (tests in `tests/`, FilmCraft decoder) + +Synthetic speech (glottal pulses 100–200 Hz through three moving formants, syllable envelopes, +pauses) and music (three-note harmonic chords); waveform SNR against the input band-limited to +the mode's bandwidth (full band for hybrid/CELT), residual delay 0 (±1 for narrowband) after the +pre-skip: + +| mode / bandwidth | bitrate target → actual | speech SNR | music SNR | +|--------------------|-------------------------|------------|-----------| +| SILK NB | 8 → 7.5 kb/s | 15.9 dB | 6.9 dB (9 kb/s) | +| SILK MB | 11 → 10.1 kb/s | 21.8 dB | 7.9 dB | +| SILK WB | 12 → 10.9 kb/s | 21.5 dB | 8.1 dB | +| SILK WB | 16 → 14.3 kb/s | 28.1 dB | 12.1 dB | +| SILK WB | 24 → 21.1 kb/s | 37.0 dB | 19.7 dB | +| hybrid SWB | 24 kb/s (CBR) | 28.7 dB | 13.0 dB | +| hybrid FB | 32 kb/s (CBR) | 32.1 dB | 15.6 dB | +| CELT FB | 12 kb/s (CBR) | 13.0 dB | — | + +At 12 kb/s mono, SILK wideband beats the CELT path on speech by ~8 dB (21.0 vs 13.0 dB +full-band SNR). SILK bitrates land within −13 % … 0 % of the target (speech spends little in +pauses), hybrid is exact. Stereo SILK WB at 32 kb/s: 41 dB dual-mono, 40/37 dB panned, 22/19 dB +for two independent speech signals. + +ffmpeg (external oracle, Ogg Opus written by the test) decodes every mode with both its native +`opus` decoder and `libopus` without errors, honouring pre-skip and end trimming, with the same +SNRs (libopus residual delay 0, ffmpeg's native decoder 0–4 samples). The only message tolerated +is ffmpeg's raw-PCM muxer timestamp complaint that its native decoder also prints for +narrowband files encoded by libopus itself. + ## Specifications - RFC 6716, *Definition of the Opus Audio Codec* (September 2012), with the RFC 8251 updates @@ -26,8 +121,16 @@ let packet = enc.encode_float(&vec![0.0; effectcraft_opusenc::OpusEncoder::FRAME - RFC 7845, *Ogg Encapsulation for the Opus Audio Codec* (April 2016), §5.1 `OpusHead` and §4.2 pre-skip. -Implemented from the RFC text. Constant tables (band edges, `eMeans`, the coarse-energy Laplace -model, the allocation table, ICDFs) and the decoder-side allocation arithmetic are mirrored from -FilmCraft's first-party, spec-derived `filmcraft-opus` decoder (MIT OR Apache-2.0), which is also -the round-trip oracle in `crates/export/tests/opus_roundtrip.rs` (ffmpeg is a second, external -oracle when installed). No third-party encoder or decoder source was consulted. +## Provenance + +Implemented from the RFC text. Constant tables (CELT band edges, `eMeans`, the coarse-energy +Laplace model, the allocation table, ICDFs; all SILK probability tables and codebooks: NLSF +stage-1/2 codebooks and prediction weights, gain, pitch, contour, LTP and pulse ICDFs, stereo +weights) and the decoder-side normative arithmetic the encoder must reproduce bit-exactly (CELT +allocation; SILK NLSF dequantisation and stabilisation, NLSF → LPC conversion, `log2lin`, the +fixed-point division/inverse helpers and the LPC analysis filter) are mirrored from FilmCraft's +first-party, spec-derived `filmcraft-opus` decoder (MIT OR Apache-2.0), which is also the +round-trip oracle in `tests/` and `crates/export/tests/opus_roundtrip.rs` (ffmpeg is a second, +external oracle when installed). All encoder-side analysis and search (pitch, LPC, NLSF and +LTP search, noise feedback, rate control, stereo prediction, the decimator) is our own design. +No third-party encoder or decoder source (libopus or otherwise) was consulted. diff --git a/crates/opusenc/src/lib.rs b/crates/opusenc/src/lib.rs index 1e7a2cec..04963abf 100644 --- a/crates/opusenc/src/lib.rs +++ b/crates/opusenc/src/lib.rs @@ -1,18 +1,26 @@ -//! Clean-room pure-Rust Opus encoder (RFC 6716 CELT-only mode; RFC 7845 `OpusHead`). +//! Clean-room pure-Rust Opus encoder (RFC 6716 SILK, hybrid and CELT modes; RFC 7845 `OpusHead`). //! -//! Produces fullband (20 kHz) 48 kHz Opus packets of 20 ms, mono or stereo, at a constant -//! packet size derived from the requested bitrate. Each packet is a single CELT frame (TOC -//! configuration 31, frame-count code 0). Stereo is coded as dual (independent L/R) stereo. +//! Produces 20 ms Opus packets from 48 kHz float input, mono or stereo. The coding mode and +//! audio bandwidth are chosen once per stream from the per-channel bitrate and the +//! [`Application`] (see the crate README for the table): +//! +//! - **SILK** (narrowband/mediumband/wideband): linear-predictive speech coding at an internal +//! rate of 8/12/16 kHz; packet sizes vary around the target bitrate. +//! - **Hybrid** (super-wideband/fullband): SILK wideband codes 0–8 kHz and CELT codes the bands +//! above in the same range-coded frame; constant packet size. +//! - **CELT** (fullband): MDCT transform coding; constant packet size. //! //! Layer L0: no dependencies beyond `std`; no `unsafe`; builds for `wasm32-unknown-unknown`. //! //! ``` -//! use effectcraft_opusenc::OpusEncoder; +//! use effectcraft_opusenc::{Application, Mode, OpusEncoder}; //! let mut enc = OpusEncoder::new(2, 128_000); //! let pcm = vec![0.0f32; OpusEncoder::FRAME_SIZE * 2]; //! let packet = enc.encode_float(&pcm); //! assert_eq!(packet[0] >> 3, 31); // CELT-only, fullband, 20 ms //! assert_eq!(enc.opus_head().len(), 19); +//! let voip = OpusEncoder::with_application(1, 16_000, Application::Voip); +//! assert_eq!(voip.mode(), Mode::Silk); //! ``` // The bit-allocation code mirrors the normative integer arithmetic; keep C-like shift expressions. @@ -23,6 +31,8 @@ mod energy; mod mdct; mod range; mod rate; +mod resample; +mod silk; mod tables; use std::sync::OnceLock; @@ -31,7 +41,9 @@ use bands::{SPREAD_NORMAL, quant_all_bands}; use energy::{quant_coarse_energy, quant_energy_finalise, quant_fine_energy}; use mdct::Mdct; use range::{BITRES, RangeEncoder}; -use rate::{Mode, compute_allocation}; +use rate::{Mode as CeltMode, compute_allocation}; +use resample::Downsampler; +use silk::SilkEncoder; use tables::*; /// Samples per channel in one 20 ms frame at 48 kHz. @@ -40,18 +52,91 @@ const N: usize = 960; const LM: usize = 3; /// Smallest CELT payload the encoder produces (bytes). const MIN_FRAME_BYTES: usize = 16; -/// Largest CELT payload allowed in one frame (RFC 6716 §3.4, R2). +/// Largest payload allowed in one frame (RFC 6716 §3.4, R2). const MAX_FRAME_BYTES: usize = 1275; +/// First CELT band coded in hybrid frames (8 kHz). +const HYBRID_START_BAND: usize = 17; -fn mode() -> &'static Mode { - static MODE: OnceLock = OnceLock::new(); - MODE.get_or_init(Mode::new) +fn celt_mode() -> &'static CeltMode { + static MODE: OnceLock = OnceLock::new(); + MODE.get_or_init(CeltMode::new) +} + +/// Intended use of the stream; selects the mode/bandwidth table. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub enum Application { + /// General audio (music, mixed content): CELT unless the bitrate is low. + #[default] + Audio, + /// Speech: SILK and hybrid over a wider bitrate range, narrower bandwidths at low rates. + Voip, +} + +/// Opus coding mode (RFC 6716 §2). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Mode { + /// MDCT transform coding only. + Celt, + /// Linear-prediction coding only. + Silk, + /// SILK below 8 kHz plus CELT above. + Hybrid, +} + +/// Coded audio bandwidth (RFC 6716 §2, Table 1). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Bandwidth { + /// 4 kHz (8 kHz sampling). + Narrowband, + /// 6 kHz (12 kHz sampling). + Mediumband, + /// 8 kHz (16 kHz sampling). + Wideband, + /// 12 kHz (24 kHz sampling). + SuperWideband, + /// 20 kHz (48 kHz sampling). + Fullband, +} + +/// Mode and bandwidth for a per-channel bitrate (bits per second per channel). +fn choose_mode(app: Application, per_channel: u32) -> (Mode, Bandwidth) { + match app { + Application::Voip => match per_channel { + 0..10_000 => (Mode::Silk, Bandwidth::Narrowband), + 10_000..12_000 => (Mode::Silk, Bandwidth::Mediumband), + 12_000..18_000 => (Mode::Silk, Bandwidth::Wideband), + 18_000..28_000 => (Mode::Hybrid, Bandwidth::SuperWideband), + 28_000..40_000 => (Mode::Hybrid, Bandwidth::Fullband), + _ => (Mode::Celt, Bandwidth::Fullband), + }, + Application::Audio => match per_channel { + 0..16_000 => (Mode::Silk, Bandwidth::Wideband), + 16_000..36_000 => (Mode::Hybrid, Bandwidth::Fullband), + _ => (Mode::Celt, Bandwidth::Fullband), + }, + } +} + +/// SILK side of a SILK-only or hybrid stream. +struct SilkPath { + enc: SilkEncoder, + down: Vec, + /// Bits per frame aimed at (excluding the TOC byte in SILK-only mode). + target_bits: f64, + /// Bits saved (positive) or overspent by previous frames (SILK-only mode). + reservoir: f64, } /// Opus encoder for one mono or stereo stream at 48 kHz. pub struct OpusEncoder { channels: usize, - /// CELT payload bytes per packet (the packet is one byte longer: the TOC). + application: Application, + mode: Mode, + bandwidth: Bandwidth, + /// Requested bitrate (clamped), bits per second. + bitrate_bps: u32, + /// Payload bytes per packet in CELT and hybrid modes (the packet is one byte longer: the + /// TOC). frame_bytes: usize, mdct: Mdct, /// Last input sample of each channel (pre-emphasis filter memory, ×32768). @@ -62,28 +147,88 @@ pub struct OpusEncoder { old_band_e: [f32; 2 * NB_EBANDS], block: Vec, freq: Vec, + silk: Option, } impl OpusEncoder { /// Samples per channel per packet (20 ms at 48 kHz). pub const FRAME_SIZE: usize = N; - /// Creates an encoder for `channels` (1 or 2) channels of 48 kHz audio at `bitrate_bps` - /// (total for all channels; packets are constant-size, clamped to 6.8–510 kb/s). + /// Creates a general-audio encoder ([`Application::Audio`]) for `channels` (1 or 2) + /// channels of 48 kHz audio at `bitrate_bps` (total for all channels). pub fn new(channels: u8, bitrate_bps: u32) -> Self { + Self::with_application(channels, bitrate_bps, Application::Audio) + } + + /// Creates an encoder for `channels` (1 or 2) channels of 48 kHz audio at `bitrate_bps` + /// (total for all channels; clamped to 6 kb/s per channel – 510 kb/s), choosing the mode + /// and bandwidth for `application`. + pub fn with_application(channels: u8, bitrate_bps: u32, application: Application) -> Self { + let c = channels.max(1) as u32; + let (mode, bandwidth) = choose_mode(application, bitrate_bps.clamp(6_000 * c, 510_000) / c); + Self::build(channels, bitrate_bps, application, mode, bandwidth) + } + + /// Creates an encoder with an explicit mode and bandwidth instead of the application's + /// table. Valid combinations: SILK with narrowband, mediumband or wideband; hybrid with + /// super-wideband or fullband; CELT with fullband. + /// + /// # Panics + /// On any other combination, or when `channels` is not 1 or 2. + pub fn with_mode(channels: u8, bitrate_bps: u32, mode: Mode, bandwidth: Bandwidth) -> Self { + let ok = match mode { + Mode::Silk => bandwidth <= Bandwidth::Wideband, + Mode::Hybrid => bandwidth >= Bandwidth::SuperWideband, + Mode::Celt => bandwidth == Bandwidth::Fullband, + }; + assert!(ok, "unsupported mode/bandwidth combination {mode:?}/{bandwidth:?}"); + Self::build(channels, bitrate_bps, Application::Audio, mode, bandwidth) + } + + fn build(channels: u8, bitrate_bps: u32, application: Application, mode: Mode, bandwidth: Bandwidth) -> Self { assert!(channels == 1 || channels == 2, "Opus family-0 streams have 1 or 2 channels"); let c = channels as usize; + let bitrate_bps = bitrate_bps.clamp(6_000 * c as u32, 510_000); let packet_bytes = (bitrate_bps as usize).div_ceil(400); let frame_bytes = packet_bytes.saturating_sub(1).clamp(MIN_FRAME_BYTES, MAX_FRAME_BYTES); + let silk = match mode { + Mode::Celt => None, + Mode::Silk | Mode::Hybrid => { + let fs_khz = match bandwidth { + Bandwidth::Narrowband => 8, + Bandwidth::Mediumband => 12, + _ => 16, + }; + let target_bits = if mode == Mode::Silk { + bitrate_bps as f64 / 50.0 - 8.0 + } else { + // SILK gets most of a hybrid packet; CELT codes 8 kHz and up with the rest. + let per = (bitrate_bps / c as u32) as f64; + let share = if bandwidth == Bandwidth::SuperWideband { 0.72 } else { 0.62 }; + c as f64 * (per * share).min(per - 6_000.0).max(per * 0.5) / 50.0 + }; + Some(SilkPath { + enc: SilkEncoder::new(fs_khz, c), + down: (0..c).map(|_| Downsampler::new(fs_khz as u32 * 1000)).collect(), + target_bits, + reservoir: 0.0, + }) + } + }; OpusEncoder { channels: c, + application, + mode, + bandwidth, + bitrate_bps, frame_bytes, mdct: Mdct::new(N), preemph_mem: [0.0; 2], hist: vec![0.0; c * OVERLAP], - old_band_e: [-28.0; 2 * NB_EBANDS], + old_band_e: [0.0; 2 * NB_EBANDS], block: vec![0.0; N + OVERLAP], freq: vec![0.0; c * N], + silk, } } @@ -92,15 +237,43 @@ impl OpusEncoder { self.channels as u8 } - /// Actual bitrate in bits per second (packet size × 50 packets per second). - pub fn bitrate(&self) -> u32 { - ((self.frame_bytes + 1) * 8 * 50) as u32 + /// The application the encoder was created for. + pub fn application(&self) -> Application { + self.application } - /// Samples at 48 kHz the decoder must discard from the start of the stream (the MDCT - /// overlap); this is the `OpusHead` pre-skip. + /// Coding mode of every packet of the stream. + pub fn mode(&self) -> Mode { + self.mode + } + + /// Coded audio bandwidth of every packet of the stream. + pub fn bandwidth(&self) -> Bandwidth { + self.bandwidth + } + + /// Bitrate in bits per second: the constant packet size × 50 packets per second in CELT + /// and hybrid modes, the (average) target in SILK mode. + pub fn bitrate(&self) -> u32 { + match self.mode { + Mode::Silk => self.bitrate_bps, + _ => ((self.frame_bytes + 1) * 8 * 50) as u32, + } + } + + /// Samples at 48 kHz the decoder must discard from the start of the stream (the `OpusHead` + /// pre-skip): the encoder's total delay. CELT: the 120-sample MDCT overlap. SILK/hybrid: + /// the decimator (83 samples, 88 for narrowband) plus the decoder's SILK delay (one + /// internal-rate sample and the resampler delay of RFC 6716 Table 54), which also rounds + /// to 120 samples. pub fn pre_skip(&self) -> u16 { - OVERLAP as u16 + let (fs, table54_ms) = match (self.mode, self.bandwidth) { + (Mode::Celt, _) => return OVERLAP as u16, + (_, Bandwidth::Narrowband) => (8_000, 0.538), + (_, Bandwidth::Mediumband) => (12_000, 0.692), + _ => (16_000, 0.706), + }; + (resample::delay(fs) as f64 + 48_000.0 / fs as f64 + table54_ms * 48.0).round() as u16 } /// The 19-byte `OpusHead` identification header (RFC 7845 §5.1): version 1, channel count, @@ -117,14 +290,84 @@ impl OpusEncoder { h } + /// TOC byte (RFC 6716 §3.1, Table 2) of a 20 ms single-frame packet. + fn toc(&self) -> u8 { + let config: u8 = match (self.mode, self.bandwidth) { + (Mode::Silk, Bandwidth::Narrowband) => 1, + (Mode::Silk, Bandwidth::Mediumband) => 5, + (Mode::Silk, _) => 9, + (Mode::Hybrid, Bandwidth::SuperWideband) => 13, + (Mode::Hybrid, _) => 15, + (Mode::Celt, _) => 31, + }; + (config << 3) | if self.channels == 2 { 4 } else { 0 } + } + /// Encodes one 20 ms frame of interleaved f32 samples in [-1, 1] (exactly - /// `FRAME_SIZE * channels` values) and returns one Opus packet (TOC byte + CELT frame). + /// `FRAME_SIZE * channels` values) and returns one Opus packet. pub fn encode_float(&mut self, pcm: &[f32]) -> Vec { let c = self.channels; assert_eq!(pcm.len(), N * c, "encode_float takes exactly FRAME_SIZE samples per channel"); + let mut packet = vec![self.toc()]; + match self.mode { + Mode::Celt => { + let (log_e, silence) = self.celt_analysis(pcm); + let len = self.frame_bytes; + packet.extend_from_slice(&self.encode_celt(RangeEncoder::new(), len, 0, NB_EBANDS, &log_e, silence)); + } + Mode::Silk => { + let x = self.silk_input(pcm); + let sp = self.silk.as_mut().expect("SILK state"); + let target = sp.target_bits + 0.25 * sp.reservoir; + let mut enc = RangeEncoder::new(); + sp.enc.encode(&x, target.max(24.0), (MAX_FRAME_BYTES * 8) as i32, &mut enc); + let bytes = (enc.tell() as usize).div_ceil(8).max(2); + let lim = 4.0 * sp.target_bits; + sp.reservoir = (sp.reservoir + sp.target_bits - (bytes * 8) as f64).clamp(-lim, lim); + packet.extend_from_slice(&enc.done(bytes)); + } + Mode::Hybrid => { + let x = self.silk_input(pcm); + let len = self.frame_bytes; + let total = (len * 8) as i32; + let sp = self.silk.as_mut().expect("SILK state"); + let celt_min = (total / 5).max(80); + let mut enc = RangeEncoder::new(); + sp.enc.encode(&x, sp.target_bits, total - celt_min, &mut enc); + // No redundancy (RFC 6716 §4.5.1); the flag is present when there is room. + if enc.tell() + 17 + 20 <= total { + enc.bit_logp(false, 12); + } + let (log_e, _) = self.celt_analysis(pcm); + let end = if self.bandwidth == Bandwidth::SuperWideband { 19 } else { NB_EBANDS }; + packet.extend_from_slice(&self.encode_celt(enc, len, HYBRID_START_BAND, end, &log_e, false)); + } + } + packet + } - // Pre-emphasis, the MDCT and the band energies. - let mut band_e = [0f32; 2 * NB_EBANDS]; + /// Decimates the input to the SILK internal rate (16-bit scale), one vector per channel. + fn silk_input(&mut self, pcm: &[f32]) -> Vec> { + let c = self.channels; + let sp = self.silk.as_mut().expect("SILK state"); + let mut out = Vec::with_capacity(c); + let mut tmp = vec![0f32; N]; + for ch in 0..c { + for (j, t) in tmp.iter_mut().enumerate() { + let s = pcm[j * c + ch]; + *t = if s.is_finite() { s.clamp(-1.0, 1.0) * 32768.0 } else { 0.0 }; + } + let mut y = Vec::with_capacity(N / 3); + sp.down[ch].process(&tmp, &mut y); + out.push(y); + } + out + } + + /// Pre-emphasis, the MDCT, band energies (log2, relative to `E_MEANS`) and normalised + /// bands; returns the energies and whether the frame is digital silence. + fn celt_analysis(&mut self, pcm: &[f32]) -> ([f32; 2 * NB_EBANDS], bool) { + let c = self.channels; let mut log_e = [0f32; 2 * NB_EBANDS]; let mut peak = 0f32; for ch in 0..c { @@ -146,7 +389,6 @@ impl OpusEncoder { let lo = (EBANDS[i] as usize) << LM; let hi = (EBANDS[i + 1] as usize) << LM; let e = (1e-27f32 + freq[lo..hi].iter().map(|v| v * v).sum::()).sqrt(); - band_e[ch * NB_EBANDS + i] = e; log_e[ch * NB_EBANDS + i] = e.log2() - E_MEANS[i]; let g = 1.0 / e; for v in freq[lo..hi].iter_mut() { @@ -154,35 +396,33 @@ impl OpusEncoder { } } } - let silence = peak < 1e-4; - let toc = (31u8 << 3) | if c == 2 { 4 } else { 0 }; - let mut packet = vec![toc]; - packet.extend_from_slice(&self.encode_celt(&log_e, silence)); - packet + (log_e, peak < 1e-4) } - /// Codes one CELT frame (RFC 6716 §4.3, in bitstream order). - fn encode_celt(&mut self, log_e: &[f32; 2 * NB_EBANDS], silence: bool) -> Vec { - let m = mode(); + /// Codes one CELT frame (RFC 6716 §4.3, in bitstream order) of `len` bytes into `enc` + /// (which already holds the SILK frame in hybrid mode), coding bands `start..end`, and + /// finalises the frame. + #[allow(clippy::too_many_arguments)] + fn encode_celt(&mut self, mut enc: RangeEncoder, len: usize, start: usize, end: usize, log_e: &[f32; 2 * NB_EBANDS], silence: bool) -> Vec { + let m = celt_mode(); let c = self.channels; - let (start, end) = (0usize, NB_EBANDS); - let mut enc = RangeEncoder::new(); if c == 1 { for i in 0..NB_EBANDS { self.old_band_e[i] = self.old_band_e[i].max(self.old_band_e[NB_EBANDS + i]); } } - if silence { + if silence && start == 0 { // The silence flag; the decoder treats every remaining bit as consumed. enc.bit_logp(true, 15); self.old_band_e = [-28.0; 2 * NB_EBANDS]; return enc.done(2); } - let len = self.frame_bytes; let total_bits_raw = len as i32 * 8; - enc.bit_logp(false, 15); - // No pitch post-filter. - if enc.tell() + 16 <= total_bits_raw { + if enc.tell() == 1 { + enc.bit_logp(false, 15); + } + // No pitch post-filter (only signalled in CELT-only frames). + if start == 0 && enc.tell() + 16 <= total_bits_raw { enc.bit_logp(false, 1); } // Long blocks only. @@ -270,6 +510,12 @@ impl OpusEncoder { let (a, b) = self.old_band_e.split_at_mut(NB_EBANDS); b.copy_from_slice(a); } + // The decoder forgets the energies of bands it did not code. + for ch in 0..2 { + for i in (0..start).chain(end..NB_EBANDS) { + self.old_band_e[ch * NB_EBANDS + i] = 0.0; + } + } enc.done(len) } } diff --git a/crates/opusenc/src/resample.rs b/crates/opusenc/src/resample.rs new file mode 100644 index 00000000..9045cb86 --- /dev/null +++ b/crates/opusenc/src/resample.rs @@ -0,0 +1,131 @@ +//! 48 kHz → 8/12/16 kHz decimator feeding the SILK layer (own design). +//! +//! A linear-phase low-pass FIR (Kaiser-windowed sinc at 48 kHz) evaluated only at the retained +//! output phases. Its group delay ([`delay`]) is a whole number of 48 kHz samples, chosen so +//! that the SILK path (this filter + the decoder's internal-rate delay and resampler, RFC 6716 +//! Table 54) lands within a fraction of a sample of the CELT path's 120-sample MDCT overlap. + +/// Group delay of the decimator to `fs_out` in 48 kHz samples (the filter has `2 * delay + 1` +/// taps). +pub fn delay(fs_out: u32) -> usize { + if fs_out == 8_000 { 88 } else { 83 } +} + +/// Zeroth-order modified Bessel function of the first kind (power series). +fn bessel_i0(x: f64) -> f64 { + let mut sum = 1.0; + let mut term = 1.0; + let q = x * x / 4.0; + for k in 1..64 { + term *= q / (k * k) as f64; + sum += term; + if term < sum * 1e-17 { + break; + } + } + sum +} + +/// Windowed-sinc low-pass with cutoff `fc` (Hz at 48 kHz), unit DC gain. +fn design(fc: f64, beta: f64, delay: usize) -> Vec { + let wc = 2.0 * std::f64::consts::PI * fc / 48_000.0; + let i0b = bessel_i0(beta); + let mut h: Vec = (0..2 * delay + 1) + .map(|k| { + let m = k as f64 - delay as f64; + let sinc = if m == 0.0 { wc / std::f64::consts::PI } else { (wc * m).sin() / (std::f64::consts::PI * m) }; + let r = m / delay as f64; + sinc * bessel_i0(beta * (1.0 - r * r).max(0.0).sqrt()) / i0b + }) + .collect(); + let s: f64 = h.iter().sum(); + for v in h.iter_mut() { + *v /= s; + } + h.iter().map(|&v| v as f32).collect() +} + +/// Decimates one channel of 48 kHz audio by 3, 4 or 6. +#[derive(Clone)] +pub struct Downsampler { + factor: usize, + h: Vec, + taps: usize, + /// The last `taps - 1` input samples followed by the current frame. + buf: Vec, +} + +impl Downsampler { + /// `fs_out` is 8000, 12000 or 16000. + pub fn new(fs_out: u32) -> Self { + let factor = (48_000 / fs_out) as usize; + // Cutoff at 95 % of the output Nyquist frequency (pass band to ~88 %, stop band from ~110 %). + let fc = 0.95 * fs_out as f64 / 2.0; + let d = delay(fs_out); + let taps = 2 * d + 1; + Downsampler { factor, h: design(fc, 6.5, d), taps, buf: vec![0.0; taps - 1] } + } + + /// Decimates `input` (a multiple of the factor long), appending to `out`. Output sample `j` + /// is the filtered input at frame position `factor * j`, delayed by [`delay`]. + pub fn process(&mut self, input: &[f32], out: &mut Vec) { + debug_assert!(input.len().is_multiple_of(self.factor)); + let taps = self.taps; + self.buf.truncate(taps - 1); + self.buf.extend_from_slice(input); + for j in 0..input.len() / self.factor { + let t = taps - 1 + self.factor * j; + let win = &self.buf[t + 1 - taps..=t]; + // h is symmetric, so the window can be taken in forward order. + out.push(win.iter().zip(&self.h).map(|(a, b)| a * b).sum()); + } + let n = self.buf.len(); + self.buf.copy_within(n - (taps - 1).., 0); + self.buf.truncate(taps - 1); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tone(f: f64, n: usize) -> Vec { + (0..n).map(|i| (2.0 * std::f64::consts::PI * f * i as f64 / 48_000.0).sin() as f32).collect() + } + + fn rms(x: &[f32]) -> f64 { + (x.iter().map(|&v| (v as f64).powi(2)).sum::() / x.len() as f64).sqrt() + } + + #[test] + fn passes_band_and_rejects_aliases() { + for &(fs, pass, stop) in &[(16_000u32, 6_000.0, 8_600.0), (12_000, 4_500.0, 6_500.0), (8_000, 3_000.0, 4_500.0)] { + for &(f, want_pass) in &[(pass, true), (stop, false)] { + let mut d = Downsampler::new(fs); + let x = tone(f, 9600); + let mut y = Vec::new(); + for c in x.chunks(960) { + d.process(c, &mut y); + } + let r = rms(&y[y.len() / 2..]) * std::f64::consts::SQRT_2; + if want_pass { + assert!((r - 1.0).abs() < 0.02, "{fs}: {f} Hz gain {r}"); + } else { + assert!(r < 0.01, "{fs}: {f} Hz leaks {r}"); + } + } + } + } + + #[test] + fn delay_is_exact() { + let mut d = Downsampler::new(16_000); + let mut x = vec![0f32; 960]; + x[300] = 1.0; + let mut y = Vec::new(); + d.process(&x, &mut y); + // The impulse at 300 shows up centred at 48 kHz time 300 + 83 = 383 (output 127.67). + let peak = y.iter().enumerate().max_by(|a, b| a.1.total_cmp(b.1)).map(|(i, _)| i).unwrap_or(0); + assert!((peak as f64 - (300.0 + delay(16_000) as f64) / 3.0).abs() < 1.0, "peak at {peak}"); + } +} diff --git a/crates/opusenc/src/silk/analysis.rs b/crates/opusenc/src/silk/analysis.rs new file mode 100644 index 00000000..de510433 --- /dev/null +++ b/crates/opusenc/src/silk/analysis.rs @@ -0,0 +1,322 @@ +//! Encoder-side signal analysis for SILK (own design): windowed autocorrelation LPC, LPC → NLSF +//! conversion, open-loop pitch search and LTP codebook search. + +use super::tables::*; + +/// Short-term prediction coefficients `a` (prediction `x[n] ≈ Σ a[j] x[n-1-j]`) from the +/// windowed segment `x`, with a Gaussian lag window and white-noise correction. +pub fn lpc(x: &[f32], order: usize, fs_khz: usize) -> Vec { + let n = x.len(); + // Asymmetric window: sin² rise over the first half, flat, cos fall over the last 1/8. + let rise = n / 2; + let fall = n / 8; + let w = |i: usize| -> f64 { + if i < rise { + let s = (std::f64::consts::FRAC_PI_2 * (i as f64 + 0.5) / rise as f64).sin(); + s * s + } else if i >= n - fall { + (std::f64::consts::FRAC_PI_2 * (i + fall - n) as f64 / fall as f64 + 0.0).cos() + } else { + 1.0 + } + }; + let xw: Vec = x.iter().enumerate().map(|(i, &v)| v as f64 * w(i)).collect(); + let mut r = vec![0f64; order + 1]; + for (k, rk) in r.iter_mut().enumerate() { + *rk = xw[k..].iter().zip(&xw).map(|(a, b)| a * b).sum(); + } + if r[0] <= 1e-6 { + return vec![0.0; order]; + } + // White-noise correction (-40 dB) and a 50 Hz Gaussian lag window. + r[0] *= 1.0 + 1e-4; + let f0 = 2.0 * std::f64::consts::PI * 50.0 / (fs_khz as f64 * 1000.0); + for (k, rk) in r.iter_mut().enumerate().skip(1) { + *rk *= (-0.5 * (f0 * k as f64).powi(2)).exp(); + } + let mut a = levinson(&r, order); + // Mild bandwidth expansion keeps the NLSFs apart. + let mut g = 1.0; + for v in a.iter_mut() { + g *= 0.996; + *v *= g; + } + a +} + +/// Levinson-Durbin recursion on autocorrelation `r` (predictor convention as in [`lpc`]). +pub fn levinson(r: &[f64], order: usize) -> Vec { + let mut a = vec![0f64; order]; + let mut err = r[0]; + let mut tmp = vec![0f64; order]; + for i in 0..order { + let mut acc = r[i + 1]; + for j in 0..i { + acc -= a[j] * r[i - j]; + } + let k = (acc / err).clamp(-0.9999, 0.9999); + tmp[..i].copy_from_slice(&a[..i]); + for j in 0..i { + a[j] = tmp[j] - k * tmp[i - 1 - j]; + } + a[i] = k; + err *= 1.0 - k * k; + if err <= 0.0 { + break; + } + } + a +} + +/// Evaluates the symmetric polynomial `p` (length `d + 1`, `d` even) as the cosine series +/// `e^{jωd/2} P(e^{jω})`. +fn cos_series(p: &[f64], w: f64) -> f64 { + let h = (p.len() - 1) / 2; + let mut s = p[h]; + for k in 0..h { + s += 2.0 * p[k] * ((h - k) as f64 * w).cos(); + } + s +} + +fn roots(p: &[f64], out: &mut Vec) { + const GRID: usize = 1024; + let mut w0 = 0.0; + let mut f0 = cos_series(p, w0); + for g in 1..=GRID { + let w1 = std::f64::consts::PI * g as f64 / GRID as f64; + let f1 = cos_series(p, w1); + if f0 == 0.0 { + out.push(w0); + } else if f0 * f1 < 0.0 { + let (mut lo, mut hi, mut flo) = (w0, w1, f0); + for _ in 0..40 { + let mid = 0.5 * (lo + hi); + let fm = cos_series(p, mid); + if fm * flo <= 0.0 { + hi = mid; + } else { + lo = mid; + flo = fm; + } + } + out.push(0.5 * (lo + hi)); + } + w0 = w1; + f0 = f1; + } +} + +/// LPC → normalised line spectral frequencies in radians (ascending, `order` values), or `None` +/// when the root search fails (unstable or degenerate filter). +pub fn a2nlsf(a: &[f64]) -> Option> { + let d = a.len(); + let mut c = vec![0f64; d + 2]; + c[0] = 1.0; + for k in 0..d { + c[k + 1] = -a[k]; + } + let mut p = vec![0f64; d + 2]; + let mut q = vec![0f64; d + 2]; + for k in 0..d + 2 { + p[k] = c[k] + c[d + 1 - k]; + q[k] = c[k] - c[d + 1 - k]; + } + // Remove the trivial roots at z = -1 (P) and z = +1 (Q). + let mut p1 = vec![0f64; d + 1]; + let mut q1 = vec![0f64; d + 1]; + p1[0] = p[0]; + q1[0] = q[0]; + for k in 1..=d { + p1[k] = p[k] - p1[k - 1]; + q1[k] = q[k] + q1[k - 1]; + } + let mut r = Vec::with_capacity(d); + roots(&p1, &mut r); + roots(&q1, &mut r); + if r.len() != d { + return None; + } + r.sort_by(f64::total_cmp); + Some(r) +} + +/// Normalised cross-correlation of `x[n]` and `x[n - lag]` over `n` in `range`. +fn ncorr(x: &[f32], range: std::ops::Range, lag: usize) -> f64 { + let (mut xy, mut xx, mut yy) = (0f64, 0f64, 0f64); + for n in range { + let a = x[n] as f64; + let b = x[n - lag] as f64; + xy += a * b; + xx += a * a; + yy += b * b; + } + if xy <= 0.0 { 0.0 } else { xy / (xx * yy + 1e-9).sqrt() } +} + +pub struct Pitch { + pub lag_index: i32, + pub contour: usize, + pub lags: [i32; 4], + pub score: f64, +} + +/// Open-loop pitch search on `x` (history followed by the frame starting at `start`, `len` +/// samples, four subframes) at `fs_khz`. +pub fn pitch_search(x: &[f32], start: usize, len: usize, fs_khz: usize) -> Pitch { + let fs = fs_khz as i32; + let min_lag = 2 * fs; + let max_lag = 18 * fs - 1; + let sub = len / 4; + let nl = (max_lag - min_lag + 1) as usize; + // Whole-frame score per lag. + let full: Vec = (0..nl).map(|i| ncorr(x, start..start + len, min_lag as usize + i)).collect(); + let mut best = 0usize; + for i in 0..nl { + // Slight preference for shorter lags. + if full[i] * (1.0 - 0.0004 * i as f64) > full[best] * (1.0 - 0.0004 * best as f64) { + best = i; + } + } + let mut lag = min_lag + best as i32; + // Prefer a sub-multiple when it explains the signal almost as well (octave errors). + for div in [4, 3, 2] { + let cand = (lag as f64 / div as f64).round() as i32; + let mut cbest = (0.0, 0); + for c in cand - 1..=cand + 1 { + if c >= min_lag && c <= max_lag { + let s = full[(c - min_lag) as usize]; + if s > cbest.0 { + cbest = (s, c); + } + } + } + if cbest.1 != 0 && cbest.0 > 0.9 * full[(lag - min_lag) as usize] { + lag = cbest.1; + break; + } + } + let score = full[(lag - min_lag) as usize]; + // Contour: per-subframe lag offsets around the chosen lag. + let cb: &[[i32; 4]] = if fs_khz == 8 { &CB_NB_20 } else { &CB_WB_20 }; + let sub_score = |k: usize, l: i32| -> f64 { + let l = l.clamp(min_lag, 18 * fs); + ncorr(x, start + k * sub..start + (k + 1) * sub, l as usize) + }; + let mut best = (f64::MIN, lag, 0usize); + for l0 in (lag - 2).max(min_lag)..=(lag + 2).min(max_lag) { + for (ci, offs) in cb.iter().enumerate() { + let s: f64 = (0..4).map(|k| sub_score(k, l0 + offs[k])).sum::() - 0.002 * ci as f64; + if s > best.0 { + best = (s, l0, ci); + } + } + } + let (_, l0, ci) = best; + let mut lags = [0i32; 4]; + for k in 0..4 { + lags[k] = (l0 + cb[ci][k]).clamp(min_lag, 18 * fs); + } + Pitch { lag_index: l0 - min_lag, contour: ci, lags, score } +} + +/// Result of the LTP codebook search. +pub struct Ltp { + pub per_index: usize, + pub ltp_index: [usize; 4], + /// Residual energy after LTP prediction / before, per subframe. + pub err: [f64; 4], + pub energy: [f64; 4], +} + +fn icdf_bits(icdf: &[u8], s: usize) -> f64 { + let hi = if s == 0 { 256 } else { icdf[s - 1] as u32 }; + let p = (hi - icdf[s] as u32).max(1) as f64 / 256.0; + -p.log2() +} + +/// Searches the three LTP codebooks (RFC 6716 Tables 39–42) for the taps that best predict the +/// residual `r` (history + frame starting at `start`) from its past at the per-subframe `lags`. +pub fn ltp_search(r: &[f64], start: usize, sub: usize, lags: &[i32; 4]) -> Ltp { + let mut rr = [[0f64; 25]; 4]; + let mut cv = [[0f64; 5]; 4]; + let mut e = [0f64; 4]; + for k in 0..4 { + let s0 = start + k * sub; + let lag = lags[k] as usize; + for n in s0..s0 + sub { + let t = r[n]; + e[k] += t * t; + let base = n + 2 - lag; + for i in 0..5 { + let vi = r[base - i]; + cv[k][i] += t * vi; + for j in 0..5 { + rr[k][i * 5 + j] += vi * r[base - j]; + } + } + } + } + let books: [&[[i32; 5]]; 3] = [<P_TAPS_0, <P_TAPS_1, <P_TAPS_2]; + let mut best = (f64::MAX, 0usize, [0usize; 4], [0f64; 4]); + for (p, book) in books.iter().enumerate() { + let mut total = icdf_bits(<P_PERIODICITY, p) * 1e-3 * e.iter().sum::() / 4.0; + let mut idx = [0usize; 4]; + let mut errs = [0f64; 4]; + for k in 0..4 { + let mut bk = (f64::MAX, 0usize, 0f64); + for (i, taps) in book.iter().enumerate() { + let b: [f64; 5] = std::array::from_fn(|j| taps[j] as f64 / 128.0); + let mut err = e[k]; + for a in 0..5 { + err -= 2.0 * b[a] * cv[k][a]; + for c in 0..5 { + err += b[a] * b[c] * rr[k][a * 5 + c]; + } + } + let cost = err + icdf_bits(LTP_FILTER[p], i) * 1e-3 * e[k]; + if cost < bk.0 { + bk = (cost, i, err.max(0.0)); + } + } + total += bk.0; + idx[k] = bk.1; + errs[k] = bk.2; + } + if total < best.0 { + best = (total, p, idx, errs); + } + } + Ltp { per_index: best.1, ltp_index: best.2, err: best.3, energy: e } +} + +#[cfg(test)] +mod tests { + use super::super::lpc::nlsf2a; + use super::*; + + #[test] + fn nlsf_roundtrip_through_decoder_conversion() { + // A resonant 10th/16th-order predictor from a synthetic signal. + for &(order, fs) in &[(10usize, 8usize), (16, 16)] { + let mut st = [0f64; 4]; + let mut seed = 1u32; + let x: Vec = (0..400) + .map(|_| { + seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223); + let w = (seed >> 9) as f64 / (1u32 << 23) as f64 - 1.0; + let y = w + 1.6 * st[0] - 0.9 * st[1] + 0.3 * st[2] - 0.2 * st[3]; + st = [y, st[0], st[1], st[2]]; + (y * 1000.0) as f32 + }) + .collect(); + let a = lpc(&x, order, fs); + let nlsf = a2nlsf(&a).expect("roots"); + let q15: Vec = nlsf.iter().map(|w| (w / std::f64::consts::PI * 32768.0).round() as i32).collect(); + let a_q12 = nlsf2a(&q15); + for j in 0..order { + assert!((a_q12[j] as f64 / 4096.0 - a[j]).abs() < 0.02, "order {order} coef {j}: {} vs {}", a_q12[j] as f64 / 4096.0, a[j]); + } + } + } +} diff --git a/crates/opusenc/src/silk/lpc.rs b/crates/opusenc/src/silk/lpc.rs new file mode 100644 index 00000000..7846d297 --- /dev/null +++ b/crates/opusenc/src/silk/lpc.rs @@ -0,0 +1,367 @@ +//! Fixed-point NLSF decoding, NLSF→LPC conversion and filter stabilisation +//! (RFC 6716 §4.2.7.5, with the RFC 8251 §6/§7 overflow fixes). +//! +//! This is the decoder's normative arithmetic, mirrored from FilmCraft's first-party, +//! spec-derived `filmcraft-opus` decoder (MIT OR Apache-2.0): the encoder must reconstruct the +//! quantised NLSFs and LPC coefficients bit-exactly as the decoder does. + +use super::tables::*; + +pub const MAX_LPC_ORDER: usize = 16; + +#[inline] +pub fn smulwb(a: i32, b: i32) -> i32 { + ((a as i64 * (b as i16) as i64) >> 16) as i32 +} +#[inline] +pub fn smlawb(a: i32, b: i32, c: i32) -> i32 { + a.wrapping_add(smulwb(b, c)) +} +#[inline] +pub fn smulww(a: i32, b: i32) -> i32 { + ((a as i64 * b as i64) >> 16) as i32 +} +#[inline] +pub fn smmul(a: i32, b: i32) -> i32 { + ((a as i64 * b as i64) >> 32) as i32 +} +#[inline] +pub fn rshift_round(a: i32, s: u32) -> i32 { + if s == 1 { (a >> 1) + (a & 1) } else { ((a >> (s - 1)) + 1) >> 1 } +} +#[inline] +pub fn rshift_round64(a: i64, s: u32) -> i64 { + if s == 1 { (a >> 1) + (a & 1) } else { ((a >> (s - 1)) + 1) >> 1 } +} +#[inline] +pub fn sat16(a: i32) -> i32 { + a.clamp(-32768, 32767) +} + +/// `silk_INVERSE32_varQ`: approximation of `(1 << q_res) / b32`. +pub fn inverse32_varq(b32: i32, q_res: i32) -> i32 { + let b_headrm = b32.unsigned_abs().leading_zeros() as i32 - 1; + let b32_nrm = b32.wrapping_shl(b_headrm as u32); + let b32_inv = (i32::MAX >> 2) / (b32_nrm >> 16); + let mut result = b32_inv << 16; + let err_q32 = ((1i32 << 29).wrapping_sub(smulwb(b32_nrm, b32_inv))).wrapping_shl(3); + result = result.wrapping_add(((err_q32 as i64 * b32_inv as i64) >> 16) as i32); + let lshift = 61 - b_headrm - q_res; + if lshift <= 0 { + let s = (-lshift) as u32; + let lim_hi = i32::MAX >> s; + let lim_lo = i32::MIN >> s; + result.clamp(lim_lo, lim_hi) << s + } else if lshift < 32 { + result >> lshift + } else { + 0 + } +} + +/// `silk_DIV32_varQ`: approximation of `(a32 << q_res) / b32`. +pub fn div32_varq(a32: i32, b32: i32, q_res: i32) -> i32 { + let a_headrm = a32.unsigned_abs().leading_zeros() as i32 - 1; + let a32_nrm = a32.wrapping_shl(a_headrm as u32); + let b_headrm = b32.unsigned_abs().leading_zeros() as i32 - 1; + let b32_nrm = b32.wrapping_shl(b_headrm as u32); + let b32_inv = (i32::MAX >> 2) / (b32_nrm >> 16); + let mut result = smulwb(a32_nrm, b32_inv); + let a32_nrm = a32_nrm.wrapping_sub(((smmul(b32_nrm, result) as i64) << 3) as i32); + result = smlawb(result, a32_nrm, b32_inv); + let lshift = 29 + a_headrm - b_headrm - q_res; + if lshift < 0 { + let s = (-lshift) as u32; + let lim_hi = i32::MAX >> s; + let lim_lo = i32::MIN >> s; + result.clamp(lim_lo, lim_hi) << s + } else if lshift < 32 { + result >> lshift + } else { + 0 + } +} + +pub fn sqrt_approx(x: i32) -> i32 { + if x <= 0 { + return 0; + } + let lz = x.leading_zeros() as i32; + let frac = (x.rotate_right((24 - lz).rem_euclid(32) as u32)) & 0x7f; + let mut y = if lz & 1 != 0 { 32768 } else { 46214 }; + y >>= lz >> 1; + smlawb(y, y, 213 * frac) +} + +/// Decodes the stage-1/stage-2 indices into stabilised NLSFs (Q15). +pub fn nlsf_decode(indices: &[i32], wb: bool) -> [i32; MAX_LPC_ORDER] { + let order = if wb { 16 } else { 10 }; + let i1 = indices[0] as usize; + let cb1: &[i32] = if wb { &NLSF_CB_WB[i1] } else { &NLSF_CB_NBMB[i1] }; + let qstep = if wb { 9830 } else { 11796 }; + // Residual dequantisation with backwards prediction. + let mut res_q10 = [0i32; MAX_LPC_ORDER]; + let mut out_q10 = 0i32; + for i in (0..order).rev() { + let pred = if i + 1 < order { + let sel = if wb { NLSF_PSEL_WB[i1][i] } else { NLSF_PSEL_NBMB[i1][i] } as usize; + let w = if wb { NLSF_PRED_WB[sel][i] } else { NLSF_PRED_NBMB[sel][i] }; + (out_q10 * w) >> 8 + } else { + 0 + }; + let mut v = indices[i + 1] << 10; + if v > 0 { + v -= 102; + } else if v < 0 { + v += 102; + } + out_q10 = smlawb(pred, v, qstep); + res_q10[i] = out_q10; + } + // Laroia weights. + let mut nlsf = [0i32; MAX_LPC_ORDER]; + for k in 0..order { + let prev = if k == 0 { 0 } else { cb1[k - 1] }; + let next = if k + 1 == order { 256 } else { cb1[k + 1] }; + let w = (1024 / (cb1[k] - prev).max(1) + 1024 / (next - cb1[k]).max(1)).min(32767); + let w_q9 = sqrt_approx(w << 16); + let v = (cb1[k] << 7) + (res_q10[k] << 14) / w_q9.max(1); + nlsf[k] = v.clamp(0, 32767); + } + let dmin: &[i32] = if wb { &NLSF_MIN_SPACING_WB } else { &NLSF_MIN_SPACING_NBMB }; + nlsf_stabilize(&mut nlsf[..order], dmin); + nlsf +} + +pub fn nlsf_stabilize(nlsf: &mut [i32], dmin: &[i32]) { + let l = nlsf.len(); + for _ in 0..20 { + let mut min_diff = nlsf[0] - dmin[0]; + let mut idx = 0; + for i in 1..l { + let d = nlsf[i] - (nlsf[i - 1] + dmin[i]); + if d < min_diff { + min_diff = d; + idx = i; + } + } + let d = 32768 - (nlsf[l - 1] + dmin[l]); + if d < min_diff { + min_diff = d; + idx = l; + } + if min_diff >= 0 { + return; + } + if idx == 0 { + nlsf[0] = dmin[0]; + } else if idx == l { + nlsf[l - 1] = 32768 - dmin[l]; + } else { + let mut min_center = dmin[..idx].iter().sum::(); + min_center += dmin[idx] >> 1; + let mut max_center = 32768 - dmin[idx + 1..=l].iter().sum::(); + max_center -= dmin[idx] >> 1; + let c = rshift_round(nlsf[idx - 1] + nlsf[idx], 1); + let c = if min_center > max_center { c.clamp(max_center, min_center) } else { c.clamp(min_center, max_center) }; + nlsf[idx - 1] = c - (dmin[idx] >> 1); + nlsf[idx] = nlsf[idx - 1] + dmin[idx]; + } + } + // Fallback. + nlsf.sort_unstable(); + nlsf[0] = nlsf[0].max(dmin[0]); + for i in 1..l { + nlsf[i] = nlsf[i].max(sat16(nlsf[i - 1] + dmin[i])); + } + nlsf[l - 1] = nlsf[l - 1].min(32768 - dmin[l]); + for i in (0..l - 1).rev() { + nlsf[i] = nlsf[i].min(nlsf[i + 1] - dmin[i + 1]); + } +} + +fn bwexpander_32(ar: &mut [i32], mut chirp_q16: i32) { + let d = ar.len(); + let chirp_minus_one = chirp_q16 - 65536; + for v in ar.iter_mut().take(d - 1) { + *v = smulww(chirp_q16, *v); + chirp_q16 += rshift_round(chirp_q16.wrapping_mul(chirp_minus_one), 16); + } + ar[d - 1] = smulww(chirp_q16, ar[d - 1]); +} + +fn lpc_inverse_pred_gain(a_q12: &[i32]) -> i32 { + const QA: u32 = 24; + const A_LIMIT: i32 = 16_773_022; + let order = a_q12.len(); + let mut a = [0i32; MAX_LPC_ORDER]; + let mut dc = 0; + for k in 0..order { + dc += a_q12[k]; + a[k] = a_q12[k] << (QA - 12); + } + if dc >= 4096 { + return 0; + } + let mut inv_gain: i32 = 1 << 30; + let mul32_frac_q31 = |x: i32, y: i32| rshift_round64(x as i64 * y as i64, 31) as i32; + for k in (1..order).rev() { + if a[k] > A_LIMIT || a[k] < -A_LIMIT { + return 0; + } + let rc_q31 = -(a[k] << (31 - QA)); + let rc_mult1 = (1i32 << 30) - smmul(rc_q31, rc_q31); + inv_gain = smmul(inv_gain, rc_mult1) << 2; + if inv_gain < 107_374 { + return 0; + } + let mult2q = 32 - rc_mult1.unsigned_abs().leading_zeros() as i32; + let rc_mult2 = inverse32_varq(rc_mult1, mult2q + 30); + for n in 0..(k + 1) >> 1 { + let t1 = a[n]; + let t2 = a[k - n - 1]; + let v1 = rshift_round64(t1.saturating_sub(mul32_frac_q31(t2, rc_q31)) as i64 * rc_mult2 as i64, mult2q as u32); + if v1 > i32::MAX as i64 || v1 < i32::MIN as i64 { + return 0; + } + let v2 = rshift_round64(t2.saturating_sub(mul32_frac_q31(t1, rc_q31)) as i64 * rc_mult2 as i64, mult2q as u32); + if v2 > i32::MAX as i64 || v2 < i32::MIN as i64 { + return 0; + } + a[n] = v1 as i32; + a[k - n - 1] = v2 as i32; + } + } + if a[0] > A_LIMIT || a[0] < -A_LIMIT { + return 0; + } + let rc_q31 = -(a[0] << (31 - QA)); + let rc_mult1 = (1i32 << 30) - smmul(rc_q31, rc_q31); + inv_gain = smmul(inv_gain, rc_mult1) << 2; + if inv_gain < 107_374 { + return 0; + } + inv_gain +} + +/// Converts NLSFs (Q15) to stable Q12 LPC coefficients (`silk_NLSF2A`). +pub fn nlsf2a(nlsf: &[i32]) -> [i32; MAX_LPC_ORDER] { + let d = nlsf.len(); + let ordering: &[usize] = if d == 16 { &LSF_ORDER_WB } else { &LSF_ORDER_NBMB }; + let mut c_q17 = [0i32; MAX_LPC_ORDER]; + for k in 0..d { + let i = (nlsf[k] >> 8) as usize; + let f = nlsf[k] & 255; + let c0 = COS_Q12[i]; + let c1 = COS_Q12[(i + 1).min(128)]; + c_q17[ordering[k]] = (c0 * 256 + (c1 - c0) * f + 4) >> 3; + } + let d2 = d / 2; + let find_poly = |off: usize| -> [i64; 10] { + let mut out = [0i64; 10]; + out[0] = 1 << 16; + out[1] = -(c_q17[off] as i64); + for k in 1..d2 { + let ftmp = c_q17[2 * k + off] as i64; + out[k + 1] = 2 * out[k - 1] - ((ftmp * out[k] + 32768) >> 16); + for n in (2..=k).rev() { + out[n] += out[n - 2] - ((ftmp * out[n - 1] + 32768) >> 16); + } + out[1] -= ftmp; + } + out + }; + let p = find_poly(0); + let q = find_poly(1); + let mut a32 = [0i32; MAX_LPC_ORDER]; + for k in 0..d2 { + let ptmp = p[k + 1] + p[k]; + let qtmp = q[k + 1] - q[k]; + a32[k] = (-qtmp - ptmp) as i32; + a32[d - k - 1] = (qtmp - ptmp) as i32; + } + let a32 = &mut a32[..d]; + // Limit the dynamic range (silk_LPC_fit with QIN = 17, QOUT = 12). + let mut a_q12 = [0i32; MAX_LPC_ORDER]; + let mut i = 0; + while i < 10 { + let mut maxabs = 0i32; + let mut idx = 0; + for (k, &v) in a32.iter().enumerate() { + let av = v.saturating_abs(); + if av > maxabs { + maxabs = av; + idx = k; + } + } + let maxabs = rshift_round(maxabs, 5); + if maxabs > 32767 { + let maxabs = maxabs.min(163_838); + let chirp = 65470 - ((maxabs - 32767) << 14) / ((maxabs * (idx as i32 + 1)) >> 2); + bwexpander_32(a32, chirp); + } else { + break; + } + i += 1; + } + if i == 10 { + for k in 0..d { + a_q12[k] = sat16(rshift_round(a32[k], 5)); + a32[k] = a_q12[k] << 5; + } + } else { + for k in 0..d { + a_q12[k] = rshift_round(a32[k], 5); + } + } + for i in 0..16 { + if lpc_inverse_pred_gain(&a_q12[..d]) == 0 { + bwexpander_32(a32, 65536 - (2 << i)); + for k in 0..d { + a_q12[k] = rshift_round(a32[k], 5); + } + } else { + break; + } + } + a_q12 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn flat_nlsf_gives_small_lpc() { + // Uniformly spaced NLSFs correspond to an (almost) flat spectrum. + let nlsf: Vec = (1..=16).map(|k| k * 32768 / 17).collect(); + let a = nlsf2a(&nlsf); + assert!(a.iter().all(|v| v.abs() < 600), "{a:?}"); + let nlsf: Vec = (1..=10).map(|k| k * 32768 / 11).collect(); + let a = nlsf2a(&nlsf); + assert!(a[..10].iter().all(|v| v.abs() < 600), "{a:?}"); + } + + #[test] + fn stabilize_enforces_spacing() { + let mut v = [100, 90, 80, 30000, 30001, 30002, 31000, 32000, 32700, 32760]; + nlsf_stabilize(&mut v, &NLSF_MIN_SPACING_NBMB); + assert!(v[0] >= NLSF_MIN_SPACING_NBMB[0]); + for i in 1..10 { + assert!(v[i] - v[i - 1] >= NLSF_MIN_SPACING_NBMB[i], "{v:?}"); + } + assert!(32768 - v[9] >= NLSF_MIN_SPACING_NBMB[10]); + } + + #[test] + fn inverse_approximations() { + for &b in &[65536, 100_000, 1 << 20, 81920, 1_686_110_208] { + let inv = inverse32_varq(b, 47) as f64; + let exact = 2f64.powi(47) / b as f64; + assert!((inv - exact).abs() / exact < 1e-3, "{b}"); + } + let q = div32_varq(65536, 131072, 16) as f64; + assert!((q - 32768.0).abs() < 2.0); + } +} diff --git a/crates/opusenc/src/silk/mod.rs b/crates/opusenc/src/silk/mod.rs new file mode 100644 index 00000000..c7a4b869 --- /dev/null +++ b/crates/opusenc/src/silk/mod.rs @@ -0,0 +1,706 @@ +//! SILK layer encoder (RFC 6716 §4.2, encoder direction), 20 ms frames at 8, 12 or 16 kHz. +//! +//! Each frame is analysed (VAD, open-loop pitch, windowed-autocorrelation LPC, LTP codebook +//! search), its NLSFs are quantised, and the excitation is chosen sample by sample by running +//! the decoder's exact fixed-point reconstruction (LTP and LPC synthesis with the dequantised +//! Q12/Q14 coefficients, quantisation offsets and the pseudo-random sign sequence), so the +//! encoder always holds the decoder's state and there is no drift. A noise-feedback loop +//! shapes the coding noise with a bandwidth-expanded LPC envelope, and a per-frame search over +//! a gain offset hits the bit budget. Stereo input is coded as mid/side with the decoder's +//! stereo prediction (§4.2.8). No LBRR, no NLSF interpolation. + +pub(crate) mod analysis; +pub(crate) mod lpc; +mod nlsf; +mod pulses; +#[allow(dead_code)] +pub(crate) mod tables; + +use lpc::*; +use tables::*; + +use crate::range::RangeEncoder; + +const MAX_FRAME: usize = 320; +const LTP_ORDER: usize = 5; +/// Frame RMS (16-bit scale) below which a frame is coded as inactive (VAD off). +const VAD_RMS: f32 = 30.0; +/// Normalised pitch correlation above which a frame is a voicing candidate. +const VOICING_THRESHOLD: f64 = 0.45; +/// Minimum LTP prediction gain (energy ratio) for a frame to be coded as voiced. +const MIN_LTP_GAIN: f64 = 1.3; +/// Rate penalty (squared pulse units per unit magnitude) of the excitation quantiser. +const RD_LAMBDA: f64 = 0.15; +/// Extra gain offset (log2) for inactive frames. +const INACTIVE_OFFSET: f64 = 1.0; +/// Noise feedback: the coding noise is shaped by `1 / A(z/γ)` (A the short-term predictor). +/// γ = 0 leaves it white (closed-loop DPCM, best waveform SNR at high rates), γ = 1 shapes it +/// like the spectral envelope (open-loop residual coding, needed at low rates). γ grows with the +/// gain offset, i.e. as the quantisation gets coarser. +fn nf_gamma(offset: f64) -> f64 { + (0.75 + 0.05 * offset).clamp(0.75, 0.9) +} + +/// `silk_log2lin` (decoder arithmetic). +fn log2lin(in_log_q7: i32) -> i32 { + if in_log_q7 < 0 { + return 0; + } + if in_log_q7 >= 3967 { + return i32::MAX; + } + let out = 1i32 << (in_log_q7 >> 7); + let frac = in_log_q7 & 0x7F; + if in_log_q7 < 2048 { out + ((out * smlawb(frac, frac * (128 - frac), -174)) >> 7) } else { out + (out >> 7) * smlawb(frac, frac * (128 - frac), -174) } +} + +fn gain_q16(index: i32) -> i32 { + log2lin((smulwb(0x1D1C71, index) + 2090).min(3967)) +} + +/// The decoder's LPC analysis filter on 16-bit samples. +fn lpc_analysis_filter(out: &mut [i16], input: &[i16], b: &[i32], d: usize) { + let len = out.len(); + for ix in d..len { + let mut acc: i32 = 0; + for j in 0..d { + acc = acc.wrapping_add((input[ix - 1 - j] as i32).wrapping_mul(b[j])); + } + let v = ((input[ix] as i32) << 12).wrapping_sub(acc); + out[ix] = sat16(rshift_round(v, 12)) as i16; + } + for v in out.iter_mut().take(d.min(len)) { + *v = 0; + } +} + +/// The part of the decoder's channel state that the reconstruction depends on. +#[derive(Clone)] +struct DecState { + prev_gain_q16: i32, + s_lpc_q14: [i32; MAX_LPC_ORDER], + out_buf: [i16; MAX_FRAME], + last_gain_index: i32, +} + +impl DecState { + fn new() -> Self { + DecState { prev_gain_q16: 65536, s_lpc_q14: [0; MAX_LPC_ORDER], out_buf: [0; MAX_FRAME], last_gain_index: 10 } + } + + /// The decoder's reset of the side channel when it is coded again after mid-only frames. + fn reset_side(&mut self) { + self.out_buf = [0; MAX_FRAME]; + self.s_lpc_q14 = [0; MAX_LPC_ORDER]; + self.last_gain_index = 10; + } +} + +/// Coded parameters of one frame (everything except gains and pulses). +#[derive(Clone, Default)] +struct Params { + vad: bool, + signal_type: usize, + quant_offset_type: usize, + nlsf_ix: [i32; 17], + a_q12: [i32; MAX_LPC_ORDER], + lag_index: i32, + contour: usize, + per_index: usize, + ltp_index: [usize; 4], + pitch_l: [i32; 4], + /// RMS (16-bit scale) of the open-loop prediction residual per subframe. + res_rms: [f32; 4], +} + +/// Gains, seed and pulses chosen by the quantiser. +struct Coded { + gain_ix: [i32; 4], + gains_q16: [i32; 4], + seed: usize, + pulses: Vec, +} + +#[derive(Clone)] +struct Channel { + fs_khz: usize, + len: usize, + sub: usize, + order: usize, + dec: DecState, + /// The last two frames of input (internal rate, 16-bit scale). + hist: Vec, + /// Recent output errors (decoded − input), most recent first, for noise feedback. + nf: [f64; MAX_LPC_ORDER], +} + +impl Channel { + fn new(fs_khz: usize) -> Self { + let len = 20 * fs_khz; + Channel { + fs_khz, + len, + sub: 5 * fs_khz, + order: if fs_khz == 16 { 16 } else { 10 }, + dec: DecState::new(), + hist: vec![0.0; 2 * len], + nf: [0.0; MAX_LPC_ORDER], + } + } + + fn push_history(&mut self, x: &[f32]) { + let l = self.len; + self.hist.copy_within(l.., 0); + self.hist[l..].copy_from_slice(x); + } + + /// Frame analysis: VAD, signal type, pitch, LPC/NLSF quantisation and LTP taps. + fn analyze(&self, x: &[f32]) -> Params { + let l = self.len; + let sub = self.sub; + let order = self.order; + let wb = order == 16; + let mut buf = self.hist.clone(); + buf.extend_from_slice(x); + let start = 2 * l; + let rms = (x.iter().map(|v| v * v).sum::() / l as f32).sqrt(); + let vad = rms > VAD_RMS; + let pitch = if vad { Some(analysis::pitch_search(&buf, start, l, self.fs_khz)) } else { None }; + let mut voiced = pitch.as_ref().is_some_and(|p| p.score > VOICING_THRESHOLD); + + let a = analysis::lpc(&buf[start - l / 4..], order, self.fs_khz); + let target: Vec = match analysis::a2nlsf(&a) { + Some(w) => w.iter().map(|v| v / std::f64::consts::PI * 32768.0).collect(), + None => (1..=order).map(|k| k as f64 * 32768.0 / (order + 1) as f64).collect(), + }; + let quant = |signal_type: usize| { + let (ix, nlsf) = nlsf::quantize(&target, signal_type, wb); + (ix, nlsf2a(&nlsf[..order])) + }; + let mut p = Params { vad, ..Default::default() }; + let (mut ix, mut a_q12) = quant(if voiced { 2 } else { 1 }); + // Open-loop residual with the quantised predictor. + let mut r = vec![0f64; buf.len()]; + for n in order..buf.len() { + let mut acc = buf[n] as f64; + for j in 0..order { + acc -= a_q12[j] as f64 / 4096.0 * buf[n - 1 - j] as f64; + } + r[n] = acc; + } + if voiced && let Some(pitch) = &pitch { + let ltp = analysis::ltp_search(&r, start, sub, &pitch.lags); + let gain = ltp.energy.iter().sum::() / ltp.err.iter().sum::().max(1e-9); + if gain >= MIN_LTP_GAIN { + p.lag_index = pitch.lag_index; + p.contour = pitch.contour; + p.pitch_l = pitch.lags; + p.per_index = ltp.per_index; + p.ltp_index = ltp.ltp_index; + } else { + voiced = false; + (ix, a_q12) = quant(1); + } + } + // Gains follow the short-term residual level (the long-term predictor then only + // reduces the number of pulses), which keeps the noise-to-signal ratio steady. + for k in 0..4 { + let e: f64 = r[start + k * sub..start + (k + 1) * sub].iter().map(|v| v * v).sum(); + p.res_rms[k] = (e / sub as f64).sqrt() as f32; + } + p.signal_type = if !vad { + 0 + } else if voiced { + 2 + } else { + 1 + }; + p.quant_offset_type = 0; + p.nlsf_ix = ix; + p.a_q12 = a_q12; + p + } + + /// Chooses gains and pulses for target `x` by running the decoder's reconstruction, and + /// advances the mirrored decoder state. `offset` scales the gains (log2), `floor` gives + /// minimum gains (Q16) per subframe. Returns the coded values and the decoded frame. + fn quantize(&mut self, p: &Params, x: &[f32], offset: f64, floor: Option<&[i32; 4]>, seed: usize) -> (Coded, Vec) { + let l = self.len; + let sub = self.sub; + let order = self.order; + let st = &mut self.dec; + let nf = &mut self.nf; + let off = if p.signal_type == 0 { offset + INACTIVE_OFFSET } else { offset }; + let gamma = nf_gamma(off); + let mut nf_c = [0f64; MAX_LPC_ORDER]; + let mut gpow = 1.0; + for j in 0..order { + gpow *= gamma; + nf_c[j] = p.a_q12[j] as f64 / 4096.0 * gpow; + } + // Gains (independent coding of the first subframe, deltas for the rest). + let mut gain_ix = [0i32; 4]; + let mut gains_q16 = [0i32; 4]; + let mut prev = st.last_gain_index; + for k in 0..4 { + let mut g = p.res_rms[k] as f64 * off.exp2(); + if let Some(f) = floor { + g = g.max(f[k] as f64 / 65536.0); + } + let want = (((g * 65536.0).max(1.0).log2() * 128.0 - 2090.0) / (1_907_825.0 / 65536.0)).round().clamp(0.0, 63.0) as i32; + if k == 0 { + gain_ix[0] = want; + prev = want.max(prev - 16).clamp(0, 63); + } else { + let mut best = (i32::MAX, 0i32, prev); + for ind in 0..41 { + let tmp = ind - 4; + let thr = 2 * 36 - 64 + prev; + let d = if tmp > thr { prev + (tmp << 1) - thr } else { prev + tmp }.clamp(0, 63); + let e = (d - want).abs(); + if e < best.0 { + best = (e, ind, d); + } + } + gain_ix[k] = best.1; + prev = best.2; + } + gains_q16[k] = gain_q16(prev); + } + st.last_gain_index = prev; + + // Analysis-by-synthesis excitation quantisation through the decoder's exact synthesis. + let voiced = p.signal_type == 2; + let offset_q10 = [[100, 240], [32, 100]][p.signal_type >> 1][p.quant_offset_type]; + let off16 = offset_q10 << 4; + let mut seed_v = seed as i32; + let mut pulses = vec![0i32; l]; + let mut xq = vec![0i16; l]; + let mut s_lpc = [0i32; MAX_FRAME / 4 + MAX_LPC_ORDER]; + s_lpc[..MAX_LPC_ORDER].copy_from_slice(&st.s_lpc_q14); + let mut s_ltp = [0i16; MAX_FRAME]; + let mut s_ltp_q15 = [0i32; 2 * MAX_FRAME]; + let ltp_mem = l; + let mut ltp_buf_idx = ltp_mem; + let a_q12 = &p.a_q12; + let ltp_scale_q14 = 15565; + for k in 0..4 { + let mut b_q14 = [0i32; LTP_ORDER]; + if voiced { + let ix = p.ltp_index[k]; + let taps = match p.per_index { + 0 => LTP_TAPS_0[ix], + 1 => LTP_TAPS_1[ix], + _ => LTP_TAPS_2[ix], + }; + for i in 0..LTP_ORDER { + b_q14[i] = taps[i] << 7; + } + } + let gain_q10 = gains_q16[k] >> 6; + let mut inv_gain_q31 = inverse32_varq(gains_q16[k], 47); + let gain_adj_q16 = if gains_q16[k] != st.prev_gain_q16 { + let g = div32_varq(st.prev_gain_q16, gains_q16[k], 16); + for v in s_lpc[..MAX_LPC_ORDER].iter_mut() { + *v = smulww(g, *v); + } + g + } else { + 1 << 16 + }; + st.prev_gain_q16 = gains_q16[k]; + let lag = p.pitch_l[k]; + if voiced { + let lag_u = lag as usize; + if k == 0 { + let start_idx = (ltp_mem as isize - lag as isize - order as isize - (LTP_ORDER / 2) as isize).max(0) as usize; + lpc_analysis_filter(&mut s_ltp[start_idx..ltp_mem], &st.out_buf[start_idx..ltp_mem], a_q12, order); + inv_gain_q31 = smulwb(inv_gain_q31, ltp_scale_q14) << 2; + for i in 0..(lag_u + LTP_ORDER / 2).min(ltp_mem) { + s_ltp_q15[ltp_buf_idx - i - 1] = smulwb(inv_gain_q31, s_ltp[ltp_mem - i - 1] as i32); + } + } else if gain_adj_q16 != 1 << 16 { + for i in 0..(lag_u + LTP_ORDER / 2).min(ltp_buf_idx) { + let q = ltp_buf_idx - i - 1; + s_ltp_q15[q] = smulww(gain_adj_q16, s_ltp_q15[q]); + } + } + } + let inv_g = 16_777_216.0 / gain_q10.max(1) as f64; + for i in 0..sub { + let n = k * sub + i; + seed_v = seed_v.wrapping_mul(196_314_165).wrapping_add(907_633_515); + let ltp_q14 = if voiced { + let base = ltp_buf_idx as isize - lag as isize + (LTP_ORDER / 2) as isize; + let g = |o: isize| -> i32 { if base - o >= 0 { s_ltp_q15[(base - o) as usize] } else { 0 } }; + let mut pred = 2i32; + for o in 0..LTP_ORDER { + pred = smlawb(pred, g(o as isize), b_q14[o]); + } + pred << 1 + } else { + 0 + }; + let mut lpc_pred = (order >> 1) as i32; + for j in 0..order { + lpc_pred = smlawb(lpc_pred, s_lpc[MAX_LPC_ORDER + i - 1 - j], a_q12[j]); + } + let lpc_q14 = lpc_pred.saturating_mul(16); + // Target excitation (Q14) for this sample. + let fb: f64 = (0..order).map(|j| nf_c[j] * nf[j]).sum(); + let tgt = (x[n] as f64 + fb).clamp(-32768.0, 32767.0) * inv_g - lpc_q14 as f64 - ltp_q14 as f64; + let sgn = if seed_v < 0 { -1.0 } else { 1.0 }; + let u = sgn * tgt; + let pf = (u - off16 as f64) / 16384.0; + let p0 = if pf > 0.0 { (pf + 0.078_125).round() } else { (pf - 0.078_125).round() } as i32; + let level = |q: i32| -> i32 { + let mut e = q << 14; + if e > 0 { + e -= 80 << 4; + } else if e < 0 { + e += 80 << 4; + } + e + off16 + }; + let mut best = (f64::MAX, 0i32); + for q in [p0 - 1, p0, p0 + 1, 0] { + let q = q.clamp(-pulses::MAX_PULSE, pulses::MAX_PULSE); + let d = (level(q) as f64 - u) / 16384.0; + let cost = d * d + RD_LAMBDA * q.abs() as f64; + if cost < best.0 { + best = (cost, q); + } + } + let q = best.1; + pulses[n] = q; + let mut e = level(q); + if seed_v < 0 { + e = -e; + } + seed_v = seed_v.wrapping_add(q); + let res = e.wrapping_add(ltp_q14); + if voiced { + s_ltp_q15[ltp_buf_idx] = res << 1; + ltp_buf_idx += 1; + } + let v = res.saturating_add(lpc_q14); + s_lpc[MAX_LPC_ORDER + i] = v; + xq[n] = sat16(rshift_round(smulww(v, gain_q10), 8)) as i16; + nf.copy_within(0..MAX_LPC_ORDER - 1, 1); + nf[0] = xq[n] as f64 - x[n] as f64; + } + s_lpc.copy_within(sub..sub + MAX_LPC_ORDER, 0); + } + st.s_lpc_q14.copy_from_slice(&s_lpc[..MAX_LPC_ORDER]); + st.out_buf[..l].copy_from_slice(&xq); + (Coded { gain_ix, gains_q16, seed, pulses }, xq) + } + + /// Writes the frame's indices and pulses (`decode_indices` + `decode_pulses` order, coded + /// independently). + fn write(&self, enc: &mut RangeEncoder, p: &Params, c: &Coded) { + let ix = p.signal_type * 2 + p.quant_offset_type; + if p.vad { + enc.icdf(ix - 2, &TYPE_ACTIVE, 8); + } else { + enc.icdf(ix, &TYPE_INACTIVE, 8); + } + enc.icdf((c.gain_ix[0] >> 3) as usize, GAIN_MSB[p.signal_type], 8); + enc.icdf((c.gain_ix[0] & 7) as usize, &UNIFORM8, 8); + for k in 1..4 { + enc.icdf(c.gain_ix[k] as usize, &DELTA_GAIN, 8); + } + let wb = self.order == 16; + let i1 = p.nlsf_ix[0] as usize; + enc.icdf(i1, NLSF_STAGE1[(p.signal_type >> 1) + if wb { 2 } else { 0 }], 8); + for i in 0..self.order { + let cb = if wb { 8 + NLSF_SEL_WB[i1][i] as usize } else { NLSF_SEL_NBMB[i1][i] as usize }; + let v = p.nlsf_ix[i + 1] + 4; + if v <= 0 { + enc.icdf(0, NLSF_STAGE2[cb], 8); + enc.icdf((-v) as usize, &NLSF_EXT, 8); + } else if v >= 8 { + enc.icdf(8, NLSF_STAGE2[cb], 8); + enc.icdf((v - 8) as usize, &NLSF_EXT, 8); + } else { + enc.icdf(v as usize, NLSF_STAGE2[cb], 8); + } + } + // No NLSF interpolation. + enc.icdf(4, &NLSF_INTERP, 8); + if p.signal_type == 2 { + let half = (self.fs_khz >> 1) as i32; + enc.icdf((p.lag_index / half) as usize, &PITCH_HIGH, 8); + let low = match self.fs_khz { + 8 => PITCH_LOW[0], + 12 => PITCH_LOW[1], + _ => PITCH_LOW[2], + }; + enc.icdf((p.lag_index % half) as usize, low, 8); + enc.icdf(p.contour, if self.fs_khz == 8 { PITCH_CONTOUR[1] } else { PITCH_CONTOUR[3] }, 8); + enc.icdf(p.per_index, <P_PERIODICITY, 8); + for k in 0..4 { + enc.icdf(p.ltp_index[k], LTP_FILTER[p.per_index], 8); + } + // LTP scaling index 0 (15565 in Q14), matching `quantize`. + enc.icdf(0, <P_SCALE, 8); + } + enc.icdf(c.seed, &UNIFORM4, 8); + pulses::encode(enc, &c.pulses, p.signal_type, p.quant_offset_type); + } +} + +/// Quantises a stereo prediction weight (Q13) to the decoder's grid (§4.2.7.1): returns the +/// (UNIFORM3, UNIFORM5, joint) indices and the reconstructed value. +fn quant_pred(w: f64) -> ([usize; 3], i32) { + let mut best = (f64::MAX, [0usize; 3], 0i32); + for i in 0..15 { + let low = STEREO_WEIGHTS_Q13[i]; + let step = smulwb(STEREO_WEIGHTS_Q13[i + 1] - low, 6554); + for j in 0..5 { + let v = low + step * (2 * j as i32 + 1); + let e = (v as f64 - w).abs(); + if e < best.0 { + best = (e, [i % 3, j, i / 3], v); + } + } + } + (best.1, best.2) +} + +/// Per-frame preparation shared by every rate-loop trial. +struct Prep { + x: [Vec; 2], + params: [Params; 2], + mid_only: bool, + pred_ix: [[usize; 3]; 2], + pred_q13: [i32; 2], +} + +/// SILK encoder for one mono or stereo (mid/side) stream. +#[derive(Clone)] +pub struct SilkEncoder { + fs_khz: usize, + channels: usize, + ch: [Channel; 2], + pred_prev_q13: [i32; 2], + prev_mid_only: bool, + /// Last mid sample of the previous frame (for the stereo predictor's low-pass). + prev_mid: f32, + frame: usize, + /// Gain offset chosen for the previous frame (search starting point). + pub last_offset: f64, +} + +impl SilkEncoder { + pub fn new(fs_khz: usize, channels: usize) -> Self { + SilkEncoder { + fs_khz, + channels, + ch: [Channel::new(fs_khz), Channel::new(fs_khz)], + pred_prev_q13: [0; 2], + prev_mid_only: false, + prev_mid: 0.0, + frame: 0, + last_offset: 1.0, + } + } + + /// Samples per channel of one 20 ms frame at the internal rate. + pub fn frame_len(&self) -> usize { + 20 * self.fs_khz + } + + fn prepare(&self, input: &[Vec]) -> Prep { + let l = self.frame_len(); + if self.channels == 1 { + let x = input[0].clone(); + let p = self.ch[0].analyze(&x); + return Prep { x: [x, vec![0.0; l]], params: [p, Params::default()], mid_only: true, pred_ix: [[0; 3]; 2], pred_q13: [0; 2] }; + } + let mid: Vec = (0..l).map(|i| 0.5 * (input[0][i] + input[1][i])).collect(); + let side: Vec = (0..l).map(|i| 0.5 * (input[0][i] - input[1][i])).collect(); + let lp = |j: usize| -> f64 { + let a = if j == 0 { self.prev_mid } else { mid[j - 1] }; + let c = if j + 1 < l { mid[j + 1] } else { mid[l - 1] }; + (a as f64 + 2.0 * mid[j] as f64 + c as f64) / 4.0 + }; + // Least-squares prediction of the side from the low-passed mid and the mid. + let (mut r00, mut r01, mut r11, mut c0, mut c1, mut ss, mut mm) = (0f64, 0f64, 0f64, 0f64, 0f64, 0f64, 0f64); + for j in 0..l { + let (a, b, s) = (lp(j), mid[j] as f64, side[j] as f64); + r00 += a * a; + r01 += a * b; + r11 += b * b; + c0 += a * s; + c1 += b * s; + ss += s * s; + mm += b * b; + } + // Ridge regularisation: the low-passed mid and the mid are nearly collinear. + let lam = 0.02 * 0.5 * (r00 + r11) + 1e-9; + let (a00, a11) = (r00 + lam, r11 + lam); + let det = a00 * a11 - r01 * r01; + let (p0, p1) = ((c0 * a11 - c1 * r01) / det, (c1 * a00 - c0 * r01) / det); + let lim = 13732.0; + let quant = |p0: f64, p1: f64| { + let (ix0, q0) = quant_pred(((p0 + p1) * 8192.0).clamp(-lim, lim)); + let (ix1, q1) = quant_pred((p1 * 8192.0).clamp(-lim, lim)); + ([ix0, ix1], [q0 - q1, q1]) + }; + // Side residual with the decoder's interpolated predictor. + let fs = self.fs_khz as i32; + let denom_q16 = (1 << 16) / (8 * fs); + let interp = 8 * self.fs_khz; + let residual = |pred_q13: [i32; 2]| -> Vec { + let d0 = rshift_round((pred_q13[0] - self.pred_prev_q13[0]) as i16 as i32 * denom_q16 as i16 as i32, 16); + let d1 = rshift_round((pred_q13[1] - self.pred_prev_q13[1]) as i16 as i32 * denom_q16 as i16 as i32, 16); + let (mut pp0, mut pp1) = (self.pred_prev_q13[0] + d0, self.pred_prev_q13[1] + d1); + (0..l) + .map(|j| { + if j + 1 < interp { + pp0 += d0; + pp1 += d1; + } else { + pp0 = pred_q13[0]; + pp1 = pred_q13[1]; + } + (side[j] as f64 - (pp0 as f64 * lp(j) + pp1 as f64 * mid[j] as f64) / 8192.0) as f32 + }) + .collect() + }; + let energy = |v: &[f32]| -> f64 { v.iter().map(|&x| (x as f64).powi(2)).sum() }; + let (mut pred_ix, mut pred_q13) = quant(p0, p1); + let mut res = residual(pred_q13); + let mut res_e = energy(&res); + // Never predict worse than not predicting at all. + if res_e > ss { + (pred_ix, pred_q13) = quant(0.0, 0.0); + res = residual(pred_q13); + res_e = energy(&res); + } + let res_rms = (res_e / l as f64).sqrt(); + let mid_rms = (mm / l as f64).sqrt(); + let mid_only = res_rms < 0.01 * mid_rms + 1.0; + let pm = self.ch[0].analyze(&mid); + let ps = if mid_only { Params::default() } else { self.ch[1].analyze(&res) }; + Prep { x: [mid, res], params: [pm, ps], mid_only, pred_ix, pred_q13 } + } + + /// Codes one frame into `enc` with gain offset `offset`. + fn code(&mut self, prep: &Prep, offset: f64, enc: &mut RangeEncoder) { + let stereo = self.channels == 2; + let side_vad = stereo && !prep.mid_only && prep.params[1].vad; + enc.bit_logp(prep.params[0].vad, 1); + enc.bit_logp(false, 1); + if stereo { + enc.bit_logp(side_vad, 1); + enc.bit_logp(false, 1); + let [a, b] = prep.pred_ix; + enc.icdf(5 * a[2] + b[2], &STEREO_PRED_JOINT, 8); + enc.icdf(a[0], &UNIFORM3, 8); + enc.icdf(a[1], &UNIFORM5, 8); + enc.icdf(b[0], &UNIFORM3, 8); + enc.icdf(b[1], &UNIFORM5, 8); + if !side_vad { + enc.icdf(prep.mid_only as usize, &MID_ONLY, 8); + } + } + let seed = self.frame & 3; + let (cm, _) = self.ch[0].quantize(&prep.params[0], &prep.x[0], offset, None, seed); + self.ch[0].write(enc, &prep.params[0], &cm); + if stereo { + if !prep.mid_only { + if self.prev_mid_only { + self.ch[1].dec.reset_side(); + } + let (cs, _) = self.ch[1].quantize(&prep.params[1], &prep.x[1], offset, Some(&cm.gains_q16), seed); + self.ch[1].write(enc, &prep.params[1], &cs); + } + self.pred_prev_q13 = prep.pred_q13; + self.prev_mid_only = prep.mid_only; + } + } + + /// Encodes one 20 ms frame (`input`: one internal-rate, 16-bit-scale vector per channel) + /// into `enc`, aiming at `target_bits` total and never exceeding `max_bits`. + pub fn encode(&mut self, input: &[Vec], target_bits: f64, max_bits: i32, enc: &mut RangeEncoder) { + let prep = self.prepare(input); + let start_bits = enc.tell(); + let trial = |s: &Self, o: f64| -> i32 { + let mut t = s.clone(); + let mut e = enc.clone(); + t.code(&prep, o, &mut e); + e.tell() - start_bits + }; + // Bisection on the gain offset: the smallest offset (finest quantisation) within budget. + let target = target_bits.min(max_bits as f64); + // Bits are not strictly monotonic in the offset (very coarse quantisation feeds large + // errors back), so walk an integer grid from the previous frame's offset to find a + // bracket [infeasible, feasible], then bisect inside it. + const MIN_O: f64 = -3.0; + const MAX_O: f64 = 6.0; + let mut best_any = (i32::MAX, MAX_O); + let mut probe = |o: f64| -> bool { + let b = trial(self, o); + if b < best_any.0 { + best_any = (b, o); + } + b as f64 <= target + }; + let g0 = self.last_offset.round().clamp(MIN_O, MAX_O); + let mut bracket = None; + if probe(g0) { + let mut hi = g0; + loop { + if hi - 1.0 < MIN_O { + bracket = Some((None, hi)); + break; + } + if probe(hi - 1.0) { + hi -= 1.0; + } else { + bracket = Some((Some(hi - 1.0), hi)); + break; + } + } + } else { + let mut lo = g0; + while lo + 1.0 <= MAX_O { + if probe(lo + 1.0) { + bracket = Some((Some(lo), lo + 1.0)); + break; + } + lo += 1.0; + } + } + let mut o = match bracket { + Some((Some(mut lo), mut hi)) => { + for _ in 0..4 { + let m = 0.5 * (lo + hi); + if probe(m) { + hi = m; + } else { + lo = m; + } + } + hi + } + Some((None, hi)) => hi, + None => best_any.1, + }; + // Hard limit (hybrid packets must leave room for CELT). + while trial(self, o) > max_bits && o < 12.0 { + o += 1.0; + } + self.last_offset = o; + self.code(&prep, o, enc); + for ch in 0..self.channels { + self.ch[ch].push_history(&prep.x[ch]); + } + if self.channels == 2 { + self.prev_mid = prep.x[0][self.frame_len() - 1]; + } + self.frame += 1; + } +} diff --git a/crates/opusenc/src/silk/nlsf.rs b/crates/opusenc/src/silk/nlsf.rs new file mode 100644 index 00000000..3f1f17ac --- /dev/null +++ b/crates/opusenc/src/silk/nlsf.rs @@ -0,0 +1,128 @@ +//! NLSF quantisation (encoder side of RFC 6716 §4.2.7.5): stage-1 vector codebook preselection +//! and a backwards-predictive stage-2 scalar quantiser that reproduces the decoder's residual +//! dequantisation exactly. The search strategy is our own. + +use super::lpc::{MAX_LPC_ORDER, nlsf_decode, smlawb, sqrt_approx}; +use super::tables::*; + +/// Number of stage-1 candidates refined with stage 2. +const SURVIVORS: usize = 8; +/// Rate weight (weighted squared Q15 error per bit). +const LAMBDA: f64 = 60.0; + +fn icdf_bits(icdf: &[u8], s: usize) -> f64 { + let hi = if s == 0 { 256 } else { icdf[s - 1] as u32 }; + let p = (hi - icdf[s] as u32).max(1) as f64 / 256.0; + -p.log2() +} + +/// Bits of a stage-2 index `q` (-10..=10) with stage-2 table `tab`. +fn stage2_bits(tab: &[u8], q: i32) -> f64 { + let v = q + 4; + if v <= 0 { + icdf_bits(tab, 0) + icdf_bits(&NLSF_EXT, (-v) as usize) + } else if v >= 8 { + icdf_bits(tab, 8) + icdf_bits(&NLSF_EXT, (v - 8) as usize) + } else { + icdf_bits(tab, v as usize) + } +} + +/// Quantises target NLSFs (`target`, Q15 as f64, ascending) and returns the 1 + order indices +/// in the decoder's layout (`[stage1, stage2...]`) together with the reconstructed NLSFs. +pub fn quantize(target: &[f64], signal_type: usize, wb: bool) -> ([i32; 17], [i32; MAX_LPC_ORDER]) { + let order = target.len(); + // Laroia-style weights. + let w: Vec = (0..order) + .map(|k| { + let prev = if k == 0 { 0.0 } else { target[k - 1] }; + let next = if k + 1 == order { 32768.0 } else { target[k + 1] }; + 1.0 / (target[k] - prev).max(50.0) + 1.0 / (next - target[k]).max(50.0) + }) + .collect(); + let s1_tab = NLSF_STAGE1[(signal_type >> 1) + if wb { 2 } else { 0 }]; + let n_cb = 32; + let mut cands: Vec<(f64, usize)> = (0..n_cb) + .map(|i1| { + let cb: &[i32] = if wb { &NLSF_CB_WB[i1] } else { &NLSF_CB_NBMB[i1] }; + let d: f64 = (0..order).map(|k| w[k] * ((cb[k] << 7) as f64 - target[k]).powi(2)).sum(); + (d + LAMBDA * icdf_bits(s1_tab, i1), i1) + }) + .collect(); + cands.sort_by(|a, b| a.0.total_cmp(&b.0)); + let qstep = if wb { 9830 } else { 11796 }; + let mut best = (f64::MAX, [0i32; 17], [0i32; MAX_LPC_ORDER]); + for &(_, i1) in cands.iter().take(SURVIVORS) { + let cb1: &[i32] = if wb { &NLSF_CB_WB[i1] } else { &NLSF_CB_NBMB[i1] }; + let mut ix = [0i32; 17]; + ix[0] = i1 as i32; + let mut bits = icdf_bits(s1_tab, i1); + // Exact decoder weights per coefficient. + let w_q9: Vec = (0..order) + .map(|k| { + let prev = if k == 0 { 0 } else { cb1[k - 1] }; + let next = if k + 1 == order { 256 } else { cb1[k + 1] }; + let ww = (1024 / (cb1[k] - prev).max(1) + 1024 / (next - cb1[k]).max(1)).min(32767); + sqrt_approx(ww << 16).max(1) + }) + .collect(); + let mut out_q10 = 0i32; + for i in (0..order).rev() { + let pred = if i + 1 < order { + let sel = if wb { NLSF_PSEL_WB[i1][i] } else { NLSF_PSEL_NBMB[i1][i] } as usize; + let wp = if wb { NLSF_PRED_WB[sel][i] } else { NLSF_PRED_NBMB[sel][i] }; + (out_q10 * wp) >> 8 + } else { + 0 + }; + let cb = if wb { 8 + NLSF_SEL_WB[i1][i] as usize } else { NLSF_SEL_NBMB[i1][i] as usize }; + let tab = NLSF_STAGE2[cb]; + let want_q10 = (target[i] - (cb1[i] << 7) as f64) * w_q9[i] as f64 / 16384.0; + let step = qstep as f64 * 1024.0 / 65536.0; + let q0 = ((want_q10 - pred as f64) / step).round() as i32; + let mut bq = (f64::MAX, 0i32, 0i32); + for q in (q0 - 1).max(-10)..=(q0 + 1).min(10) { + let mut v = q << 10; + if v > 0 { + v -= 102; + } else if v < 0 { + v += 102; + } + let o = smlawb(pred, v, qstep); + let nl = (cb1[i] << 7) as f64 + ((o << 14) / w_q9[i]) as f64; + let cost = w[i] * (nl - target[i]).powi(2) + LAMBDA * stage2_bits(tab, q); + if cost < bq.0 { + bq = (cost, q, o); + } + } + ix[i + 1] = bq.1; + out_q10 = bq.2; + bits += stage2_bits(tab, bq.1); + } + let nlsf = nlsf_decode(&ix, wb); + let d: f64 = (0..order).map(|k| w[k] * (nlsf[k] as f64 - target[k]).powi(2)).sum(); + let cost = d + LAMBDA * bits; + if cost < best.0 { + best = (cost, ix, nlsf); + } + } + (best.1, best.2) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn quantised_nlsfs_track_the_target() { + for wb in [false, true] { + let order = if wb { 16 } else { 10 }; + // A smooth, plausible NLSF vector. + let target: Vec = (0..order).map(|k| 32768.0 * (k as f64 + 0.8 + 0.3 * (k as f64).sin()) / (order as f64 + 1.0)).collect(); + let (ix, nlsf) = quantize(&target, 1, wb); + assert!(ix[1..=order].iter().all(|v| (-10..=10).contains(v))); + let max_err = (0..order).map(|k| (nlsf[k] as f64 - target[k]).abs()).fold(0.0, f64::max); + assert!(max_err < 1200.0, "wb={wb} max err {max_err}: {:?} vs {target:?}", &nlsf[..order]); + } + } +} diff --git a/crates/opusenc/src/silk/pulses.rs b/crates/opusenc/src/silk/pulses.rs new file mode 100644 index 00000000..f8cda15a --- /dev/null +++ b/crates/opusenc/src/silk/pulses.rs @@ -0,0 +1,113 @@ +//! Excitation coding (encoder side of RFC 6716 §4.2.7.8): rate level, per-block pulse counts +//! with LSB escapes, the recursive shell code, LSBs and signs, written in exactly the order the +//! decoder reads them. + +use super::tables::*; +use crate::range::RangeEncoder; + +/// Largest pulse magnitude the quantiser produces (keeps every 16-sample block codable). +pub const MAX_PULSE: i32 = 500; + +pub fn icdf_bits(icdf: &[u8], s: usize) -> f32 { + let hi = if s == 0 { 256 } else { icdf[s - 1] as u32 }; + let p = (hi - icdf[s] as u32).max(1) as f32 / 256.0; + -p.log2() +} + +fn count_table(nls: usize) -> &'static [u8] { + if nls == 10 { PULSE_COUNT[10] } else { PULSE_COUNT[9] } +} + +fn shell(enc: &mut RangeEncoder, v: &[i32], count: i32) { + let n = v.len(); + if n == 1 || count == 0 { + return; + } + let tab = match n { + 16 => SHELL_16[(count - 1) as usize], + 8 => SHELL_8[(count - 1) as usize], + 4 => SHELL_4[(count - 1) as usize], + _ => SHELL_2[(count - 1) as usize], + }; + let (a, b) = v.split_at(n / 2); + let left: i32 = a.iter().sum(); + enc.icdf(left as usize, tab, 8); + shell(enc, a, left); + shell(enc, b, count - left); +} + +/// Codes the frame's pulses (`len` a multiple of 16). +pub fn encode(enc: &mut RangeEncoder, pulses: &[i32], signal_type: usize, quant_offset_type: usize) { + let nblk = pulses.len() / 16; + let mut nls = vec![0usize; nblk]; + let mut sums = vec![0i32; nblk]; + let mut hi = vec![0i32; pulses.len()]; + for b in 0..nblk { + let blk = &pulses[b * 16..(b + 1) * 16]; + let mut s = 0usize; + loop { + let sum: i32 = blk.iter().map(|p| p.abs() >> s).sum(); + if sum <= 16 || s == 10 { + sums[b] = sum.min(16); + break; + } + s += 1; + } + nls[b] = s; + for (h, p) in hi[b * 16..(b + 1) * 16].iter_mut().zip(blk) { + *h = p.abs() >> s; + } + } + // Rate level with the cheapest pulse-count symbols. + let rl_tab = RATE_LEVEL[signal_type >> 1]; + let mut best = (f32::MAX, 0usize); + for rl in 0..9 { + let mut bits = icdf_bits(rl_tab, rl); + for b in 0..nblk { + bits += if nls[b] == 0 { icdf_bits(PULSE_COUNT[rl], sums[b] as usize) } else { icdf_bits(PULSE_COUNT[rl], 17) }; + } + if bits < best.0 { + best = (bits, rl); + } + } + let rl = best.1; + enc.icdf(rl, rl_tab, 8); + for b in 0..nblk { + if nls[b] == 0 { + enc.icdf(sums[b] as usize, PULSE_COUNT[rl], 8); + } else { + enc.icdf(17, PULSE_COUNT[rl], 8); + for j in 1..nls[b] { + enc.icdf(17, count_table(j), 8); + } + enc.icdf(sums[b] as usize, count_table(nls[b]), 8); + } + } + for b in 0..nblk { + if sums[b] > 0 { + shell(enc, &hi[b * 16..(b + 1) * 16], sums[b]); + } + } + for b in 0..nblk { + if nls[b] > 0 { + for p in &pulses[b * 16..(b + 1) * 16] { + let a = p.abs(); + for j in (0..nls[b]).rev() { + enc.icdf(((a >> j) & 1) as usize, &EXC_LSB, 8); + } + } + } + } + let base = 7 * (quant_offset_type + (signal_type << 1)); + for b in 0..nblk { + let p = sums[b] | ((nls[b] as i32) << 5); + if p > 0 { + let tab = EXC_SIGN[base + ((p & 0x1F) as usize).min(6)]; + for &q in &pulses[b * 16..(b + 1) * 16] { + if q != 0 { + enc.icdf((q > 0) as usize, tab, 8); + } + } + } + } +} diff --git a/crates/opusenc/src/silk/tables.rs b/crates/opusenc/src/silk/tables.rs new file mode 100644 index 00000000..8b472726 --- /dev/null +++ b/crates/opusenc/src/silk/tables.rs @@ -0,0 +1,540 @@ +//! SILK probability tables and codebooks, transcribed from RFC 6716 §4.2 (Tables 4–53). +//! +//! PDFs are written as in the RFC (`{...}/256`) and converted at compile time to the inverse-CDF +//! form consumed by the range coder. +//! +//! Copied from FilmCraft's first-party, spec-derived `filmcraft-opus` decoder (MIT OR +//! Apache-2.0) so encoder and decoder share exactly the same values. + +/// Converts a PDF (summing to 256) into an inverse CDF (`256 - cdf[i+1]`). +pub const fn icdf(pdf: &[u8; N]) -> [u8; N] { + let mut out = [0u8; N]; + let mut acc: u32 = 0; + let mut i = 0; + while i < N { + acc += pdf[i] as u32; + out[i] = (256 - acc) as u8; + i += 1; + } + out +} + +pub const STEREO_PRED_JOINT: [u8; 25] = icdf(&[7, 2, 1, 1, 1, 10, 24, 8, 1, 1, 3, 23, 92, 23, 3, 1, 1, 8, 24, 10, 1, 1, 1, 2, 7]); +pub const UNIFORM3: [u8; 3] = icdf(&[85, 86, 85]); +pub const UNIFORM5: [u8; 5] = icdf(&[51, 51, 52, 51, 51]); +pub const STEREO_WEIGHTS_Q13: [i32; 16] = [-13732, -10050, -8266, -7526, -6500, -5000, -2950, -820, 820, 2950, 5000, 6500, 7526, 8266, 10050, 13732]; +pub const MID_ONLY: [u8; 2] = icdf(&[192, 64]); +pub const TYPE_INACTIVE: [u8; 2] = icdf(&[26, 230]); +pub const TYPE_ACTIVE: [u8; 4] = icdf(&[24, 74, 148, 10]); +pub const GAIN_MSB: [&[u8]; 3] = + [&icdf::<8>(&[32, 112, 68, 29, 12, 1, 1, 1]), &icdf::<8>(&[2, 17, 45, 60, 62, 47, 19, 4]), &icdf::<8>(&[1, 3, 26, 71, 94, 50, 9, 2])]; +pub const UNIFORM8: [u8; 8] = icdf(&[32, 32, 32, 32, 32, 32, 32, 32]); +pub const DELTA_GAIN: [u8; 41] = + icdf(&[6, 5, 11, 31, 132, 21, 8, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); +pub const NLSF_STAGE1: [&[u8]; 4] = [ + &icdf::<32>(&[44, 34, 30, 19, 21, 12, 11, 3, 3, 2, 16, 2, 2, 1, 5, 2, 1, 3, 3, 1, 1, 2, 2, 2, 3, 1, 9, 9, 2, 7, 2, 1]), + &icdf::<32>(&[1, 10, 1, 8, 3, 8, 8, 14, 13, 14, 1, 14, 12, 13, 11, 11, 12, 11, 10, 10, 11, 8, 9, 8, 7, 8, 1, 1, 6, 1, 6, 5]), + &icdf::<32>(&[31, 21, 3, 17, 1, 8, 17, 4, 1, 18, 16, 4, 2, 3, 1, 10, 1, 3, 16, 11, 16, 2, 2, 3, 2, 11, 1, 4, 9, 8, 7, 3]), + &icdf::<32>(&[1, 4, 16, 5, 18, 11, 5, 14, 15, 1, 3, 12, 13, 14, 14, 6, 14, 12, 2, 6, 1, 12, 12, 11, 10, 3, 10, 5, 1, 1, 1, 3]), +]; +pub const NLSF_STAGE2: [&[u8]; 16] = [ + &icdf::<9>(&[1, 1, 1, 15, 224, 11, 1, 1, 1]), + &icdf::<9>(&[1, 1, 2, 34, 183, 32, 1, 1, 1]), + &icdf::<9>(&[1, 1, 4, 42, 149, 55, 2, 1, 1]), + &icdf::<9>(&[1, 1, 8, 52, 123, 61, 8, 1, 1]), + &icdf::<9>(&[1, 3, 16, 53, 101, 74, 6, 1, 1]), + &icdf::<9>(&[1, 3, 17, 55, 90, 73, 15, 1, 1]), + &icdf::<9>(&[1, 7, 24, 53, 74, 67, 26, 3, 1]), + &icdf::<9>(&[1, 1, 18, 63, 78, 58, 30, 6, 1]), + &icdf::<9>(&[1, 1, 1, 9, 232, 9, 1, 1, 1]), + &icdf::<9>(&[1, 1, 2, 28, 186, 35, 1, 1, 1]), + &icdf::<9>(&[1, 1, 3, 42, 152, 53, 2, 1, 1]), + &icdf::<9>(&[1, 1, 10, 49, 126, 65, 2, 1, 1]), + &icdf::<9>(&[1, 4, 19, 48, 100, 77, 5, 1, 1]), + &icdf::<9>(&[1, 1, 14, 54, 100, 72, 12, 1, 1]), + &icdf::<9>(&[1, 1, 15, 61, 87, 61, 25, 4, 1]), + &icdf::<9>(&[1, 7, 21, 50, 77, 81, 17, 1, 1]), +]; +pub const NLSF_SEL_NBMB: [[u8; 10]; 32] = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 3, 1, 2, 2, 1, 2, 1, 1, 1], + [2, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 2, 2, 2, 2, 1, 2, 1, 1, 1], + [2, 3, 3, 3, 3, 2, 2, 2, 2, 2], + [0, 5, 3, 3, 2, 2, 2, 2, 1, 1], + [0, 2, 2, 2, 2, 2, 2, 2, 2, 1], + [2, 3, 6, 4, 4, 4, 5, 4, 5, 5], + [2, 4, 5, 5, 4, 5, 4, 6, 4, 4], + [2, 4, 4, 7, 4, 5, 4, 5, 5, 4], + [4, 3, 3, 3, 2, 3, 2, 2, 2, 2], + [1, 5, 5, 6, 4, 5, 4, 5, 5, 5], + [2, 7, 4, 6, 5, 5, 5, 5, 5, 5], + [2, 7, 5, 5, 5, 5, 5, 6, 5, 4], + [3, 3, 5, 4, 4, 5, 4, 5, 4, 4], + [2, 3, 3, 5, 5, 4, 4, 4, 4, 4], + [2, 4, 4, 6, 4, 5, 4, 5, 5, 5], + [2, 5, 4, 6, 5, 5, 5, 4, 5, 4], + [2, 7, 4, 5, 4, 5, 4, 5, 5, 5], + [2, 5, 4, 6, 7, 6, 5, 6, 5, 4], + [3, 6, 7, 4, 6, 5, 5, 6, 4, 5], + [2, 7, 6, 4, 4, 4, 5, 4, 5, 5], + [4, 5, 5, 4, 6, 6, 5, 6, 5, 4], + [2, 5, 5, 6, 5, 6, 4, 6, 4, 4], + [4, 5, 5, 5, 3, 7, 4, 5, 5, 4], + [2, 3, 4, 5, 5, 6, 4, 5, 5, 4], + [2, 3, 2, 3, 3, 4, 2, 3, 3, 3], + [1, 1, 2, 2, 2, 2, 2, 3, 2, 2], + [4, 5, 5, 6, 6, 6, 5, 6, 4, 5], + [3, 5, 5, 4, 4, 4, 4, 3, 3, 2], + [2, 5, 3, 7, 5, 5, 4, 4, 5, 4], + [4, 4, 5, 4, 5, 6, 5, 6, 5, 4], +]; +pub const NLSF_SEL_WB: [[u8; 16]; 32] = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 0, 3], + [2, 5, 5, 3, 7, 4, 4, 5, 2, 5, 4, 5, 5, 4, 3, 3], + [0, 2, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1], + [0, 6, 5, 4, 6, 4, 7, 5, 4, 4, 4, 5, 5, 4, 4, 3], + [0, 3, 5, 5, 4, 3, 3, 5, 3, 3, 3, 3, 3, 3, 2, 4], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 6, 3, 7, 2, 5, 3, 4, 5, 5, 4, 3, 3, 2, 3], + [0, 6, 2, 6, 6, 4, 5, 4, 6, 5, 4, 4, 5, 3, 3, 3], + [2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 3, 2, 2, 1, 3], + [2, 2, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 3], + [3, 4, 4, 4, 6, 4, 4, 5, 3, 5, 4, 4, 5, 4, 3, 4], + [0, 6, 4, 5, 4, 7, 5, 2, 6, 5, 7, 4, 4, 3, 5, 3], + [0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0], + [1, 6, 5, 7, 5, 4, 5, 3, 4, 5, 4, 4, 4, 3, 3, 4], + [1, 3, 3, 4, 4, 3, 3, 5, 2, 3, 3, 5, 5, 5, 3, 4], + [2, 3, 3, 2, 2, 2, 3, 2, 1, 2, 1, 2, 1, 1, 1, 4], + [0, 2, 3, 5, 3, 3, 2, 2, 2, 1, 1, 0, 0, 0, 0, 0], + [3, 4, 3, 5, 3, 3, 2, 2, 1, 1, 1, 1, 1, 2, 2, 4], + [2, 6, 3, 7, 7, 4, 5, 4, 5, 3, 5, 3, 3, 2, 3, 3], + [2, 3, 5, 6, 6, 3, 5, 3, 4, 4, 3, 3, 3, 3, 2, 4], + [1, 3, 3, 4, 4, 4, 4, 3, 5, 5, 5, 3, 1, 1, 1, 1], + [2, 5, 3, 6, 6, 4, 7, 4, 4, 5, 3, 4, 4, 3, 3, 3], + [0, 6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 6, 6, 3, 5, 2, 5, 5, 3, 4, 4, 7, 7, 4, 4, 4], + [3, 3, 7, 3, 5, 4, 3, 3, 3, 2, 2, 3, 3, 3, 2, 3], + [0, 0, 1, 0, 0, 0, 2, 1, 2, 1, 1, 2, 2, 2, 1, 1], + [0, 3, 2, 5, 3, 3, 2, 3, 2, 1, 0, 0, 1, 0, 0, 1], + [3, 5, 5, 4, 7, 5, 3, 3, 2, 3, 2, 2, 1, 0, 1, 0], + [2, 3, 5, 3, 4, 3, 3, 3, 2, 1, 2, 6, 4, 0, 0, 0], +]; +pub const NLSF_EXT: [u8; 7] = icdf(&[156, 60, 24, 9, 4, 2, 1]); +pub const NLSF_PRED_NBMB: [[i32; 9]; 2] = [[179, 138, 140, 148, 151, 149, 153, 151, 163], [116, 67, 82, 59, 92, 72, 100, 89, 92]]; +pub const NLSF_PRED_WB: [[i32; 15]; 2] = + [[175, 148, 160, 176, 178, 173, 174, 164, 177, 174, 196, 182, 198, 192, 182], [68, 62, 66, 60, 72, 117, 85, 90, 118, 136, 151, 142, 160, 142, 155]]; +pub const NLSF_PSEL_NBMB: [[u8; 9]; 32] = [ + [0, 1, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 1, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 1, 1, 0, 0, 0, 1, 0], + [0, 1, 1, 0, 0, 1, 1, 0, 0], + [0, 0, 1, 1, 0, 1, 0, 1, 1], + [0, 0, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 1, 1, 1, 1, 1, 0], + [0, 1, 0, 1, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 0], + [1, 0, 1, 1, 0, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 0, 1, 0], + [0, 0, 1, 1, 0, 1, 0, 1, 0], + [0, 0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 0, 0, 1, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 0, 1, 0], + [0, 1, 1, 0, 0, 1, 0, 1, 0], + [0, 1, 1, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 0, 0, 1, 1, 1, 1], + [0, 0, 1, 1, 0, 0, 0, 1, 1], + [0, 0, 0, 1, 0, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 0, 1, 0], + [1, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 0, 1, 0, 1], + [1, 0, 1, 1, 0, 1, 1, 1, 1], +]; +pub const NLSF_PSEL_WB: [[u8; 15]; 32] = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0], + [0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1], + [0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0], + [0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0], + [0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], + [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], + [0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0], + [0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0], + [0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0], +]; +pub const NLSF_CB_NBMB: [[i32; 10]; 32] = [ + [12, 35, 60, 83, 108, 132, 157, 180, 206, 228], + [15, 32, 55, 77, 101, 125, 151, 175, 201, 225], + [19, 42, 66, 89, 114, 137, 162, 184, 209, 230], + [12, 25, 50, 72, 97, 120, 147, 172, 200, 223], + [26, 44, 69, 90, 114, 135, 159, 180, 205, 225], + [13, 22, 53, 80, 106, 130, 156, 180, 205, 228], + [15, 25, 44, 64, 90, 115, 142, 168, 196, 222], + [19, 24, 62, 82, 100, 120, 145, 168, 190, 214], + [22, 31, 50, 79, 103, 120, 151, 170, 203, 227], + [21, 29, 45, 65, 106, 124, 150, 171, 196, 224], + [30, 49, 75, 97, 121, 142, 165, 186, 209, 229], + [19, 25, 52, 70, 93, 116, 143, 166, 192, 219], + [26, 34, 62, 75, 97, 118, 145, 167, 194, 217], + [25, 33, 56, 70, 91, 113, 143, 165, 196, 223], + [21, 34, 51, 72, 97, 117, 145, 171, 196, 222], + [20, 29, 50, 67, 90, 117, 144, 168, 197, 221], + [22, 31, 48, 66, 95, 117, 146, 168, 196, 222], + [24, 33, 51, 77, 116, 134, 158, 180, 200, 224], + [21, 28, 70, 87, 106, 124, 149, 170, 194, 217], + [26, 33, 53, 64, 83, 117, 152, 173, 204, 225], + [27, 34, 65, 95, 108, 129, 155, 174, 210, 225], + [20, 26, 72, 99, 113, 131, 154, 176, 200, 219], + [34, 43, 61, 78, 93, 114, 155, 177, 205, 229], + [23, 29, 54, 97, 124, 138, 163, 179, 209, 229], + [30, 38, 56, 89, 118, 129, 158, 178, 200, 231], + [21, 29, 49, 63, 85, 111, 142, 163, 193, 222], + [27, 48, 77, 103, 133, 158, 179, 196, 215, 232], + [29, 47, 74, 99, 124, 151, 176, 198, 220, 237], + [33, 42, 61, 76, 93, 121, 155, 174, 207, 225], + [29, 53, 87, 112, 136, 154, 170, 188, 208, 227], + [24, 30, 52, 84, 131, 150, 166, 186, 203, 229], + [37, 48, 64, 84, 104, 118, 156, 177, 201, 230], +]; +pub const NLSF_CB_WB: [[i32; 16]; 32] = [ + [7, 23, 38, 54, 69, 85, 100, 116, 131, 147, 162, 178, 193, 208, 223, 239], + [13, 25, 41, 55, 69, 83, 98, 112, 127, 142, 157, 171, 187, 203, 220, 236], + [15, 21, 34, 51, 61, 78, 92, 106, 126, 136, 152, 167, 185, 205, 225, 240], + [10, 21, 36, 50, 63, 79, 95, 110, 126, 141, 157, 173, 189, 205, 221, 237], + [17, 20, 37, 51, 59, 78, 89, 107, 123, 134, 150, 164, 184, 205, 224, 240], + [10, 15, 32, 51, 67, 81, 96, 112, 129, 142, 158, 173, 189, 204, 220, 236], + [8, 21, 37, 51, 65, 79, 98, 113, 126, 138, 155, 168, 179, 192, 209, 218], + [12, 15, 34, 55, 63, 78, 87, 108, 118, 131, 148, 167, 185, 203, 219, 236], + [16, 19, 32, 36, 56, 79, 91, 108, 118, 136, 154, 171, 186, 204, 220, 237], + [11, 28, 43, 58, 74, 89, 105, 120, 135, 150, 165, 180, 196, 211, 226, 241], + [6, 16, 33, 46, 60, 75, 92, 107, 123, 137, 156, 169, 185, 199, 214, 225], + [11, 19, 30, 44, 57, 74, 89, 105, 121, 135, 152, 169, 186, 202, 218, 234], + [12, 19, 29, 46, 57, 71, 88, 100, 120, 132, 148, 165, 182, 199, 216, 233], + [17, 23, 35, 46, 56, 77, 92, 106, 123, 134, 152, 167, 185, 204, 222, 237], + [14, 17, 45, 53, 63, 75, 89, 107, 115, 132, 151, 171, 188, 206, 221, 240], + [9, 16, 29, 40, 56, 71, 88, 103, 119, 137, 154, 171, 189, 205, 222, 237], + [16, 19, 36, 48, 57, 76, 87, 105, 118, 132, 150, 167, 185, 202, 218, 236], + [12, 17, 29, 54, 71, 81, 94, 104, 126, 136, 149, 164, 182, 201, 221, 237], + [15, 28, 47, 62, 79, 97, 115, 129, 142, 155, 168, 180, 194, 208, 223, 238], + [8, 14, 30, 45, 62, 78, 94, 111, 127, 143, 159, 175, 192, 207, 223, 239], + [17, 30, 49, 62, 79, 92, 107, 119, 132, 145, 160, 174, 190, 204, 220, 235], + [14, 19, 36, 45, 61, 76, 91, 108, 121, 138, 154, 172, 189, 205, 222, 238], + [12, 18, 31, 45, 60, 76, 91, 107, 123, 138, 154, 171, 187, 204, 221, 236], + [13, 17, 31, 43, 53, 70, 83, 103, 114, 131, 149, 167, 185, 203, 220, 237], + [17, 22, 35, 42, 58, 78, 93, 110, 125, 139, 155, 170, 188, 206, 224, 240], + [8, 15, 34, 50, 67, 83, 99, 115, 131, 146, 162, 178, 193, 209, 224, 239], + [13, 16, 41, 66, 73, 86, 95, 111, 128, 137, 150, 163, 183, 206, 225, 241], + [17, 25, 37, 52, 63, 75, 92, 102, 119, 132, 144, 160, 175, 191, 212, 231], + [19, 31, 49, 65, 83, 100, 117, 133, 147, 161, 174, 187, 200, 213, 227, 242], + [18, 31, 52, 68, 88, 103, 117, 126, 138, 149, 163, 177, 192, 207, 223, 239], + [16, 29, 47, 61, 76, 90, 106, 119, 133, 147, 161, 176, 193, 209, 224, 240], + [15, 21, 35, 50, 61, 73, 86, 97, 110, 119, 129, 141, 175, 198, 218, 237], +]; +pub const NLSF_MIN_SPACING_NBMB: [i32; 11] = [250, 3, 6, 3, 3, 3, 4, 3, 3, 3, 461]; +pub const NLSF_MIN_SPACING_WB: [i32; 17] = [100, 3, 40, 3, 3, 3, 5, 14, 14, 10, 11, 3, 8, 9, 7, 3, 347]; +pub const NLSF_INTERP: [u8; 5] = icdf(&[13, 22, 29, 11, 181]); +pub const LSF_ORDER_NBMB: [usize; 10] = [0, 9, 6, 3, 4, 5, 8, 1, 2, 7]; +pub const LSF_ORDER_WB: [usize; 16] = [0, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 1]; +pub const COS_Q12: [i32; 129] = [ + 4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3997, 3973, 3948, 3920, 3889, 3857, 3822, 3784, 3745, 3703, 3659, 3613, 3564, 3513, 3461, 3406, 3349, + 3290, 3229, 3166, 3102, 3035, 2967, 2896, 2824, 2751, 2676, 2599, 2520, 2440, 2359, 2276, 2191, 2106, 2019, 1931, 1842, 1751, 1660, 1568, 1474, 1380, 1285, + 1189, 1093, 995, 897, 799, 700, 601, 501, 401, 301, 201, 101, 0, -101, -201, -301, -401, -501, -601, -700, -799, -897, -995, -1093, -1189, -1285, -1380, + -1474, -1568, -1660, -1751, -1842, -1931, -2019, -2106, -2191, -2276, -2359, -2440, -2520, -2599, -2676, -2751, -2824, -2896, -2967, -3035, -3102, -3166, + -3229, -3290, -3349, -3406, -3461, -3513, -3564, -3613, -3659, -3703, -3745, -3784, -3822, -3857, -3889, -3920, -3948, -3973, -3997, -4017, -4036, -4052, + -4065, -4076, -4085, -4091, -4095, -4096, +]; +pub const PITCH_HIGH: [u8; 32] = icdf(&[3, 3, 6, 11, 21, 30, 32, 19, 11, 10, 12, 13, 13, 12, 11, 9, 8, 7, 6, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1]); +pub const PITCH_LOW: [&[u8]; 3] = [&icdf::<4>(&[64, 64, 64, 64]), &icdf::<6>(&[43, 42, 43, 43, 42, 43]), &icdf::<8>(&[32, 32, 32, 32, 32, 32, 32, 32])]; +pub const PITCH_DELTA: [u8; 21] = icdf(&[46, 2, 2, 3, 4, 6, 10, 15, 26, 38, 30, 22, 15, 10, 7, 6, 4, 4, 2, 2, 2]); +pub const PITCH_CONTOUR: [&[u8]; 4] = [ + &icdf::<3>(&[143, 50, 63]), + &icdf::<11>(&[68, 12, 21, 17, 19, 22, 30, 24, 17, 16, 10]), + &icdf::<12>(&[91, 46, 39, 19, 14, 12, 8, 7, 6, 5, 5, 4]), + &icdf::<34>(&[33, 22, 18, 16, 15, 14, 14, 13, 13, 10, 9, 9, 8, 6, 6, 6, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1]), +]; +pub const CB_NB_10: [[i32; 2]; 3] = [[0, 0], [1, 0], [0, 1]]; +pub const CB_NB_20: [[i32; 4]; 11] = [ + [0, 0, 0, 0], + [2, 1, 0, -1], + [-1, 0, 1, 2], + [-1, 0, 0, 1], + [-1, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 1], + [1, 1, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, -1], + [1, 0, 0, -1], +]; +pub const CB_WB_10: [[i32; 2]; 12] = [[0, 0], [0, 1], [1, 0], [-1, 1], [1, -1], [-1, 2], [2, -1], [-2, 2], [2, -2], [-2, 3], [3, -2], [-3, 3]]; +pub const CB_WB_20: [[i32; 4]; 34] = [ + [0, 0, 0, 0], + [0, 0, 1, 1], + [1, 1, 0, 0], + [-1, 0, 0, 0], + [0, 0, 0, 1], + [1, 0, 0, 0], + [-1, 0, 0, 1], + [0, 0, 0, -1], + [-1, 0, 1, 2], + [1, 0, 0, -1], + [-2, -1, 1, 2], + [2, 1, 0, -1], + [-2, 0, 0, 2], + [-2, 0, 1, 3], + [2, 1, -1, -2], + [-3, -1, 1, 3], + [2, 0, 0, -2], + [3, 1, 0, -2], + [-3, -1, 2, 4], + [-4, -1, 1, 4], + [3, 1, -1, -3], + [-4, -1, 2, 5], + [4, 2, -1, -3], + [4, 1, -1, -4], + [-5, -1, 2, 6], + [5, 2, -1, -4], + [-6, -2, 2, 6], + [-5, -2, 2, 5], + [6, 2, -1, -5], + [-7, -2, 3, 8], + [6, 2, -2, -6], + [5, 2, -2, -5], + [8, 3, -2, -7], + [-9, -3, 3, 9], +]; +pub const LTP_PERIODICITY: [u8; 3] = icdf(&[77, 80, 99]); +pub const LTP_FILTER: [&[u8]; 3] = [ + &icdf::<8>(&[185, 15, 13, 13, 9, 9, 6, 6]), + &icdf::<16>(&[57, 34, 21, 20, 15, 13, 12, 13, 10, 10, 9, 10, 9, 8, 7, 8]), + &icdf::<32>(&[15, 16, 14, 12, 12, 12, 11, 11, 11, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 6, 6, 5, 4, 5, 4, 4, 4, 3, 4, 3, 2]), +]; +pub const LTP_TAPS_0: [[i32; 5]; 8] = [ + [4, 6, 24, 7, 5], + [0, 0, 2, 0, 0], + [12, 28, 41, 13, -4], + [-9, 15, 42, 25, 14], + [1, -2, 62, 41, -9], + [-10, 37, 65, -4, 3], + [-6, 4, 66, 7, -8], + [16, 14, 38, -3, 33], +]; +pub const LTP_TAPS_1: [[i32; 5]; 16] = [ + [13, 22, 39, 23, 12], + [-1, 36, 64, 27, -6], + [-7, 10, 55, 43, 17], + [1, 1, 8, 1, 1], + [6, -11, 74, 53, -9], + [-12, 55, 76, -12, 8], + [-3, 3, 93, 27, -4], + [26, 39, 59, 3, -8], + [2, 0, 77, 11, 9], + [-8, 22, 44, -6, 7], + [40, 9, 26, 3, 9], + [-7, 20, 101, -7, 4], + [3, -8, 42, 26, 0], + [-15, 33, 68, 2, 23], + [-2, 55, 46, -2, 15], + [3, -1, 21, 16, 41], +]; +pub const LTP_TAPS_2: [[i32; 5]; 32] = [ + [-6, 27, 61, 39, 5], + [-11, 42, 88, 4, 1], + [-2, 60, 65, 6, -4], + [-1, -5, 73, 56, 1], + [-9, 19, 94, 29, -9], + [0, 12, 99, 6, 4], + [8, -19, 102, 46, -13], + [3, 2, 13, 3, 2], + [9, -21, 84, 72, -18], + [-11, 46, 104, -22, 8], + [18, 38, 48, 23, 0], + [-16, 70, 83, -21, 11], + [5, -11, 117, 22, -8], + [-6, 23, 117, -12, 3], + [3, -8, 95, 28, 4], + [-10, 15, 77, 60, -15], + [-1, 4, 124, 2, -4], + [3, 38, 84, 24, -25], + [2, 13, 42, 13, 31], + [21, -4, 56, 46, -1], + [-1, 35, 79, -13, 19], + [-7, 65, 88, -9, -14], + [20, 4, 81, 49, -29], + [20, 0, 75, 3, -17], + [5, -9, 44, 92, -8], + [1, -3, 22, 69, 31], + [-6, 95, 41, -12, 5], + [39, 67, 16, -4, 1], + [0, -6, 120, 55, -36], + [-13, 44, 122, 4, -24], + [81, 5, 11, 3, 7], + [2, 0, 9, 10, 88], +]; +pub const LTP_SCALE: [u8; 3] = icdf(&[128, 64, 64]); +pub const UNIFORM4: [u8; 4] = icdf(&[64, 64, 64, 64]); +pub const RATE_LEVEL: [&[u8]; 2] = [&icdf::<9>(&[15, 51, 12, 46, 45, 13, 33, 27, 14]), &icdf::<9>(&[33, 30, 36, 17, 34, 49, 18, 21, 18])]; +pub const PULSE_COUNT: [&[u8]; 11] = [ + &icdf::<18>(&[131, 74, 25, 8, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), + &icdf::<18>(&[58, 93, 60, 23, 7, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), + &icdf::<18>(&[43, 51, 46, 33, 24, 16, 11, 8, 6, 3, 3, 3, 2, 1, 1, 2, 1, 2]), + &icdf::<18>(&[17, 52, 71, 57, 31, 12, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]), + &icdf::<18>(&[6, 21, 41, 53, 49, 35, 21, 11, 6, 3, 2, 2, 1, 1, 1, 1, 1, 1]), + &icdf::<18>(&[7, 14, 22, 28, 29, 28, 25, 20, 17, 13, 11, 9, 7, 5, 4, 4, 3, 10]), + &icdf::<18>(&[2, 5, 14, 29, 42, 46, 41, 31, 19, 11, 6, 3, 2, 1, 1, 1, 1, 1]), + &icdf::<18>(&[1, 2, 4, 10, 19, 29, 35, 37, 34, 28, 20, 14, 8, 5, 4, 2, 2, 2]), + &icdf::<18>(&[1, 2, 2, 5, 9, 14, 20, 24, 27, 28, 26, 23, 20, 15, 11, 8, 6, 15]), + &icdf::<18>(&[1, 1, 1, 6, 27, 58, 56, 39, 25, 14, 10, 6, 3, 3, 2, 1, 1, 2]), + &icdf::<18>(&[2, 1, 6, 27, 58, 56, 39, 25, 14, 10, 6, 3, 3, 2, 1, 1, 2, 0]), +]; +pub const SHELL_16: [&[u8]; 16] = [ + &icdf::<2>(&[126, 130]), + &icdf::<3>(&[56, 142, 58]), + &icdf::<4>(&[25, 101, 104, 26]), + &icdf::<5>(&[12, 60, 108, 64, 12]), + &icdf::<6>(&[7, 35, 84, 87, 37, 6]), + &icdf::<7>(&[4, 20, 59, 86, 63, 21, 3]), + &icdf::<8>(&[3, 12, 38, 72, 75, 42, 12, 2]), + &icdf::<9>(&[2, 8, 25, 54, 73, 59, 27, 7, 1]), + &icdf::<10>(&[2, 5, 17, 39, 63, 65, 42, 18, 4, 1]), + &icdf::<11>(&[1, 4, 12, 28, 49, 63, 54, 30, 11, 3, 1]), + &icdf::<12>(&[1, 4, 8, 20, 37, 55, 57, 41, 22, 8, 2, 1]), + &icdf::<13>(&[1, 3, 7, 15, 28, 44, 53, 48, 33, 16, 6, 1, 1]), + &icdf::<14>(&[1, 2, 6, 12, 21, 35, 47, 48, 40, 25, 12, 5, 1, 1]), + &icdf::<15>(&[1, 1, 4, 10, 17, 27, 37, 47, 43, 33, 21, 9, 4, 1, 1]), + &icdf::<16>(&[1, 1, 1, 8, 14, 22, 33, 40, 43, 38, 28, 16, 8, 1, 1, 1]), + &icdf::<17>(&[1, 1, 1, 1, 13, 18, 27, 36, 41, 41, 34, 24, 14, 1, 1, 1, 1]), +]; +pub const SHELL_8: [&[u8]; 16] = [ + &icdf::<2>(&[127, 129]), + &icdf::<3>(&[53, 149, 54]), + &icdf::<4>(&[22, 105, 106, 23]), + &icdf::<5>(&[11, 61, 111, 63, 10]), + &icdf::<6>(&[6, 35, 86, 88, 36, 5]), + &icdf::<7>(&[4, 20, 59, 87, 62, 21, 3]), + &icdf::<8>(&[3, 13, 40, 71, 73, 41, 13, 2]), + &icdf::<9>(&[3, 9, 27, 53, 70, 56, 28, 9, 1]), + &icdf::<10>(&[3, 8, 19, 37, 57, 61, 44, 20, 6, 1]), + &icdf::<11>(&[3, 7, 15, 28, 44, 54, 49, 33, 17, 5, 1]), + &icdf::<12>(&[1, 7, 13, 22, 34, 46, 48, 38, 28, 14, 4, 1]), + &icdf::<13>(&[1, 1, 11, 22, 27, 35, 42, 47, 33, 25, 10, 1, 1]), + &icdf::<14>(&[1, 1, 6, 14, 26, 37, 43, 43, 37, 26, 14, 6, 1, 1]), + &icdf::<15>(&[1, 1, 4, 10, 20, 31, 40, 42, 40, 31, 20, 10, 4, 1, 1]), + &icdf::<16>(&[1, 1, 3, 8, 16, 26, 35, 38, 38, 35, 26, 16, 8, 3, 1, 1]), + &icdf::<17>(&[1, 1, 2, 6, 12, 21, 30, 36, 38, 36, 30, 21, 12, 6, 2, 1, 1]), +]; +pub const SHELL_4: [&[u8]; 16] = [ + &icdf::<2>(&[127, 129]), + &icdf::<3>(&[49, 157, 50]), + &icdf::<4>(&[20, 107, 109, 20]), + &icdf::<5>(&[11, 60, 113, 62, 10]), + &icdf::<6>(&[7, 36, 84, 87, 36, 6]), + &icdf::<7>(&[6, 24, 57, 82, 60, 23, 4]), + &icdf::<8>(&[5, 18, 39, 64, 68, 42, 16, 4]), + &icdf::<9>(&[6, 14, 29, 47, 61, 52, 30, 14, 3]), + &icdf::<10>(&[1, 15, 23, 35, 51, 50, 40, 30, 10, 1]), + &icdf::<11>(&[1, 1, 21, 32, 42, 52, 46, 41, 18, 1, 1]), + &icdf::<12>(&[1, 6, 16, 27, 36, 42, 42, 36, 27, 16, 6, 1]), + &icdf::<13>(&[1, 5, 12, 21, 31, 38, 40, 38, 31, 21, 12, 5, 1]), + &icdf::<14>(&[1, 3, 9, 17, 26, 34, 38, 38, 34, 26, 17, 9, 3, 1]), + &icdf::<15>(&[1, 3, 7, 14, 22, 29, 34, 36, 34, 29, 22, 14, 7, 3, 1]), + &icdf::<16>(&[1, 2, 5, 11, 18, 25, 31, 35, 35, 31, 25, 18, 11, 5, 2, 1]), + &icdf::<17>(&[1, 1, 4, 9, 15, 21, 28, 32, 34, 32, 28, 21, 15, 9, 4, 1, 1]), +]; +pub const SHELL_2: [&[u8]; 16] = [ + &icdf::<2>(&[128, 128]), + &icdf::<3>(&[42, 172, 42]), + &icdf::<4>(&[21, 107, 107, 21]), + &icdf::<5>(&[12, 60, 112, 61, 11]), + &icdf::<6>(&[8, 34, 86, 86, 35, 7]), + &icdf::<7>(&[8, 23, 55, 90, 55, 20, 5]), + &icdf::<8>(&[5, 15, 38, 72, 72, 36, 15, 3]), + &icdf::<9>(&[6, 12, 27, 52, 77, 47, 20, 10, 5]), + &icdf::<10>(&[6, 19, 28, 35, 40, 40, 35, 28, 19, 6]), + &icdf::<11>(&[4, 14, 22, 31, 37, 40, 37, 31, 22, 14, 4]), + &icdf::<12>(&[3, 10, 18, 26, 33, 38, 38, 33, 26, 18, 10, 3]), + &icdf::<13>(&[2, 8, 13, 21, 29, 36, 38, 36, 29, 21, 13, 8, 2]), + &icdf::<14>(&[1, 5, 10, 17, 25, 32, 38, 38, 32, 25, 17, 10, 5, 1]), + &icdf::<15>(&[1, 4, 7, 13, 21, 29, 35, 36, 35, 29, 21, 13, 7, 4, 1]), + &icdf::<16>(&[1, 2, 5, 10, 17, 25, 32, 36, 36, 32, 25, 17, 10, 5, 2, 1]), + &icdf::<17>(&[1, 2, 4, 7, 13, 21, 28, 34, 36, 34, 28, 21, 13, 7, 4, 2, 1]), +]; +pub const EXC_LSB: [u8; 2] = icdf(&[136, 120]); +pub const EXC_SIGN: [&[u8]; 42] = [ + &icdf::<2>(&[2, 254]), + &icdf::<2>(&[207, 49]), + &icdf::<2>(&[189, 67]), + &icdf::<2>(&[179, 77]), + &icdf::<2>(&[174, 82]), + &icdf::<2>(&[163, 93]), + &icdf::<2>(&[157, 99]), + &icdf::<2>(&[58, 198]), + &icdf::<2>(&[245, 11]), + &icdf::<2>(&[238, 18]), + &icdf::<2>(&[232, 24]), + &icdf::<2>(&[225, 31]), + &icdf::<2>(&[220, 36]), + &icdf::<2>(&[211, 45]), + &icdf::<2>(&[1, 255]), + &icdf::<2>(&[210, 46]), + &icdf::<2>(&[190, 66]), + &icdf::<2>(&[178, 78]), + &icdf::<2>(&[169, 87]), + &icdf::<2>(&[162, 94]), + &icdf::<2>(&[152, 104]), + &icdf::<2>(&[48, 208]), + &icdf::<2>(&[242, 14]), + &icdf::<2>(&[235, 21]), + &icdf::<2>(&[224, 32]), + &icdf::<2>(&[214, 42]), + &icdf::<2>(&[205, 51]), + &icdf::<2>(&[190, 66]), + &icdf::<2>(&[1, 255]), + &icdf::<2>(&[162, 94]), + &icdf::<2>(&[152, 104]), + &icdf::<2>(&[147, 109]), + &icdf::<2>(&[144, 112]), + &icdf::<2>(&[141, 115]), + &icdf::<2>(&[138, 118]), + &icdf::<2>(&[8, 248]), + &icdf::<2>(&[203, 53]), + &icdf::<2>(&[187, 69]), + &icdf::<2>(&[176, 80]), + &icdf::<2>(&[168, 88]), + &icdf::<2>(&[161, 95]), + &icdf::<2>(&[154, 102]), +]; +pub const LBRR_FLAGS: [&[u8]; 2] = [&icdf::<3>(&[53, 53, 150]), &icdf::<7>(&[41, 20, 29, 41, 15, 28, 82])]; diff --git a/crates/opusenc/tests/common/mod.rs b/crates/opusenc/tests/common/mod.rs new file mode 100644 index 00000000..9b420f31 --- /dev/null +++ b/crates/opusenc/tests/common/mod.rs @@ -0,0 +1,285 @@ +//! Shared helpers for the SILK/hybrid round-trip tests: synthetic signals, encoding, decoding +//! with FilmCraft's spec-derived decoder, delay-searched SNR and an Ogg Opus writer for ffmpeg. +#![allow(dead_code)] + +use effectcraft_opusenc::OpusEncoder; + +pub const SR: f64 = 48_000.0; +pub const FRAME: usize = OpusEncoder::FRAME_SIZE; + +/// Two-pole resonator state. +struct Reson { + y1: f64, + y2: f64, +} + +impl Reson { + fn run(&mut self, x: f64, f: f64, bw: f64) -> f64 { + let r = (-std::f64::consts::PI * bw / SR).exp(); + let a1 = 2.0 * r * (2.0 * std::f64::consts::PI * f / SR).cos(); + let a2 = -r * r; + let g = 1.0 - r; + let y = g * x + a1 * self.y1 + a2 * self.y2; + self.y2 = self.y1; + self.y1 = y; + y + } +} + +/// Speech-like test signal: a glottal pulse train (100–200 Hz, gliding pitch) through three +/// formant resonators whose frequencies change per syllable, with syllable envelopes, short +/// pauses and a little aspiration noise. +pub fn speech(len: usize, seed: u32) -> Vec { + let vowels: [[f64; 3]; 5] = [[730.0, 1090.0, 2440.0], [270.0, 2290.0, 3010.0], [530.0, 1840.0, 2480.0], [570.0, 840.0, 2410.0], [300.0, 870.0, 2240.0]]; + let syl = (0.24 * SR) as usize; + let mut out = Vec::with_capacity(len); + let mut res = [Reson { y1: 0.0, y2: 0.0 }, Reson { y1: 0.0, y2: 0.0 }, Reson { y1: 0.0, y2: 0.0 }]; + let mut phase = 0.0f64; + let mut s = seed; + let mut prev_glottal = 0.0; + for n in 0..len { + let t = n as f64 / SR; + let k = n / syl; + let pos = (n % syl) as f64 / syl as f64; + // Every fourth syllable is a pause. + let pause = k % 4 == 3; + let env = if pause { 0.0 } else { (std::f64::consts::PI * pos).sin().powf(0.7) }; + let f0 = 100.0 + 50.0 * (1.0 + (2.0 * std::f64::consts::PI * 0.9 * t).sin()) + 15.0 * ((k * 7) % 5) as f64 - 30.0 * pos; + phase += f0 / SR; + if phase >= 1.0 { + phase -= 1.0; + } + // Rosenberg-like glottal flow; its derivative excites the vocal tract. + let g = if phase < 0.4 { + 0.5 * (1.0 - (std::f64::consts::PI * phase / 0.4).cos()) + } else if phase < 0.56 { + (std::f64::consts::FRAC_PI_2 * (phase - 0.4) / 0.16).cos() + } else { + 0.0 + }; + let dg = (g - prev_glottal) * 40.0; + prev_glottal = g; + s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223); + let noise = ((s >> 8) as f64 / (1u32 << 24) as f64 - 0.5) * 0.02; + let v = vowels[(k * 3 + 1) % 5]; + let w = vowels[(k * 3 + 4) % 5]; + let mix = pos; + let mut x = dg + noise; + let mut y = 0.0; + for (i, r) in res.iter_mut().enumerate() { + let f = v[i] * (1.0 - mix) + w[i] * mix; + y += r.run(x, f, 60.0 + 40.0 * i as f64) * [1.0, 0.6, 0.3][i]; + } + x = y * env * 1.2; + out.push(x as f32); + } + let peak = out.iter().fold(0f32, |m, v| m.max(v.abs())); + let g = 0.5 / peak.max(1e-9); + out.iter().map(|v| v * g).collect() +} + +/// Music-like test signal: three-note chords of harmonic tones (six partials each), changing +/// every half second with soft attacks and releases. +pub fn music(len: usize) -> Vec { + let chords: [[f64; 3]; 4] = [[261.63, 329.63, 392.0], [220.0, 277.18, 329.63], [174.61, 220.0, 261.63], [196.0, 246.94, 293.66]]; + let seg = (0.5 * SR) as usize; + (0..len) + .map(|n| { + let t = n as f64 / SR; + let k = n / seg; + let pos = (n % seg) as f64 / seg as f64; + let env = (pos * 20.0).min(1.0) * ((1.0 - pos) * 10.0).min(1.0); + let mut x = 0.0; + for (j, &f) in chords[k % 4].iter().enumerate() { + for h in 1..=6 { + x += (2.0 * std::f64::consts::PI * f * h as f64 * t + j as f64).sin() / h as f64; + } + } + (0.12 * env * x) as f32 + }) + .collect() +} + +pub fn interleave(planar: &[Vec]) -> Vec { + let c = planar.len(); + let len = planar[0].len(); + let mut pcm = vec![0f32; len * c]; + for (ch, p) in planar.iter().enumerate() { + for (i, &v) in p.iter().enumerate() { + pcm[i * c + ch] = v; + } + } + pcm +} + +/// Encodes planar input (padding the tail so the encoder delay is flushed). +pub fn encode(mut enc: OpusEncoder, planar: &[Vec]) -> (OpusEncoder, Vec>) { + let c = planar.len(); + let len = planar[0].len(); + let total = len + enc.pre_skip() as usize + 16; + let frames = total.div_ceil(FRAME); + let mut pcm = interleave(planar); + pcm.resize(frames * FRAME * c, 0.0); + let packets = pcm.chunks(FRAME * c).map(|f| enc.encode_float(f)).collect(); + (enc, packets) +} + +/// Decodes with filmcraft-opus, drops the pre-skip and returns planar channels (full length). +pub fn decode(head: &[u8], packets: &[Vec]) -> Vec> { + let mut dec = filmcraft_opus::Decoder::new(head).expect("OpusHead"); + dec.set_trim_pre_skip(true); + let mut out: Vec> = vec![Vec::new(); dec.channels()]; + for p in packets { + let planes = dec.decode(Some(p)).expect("decode"); + for (o, p) in out.iter_mut().zip(planes) { + o.extend_from_slice(&p); + } + } + out +} + +/// Waveform SNR (dB) of `dec[i + shift]` against `orig[i]`, excluding the first and last 20 ms. +pub fn snr_at(orig: &[f32], dec: &[f32], shift: isize) -> f64 { + let (mut s, mut e) = (0f64, 0f64); + for i in FRAME..orig.len() - FRAME { + let j = i as isize + shift; + let d = if j >= 0 && (j as usize) < dec.len() { dec[j as usize] as f64 } else { 0.0 }; + s += (orig[i] as f64).powi(2); + e += (orig[i] as f64 - d).powi(2); + } + 10.0 * (s / e.max(1e-30)).log10() +} + +/// Best SNR over residual delays of ±`range` samples: (SNR dB, delay). +pub fn snr_search(orig: &[f32], dec: &[f32], range: isize) -> (f64, isize) { + (-range..=range).map(|d| (snr_at(orig, dec, d), d)).fold((f64::MIN, 0), |a, b| if b.0 > a.0 { b } else { a }) +} + +/// Segmental SNR (dB): mean over 20 ms segments holding signal (each clamped to [-10, 40]). +pub fn seg_snr(orig: &[f32], dec: &[f32], shift: isize) -> f64 { + let mut acc = 0f64; + let mut n = 0; + let mut i = FRAME; + while i + FRAME <= orig.len() - FRAME { + let (mut s, mut e) = (0f64, 0f64); + for k in i..i + FRAME { + let j = k as isize + shift; + let d = if j >= 0 && (j as usize) < dec.len() { dec[j as usize] as f64 } else { 0.0 }; + s += (orig[k] as f64).powi(2); + e += (orig[k] as f64 - d).powi(2); + } + if s / FRAME as f64 > 1e-5 { + acc += (10.0 * (s / e.max(1e-30)).log10()).clamp(-10.0, 40.0); + n += 1; + } + i += FRAME; + } + acc / n.max(1) as f64 +} + +/// Average bitrate (bits per second) of a packet sequence of 20 ms packets. +pub fn bitrate(packets: &[Vec]) -> f64 { + packets.iter().map(|p| p.len() * 8).sum::() as f64 / (packets.len() as f64 * 0.02) +} + +/// Low-pass filtered copy (FFT brick wall) used to compare band-limited modes fairly. +pub fn lowpass(x: &[f32], fc: f64) -> Vec { + let n = x.len().next_power_of_two(); + let mut re: Vec = x.iter().map(|&v| v as f64).collect(); + re.resize(n, 0.0); + let mut im = vec![0f64; n]; + fft(&mut re, &mut im, false); + for k in 0..n { + let f = (k.min(n - k)) as f64 * SR / n as f64; + if f > fc { + re[k] = 0.0; + im[k] = 0.0; + } + } + fft(&mut re, &mut im, true); + re[..x.len()].iter().map(|&v| (v / n as f64) as f32).collect() +} + +pub fn fft(re: &mut [f64], im: &mut [f64], inverse: bool) { + let n = re.len(); + let mut j = 0; + for i in 1..n { + let mut bit = n >> 1; + while j & bit != 0 { + j ^= bit; + bit >>= 1; + } + j |= bit; + if i < j { + re.swap(i, j); + im.swap(i, j); + } + } + let mut len = 2; + while len <= n { + let ang = if inverse { 2.0 } else { -2.0 } * std::f64::consts::PI / len as f64; + for s in (0..n).step_by(len) { + for k in 0..len / 2 { + let (wr, wi) = ((ang * k as f64).cos(), (ang * k as f64).sin()); + let (a, b) = (s + k, s + k + len / 2); + let tr = re[b] * wr - im[b] * wi; + let ti = re[b] * wi + im[b] * wr; + re[b] = re[a] - tr; + im[b] = im[a] - ti; + re[a] += tr; + im[a] += ti; + } + } + len <<= 1; + } +} + +// ---- Ogg Opus (RFC 3533 + RFC 7845) for the ffmpeg oracle ---- + +fn ogg_crc(data: &[u8]) -> u32 { + let mut crc = 0u32; + for &b in data { + crc ^= (b as u32) << 24; + for _ in 0..8 { + crc = if crc & 0x8000_0000 != 0 { (crc << 1) ^ 0x04C1_1DB7 } else { crc << 1 }; + } + } + crc +} + +fn ogg_page(out: &mut Vec, packet: &[u8], header_type: u8, granule: u64, seq: u32) { + let mut lacing = vec![255u8; packet.len() / 255]; + lacing.push((packet.len() % 255) as u8); + let start = out.len(); + out.extend_from_slice(b"OggS"); + out.push(0); + out.push(header_type); + out.extend_from_slice(&granule.to_le_bytes()); + out.extend_from_slice(&0x4543_4F50u32.to_le_bytes()); + out.extend_from_slice(&seq.to_le_bytes()); + out.extend_from_slice(&0u32.to_le_bytes()); + out.push(lacing.len() as u8); + out.extend_from_slice(&lacing); + out.extend_from_slice(packet); + let crc = ogg_crc(&out[start..]); + out[start + 22..start + 26].copy_from_slice(&crc.to_le_bytes()); +} + +/// An Ogg Opus file holding `packets` for `len` samples of audio. +pub fn ogg_opus(enc: &OpusEncoder, packets: &[Vec], len: usize) -> Vec { + let mut out = Vec::new(); + ogg_page(&mut out, &enc.opus_head(), 0x02, 0, 0); + let vendor = b"effectcraft-opusenc"; + let mut tags = b"OpusTags".to_vec(); + tags.extend_from_slice(&(vendor.len() as u32).to_le_bytes()); + tags.extend_from_slice(vendor); + tags.extend_from_slice(&0u32.to_le_bytes()); + ogg_page(&mut out, &tags, 0, 0, 1); + let pre_skip = enc.pre_skip() as u64; + for (i, p) in packets.iter().enumerate() { + let last = i + 1 == packets.len(); + let granule = if last { pre_skip + len as u64 } else { ((i + 1) * FRAME) as u64 }; + ogg_page(&mut out, p, if last { 0x04 } else { 0 }, granule, i as u32 + 2); + } + out +} diff --git a/crates/opusenc/tests/ffmpeg.rs b/crates/opusenc/tests/ffmpeg.rs new file mode 100644 index 00000000..672426cc --- /dev/null +++ b/crates/opusenc/tests/ffmpeg.rs @@ -0,0 +1,81 @@ +//! ffmpeg as a second, external oracle: every mode/bandwidth written as Ogg Opus must decode +//! without errors with both ffmpeg's native `opus` decoder and `libopus`, honour the pre-skip +//! and end granule, and reproduce the input. Skipped when ffmpeg is not installed. + +mod common; + +use std::process::Command; + +use common::*; +use effectcraft_opusenc::{Bandwidth, Mode, OpusEncoder}; + +const FFMPEG: &str = "/opt/homebrew/bin/ffmpeg"; + +fn ffmpeg() -> Option<&'static str> { + [FFMPEG, "ffmpeg"].into_iter().find(|exe| Command::new(exe).arg("-version").output().is_ok_and(|o| o.status.success())) +} + +/// Decodes an Ogg Opus file with ffmpeg's decoder `codec`; returns interleaved f32 output. +fn ffmpeg_decode(exe: &str, codec: &str, file: &[u8], channels: usize, tag: &str) -> Option> { + let has = Command::new(exe).args(["-hide_banner", "-decoders"]).output().ok()?; + if !String::from_utf8_lossy(&has.stdout).lines().any(|l| l.split_whitespace().nth(1) == Some(codec)) { + eprintln!("ffmpeg has no {codec} decoder; skipping it"); + return None; + } + let path = std::env::temp_dir().join(format!("effectcraft-opusenc-{}-{tag}.opus", std::process::id())); + std::fs::write(&path, file).unwrap(); + let out = Command::new(exe) + .args(["-v", "error", "-c:a", codec, "-i"]) + .arg(&path) + .args(["-f", "f32le", "-ac", &channels.to_string(), "-ar", "48000", "-"]) + .output() + .expect("run ffmpeg"); + let _ = std::fs::remove_file(&path); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!(out.status.success(), "ffmpeg ({codec}) failed: {stderr}"); + // ffmpeg's native decoder also makes its raw-PCM muxer print a timestamp complaint for + // narrowband SILK streams written by libopus itself; that line is about ffmpeg's own + // timestamping, not the bitstream, so it is the one message tolerated. + let errors: Vec<&str> = stderr.lines().filter(|l| !l.trim().is_empty() && !l.contains("non monotonically increasing dts to muxer")).collect(); + assert!(errors.is_empty(), "ffmpeg ({codec}) reported errors: {errors:?}"); + Some(out.stdout.chunks_exact(4).map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()) +} + +#[test] +fn ffmpeg_decodes_every_mode() { + let Some(exe) = ffmpeg() else { + eprintln!("ffmpeg not installed; skipping the oracle test"); + return; + }; + let len = 48_000; + let sp = speech(len + 30_000, 3); + let left = sp[..len].to_vec(); + let right: Vec = sp[30_000..].iter().map(|v| v * 0.7).collect(); + let cases = [ + (1usize, Mode::Silk, Bandwidth::Narrowband, 8_000u32, 3_400.0, 8.0), + (1, Mode::Silk, Bandwidth::Mediumband, 11_000, 5_000.0, 10.0), + (1, Mode::Silk, Bandwidth::Wideband, 16_000, 7_000.0, 12.0), + (2, Mode::Silk, Bandwidth::Wideband, 32_000, 7_000.0, 12.0), + (1, Mode::Hybrid, Bandwidth::SuperWideband, 24_000, 24_000.0, 10.0), + (1, Mode::Hybrid, Bandwidth::Fullband, 32_000, 24_000.0, 10.0), + (2, Mode::Hybrid, Bandwidth::Fullband, 64_000, 24_000.0, 10.0), + (2, Mode::Celt, Bandwidth::Fullband, 96_000, 24_000.0, 10.0), + ]; + for (c, mode, bw, rate, fc, min_snr) in cases { + let planar: Vec> = if c == 1 { vec![left.clone()] } else { vec![left.clone(), right.clone()] }; + let (enc, packets) = encode(OpusEncoder::with_mode(c as u8, rate, mode, bw), &planar); + let file = ogg_opus(&enc, &packets, len); + for codec in ["opus", "libopus"] { + let Some(dec) = ffmpeg_decode(exe, codec, &file, c, &format!("{mode:?}{bw:?}{c}{codec}")) else { + continue; + }; + assert_eq!(dec.len(), len * c, "{codec}: pre-skip and end granule must be honoured"); + for (ch, x) in planar.iter().enumerate() { + let d: Vec = (0..len).map(|i| dec[i * c + ch]).collect(); + let (snr, delay) = snr_search(&lowpass(x, fc), &d, 6); + eprintln!("ffmpeg {codec:7} {mode:?}/{bw:?} {c} ch @ {} kb/s, channel {ch}: SNR {snr:.1} dB (residual delay {delay})", rate / 1000); + assert!(snr > min_snr, "{codec} {mode:?}/{bw:?}: SNR {snr:.1} dB"); + } + } + } +} diff --git a/crates/opusenc/tests/modes.rs b/crates/opusenc/tests/modes.rs new file mode 100644 index 00000000..843cf3d9 --- /dev/null +++ b/crates/opusenc/tests/modes.rs @@ -0,0 +1,272 @@ +//! SILK, hybrid and CELT round trips through FilmCraft's spec-derived Opus decoder: every +//! mode/bandwidth decodes, waveform quality on speech-like and music-like signals, bitrate +//! tracking, stereo, the application tables and delay alignment. + +mod common; + +use common::*; +use effectcraft_opusenc::{Application, Bandwidth, Mode, OpusEncoder}; + +struct Run { + snr: f64, + seg: f64, + delay: isize, + kbps: f64, + packets: Vec>, +} + +/// Encodes `planar`, decodes, and measures each channel against `reference` (searching a +/// residual delay of up to ±6 samples). +fn run(enc: OpusEncoder, planar: &[Vec], reference: &[Vec]) -> Vec { + let (enc, packets) = encode(enc, planar); + let dec = decode(&enc.opus_head(), &packets); + let kbps = bitrate(&packets) / 1000.0; + reference + .iter() + .zip(&dec) + .map(|(r, d)| { + assert!(d.len() >= r.len(), "decoded {} < {}", d.len(), r.len()); + assert!(d.iter().all(|v| v.is_finite())); + let (snr, delay) = snr_search(r, d, 6); + Run { snr, seg: seg_snr(r, d, delay), delay, kbps, packets: packets.clone() } + }) + .collect() +} + +fn mono(mode: Mode, bw: Bandwidth, rate: u32, x: &[f32], fc: f64) -> Run { + let reference = if fc < 20_000.0 { lowpass(x, fc) } else { x.to_vec() }; + run(OpusEncoder::with_mode(1, rate, mode, bw), &[x.to_vec()], &[reference]).remove(0) +} + +/// Upper edge of the band a SILK bandwidth keeps (for band-limited references). +fn band_edge(bw: Bandwidth) -> f64 { + match bw { + Bandwidth::Narrowband => 3_400.0, + Bandwidth::Mediumband => 5_000.0, + Bandwidth::Wideband => 7_000.0, + _ => 24_000.0, + } +} + +const LEN: usize = 96_000; + +#[test] +fn every_mode_and_bandwidth_decodes() { + let sp = speech(LEN + 40_000, 11); + let left = sp[..LEN].to_vec(); + let right: Vec = sp[40_000..].iter().map(|v| v * 0.6).collect(); + for (mode, bw, config, rate) in [ + (Mode::Silk, Bandwidth::Narrowband, 1u8, 8_000u32), + (Mode::Silk, Bandwidth::Mediumband, 5, 11_000), + (Mode::Silk, Bandwidth::Wideband, 9, 16_000), + (Mode::Hybrid, Bandwidth::SuperWideband, 13, 24_000), + (Mode::Hybrid, Bandwidth::Fullband, 15, 32_000), + (Mode::Celt, Bandwidth::Fullband, 31, 64_000), + ] { + for c in [1usize, 2] { + let planar = if c == 1 { vec![left.clone()] } else { vec![left.clone(), right.clone()] }; + let reference: Vec> = planar.iter().map(|x| lowpass(x, band_edge(bw))).collect(); + let enc = OpusEncoder::with_mode(c as u8, rate * c as u32, mode, bw); + assert_eq!((enc.mode(), enc.bandwidth()), (mode, bw)); + let res = run(enc, &planar, &reference); + for p in &res[0].packets { + assert_eq!(p[0], (config << 3) | if c == 2 { 4 } else { 0 }, "TOC of {mode:?}/{bw:?}"); + assert!(p.len() >= 3 && p.len() <= 1276); + } + for (ch, r) in res.iter().enumerate() { + eprintln!( + "{mode:?}/{bw:?} {c} ch @ {} kb/s: {:.1} kb/s, channel {ch} SNR {:.1} dB, segSNR {:.1} dB, residual delay {}", + rate * c as u32 / 1000, + r.kbps, + r.snr, + r.seg, + r.delay + ); + assert!(r.snr > 8.0, "{mode:?}/{bw:?} {c} ch channel {ch}: SNR {:.1} dB", r.snr); + assert!(r.delay.abs() <= 1, "{mode:?}/{bw:?}: residual delay {} (pre-skip mismatch)", r.delay); + } + } + } +} + +#[test] +fn silk_speech_quality() { + let x = speech(LEN, 7); + for (bw, rate, min_snr) in [ + (Bandwidth::Narrowband, 8_000u32, 10.0), + (Bandwidth::Mediumband, 11_000, 14.0), + (Bandwidth::Wideband, 12_000, 14.0), + (Bandwidth::Wideband, 16_000, 18.0), + (Bandwidth::Wideband, 24_000, 24.0), + ] { + let r = mono(Mode::Silk, bw, rate, &x, band_edge(bw)); + eprintln!("SILK {bw:?} speech @ {} kb/s: {:.1} kb/s, SNR {:.1} dB, segSNR {:.1} dB", rate / 1000, r.kbps, r.snr, r.seg); + assert!(r.snr > min_snr && r.seg > min_snr - 3.0, "SILK {bw:?} @ {rate}: SNR {:.1} / seg {:.1}", r.snr, r.seg); + } +} + +#[test] +fn silk_beats_celt_on_speech_at_low_rate() { + let x = speech(LEN, 5); + // Both measured against the full-band input. + let silk = mono(Mode::Silk, Bandwidth::Wideband, 12_000, &x, 24_000.0); + let celt = mono(Mode::Celt, Bandwidth::Fullband, 12_000, &x, 24_000.0); + eprintln!( + "12 kb/s mono speech: SILK WB {:.1} dB (seg {:.1}) at {:.1} kb/s vs CELT {:.1} dB (seg {:.1}) at {:.1} kb/s", + silk.snr, silk.seg, silk.kbps, celt.snr, celt.seg, celt.kbps + ); + assert!(silk.snr > celt.snr + 3.0, "SILK {:.1} dB vs CELT {:.1} dB", silk.snr, celt.snr); + assert!(silk.seg > celt.seg + 3.0, "SILK seg {:.1} dB vs CELT seg {:.1} dB", silk.seg, celt.seg); +} + +#[test] +fn hybrid_quality() { + let sp = speech(LEN, 9); + let mu = music(LEN); + for (name, x, bw, rate, min_snr) in [ + ("speech", &sp, Bandwidth::SuperWideband, 24_000u32, 18.0), + ("speech", &sp, Bandwidth::Fullband, 32_000, 20.0), + ("music", &mu, Bandwidth::SuperWideband, 24_000, 9.0), + ("music", &mu, Bandwidth::Fullband, 32_000, 10.0), + ] { + let r = mono(Mode::Hybrid, bw, rate, x, 24_000.0); + eprintln!("hybrid {bw:?} {name} @ {} kb/s: {:.1} kb/s, SNR {:.1} dB, segSNR {:.1} dB", rate / 1000, r.kbps, r.snr, r.seg); + assert!(r.snr > min_snr, "hybrid {bw:?} {name}: SNR {:.1} dB", r.snr); + // Constant packet size. + assert!(r.packets.iter().all(|p| p.len() == rate as usize / 400)); + } +} + +#[test] +fn silk_music_quality() { + let x = music(LEN); + for (rate, min_snr) in [(12_000u32, 5.0), (16_000, 8.0), (24_000, 13.0)] { + let r = mono(Mode::Silk, Bandwidth::Wideband, rate, &x, 7_000.0); + eprintln!("SILK WB music @ {} kb/s: {:.1} kb/s, SNR {:.1} dB", rate / 1000, r.kbps, r.snr); + assert!(r.snr > min_snr, "SILK music @ {rate}: SNR {:.1} dB", r.snr); + } +} + +#[test] +fn bitrate_tracking() { + let sp = speech(LEN, 13); + let mu = music(LEN); + for (name, x) in [("speech", &sp), ("music", &mu)] { + for (mode, bw, rate) in [ + (Mode::Silk, Bandwidth::Narrowband, 6_000u32), + (Mode::Silk, Bandwidth::Narrowband, 9_000), + (Mode::Silk, Bandwidth::Mediumband, 11_000), + (Mode::Silk, Bandwidth::Wideband, 12_000), + (Mode::Silk, Bandwidth::Wideband, 20_000), + (Mode::Silk, Bandwidth::Wideband, 32_000), + (Mode::Hybrid, Bandwidth::SuperWideband, 20_000), + (Mode::Hybrid, Bandwidth::Fullband, 36_000), + ] { + let r = mono(mode, bw, rate, x, band_edge(bw)); + let ratio = r.kbps * 1000.0 / rate as f64; + eprintln!("{name} {mode:?}/{bw:?} target {} kb/s: actual {:.2} kb/s ({:+.0} %), SNR {:.1} dB", rate / 1000, r.kbps, (ratio - 1.0) * 100.0, r.snr); + assert!((0.75..=1.25).contains(&ratio), "{name} {mode:?}/{bw:?} @ {rate}: {:.2} kb/s", r.kbps); + } + } +} + +#[test] +fn stereo_silk_and_hybrid() { + let sp = speech(LEN + 40_000, 21); + let a = sp[..LEN].to_vec(); + let b: Vec = sp[40_000..].iter().map(|v| v * 0.7).collect(); + let mu = music(LEN); + let cases: Vec<(&str, Vec, Vec)> = vec![ + ("dual mono", a.clone(), a.clone()), + ("panned", a.iter().map(|v| v * 0.9).collect(), a.iter().map(|v| v * 0.35).collect()), + ("independent", a.clone(), b), + ("speech+music", a.iter().zip(&mu).map(|(x, y)| 0.8 * x + 0.3 * y).collect(), a.iter().zip(&mu).map(|(x, y)| 0.3 * x + 0.8 * y).collect()), + ]; + for (name, l, r) in &cases { + for (mode, bw, rate, min_snr) in [(Mode::Silk, Bandwidth::Wideband, 32_000u32, 9.0), (Mode::Hybrid, Bandwidth::Fullband, 64_000, 9.0)] { + let fc = band_edge(bw); + let res = run(OpusEncoder::with_mode(2, rate, mode, bw), &[l.clone(), r.clone()], &[lowpass(l, fc), lowpass(r, fc)]); + eprintln!("stereo {name} {mode:?}/{bw:?} @ {} kb/s: {:.1} kb/s, L {:.1} dB, R {:.1} dB", rate / 1000, res[0].kbps, res[0].snr, res[1].snr); + assert!(res[0].snr > min_snr && res[1].snr > min_snr, "stereo {name} {mode:?}: L {:.1} R {:.1}", res[0].snr, res[1].snr); + } + } +} + +#[test] +fn application_tables_and_pre_skip() { + let cases = [ + (Application::Voip, 1u8, 8_000u32, Mode::Silk, Bandwidth::Narrowband), + (Application::Voip, 1, 11_000, Mode::Silk, Bandwidth::Mediumband), + (Application::Voip, 1, 16_000, Mode::Silk, Bandwidth::Wideband), + (Application::Voip, 1, 24_000, Mode::Hybrid, Bandwidth::SuperWideband), + (Application::Voip, 1, 32_000, Mode::Hybrid, Bandwidth::Fullband), + (Application::Voip, 1, 48_000, Mode::Celt, Bandwidth::Fullband), + (Application::Voip, 2, 32_000, Mode::Silk, Bandwidth::Wideband), + (Application::Audio, 1, 12_000, Mode::Silk, Bandwidth::Wideband), + (Application::Audio, 1, 24_000, Mode::Hybrid, Bandwidth::Fullband), + (Application::Audio, 2, 48_000, Mode::Hybrid, Bandwidth::Fullband), + (Application::Audio, 1, 64_000, Mode::Celt, Bandwidth::Fullband), + (Application::Audio, 2, 128_000, Mode::Celt, Bandwidth::Fullband), + ]; + for (app, c, rate, mode, bw) in cases { + let e = OpusEncoder::with_application(c, rate, app); + assert_eq!((e.mode(), e.bandwidth(), e.application()), (mode, bw, app), "{app:?} {c} ch @ {rate}"); + assert_eq!(e.pre_skip(), 120, "every mode is aligned to a 120-sample delay"); + } + assert_eq!(OpusEncoder::new(2, 128_000).mode(), Mode::Celt); + assert_eq!(OpusEncoder::new(1, 12_000).application(), Application::Audio); +} + +#[test] +#[should_panic(expected = "unsupported mode/bandwidth")] +fn invalid_mode_bandwidth_panics() { + let _ = OpusEncoder::with_mode(1, 16_000, Mode::Silk, Bandwidth::Fullband); +} + +#[test] +fn extreme_inputs_stay_decodable() { + let len = 24_000; + let mut s = 1u32; + let noise: Vec = (0..len) + .map(|_| { + s = s.wrapping_mul(1_664_525).wrapping_add(1_013_904_223); + (s >> 8) as f32 / (1u32 << 23) as f32 - 1.0 + }) + .collect(); + let square: Vec = (0..len).map(|i| if (i / 60) % 2 == 0 { 0.999 } else { -0.999 }).collect(); + let clicks: Vec = (0..len).map(|i| if i % 4_801 == 0 { 1.0 } else { 0.0 }).collect(); + let dc = vec![0.7f32; len]; + let nan: Vec = (0..len).map(|i| if i % 1000 == 0 { f32::NAN } else { 0.1 * (i as f32 * 0.01).sin() }).collect(); + for (name, x) in [("noise", &noise), ("square", &square), ("clicks", &clicks), ("dc", &dc), ("nan", &nan)] { + for (mode, bw, rate) in + [(Mode::Silk, Bandwidth::Narrowband, 6_000u32), (Mode::Silk, Bandwidth::Wideband, 40_000), (Mode::Hybrid, Bandwidth::Fullband, 24_000)] + { + for c in [1usize, 2] { + let planar: Vec> = if c == 1 { vec![x.clone()] } else { vec![x.clone(), x.iter().map(|v| -0.5 * v).collect()] }; + let (enc, packets) = encode(OpusEncoder::with_mode(c as u8, rate * c as u32, mode, bw), &planar); + assert!(packets.iter().all(|p| p.len() >= 3 && p.len() <= 1276), "{name} {mode:?}: packet sizes"); + let dec = decode(&enc.opus_head(), &packets); + let peak = dec.iter().flatten().fold(0f32, |m, v| m.max(v.abs())); + let snr = if name == "nan" { f64::NAN } else { snr_search(&planar[0], &dec[0], 2).0 }; + eprintln!("{name} {mode:?}/{bw:?} {c} ch: peak {peak:.2}, SNR {snr:.1} dB, {:.1} kb/s", bitrate(&packets) / 1000.0); + assert!(dec.iter().all(|ch| ch.iter().all(|v| v.is_finite() && v.abs() <= 4.0)), "{name} {mode:?}/{bw:?} {c} ch: bad output"); + } + } + } +} + +#[test] +fn silk_and_hybrid_silence() { + for (mode, bw, rate) in [(Mode::Silk, Bandwidth::Wideband, 16_000u32), (Mode::Hybrid, Bandwidth::Fullband, 32_000)] { + let x = vec![0f32; 48_000]; + let (enc, packets) = encode(OpusEncoder::with_mode(1, rate, mode, bw), std::slice::from_ref(&x)); + let dec = decode(&enc.opus_head(), &packets); + let peak = dec[0].iter().fold(0f32, |m, v| m.max(v.abs())); + let kbps = bitrate(&packets) / 1000.0; + eprintln!("{mode:?} silence: {kbps:.2} kb/s, decoded peak {peak:.2e}"); + assert!(peak < 1e-3, "{mode:?} silence decodes to peak {peak}"); + if mode == Mode::Silk { + assert!(kbps < 4.0, "SILK silence costs {kbps:.2} kb/s"); + } + } +} diff --git a/crates/project/src/render_queue.rs b/crates/project/src/render_queue.rs index 35aed0be..c3a5503e 100644 --- a/crates/project/src/render_queue.rs +++ b/crates/project/src/render_queue.rs @@ -515,11 +515,18 @@ pub enum OutputFormat { Wav, /// Audio only: AIFF (16-bit PCM). Aiff, + /// HEVC (H.265) video (+ AAC audio) in MP4 (`hvc1`). + Hevc, + /// AV1 video (+ AAC audio) in MP4 (`av01`). WebM with AV1: [`OutputFormat::WebM`] with + /// [`WebmVideoCodec::Av1`]. + Av1, } impl OutputFormat { - pub const ALL: [OutputFormat; 10] = [ + pub const ALL: [OutputFormat; 12] = [ OutputFormat::H264, + OutputFormat::Hevc, + OutputFormat::Av1, OutputFormat::ProRes, OutputFormat::WebM, OutputFormat::PngSequence, @@ -543,6 +550,8 @@ impl OutputFormat { OutputFormat::WebM => "WebM (VP9 + Opus)", OutputFormat::Wav => "WAV", OutputFormat::Aiff => "AIFF", + OutputFormat::Hevc => "HEVC (H.265)", + OutputFormat::Av1 => "AV1", } } pub fn extension(self) -> &'static str { @@ -557,29 +566,34 @@ impl OutputFormat { OutputFormat::WebM => "webm", OutputFormat::Wav => "wav", OutputFormat::Aiff => "aif", + OutputFormat::Hevc | OutputFormat::Av1 => "mp4", } } pub fn is_sequence(self) -> bool { matches!(self, OutputFormat::PngSequence | OutputFormat::JpegSequence | OutputFormat::TiffSequence | OutputFormat::ExrSequence) } pub fn is_movie(self) -> bool { - matches!(self, OutputFormat::H264 | OutputFormat::ProRes | OutputFormat::WebM) + matches!(self, OutputFormat::H264 | OutputFormat::ProRes | OutputFormat::WebM | OutputFormat::Hevc | OutputFormat::Av1) + } + /// Formats whose video codec options live in [`OutputModule::codec`] (HEVC, AV1). + pub fn has_codec_options(self) -> bool { + matches!(self, OutputFormat::Hevc | OutputFormat::Av1) } /// Audio-only outputs (no video is rendered). pub fn is_audio_only(self) -> bool { matches!(self, OutputFormat::Wav | OutputFormat::Aiff) } pub fn supports_alpha(self) -> bool { - !matches!(self, OutputFormat::H264 | OutputFormat::JpegSequence | OutputFormat::Wav | OutputFormat::Aiff) + !matches!(self, OutputFormat::H264 | OutputFormat::Hevc | OutputFormat::Av1 | OutputFormat::JpegSequence | OutputFormat::Wav | OutputFormat::Aiff) } pub fn supports_audio(self) -> bool { self.is_movie() || self.is_audio_only() } - /// The written frame size for a rendered size: H.264 needs even dimensions (rounded down, + /// The written frame size for a rendered size: H.264 and HEVC need even dimensions (rounded down, /// at least 2). pub fn coded_size(self, w: u32, h: u32) -> (u32, u32) { let even = |v: u32| if v < 2 { 2 } else { v & !1 }; - if self == OutputFormat::H264 { (even(w), even(h)) } else { (w, h) } + if matches!(self, OutputFormat::H264 | OutputFormat::Hevc) { (even(w), even(h)) } else { (w, h) } } /// Parse a format name: `h264`, `mp4`, `prores`, `mov`, `png`, `jpeg`/`jpg`, `tiff`/`tif`, /// `exr`, `gif`, or a label. @@ -587,6 +601,8 @@ impl OutputFormat { let n = s.trim().to_ascii_lowercase().replace([' ', '.', '-', '_'], ""); Some(match n.as_str() { "h264" | "mp4" | "avc" => OutputFormat::H264, + "hevc" | "h265" | "hevch265" | "hvc1" | "x265" => OutputFormat::Hevc, + "av1" | "av01" | "mp4av1" => OutputFormat::Av1, "prores" | "mov" | "quicktime" | "quicktimeprores" => OutputFormat::ProRes, "png" | "pngsequence" => OutputFormat::PngSequence, "jpg" | "jpeg" | "jpegsequence" => OutputFormat::JpegSequence, @@ -800,6 +816,155 @@ impl ProResProfile { } } +/// HEVC / AV1 profile: 8-bit (HEVC Main, AV1 Main 8-bit) or 10-bit (HEVC Main 10, AV1 Main 10-bit). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CodecProfile { + #[default] + Main, + Main10, +} + +impl CodecProfile { + pub const ALL: [CodecProfile; 2] = [CodecProfile::Main, CodecProfile::Main10]; + pub fn label(self) -> &'static str { + match self { + CodecProfile::Main => "Main", + CodecProfile::Main10 => "Main 10", + } + } + pub fn bit_depth(self) -> u8 { + match self { + CodecProfile::Main => 8, + CodecProfile::Main10 => 10, + } + } + pub fn from_name(s: &str) -> Option { + match s.to_ascii_lowercase().replace([' ', '-', '_'], "").as_str() { + "main" | "main8" | "8" | "8bit" => Some(CodecProfile::Main), + "main10" | "10" | "10bit" => Some(CodecProfile::Main10), + _ => None, + } + } +} + +/// HEVC / AV1 rate control: a target bitrate ([`OutputModule::bitrate_kbps`]) or constant +/// quality ([`VideoCodecOptions::quality`], CRF-like). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum RateControlMode { + #[default] + Bitrate, + Quality, +} + +impl RateControlMode { + pub fn label(self) -> &'static str { + match self { + RateControlMode::Bitrate => "Target Bitrate", + RateControlMode::Quality => "Constant Quality", + } + } + pub fn from_name(s: &str) -> Option { + match s.to_ascii_lowercase().replace([' ', '-', '_'], "").as_str() { + "bitrate" | "vbr" | "abr" | "targetbitrate" => Some(RateControlMode::Bitrate), + "quality" | "crf" | "cq" | "constantquality" | "qp" => Some(RateControlMode::Quality), + _ => None, + } + } +} + +/// Video codec options of the HEVC and AV1 formats (and AV1 in WebM). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +pub struct VideoCodecOptions { + pub profile: CodecProfile, + /// Level × 10 (`41` = level 4.1); `None` picks the lowest level that fits the frame size and rate. + pub level: Option, + pub rate_control: RateControlMode, + /// Constant-quality setting 1–100 (100 ≈ visually lossless), used with [`RateControlMode::Quality`]. + pub quality: u8, +} + +impl Default for VideoCodecOptions { + fn default() -> Self { + VideoCodecOptions { profile: CodecProfile::Main, level: None, rate_control: RateControlMode::Bitrate, quality: 70 } + } +} + +impl VideoCodecOptions { + /// HEVC levels (× 10) offered in the UI (Main tier). + pub const HEVC_LEVELS: [u8; 13] = [10, 20, 21, 30, 31, 40, 41, 50, 51, 52, 60, 61, 62]; + /// AV1 levels (× 10) offered in the UI. + pub const AV1_LEVELS: [u8; 14] = [20, 21, 30, 31, 40, 41, 50, 51, 52, 53, 60, 61, 62, 63]; + /// HEVC QP (0–51) for the constant-quality setting. + pub fn hevc_qp(&self) -> u8 { + (4.0 + (100 - self.quality.clamp(1, 100)) as f64 * 0.45).round() as u8 + } + /// AV1 `base_q_idx` (1–255) for the constant-quality setting. + pub fn av1_qindex(&self) -> u8 { + (4.0 + (100 - self.quality.clamp(1, 100)) as f64 * 2.4).round() as u8 + } + /// `general_level_idc` (30 × level). + pub fn hevc_level_idc(&self) -> Option { + self.level.map(|l| l.saturating_mul(3)) + } + /// AV1 `seq_level_idx`: (major − 2) × 4 + minor. + pub fn av1_level_idx(&self) -> Option { + self.level.map(|l| ((l / 10).clamp(2, 7) - 2) * 4 + (l % 10).min(3)) + } + pub fn level_label(&self) -> String { + match self.level { + None => "Auto".into(), + Some(l) => format!("{}.{}", l / 10, l % 10), + } + } + /// Parse `"auto"`, `"4.1"`, `"41"` (level × 10). `None` when unparsable; `Some(None)` = auto. + pub fn parse_level(s: &str) -> Option> { + let s = s.trim().to_ascii_lowercase(); + if s == "auto" || s.is_empty() { + return Some(None); + } + let v: f64 = s.trim_start_matches('l').parse().ok()?; + let l = if v >= 10.0 { v.round() } else { (v * 10.0).round() }; + (10.0..=73.0).contains(&l).then_some(Some(l as u8)) + } +} + +/// The video codec of a WebM output. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum WebmVideoCodec { + #[default] + Vp9, + /// AV1 (`V_AV1`) with the [`OutputModule::codec`] options; no alpha. + Av1, +} + +impl WebmVideoCodec { + pub fn label(self) -> &'static str { + match self { + WebmVideoCodec::Vp9 => "VP9", + WebmVideoCodec::Av1 => "AV1", + } + } +} + +/// Opus coding application: general audio (music and mixed content) or voice (speech-tuned +/// SILK / hybrid coding at low bitrates). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum OpusApplication { + #[default] + Audio, + Voip, +} + +impl OpusApplication { + pub fn label(self) -> &'static str { + match self { + OpusApplication::Audio => "Audio", + OpusApplication::Voip => "Voice", + } + } +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(default)] pub struct OutputModule { @@ -814,7 +979,7 @@ pub struct OutputModule { pub output: String, /// JPEG / WebM quality 1–100. pub quality: u8, - /// H.264 target bitrate (and WebM's when `webm_bitrate`). + /// H.264 target bitrate (and WebM's when `webm_bitrate`; HEVC / AV1 with [`RateControlMode::Bitrate`]). pub bitrate_kbps: u32, /// WebM: rate control by `bitrate_kbps` instead of `quality`. pub webm_bitrate: bool, @@ -830,6 +995,13 @@ pub struct OutputModule { pub audio_format: AudioFormat, /// GIF: loop forever. pub gif_loop: bool, + /// HEVC / AV1 options. + pub codec: VideoCodecOptions, + /// WebM video codec. + pub webm_codec: WebmVideoCodec, + /// WebM Opus audio bitrate (stereo total), kbit/s. + pub opus_bitrate_kbps: u32, + pub opus_application: OpusApplication, /// Include Project Link: kept for parity; EffectCraft's writers have no place to store a /// link back to the project, so it changes nothing in the file. pub include_project_link: bool, @@ -855,6 +1027,10 @@ impl Default for OutputModule { audio_channels: 2, audio_format: AudioFormat::S16, gif_loop: true, + codec: VideoCodecOptions::default(), + webm_codec: WebmVideoCodec::Vp9, + opus_bitrate_kbps: 192, + opus_application: OpusApplication::Audio, include_project_link: true, } } @@ -941,7 +1117,7 @@ impl OutputModule { pub fn set_format(&mut self, format: OutputFormat) { let old = self.format; self.format = format; - if !format.supports_alpha() && self.channels == Channels::Rgba { + if !self.supports_alpha() && self.channels == Channels::Rgba { self.channels = Channels::Rgb; } if self.output.contains("[fileExtension]") { @@ -961,6 +1137,24 @@ impl OutputModule { } self.output = format!("{stem}.{}", format.extension()); } + /// Whether this module can write alpha (the format can, and WebM uses VP9). + pub fn supports_alpha(&self) -> bool { + self.format.supports_alpha() && !(self.format == OutputFormat::WebM && self.webm_codec == WebmVideoCodec::Av1) + } + /// The key-frame interval in frames at `fps` ([`OutputModule::keyframe_interval`]; automatic: 2 s). + pub fn keyint(&self, fps: f64) -> u32 { + if self.keyframe_interval > 0 { self.keyframe_interval } else { (fps * 2.0).round().max(1.0) as u32 } + } + /// Codec options summary: `Main · 10000 kbps · Level Auto · Key every auto`. + fn codec_summary(&self) -> String { + let c = &self.codec; + let rate = match c.rate_control { + RateControlMode::Bitrate => format!("{} kbps", self.bitrate_kbps), + RateControlMode::Quality => format!("Quality {}", c.quality), + }; + let gop = if self.keyframe_interval == 0 { "auto".to_string() } else { self.keyframe_interval.to_string() }; + format!("{} · {rate} · Level {} · Key every {gop}", c.profile.label(), c.level_label()) + } /// One-line summary shown next to "Output Module:". pub fn summary(&self) -> String { if !self.name.is_empty() { @@ -973,6 +1167,10 @@ impl OutputModule { "{} · {ch}", if self.channels == Channels::Rgba && !self.prores_profile.is_4444() { "Apple ProRes 4444" } else { self.prores_profile.label() } ), + OutputFormat::Hevc | OutputFormat::Av1 => format!("{} · {}", self.format.label(), self.codec_summary()), + OutputFormat::WebM if self.webm_codec == WebmVideoCodec::Av1 => { + format!("WebM (AV1 + Opus {} kbps) · {}", self.opus_bitrate_kbps, self.codec_summary()) + } f => format!("{} · {ch}", f.label()), } } @@ -1353,4 +1551,42 @@ mod tests { assert_eq!(s.first_frame(&c), 30); assert!(s.frame_time(&c, 0) >= Tick::from_seconds_f64(1.0)); } + + #[test] + fn hevc_av1_codec_options() { + assert_eq!(OutputFormat::from_name("H.265"), Some(OutputFormat::Hevc)); + assert_eq!(OutputFormat::from_name("hevc"), Some(OutputFormat::Hevc)); + assert_eq!(OutputFormat::from_name("AV1"), Some(OutputFormat::Av1)); + assert_eq!(OutputFormat::Hevc.coded_size(33, 17), (32, 16)); + assert_eq!(OutputFormat::Av1.coded_size(33, 17), (33, 17)); + assert!(!OutputFormat::Hevc.supports_alpha() && OutputFormat::Av1.is_movie() && OutputFormat::Av1.supports_audio()); + assert_eq!(VideoCodecOptions::parse_level("4.1"), Some(Some(41))); + assert_eq!(VideoCodecOptions::parse_level("51"), Some(Some(51))); + assert_eq!(VideoCodecOptions::parse_level("auto"), Some(None)); + assert_eq!(VideoCodecOptions::parse_level("9"), None); + let mut o = VideoCodecOptions { level: Some(41), ..Default::default() }; + assert_eq!((o.hevc_level_idc(), o.av1_level_idx()), (Some(123), Some(9))); + o.level = Some(20); + assert_eq!(o.av1_level_idx(), Some(0)); + o.quality = 100; + assert_eq!((o.hevc_qp(), o.av1_qindex()), (4, 4)); + o.quality = 1; + assert!(o.hevc_qp() <= 51 && o.av1_qindex() >= 240); + // WebM with AV1 has no alpha; switching drops RGB + Alpha. + let mut m = OutputModule::for_format(OutputFormat::WebM); + m.channels = Channels::Rgba; + assert!(m.supports_alpha()); + m.webm_codec = WebmVideoCodec::Av1; + assert!(!m.supports_alpha()); + assert!(m.summary().starts_with("WebM (AV1 + Opus 192 kbps)"), "{}", m.summary()); + let mut h = OutputModule::for_format(OutputFormat::Hevc); + assert_eq!(h.keyint(29.97), 60); + h.keyframe_interval = 1; + assert_eq!(h.keyint(29.97), 1); + h.keyframe_interval = 0; + assert_eq!(h.summary(), "HEVC (H.265) · Main · 10000 kbps · Level Auto · Key every auto"); + // Older projects (no codec fields) load with the defaults. + let old: OutputModule = serde_json::from_str(r#"{"format":"H264","bitrate_kbps":5000}"#).unwrap(); + assert_eq!((old.codec, old.webm_codec, old.opus_bitrate_kbps), (VideoCodecOptions::default(), WebmVideoCodec::Vp9, 192)); + } } diff --git a/crates/ui-egui/src/panels/render_queue.rs b/crates/ui-egui/src/panels/render_queue.rs index ec0853a1..cf6c0c71 100644 --- a/crates/ui-egui/src/panels/render_queue.rs +++ b/crates/ui-egui/src/panels/render_queue.rs @@ -12,8 +12,8 @@ //! key frame interval, Make Template) open a form that runs the same command. use effectcraft_engine::project::render_queue::{ - AlphaMode, AudioFormat, AudioOutput, Channels, ColorDepth, CurrentOrOff, EffectsMode, FieldRender, OutputFormat, PostRenderAction, ProResProfile, Pulldown, - RESIZE_PRESETS, RenderLog, RenderQuality, RenderQueueItem, RenderStatus, ResizeQuality, SwitchOverride, TimeSpan, + AlphaMode, AudioFormat, AudioOutput, Channels, ColorDepth, CurrentOrOff, EffectsMode, FieldRender, OpusApplication, OutputFormat, PostRenderAction, + ProResProfile, Pulldown, RESIZE_PRESETS, RenderLog, RenderQuality, RenderQueueItem, RenderStatus, ResizeQuality, SwitchOverride, TimeSpan, WebmVideoCodec, }; use effectcraft_engine::project::render_templates::RenderTemplates; use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2}; @@ -144,7 +144,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren } v.push(("-".into(), Value::Null)); v.push((mark(o.channels == Channels::Rgb, "Channels: RGB"), json!({"channels": "rgb"}))); - if o.format.supports_alpha() { + if o.supports_alpha() { v.push((mark(o.channels == Channels::Rgba, "Channels: RGB + Alpha"), json!({"channels": "rgba"}))); } if !o.format.is_audio_only() { @@ -167,15 +167,35 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren v.push((mark(o.bitrate_kbps == kbps, &format!("Bitrate: {} Mbps", kbps / 1000)), json!({"bitrate": kbps}))); } } - OutputFormat::JpegSequence | OutputFormat::WebM => { + OutputFormat::Hevc | OutputFormat::Av1 => codec_menu(o, &mut v), + OutputFormat::WebM => { v.push(("-".into(), Value::Null)); - for q in [60u8, 80, 90, 100] { - v.push((mark(o.quality == q && !o.webm_bitrate, &format!("Quality: {q}")), json!({"quality": q, "webmBitrate": false}))); + for c in [WebmVideoCodec::Vp9, WebmVideoCodec::Av1] { + v.push((mark(o.webm_codec == c, &format!("Video Codec: {}", c.label())), json!({"webmCodec": c.label().to_ascii_lowercase()}))); } - if o.format == OutputFormat::WebM { + if o.webm_codec == WebmVideoCodec::Av1 { + codec_menu(o, &mut v); + } else { + v.push(("-".into(), Value::Null)); + for q in [60u8, 80, 90, 100] { + v.push((mark(o.quality == q && !o.webm_bitrate, &format!("Quality: {q}")), json!({"quality": q, "webmBitrate": false}))); + } v.push((mark(o.webm_bitrate, &format!("Target Bitrate ({} kbps)…", o.bitrate_kbps)), json!({"form": "bitrate"}))); v.push((mark(o.keyframe_interval > 0, "Key Frame Interval…"), json!({"form": "keyframes"}))); } + v.push(("-".into(), Value::Null)); + for kbps in [24u32, 32, 64, 96, 128, 192, 256] { + v.push((mark(o.opus_bitrate_kbps == kbps, &format!("Opus Bitrate: {kbps} kbps")), json!({"audioBitrate": kbps}))); + } + for (a, k) in [(OpusApplication::Audio, "audio"), (OpusApplication::Voip, "voice")] { + v.push((mark(o.opus_application == a, &format!("Opus Tuning: {}", a.label())), json!({"opusApplication": k}))); + } + } + OutputFormat::JpegSequence => { + v.push(("-".into(), Value::Null)); + for q in [60u8, 80, 90, 100] { + v.push((mark(o.quality == q, &format!("Quality: {q}")), json!({"quality": q}))); + } } OutputFormat::Gif => { v.push(("-".into(), Value::Null)); @@ -236,6 +256,49 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren v } +/// HEVC / AV1 options: profile, level, rate control (bitrate or constant quality), key frames. +fn codec_menu(o: &effectcraft_engine::project::render_queue::OutputModule, v: &mut Vec<(String, Value)>) { + use effectcraft_engine::project::render_queue::{CodecProfile, RateControlMode, VideoCodecOptions}; + let c = &o.codec; + v.push(("-".into(), Value::Null)); + for p in CodecProfile::ALL { + v.push((mark(c.profile == p, &format!("Profile: {}", p.label())), json!({"profile": format!("{p:?}").to_ascii_lowercase()}))); + } + v.push(("-".into(), Value::Null)); + v.push((mark(c.level.is_none(), "Level: Auto"), json!({"level": "auto"}))); + // The common HEVC / AV1 levels (every level is reachable over the control channel). + for l in [30u8, 31, 40, 41, 50, 51, 52] { + debug_assert!(VideoCodecOptions::HEVC_LEVELS.contains(&l) && VideoCodecOptions::AV1_LEVELS.contains(&l)); + let name = format!("{}.{}", l / 10, l % 10); + v.push((mark(c.level == Some(l), &format!("Level: {name}")), json!({"level": name}))); + } + v.push(("-".into(), Value::Null)); + for r in [RateControlMode::Bitrate, RateControlMode::Quality] { + v.push((mark(c.rate_control == r, &format!("Rate Control: {}", r.label())), json!({"rateControl": format!("{r:?}").to_ascii_lowercase()}))); + } + if c.rate_control == RateControlMode::Bitrate { + for kbps in [1_000u32, 2_000, 5_000, 10_000, 20_000, 40_000] { + let l = if kbps < 2_000 { format!("Bitrate: {kbps} kbps") } else { format!("Bitrate: {} Mbps", kbps / 1000) }; + v.push((mark(o.bitrate_kbps == kbps, &l), json!({"bitrate": kbps}))); + } + v.push((mark(false, "Bitrate: Custom…"), json!({"form": "codecBitrate"}))); + } else { + for q in [40u8, 60, 70, 80, 90, 100] { + v.push((mark(c.quality == q, &format!("Quality: {q}")), json!({"quality": q}))); + } + } + v.push(("-".into(), Value::Null)); + for k in [0u32, 1, 30, 60, 120, 250] { + let l = match k { + 0 => "Key Frames: Auto (2 s)".to_string(), + 1 => "Key Frames: Every Frame (all intra)".to_string(), + k => format!("Key Frames: Every {k} Frames"), + }; + v.push((mark(o.keyframe_interval == k, &l), json!({"keyframeInterval": k}))); + } + v.push((mark(false, "Key Frame Interval…"), json!({"form": "keyframes"}))); +} + /// Open the form behind a `{"form": …}` menu entry. fn open_menu_form(app: &mut EffectcraftApp, it: &RenderQueueItem, form: &str) { use crate::panels::forms::{Field, form as open}; @@ -297,6 +360,13 @@ fn open_menu_form(app: &mut EffectcraftApp, it: &RenderQueueItem, form: &str) { json!({"item": id, "webmBitrate": true}), vec![Field::num("bitrate", "Bitrate (kbps)", o.bitrate_kbps as f64)], ), + "codecBitrate" => open( + app, + "Target Bitrate", + "renderQueue.setOutputModule", + json!({"item": id, "rateControl": "bitrate"}), + vec![Field::num("bitrate", "Bitrate (kbps)", o.bitrate_kbps as f64)], + ), "keyframes" => open( app, "Key Frame Interval", diff --git a/docs/architecture.md b/docs/architecture.md index d0c9267e..f45aaa3c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -17,7 +17,9 @@ cpal or muda. Everything in L0 to L4, the egui UI and the web app also build for | L0 | `geom` | Vectors, matrices, quaternions, the layer transform (anchor, position, scale, orientation, rotation) | | L0 | `color` | sRGB and linear, HSL/HSV, luminance, the 38 blend modes, label colors | | L0 | `vp9enc` | VP9 intra-frame encoder (profile 0, 8-bit 4:2:0; lossless at quality 100) for WebM export, from the VP9 bitstream specification | -| L0 | `opusenc` | Opus (CELT) encoder: RFC 6716 CELT-only fullband 48 kHz 20 ms packets, mono/stereo, and the RFC 7845 `OpusHead`, for WebM export audio | +| L0 | `opusenc` | Opus encoder: RFC 6716 SILK (NB/MB/WB), hybrid (SWB/FB) and CELT (FB) modes chosen by bitrate and application (audio / voice), 48 kHz 20 ms packets, mono/stereo, and the RFC 7845 `OpusHead`, for WebM export audio | +| L0 | `hevcenc` | HEVC (H.265) encoder from ITU-T H.265: Main / Main 10 4:2:0, IDR + P slices (quarter-pel motion, merge/AMVP), CABAC, deblocking, bitrate or constant-QP rate control, for MP4 (`hvc1`) export | +| L0 | `av1enc` | AV1 encoder from the AV1 bitstream specification: Main profile 8/10-bit 4:2:0, key + inter frames (quarter-pel motion), deblocking, for MP4 (`av01`) and WebM export | | L1 | `raster` | Premultiplied float images, sampling, affine and projective warps, blurs, compositing (parallel with rayon) | | L1 | `keyframe` | Animated values, keyframes with temporal ease and spatial Bezier, roving, hold, velocity | | L1 | `psd` | Photoshop PSD/PSB reader (layers, groups, masks, blend modes, text, layer effects, adjustment layers; 8/16/32-bit; RGB/CMYK/Gray/Lab) and a minimal writer, from Adobe's published format specification | @@ -32,7 +34,7 @@ cpal or muda. Everything in L0 to L4, the egui UI and the web app also build for | L3 | `render` | Evaluation and compositing: sources, masks, effects, transforms, 3D, motion blur, mattes, blending, layer cache, audio mixdown | | L3 | `media` | Footage decoding (FilmCraft's pure-Rust codecs), image sequences, Photoshop and SVG stills, frame cache | | L3 | `expr` | The expression engine (JavaScript via boa) with the After Effects object model | -| L3 | `export` | Render queue encoding: H.264, ProRes, WebM (VP9 + alpha, Opus), PNG/JPEG/TIFF/EXR sequences, GIF, WAV/AIFF | +| L3 | `export` | Render queue encoding: H.264, HEVC and AV1 MP4, ProRes, WebM (VP9 + alpha or AV1, Opus), PNG/JPEG/TIFF/EXR sequences, GIF, WAV/AIFF | | L3 | `gpu` | The GPU compositor and GPU effects on wgpu compute shaders (Metal, Vulkan, Direct3D 12, WebGPU), checked against the CPU renderer | | L3 | `lottie` | Lottie JSON / dotLottie import and export (layers, precomps, eased and spatial keyframes, shapes, masks, mattes) with a warnings list for what Lottie cannot express | | L3 | `plugin` | Effect plug-ins: loads sandboxed WebAssembly effect modules (plug-in API v1; the wasmi interpreter behind the `wasm` feature, on for native hosts) into the effect registry ([plugins.md](plugins.md)) | diff --git a/docs/parity.md b/docs/parity.md index 94603950..378c973a 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -36,7 +36,7 @@ Tracking 96%, Effects 85%, Web 65%. What is left, in priority order: the web app's depth (threads, storage, audio, non-blocking renders); stroke taper/wave and multi-segment dashes; variable mask feather points; camera iris/bokeh and focus-link commands; approximated effects (Key Cleaner) and Liquify's viewer brush; mesh shadings and JPX images in -imported PDF/AI files and Illustrator procset EPS; and more codecs. Face tracking and a true Subspace Warp +imported PDF/AI files and Illustrator procset EPS; and codec depth (B-frames, SAO/CDEF; HEVC and AV1 export and Opus SILK/hybrid landed in M13.4). Face tracking and a true Subspace Warp landed in M13.3. ScriptUI (dialogs that block `show()`, palettes, dockable ScriptUI panels, File ▸ Scripts install and sample scripts), puppet pin recording and the remaining "better than After Effects" items — a versioned effect plug-in API with sandboxed WebAssembly plug-ins ([plugins.md](plugins.md)), @@ -126,7 +126,7 @@ the disk cache in the formats wave; the 3D Camera Tracker in M12.6.) | Area | Weighted parity | Remaining (agent-hours) | Biggest gaps | |---|---|---|---| | Layers | 88% | 4.8 | frame blending, collapse transformations, slip edit | -| Output | 97% | 0.8 | Render Settings complete (field render + 3:2 pulldown, effects/solo/guide/depth/blending/blur overrides, time sampling, storage overflow), Output Module crop/ROI/resize, alpha modes, post-render actions, PCM formats, templates with defaults, render logs, Notify (M10.2); WebM VP9 key + inter frames with motion search, loop filter and rate control. Left: Opus is CELT-only, Photoshop sequence output, overflow for movies only checks at file creation | +| Output | 97% | 0.8 | Render Settings complete (field render + 3:2 pulldown, effects/solo/guide/depth/blending/blur overrides, time sampling, storage overflow), Output Module crop/ROI/resize, alpha modes, post-render actions, PCM formats, templates with defaults, render logs, Notify (M10.2); WebM VP9 key + inter frames with motion search, loop filter and rate control; M13.4: HEVC export (MP4 `hvc1`, `effectcraft-hevcenc`: Main / Main 10, IDR + P slices, quarter-pel motion, deblocking, bit-exact with ffmpeg) and AV1 export (MP4 `av01` and WebM, `effectcraft-av1enc`: 8/10-bit key + inter frames, quarter-pel motion, loop filter, bit-exact with libdav1d) with profile, level, bitrate / constant-quality and key-frame interval options; Opus SILK (NB/MB/WB, mid/side stereo) and hybrid (SWB/FB) modes chosen by bitrate and Audio/Voice tuning, with the Opus bitrate in the Output Module. HEVC/AV1 import decodes through FilmCraft (MP4/MOV/MKV/WebM). Left: HEVC/AV1 have no B-frames, multi-reference, SAO/CDEF/restoration or alpha (compression below mature encoders); Opus has no FEC/DTX and only 20 ms frames; Photoshop sequence output, overflow for movies only checks at file creation | | Audio | 85% | 0.5 | audio to keyframes | | Import | 96% | 0.6 | mesh shadings (types 4–7), JPX / CCITT / JBIG2 images and non-Identity CJK CMaps inside PDF/AI files, Illustrator EPS relying on Adobe procsets, PSD 3D layers (M13.6: PDF/AI text with embedded TrueType / CFF / Type 1 / Type 3 fonts and standard-14 fallback to the bundled fonts, Flate / DCT / inline / stencil images with soft masks, Indexed and ICC alternates, luminosity / alpha soft masks, the 16 blend modes, tiling patterns, calculator functions, any page via `file.import page` and the Import dialog, clip groups kept by Create Shapes from Vector Layer as layer masks / Merge Paths; PSD smart-object perspective quads and placed-layer warps — named styles and custom quilt meshes — baked as placed; PDF / PDF-compatible AI / EPS vector footage with Continuously Rasterize, layered composition import and Create Shapes from Vector Layer, and PSD smart objects with embedded files landed in M13.2; PSD as footage/composition/retain layer sizes, SVG footage earlier) | | Automation | ≈ 99% | 0.1 | `.jsxbin` (AUT-2: ScriptUI resource strings, `onDraw`/ScriptUIGraphics, live `onChanging`, `Socket`, Essential Graphics hooks — `addToMotionGraphicsTemplate(As)`, `canAddToMotionGraphicsTemplate`, `exportAsMotionGraphicsTemplate`, `motionGraphicsTemplateName`, controller count/names — and Watch Folder landed in M13.7; the core object model landed in M14.4; ScriptUI windows/dialogs/dockable panels with `scriptui.*` agent commands, File ▸ Scripts install + sample scripts, and the effect plug-in API (EFF-6, WebAssembly) landed in M13.1) | diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 01cb44b4..681aee1e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -26,6 +26,8 @@ const LAYERS: &[(&str, u8)] = &[ ("vp9enc", 0), ("testkit", 0), ("opusenc", 0), + ("hevcenc", 0), + ("av1enc", 0), ("raster", 1), ("keyframe", 1), ("path", 1),