[FFmpeg-devel] [PATCH] WIP: lavf/segment: provide a virtual AVIOContext representing all the segments
Rodger Combs
rodger.combs at gmail.com
Mon Mar 30 06:07:21 CEST 2015
This needs a fair bit of testing and review before merge.
Re: mini:
> if the header does get updated at the end this would mismatch if only a
> subset of segments get concatenated
This is one reason why I have the `seekback` option disabled by default
(the other being that it avoids potential interprocess races)
---
libavformat/segment.c | 251 +++++++++++++++++++++++++++++++++++++-------------
1 file changed, 189 insertions(+), 62 deletions(-)
diff --git a/libavformat/segment.c b/libavformat/segment.c
index 7b8fdad..285adfc 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,8 +320,13 @@ 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)
size += strlen(seg->entry_prefix);
@@ -226,19 +355,23 @@ static int segment_start(AVFormatContext *s, int write_header)
}
seg->segment_idx++;
- if ((seg->segment_idx_wrap) && (seg->segment_idx%seg->segment_idx_wrap == 0))
+ if ((seg->segment_idx_wrap) && (seg->segment_idx % seg->segment_idx_wrap == 0))
seg->segment_idx_wrap_nb++;
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 +459,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 +470,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 +512,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 +635,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 +695,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 +709,8 @@ static int seg_write_header(AVFormatContext *s)
int ret;
int i;
+ seg->max_offset = seg->cur_entry.start_offset = SIZE_MAX;
+
seg->segment_count = 0;
if (!seg->write_header_trailer)
seg->individual_header_trailer = 0;
@@ -676,16 +795,23 @@ 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->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 +841,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 +979,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 +999,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 +1051,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
More information about the ffmpeg-devel
mailing list