Commit 785290b537 for ffmpeg
commit 785290b537be774a05ff7de1df7c16a9bd1908e8
Author: Baptiste Coudurier <baptiste.coudurier@gmail.com>
Date: Fri Oct 16 12:08:09 2026 -0700
avcodec/h264_parser: count both fields of a field pair in complete frames
With complete frames, the parser stopped at the first slice and
reported a field picture as a single field even when the packet holds
both fields of the frame, as in MXF. This halved the avg_frame_rate
estimated by avformat_find_stream_info().
Look for the opposite-parity field with the same frame number and
account for both fields. Derive their display order from the field POCs
using a copy of the parser state, leaving it unknown when the order
cannot be established.
Advance directly to the next NAL boundary for AVC, and retain start-code
overlap after capped Annex B slice-header reads.
Extend the existing MXF probe test to check the corrected average frame
rate while excluding the sample's truncated final packet. Add a probe
regression test ensuring progressive-source fields retain unknown stream
field order rather than acquiring an interlaced label.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/libavcodec/h264_parser.c b/libavcodec/h264_parser.c
index 1bd0be38a5..87228fb63a 100644
--- a/libavcodec/h264_parser.c
+++ b/libavcodec/h264_parser.c
@@ -251,6 +251,54 @@ static int scan_mmco_reset(AVCodecParserContext *s, GetBitContext *gb,
return 0;
}
+/**
+ * Check whether a slice is the first slice of the field of opposite
+ * parity to the field picture already parsed, and derive its POC if possible.
+ */
+static int parse_second_field(const H264ParseContext *p, H2645NAL *nal,
+ int *second_poc)
+{
+ GetBitContext *gb = &nal->gb;
+ H264POCContext poc = p->poc;
+ int field_poc[2] = { INT_MAX, INT_MAX };
+ int picture_structure, pic_poc;
+ const SPS *sps;
+ unsigned int pps_id;
+
+ if (get_ue_golomb_long(gb)) // first_mb_in_slice
+ return 0;
+ get_ue_golomb_31(gb); // slice_type
+ pps_id = get_ue_golomb(gb);
+ if (pps_id >= MAX_PPS_COUNT || !p->ps.pps_list[pps_id])
+ return 0;
+ sps = p->ps.pps_list[pps_id]->sps;
+ if (sps->residual_color_transform_flag)
+ skip_bits(gb, 2); // colour_plane_id
+ poc.frame_num = get_bits(gb, sps->log2_max_frame_num);
+ if (poc.frame_num != p->poc.prev_frame_num ||
+ sps->frame_mbs_only_flag || !get_bits1(gb)) // field_pic_flag
+ return 0;
+ picture_structure = PICT_TOP_FIELD + get_bits1(gb); // bottom_field_flag
+ if (picture_structure == p->picture_structure || get_bits_left(gb) < 0)
+ return 0;
+
+ /* Derive display order without changing the first field's parser state. */
+ if (nal->type == H264_NAL_IDR_SLICE) {
+ poc.prev_frame_num = poc.prev_frame_num_offset = 0;
+ poc.prev_poc_msb = poc.prev_poc_lsb = 0;
+ get_ue_golomb_long(gb); // idr_pic_id
+ }
+ if (sps->poc_type == 0)
+ poc.poc_lsb = get_bits(gb, sps->log2_max_poc_lsb);
+ if (sps->poc_type == 1 && !sps->delta_pic_order_always_zero_flag)
+ poc.delta_poc[0] = get_se_golomb(gb);
+ if (sps == p->ps.sps && get_bits_left(gb) >= 0 &&
+ ff_h264_init_poc(field_poc, &pic_poc, sps, &poc,
+ picture_structure, nal->ref_idc) >= 0)
+ *second_poc = pic_poc;
+ return 1;
+}
+
/**
* Parse NAL units of found picture and decode some basic information.
*
@@ -270,6 +318,7 @@ static inline int parse_nal_units(AVCodecParserContext *s,
unsigned int pps_id;
unsigned int slice_type;
int state = -1, got_reset = 0;
+ int first_field = 0;
int q264 = buf_size >=4 && !memcmp("Q264", buf, 4);
int field_poc[2];
int ret;
@@ -332,7 +381,14 @@ static inline int parse_nal_units(AVCodecParserContext *s,
if (consumed < 0)
break;
- buf_index += consumed;
+ /* A capped slice-header read may end inside the next start code. */
+ if (consumed == src_length && src_length < next_avc - buf_index) {
+ if (p->is_avc)
+ buf_index = next_avc;
+ else
+ buf_index += consumed - 3;
+ } else
+ buf_index += consumed;
ret = init_get_bits8(&nal.gb, nal.data, nal.size);
if (ret < 0)
@@ -341,6 +397,37 @@ static inline int parse_nal_units(AVCodecParserContext *s,
nal.ref_idc = get_bits(&nal.gb, 2);
nal.type = get_bits(&nal.gb, 5);
+ if (first_field) {
+ int second_poc = INT_MAX;
+
+ /* The packet also holds the second field of the pair. */
+ if ((nal.type == H264_NAL_SLICE ||
+ nal.type == H264_NAL_IDR_SLICE ||
+ nal.type == H264_NAL_DPA) &&
+ parse_second_field(p, &nal, &second_poc)) {
+ s->repeat_pict++;
+ s->field_order = AV_FIELD_UNKNOWN;
+ if (!got_reset && nal.ref_idc &&
+ !(p->ps.sps->pic_struct_present_flag &&
+ p->sei.picture_timing.present &&
+ (p->sei.picture_timing.ct_type & 3) == 1) &&
+ second_poc != INT_MAX &&
+ second_poc != s->output_picture_number) {
+ if (p->picture_structure == PICT_TOP_FIELD)
+ s->field_order = s->output_picture_number < second_poc ?
+ AV_FIELD_TT : AV_FIELD_TB;
+ else
+ s->field_order = s->output_picture_number < second_poc ?
+ AV_FIELD_BB : AV_FIELD_BT;
+ }
+ s->picture_structure = AV_PICTURE_STRUCTURE_FRAME;
+ break;
+ }
+ /* Keep the parameter sets the second field may refer to. */
+ if (nal.type != H264_NAL_SPS && nal.type != H264_NAL_PPS)
+ continue;
+ }
+
switch (nal.type) {
case H264_NAL_SPS:
ff_h264_decode_seq_parameter_set(&nal.gb, avctx, &p->ps, 0);
@@ -577,11 +664,19 @@ static inline int parse_nal_units(AVCodecParserContext *s,
sps->num_units_in_tick * 2, den, 1 << 30);
}
+ /* A complete frame starting with a field picture may hold
+ * both fields of the pair, look for the second one. */
+ if ((s->flags & PARSER_FLAG_COMPLETE_FRAMES) &&
+ p->picture_structure != PICT_FRAME) {
+ first_field = 1;
+ break;
+ }
+
av_freep(&rbsp.rbsp_buffer);
return 0; /* no need to evaluate the rest */
}
}
- if (q264) {
+ if (first_field || q264) {
av_freep(&rbsp.rbsp_buffer);
return 0;
}
diff --git a/tests/fate/h264.mak b/tests/fate/h264.mak
index 1ee70f216d..96c6292c28 100644
--- a/tests/fate/h264.mak
+++ b/tests/fate/h264.mak
@@ -253,6 +253,7 @@ FATE_H264-$(call FRAMECRC, MPEGTS, H264, H264_PARSER MP3_DECODER SCALE_FILTER AR
FATE_H264_FFPROBE-$(call DEMDEC, MATROSKA, H264) += fate-h264-dts_5frames
FATE_H264_FFPROBE-$(call DEMDEC, MATROSKA, H264) += fate-h264-skip-pred-pts
FATE_H264_FFPROBE-$(call DEMDEC, MOV, H264) += fate-h264-late-sei
+FATE_H264_FFPROBE-$(call DEMDEC, MOV, H264, H264_PARSER) += fate-h264-twofields-packet-probe
FATE_H264_FFPROBE-$(call PARSERDEMDEC, H264, H264, H264) += fate-h264-afd
FATE_H264_FFPROBE-$(call PARSERDEMDEC, H264, H264, H264) += fate-h264-timecode-probe
FATE_H264_FFPROBE-$(call PARSERDEMDEC, H264, H264, H264) += fate-h264-skip-pred \
@@ -523,3 +524,8 @@ fate-h264-bsf-redundant-pps-side-data2: CMD = transcode mov $(TARGET_SAMPLES)/h2
fate-h264-encparams: CMD = venc_data $(TARGET_SAMPLES)/h264-conformance/FRext/FRExt_MMCO4_Sony_B.264 0 1
FATE_SAMPLES_DUMP_DATA += fate-h264-encparams
+
+# Progressive-source fields must not acquire an interlaced stream label.
+fate-h264-twofields-packet-probe: CMD = run ffprobe$(PROGSSUF)$(EXESUF) -bitexact -v 0 -select_streams v \
+ -show_entries stream=field_order,avg_frame_rate \
+ $(TARGET_SAMPLES)/h264/twofields_packet.mp4
diff --git a/tests/fate/mxf.mak b/tests/fate/mxf.mak
index 10c310dd34..e64da46d77 100644
--- a/tests/fate/mxf.mak
+++ b/tests/fate/mxf.mak
@@ -24,9 +24,10 @@ fate-mxf-probe-d10: SRC = $(TARGET_SAMPLES)/mxf/Sony-00001.mxf
fate-mxf-probe-d10: CMD = run $(PROBE_FORMAT_STREAMS_COMMAND) -i "$(SRC)"
# Long GOP AVC must retain its in-band parameter sets, not AVC-Intra defaults.
+# Count both fields per packet; discard the sample's truncated final packet.
FATE_MXF_PROBE-$(call DEMDEC, MXF, H264, H264_PARSER EXTRACT_EXTRADATA_BSF) += fate-mxf-probe-h264-extradata
fate-mxf-probe-h264-extradata: SRC = $(TARGET_SAMPLES)/h264/SonyXAVC_LongGOP_green_pixelation_early_Frames.MXF
-fate-mxf-probe-h264-extradata: CMD = run ffprobe$(PROGSSUF)$(EXESUF) -bitexact -v 0 -select_streams v -show_entries stream=extradata,extradata_size,extradata_hash -show_data_hash md5 -i "$(SRC)"
+fate-mxf-probe-h264-extradata: CMD = run ffprobe$(PROGSSUF)$(EXESUF) -bitexact -v 0 -fflags +discardcorrupt -select_streams v -show_entries stream=avg_frame_rate,extradata,extradata_size,extradata_hash -show_data_hash md5 -i "$(SRC)"
FATE_MXF_PROBE-$(call DEMDEC, MXF, DNXHD) += fate-mxf-probe-dnxhd
fate-mxf-probe-dnxhd: SRC = $(TARGET_SAMPLES)/mxf/multiple_components.mxf
diff --git a/tests/ref/fate/h264-twofields-packet-probe b/tests/ref/fate/h264-twofields-packet-probe
new file mode 100644
index 0000000000..f215a7e0d1
--- /dev/null
+++ b/tests/ref/fate/h264-twofields-packet-probe
@@ -0,0 +1,4 @@
+[STREAM]
+field_order=unknown
+avg_frame_rate=30000/1001
+[/STREAM]
diff --git a/tests/ref/fate/mxf-probe-h264-extradata b/tests/ref/fate/mxf-probe-h264-extradata
index 482bca0ce5..5de7bb31e5 100644
--- a/tests/ref/fate/mxf-probe-h264-extradata
+++ b/tests/ref/fate/mxf-probe-h264-extradata
@@ -1,4 +1,5 @@
[STREAM]
+avg_frame_rate=25/1
extradata_size=254
extradata_hash=MD5:0a4fe18be973294d0433253d1e8548bc
[/STREAM]