Commit f8ef91ff authored by Clément Bœsch's avatar Clément Bœsch Committed by Martin Storsjö

movenc: add faststart option for web streaming

Faststart moves the moov atom to the beginning of the file and rewrites
the rest of the file after muxing is complete.
Signed-off-by: 's avatarMartin Storsjö <martin@martin.st>
parent 4622f11f
...@@ -239,6 +239,10 @@ more efficient), but with this option set, the muxer writes one moof/mdat ...@@ -239,6 +239,10 @@ more efficient), but with this option set, the muxer writes one moof/mdat
pair for each track, making it easier to separate tracks. pair for each track, making it easier to separate tracks.
This option is implicitly set when writing ismv (Smooth Streaming) files. This option is implicitly set when writing ismv (Smooth Streaming) files.
@item -movflags faststart
Run a second pass moving the index (moov atom) to the beginning of the file.
This operation can take a while, and will not work in various situations such
as fragmented output, thus it is not enabled by default.
@end table @end table
Smooth Streaming content can be pushed in real time to a publishing Smooth Streaming content can be pushed in real time to a publishing
......
...@@ -51,6 +51,7 @@ static const AVOption options[] = { ...@@ -51,6 +51,7 @@ static const AVOption options[] = {
{ "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
{ "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
{ "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_ISML}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, { "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_ISML}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
{ "faststart", "Run a second pass to put the index (moov atom) at the beginning of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FASTSTART}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags), FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags),
{ "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM},
{ "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM}, { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM},
...@@ -3027,6 +3028,13 @@ static int mov_write_header(AVFormatContext *s) ...@@ -3027,6 +3028,13 @@ static int mov_write_header(AVFormatContext *s)
FF_MOV_FLAG_FRAG_CUSTOM)) FF_MOV_FLAG_FRAG_CUSTOM))
mov->flags |= FF_MOV_FLAG_FRAGMENT; mov->flags |= FF_MOV_FLAG_FRAGMENT;
/* faststart: moov at the beginning of the file, if supported */
if (mov->flags & FF_MOV_FLAG_FASTSTART) {
if ((mov->flags & FF_MOV_FLAG_FRAGMENT) ||
(s->flags & AVFMT_FLAG_CUSTOM_IO))
mov->flags &= ~FF_MOV_FLAG_FASTSTART;
}
/* Non-seekable output is ok if using fragmentation. If ism_lookahead /* Non-seekable output is ok if using fragmentation. If ism_lookahead
* is enabled, we don't support non-seekable output at all. */ * is enabled, we don't support non-seekable output at all. */
if (!s->pb->seekable && if (!s->pb->seekable &&
...@@ -3169,8 +3177,11 @@ static int mov_write_header(AVFormatContext *s) ...@@ -3169,8 +3177,11 @@ static int mov_write_header(AVFormatContext *s)
FF_MOV_FLAG_FRAGMENT; FF_MOV_FLAG_FRAGMENT;
} }
if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
if (mov->flags & FF_MOV_FLAG_FASTSTART)
mov->reserved_moov_pos = avio_tell(pb);
mov_write_mdat_tag(pb, mov); mov_write_mdat_tag(pb, mov);
}
if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) if (t = av_dict_get(s->metadata, "creation_time", NULL, 0))
mov->time = ff_iso8601_to_unix_time(t->value); mov->time = ff_iso8601_to_unix_time(t->value);
...@@ -3208,6 +3219,115 @@ static int mov_write_header(AVFormatContext *s) ...@@ -3208,6 +3219,115 @@ static int mov_write_header(AVFormatContext *s)
return -1; return -1;
} }
static int get_moov_size(AVFormatContext *s)
{
int ret;
uint8_t *buf;
AVIOContext *moov_buf;
MOVMuxContext *mov = s->priv_data;
if ((ret = avio_open_dyn_buf(&moov_buf)) < 0)
return ret;
mov_write_moov_tag(moov_buf, mov, s);
ret = avio_close_dyn_buf(moov_buf, &buf);
av_free(buf);
return ret;
}
/*
* This function gets the moov size if moved to the top of the file: the chunk
* offset table can switch between stco (32-bit entries) to co64 (64-bit
* entries) when the moov is moved to the beginning, so the size of the moov
* would change. It also updates the chunk offset tables.
*/
static int compute_moov_size(AVFormatContext *s)
{
int i, moov_size, moov_size2;
MOVMuxContext *mov = s->priv_data;
moov_size = get_moov_size(s);
if (moov_size < 0)
return moov_size;
for (i = 0; i < mov->nb_streams; i++)
mov->tracks[i].data_offset += moov_size;
moov_size2 = get_moov_size(s);
if (moov_size2 < 0)
return moov_size2;
/* if the size changed, we just switched from stco to co64 and need to
* update the offsets */
if (moov_size2 != moov_size)
for (i = 0; i < mov->nb_streams; i++)
mov->tracks[i].data_offset += moov_size2 - moov_size;
return moov_size2;
}
static int shift_data(AVFormatContext *s)
{
int ret = 0, moov_size;
MOVMuxContext *mov = s->priv_data;
int64_t pos, pos_end = avio_tell(s->pb);
uint8_t *buf, *read_buf[2];
int read_buf_id = 0;
int read_size[2];
AVIOContext *read_pb;
moov_size = compute_moov_size(s);
if (moov_size < 0)
return moov_size;
buf = av_malloc(moov_size * 2);
if (!buf)
return AVERROR(ENOMEM);
read_buf[0] = buf;
read_buf[1] = buf + moov_size;
/* Shift the data: the AVIO context of the output can only be used for
* writing, so we re-open the same output, but for reading. It also avoids
* a read/seek/write/seek back and forth. */
avio_flush(s->pb);
ret = avio_open(&read_pb, s->filename, AVIO_FLAG_READ);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Unable to re-open %s output file for "
"the second pass (faststart)\n", s->filename);
goto end;
}
/* mark the end of the shift to up to the last data we wrote, and get ready
* for writing */
pos_end = avio_tell(s->pb);
avio_seek(s->pb, mov->reserved_moov_pos + moov_size, SEEK_SET);
/* start reading at where the new moov will be placed */
avio_seek(read_pb, mov->reserved_moov_pos, SEEK_SET);
pos = avio_tell(read_pb);
#define READ_BLOCK do { \
read_size[read_buf_id] = avio_read(read_pb, read_buf[read_buf_id], moov_size); \
read_buf_id ^= 1; \
} while (0)
/* shift data by chunk of at most moov_size */
READ_BLOCK;
do {
int n;
READ_BLOCK;
n = read_size[read_buf_id];
if (n <= 0)
break;
avio_write(s->pb, read_buf[read_buf_id], n);
pos += n;
} while (pos < pos_end);
avio_close(read_pb);
end:
av_free(buf);
return ret;
}
static int mov_write_trailer(AVFormatContext *s) static int mov_write_trailer(AVFormatContext *s)
{ {
MOVMuxContext *mov = s->priv_data; MOVMuxContext *mov = s->priv_data;
...@@ -3226,9 +3346,9 @@ static int mov_write_trailer(AVFormatContext *s) ...@@ -3226,9 +3346,9 @@ static int mov_write_trailer(AVFormatContext *s)
} }
} }
if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
moov_pos = avio_tell(pb); moov_pos = avio_tell(pb);
if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
/* Write size of mdat tag */ /* Write size of mdat tag */
if (mov->mdat_size + 8 <= UINT32_MAX) { if (mov->mdat_size + 8 <= UINT32_MAX) {
avio_seek(pb, mov->mdat_pos, SEEK_SET); avio_seek(pb, mov->mdat_pos, SEEK_SET);
...@@ -3244,7 +3364,16 @@ static int mov_write_trailer(AVFormatContext *s) ...@@ -3244,7 +3364,16 @@ static int mov_write_trailer(AVFormatContext *s)
} }
avio_seek(pb, moov_pos, SEEK_SET); avio_seek(pb, moov_pos, SEEK_SET);
if (mov->flags & FF_MOV_FLAG_FASTSTART) {
av_log(s, AV_LOG_INFO, "Starting second pass: moving the moov atom to the beginning of the file\n");
res = shift_data(s);
if (res == 0) {
avio_seek(s->pb, mov->reserved_moov_pos, SEEK_SET);
mov_write_moov_tag(pb, mov, s);
}
} else {
mov_write_moov_tag(pb, mov, s); mov_write_moov_tag(pb, mov, s);
}
} else { } else {
mov_flush_fragment(s); mov_flush_fragment(s);
mov_write_mfra_tag(pb, mov); mov_write_mfra_tag(pb, mov);
......
...@@ -156,6 +156,8 @@ typedef struct MOVMuxContext { ...@@ -156,6 +156,8 @@ typedef struct MOVMuxContext {
int max_fragment_size; int max_fragment_size;
int ism_lookahead; int ism_lookahead;
AVIOContext *mdat_buf; AVIOContext *mdat_buf;
int64_t reserved_moov_pos;
} MOVMuxContext; } MOVMuxContext;
#define FF_MOV_FLAG_RTP_HINT 1 #define FF_MOV_FLAG_RTP_HINT 1
...@@ -165,6 +167,7 @@ typedef struct MOVMuxContext { ...@@ -165,6 +167,7 @@ typedef struct MOVMuxContext {
#define FF_MOV_FLAG_SEPARATE_MOOF 16 #define FF_MOV_FLAG_SEPARATE_MOOF 16
#define FF_MOV_FLAG_FRAG_CUSTOM 32 #define FF_MOV_FLAG_FRAG_CUSTOM 32
#define FF_MOV_FLAG_ISML 64 #define FF_MOV_FLAG_ISML 64
#define FF_MOV_FLAG_FASTSTART 128
int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt); int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment