[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