Commit 09382db7f5 for ffmpeg
commit 09382db7f5791362dcaf0f4f26fad2bb18cb0dcb
Author: jiangjie <jiangjie@agora.io>
Date: Tue Sep 15 19:14:18 2026 +0800
avformat/movenc: fix global sidx fragment offsets
Use moof-relative addressing when global sidx shifts fragmented data,
unless absolute offsets are already avoided or no sidx is inserted,
and test it in api-movenc-test.
Fixes issue #24438.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Baptiste Coudurier <baptiste.coudurier@gmail.com>
diff --git a/doc/muxers.texi b/doc/muxers.texi
index ebca0971be..16a772818f 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -537,7 +537,9 @@ fragment at every frame
start a new fragment at each video keyframe
@item global_sidx
-write a global sidx index at the start of the file
+write a global sidx index at the start of the file. For fragmented output
+that would otherwise use absolute fragment offsets, this enables
+@samp{default_base_moof}.
@item isml
create a live smooth streaming feed (for pushing to a publishing point)
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index c3fa66d816..27e1dd7718 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -8414,6 +8414,17 @@ static int mov_init(AVFormatContext *s)
mov->flags |= FF_MOV_FLAG_FRAGMENT | FF_MOV_FLAG_EMPTY_MOOV |
FF_MOV_FLAG_DEFAULT_BASE_MOOF | FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS;
+ /* global_sidx is inserted in a second pass, shifting all fragments. Use
+ * moof-relative addressing so that tfhd offsets remain valid after that
+ * shift, unless absolute offsets are already avoided (omit_tfhd_offset,
+ * ISMV) or no sidx is inserted (hybrid_fragmented). */
+ if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX &&
+ mov->flags & FF_MOV_FLAG_FRAGMENT &&
+ !(mov->flags & (FF_MOV_FLAG_OMIT_TFHD_OFFSET |
+ FF_MOV_FLAG_HYBRID_FRAGMENTED)) &&
+ mov->mode != MODE_ISM)
+ mov->flags |= FF_MOV_FLAG_DEFAULT_BASE_MOOF;
+
if (mov->flags & FF_MOV_FLAG_EMPTY_MOOV && s->flags & AVFMT_FLAG_AUTO_BSF) {
av_log(s, AV_LOG_VERBOSE, "Empty MOOV enabled; disabling automatic bitstream filtering\n");
s->flags &= ~AVFMT_FLAG_AUTO_BSF;
diff --git a/tests/api/api-movenc-test.c b/tests/api/api-movenc-test.c
index 39494748fd..f4a78313f5 100644
--- a/tests/api/api-movenc-test.c
+++ b/tests/api/api-movenc-test.c
@@ -51,6 +51,7 @@ static uint8_t iobuf[32768];
static AVDictionary *opts;
static int write_file;
+static const char *out_path;
static const char *cur_name;
static FILE* out;
static int out_size;
@@ -181,16 +182,18 @@ static void init_fps(int bf, int audio_preroll, int fps, int id3)
{
AVStream *st;
int iobuf_size = force_iobuf_size ? force_iobuf_size : sizeof(iobuf);
- ctx = avformat_alloc_context();
- if (!ctx)
+ if (avformat_alloc_output_context2(&ctx, NULL, format, out_path) < 0)
exit(1);
- ctx->oformat = av_guess_format(format, NULL, NULL);
- if (!ctx->oformat)
- exit(1);
- ctx->pb = avio_alloc_context(iobuf, iobuf_size, 1, NULL, NULL, io_write, NULL);
- if (!ctx->pb)
- exit(1);
- ctx->pb->write_data_type = io_write_data_type;
+ if (out_path) {
+ // A real file, as global_sidx seeks and reopens the output
+ if (avio_open(&ctx->pb, out_path, AVIO_FLAG_WRITE) < 0)
+ exit(1);
+ } else {
+ ctx->pb = avio_alloc_context(iobuf, iobuf_size, 1, NULL, NULL, io_write, NULL);
+ if (!ctx->pb)
+ exit(1);
+ ctx->pb->write_data_type = io_write_data_type;
+ }
ctx->flags |= AVFMT_FLAG_BITEXACT;
st = avformat_new_stream(ctx, NULL);
@@ -373,16 +376,44 @@ static void signal_init_ts(void)
static void finish(void)
{
- av_write_trailer(ctx);
- avio_context_free(&ctx->pb);
+ check(av_write_trailer(ctx) >= 0, "Failed to write trailer");
+ if (out_path)
+ avio_closep(&ctx->pb);
+ else
+ avio_context_free(&ctx->pb);
avformat_free_context(ctx);
ctx = NULL;
}
+// Inserting a global sidx shifts all fragments; check that every packet
+// still reads back the payload mux_frames() wrote, its pts.
+static void test_global_sidx(const char *movflags)
+{
+ AVFormatContext *ic = NULL;
+ int ret, count = 0, wrong = 0;
+
+ av_dict_set(&opts, "movflags", movflags, 0);
+ av_dict_set(&opts, "use_editlist", "0", 0);
+ init(0, 0);
+ mux_gops(2);
+ finish();
+
+ ret = avformat_open_input(&ic, out_path, NULL, NULL);
+ while (ret >= 0 && (ret = av_read_frame(ic, pkt)) >= 0) {
+ wrong += pkt->size != 8 || AV_RB32(pkt->data + 4) != pkt->pts;
+ count++;
+ av_packet_unref(pkt);
+ }
+ printf("%s: %d packets, %d wrong, eof=%d\n",
+ movflags, count, wrong, ret == AVERROR_EOF);
+ avformat_close_input(&ic);
+}
+
static void help(void)
{
- printf("movenc-test [-w]\n"
- "-w write output into files\n");
+ printf("movenc-test [-w] [-s file]\n"
+ "-w write output into files\n"
+ "-s file check global_sidx output written to file\n");
}
int main(int argc, char **argv)
@@ -394,10 +425,13 @@ int main(int argc, char **argv)
int prev_pos;
for (;;) {
- c = getopt(argc, argv, "wh");
+ c = getopt(argc, argv, "ws:h");
if (c == -1)
break;
switch (c) {
+ case 's':
+ out_path = optarg;
+ break;
case 'w':
write_file = 1;
break;
@@ -417,6 +451,12 @@ int main(int argc, char **argv)
return 1;
}
+ if (out_path) {
+ test_global_sidx("frag_keyframe+empty_moov+global_sidx");
+ test_global_sidx("frag_keyframe+delay_moov+global_sidx");
+ goto end;
+ }
+
// Write a fragmented file with an initial moov and no samples.
init_out("non-empty-moov-no-mdat");
av_dict_set(&opts, "movflags", "+frag_keyframe", 0);
@@ -843,6 +883,7 @@ int main(int argc, char **argv)
finish();
close_out();
+end:
av_free(md5);
av_packet_free(&pkt);
diff --git a/tests/fate/api.mak b/tests/fate/api.mak
index 1659c7979c..1f3c1b43c7 100644
--- a/tests/fate/api.mak
+++ b/tests/fate/api.mak
@@ -54,6 +54,10 @@ FATE_API_LIBAVFORMAT-$(call ALLYES, MP4_MUXER ISMV_MUXER) += fate-api-movenc
fate-api-movenc: $(APITESTSDIR)/api-movenc-test$(EXESUF)
fate-api-movenc: CMD = run $(APITESTSDIR)/api-movenc-test$(EXESUF)
+FATE_API_LIBAVFORMAT-$(call ALLYES, MP4_MUXER MOV_DEMUXER FILE_PROTOCOL) += fate-api-movenc-global-sidx
+fate-api-movenc-global-sidx: $(APITESTSDIR)/api-movenc-test$(EXESUF)
+fate-api-movenc-global-sidx: CMD = run $(APITESTSDIR)/api-movenc-test$(EXESUF) -s $(TARGET_PATH)/tests/data/fate/api-movenc-global-sidx.mp4
+
FATE_API-$(HAVE_THREADS) += fate-api-threadmessage
fate-api-threadmessage: $(APITESTSDIR)/api-threadmessage-test$(EXESUF)
fate-api-threadmessage: CMD = run $(APITESTSDIR)/api-threadmessage-test$(EXESUF) 3 10 30 50 2 20 40
diff --git a/tests/ref/fate/api-movenc-global-sidx b/tests/ref/fate/api-movenc-global-sidx
new file mode 100644
index 0000000000..5de4663a1b
--- /dev/null
+++ b/tests/ref/fate/api-movenc-global-sidx
@@ -0,0 +1,2 @@
+frag_keyframe+empty_moov+global_sidx: 147 packets, 0 wrong, eof=1
+frag_keyframe+delay_moov+global_sidx: 147 packets, 0 wrong, eof=1