PR #25311 opened by Marti2203
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25311
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25311.patch

VP9 profile 1 allows 4:4:0 chroma subsampling (yuv440p), but the vpcC record of 
the VP9 ISO-BMFF binding has no `chromaSubsampling` value for it (0 and 1 are 
4:2:0, 2 is 4:2:2, 3 is 4:4:4, the rest reserved). `ff_isom_write_vpcc()` 
therefore fails with "Unsupported pixel format" before writing anything. movenc 
and flvenc ignore that error: movenc closes an empty 8-byte vpcC box, flvenc 
writes a sequence start tag with an empty record, and ffmpeg exits 0. FFmpeg's 
own mov demuxer then refuses the file:

```
ffmpeg -i vp91-2-04-yuv440.webm -c copy out.mp4
[mp4 @ ...] Unsupported pixel format (31)
(exit status 0)
ffprobe out.mp4
[mov,mp4,m4a,3gp,3g2,mj2 @ ...] Empty VP Codec Configuration box
[mov,mp4,m4a,3gp,3g2,mj2 @ ...] error reading header
out.mp4: Invalid data found when processing input
```

The input is `vp9-test-vectors/vp91-2-04-yuv440.webm` from the FATE suite; a 
generated IVF with 4:4:0 keyframe headers behaves the same.

Since 4:4:0 cannot be signalled, the series makes both muxers return the error 
instead of writing a broken file. The first patch does it in 
`mov_write_vpcc_tag()` and its caller, the second makes 
`flv_write_codec_header()` return an error code and checks it in 
`flv_write_header()` and `flv_write_packet()`. After it:

```
[mp4 @ ...] Unsupported pixel format (31)
[out#0/mp4 @ ...] Error writing trailer: Invalid data found when processing 
input
(exit status 183)
[flv @ ...] Unsupported pixel format (31)
[out#0/flv @ ...] Could not write header (incorrect codec parameters ?): 
Invalid data found when processing input
(exit status 183)
```

The third patch adds fate-mov-mp4-vp9-yuv440 and fate-enhanced-flv-vp9-yuv440, 
which mux that existing sample and expect failure. They fail without the fix 
and pass with it.

Testing: the two new tests; fate-enhanced-flv-vp9, 
fate-enhanced-flv-multitrack, fate-lavf-fate-{h264,hevc,av1}.mp4, 
fate-lavf-fate-hevc.flv, all sample-free mov/mp4/flv/ismv tests and fate-source 
pass. Remuxing the 4:2:0, 4:2:2 and 4:4:4 VP9 test vectors to mp4 and flv gives 
byte-identical output before and after.

Caveat: for non-fragmented mp4 the moov is written in the trailer, so the error 
shows up only there and the partial output file is left behind (as for other 
trailer errors). With `-movflags frag_keyframe+empty_moov` it fails in the 
header. If an early check is preferred, the pixel format could also be checked 
in `mov_init()`; I kept the change to propagating the existing error.

This issue was found by an LLM-Enhanced Static Analysis Engine and verified 
manually.

🤖 Generated with [Claude Code](https://claude.com/claude-code)



From 96a1f530c29726db805afb3b5cb76334732f1ac1 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 19:04:57 +0800
Subject: [PATCH 1/3] avformat/movenc: fail if the vpcC box cannot be written

ff_isom_write_vpcc() fails without writing anything when the pixel
format cannot be described in the vpcC box, e.g. for VP9 4:4:0
(yuv440p, allowed in profile 1), for which the VP9 ISO-BMFF binding
has no chromaSubsampling value. mov_write_vpcc_tag() ignored the error
and closed an empty 8-byte vpcC box, so ffmpeg exited successfully and
wrote a file that the mov demuxer then refuses to open ("error reading
header").

Return the error instead, so that muxing fails rather than producing a
broken file.

Signed-off-by: Martin Mirchev <[email protected]>
---
 libavformat/movenc.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 2c349ef663b..1d59ecb1ae3 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -1702,11 +1702,14 @@ static int mov_write_av3c_tag(AVIOContext *pb, MOVTrack 
*track)
 static int mov_write_vpcc_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack 
*track)
 {
     int64_t pos = avio_tell(pb);
+    int ret;
 
     avio_wb32(pb, 0);
     ffio_wfourcc(pb, "vpcC");
-    ff_isom_write_vpcc(s, pb, track->extradata[track->last_stsd_index],
-                       track->extradata_size[track->last_stsd_index], 
track->par);
+    ret = ff_isom_write_vpcc(s, pb, track->extradata[track->last_stsd_index],
+                             track->extradata_size[track->last_stsd_index], 
track->par);
+    if (ret < 0)
+        return ret;
     return update_size(pb, pos);
 }
 
@@ -2983,7 +2986,9 @@ static int mov_write_video_tag(AVFormatContext *s, 
AVIOContext *pb, MOVMuxContex
     } else if (track->par->codec_id ==AV_CODEC_ID_APV) {
         mov_write_apvc_tag(mov->fc, pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_VP9) {
-        mov_write_vpcc_tag(mov->fc, pb, track);
+        ret = mov_write_vpcc_tag(mov->fc, pb, track);
+        if (ret < 0)
+            return ret;
     } else if (track->par->codec_id == AV_CODEC_ID_AV1) {
         mov_write_av1c_tag(pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_VC1 && 
track->extradata_size[track->last_stsd_index] > 0)
-- 
2.54.0


From e899fa5e19024b4bbf5b9db0b43e84ac7a1a9a3a Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 19:04:57 +0800
Subject: [PATCH 2/3] avformat/flvenc: fail if the VP9 sequence header cannot
 be written

As in movenc, ff_isom_write_vpcc() fails without writing anything when
the pixel format cannot be described in a vpcC record, e.g. for VP9
4:4:0. flv_write_codec_header() ignored the error and wrote a
PacketTypeSequenceStart tag with an empty configuration record.

Make flv_write_codec_header() return an error code and propagate it.

Signed-off-by: Martin Mirchev <[email protected]>
---
 libavformat/flvenc.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
index 2febb96d055..1f2a4deb2f1 100644
--- a/libavformat/flvenc.c
+++ b/libavformat/flvenc.c
@@ -804,7 +804,7 @@ static void flv_write_multichannel_header(AVFormatContext* 
s, AVCodecParameters*
     avio_wb32(pb, data_size + 5 + 11); // previous tag size
 }
 
-static void flv_write_codec_header(AVFormatContext* s, AVCodecParameters* par, 
int64_t ts, int stream_index) {
+static int flv_write_codec_header(AVFormatContext* s, AVCodecParameters* par, 
int64_t ts, int stream_index) {
     int64_t data_size;
     AVIOContext *pb = s->pb;
     FLVContext *flv = s->priv_data;
@@ -891,9 +891,11 @@ static void flv_write_codec_header(AVFormatContext* s, 
AVCodecParameters* par, i
                 ff_isom_write_vvcc(pb, par->extradata, par->extradata_size, 1);
             else if (par->codec_id == AV_CODEC_ID_AV1)
                 ff_isom_write_av1c(pb, par->extradata, par->extradata_size, 1);
-            else if (par->codec_id == AV_CODEC_ID_VP9)
-                ff_isom_write_vpcc(s, pb, par->extradata, par->extradata_size, 
par);
-            else if (par->codec_id == AV_CODEC_ID_H264)
+            else if (par->codec_id == AV_CODEC_ID_VP9) {
+                int ret = ff_isom_write_vpcc(s, pb, par->extradata, 
par->extradata_size, par);
+                if (ret < 0)
+                    return ret;
+            } else if (par->codec_id == AV_CODEC_ID_H264)
                 ff_isom_write_avcc(pb, par->extradata, par->extradata_size);
             else if (par->codec_id == AV_CODEC_ID_MPEG4)
                 avio_write(pb, par->extradata, par->extradata_size);
@@ -911,6 +913,8 @@ static void flv_write_codec_header(AVFormatContext* s, 
AVCodecParameters* par, i
         (av_channel_layout_compare(&par->ch_layout, 
&(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO) == 1 &&
          av_channel_layout_compare(&par->ch_layout, 
&(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO) == 1)))
         flv_write_multichannel_header(s, par, ts, stream_index);
+
+    return 0;
 }
 
 static int flv_append_keyframe_info(AVFormatContext *s, FLVContext *flv, 
double ts, int64_t pos)
@@ -1110,7 +1114,9 @@ static int flv_write_header(AVFormatContext *s)
     }
 
     for (i = 0; i < s->nb_streams; i++) {
-        flv_write_codec_header(s, s->streams[i]->codecpar, 0, i);
+        int ret = flv_write_codec_header(s, s->streams[i]->codecpar, 0, i);
+        if (ret < 0)
+            return ret;
     }
 
     flv->datastart_offset = avio_tell(pb);
@@ -1259,7 +1265,9 @@ static int flv_write_packet(AVFormatContext *s, AVPacket 
*pkt)
             if (ret < 0)
                 return ret;
             memcpy(par->extradata, side, side_size);
-            flv_write_codec_header(s, par, pkt->dts, pkt->stream_index);
+            ret = flv_write_codec_header(s, par, pkt->dts, pkt->stream_index);
+            if (ret < 0)
+                return ret;
         }
         flv_write_metadata_packet(s, par, pkt->dts, pkt->stream_index);
     }
-- 
2.54.0


From 639c86604cc051bd7baa26799daaa84dddbd1129 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 19:05:55 +0800
Subject: [PATCH 3/3] fate: test that VP9 4:4:0 is rejected by the mp4 and flv
 muxers

Uses an existing sample from the VP9 test vectors; vpcC has no
chromaSubsampling value for 4:4:0, so muxing it must fail.

Signed-off-by: Martin Mirchev <[email protected]>
---
 tests/fate/flvenc.mak | 5 +++++
 tests/fate/mov.mak    | 5 +++++
 2 files changed, 10 insertions(+)

diff --git a/tests/fate/flvenc.mak b/tests/fate/flvenc.mak
index 1cc8e1f1603..d1fd87f911c 100644
--- a/tests/fate/flvenc.mak
+++ b/tests/fate/flvenc.mak
@@ -13,6 +13,11 @@ FATE_ENHANCED_FLVENC_FFMPEG-$(call REMUX, FLV IVF, 
FLV_DEMUXER VP9_PARSER) += fa
 fate-enhanced-flv-vp9: CMD = transcode ivf 
$(TARGET_SAMPLES)/vp9-test-vectors/vp90-2-05-resize.ivf\
                flv "-c copy" "-c copy"
 
+# VP9 4:4:0 cannot be signalled in vpcC, so muxing must fail instead of 
writing a broken file
+FATE_ENHANCED_FLVENC_FFMPEG-$(call ALLYES, MATROSKA_DEMUXER VP9_PARSER 
FLV_MUXER FILE_PROTOCOL) += fate-enhanced-flv-vp9-yuv440
+fate-enhanced-flv-vp9-yuv440: CMP = null
+fate-enhanced-flv-vp9-yuv440: CMD = 
out=tests/data/fate/enhanced-flv-vp9-yuv440.flv; ffmpeg -i 
$(TARGET_SAMPLES)/vp9-test-vectors/vp91-2-04-yuv440.webm -c copy -f flv -y 
$$out; ret=$$?; rm -f $$out; test $$ret -ne 0
+
 FATE_ENHANCED_FLVENC_FFMPEG-$(call REMUX, FLV IVF, FLV_DEMUXER AV1_DECODER 
AV1_PARSER EXTRACT_EXTRADATA_BSF) += fate-enhanced-flv-av1
 fate-enhanced-flv-av1: CMD = stream_remux ivf 
$(TARGET_SAMPLES)/av1/seq_hdr_op_param_info.ivf "-c:v av1" \
                flv "-c copy" "-c:v av1" "-c copy"
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 3550e1d7b81..02c433980ef 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -341,6 +341,11 @@ fate-mov-mp4-mpegh-5_1_4: CMD = stream_demux mp4 
$(TARGET_SAMPLES)/mov/mpegh_lay
 FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call REMUX, MP4 MOV) += 
fate-mov-mp4-mpegh-5_1_4-copy
 fate-mov-mp4-mpegh-5_1_4-copy: CMD = stream_remux mp4 
$(TARGET_SAMPLES)/mov/mpegh_layout16.mp4 "" mp4 "" "" "-c:a copy" 
"-show_entries stream=profile,level,codec_tag_string"
 
+# VP9 4:4:0 cannot be signalled in vpcC, so muxing must fail instead of 
writing a broken file
+FATE_MOV_FFMPEG_SAMPLES-$(call ALLYES, MATROSKA_DEMUXER VP9_PARSER MP4_MUXER 
FILE_PROTOCOL) += fate-mov-mp4-vp9-yuv440
+fate-mov-mp4-vp9-yuv440: CMP = null
+fate-mov-mp4-vp9-yuv440: CMD = out=tests/data/fate/mov-mp4-vp9-yuv440.mp4; 
ffmpeg -i $(TARGET_SAMPLES)/vp9-test-vectors/vp91-2-04-yuv440.webm -c copy -f 
mp4 -y $$out; ret=$$?; rm -f $$out; test $$ret -ne 0
+
 FATE_SAMPLES_FFMPEG_FFPROBE += $(FATE_MOV_FFMPEG_FFPROBE_SAMPLES-yes)
 FATE_SAMPLES_FFMPEG += $(FATE_MOV_FFMPEG_SAMPLES-yes)
 
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to