Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2015 Martin Storsjo | ||
3 | * | ||
4 | * This file is part of FFmpeg. | ||
5 | * | ||
6 | * FFmpeg is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU Lesser General Public | ||
8 | * License as published by the Free Software Foundation; either | ||
9 | * version 2.1 of the License, or (at your option) any later version. | ||
10 | * | ||
11 | * FFmpeg is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | * Lesser General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU Lesser General Public | ||
17 | * License along with FFmpeg; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | */ | ||
20 | |||
21 | #include "config.h" | ||
22 | |||
23 | #include "libavutil/intreadwrite.h" | ||
24 | #include "libavutil/mathematics.h" | ||
25 | #include "libavutil/md5.h" | ||
26 | |||
27 | #include "libavformat/avformat.h" | ||
28 | |||
29 | #if HAVE_UNISTD_H | ||
30 | #include <unistd.h> | ||
31 | #endif | ||
32 | |||
33 | #if !HAVE_GETOPT | ||
34 | #include "compat/getopt.c" | ||
35 | #endif | ||
36 | |||
37 | #define HASH_SIZE 16 | ||
38 | |||
39 | static const uint8_t h264_extradata[] = { | ||
40 | 0x01, 0x4d, 0x40, 0x1e, 0xff, 0xe1, 0x00, 0x02, 0x67, 0x4d, 0x01, 0x00, 0x02, 0x68, 0xef | ||
41 | }; | ||
42 | static const uint8_t aac_extradata[] = { | ||
43 | 0x12, 0x10 | ||
44 | }; | ||
45 | |||
46 | |||
47 | static const char *format = "mp4"; | ||
48 | AVFormatContext *ctx; | ||
49 | uint8_t iobuf[32768]; | ||
50 | AVDictionary *opts; | ||
51 | |||
52 | int write_file; | ||
53 | const char *cur_name; | ||
54 | FILE* out; | ||
55 | int out_size; | ||
56 | struct AVMD5* md5; | ||
57 | uint8_t hash[HASH_SIZE]; | ||
58 | |||
59 | AVPacket *pkt; | ||
60 | AVStream *video_st, *audio_st; | ||
61 | int64_t audio_dts, video_dts; | ||
62 | |||
63 | int bframes; | ||
64 | int64_t duration; | ||
65 | int64_t audio_duration; | ||
66 | int frames; | ||
67 | int gop_size; | ||
68 | int64_t next_p_pts; | ||
69 | enum AVPictureType last_picture; | ||
70 | int skip_write; | ||
71 | int skip_write_audio; | ||
72 | int clear_duration; | ||
73 | int force_iobuf_size; | ||
74 | int do_interleave; | ||
75 | int fake_pkt_duration; | ||
76 | |||
77 | int num_warnings; | ||
78 | |||
79 | int check_faults; | ||
80 | |||
81 | |||
82 | 7 | static void count_warnings(void *avcl, int level, const char *fmt, va_list vl) | |
83 | { | ||
84 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
|
7 | if (level == AV_LOG_WARNING) |
85 | 5 | num_warnings++; | |
86 | 7 | } | |
87 | |||
88 | 2 | static void init_count_warnings(void) | |
89 | { | ||
90 | 2 | av_log_set_callback(count_warnings); | |
91 | 2 | num_warnings = 0; | |
92 | 2 | } | |
93 | |||
94 | 2 | static void reset_count_warnings(void) | |
95 | { | ||
96 | 2 | av_log_set_callback(av_log_default_callback); | |
97 | 2 | } | |
98 | |||
99 | 119 | static int io_write(void *opaque, const uint8_t *buf, int size) | |
100 | { | ||
101 | 119 | out_size += size; | |
102 | 119 | av_md5_update(md5, buf, size); | |
103 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
|
119 | if (out) |
104 | ✗ | fwrite(buf, 1, size, out); | |
105 | 119 | return size; | |
106 | } | ||
107 | |||
108 | 119 | static int io_write_data_type(void *opaque, const uint8_t *buf, int size, | |
109 | enum AVIODataMarkerType type, int64_t time) | ||
110 | { | ||
111 | 119 | char timebuf[30], content[5] = { 0 }; | |
112 | const char *str; | ||
113 |
5/6✓ Branch 0 taken 38 times.
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 27 times.
✗ Branch 5 not taken.
|
119 | switch (type) { |
114 | 38 | case AVIO_DATA_MARKER_HEADER: str = "header"; break; | |
115 | 45 | case AVIO_DATA_MARKER_SYNC_POINT: str = "sync"; break; | |
116 | 3 | case AVIO_DATA_MARKER_BOUNDARY_POINT: str = "boundary"; break; | |
117 | 6 | case AVIO_DATA_MARKER_UNKNOWN: str = "unknown"; break; | |
118 | 27 | case AVIO_DATA_MARKER_TRAILER: str = "trailer"; break; | |
119 | ✗ | default: str = "unknown"; break; | |
120 | } | ||
121 |
2/2✓ Branch 0 taken 71 times.
✓ Branch 1 taken 48 times.
|
119 | if (time == AV_NOPTS_VALUE) |
122 | 71 | snprintf(timebuf, sizeof(timebuf), "nopts"); | |
123 | else | ||
124 | 48 | snprintf(timebuf, sizeof(timebuf), "%"PRId64, time); | |
125 | // There can be multiple header/trailer callbacks, only log the box type | ||
126 | // for header at out_size == 0 | ||
127 |
4/4✓ Branch 0 taken 113 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 86 times.
✓ Branch 3 taken 27 times.
|
119 | if (type != AVIO_DATA_MARKER_UNKNOWN && |
128 |
2/2✓ Branch 0 taken 38 times.
✓ Branch 1 taken 48 times.
|
86 | type != AVIO_DATA_MARKER_TRAILER && |
129 |
3/4✓ Branch 0 taken 24 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
|
86 | (type != AVIO_DATA_MARKER_HEADER || out_size == 0) && |
130 | size >= 8) | ||
131 | 72 | memcpy(content, &buf[4], 4); | |
132 | else | ||
133 | 47 | snprintf(content, sizeof(content), "-"); | |
134 | 119 | printf("write_data len %d, time %s, type %s atom %s\n", size, timebuf, str, content); | |
135 | 119 | return io_write(opaque, buf, size); | |
136 | } | ||
137 | |||
138 | 34 | static void init_out(const char *name) | |
139 | { | ||
140 | char buf[100]; | ||
141 | 34 | cur_name = name; | |
142 | 34 | snprintf(buf, sizeof(buf), "%s.%s", cur_name, format); | |
143 | |||
144 | 34 | av_md5_init(md5); | |
145 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
|
34 | if (write_file) { |
146 | ✗ | out = fopen(buf, "wb"); | |
147 | ✗ | if (!out) | |
148 | ✗ | perror(buf); | |
149 | } | ||
150 | 34 | out_size = 0; | |
151 | 34 | } | |
152 | |||
153 | 34 | static void close_out(void) | |
154 | { | ||
155 | int i; | ||
156 | 34 | av_md5_final(md5, hash); | |
157 |
2/2✓ Branch 0 taken 544 times.
✓ Branch 1 taken 34 times.
|
578 | for (i = 0; i < HASH_SIZE; i++) |
158 | 544 | printf("%02x", hash[i]); | |
159 | 34 | printf(" %d %s\n", out_size, cur_name); | |
160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
|
34 | if (out) |
161 | ✗ | fclose(out); | |
162 | 34 | out = NULL; | |
163 | 34 | } | |
164 | |||
165 | 19 | static void check_func(int value, int line, const char *msg, ...) | |
166 | { | ||
167 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (!value) { |
168 | va_list ap; | ||
169 | ✗ | va_start(ap, msg); | |
170 | ✗ | printf("%d: ", line); | |
171 | ✗ | vprintf(msg, ap); | |
172 | ✗ | printf("\n"); | |
173 | ✗ | check_faults++; | |
174 | ✗ | va_end(ap); | |
175 | } | ||
176 | 19 | } | |
177 | #define check(value, ...) check_func(value, __LINE__, __VA_ARGS__) | ||
178 | |||
179 | 27 | static void init_fps(int bf, int audio_preroll, int fps) | |
180 | { | ||
181 | AVStream *st; | ||
182 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26 times.
|
27 | int iobuf_size = force_iobuf_size ? force_iobuf_size : sizeof(iobuf); |
183 | 27 | ctx = avformat_alloc_context(); | |
184 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (!ctx) |
185 | ✗ | exit(1); | |
186 | 27 | ctx->oformat = av_guess_format(format, NULL, NULL); | |
187 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (!ctx->oformat) |
188 | ✗ | exit(1); | |
189 | 27 | ctx->pb = avio_alloc_context(iobuf, iobuf_size, 1, NULL, NULL, io_write, NULL); | |
190 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (!ctx->pb) |
191 | ✗ | exit(1); | |
192 | 27 | ctx->pb->write_data_type = io_write_data_type; | |
193 | 27 | ctx->flags |= AVFMT_FLAG_BITEXACT; | |
194 | |||
195 | 27 | st = avformat_new_stream(ctx, NULL); | |
196 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (!st) |
197 | ✗ | exit(1); | |
198 | 27 | st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; | |
199 | 27 | st->codecpar->codec_id = AV_CODEC_ID_H264; | |
200 | 27 | st->codecpar->width = 640; | |
201 | 27 | st->codecpar->height = 480; | |
202 | 27 | st->time_base.num = 1; | |
203 | 27 | st->time_base.den = 30; | |
204 | 27 | st->codecpar->extradata_size = sizeof(h264_extradata); | |
205 | 27 | st->codecpar->extradata = av_mallocz(st->codecpar->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); | |
206 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (!st->codecpar->extradata) |
207 | ✗ | exit(1); | |
208 | 27 | memcpy(st->codecpar->extradata, h264_extradata, sizeof(h264_extradata)); | |
209 | 27 | video_st = st; | |
210 | |||
211 | 27 | st = avformat_new_stream(ctx, NULL); | |
212 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (!st) |
213 | ✗ | exit(1); | |
214 | 27 | st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; | |
215 | 27 | st->codecpar->codec_id = AV_CODEC_ID_AAC; | |
216 | 27 | st->codecpar->sample_rate = 44100; | |
217 | 27 | st->codecpar->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; | |
218 | 27 | st->time_base.num = 1; | |
219 | 27 | st->time_base.den = 44100; | |
220 | 27 | st->codecpar->extradata_size = sizeof(aac_extradata); | |
221 | 27 | st->codecpar->extradata = av_mallocz(st->codecpar->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE); | |
222 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (!st->codecpar->extradata) |
223 | ✗ | exit(1); | |
224 | 27 | memcpy(st->codecpar->extradata, aac_extradata, sizeof(aac_extradata)); | |
225 | 27 | audio_st = st; | |
226 | |||
227 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
|
27 | if (avformat_write_header(ctx, &opts) < 0) |
228 | ✗ | exit(1); | |
229 | 27 | av_dict_free(&opts); | |
230 | |||
231 | 27 | frames = 0; | |
232 | 27 | gop_size = 30; | |
233 | 27 | duration = video_st->time_base.den / fps; | |
234 | 27 | audio_duration = 1024LL * audio_st->time_base.den / audio_st->codecpar->sample_rate; | |
235 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 16 times.
|
27 | if (audio_preroll) |
236 | 11 | audio_preroll = 2048LL * audio_st->time_base.den / audio_st->codecpar->sample_rate; | |
237 | |||
238 | 27 | bframes = bf; | |
239 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 10 times.
|
27 | video_dts = bframes ? -duration : 0; |
240 | 27 | audio_dts = -audio_preroll; | |
241 | 27 | } | |
242 | |||
243 | 23 | static void init(int bf, int audio_preroll) | |
244 | { | ||
245 | 23 | init_fps(bf, audio_preroll, 30); | |
246 | 23 | } | |
247 | |||
248 | 44 | static void mux_frames(int n, int c) | |
249 | { | ||
250 | 44 | int end_frames = frames + n; | |
251 | 5107 | while (1) { | |
252 | 5151 | uint8_t pktdata[8] = { 0 }; | |
253 | 5151 | av_packet_unref(pkt); | |
254 | |||
255 |
2/2✓ Branch 1 taken 3487 times.
✓ Branch 2 taken 1664 times.
|
5151 | if (av_compare_ts(audio_dts, audio_st->time_base, video_dts, video_st->time_base) < 0) { |
256 | 3487 | pkt->dts = pkt->pts = audio_dts; | |
257 | 3487 | pkt->stream_index = 1; | |
258 | 3487 | pkt->duration = audio_duration; | |
259 | 3487 | audio_dts += audio_duration; | |
260 | } else { | ||
261 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 1620 times.
|
1664 | if (frames == end_frames) |
262 | 44 | break; | |
263 | 1620 | pkt->dts = video_dts; | |
264 | 1620 | pkt->stream_index = 0; | |
265 | 1620 | pkt->duration = duration; | |
266 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 1566 times.
|
1620 | if ((frames % gop_size) == 0) { |
267 | 54 | pkt->flags |= AV_PKT_FLAG_KEY; | |
268 | 54 | last_picture = AV_PICTURE_TYPE_I; | |
269 | 54 | pkt->pts = pkt->dts + duration; | |
270 | 54 | video_dts = pkt->pts; | |
271 | } else { | ||
272 |
2/2✓ Branch 0 taken 756 times.
✓ Branch 1 taken 810 times.
|
1566 | if (last_picture == AV_PICTURE_TYPE_P) { |
273 | 756 | last_picture = AV_PICTURE_TYPE_B; | |
274 | 756 | pkt->pts = pkt->dts; | |
275 | 756 | video_dts = next_p_pts; | |
276 | } else { | ||
277 | 810 | last_picture = AV_PICTURE_TYPE_P; | |
278 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 756 times.
|
810 | if (((frames + 1) % gop_size) == 0) { |
279 | 54 | pkt->pts = pkt->dts + duration; | |
280 | 54 | video_dts = pkt->pts; | |
281 | } else { | ||
282 | 756 | next_p_pts = pkt->pts = pkt->dts + 2 * duration; | |
283 | 756 | video_dts += duration; | |
284 | } | ||
285 | } | ||
286 | } | ||
287 |
2/2✓ Branch 0 taken 600 times.
✓ Branch 1 taken 1020 times.
|
1620 | if (!bframes) |
288 | 600 | pkt->pts = pkt->dts; | |
289 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1619 times.
|
1620 | if (fake_pkt_duration) |
290 | 1 | pkt->duration = fake_pkt_duration; | |
291 | 1620 | frames++; | |
292 | } | ||
293 | |||
294 |
2/2✓ Branch 0 taken 328 times.
✓ Branch 1 taken 4779 times.
|
5107 | if (clear_duration) |
295 | 328 | pkt->duration = 0; | |
296 | 5107 | AV_WB32(pktdata + 4, pkt->pts); | |
297 | 5107 | pkt->data = pktdata; | |
298 | 5107 | pkt->size = 8; | |
299 |
2/2✓ Branch 0 taken 368 times.
✓ Branch 1 taken 4739 times.
|
5107 | if (skip_write) |
300 | 542 | continue; | |
301 |
4/4✓ Branch 0 taken 294 times.
✓ Branch 1 taken 4445 times.
✓ Branch 2 taken 174 times.
✓ Branch 3 taken 120 times.
|
4739 | if (skip_write_audio && pkt->stream_index == 1) |
302 | 174 | continue; | |
303 | |||
304 |
2/2✓ Branch 0 taken 73 times.
✓ Branch 1 taken 4492 times.
|
4565 | if (c) { |
305 | 73 | pkt->pts += (1LL<<32); | |
306 | 73 | pkt->dts += (1LL<<32); | |
307 | } | ||
308 | |||
309 |
2/2✓ Branch 0 taken 173 times.
✓ Branch 1 taken 4392 times.
|
4565 | if (do_interleave) |
310 | 173 | av_interleaved_write_frame(ctx, pkt); | |
311 | else | ||
312 | 4392 | av_write_frame(ctx, pkt); | |
313 | } | ||
314 | 44 | } | |
315 | |||
316 | 36 | static void mux_gops(int n) | |
317 | { | ||
318 | 36 | mux_frames(gop_size * n, 0); | |
319 | 36 | } | |
320 | |||
321 | 5 | static void skip_gops(int n) | |
322 | { | ||
323 | 5 | skip_write = 1; | |
324 | 5 | mux_gops(n); | |
325 | 5 | skip_write = 0; | |
326 | 5 | } | |
327 | |||
328 | 2 | static void signal_init_ts(void) | |
329 | { | ||
330 | 2 | av_packet_unref(pkt); | |
331 | |||
332 | 2 | pkt->stream_index = 0; | |
333 | 2 | pkt->dts = video_dts; | |
334 | 2 | pkt->pts = 0; | |
335 | 2 | av_write_frame(ctx, pkt); | |
336 | |||
337 | 2 | pkt->stream_index = 1; | |
338 | 2 | pkt->dts = pkt->pts = audio_dts; | |
339 | 2 | av_write_frame(ctx, pkt); | |
340 | 2 | } | |
341 | |||
342 | 27 | static void finish(void) | |
343 | { | ||
344 | 27 | av_write_trailer(ctx); | |
345 | 27 | avio_context_free(&ctx->pb); | |
346 | 27 | avformat_free_context(ctx); | |
347 | 27 | ctx = NULL; | |
348 | 27 | } | |
349 | |||
350 | ✗ | static void help(void) | |
351 | { | ||
352 | ✗ | printf("movenc-test [-w]\n" | |
353 | "-w write output into files\n"); | ||
354 | ✗ | } | |
355 | |||
356 | 1 | int main(int argc, char **argv) | |
357 | { | ||
358 | int c; | ||
359 | uint8_t header[HASH_SIZE]; | ||
360 | uint8_t content[HASH_SIZE]; | ||
361 | int empty_moov_pos; | ||
362 | int prev_pos; | ||
363 | |||
364 | for (;;) { | ||
365 | 1 | c = getopt(argc, argv, "wh"); | |
366 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (c == -1) |
367 | 1 | break; | |
368 | ✗ | switch (c) { | |
369 | ✗ | case 'w': | |
370 | ✗ | write_file = 1; | |
371 | ✗ | break; | |
372 | ✗ | default: | |
373 | case 'h': | ||
374 | ✗ | help(); | |
375 | ✗ | return 0; | |
376 | } | ||
377 | } | ||
378 | |||
379 | 1 | md5 = av_md5_alloc(); | |
380 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!md5) |
381 | ✗ | return 1; | |
382 | 1 | pkt = av_packet_alloc(); | |
383 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!pkt) { |
384 | ✗ | av_free(md5); | |
385 | ✗ | return 1; | |
386 | } | ||
387 | |||
388 | // Write a fragmented file with an initial moov that actually contains some | ||
389 | // samples. One moov+mdat with 1 second of data and one moof+mdat with 1 | ||
390 | // second of data. | ||
391 | 1 | init_out("non-empty-moov"); | |
392 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe", 0); | |
393 | 1 | init(0, 0); | |
394 | 1 | mux_gops(2); | |
395 | 1 | finish(); | |
396 | 1 | close_out(); | |
397 | |||
398 | // Write a similar file, but with B-frames and audio preroll, handled | ||
399 | // via an edit list. | ||
400 | 1 | init_out("non-empty-moov-elst"); | |
401 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe", 0); | |
402 | 1 | av_dict_set(&opts, "use_editlist", "1", 0); | |
403 | 1 | init(1, 1); | |
404 | 1 | mux_gops(2); | |
405 | 1 | finish(); | |
406 | 1 | close_out(); | |
407 | |||
408 | // Use B-frames but no audio-preroll, but without an edit list. | ||
409 | // Due to avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO, the dts | ||
410 | // of the first audio packet is > 0, but it is set to zero since edit | ||
411 | // lists aren't used, increasing the duration of the first packet instead. | ||
412 | 1 | init_out("non-empty-moov-no-elst"); | |
413 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe", 0); | |
414 | 1 | av_dict_set(&opts, "use_editlist", "0", 0); | |
415 | 1 | init(1, 0); | |
416 | 1 | mux_gops(2); | |
417 | 1 | finish(); | |
418 | 1 | close_out(); | |
419 | |||
420 | 1 | format = "ismv"; | |
421 | // Write an ISMV, with B-frames and audio preroll. | ||
422 | 1 | init_out("ismv"); | |
423 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe", 0); | |
424 | 1 | init(1, 1); | |
425 | 1 | mux_gops(2); | |
426 | 1 | finish(); | |
427 | 1 | close_out(); | |
428 | 1 | format = "mp4"; | |
429 | |||
430 | // An initial moov that doesn't contain any samples, followed by two | ||
431 | // moof+mdat pairs. | ||
432 | 1 | init_out("empty-moov"); | |
433 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+empty_moov", 0); | |
434 | 1 | av_dict_set(&opts, "use_editlist", "0", 0); | |
435 | 1 | init(0, 0); | |
436 | 1 | mux_gops(2); | |
437 | 1 | finish(); | |
438 | 1 | close_out(); | |
439 | 1 | memcpy(content, hash, HASH_SIZE); | |
440 | |||
441 | // Similar to the previous one, but with input that doesn't start at | ||
442 | // pts/dts 0. avoid_negative_ts behaves in the same way as | ||
443 | // in non-empty-moov-no-elst above. | ||
444 | 1 | init_out("empty-moov-no-elst"); | |
445 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+empty_moov", 0); | |
446 | 1 | init(1, 0); | |
447 | 1 | mux_gops(2); | |
448 | 1 | finish(); | |
449 | 1 | close_out(); | |
450 | |||
451 | // Same as the previous one, but disable avoid_negative_ts (which | ||
452 | // would require using an edit list, but with empty_moov, one can't | ||
453 | // write a sensible edit list, when the start timestamps aren't known). | ||
454 | // This should trigger a warning - we check that the warning is produced. | ||
455 | 1 | init_count_warnings(); | |
456 | 1 | init_out("empty-moov-no-elst-no-adjust"); | |
457 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+empty_moov", 0); | |
458 | 1 | av_dict_set(&opts, "avoid_negative_ts", "disabled", 0); | |
459 | 1 | init(1, 0); | |
460 | 1 | mux_gops(2); | |
461 | 1 | finish(); | |
462 | 1 | close_out(); | |
463 | |||
464 | 1 | reset_count_warnings(); | |
465 | 1 | check(num_warnings > 0, "No warnings printed for unhandled start offset"); | |
466 | |||
467 | // Verify that delay_moov produces the same as empty_moov for | ||
468 | // simple input | ||
469 | 1 | init_out("delay-moov"); | |
470 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0); | |
471 | 1 | av_dict_set(&opts, "use_editlist", "0", 0); | |
472 | 1 | init(0, 0); | |
473 | 1 | mux_gops(2); | |
474 | 1 | finish(); | |
475 | 1 | close_out(); | |
476 | 1 | check(!memcmp(hash, content, HASH_SIZE), "delay_moov differs from empty_moov"); | |
477 | |||
478 | // Test writing content that requires an edit list using delay_moov | ||
479 | 1 | init_out("delay-moov-elst"); | |
480 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0); | |
481 | 1 | init(1, 1); | |
482 | 1 | mux_gops(2); | |
483 | 1 | finish(); | |
484 | 1 | close_out(); | |
485 | |||
486 | // Test writing a file with one track lacking packets, with delay_moov. | ||
487 | 1 | skip_write_audio = 1; | |
488 | 1 | init_out("delay-moov-empty-track"); | |
489 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0); | |
490 | 1 | init(0, 0); | |
491 | 1 | mux_gops(2); | |
492 | // The automatic flushing shouldn't output anything, since we're still | ||
493 | // waiting for data for some tracks | ||
494 | 1 | check(out_size == 0, "delay_moov flushed prematurely"); | |
495 | // When closed (or manually flushed), all the written data should still | ||
496 | // be output. | ||
497 | 1 | finish(); | |
498 | 1 | close_out(); | |
499 | 1 | check(out_size > 0, "delay_moov didn't output anything"); | |
500 | |||
501 | // Check that manually flushing still outputs things as expected. This | ||
502 | // produces two fragments, while the one above produces only one. | ||
503 | 1 | init_out("delay-moov-empty-track-flush"); | |
504 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov", 0); | |
505 | 1 | init(0, 0); | |
506 | 1 | mux_gops(1); | |
507 | 1 | av_write_frame(ctx, NULL); // Force writing the moov | |
508 | 1 | check(out_size > 0, "No moov written"); | |
509 | 1 | av_write_frame(ctx, NULL); | |
510 | 1 | mux_gops(1); | |
511 | 1 | av_write_frame(ctx, NULL); | |
512 | 1 | finish(); | |
513 | 1 | close_out(); | |
514 | |||
515 | 1 | skip_write_audio = 0; | |
516 | |||
517 | |||
518 | |||
519 | // Verify that the header written by delay_moov when manually flushed | ||
520 | // is identical to the one by empty_moov. | ||
521 | 1 | init_out("empty-moov-header"); | |
522 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+empty_moov", 0); | |
523 | 1 | av_dict_set(&opts, "use_editlist", "0", 0); | |
524 | 1 | init(0, 0); | |
525 | 1 | close_out(); | |
526 | 1 | memcpy(header, hash, HASH_SIZE); | |
527 | 1 | init_out("empty-moov-content"); | |
528 | 1 | mux_gops(2); | |
529 | // Written 2 seconds of content, with an automatic flush after 1 second. | ||
530 | 1 | check(out_size > 0, "No automatic flush?"); | |
531 | 1 | empty_moov_pos = prev_pos = out_size; | |
532 | // Manually flush the second fragment | ||
533 | 1 | av_write_frame(ctx, NULL); | |
534 | 1 | check(out_size > prev_pos, "No second fragment flushed?"); | |
535 | 1 | prev_pos = out_size; | |
536 | // Check that an extra flush doesn't output any more data | ||
537 | 1 | av_write_frame(ctx, NULL); | |
538 | 1 | check(out_size == prev_pos, "More data written?"); | |
539 | 1 | close_out(); | |
540 | 1 | memcpy(content, hash, HASH_SIZE); | |
541 | // Ignore the trailer written here | ||
542 | 1 | finish(); | |
543 | |||
544 | 1 | init_out("delay-moov-header"); | |
545 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov", 0); | |
546 | 1 | av_dict_set(&opts, "use_editlist", "0", 0); | |
547 | 1 | init(0, 0); | |
548 | 1 | check(out_size == 0, "Output written during init with delay_moov"); | |
549 | 1 | mux_gops(1); // Write 1 second of content | |
550 | 1 | av_write_frame(ctx, NULL); // Force writing the moov | |
551 | 1 | close_out(); | |
552 | 1 | check(!memcmp(hash, header, HASH_SIZE), "delay_moov header differs from empty_moov"); | |
553 | 1 | init_out("delay-moov-content"); | |
554 | 1 | av_write_frame(ctx, NULL); // Flush the first fragment | |
555 | 1 | check(out_size == empty_moov_pos, "Manually flushed content differs from automatically flushed, %d vs %d", out_size, empty_moov_pos); | |
556 | 1 | mux_gops(1); // Write the rest of the content | |
557 | 1 | av_write_frame(ctx, NULL); // Flush the second fragment | |
558 | 1 | close_out(); | |
559 | 1 | check(!memcmp(hash, content, HASH_SIZE), "delay_moov content differs from empty_moov"); | |
560 | 1 | finish(); | |
561 | |||
562 | |||
563 | // Verify that we can produce an identical second fragment without | ||
564 | // writing the first one. First write the reference fragments that | ||
565 | // we want to reproduce. | ||
566 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+empty_moov+dash", 0); | |
567 | 1 | init(0, 0); | |
568 | 1 | mux_gops(1); | |
569 | 1 | av_write_frame(ctx, NULL); // Output the first fragment | |
570 | 1 | init_out("empty-moov-second-frag"); | |
571 | 1 | mux_gops(1); | |
572 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
573 | 1 | close_out(); | |
574 | 1 | memcpy(content, hash, HASH_SIZE); | |
575 | 1 | finish(); | |
576 | |||
577 | // Produce the same second fragment without actually writing the first | ||
578 | // one before. | ||
579 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+empty_moov+dash+frag_discont", 0); | |
580 | 1 | av_dict_set(&opts, "fragment_index", "2", 0); | |
581 | 1 | av_dict_set(&opts, "avoid_negative_ts", "disabled", 0); | |
582 | 1 | av_dict_set(&opts, "use_editlist", "0", 0); | |
583 | 1 | init(0, 0); | |
584 | 1 | skip_gops(1); | |
585 | 1 | init_out("empty-moov-second-frag-discont"); | |
586 | 1 | mux_gops(1); | |
587 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
588 | 1 | close_out(); | |
589 | 1 | check(!memcmp(hash, content, HASH_SIZE), "discontinuously written fragment differs"); | |
590 | 1 | finish(); | |
591 | |||
592 | // Produce the same thing by using delay_moov, which requires a slightly | ||
593 | // different call sequence. | ||
594 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov+dash+frag_discont", 0); | |
595 | 1 | av_dict_set(&opts, "fragment_index", "2", 0); | |
596 | 1 | init(0, 0); | |
597 | 1 | skip_gops(1); | |
598 | 1 | mux_gops(1); | |
599 | 1 | av_write_frame(ctx, NULL); // Output the moov | |
600 | 1 | init_out("delay-moov-second-frag-discont"); | |
601 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
602 | 1 | close_out(); | |
603 | 1 | check(!memcmp(hash, content, HASH_SIZE), "discontinuously written fragment differs"); | |
604 | 1 | finish(); | |
605 | |||
606 | |||
607 | // Test discontinuously written fragments with B-frames (where the | ||
608 | // assumption of starting at pts=0 works) but not with audio preroll | ||
609 | // (which can't be guessed). | ||
610 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov+dash", 0); | |
611 | 1 | init(1, 0); | |
612 | 1 | mux_gops(1); | |
613 | 1 | init_out("delay-moov-elst-init"); | |
614 | 1 | av_write_frame(ctx, NULL); // Output the moov | |
615 | 1 | close_out(); | |
616 | 1 | memcpy(header, hash, HASH_SIZE); | |
617 | 1 | av_write_frame(ctx, NULL); // Output the first fragment | |
618 | 1 | init_out("delay-moov-elst-second-frag"); | |
619 | 1 | mux_gops(1); | |
620 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
621 | 1 | close_out(); | |
622 | 1 | memcpy(content, hash, HASH_SIZE); | |
623 | 1 | finish(); | |
624 | |||
625 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov+dash+frag_discont", 0); | |
626 | 1 | av_dict_set(&opts, "fragment_index", "2", 0); | |
627 | 1 | init(1, 0); | |
628 | 1 | skip_gops(1); | |
629 | 1 | mux_gops(1); // Write the second fragment | |
630 | 1 | init_out("delay-moov-elst-init-discont"); | |
631 | 1 | av_write_frame(ctx, NULL); // Output the moov | |
632 | 1 | close_out(); | |
633 | 1 | check(!memcmp(hash, header, HASH_SIZE), "discontinuously written header differs"); | |
634 | 1 | init_out("delay-moov-elst-second-frag-discont"); | |
635 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
636 | 1 | close_out(); | |
637 | 1 | check(!memcmp(hash, content, HASH_SIZE), "discontinuously written fragment differs"); | |
638 | 1 | finish(); | |
639 | |||
640 | |||
641 | // Test discontinuously written fragments with B-frames and audio preroll, | ||
642 | // properly signaled. | ||
643 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov+dash", 0); | |
644 | 1 | init(1, 1); | |
645 | 1 | mux_gops(1); | |
646 | 1 | init_out("delay-moov-elst-signal-init"); | |
647 | 1 | av_write_frame(ctx, NULL); // Output the moov | |
648 | 1 | close_out(); | |
649 | 1 | memcpy(header, hash, HASH_SIZE); | |
650 | 1 | av_write_frame(ctx, NULL); // Output the first fragment | |
651 | 1 | init_out("delay-moov-elst-signal-second-frag"); | |
652 | 1 | mux_gops(1); | |
653 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
654 | 1 | close_out(); | |
655 | 1 | memcpy(content, hash, HASH_SIZE); | |
656 | 1 | finish(); | |
657 | |||
658 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov+dash+frag_discont", 0); | |
659 | 1 | av_dict_set(&opts, "fragment_index", "2", 0); | |
660 | 1 | init(1, 1); | |
661 | 1 | signal_init_ts(); | |
662 | 1 | skip_gops(1); | |
663 | 1 | mux_gops(1); // Write the second fragment | |
664 | 1 | init_out("delay-moov-elst-signal-init-discont"); | |
665 | 1 | av_write_frame(ctx, NULL); // Output the moov | |
666 | 1 | close_out(); | |
667 | 1 | check(!memcmp(hash, header, HASH_SIZE), "discontinuously written header differs"); | |
668 | 1 | init_out("delay-moov-elst-signal-second-frag-discont"); | |
669 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
670 | 1 | close_out(); | |
671 | 1 | check(!memcmp(hash, content, HASH_SIZE), "discontinuously written fragment differs"); | |
672 | 1 | finish(); | |
673 | |||
674 | |||
675 | // Test muxing discontinuous fragments with very large (> (1<<31)) timestamps. | ||
676 | 1 | av_dict_set(&opts, "movflags", "+frag_custom+delay_moov+dash+frag_discont", 0); | |
677 | 1 | av_dict_set(&opts, "fragment_index", "2", 0); | |
678 | 1 | init(1, 1); | |
679 | 1 | signal_init_ts(); | |
680 | 1 | skip_gops(1); | |
681 | 1 | mux_frames(gop_size, 1); // Write the second fragment | |
682 | 1 | init_out("delay-moov-elst-signal-init-discont-largets"); | |
683 | 1 | av_write_frame(ctx, NULL); // Output the moov | |
684 | 1 | close_out(); | |
685 | 1 | init_out("delay-moov-elst-signal-second-frag-discont-largets"); | |
686 | 1 | av_write_frame(ctx, NULL); // Output the second fragment | |
687 | 1 | close_out(); | |
688 | 1 | finish(); | |
689 | |||
690 | // Test VFR content, with sidx atoms (which declare the pts duration | ||
691 | // of a fragment, forcing overriding the start pts of the next one). | ||
692 | // Here, the fragment duration in pts is significantly different from | ||
693 | // the duration in dts. The video stream starts at dts=-10,pts=0, and | ||
694 | // the second fragment starts at dts=155,pts=156. The trun duration sum | ||
695 | // of the first fragment is 165, which also is written as | ||
696 | // baseMediaDecodeTime in the tfdt in the second fragment. The sidx for | ||
697 | // the first fragment says earliest_presentation_time = 0 and | ||
698 | // subsegment_duration = 156, which also matches the sidx in the second | ||
699 | // fragment. For the audio stream, the pts and dts durations also don't | ||
700 | // match - the input stream starts at pts=-2048, but that part is excluded | ||
701 | // by the edit list. | ||
702 | 1 | init_out("vfr"); | |
703 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0); | |
704 | 1 | init_fps(1, 1, 3); | |
705 | 1 | mux_frames(gop_size/2, 0); | |
706 | 1 | duration /= 10; | |
707 | 1 | mux_frames(gop_size/2, 0); | |
708 | 1 | mux_gops(1); | |
709 | 1 | finish(); | |
710 | 1 | close_out(); | |
711 | |||
712 | // Test VFR content, with cleared duration fields. In these cases, | ||
713 | // the muxer must guess the duration of the last packet of each | ||
714 | // fragment. As long as the framerate doesn't vary (too much) at the | ||
715 | // fragment edge, it works just fine. Additionally, when automatically | ||
716 | // cutting fragments, the muxer already know the timestamps of the next | ||
717 | // packet for one stream (in most cases the video stream), avoiding | ||
718 | // having to use guesses for that one. | ||
719 | 1 | init_count_warnings(); | |
720 | 1 | clear_duration = 1; | |
721 | 1 | init_out("vfr-noduration"); | |
722 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0); | |
723 | 1 | init_fps(1, 1, 3); | |
724 | 1 | mux_frames(gop_size/2, 0); | |
725 | 1 | duration /= 10; | |
726 | 1 | mux_frames(gop_size/2, 0); | |
727 | 1 | mux_gops(1); | |
728 | 1 | finish(); | |
729 | 1 | close_out(); | |
730 | 1 | clear_duration = 0; | |
731 | 1 | reset_count_warnings(); | |
732 | 1 | check(num_warnings > 0, "No warnings printed for filled in durations"); | |
733 | |||
734 | // Test with an IO buffer size that is too small to hold a full fragment; | ||
735 | // this will cause write_data_type to be called with the type unknown. | ||
736 | 1 | force_iobuf_size = 1500; | |
737 | 1 | init_out("large_frag"); | |
738 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0); | |
739 | 1 | init_fps(1, 1, 3); | |
740 | 1 | mux_gops(2); | |
741 | 1 | finish(); | |
742 | 1 | close_out(); | |
743 | 1 | force_iobuf_size = 0; | |
744 | |||
745 | // Test VFR content with bframes with interleaving. | ||
746 | // Here, using av_interleaved_write_frame allows the muxer to get the | ||
747 | // fragment end durations right. We always set the packet duration to | ||
748 | // the expected, but we simulate dropped frames at one point. | ||
749 | 1 | do_interleave = 1; | |
750 | 1 | init_out("vfr-noduration-interleave"); | |
751 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0); | |
752 | 1 | av_dict_set(&opts, "frag_duration", "650000", 0); | |
753 | 1 | init_fps(1, 1, 30); | |
754 | 1 | mux_frames(gop_size/2, 0); | |
755 | // Pretend that the packet duration is the normal, even if | ||
756 | // we actually skip a bunch of frames. (I.e., simulate that | ||
757 | // we don't know of the framedrop in advance.) | ||
758 | 1 | fake_pkt_duration = duration; | |
759 | 1 | duration *= 10; | |
760 | 1 | mux_frames(1, 0); | |
761 | 1 | fake_pkt_duration = 0; | |
762 | 1 | duration /= 10; | |
763 | 1 | mux_frames(gop_size/2 - 1, 0); | |
764 | 1 | mux_gops(1); | |
765 | 1 | finish(); | |
766 | 1 | close_out(); | |
767 | 1 | clear_duration = 0; | |
768 | 1 | do_interleave = 0; | |
769 | |||
770 | // Write a fragmented file with b-frames and audio preroll, | ||
771 | // with negative cts values, removing the edit list for the | ||
772 | // video track. | ||
773 | 1 | init_out("delay-moov-elst-neg-cts"); | |
774 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+negative_cts_offsets", 0); | |
775 | 1 | init(1, 1); | |
776 | 1 | mux_gops(2); | |
777 | 1 | finish(); | |
778 | 1 | close_out(); | |
779 | |||
780 | // Write a fragmented file with b-frames without audio preroll, | ||
781 | // with negative cts values, avoiding any edit lists, allowing | ||
782 | // to use empty_moov instead of delay_moov. | ||
783 | 1 | init_out("empty-moov-neg-cts"); | |
784 | 1 | av_dict_set(&opts, "movflags", "+frag_keyframe+empty_moov+negative_cts_offsets", 0); | |
785 | 1 | init(1, 0); | |
786 | 1 | mux_gops(2); | |
787 | 1 | finish(); | |
788 | 1 | close_out(); | |
789 | |||
790 | 1 | av_free(md5); | |
791 | 1 | av_packet_free(&pkt); | |
792 | |||
793 | 1 | return check_faults > 0 ? 1 : 0; | |
794 | } | ||
795 |