PR #25619 opened by Kilian (lusoris) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25619 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25619.patch
DASH output write and close failures are reported as success. On a full disk, `ffmpeg -f dash` exits 0 with missing or truncated media, manifests or playlists. This propagates those errors and keeps `ignore_io_errors` working. - **`avformat/hlsplaylist`:** `ff_hls_write_file_entry` now returns `out->error`. `hlsenc.c` is unchanged: its existing warning for this return value now fires, and its close and retry behaviour is the same as before. - **`avformat/dashenc`:** `dashenc_io_close` returns close and HTTP shutdown errors, and the manifest, playlist, init and media segment writers and their callers check them. A failed media playlist rename, whose result was discarded, is now returned too. The rename is still attempted, and with `ignore_io_errors=1` every one of these failures still exits 0. **Tests** - With lavfi input and `/dev/full` as each output target, base exited 0 for media playlist, MPD, master playlist, init and segment files. These now exit 228 (ENOSPC) with `ignore_io_errors=0` and 0 with `ignore_io_errors=1`. - With a directory at `media_0.m3u8`, base exited 0. It now exits 235 with `ignore_io_errors=0` and 0 with `ignore_io_errors=1`, and the rename is attempted in both cases. - The reproducer has 40 cases, of which 16 fail on base and none now; streaming and low-latency modes have 28 each, of which 12 fail on base and none now. - A full build with the sample suite passes all 40 HLS and DASH FATE targets it selects. That build has no libxml2, so the DASH demuxer tests ran in a separate minimal libxml2 build: `fate-dash-big-init`, `fate-dash-seek-zero` and `fate-dash-seek-reference` pass. This branch and PR 25475 both change `libavformat/dashenc.c` and conflict textually. Whichever lands second needs a small rebase, which I will do on this branch if this one is second. This addresses a pre-existing limitation listed in the automated review of PR 25475; it does not change that PR. >From f56790e203f21c50d75ad2945b015456be548067 Mon Sep 17 00:00:00 2001 From: lusoris <[email protected]> Date: Fri, 30 Oct 2026 15:21:26 +0100 Subject: [PATCH 1/2] avformat/hlsplaylist: return playlist write errors The entry writer returned success even when AVIO had recorded a write error. Return out->error so callers can detect the failure. In hls_window, the existing warning "ff_hls_write_file_entry get error" now fires. The fail label still overwrites ret with hlsenc_io_close's result, as on master; close, retry and trailer behavior are unchanged. An ENOSPC callback now returns ENOSPC instead of zero. A /dev/full VOD playlist exceeding the AVIO buffer logs the warning and still exits zero with either ignore_io_errors value. HLS close stubs and the four DASH and nine sample-free HLS FATE tests pass. Signed-off-by: lusoris <[email protected]> --- libavformat/hlsplaylist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c index 774bc5ac84b..55cfa294953 100644 --- a/libavformat/hlsplaylist.c +++ b/libavformat/hlsplaylist.c @@ -193,7 +193,7 @@ int ff_hls_write_file_entry(AVIOContext *out, int insert_discont, avio_printf(out, "%s", baseurl); avio_printf(out, "%s\n", filename); - return 0; + return out->error; } void ff_hls_write_end_list(AVIOContext *out) -- 2.54.0 >From 24e321097c206cbf6e5d67bd848d5477e117efc4 Mon Sep 17 00:00:00 2001 From: lusoris <[email protected]> Date: Fri, 30 Oct 2026 15:22:32 +0100 Subject: [PATCH 2/2] avformat/dashenc: propagate output close errors dashenc_io_close discarded close and HTTP shutdown results, allowing failed output writes to appear successful. Return these errors and buffered AVIO errors after shutdown, then check them in the manifest, playlist, initialization and media segment writers and their callers. Keep ignore_io_errors effective, including media playlist rename failures whose result was previously discarded. Always attempt that rename; return its error only when ignore_io_errors is disabled. Other rename sites retain their existing error handling. With lavfi input and /dev/full targets, the base missed output errors. The fixed executable fails with ignore_io_errors=0 and exits zero with ignore_io_errors=1. A directory at media_0.m3u8 gives exit 235 or zero, respectively, with rename attempted in both cases. Standard, streaming and low-latency HLS checks pass, as do offline close/flush/shutdown stubs and the four DASH and nine sample-free HLS FATE tests. Signed-off-by: lusoris <[email protected]> --- libavformat/dashenc.c | 96 +++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 36f39bd083a..861d52cd375 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -225,23 +225,30 @@ static int dashenc_io_open(AVFormatContext *s, AVIOContext **pb, char *filename, return err; } -static void dashenc_io_close(AVFormatContext *s, AVIOContext **pb, char *filename) { +static int dashenc_io_close(AVFormatContext *s, AVIOContext **pb, char *filename) { DASHContext *c = s->priv_data; int http_base_proto = filename ? ff_is_http_proto(filename) : 0; + int ret = 0; if (!*pb) - return; + return 0; if (!http_base_proto || !c->http_persistent) { - ff_format_io_close(s, pb); + ret = ff_format_io_close(s, pb); #if CONFIG_HTTP_PROTOCOL } else { URLContext *http_url_context = ffio_geturlcontext(*pb); av_assert0(http_url_context); avio_flush(*pb); - ffurl_shutdown(http_url_context, AVIO_FLAG_WRITE); + ret = ffurl_shutdown(http_url_context, AVIO_FLAG_WRITE); + if (ret >= 0) + ret = (*pb)->error; #endif } + if (ret < 0) + av_log(s, c->ignore_io_errors ? AV_LOG_WARNING : AV_LOG_ERROR, + "Unable to write %s: %s\n", filename, av_err2str(ret)); + return c->ignore_io_errors ? 0 : ret; } static const char *get_format_str(SegmentType segment_type) @@ -375,16 +382,16 @@ static void get_start_index_number(OutputStream *os, DASHContext *c, } } -static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, - int representation_id, int final, - char *prefetch_url) { +static int write_hls_media_playlist(OutputStream *os, AVFormatContext *s, + int representation_id, int final, + char *prefetch_url) { DASHContext *c = s->priv_data; int timescale = os->ctx->streams[0]->time_base.den; char temp_filename_hls[1024]; char filename_hls[1024]; AVDictionary *http_opts = NULL; int target_duration = 0; - int ret = 0; + int ret = 0, err; const char *proto = avio_find_protocol_name(c->dirname); int use_rename = proto && !strcmp(proto, "file"); int i, start_index, start_number; @@ -394,7 +401,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, if (!c->hls_playlist || start_index >= os->nb_segments || os->segment_type != SEGMENT_TYPE_MP4) - return; + return 0; get_hls_playlist_name(filename_hls, sizeof(filename_hls), c->dirname, representation_id); @@ -404,10 +411,8 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, set_http_options(&http_opts, c); ret = dashenc_io_open(s, &c->m3u8_out, temp_filename_hls, &http_opts); av_dict_free(&http_opts); - if (ret < 0) { - handle_io_open_error(s, ret, temp_filename_hls); - return; - } + if (ret < 0) + return handle_io_open_error(s, ret, temp_filename_hls); for (i = start_index; i < os->nb_segments; i++) { Segment *seg = os->segments[i]; double duration = (double) seg->duration / timescale; @@ -437,9 +442,8 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, seg->range_length, seg->start_pos, NULL, c->single_file ? os->initfile : seg->file, &prog_date_time, 0); - if (ret < 0) { - av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n"); - } + if (ret < 0) + goto fail; } if (prefetch_url) @@ -448,10 +452,16 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, if (final) ff_hls_write_end_list(c->m3u8_out); - dashenc_io_close(s, &c->m3u8_out, temp_filename_hls); +fail: + err = dashenc_io_close(s, &c->m3u8_out, temp_filename_hls); + if (ret >= 0) + ret = err; + if (ret < 0) + return c->ignore_io_errors ? 0 : ret; if (use_rename) - ff_rename(temp_filename_hls, filename_hls, os->ctx); + ret = ff_rename(temp_filename_hls, filename_hls, os->ctx); + return c->ignore_io_errors ? 0 : ret; } static int flush_init_segment(AVFormatContext *s, OutputStream *os) @@ -467,7 +477,7 @@ static int flush_init_segment(AVFormatContext *s, OutputStream *os) if (!c->single_file) { char filename[1024]; snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile); - dashenc_io_close(s, &os->out, filename); + return dashenc_io_close(s, &os->out, filename); } return 0; } @@ -514,8 +524,8 @@ static void dash_free(AVFormatContext *s) ff_format_io_close(s, &c->http_delete); } -static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatContext *s, - int representation_id, int final) +static int output_segment_list(OutputStream *os, AVIOContext *out, AVFormatContext *s, + int representation_id, int final) { DASHContext *c = s->priv_data; int i, start_index, start_number; @@ -583,10 +593,9 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatCont } avio_printf(out, "\t\t\t\t</SegmentList>\n"); } - if (!c->lhls || final) { - write_hls_media_playlist(os, s, representation_id, final, NULL); - } - + if (!c->lhls || final) + return write_hls_media_playlist(os, s, representation_id, final, NULL); + return 0; } static char *xmlescape(const char *str) { @@ -637,7 +646,7 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind DASHContext *c = s->priv_data; AdaptationSet *as = &c->as[as_index]; AVDictionaryEntry *lang, *role; - int i; + int i, ret; avio_printf(out, "\t\t<AdaptationSet id=\"%d\" contentType=\"%s\" startWithSAP=\"1\" segmentAlignment=\"true\" bitstreamSwitching=\"true\"", as->id, as->media_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio"); @@ -724,7 +733,9 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind if (!final && c->ldash && os->gop_size && os->frag_type != FRAG_TYPE_NONE && !(c->profile & MPD_PROFILE_DVB) && (os->frag_type != FRAG_TYPE_DURATION || os->frag_duration != os->seg_duration)) avio_printf(out, "\t\t\t\t<Resync dT=\"%"PRId64"\" type=\"1\"/>\n", os->gop_size); - output_segment_list(os, out, s, i, final); + ret = output_segment_list(os, out, s, i, final); + if (ret < 0) + return ret; avio_printf(out, "\t\t\t</Representation>\n"); } avio_printf(out, "\t\t</AdaptationSet>\n"); @@ -1100,7 +1111,9 @@ static int write_manifest(AVFormatContext *s, int final) avio_printf(out, "</MPD>\n"); avio_flush(out); - dashenc_io_close(s, &c->mpd_out, temp_filename); + ret = dashenc_io_close(s, &c->mpd_out, temp_filename); + if (ret < 0) + return ret; if (use_rename) { if ((ret = ff_rename(temp_filename, s->url, s)) < 0) @@ -1217,7 +1230,9 @@ static int write_manifest(AVFormatContext *s, int final) } } - dashenc_io_close(s, &c->m3u8_out, temp_filename); + ret = dashenc_io_close(s, &c->m3u8_out, temp_filename); + if (ret < 0) + return ret; if (use_rename) if ((ret = ff_rename(temp_filename, filename_hls, s)) < 0) return ret; @@ -1831,7 +1846,9 @@ static int dash_flush(AVFormatContext *s, int final, int stream) if (c->single_file) { find_index_range(s, os->full_path, os->pos, &index_length); } else { - dashenc_io_close(s, &os->out, os->temp_path); + ret = dashenc_io_close(s, &os->out, os->temp_path); + if (ret < 0) + break; if (use_rename) { ret = ff_rename(os->temp_path, os->full_path, os->ctx); @@ -2107,8 +2124,8 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) os->total_pkt_duration += pkt->duration; os->last_flags = pkt->flags; - if (!os->init_range_length) - flush_init_segment(s, os); + if (!os->init_range_length && (ret = flush_init_segment(s, os)) < 0) + return ret; //open the output context when the first frame of a segment is ready if (!c->single_file && os->packets_written == 1) { @@ -2135,13 +2152,14 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) // in streaming mode, the segments are available for playing // before fully written but the manifest is needed so that // clients and discover the segment filenames. - if (c->streaming) { - write_manifest(s, 0); - } + if (c->streaming && (ret = write_manifest(s, 0)) < 0) + return ret; if (c->lhls) { char *prefetch_url = use_rename ? NULL : os->filename; - write_hls_media_playlist(os, s, pkt->stream_index, 0, prefetch_url); + ret = write_hls_media_playlist(os, s, pkt->stream_index, 0, prefetch_url); + if (ret < 0) + return ret; } } @@ -2164,7 +2182,7 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) static int dash_write_trailer(AVFormatContext *s) { DASHContext *c = s->priv_data; - int i; + int i, ret; if (s->nb_streams > 0) { OutputStream *os = &c->streams[0]; @@ -2178,7 +2196,7 @@ static int dash_write_trailer(AVFormatContext *s) s->streams[0]->time_base, AV_TIME_BASE_Q); } - dash_flush(s, 1, -1); + ret = dash_flush(s, 1, -1); if (c->remove_at_exit) { for (i = 0; i < s->nb_streams; ++i) { @@ -2200,7 +2218,7 @@ static int dash_write_trailer(AVFormatContext *s) } } - return 0; + return ret; } static int dash_check_bitstream(AVFormatContext *s, AVStream *st, -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
