Files
libva-v4l2-request-fourier/src/h264_slice_header.h
T
test0r 9de1be34ef h264: bit-parse slice_header to populate DECODE_PARAMS bit-size fields
The load-bearing fix from diff_against_ffmpeg.md (campaign repo).

Adds src/h264_slice_header.{c,h} — a minimal H.264 slice_header()
bit-parser per ITU-T H.264 (08/2024) §7.3.3. Parses just enough of
the slice header to populate the V4L2 DECODE_PARAMS fields VAAPI
doesn't carry and that hantro G1 hardware reads directly out of
DECODE_PARAMS into MMIO registers:

  dec_param->dec_ref_pic_marking_bit_size  -> G1_REG_DEC_CTRL5_REFPIC_MK_LEN
  dec_param->idr_pic_id                    -> G1_REG_DEC_CTRL5_IDR_PIC_ID
  dec_param->pic_order_cnt_bit_size        -> G1_REG_DEC_CTRL6_POC_LENGTH
  dec_param->pic_order_cnt_lsb             -> hantro reflist builder (poc_type=0)
  dec_param->delta_pic_order_cnt_bottom    -> same
  dec_param->delta_pic_order_cnt0/1        -> hantro reflist builder (poc_type=1)

Without these set correctly, hantro's hardware bitstream parser
walks past zero bits in the slice header, lands on garbage, decodes
zero pixels — the all-zero CAPTURE output observed across both mpv
and Firefox during 2026-05-04 Phase 0 (see libva-multiplanar campaign
phase0_evidence/2026-05-04-kernel-trace/findings.md).

Implementation:
- Minimal RBSP bit reader (br_read_u/_ue/_se), MSB-first, fault-flag
  on overrun.
- Emulation-prevention unescape (strips 0x03 after 0x00 0x00) on
  the first 64 bytes of the slice — slice headers fit comfortably.
- Walks slice_header() up to and including dec_ref_pic_marking(),
  measuring bit positions for the *_bit_size fields.
- Skips ref_pic_list_modification() and pred_weight_table() —
  needed only to advance the bit position to dec_ref_pic_marking().
- Returns a struct with the V4L2 fields plus diagnostics
  (first_mb_in_slice, slice_type, pps_id, frame_num).

Wired into h264_va_picture_to_v4l2 (src/h264.c) right after the
nal_ref_idc/nal_unit_type extraction. SPS/PPS context is built from
VAPicture's seq_fields and pic_fields; num_ref_idx_l0/l1_active
defaults come from VASlice (best available substitute for the
parsed PPS values). On parse success, populates decode_params with
the recovered values + emits a request_log with the decoded fields
for cross-validation against VAAPI's pre-parsed values.

src/meson.build: adds h264_slice_header.{c,h} to sources.

Cross-references:
- FFmpeg libavcodec/h264_slice.c (Kwiboo v4l2-request-n8.1) — populates
  H264SliceContext::ref_pic_marking_bit_size / pic_order_cnt_bit_size
  by the same bit-precise parse, then v4l2_request_h264.c forwards
  to V4L2.
- Linux drivers/media/platform/verisilicon/hantro_g1_h264_dec.c
  set_params() — the register-write code that reads these fields.

MVC nal_unit_type 20/21 unhandled (this fork strips MVC alongside
HEVC). Multi-slice non-IDR streams parse the first slice's header
only; for FRAME_BASED mode that's fine — kernel sees the whole
bitstream and parses subsequent slices itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:34:47 +00:00

96 lines
3.5 KiB
C

/*
* H.264 slice header bit-parser for libva-v4l2-request.
*
* Extracts the slice-header bit-position and value fields that
* V4L2_CID_STATELESS_H264_DECODE_PARAMS requires (idr_pic_id,
* pic_order_cnt_lsb, delta_pic_order_cnt_*, pic_order_cnt_bit_size,
* dec_ref_pic_marking_bit_size). VAAPI's pre-parsed
* VAPictureParameterBufferH264 / VASliceParameterBufferH264 do not
* carry these — they live only in the bitstream's slice_header()
* syntax. Hantro G1 (drivers/media/platform/verisilicon/
* hantro_g1_h264_dec.c::set_params) writes the bit_size fields
* directly into MMIO registers G1_REG_DEC_CTRL5_REFPIC_MK_LEN and
* G1_REG_DEC_CTRL6_POC_LENGTH; with zeros the hardware bitstream
* parser walks past zero bits, lands on garbage, decodes nothing.
*
* Spec reference: ITU-T Rec. H.264 (08/2024) §7.3.3 slice_header
* and §7.3.3.1 ref_pic_list_modification, §7.3.3.2 pred_weight_table,
* §7.3.3.3 dec_ref_pic_marking.
*
* Cross-reference (proven working on hantro): FFmpeg's
* libavcodec/h264_slice.c populates H264SliceContext::ref_pic_marking_
* bit_size and pic_order_cnt_bit_size from its bit-precise slice
* header parse, then v4l2_request_h264.c forwards them.
*/
#ifndef H264_SLICE_HEADER_H
#define H264_SLICE_HEADER_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct h264_slice_header_context {
/* From SPS (the active SPS at slice-time). */
bool separate_colour_plane_flag;
uint8_t log2_max_frame_num_minus4;
bool frame_mbs_only_flag;
uint8_t pic_order_cnt_type;
uint8_t log2_max_pic_order_cnt_lsb_minus4;
bool delta_pic_order_always_zero_flag;
/* From PPS (the active PPS at slice-time). */
bool bottom_field_pic_order_in_frame_present_flag;
bool redundant_pic_cnt_present_flag;
bool weighted_pred_flag;
uint8_t weighted_bipred_idc;
uint8_t num_ref_idx_l0_default_active_minus1;
uint8_t num_ref_idx_l1_default_active_minus1;
uint8_t chroma_format_idc;
uint8_t bit_depth_luma_minus8;
uint8_t bit_depth_chroma_minus8;
/* From the NAL unit header (already extracted by the caller). */
uint8_t nal_unit_type;
uint8_t nal_ref_idc;
};
struct h264_slice_header_info {
uint16_t idr_pic_id;
uint16_t pic_order_cnt_lsb;
int32_t delta_pic_order_cnt_bottom;
int32_t delta_pic_order_cnt0;
int32_t delta_pic_order_cnt1;
uint32_t pic_order_cnt_bit_size;
uint32_t dec_ref_pic_marking_bit_size;
/* Diagnostic — useful for cross-checking VAAPI vs bitstream values. */
uint32_t first_mb_in_slice;
uint32_t slice_type;
uint32_t pic_parameter_set_id;
uint32_t frame_num;
};
/*
* Parse slice_header() up to dec_ref_pic_marking() (inclusive) of
* the H.264 RBSP slice_layer_without_partitioning_rbsp() syntax,
* extracting the V4L2 DECODE_PARAMS fields. Returns 0 on success,
* negative errno-shaped value on parse failure (insufficient data,
* malformed exp-Golomb, etc.).
*
* @nal_payload: pointer to the byte AFTER the NAL header byte
* (i.e. start of the RBSP proper; caller has already
* skipped any ANNEX_B start code and the 1-byte
* nal_unit_header). Will be RBSP-unescaped internally
* before parsing.
* @nal_payload_length: bytes available at @nal_payload.
* @ctx: SPS/PPS/NAL context required to drive the parse.
* @out: filled on success. All fields zero-initialized first.
*/
int h264_parse_slice_header(const uint8_t *nal_payload,
size_t nal_payload_length,
const struct h264_slice_header_context *ctx,
struct h264_slice_header_info *out);
#endif /* H264_SLICE_HEADER_H */