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>
This commit is contained in:
+85
@@ -39,6 +39,7 @@
|
||||
#include "utils.h"
|
||||
#include "surface.h"
|
||||
#include "v4l2.h"
|
||||
#include "h264_slice_header.h"
|
||||
|
||||
enum h264_slice_type {
|
||||
H264_SLICE_P = 0,
|
||||
@@ -321,6 +322,90 @@ static void h264_va_picture_to_v4l2(struct request_data *driver_data,
|
||||
nal_ref_idc = (b[0] >> 5) & 0x3;
|
||||
nal_unit_type = b[0] & 0x1f;
|
||||
|
||||
/*
|
||||
* Bit-parse the slice_header() to recover fields VAAPI doesn't
|
||||
* forward and that hantro G1 hardware reads out of DECODE_PARAMS:
|
||||
*
|
||||
* - dec_ref_pic_marking_bit_size -> G1_REG_DEC_CTRL5_REFPIC_MK_LEN
|
||||
* - idr_pic_id -> G1_REG_DEC_CTRL5_IDR_PIC_ID
|
||||
* - pic_order_cnt_bit_size -> G1_REG_DEC_CTRL6_POC_LENGTH
|
||||
* - pic_order_cnt_lsb / delta_pic_order_cnt_* (used by hantro
|
||||
* reference-list builder for poc_type=0/1 inter prediction)
|
||||
*
|
||||
* Without these set correctly, hantro's hardware bitstream parser
|
||||
* walks past zero bits, lands on garbage, decodes zero pixels —
|
||||
* the all-zero CAPTURE output observed during 2026-05-04 Phase 0.
|
||||
*
|
||||
* Spec: ITU-T H.264 §7.3.3 slice_header. Cross-reference (proven
|
||||
* working): FFmpeg libavcodec/h264_slice.c populates
|
||||
* H264SliceContext::ref_pic_marking_bit_size and
|
||||
* pic_order_cnt_bit_size by the same bit-precise parse.
|
||||
*/
|
||||
{
|
||||
const struct h264_slice_header_context sh_ctx = {
|
||||
.separate_colour_plane_flag =
|
||||
(VAPicture->seq_fields.bits.residual_colour_transform_flag != 0),
|
||||
.log2_max_frame_num_minus4 =
|
||||
VAPicture->seq_fields.bits.log2_max_frame_num_minus4,
|
||||
.frame_mbs_only_flag =
|
||||
(VAPicture->seq_fields.bits.frame_mbs_only_flag != 0),
|
||||
.pic_order_cnt_type =
|
||||
VAPicture->seq_fields.bits.pic_order_cnt_type,
|
||||
.log2_max_pic_order_cnt_lsb_minus4 =
|
||||
VAPicture->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4,
|
||||
.delta_pic_order_always_zero_flag =
|
||||
(VAPicture->seq_fields.bits.delta_pic_order_always_zero_flag != 0),
|
||||
.bottom_field_pic_order_in_frame_present_flag =
|
||||
(VAPicture->pic_fields.bits.pic_order_present_flag != 0),
|
||||
.redundant_pic_cnt_present_flag =
|
||||
(VAPicture->pic_fields.bits.redundant_pic_cnt_present_flag != 0),
|
||||
.weighted_pred_flag =
|
||||
(VAPicture->pic_fields.bits.weighted_pred_flag != 0),
|
||||
.weighted_bipred_idc =
|
||||
VAPicture->pic_fields.bits.weighted_bipred_idc,
|
||||
.num_ref_idx_l0_default_active_minus1 =
|
||||
surface->params.h264.slice.num_ref_idx_l0_active_minus1,
|
||||
.num_ref_idx_l1_default_active_minus1 =
|
||||
surface->params.h264.slice.num_ref_idx_l1_active_minus1,
|
||||
.chroma_format_idc =
|
||||
VAPicture->seq_fields.bits.chroma_format_idc,
|
||||
.bit_depth_luma_minus8 =
|
||||
VAPicture->bit_depth_luma_minus8,
|
||||
.bit_depth_chroma_minus8 =
|
||||
VAPicture->bit_depth_chroma_minus8,
|
||||
.nal_unit_type = nal_unit_type,
|
||||
.nal_ref_idc = nal_ref_idc,
|
||||
};
|
||||
struct h264_slice_header_info sh = { 0 };
|
||||
unsigned char *nal_payload = b + 1; /* past NAL header byte */
|
||||
size_t nal_payload_len = surface->slices_size -
|
||||
(size_t)((nal_payload) - (unsigned char *)surface->source_data);
|
||||
int sh_rc = h264_parse_slice_header(nal_payload, nal_payload_len,
|
||||
&sh_ctx, &sh);
|
||||
if (sh_rc == 0) {
|
||||
decode->idr_pic_id = sh.idr_pic_id;
|
||||
decode->pic_order_cnt_lsb = sh.pic_order_cnt_lsb;
|
||||
decode->delta_pic_order_cnt_bottom = sh.delta_pic_order_cnt_bottom;
|
||||
decode->delta_pic_order_cnt0 = sh.delta_pic_order_cnt0;
|
||||
decode->delta_pic_order_cnt1 = sh.delta_pic_order_cnt1;
|
||||
decode->pic_order_cnt_bit_size = sh.pic_order_cnt_bit_size;
|
||||
decode->dec_ref_pic_marking_bit_size = sh.dec_ref_pic_marking_bit_size;
|
||||
request_log("slice_header parse: idr_pic_id=%u "
|
||||
"poc_lsb=%u poc_bits=%u refmark_bits=%u "
|
||||
"frame_num=%u slice_type=%u pps_id=%u\n",
|
||||
sh.idr_pic_id, sh.pic_order_cnt_lsb,
|
||||
sh.pic_order_cnt_bit_size,
|
||||
sh.dec_ref_pic_marking_bit_size,
|
||||
sh.frame_num, sh.slice_type,
|
||||
sh.pic_parameter_set_id);
|
||||
} else {
|
||||
request_log("slice_header parse FAILED rc=%d "
|
||||
"(payload_len=%zu) — DECODE_PARAMS bit_size "
|
||||
"fields left zero, hantro will likely produce zeros\n",
|
||||
sh_rc, nal_payload_len);
|
||||
}
|
||||
}
|
||||
|
||||
h264_fill_dpb(driver_data, context, VAPicture, decode);
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user