This allows the use of muxers like matroska, which attempt to seek even when an AVIOContext doesn't set `seekable`, without concern for a rouge seek leading the muxer to overwrite the wrong data in a later segment. --- doc/muxers.texi | 17 ++++ libavformat/segment.c | 262 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 215 insertions(+), 64 deletions(-)
diff --git a/doc/muxers.texi b/doc/muxers.texi index 06483fa..f7b9ee3 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -1145,6 +1145,23 @@ muxers/codecs. It is set to @code{0} by default. @item initial_offset @var{offset} Specify timestamp offset to apply to the output packet timestamps. The argument must be a time duration specification, and defaults to 0. + +@item individual_header_trailer @var{1|0} +Write each segment as an individual distinct file in the underlying format. +When this is set to @code{0}, the segments are treated as a single continuous +stream. When set to @code{1} (the default), each individual segment receives +a header and trailer, so they can be played independently. + +@item segment_header_filename @var{name} +Write the global header to a separate file. Requires +@var{individual_header_trailer} to be @code{0}. If not set, the global header +will be written to the first file along with actual segment data. + +@item segment_seekback @var{1|0} +Allow the muxer to seek back and overwrite data from previous segments. Requires +@var{individual_header_trailer} to be @code{0}. If set to @code{0} (the default), +the underlying muxer will be unable to seek back into previous segments, so they +can be relied upon not to change once written. @end table @subsection Examples diff --git a/libavformat/segment.c b/libavformat/segment.c index 36417f2..aeff0e6 100644 --- a/libavformat/segment.c +++ b/libavformat/segment.c @@ -49,8 +49,10 @@ typedef struct SegmentListEntry { int64_t start_pts; int64_t offset_pts; char *filename; + char *full_filename; struct SegmentListEntry *next; int64_t last_duration; + size_t start_offset; } SegmentListEntry; typedef enum { @@ -119,7 +121,13 @@ typedef struct SegmentContext { SegmentListEntry cur_entry; SegmentListEntry *segment_list_entries; + SegmentListEntry *segment_list_entries_all; SegmentListEntry *segment_list_entries_end; + SegmentListEntry *segment_list_entry_writing; + int seekback; ///< allow seeking back to previous segments + AVIOContext *cur_pb; ///< current segment put-byte context + size_t write_offset; + size_t max_offset; } SegmentContext; static void print_csv_escaped_str(AVIOContext *ctx, const char *str) @@ -138,6 +146,122 @@ static void print_csv_escaped_str(AVIOContext *ctx, const char *str) avio_w8(ctx, '"'); } +static int64_t virtual_seek(void *priv, int64_t target, int whence) +{ + AVFormatContext *s = priv; + SegmentContext *seg = s->priv_data; + SegmentListEntry *it, *current = NULL; + int64_t offset = target; + int64_t ret; + + if (whence != SEEK_SET) + return AVERROR(EINVAL); + if (offset < 0) + return AVERROR(EINVAL); + + if (offset >= seg->max_offset) { + avio_closep(&seg->cur_pb); + seg->write_offset = offset; + return offset; + } + + if (seg->cur_entry.start_offset <= offset) { + current = &seg->cur_entry; + } else { + for (it = seg->segment_list_entries_all; it; it = it->next) { + if (it->start_offset <= offset) + current = it; + else if (it->start_offset > offset) + break; + } + } + + offset -= current->start_offset; + + if (current != seg->segment_list_entry_writing) { + int is_seekback = (current != &seg->cur_entry) && seg->segment_list_entries; + char *new_filename; + AVIOContext *new_ctx = NULL; + AVDictionary *options = NULL; + + if (!seg->seekback && is_seekback) + return AVERROR(EINVAL); + + new_filename = current->full_filename; + + if (new_filename) { + if (is_seekback) + av_dict_set_int(&options, "truncate", 0, 0); + if ((ret = avio_open2(&new_ctx, new_filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, &options)) < 0) { + av_log(s, AV_LOG_ERROR, "Failed to seek into segment '%s'\n", new_filename); + return ret; + } + } + + avio_close(seg->cur_pb); + seg->cur_pb = new_ctx; + seg->segment_list_entry_writing = current; + } + + if (seg->cur_pb) + if ((ret = avio_seek(seg->cur_pb, offset, SEEK_SET)) < 0) + return ret; + + seg->write_offset = offset; + + return target; +} + +static int virtual_write(void *priv, uint8_t *buf, int buf_size) +{ + AVFormatContext *s = priv; + SegmentContext *seg = s->priv_data; + int ret = 0; + int written = 0; + + while (written < buf_size) { + SegmentListEntry *cur = seg->segment_list_entry_writing; + size_t start = cur->start_offset + seg->write_offset; + SegmentListEntry *next = cur->next ? cur->next : (cur == &seg->cur_entry ? NULL : &seg->cur_entry); + size_t end = next ? next->start_offset : SIZE_MAX; + int to_write = FFMIN(end - start, buf_size - written); + if (seg->cur_pb) + avio_write(seg->cur_pb, buf, to_write); + buf += to_write; + written += to_write; + seg->write_offset += to_write; + if (written < buf_size) + if ((ret = virtual_seek(s, end, SEEK_SET)) < 0) + return ret; + } + + return written; +} + +static void virtual_close(SegmentContext *seg) +{ + avio_closep(&seg->cur_pb); + av_freep(&seg->avf->pb); +} + +static int open_virtual_ctx(AVFormatContext *s, AVIOContext **ctx) +{ + SegmentContext *seg = s->priv_data; + int buf_size = 32768; + uint8_t *buf = av_malloc(buf_size); + if (!buf) + return AVERROR(ENOMEM); + *ctx = avio_alloc_context(buf, buf_size, AVIO_FLAG_WRITE, s, NULL, + virtual_write, virtual_seek); + if (!*ctx) { + av_free(buf); + return AVERROR(ENOMEM); + } + (*ctx)->seekable = seg->seekback; + return virtual_seek(s, 0, SEEK_SET); +} + static int segment_mux_init(AVFormatContext *s) { SegmentContext *seg = s->priv_data; @@ -201,6 +325,10 @@ static int set_segment_filename(AVFormatContext *s) return AVERROR(EINVAL); } + seg->cur_entry.full_filename = av_strdup(oc->filename); + if (!seg->cur_entry.full_filename) + return AVERROR(ENOMEM); + /* copy modified name in list entry */ size = strlen(av_basename(oc->filename)) + 1; if (seg->entry_prefix) @@ -237,13 +365,17 @@ static int segment_start(AVFormatContext *s, int write_header) if ((err = set_segment_filename(s)) < 0) return err; - if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) { - av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename); - return err; + if (seg->individual_header_trailer) { + if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) { + av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename); + return err; + } + } else { + seg->cur_entry.start_offset += seg->write_offset; + if ((err = virtual_seek(s, seg->cur_entry.start_offset, SEEK_SET)) < 0) + return err; } - if (!seg->individual_header_trailer) - oc->pb->seekable = 0; if (oc->oformat->priv_class && oc->priv_data) av_opt_set(oc->priv_data, "mpegts_flags", "+resend_headers", 0); @@ -332,6 +464,7 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) { SegmentContext *seg = s->priv_data; AVFormatContext *oc = seg->avf; + SegmentListEntry *entry; int ret = 0; av_write_frame(oc, NULL); /* Flush any buffered data (fragmented mp4) */ @@ -342,29 +475,27 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n", oc->filename); - if (seg->list) { - if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) { - SegmentListEntry *entry = av_mallocz(sizeof(*entry)); - if (!entry) { - ret = AVERROR(ENOMEM); - goto end; - } + entry = av_mallocz(sizeof(*entry)); + if (!entry) { + ret = AVERROR(ENOMEM); + goto end; + } + + /* append new element */ + memcpy(entry, &seg->cur_entry, sizeof(*entry)); + if (!seg->segment_list_entries) + seg->segment_list_entries_all->next = seg->segment_list_entries = entry; + else + seg->segment_list_entries_end->next = entry; + seg->segment_list_entries_end = entry; - /* append new element */ - memcpy(entry, &seg->cur_entry, sizeof(*entry)); - entry->filename = av_strdup(entry->filename); - if (!seg->segment_list_entries) - seg->segment_list_entries = seg->segment_list_entries_end = entry; - else - seg->segment_list_entries_end->next = entry; - seg->segment_list_entries_end = entry; + seg->segment_list_entry_writing = NULL; + if (seg->list) { + if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) { /* drop first item */ if (seg->list_size && seg->segment_count >= seg->list_size) { - entry = seg->segment_list_entries; seg->segment_list_entries = seg->segment_list_entries->next; - av_freep(&entry->filename); - av_freep(&entry); } if ((ret = segment_list_open(s)) < 0) @@ -387,7 +518,8 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) seg->segment_count++; end: - avio_closep(&oc->pb); + if (seg->individual_header_trailer) + avio_closep(&oc->pb); return ret; } @@ -509,26 +641,6 @@ end: return ret; } -static int open_null_ctx(AVIOContext **ctx) -{ - int buf_size = 32768; - uint8_t *buf = av_malloc(buf_size); - if (!buf) - return AVERROR(ENOMEM); - *ctx = avio_alloc_context(buf, buf_size, AVIO_FLAG_WRITE, NULL, NULL, NULL, NULL); - if (!*ctx) { - av_free(buf); - return AVERROR(ENOMEM); - } - return 0; -} - -static void close_null_ctxp(AVIOContext **pb) -{ - av_freep(&(*pb)->buffer); - av_freep(pb); -} - static int select_reference_stream(AVFormatContext *s) { SegmentContext *seg = s->priv_data; @@ -589,6 +701,8 @@ static int select_reference_stream(AVFormatContext *s) static void seg_free_context(SegmentContext *seg) { avio_closep(&seg->list_pb); + if (!seg->individual_header_trailer) + virtual_close(seg); avformat_free_context(seg->avf); seg->avf = NULL; } @@ -601,6 +715,14 @@ static int seg_write_header(AVFormatContext *s) int ret; int i; + seg->max_offset = SIZE_MAX; + + if (seg->write_header_trailer && !seg->header_filename) { + seg->cur_entry.start_offset = 0; + } else { + seg->cur_entry.start_offset = SIZE_MAX; + } + seg->segment_count = 0; if (!seg->write_header_trailer) seg->individual_header_trailer = 0; @@ -689,16 +811,28 @@ static int seg_write_header(AVFormatContext *s) if ((ret = set_segment_filename(s)) < 0) goto fail; - if (seg->write_header_trailer) { - if ((ret = avio_open2(&oc->pb, seg->header_filename ? seg->header_filename : oc->filename, AVIO_FLAG_WRITE, + seg->segment_list_entries_all = av_mallocz(sizeof(*seg->segment_list_entries_all)); + if (!seg->segment_list_entries_all) { + ret = ENOMEM; + goto fail; + } + + if (seg->header_filename) { + seg->segment_list_entries_all->full_filename = av_strdup(seg->header_filename); + if (!seg->segment_list_entries_all->full_filename) { + ret = ENOMEM; + goto fail; + } + } + + if (seg->individual_header_trailer) { + if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL)) < 0) { av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename); goto fail; } - if (!seg->individual_header_trailer) - oc->pb->seekable = 0; } else { - if ((ret = open_null_ctx(&oc->pb)) < 0) + if ((ret = open_virtual_ctx(s, &oc->pb)) < 0) goto fail; } @@ -728,17 +862,11 @@ static int seg_write_header(AVFormatContext *s) s->avoid_negative_ts = 1; if (!seg->write_header_trailer || seg->header_filename) { - if (seg->header_filename) { - av_write_frame(oc, NULL); - avio_closep(&oc->pb); - } else { - close_null_ctxp(&oc->pb); - } - if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + av_write_frame(oc, NULL); + seg->cur_entry.start_offset = seg->write_offset; + if ((ret = virtual_seek(s, seg->write_offset, SEEK_SET)) < 0) goto fail; - if (!seg->individual_header_trailer) - oc->pb->seekable = 0; + ret = 0; } fail: @@ -872,13 +1000,17 @@ static int seg_write_trailer(struct AVFormatContext *s) if (!seg->write_header_trailer) { if ((ret = segment_end(s, 0, 1)) < 0) goto fail; - if ((ret = open_null_ctx(&oc->pb)) < 0) - goto fail; + + seg->max_offset = seg->cur_entry.start_offset + seg->write_offset; + virtual_seek(oc->pb, seg->max_offset, SEEK_SET); ret = av_write_trailer(oc); - close_null_ctxp(&oc->pb); } else { ret = segment_end(s, 1, 1); } + + if (!seg->individual_header_trailer) + virtual_close(seg); + fail: if (seg->list) avio_closep(&seg->list_pb); @@ -889,9 +1021,10 @@ fail: av_freep(&seg->frames); av_freep(&seg->cur_entry.filename); - cur = seg->segment_list_entries; + cur = seg->segment_list_entries_all; while (cur) { next = cur->next; + av_freep(&cur->full_filename); av_freep(&cur->filename); av_free(cur); cur = next; @@ -941,6 +1074,7 @@ static const AVOption options[] = { { "write_header_trailer", "write a header to the first segment and a trailer to the last one", OFFSET(write_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, { "reset_timestamps", "reset timestamps at the begin of each segment", OFFSET(reset_timestamps), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, { "initial_offset", "set initial timestamp offset", OFFSET(initial_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, -INT64_MAX, INT64_MAX, E }, + { "segment_seekback", "allow seeking back to previous segments", OFFSET(seekback), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, { NULL }, }; -- 2.6.2 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel