h264: derive sps.level_idc from H.264 Annex A.3 MaxFS

Replaces patch 0013's hardcoded level_idc = 51 with a small lookup
that picks the smallest level whose MaxFS contains the encoded
frame size. Patch 0013's TODO is resolved by this change.

VAAPI does not expose level_idc on the decode side
(VAPictureParameterBufferH264 has no such field; only
VAEncSequenceParameterBufferH264 carries it). The H.264 SPS NAL is
parsed client-side by ffmpeg-vaapi and only slice data forwards in
VASliceDataBuffer, so a SPS-NAL byte parser is not viable from the
bitstream the libva-v4l2-request layer receives. We therefore
derive level_idc from picture dimensions, which VAAPI does provide
in VAPictureParameterBufferH264.picture_{width,height}_in_mbs_minus1.

Annex A.3 (Table A-1) MaxFS thresholds:
  Level 1.0:    99 MBs   ( 176×144  =  11×9   = 99 )
  Level 1.1:   396       ( 352×288  =  22×18  = 396 )
  Level 2.0:   396
  Level 2.1:   792       ( 352×576  /  720×288 )
  Level 2.2:  1620       ( 720×480 ≈ 1350; 720×576 = 1620 )
  Level 3.0:  1620
  Level 3.1:  3600       (1280×720 ≈ 3600 )
  Level 3.2:  5120
  Level 4.0:  8192       (1920×1088 = 8160 fits )
  Level 4.1:  8192
  Level 4.2:  8704
  Level 5.0: 22080
  Level 5.1: 36864       (3840×2176 = 32640 fits; 4K@8K-edge )
  Level 5.2: 36864
  Level 6.0: 139264      (8K )

V4L2 control encoding: level_idc = (level major × 10) + (level minor).
Level 4.1 → 41, Level 5.1 → 51, Level 6.0 → 60.

Picks for typical content:
  1080p (1920×1088 = 8160 MBs) → Level 4.1 (level_idc = 41)
  4K    (3840×2176 = 32640 MBs) → Level 5.1 (level_idc = 51)
  8K   (7680×4352 = 130560 MBs) → Level 6.0 (level_idc = 60)

The previous hardcode of 51 was over-allocating for 1080p; with
this patch hantro can pre-allocate based on the actual frame size.
For our ohm corpus (1080p) this drops the requested DPB / MV
buffer sizing from level-5.1 generosity to level-4.1 right-sized.

Without VAAPI exposing framerate we cannot also check MaxMBPS /
MaxBR / MaxCPB. The frame-size-based pick is acceptable in
practice: temporally-dense streams almost always also push
spatially-large frames, so MaxFS captures the dominant
resource-sizing signal.

Cross-reference: H.264 spec Annex A, Table A-1 ("Level limits").
ext-ctrls-codec-stateless.rst V4L2_CID_STATELESS_H264_SPS lists
level_idc as required-userspace-input, no kernel-derives annotation.

Signed-off-by: Markus Fritsche <fritsche.markus@gmail.com>
This commit is contained in:
2026-05-02 12:00:00 +00:00
parent b0a93e4683
commit 8594d74275
+57 -26
View File
@@ -638,6 +638,55 @@ static inline __u8 h264_profile_to_idc(VAProfile profile)
} }
} }
/*
* Derive sps.level_idc from the encoded frame size in macroblocks per
* H.264 Annex A.3 (Table A-1) MaxFS thresholds. Each level's MaxFS is
* the maximum encoded frame size in MBs the level supports; we pick
* the smallest level whose MaxFS contains the actual frame size.
*
* Level decoding for the V4L2 control: level_idc = level * 10
* Level 1.0 → 10, Level 4.1 → 41, Level 5.1 → 51, Level 6.0 → 60.
*
* VAAPI does not expose the bitstream's actual level_idc on the
* decode side (VAPictureParameterBufferH264 has no such field) — see
* va.h. The H.264 SPS NAL is parsed client-side by ffmpeg-vaapi /
* mpv and only slice data is forwarded in VASliceDataBuffer, so a
* SPS-NAL byte parser is not viable at this layer.
*
* Without framerate we cannot also check MaxMBPS / MaxBR / MaxCPB.
* That gap is acceptable in practice: consumers that push
* temporally-dense streams (high MBPS) almost always also push
* spatially-large frames (high MaxFS), so frame-size-based level
* selection over-allocates on the temporal axis but never
* under-allocates a level the consumer relies on for correct
* decode-resource sizing.
*
* Picks for typical content:
* 1080p (8160 MBs) → Level 4.1 (level_idc = 41)
* 4K (32400 MBs) → Level 5.1 (level_idc = 51)
* 8K (138240 MBs) → Level 6.0 (level_idc = 60)
*
* Replaces the hardcoded level_idc=51 from patch 0013.
*/
static inline __u8 h264_derive_level_idc(unsigned int width_in_mbs,
unsigned int height_in_mbs)
{
const unsigned int frame_size_mbs = width_in_mbs * height_in_mbs;
if (frame_size_mbs <= 99) return 10; /* Level 1.0 */
if (frame_size_mbs <= 396) return 11; /* Level 1.1 - 2.0 */
if (frame_size_mbs <= 792) return 21; /* Level 2.1 */
if (frame_size_mbs <= 1620) return 22; /* Level 2.2 - 3.0 */
if (frame_size_mbs <= 3600) return 31; /* Level 3.1 */
if (frame_size_mbs <= 5120) return 32; /* Level 3.2 */
if (frame_size_mbs <= 8192) return 41; /* Level 4.0 - 4.1 */
if (frame_size_mbs <= 8704) return 42; /* Level 4.2 */
if (frame_size_mbs <= 22080) return 50; /* Level 5.0 */
if (frame_size_mbs <= 36864) return 51; /* Level 5.1 - 5.2 */
if (frame_size_mbs <= 139264) return 60; /* Level 6.0 - 6.2 */
return 62; /* > Level 6 ceiling */
}
int h264_set_controls(struct request_data *driver_data, int h264_set_controls(struct request_data *driver_data,
struct object_context *context, struct object_context *context,
VAProfile profile, VAProfile profile,
@@ -705,33 +754,15 @@ int h264_set_controls(struct request_data *driver_data,
sps.profile_idc = h264_profile_to_idc(profile); sps.profile_idc = h264_profile_to_idc(profile);
/* /*
* VAAPI's decode-side VAPictureParameterBufferH264 does not carry * Derive level_idc from encoded frame size per H.264 Annex A.3.
* level_idc — see va.h, the field exists only in * VAAPI doesn't expose level_idc on the decode side (see
* VAEncSequenceParameterBufferH264 on the encode path. The H.264 * h264_derive_level_idc()'s docblock for the rationale); we pick
* SPS NAL is also not included in VASliceDataBuffer (ffmpeg-vaapi * the smallest level whose MaxFS contains the picture dimensions.
* parses it client-side and forwards only slice data), so a * Replaces patch 0013's intermediate hardcode of 51.
* SPS-NAL byte extractor is not viable from the bitstream we
* receive.
*
* Hantro and other stateless H.264 decoders use level_idc to
* pre-allocate decoder resources (DPB, motion-vector buffers); a
* zero-init level_idc=0 is invalid (lowest legal is 10 = Level
* 1.0) and causes hantro to silently skip the decode hardware
* dispatch.
*
* Hardcode level_idc = 51 (Level 5.1, max for 1080p/4K@30) as a
* known-incomplete intermediate. This INTENTIONALLY OVER-ALLOCATES
* decoder resources and is sufficient for any stream up to 4K@30.
* It is corpus-correct, not contract-correct.
*
* TODO: derive level_idc from (VAProfile, picture_width_in_mbs,
* picture_height_in_mbs) per H.264 Annex A.3 max-MB-per-second
* thresholds. That is a small lookup table but requires also
* mapping the consumer's framerate, which VAAPI doesn't provide
* directly. For now the over-allocation is the upstreamable
* compromise.
*/ */
sps.level_idc = 51; sps.level_idc = h264_derive_level_idc(
(unsigned int)surface->params.h264.picture.picture_width_in_mbs_minus1 + 1u,
(unsigned int)surface->params.h264.picture.picture_height_in_mbs_minus1 + 1u);
/* /*
* Build the per-request control list incrementally: * Build the per-request control list incrementally: