[FFmpeg-devel] [PATCH] lavf/segment: provide a virtual AVIOContext representing all the segments
Rodger Combs
rodger.combs at gmail.com
Wed Apr 8 17:52:50 CEST 2015
> On Mar 30, 2015, at 21:23, Rodger Combs <rodger.combs at gmail.com> wrote:
>
> This needs a fair bit of testing and review before merge.
> ---
> libavformat/segment.c | 259 ++++++++++++++++++++++++++++++++++++++------------
> 1 file changed, 198 insertions(+), 61 deletions(-)
>
> diff --git a/libavformat/segment.c b/libavformat/segment.c
> index 69038ca..4d934a2 100644
> --- a/libavformat/segment.c
> +++ b/libavformat/segment.c
> @@ -48,8 +48,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 {
> @@ -114,7 +116,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)
> @@ -133,6 +141,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;
> @@ -196,6 +320,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)
> @@ -232,13 +360,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);
> @@ -326,6 +458,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) */
> @@ -336,28 +469,28 @@ 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);
>
> + 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;
> +
> + seg->segment_list_entry_writing = NULL;
> +
> 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;
> - }
> -
> - /* append new element */
> - memcpy(entry, &seg->cur_entry, sizeof(*entry));
> - 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;
>
> /* 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)
> @@ -378,7 +511,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;
> }
> @@ -500,26 +634,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;
> @@ -580,6 +694,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;
> }
> @@ -592,6 +708,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;
> @@ -676,16 +800,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;
> }
>
> @@ -715,17 +851,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:
> @@ -859,12 +989,17 @@ static int seg_write_trailer(struct AVFormatContext *s)
> if (!seg->write_header_trailer) {
> if ((ret = segment_end(s, 0, 1)) < 0)
> goto fail;
> - open_null_ctx(&oc->pb);
> +
> + 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);
> @@ -874,9 +1009,10 @@ fail:
> av_freep(&seg->times);
> av_freep(&seg->frames);
>
> - 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;
> @@ -925,6 +1061,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.3.4
>
Bump.
More information about the ffmpeg-devel
mailing list