feat(client): M6 begins — the libva layouts, measured rather than transcribed
The VAAPI rung's crate, in the shape the other two native rungs established:
everything that can be a pure decision or a pure conversion lives in a
cross-platform crate the ordinary gates run, and only the parts that genuinely
need a device stay behind a platform cfg. This lands the first half of that —
the buffer layouts and the decoder-creation decisions — with the conversion to
follow.
Route: minimal FFI rather than cros-libva. The plan of record permits either
("cros-libva (or minimal FFI)"), and hand-declaring keeps the crate building
and testing on macOS and in the Linux container, which is the property that
made pf-dxvadec's defects findable on a laptop instead of on a box.
The layouts are not eyeballed. A C probe compiled against real libva 2.23.0
headers printed sizeof/alignof/offsetof for every field and set individual
bit-fields to read the resulting word back; those numbers are pinned as const
assertions, so a transcription slip is a compile error rather than a driver
reading the wrong byte. The probe is committed beside them, with the command
that runs it, because evidence that cannot be re-run is a claim.
What the probe settled that a reader would otherwise get wrong: VAPictureH264
is 36 bytes and is embedded 81 times across the two buffers, so its size is
load-bearing for every later offset; the three DEPRECATED FMO fields still
occupy bytes 624..628, and dropping them would shift everything after; and C
bit-fields allocate from the least significant bit on this ABI — proven, since
that is ABI-defined rather than standardised.
Groundwork for the conversion, established here so the next work package
starts from facts:
slice_data_bit_offset needs no new parsing. VAAPI is the only backend that
wants a bit position — DXVA takes a byte offset, Vulkan takes none — and the
vendored parser already records exactly it as SliceHeader::header_bit_size,
computed as (nalu.size - epb) * 8 - bits_left: from and including the NAL
header byte, emulation-prevention bytes removed. That is the field's
definition verbatim, and it is there because cros-codecs' own production
backend is VAAPI.
The slice data buffer starts at the NAL header byte, so the start code is
skipped — SlicePlan::data is start-code-inclusive and the prefix is three OR
four bytes, the host emitting four on 100% of access units.
reference_frames is the marked DPB, the same statement DXVA's RefFrameList
makes, so it comes from the dpb_refs snapshot; Vulkan's pReferenceSlots is the
opposite and takes the access unit's own set. All three conventions now have a
written home, which is the distinction that cost M5 a defect.
Unlike DXVA short-format, VAAPI wants the per-slice reference lists and the
full prediction weight tables inline — hence the 3128-byte slice record. One
wrinkle recorded rather than left to be discovered: the vendored
PredWeightTable stores luma_offset_l0 as [i8; 32] but luma_offset_l1 as
[i16; 32], and libva wants i16 for both.
Profile selection resolves H.264 to High for every 8-bit 4:2:0 stream instead
of reading profile_idc, because High is a superset for the tools our hosts
emit and picking Main for a stream that turns out to use 8x8 transforms is a
mid-stream failure where picking High is not. 4:4:4 and 10-bit H.264 are
refused rather than narrowed to an 8-bit profile — that class of silent
narrowing decodes to garbage instead of failing.
11 tests: the probe's bit patterns, a disjointness check per bit-field word
(two probe vectors alone would not catch a shift typo that overlapped two
fields), and the envelope refusals. Gates: rustfmt, clippy, cargo doc with no
unresolved links, and the Linux container's clippy -D warnings, tests and
workspace check.
This commit is contained in:
Generated
+8
@@ -3195,6 +3195,14 @@ dependencies = [
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-vaadec"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"pf-bitstream",
|
||||
"pf-vkdecode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-vdisplay"
|
||||
version = "0.24.0"
|
||||
|
||||
@@ -27,6 +27,7 @@ members = [
|
||||
"crates/pf-vdisplay",
|
||||
"crates/pf-vkdecode",
|
||||
"crates/pf-dxvadec",
|
||||
"crates/pf-vaadec",
|
||||
"crates/pyrowave-sys",
|
||||
"crates/libvpl-sys",
|
||||
"clients/probe",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "pf-vaadec"
|
||||
description = "Native VAAPI H.264/HEVC decode for the Linux clients (M6): the hand-declared libva decode buffer layouts plus the profile/format/surface decisions — the CPU-testable half; the libva plumbing lives in pf-client-core (design/client-native-decode.md §3.4)"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pf-bitstream = { path = "../pf-bitstream" }
|
||||
# For `SlotMap`/`SlotError` ONLY — see this crate's lib.rs for why the DPB slot ledger is
|
||||
# borrowed from the Vulkan crate rather than duplicated or moved.
|
||||
pf-vkdecode = { path = "../pf-vkdecode" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Layout probe for the hand-declared libva structures in `src/va.rs`.
|
||||
*
|
||||
* `src/va.rs` declares VAAPI's decode buffers as `#[repr(C)]` Rust structs, because
|
||||
* this crate must build on macOS and in the Linux container where libva headers need
|
||||
* not exist. This file is how those declarations were CHECKED rather than eyeballed,
|
||||
* and it is committed so the check is reproducible instead of a claim in a commit
|
||||
* message.
|
||||
*
|
||||
* Run it against real headers (no Linux box required):
|
||||
*
|
||||
* docker run --rm --platform linux/amd64 -v "$PWD/crates/pf-vaadec:/w" -w /w \
|
||||
* pf-lxcheck2 bash -lc 'apt-get update -qq && apt-get install -y -qq libva-dev \
|
||||
* && gcc -w -O0 layout-probe.c -o /tmp/probe && /tmp/probe'
|
||||
*
|
||||
* Every number it prints is pinned as a `const` assertion at the bottom of
|
||||
* `src/va.rs`, so a transcription mistake is a compile error. The bit-field section
|
||||
* exists because C bit-field allocation order is ABI-defined, not standardised:
|
||||
* it PROVES least-significant-bit-first on this ABI rather than assuming it.
|
||||
*
|
||||
* Last run against libva 2.23.0-1ubuntu1, x86_64-linux-gnu.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <va/va.h>
|
||||
|
||||
#define S(t) printf("size %-34s %zu align %zu\n", #t, sizeof(t), _Alignof(t))
|
||||
#define O(t, f) printf("off %-20s %-28s %zu\n", #t, #f, offsetof(t, f))
|
||||
|
||||
int main(void) {
|
||||
S(VAPictureH264);
|
||||
O(VAPictureH264, picture_id);
|
||||
O(VAPictureH264, frame_idx);
|
||||
O(VAPictureH264, flags);
|
||||
O(VAPictureH264, TopFieldOrderCnt);
|
||||
O(VAPictureH264, BottomFieldOrderCnt);
|
||||
O(VAPictureH264, va_reserved);
|
||||
|
||||
S(VAPictureParameterBufferH264);
|
||||
O(VAPictureParameterBufferH264, CurrPic);
|
||||
O(VAPictureParameterBufferH264, ReferenceFrames);
|
||||
O(VAPictureParameterBufferH264, picture_width_in_mbs_minus1);
|
||||
O(VAPictureParameterBufferH264, picture_height_in_mbs_minus1);
|
||||
O(VAPictureParameterBufferH264, bit_depth_luma_minus8);
|
||||
O(VAPictureParameterBufferH264, bit_depth_chroma_minus8);
|
||||
O(VAPictureParameterBufferH264, num_ref_frames);
|
||||
O(VAPictureParameterBufferH264, seq_fields);
|
||||
O(VAPictureParameterBufferH264, num_slice_groups_minus1);
|
||||
O(VAPictureParameterBufferH264, slice_group_map_type);
|
||||
O(VAPictureParameterBufferH264, slice_group_change_rate_minus1);
|
||||
O(VAPictureParameterBufferH264, pic_init_qp_minus26);
|
||||
O(VAPictureParameterBufferH264, pic_init_qs_minus26);
|
||||
O(VAPictureParameterBufferH264, chroma_qp_index_offset);
|
||||
O(VAPictureParameterBufferH264, second_chroma_qp_index_offset);
|
||||
O(VAPictureParameterBufferH264, pic_fields);
|
||||
O(VAPictureParameterBufferH264, frame_num);
|
||||
O(VAPictureParameterBufferH264, va_reserved);
|
||||
|
||||
S(VAIQMatrixBufferH264);
|
||||
O(VAIQMatrixBufferH264, ScalingList4x4);
|
||||
O(VAIQMatrixBufferH264, ScalingList8x8);
|
||||
O(VAIQMatrixBufferH264, va_reserved);
|
||||
|
||||
S(VASliceParameterBufferH264);
|
||||
O(VASliceParameterBufferH264, slice_data_size);
|
||||
O(VASliceParameterBufferH264, slice_data_offset);
|
||||
O(VASliceParameterBufferH264, slice_data_flag);
|
||||
O(VASliceParameterBufferH264, slice_data_bit_offset);
|
||||
O(VASliceParameterBufferH264, first_mb_in_slice);
|
||||
O(VASliceParameterBufferH264, slice_type);
|
||||
O(VASliceParameterBufferH264, direct_spatial_mv_pred_flag);
|
||||
O(VASliceParameterBufferH264, num_ref_idx_l0_active_minus1);
|
||||
O(VASliceParameterBufferH264, num_ref_idx_l1_active_minus1);
|
||||
O(VASliceParameterBufferH264, cabac_init_idc);
|
||||
O(VASliceParameterBufferH264, slice_qp_delta);
|
||||
O(VASliceParameterBufferH264, disable_deblocking_filter_idc);
|
||||
O(VASliceParameterBufferH264, slice_alpha_c0_offset_div2);
|
||||
O(VASliceParameterBufferH264, slice_beta_offset_div2);
|
||||
O(VASliceParameterBufferH264, RefPicList0);
|
||||
O(VASliceParameterBufferH264, RefPicList1);
|
||||
O(VASliceParameterBufferH264, luma_log2_weight_denom);
|
||||
O(VASliceParameterBufferH264, chroma_log2_weight_denom);
|
||||
O(VASliceParameterBufferH264, luma_weight_l0_flag);
|
||||
O(VASliceParameterBufferH264, luma_weight_l0);
|
||||
O(VASliceParameterBufferH264, luma_offset_l0);
|
||||
O(VASliceParameterBufferH264, chroma_weight_l0_flag);
|
||||
O(VASliceParameterBufferH264, chroma_weight_l0);
|
||||
O(VASliceParameterBufferH264, chroma_offset_l0);
|
||||
O(VASliceParameterBufferH264, luma_weight_l1_flag);
|
||||
O(VASliceParameterBufferH264, luma_weight_l1);
|
||||
O(VASliceParameterBufferH264, luma_offset_l1);
|
||||
O(VASliceParameterBufferH264, chroma_weight_l1_flag);
|
||||
O(VASliceParameterBufferH264, chroma_weight_l1);
|
||||
O(VASliceParameterBufferH264, chroma_offset_l1);
|
||||
O(VASliceParameterBufferH264, va_reserved);
|
||||
|
||||
/* Bit-field allocation order: prove LSB-first rather than assume it. */
|
||||
{
|
||||
VAPictureParameterBufferH264 p;
|
||||
p.seq_fields.value = 0;
|
||||
p.seq_fields.bits.chroma_format_idc = 3;
|
||||
printf("bits seq_fields.chroma_format_idc=3 -> value 0x%08x\n", p.seq_fields.value);
|
||||
p.seq_fields.value = 0;
|
||||
p.seq_fields.bits.log2_max_frame_num_minus4 = 0xf;
|
||||
printf("bits seq_fields.log2_max_frame_num_minus4=0xf -> value 0x%08x\n", p.seq_fields.value);
|
||||
p.pic_fields.value = 0;
|
||||
p.pic_fields.bits.reference_pic_flag = 1;
|
||||
printf("bits pic_fields.reference_pic_flag=1 -> value 0x%08x\n", p.pic_fields.value);
|
||||
p.pic_fields.value = 0;
|
||||
p.pic_fields.bits.weighted_bipred_idc = 3;
|
||||
printf("bits pic_fields.weighted_bipred_idc=3 -> value 0x%08x\n", p.pic_fields.value);
|
||||
}
|
||||
printf("VA_PADDING_LOW=%d VA_PADDING_MEDIUM=%d\n", VA_PADDING_LOW, VA_PADDING_MEDIUM);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//! Decoder-creation decisions: which `VAProfile` a stream needs, which render-target
|
||||
//! format its surfaces must carry, and how many of them to allocate.
|
||||
//!
|
||||
//! The same job `pf-dxvadec`'s `config` module does for DXVA, and split out for the same
|
||||
//! reason: these are pure functions of the stream's shape, so they belong where the
|
||||
//! ordinary gates run them rather than inside `cfg(target_os = "linux")` FFI that
|
||||
//! only a box can compile.
|
||||
//!
|
||||
//! Constant values are the libva 2.23.0 enumerators.
|
||||
|
||||
/// `VAEntrypointVLD` — full bitstream decode, the only entry point this rung uses.
|
||||
pub const VA_ENTRYPOINT_VLD: u32 = 1;
|
||||
|
||||
/// `VAProfile` enumerators (`va.h`).
|
||||
pub const VA_PROFILE_H264_MAIN: i32 = 6;
|
||||
pub const VA_PROFILE_H264_HIGH: i32 = 7;
|
||||
pub const VA_PROFILE_H264_CONSTRAINED_BASELINE: i32 = 13;
|
||||
pub const VA_PROFILE_HEVC_MAIN: i32 = 17;
|
||||
pub const VA_PROFILE_HEVC_MAIN10: i32 = 18;
|
||||
|
||||
/// `VA_RT_FORMAT_*` — the surface render-target format.
|
||||
pub const VA_RT_FORMAT_YUV420: u32 = 0x0000_0001;
|
||||
pub const VA_RT_FORMAT_YUV444: u32 = 0x0000_0004;
|
||||
pub const VA_RT_FORMAT_YUV420_10: u32 = 0x0000_0100;
|
||||
|
||||
/// Which codec a session decodes. Mirrors `pf-dxvadec`'s `Codec` rather than
|
||||
/// re-exporting it: this crate must not depend on the Windows-facing one.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Codec {
|
||||
H264,
|
||||
H265,
|
||||
}
|
||||
|
||||
/// A profile choice, with the name the logs print.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct VaProfile {
|
||||
pub value: i32,
|
||||
pub name: &'static str,
|
||||
}
|
||||
|
||||
/// Why a stream cannot be decoded by this rung at all.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConfigError {
|
||||
/// A (chroma, depth) pair with no profile — 4:4:4, or a depth outside 8/10.
|
||||
UnsupportedShape { chroma_format_idc: u8, depth: u8 },
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ConfigError::UnsupportedShape {
|
||||
chroma_format_idc,
|
||||
depth,
|
||||
} => write!(
|
||||
f,
|
||||
"no VAAPI decode profile for chroma_format_idc {chroma_format_idc} at {depth} bits"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ConfigError {}
|
||||
|
||||
/// The profile a stream of this shape decodes under.
|
||||
///
|
||||
/// H.264 resolves to **High** for every 8-bit 4:2:0 stream rather than reading the
|
||||
/// SPS's `profile_idc`. That is deliberate and is what VAAPI clients do: High is a
|
||||
/// superset of Main and Constrained Baseline for the tools our hosts emit, every
|
||||
/// driver advertising H.264 decode advertises High, and picking Main for a stream
|
||||
/// that turns out to use 8x8 transforms is a mid-stream failure where picking High
|
||||
/// is not. The narrower enumerators are exported for the capability probe, which
|
||||
/// reports what the DEVICE offers.
|
||||
///
|
||||
/// 4:4:4 is refused rather than mapped: `VAProfileH264High444` exists in the header
|
||||
/// but no driver in this fleet advertises it, and the Vulkan rung is where this
|
||||
/// program's 4:4:4 support actually lives.
|
||||
pub fn profile_for(
|
||||
codec: Codec,
|
||||
chroma_format_idc: u8,
|
||||
depth: u8,
|
||||
) -> Result<VaProfile, ConfigError> {
|
||||
match (codec, chroma_format_idc, depth) {
|
||||
(Codec::H264, 1, 8) => Ok(VaProfile {
|
||||
value: VA_PROFILE_H264_HIGH,
|
||||
name: "H.264 High",
|
||||
}),
|
||||
(Codec::H265, 1, 8) => Ok(VaProfile {
|
||||
value: VA_PROFILE_HEVC_MAIN,
|
||||
name: "HEVC Main",
|
||||
}),
|
||||
(Codec::H265, 1, 10) => Ok(VaProfile {
|
||||
value: VA_PROFILE_HEVC_MAIN10,
|
||||
name: "HEVC Main 10",
|
||||
}),
|
||||
_ => Err(ConfigError::UnsupportedShape {
|
||||
chroma_format_idc,
|
||||
depth,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// The surface render-target format for a stream shape.
|
||||
pub fn rt_format(chroma_format_idc: u8, depth: u8) -> Result<u32, ConfigError> {
|
||||
match (chroma_format_idc, depth) {
|
||||
(1, 8) => Ok(VA_RT_FORMAT_YUV420),
|
||||
(1, 10) => Ok(VA_RT_FORMAT_YUV420_10),
|
||||
(3, 8) => Ok(VA_RT_FORMAT_YUV444),
|
||||
_ => Err(ConfigError::UnsupportedShape {
|
||||
chroma_format_idc,
|
||||
depth,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Headroom over the DPB for pictures the presenter still holds.
|
||||
///
|
||||
/// A surface handed to the compositor is not free to decode into, and a pool sized
|
||||
/// exactly to the DPB stalls the decoder behind the display.
|
||||
pub const PRESENTER_HEADROOM: usize = 4;
|
||||
|
||||
/// How many decode surfaces a session allocates: the DPB, plus the picture being
|
||||
/// decoded, plus [`PRESENTER_HEADROOM`].
|
||||
///
|
||||
/// VAAPI reports no driver minimum to honour (DXVA's
|
||||
/// `ConfigMinRenderTargetBuffCount` has no counterpart), so this is the whole rule.
|
||||
pub fn surface_count(max_dpb_frames: usize) -> usize {
|
||||
max_dpb_frames + 1 + PRESENTER_HEADROOM
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn h264_8bit_420_is_high() {
|
||||
let p = profile_for(Codec::H264, 1, 8).expect("the envelope's only H.264 shape");
|
||||
assert_eq!(p.value, VA_PROFILE_H264_HIGH);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hevc_picks_main_or_main10_by_depth() {
|
||||
assert_eq!(
|
||||
profile_for(Codec::H265, 1, 8).unwrap().value,
|
||||
VA_PROFILE_HEVC_MAIN
|
||||
);
|
||||
assert_eq!(
|
||||
profile_for(Codec::H265, 1, 10).unwrap().value,
|
||||
VA_PROFILE_HEVC_MAIN10
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shapes_outside_the_envelope_are_refused_not_guessed() {
|
||||
// 10-bit H.264 (High10) and 4:4:4 both have header enumerators; neither is
|
||||
// in this rung's envelope, and silently narrowing to an 8-bit profile is the
|
||||
// class of bug that decodes to garbage instead of failing.
|
||||
assert!(profile_for(Codec::H264, 1, 10).is_err());
|
||||
assert!(profile_for(Codec::H264, 3, 8).is_err());
|
||||
assert!(profile_for(Codec::H265, 3, 10).is_err());
|
||||
assert!(rt_format(1, 12).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rt_format_tracks_depth() {
|
||||
assert_eq!(rt_format(1, 8).unwrap(), VA_RT_FORMAT_YUV420);
|
||||
assert_eq!(rt_format(1, 10).unwrap(), VA_RT_FORMAT_YUV420_10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_surface_pool_covers_dpb_plus_current_plus_headroom() {
|
||||
assert_eq!(surface_count(4), 9);
|
||||
assert_eq!(surface_count(16), 21);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//! Native VAAPI decode for the Linux clients — M6 of the native-decode program, and
|
||||
//! the VAAPI counterpart of [`pf_vkdecode`] and `pf-dxvadec`.
|
||||
//!
|
||||
//! Like `pf-dxvadec`, this crate is the **CPU-testable half**: everything between
|
||||
//! pf-bitstream's per-AU plan and the buffers a `vaRenderPicture` call delivers. It
|
||||
//! links no libva, names no `VA*` handle type, and compiles on macOS and in the Linux
|
||||
//! container — which is the point. The VAAPI rung itself is
|
||||
//! `cfg(target_os = "linux")` code that only a box can build, so anything left inside
|
||||
//! that boundary is verified by a remote `cargo check` and nothing more.
|
||||
//!
|
||||
//! - [`va`]: the libva decode buffer layouts, **hand-declared**, with every size and
|
||||
//! offset measured off the real headers and pinned as compile-time assertions.
|
||||
//! - [`config`]: profile, render-target format and surface-count decisions.
|
||||
//!
|
||||
//! # Status
|
||||
//!
|
||||
//! **Layouts and configuration only.** The `AuPlan` → picture/slice/IQ-matrix
|
||||
//! conversion is the next work package; the groundwork it needs is established and
|
||||
//! recorded here so it starts from facts rather than from a reading of the spec:
|
||||
//!
|
||||
//! * **`slice_data_bit_offset` costs no new parsing.** VAAPI is the only one of the
|
||||
//! three backends that wants a bit position — DXVA takes a byte offset, Vulkan
|
||||
//! takes none — and the vendored parser already records exactly it as
|
||||
//! `SliceHeader::header_bit_size`, because cros-codecs' own production backend is
|
||||
//! VAAPI. Its definition matches field for field: computed as
|
||||
//! `(nalu.size - emulation_prevention_bytes) * 8 - bits_left`, it counts from and
|
||||
//! including the NAL header byte with emulation-prevention bytes removed, which is
|
||||
//! what `VASliceParameterBufferH264` documents.
|
||||
//! * **The slice data buffer starts at the NAL header byte**, so the start code must
|
||||
//! be skipped — `SlicePlan::data` is start-code-inclusive, and the prefix is three
|
||||
//! OR four bytes (the real host emits four on 100% of access units). The same
|
||||
//! normalisation the Vulkan ring layer performs, and the reason that layer exists.
|
||||
//! * **`VAPictureParameterBufferH264::reference_frames` is the MARKED DPB**, not the
|
||||
//! access unit's own lists — the same statement DXVA's `RefFrameList` makes, so it
|
||||
//! is filled from pf-bitstream's per-AU `dpb_refs` snapshot. Vulkan's
|
||||
//! `pReferenceSlots` is the opposite and takes the AU's own set; all three
|
||||
//! conventions now have a written home.
|
||||
//! * **Unlike DXVA's short-format slice control, VAAPI wants the per-slice reference
|
||||
//! lists themselves** (`RefPicList0`/`RefPicList1`, 32 entries each, in 8.2.4.2
|
||||
//! order) and the full prediction weight tables. `SlicePlan::ref_list0`/`ref_list1`
|
||||
//! and `SliceHeader::pred_weight_table` supply both — with one wrinkle to handle
|
||||
//! rather than discover later: the vendored `PredWeightTable` stores
|
||||
//! `luma_offset_l0` as `[i8; 32]` but `luma_offset_l1` as `[i16; 32]`, an upstream
|
||||
//! inconsistency, while libva wants `i16` for both.
|
||||
//!
|
||||
//! # Why the slot ledger is borrowed
|
||||
//!
|
||||
//! [`SlotMap`] comes from [`pf_vkdecode`] for the reason `pf-dxvadec`'s docs give at
|
||||
//! length: it is not a Vulkan object but a ledger from
|
||||
//! [`pf_bitstream::h264::PicId`] to hardware DPB slot indices, and it is as
|
||||
//! API-agnostic as it is codec-agnostic. VAAPI's own indirection is one step longer —
|
||||
//! a slot indexes the caller's surface table, because `VAPictureH264::picture_id` is
|
||||
//! a `VASurfaceID` rather than an index — so the conversion will take that table as
|
||||
//! a parameter and stay pure.
|
||||
|
||||
pub mod config;
|
||||
pub mod va;
|
||||
|
||||
/// The DPB slot ledger — borrowed, not redefined (crate docs).
|
||||
pub use pf_vkdecode::SlotError;
|
||||
pub use pf_vkdecode::SlotMap;
|
||||
|
||||
/// The planners and plans this crate converts, re-exported so the Linux layer names
|
||||
/// every type it touches through `pf_vaadec` — the same courtesy `pf-dxvadec` does
|
||||
/// for the Windows layer.
|
||||
pub use pf_bitstream::h264::AuPlan;
|
||||
pub use pf_bitstream::h264::H264Planner;
|
||||
pub use pf_bitstream::h264::PlanError;
|
||||
pub use pf_bitstream::h264::PlanWarning;
|
||||
pub use pf_bitstream::h265::AuPlan as AuPlanH265;
|
||||
pub use pf_bitstream::h265::H265Planner;
|
||||
pub use pf_bitstream::h265::PlanError as PlanErrorH265;
|
||||
pub use pf_bitstream::h265::PlanWarning as PlanWarningH265;
|
||||
/// Which warnings mean the PICTURE is damaged — pf-vkdecode's one list, so all three
|
||||
/// native rungs conceal on exactly the same predicate.
|
||||
pub use pf_vkdecode::is_integrity_warning;
|
||||
pub use pf_vkdecode::is_integrity_warning_h265;
|
||||
|
||||
pub use config::profile_for;
|
||||
pub use config::rt_format;
|
||||
pub use config::surface_count;
|
||||
pub use config::Codec;
|
||||
pub use config::ConfigError;
|
||||
pub use config::VaProfile;
|
||||
pub use config::VA_ENTRYPOINT_VLD;
|
||||
pub use va::PicFieldsH264;
|
||||
pub use va::SeqFieldsH264;
|
||||
pub use va::VaIqMatrixBufferH264;
|
||||
pub use va::VaPictureH264;
|
||||
pub use va::VaPictureParameterBufferH264;
|
||||
pub use va::VaSliceParameterBufferH264;
|
||||
@@ -0,0 +1,575 @@
|
||||
//! The libva decode buffer layouts for H.264, **hand-declared**.
|
||||
//!
|
||||
//! There is no libva binding in this workspace and this crate deliberately does not
|
||||
//! introduce one: it must compile and be tested on macOS and in the Linux container,
|
||||
//! where `libva` headers need not exist at all. So the structures VAAPI reads are
|
||||
//! declared here as plain `#[repr(C)]` PODs, exactly as `pf-dxvadec`'s `dxva` module declares
|
||||
//! DXVA's — same reasoning, same discipline.
|
||||
//!
|
||||
//! # These are not eyeballed
|
||||
//!
|
||||
//! Every size, every field offset and the bit-field allocation order below were
|
||||
//! measured against the real headers (libva **2.23.0**, `x86_64-linux-gnu`) by
|
||||
//! compiling a probe that printed `sizeof`, `_Alignof` and `offsetof` for each field
|
||||
//! and set individual bit-fields to read the resulting word back. The numbers that
|
||||
//! probe produced are pinned as `const` assertions at the bottom of this module, so
|
||||
//! a mistake here is a compile error rather than a driver reading the wrong byte.
|
||||
//!
|
||||
//! The measured facts worth stating in prose, because they are the ones a reader
|
||||
//! would otherwise assume wrongly:
|
||||
//!
|
||||
//! * `VAPictureH264` is **36** bytes — five 4-byte fields plus `VA_PADDING_LOW`
|
||||
//! (4 × `uint32_t`) of reserved tail. It is embedded 1 + 16 times in the picture
|
||||
//! parameter buffer and 64 times in the slice parameter buffer, so its size being
|
||||
//! right is load-bearing for every offset after it.
|
||||
//! * `VAPictureParameterBufferH264` is **672** bytes, `VAIQMatrixBufferH264` **240**,
|
||||
//! and `VASliceParameterBufferH264` **3128** — the last one because it carries two
|
||||
//! 32-entry reference lists *and* the full prediction weight tables inline.
|
||||
//! * The three deprecated FMO fields (`num_slice_groups_minus1`,
|
||||
//! `slice_group_map_type`, `slice_group_change_rate_minus1`) still occupy bytes
|
||||
//! 624..628. Deprecated does not mean absent: dropping them would shift every
|
||||
//! later field. They are declared, and always zero.
|
||||
//! * C bit-fields on this ABI allocate from the **least significant bit**, proven
|
||||
//! rather than assumed: setting `log2_max_frame_num_minus4` (the 4 bits declared
|
||||
//! after eight single-bit flags and a 2-bit field) to `0xf` yields `0x0000_0f00`,
|
||||
//! and `weighted_bipred_idc = 3` yields `0x0000_000c`.
|
||||
//!
|
||||
//! # Surface identity
|
||||
//!
|
||||
//! `VAPictureH264::picture_id` is a `VASurfaceID`, not a slot index — unlike DXVA,
|
||||
//! where the surface index and the DPB slot are the same number by construction.
|
||||
//! This crate never invents one: the conversion (`plan_to_va`) takes the caller's
|
||||
//! slot → `VASurfaceID` table and indexes it, so the Linux layer owns surface
|
||||
//! allocation and this half stays pure.
|
||||
|
||||
/// `VA_INVALID_SURFACE` — what an unused `ReferenceFrames` / `RefPicList` entry
|
||||
/// carries. Paired with [`VA_PICTURE_H264_INVALID`]; drivers key on the flag, but a
|
||||
/// stale surface id in an "invalid" entry is the kind of thing that decodes fine on
|
||||
/// one vendor and not another, so both are always written together.
|
||||
pub const VA_INVALID_SURFACE: u32 = 0xffff_ffff;
|
||||
|
||||
/// Flags for [`VaPictureH264::flags`].
|
||||
pub const VA_PICTURE_H264_INVALID: u32 = 0x0000_0001;
|
||||
pub const VA_PICTURE_H264_TOP_FIELD: u32 = 0x0000_0002;
|
||||
pub const VA_PICTURE_H264_BOTTOM_FIELD: u32 = 0x0000_0004;
|
||||
pub const VA_PICTURE_H264_SHORT_TERM_REFERENCE: u32 = 0x0000_0008;
|
||||
pub const VA_PICTURE_H264_LONG_TERM_REFERENCE: u32 = 0x0000_0010;
|
||||
|
||||
/// `VA_SLICE_DATA_FLAG_ALL` — this buffer holds the whole slice, which is the only
|
||||
/// shape we submit (the wire delivers complete access units; nothing here streams a
|
||||
/// slice in fragments).
|
||||
pub const VA_SLICE_DATA_FLAG_ALL: u32 = 0x00;
|
||||
|
||||
/// `VAPictureH264` — one DPB entry, or the current picture.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct VaPictureH264 {
|
||||
/// `VASurfaceID` of the decode surface holding this picture.
|
||||
pub picture_id: u32,
|
||||
/// `frame_num` for a short-term reference, `LongTermFrameIdx` for a long-term
|
||||
/// one — the same pair DXVA and Vulkan key references by, which is why
|
||||
/// [`pf_bitstream::h264::RefPic`] already carries exactly this.
|
||||
pub frame_idx: u32,
|
||||
pub flags: u32,
|
||||
pub top_field_order_cnt: i32,
|
||||
pub bottom_field_order_cnt: i32,
|
||||
/// `va_reserved[VA_PADDING_LOW]` — "must be zero".
|
||||
pub va_reserved: [u32; 4],
|
||||
}
|
||||
|
||||
impl VaPictureH264 {
|
||||
/// The entry an unused reference slot carries: invalid flag AND invalid surface.
|
||||
pub const fn invalid() -> Self {
|
||||
VaPictureH264 {
|
||||
picture_id: VA_INVALID_SURFACE,
|
||||
frame_idx: 0,
|
||||
flags: VA_PICTURE_H264_INVALID,
|
||||
top_field_order_cnt: 0,
|
||||
bottom_field_order_cnt: 0,
|
||||
va_reserved: [0; 4],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `VAPictureParameterBufferH264::seq_fields`, unpacked.
|
||||
///
|
||||
/// Declared as its own type rather than as a bare `u32` so the bit layout lives
|
||||
/// beside the structure it belongs to and can be unit-tested on its own; [`Self::pack`]
|
||||
/// is the only place the shifts appear.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct SeqFieldsH264 {
|
||||
pub chroma_format_idc: u8,
|
||||
/// `residual_colour_transform_flag` in the header's older spelling; the standard
|
||||
/// renamed it `separate_colour_plane_flag`.
|
||||
pub separate_colour_plane_flag: bool,
|
||||
pub gaps_in_frame_num_value_allowed_flag: bool,
|
||||
pub frame_mbs_only_flag: bool,
|
||||
pub mb_adaptive_frame_field_flag: bool,
|
||||
pub direct_8x8_inference_flag: bool,
|
||||
/// A.3.3.2 — level-derived, not an SPS syntax element.
|
||||
pub min_luma_bi_pred_size8x8: bool,
|
||||
pub log2_max_frame_num_minus4: u8,
|
||||
pub pic_order_cnt_type: u8,
|
||||
pub log2_max_pic_order_cnt_lsb_minus4: u8,
|
||||
pub delta_pic_order_always_zero_flag: bool,
|
||||
}
|
||||
|
||||
impl SeqFieldsH264 {
|
||||
/// Pack to the `uint32_t` the union aliases. Bit positions are the measured
|
||||
/// allocation order (module docs), LSB first, in declaration order.
|
||||
pub const fn pack(self) -> u32 {
|
||||
(self.chroma_format_idc as u32 & 0x3)
|
||||
| ((self.separate_colour_plane_flag as u32) << 2)
|
||||
| ((self.gaps_in_frame_num_value_allowed_flag as u32) << 3)
|
||||
| ((self.frame_mbs_only_flag as u32) << 4)
|
||||
| ((self.mb_adaptive_frame_field_flag as u32) << 5)
|
||||
| ((self.direct_8x8_inference_flag as u32) << 6)
|
||||
| ((self.min_luma_bi_pred_size8x8 as u32) << 7)
|
||||
| ((self.log2_max_frame_num_minus4 as u32 & 0xf) << 8)
|
||||
| ((self.pic_order_cnt_type as u32 & 0x3) << 12)
|
||||
| ((self.log2_max_pic_order_cnt_lsb_minus4 as u32 & 0xf) << 14)
|
||||
| ((self.delta_pic_order_always_zero_flag as u32) << 18)
|
||||
}
|
||||
}
|
||||
|
||||
/// `VAPictureParameterBufferH264::pic_fields`, unpacked. See [`SeqFieldsH264`].
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct PicFieldsH264 {
|
||||
pub entropy_coding_mode_flag: bool,
|
||||
pub weighted_pred_flag: bool,
|
||||
pub weighted_bipred_idc: u8,
|
||||
pub transform_8x8_mode_flag: bool,
|
||||
pub field_pic_flag: bool,
|
||||
pub constrained_intra_pred_flag: bool,
|
||||
/// `bottom_field_pic_order_in_frame_present_flag` in current spec spelling.
|
||||
pub pic_order_present_flag: bool,
|
||||
pub deblocking_filter_control_present_flag: bool,
|
||||
pub redundant_pic_cnt_present_flag: bool,
|
||||
/// `nal_ref_idc != 0` — a statement about THIS picture, not the PPS.
|
||||
pub reference_pic_flag: bool,
|
||||
}
|
||||
|
||||
impl PicFieldsH264 {
|
||||
pub const fn pack(self) -> u32 {
|
||||
(self.entropy_coding_mode_flag as u32)
|
||||
| ((self.weighted_pred_flag as u32) << 1)
|
||||
| ((self.weighted_bipred_idc as u32 & 0x3) << 2)
|
||||
| ((self.transform_8x8_mode_flag as u32) << 4)
|
||||
| ((self.field_pic_flag as u32) << 5)
|
||||
| ((self.constrained_intra_pred_flag as u32) << 6)
|
||||
| ((self.pic_order_present_flag as u32) << 7)
|
||||
| ((self.deblocking_filter_control_present_flag as u32) << 8)
|
||||
| ((self.redundant_pic_cnt_present_flag as u32) << 9)
|
||||
| ((self.reference_pic_flag as u32) << 10)
|
||||
}
|
||||
}
|
||||
|
||||
/// `VAPictureParameterBufferH264` — one per picture, before any slice data.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct VaPictureParameterBufferH264 {
|
||||
pub curr_pic: VaPictureH264,
|
||||
/// The DPB, not this AU's reference lists. VAAPI documents this as "in DPB",
|
||||
/// which is the same statement DXVA's `RefFrameList` makes and the opposite of
|
||||
/// Vulkan's `pReferenceSlots` — so it is filled from pf-bitstream's per-AU
|
||||
/// `dpb_refs` snapshot, the accessor M5 added for exactly this distinction.
|
||||
pub reference_frames: [VaPictureH264; 16],
|
||||
pub picture_width_in_mbs_minus1: u16,
|
||||
pub picture_height_in_mbs_minus1: u16,
|
||||
pub bit_depth_luma_minus8: u8,
|
||||
pub bit_depth_chroma_minus8: u8,
|
||||
pub num_ref_frames: u8,
|
||||
/// Packed [`SeqFieldsH264`]. (One byte of padding precedes it — `num_ref_frames`
|
||||
/// ends at 619 and the union is 4-aligned at 620.)
|
||||
pub seq_fields: u32,
|
||||
/// Deprecated FMO fields. Still occupy bytes 624..628; always zero here, and
|
||||
/// the conversion (`plan_to_va`) refuses a stream that uses slice groups rather
|
||||
/// than silently ignoring them.
|
||||
pub num_slice_groups_minus1: u8,
|
||||
pub slice_group_map_type: u8,
|
||||
pub slice_group_change_rate_minus1: u16,
|
||||
pub pic_init_qp_minus26: i8,
|
||||
pub pic_init_qs_minus26: i8,
|
||||
pub chroma_qp_index_offset: i8,
|
||||
pub second_chroma_qp_index_offset: i8,
|
||||
/// Packed [`PicFieldsH264`].
|
||||
pub pic_fields: u32,
|
||||
pub frame_num: u16,
|
||||
/// `va_reserved[VA_PADDING_MEDIUM]`. Two bytes of padding precede it (`frame_num`
|
||||
/// ends at 638, the array is 4-aligned at 640).
|
||||
pub va_reserved: [u32; 8],
|
||||
}
|
||||
|
||||
/// `VAIQMatrixBufferH264` — both scaling list sets, raster scan order.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct VaIqMatrixBufferH264 {
|
||||
pub scaling_list4x4: [[u8; 16]; 6],
|
||||
pub scaling_list8x8: [[u8; 64]; 2],
|
||||
pub va_reserved: [u32; 4],
|
||||
}
|
||||
|
||||
/// `VASliceParameterBufferH264` — one per slice NALU.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct VaSliceParameterBufferH264 {
|
||||
pub slice_data_size: u32,
|
||||
pub slice_data_offset: u32,
|
||||
pub slice_data_flag: u32,
|
||||
/// Bit offset from the start of the NAL unit byte to the first bit of
|
||||
/// `slice_data()`, counted **after emulation-prevention bytes are removed** even
|
||||
/// though the buffer handed to the driver still contains them.
|
||||
///
|
||||
/// Nothing else in this program needs this number: DXVA takes a byte offset to
|
||||
/// the slice and Vulkan takes none at all. The vendored parser records it as
|
||||
/// `SliceHeader::header_bit_size` because its own production backend is VAAPI,
|
||||
/// so it costs no new parsing — see the crate docs.
|
||||
pub slice_data_bit_offset: u16,
|
||||
pub first_mb_in_slice: u16,
|
||||
pub slice_type: u8,
|
||||
pub direct_spatial_mv_pred_flag: u8,
|
||||
pub num_ref_idx_l0_active_minus1: u8,
|
||||
pub num_ref_idx_l1_active_minus1: u8,
|
||||
pub cabac_init_idc: u8,
|
||||
pub slice_qp_delta: i8,
|
||||
pub disable_deblocking_filter_idc: u8,
|
||||
pub slice_alpha_c0_offset_div2: i8,
|
||||
pub slice_beta_offset_div2: i8,
|
||||
/// 8.2.4.2 reference lists — the AU's own, unlike
|
||||
/// [`VaPictureParameterBufferH264::reference_frames`].
|
||||
pub ref_pic_list0: [VaPictureH264; 32],
|
||||
pub ref_pic_list1: [VaPictureH264; 32],
|
||||
pub luma_log2_weight_denom: u8,
|
||||
pub chroma_log2_weight_denom: u8,
|
||||
pub luma_weight_l0_flag: u8,
|
||||
pub luma_weight_l0: [i16; 32],
|
||||
pub luma_offset_l0: [i16; 32],
|
||||
pub chroma_weight_l0_flag: u8,
|
||||
pub chroma_weight_l0: [[i16; 2]; 32],
|
||||
pub chroma_offset_l0: [[i16; 2]; 32],
|
||||
pub luma_weight_l1_flag: u8,
|
||||
pub luma_weight_l1: [i16; 32],
|
||||
pub luma_offset_l1: [i16; 32],
|
||||
pub chroma_weight_l1_flag: u8,
|
||||
pub chroma_weight_l1: [[i16; 2]; 32],
|
||||
pub chroma_offset_l1: [[i16; 2]; 32],
|
||||
pub va_reserved: [u32; 4],
|
||||
}
|
||||
|
||||
impl VaSliceParameterBufferH264 {
|
||||
/// An all-zero record with the reference lists invalidated — the base every
|
||||
/// slice is built from, so an unwritten entry is never a stale surface id.
|
||||
pub const fn zeroed() -> Self {
|
||||
VaSliceParameterBufferH264 {
|
||||
slice_data_size: 0,
|
||||
slice_data_offset: 0,
|
||||
slice_data_flag: VA_SLICE_DATA_FLAG_ALL,
|
||||
slice_data_bit_offset: 0,
|
||||
first_mb_in_slice: 0,
|
||||
slice_type: 0,
|
||||
direct_spatial_mv_pred_flag: 0,
|
||||
num_ref_idx_l0_active_minus1: 0,
|
||||
num_ref_idx_l1_active_minus1: 0,
|
||||
cabac_init_idc: 0,
|
||||
slice_qp_delta: 0,
|
||||
disable_deblocking_filter_idc: 0,
|
||||
slice_alpha_c0_offset_div2: 0,
|
||||
slice_beta_offset_div2: 0,
|
||||
ref_pic_list0: [VaPictureH264::invalid(); 32],
|
||||
ref_pic_list1: [VaPictureH264::invalid(); 32],
|
||||
luma_log2_weight_denom: 0,
|
||||
chroma_log2_weight_denom: 0,
|
||||
luma_weight_l0_flag: 0,
|
||||
luma_weight_l0: [0; 32],
|
||||
luma_offset_l0: [0; 32],
|
||||
chroma_weight_l0_flag: 0,
|
||||
chroma_weight_l0: [[0; 2]; 32],
|
||||
chroma_offset_l0: [[0; 2]; 32],
|
||||
luma_weight_l1_flag: 0,
|
||||
luma_weight_l1: [0; 32],
|
||||
luma_offset_l1: [0; 32],
|
||||
chroma_weight_l1_flag: 0,
|
||||
chroma_weight_l1: [[0; 2]; 32],
|
||||
chroma_offset_l1: [[0; 2]; 32],
|
||||
va_reserved: [0; 4],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Layout proofs — the probe's output, pinned.
|
||||
//
|
||||
// libva 2.23.0, x86_64-linux-gnu. A `#[repr(C)]` Rust struct and a C struct agree
|
||||
// by definition of repr(C), so these assertions are not testing the compiler: they
|
||||
// are testing that the FIELDS AND THEIR ORDER above match the header, which is the
|
||||
// part a human transcribed and can get wrong.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const _: () = {
|
||||
use std::mem::offset_of;
|
||||
use std::mem::size_of;
|
||||
|
||||
assert!(size_of::<VaPictureH264>() == 36);
|
||||
assert!(offset_of!(VaPictureH264, picture_id) == 0);
|
||||
assert!(offset_of!(VaPictureH264, frame_idx) == 4);
|
||||
assert!(offset_of!(VaPictureH264, flags) == 8);
|
||||
assert!(offset_of!(VaPictureH264, top_field_order_cnt) == 12);
|
||||
assert!(offset_of!(VaPictureH264, bottom_field_order_cnt) == 16);
|
||||
assert!(offset_of!(VaPictureH264, va_reserved) == 20);
|
||||
|
||||
assert!(size_of::<VaPictureParameterBufferH264>() == 672);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, curr_pic) == 0);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, reference_frames) == 36);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, picture_width_in_mbs_minus1) == 612);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, picture_height_in_mbs_minus1) == 614);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, bit_depth_luma_minus8) == 616);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, bit_depth_chroma_minus8) == 617);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, num_ref_frames) == 618);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, seq_fields) == 620);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, num_slice_groups_minus1) == 624);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, slice_group_map_type) == 625);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, slice_group_change_rate_minus1) == 626);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, pic_init_qp_minus26) == 628);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, pic_init_qs_minus26) == 629);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, chroma_qp_index_offset) == 630);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, second_chroma_qp_index_offset) == 631);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, pic_fields) == 632);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, frame_num) == 636);
|
||||
assert!(offset_of!(VaPictureParameterBufferH264, va_reserved) == 640);
|
||||
|
||||
assert!(size_of::<VaIqMatrixBufferH264>() == 240);
|
||||
assert!(offset_of!(VaIqMatrixBufferH264, scaling_list4x4) == 0);
|
||||
assert!(offset_of!(VaIqMatrixBufferH264, scaling_list8x8) == 96);
|
||||
assert!(offset_of!(VaIqMatrixBufferH264, va_reserved) == 224);
|
||||
|
||||
assert!(size_of::<VaSliceParameterBufferH264>() == 3128);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_data_size) == 0);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_data_offset) == 4);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_data_flag) == 8);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_data_bit_offset) == 12);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, first_mb_in_slice) == 14);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_type) == 16);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, direct_spatial_mv_pred_flag) == 17);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, num_ref_idx_l0_active_minus1) == 18);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, num_ref_idx_l1_active_minus1) == 19);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, cabac_init_idc) == 20);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_qp_delta) == 21);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, disable_deblocking_filter_idc) == 22);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_alpha_c0_offset_div2) == 23);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, slice_beta_offset_div2) == 24);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, ref_pic_list0) == 28);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, ref_pic_list1) == 1180);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, luma_log2_weight_denom) == 2332);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, chroma_log2_weight_denom) == 2333);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, luma_weight_l0_flag) == 2334);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, luma_weight_l0) == 2336);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, luma_offset_l0) == 2400);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, chroma_weight_l0_flag) == 2464);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, chroma_weight_l0) == 2466);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, chroma_offset_l0) == 2594);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, luma_weight_l1_flag) == 2722);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, luma_weight_l1) == 2724);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, luma_offset_l1) == 2788);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, chroma_weight_l1_flag) == 2852);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, chroma_weight_l1) == 2854);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, chroma_offset_l1) == 2982);
|
||||
assert!(offset_of!(VaSliceParameterBufferH264, va_reserved) == 3112);
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// The three bit patterns the probe read back off real headers. If the shifts
|
||||
// above are ever "tidied", these fail with the measured value in hand.
|
||||
#[test]
|
||||
fn seq_fields_pack_where_the_probe_measured() {
|
||||
assert_eq!(
|
||||
SeqFieldsH264 {
|
||||
chroma_format_idc: 3,
|
||||
..Default::default()
|
||||
}
|
||||
.pack(),
|
||||
0x0000_0003
|
||||
);
|
||||
assert_eq!(
|
||||
SeqFieldsH264 {
|
||||
log2_max_frame_num_minus4: 0xf,
|
||||
..Default::default()
|
||||
}
|
||||
.pack(),
|
||||
0x0000_0f00
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pic_fields_pack_where_the_probe_measured() {
|
||||
assert_eq!(
|
||||
PicFieldsH264 {
|
||||
reference_pic_flag: true,
|
||||
..Default::default()
|
||||
}
|
||||
.pack(),
|
||||
0x0000_0400
|
||||
);
|
||||
assert_eq!(
|
||||
PicFieldsH264 {
|
||||
weighted_bipred_idc: 3,
|
||||
..Default::default()
|
||||
}
|
||||
.pack(),
|
||||
0x0000_000c
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_seq_field_owns_a_distinct_bit_range() {
|
||||
// Each field set alone must light only its own bits, and the OR of all of
|
||||
// them must equal the packing of all-at-once: a shift typo that overlapped
|
||||
// two fields would still pass the two probe vectors above.
|
||||
let each = [
|
||||
SeqFieldsH264 {
|
||||
chroma_format_idc: 3,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
separate_colour_plane_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
gaps_in_frame_num_value_allowed_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
frame_mbs_only_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
mb_adaptive_frame_field_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
direct_8x8_inference_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
min_luma_bi_pred_size8x8: true,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
log2_max_frame_num_minus4: 0xf,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
pic_order_cnt_type: 3,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
log2_max_pic_order_cnt_lsb_minus4: 0xf,
|
||||
..Default::default()
|
||||
},
|
||||
SeqFieldsH264 {
|
||||
delta_pic_order_always_zero_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
let mut seen = 0u32;
|
||||
for f in each {
|
||||
let bits = f.pack();
|
||||
assert_ne!(bits, 0, "a field packed to nothing");
|
||||
assert_eq!(seen & bits, 0, "two fields share a bit: {bits:#010x}");
|
||||
seen |= bits;
|
||||
}
|
||||
let all = SeqFieldsH264 {
|
||||
chroma_format_idc: 3,
|
||||
separate_colour_plane_flag: true,
|
||||
gaps_in_frame_num_value_allowed_flag: true,
|
||||
frame_mbs_only_flag: true,
|
||||
mb_adaptive_frame_field_flag: true,
|
||||
direct_8x8_inference_flag: true,
|
||||
min_luma_bi_pred_size8x8: true,
|
||||
log2_max_frame_num_minus4: 0xf,
|
||||
pic_order_cnt_type: 3,
|
||||
log2_max_pic_order_cnt_lsb_minus4: 0xf,
|
||||
delta_pic_order_always_zero_flag: true,
|
||||
};
|
||||
assert_eq!(all.pack(), seen);
|
||||
// Nothing may reach past bit 18 — the last declared bit.
|
||||
assert_eq!(seen & !0x0007_ffff, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_pic_field_owns_a_distinct_bit_range() {
|
||||
let each = [
|
||||
PicFieldsH264 {
|
||||
entropy_coding_mode_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
weighted_pred_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
weighted_bipred_idc: 3,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
transform_8x8_mode_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
field_pic_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
constrained_intra_pred_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
pic_order_present_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
deblocking_filter_control_present_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
redundant_pic_cnt_present_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
PicFieldsH264 {
|
||||
reference_pic_flag: true,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
let mut seen = 0u32;
|
||||
for f in each {
|
||||
let bits = f.pack();
|
||||
assert_ne!(bits, 0);
|
||||
assert_eq!(seen & bits, 0, "two fields share a bit: {bits:#010x}");
|
||||
seen |= bits;
|
||||
}
|
||||
assert_eq!(seen & !0x0000_07ff, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unused_reference_entry_is_invalid_in_both_ways() {
|
||||
let e = VaPictureH264::invalid();
|
||||
assert_eq!(e.flags, VA_PICTURE_H264_INVALID);
|
||||
assert_eq!(e.picture_id, VA_INVALID_SURFACE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_zeroed_slice_record_starts_with_invalidated_lists() {
|
||||
let s = VaSliceParameterBufferH264::zeroed();
|
||||
assert!(s
|
||||
.ref_pic_list0
|
||||
.iter()
|
||||
.all(|e| e.flags == VA_PICTURE_H264_INVALID));
|
||||
assert!(s
|
||||
.ref_pic_list1
|
||||
.iter()
|
||||
.all(|e| e.picture_id == VA_INVALID_SURFACE));
|
||||
assert_eq!(s.slice_data_flag, VA_SLICE_DATA_FLAG_ALL);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user