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