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: Martin Storsjö <martin@martin.st>
... | ... |
@@ -239,6 +239,10 @@ more efficient), but with this option set, the muxer writes one moof/mdat |
239 | 239 |
pair for each track, making it easier to separate tracks. |
240 | 240 |
|
241 | 241 |
This option is implicitly set when writing ismv (Smooth Streaming) files. |
242 |
+@item -movflags faststart |
|
243 |
+Run a second pass moving the index (moov atom) to the beginning of the file. |
|
244 |
+This operation can take a while, and will not work in various situations such |
|
245 |
+as fragmented output, thus it is not enabled by default. |
|
242 | 246 |
@end table |
243 | 247 |
|
244 | 248 |
Smooth Streaming content can be pushed in real time to a publishing |
... | ... |
@@ -51,6 +51,7 @@ static const AVOption options[] = { |
51 | 51 |
{ "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" }, |
52 | 52 |
{ "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" }, |
53 | 53 |
{ "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" }, |
54 |
+ { "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" }, |
|
54 | 55 |
FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags), |
55 | 56 |
{ "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, |
56 | 57 |
{ "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 | 3027 |
FF_MOV_FLAG_FRAG_CUSTOM)) |
3028 | 3028 |
mov->flags |= FF_MOV_FLAG_FRAGMENT; |
3029 | 3029 |
|
3030 |
+ /* faststart: moov at the beginning of the file, if supported */ |
|
3031 |
+ if (mov->flags & FF_MOV_FLAG_FASTSTART) { |
|
3032 |
+ if ((mov->flags & FF_MOV_FLAG_FRAGMENT) || |
|
3033 |
+ (s->flags & AVFMT_FLAG_CUSTOM_IO)) |
|
3034 |
+ mov->flags &= ~FF_MOV_FLAG_FASTSTART; |
|
3035 |
+ } |
|
3036 |
+ |
|
3030 | 3037 |
/* Non-seekable output is ok if using fragmentation. If ism_lookahead |
3031 | 3038 |
* is enabled, we don't support non-seekable output at all. */ |
3032 | 3039 |
if (!s->pb->seekable && |
... | ... |
@@ -3169,8 +3177,11 @@ static int mov_write_header(AVFormatContext *s) |
3169 | 3169 |
FF_MOV_FLAG_FRAGMENT; |
3170 | 3170 |
} |
3171 | 3171 |
|
3172 |
- if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) |
|
3172 |
+ if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) { |
|
3173 |
+ if (mov->flags & FF_MOV_FLAG_FASTSTART) |
|
3174 |
+ mov->reserved_moov_pos = avio_tell(pb); |
|
3173 | 3175 |
mov_write_mdat_tag(pb, mov); |
3176 |
+ } |
|
3174 | 3177 |
|
3175 | 3178 |
if (t = av_dict_get(s->metadata, "creation_time", NULL, 0)) |
3176 | 3179 |
mov->time = ff_iso8601_to_unix_time(t->value); |
... | ... |
@@ -3208,6 +3219,115 @@ static int mov_write_header(AVFormatContext *s) |
3208 | 3208 |
return -1; |
3209 | 3209 |
} |
3210 | 3210 |
|
3211 |
+static int get_moov_size(AVFormatContext *s) |
|
3212 |
+{ |
|
3213 |
+ int ret; |
|
3214 |
+ uint8_t *buf; |
|
3215 |
+ AVIOContext *moov_buf; |
|
3216 |
+ MOVMuxContext *mov = s->priv_data; |
|
3217 |
+ |
|
3218 |
+ if ((ret = avio_open_dyn_buf(&moov_buf)) < 0) |
|
3219 |
+ return ret; |
|
3220 |
+ mov_write_moov_tag(moov_buf, mov, s); |
|
3221 |
+ ret = avio_close_dyn_buf(moov_buf, &buf); |
|
3222 |
+ av_free(buf); |
|
3223 |
+ return ret; |
|
3224 |
+} |
|
3225 |
+ |
|
3226 |
+/* |
|
3227 |
+ * This function gets the moov size if moved to the top of the file: the chunk |
|
3228 |
+ * offset table can switch between stco (32-bit entries) to co64 (64-bit |
|
3229 |
+ * entries) when the moov is moved to the beginning, so the size of the moov |
|
3230 |
+ * would change. It also updates the chunk offset tables. |
|
3231 |
+ */ |
|
3232 |
+static int compute_moov_size(AVFormatContext *s) |
|
3233 |
+{ |
|
3234 |
+ int i, moov_size, moov_size2; |
|
3235 |
+ MOVMuxContext *mov = s->priv_data; |
|
3236 |
+ |
|
3237 |
+ moov_size = get_moov_size(s); |
|
3238 |
+ if (moov_size < 0) |
|
3239 |
+ return moov_size; |
|
3240 |
+ |
|
3241 |
+ for (i = 0; i < mov->nb_streams; i++) |
|
3242 |
+ mov->tracks[i].data_offset += moov_size; |
|
3243 |
+ |
|
3244 |
+ moov_size2 = get_moov_size(s); |
|
3245 |
+ if (moov_size2 < 0) |
|
3246 |
+ return moov_size2; |
|
3247 |
+ |
|
3248 |
+ /* if the size changed, we just switched from stco to co64 and need to |
|
3249 |
+ * update the offsets */ |
|
3250 |
+ if (moov_size2 != moov_size) |
|
3251 |
+ for (i = 0; i < mov->nb_streams; i++) |
|
3252 |
+ mov->tracks[i].data_offset += moov_size2 - moov_size; |
|
3253 |
+ |
|
3254 |
+ return moov_size2; |
|
3255 |
+} |
|
3256 |
+ |
|
3257 |
+static int shift_data(AVFormatContext *s) |
|
3258 |
+{ |
|
3259 |
+ int ret = 0, moov_size; |
|
3260 |
+ MOVMuxContext *mov = s->priv_data; |
|
3261 |
+ int64_t pos, pos_end = avio_tell(s->pb); |
|
3262 |
+ uint8_t *buf, *read_buf[2]; |
|
3263 |
+ int read_buf_id = 0; |
|
3264 |
+ int read_size[2]; |
|
3265 |
+ AVIOContext *read_pb; |
|
3266 |
+ |
|
3267 |
+ moov_size = compute_moov_size(s); |
|
3268 |
+ if (moov_size < 0) |
|
3269 |
+ return moov_size; |
|
3270 |
+ |
|
3271 |
+ buf = av_malloc(moov_size * 2); |
|
3272 |
+ if (!buf) |
|
3273 |
+ return AVERROR(ENOMEM); |
|
3274 |
+ read_buf[0] = buf; |
|
3275 |
+ read_buf[1] = buf + moov_size; |
|
3276 |
+ |
|
3277 |
+ /* Shift the data: the AVIO context of the output can only be used for |
|
3278 |
+ * writing, so we re-open the same output, but for reading. It also avoids |
|
3279 |
+ * a read/seek/write/seek back and forth. */ |
|
3280 |
+ avio_flush(s->pb); |
|
3281 |
+ ret = avio_open(&read_pb, s->filename, AVIO_FLAG_READ); |
|
3282 |
+ if (ret < 0) { |
|
3283 |
+ av_log(s, AV_LOG_ERROR, "Unable to re-open %s output file for " |
|
3284 |
+ "the second pass (faststart)\n", s->filename); |
|
3285 |
+ goto end; |
|
3286 |
+ } |
|
3287 |
+ |
|
3288 |
+ /* mark the end of the shift to up to the last data we wrote, and get ready |
|
3289 |
+ * for writing */ |
|
3290 |
+ pos_end = avio_tell(s->pb); |
|
3291 |
+ avio_seek(s->pb, mov->reserved_moov_pos + moov_size, SEEK_SET); |
|
3292 |
+ |
|
3293 |
+ /* start reading at where the new moov will be placed */ |
|
3294 |
+ avio_seek(read_pb, mov->reserved_moov_pos, SEEK_SET); |
|
3295 |
+ pos = avio_tell(read_pb); |
|
3296 |
+ |
|
3297 |
+#define READ_BLOCK do { \ |
|
3298 |
+ read_size[read_buf_id] = avio_read(read_pb, read_buf[read_buf_id], moov_size); \ |
|
3299 |
+ read_buf_id ^= 1; \ |
|
3300 |
+} while (0) |
|
3301 |
+ |
|
3302 |
+ /* shift data by chunk of at most moov_size */ |
|
3303 |
+ READ_BLOCK; |
|
3304 |
+ do { |
|
3305 |
+ int n; |
|
3306 |
+ READ_BLOCK; |
|
3307 |
+ n = read_size[read_buf_id]; |
|
3308 |
+ if (n <= 0) |
|
3309 |
+ break; |
|
3310 |
+ avio_write(s->pb, read_buf[read_buf_id], n); |
|
3311 |
+ pos += n; |
|
3312 |
+ } while (pos < pos_end); |
|
3313 |
+ avio_close(read_pb); |
|
3314 |
+ |
|
3315 |
+end: |
|
3316 |
+ av_free(buf); |
|
3317 |
+ return ret; |
|
3318 |
+} |
|
3319 |
+ |
|
3211 | 3320 |
static int mov_write_trailer(AVFormatContext *s) |
3212 | 3321 |
{ |
3213 | 3322 |
MOVMuxContext *mov = s->priv_data; |
... | ... |
@@ -3226,9 +3346,9 @@ static int mov_write_trailer(AVFormatContext *s) |
3226 | 3226 |
} |
3227 | 3227 |
} |
3228 | 3228 |
|
3229 |
- moov_pos = avio_tell(pb); |
|
3230 |
- |
|
3231 | 3229 |
if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) { |
3230 |
+ moov_pos = avio_tell(pb); |
|
3231 |
+ |
|
3232 | 3232 |
/* Write size of mdat tag */ |
3233 | 3233 |
if (mov->mdat_size + 8 <= UINT32_MAX) { |
3234 | 3234 |
avio_seek(pb, mov->mdat_pos, SEEK_SET); |
... | ... |
@@ -3244,7 +3364,16 @@ static int mov_write_trailer(AVFormatContext *s) |
3244 | 3244 |
} |
3245 | 3245 |
avio_seek(pb, moov_pos, SEEK_SET); |
3246 | 3246 |
|
3247 |
- mov_write_moov_tag(pb, mov, s); |
|
3247 |
+ if (mov->flags & FF_MOV_FLAG_FASTSTART) { |
|
3248 |
+ av_log(s, AV_LOG_INFO, "Starting second pass: moving the moov atom to the beginning of the file\n"); |
|
3249 |
+ res = shift_data(s); |
|
3250 |
+ if (res == 0) { |
|
3251 |
+ avio_seek(s->pb, mov->reserved_moov_pos, SEEK_SET); |
|
3252 |
+ mov_write_moov_tag(pb, mov, s); |
|
3253 |
+ } |
|
3254 |
+ } else { |
|
3255 |
+ mov_write_moov_tag(pb, mov, s); |
|
3256 |
+ } |
|
3248 | 3257 |
} else { |
3249 | 3258 |
mov_flush_fragment(s); |
3250 | 3259 |
mov_write_mfra_tag(pb, mov); |
... | ... |
@@ -156,6 +156,8 @@ typedef struct MOVMuxContext { |
156 | 156 |
int max_fragment_size; |
157 | 157 |
int ism_lookahead; |
158 | 158 |
AVIOContext *mdat_buf; |
159 |
+ |
|
160 |
+ int64_t reserved_moov_pos; |
|
159 | 161 |
} MOVMuxContext; |
160 | 162 |
|
161 | 163 |
#define FF_MOV_FLAG_RTP_HINT 1 |
... | ... |
@@ -165,6 +167,7 @@ typedef struct MOVMuxContext { |
165 | 165 |
#define FF_MOV_FLAG_SEPARATE_MOOF 16 |
166 | 166 |
#define FF_MOV_FLAG_FRAG_CUSTOM 32 |
167 | 167 |
#define FF_MOV_FLAG_ISML 64 |
168 |
+#define FF_MOV_FLAG_FASTSTART 128 |
|
168 | 169 |
|
169 | 170 |
int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt); |
170 | 171 |
|