Commit | Line | Data |
---|---|---|
f6fa7814 DM |
1 | /* |
2 | * MPEG-DASH ISO BMFF segmenter | |
3 | * Copyright (c) 2014 Martin Storsjo | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * FFmpeg is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | #include "config.h" | |
23 | #if HAVE_UNISTD_H | |
24 | #include <unistd.h> | |
25 | #endif | |
26 | ||
27 | #include "libavutil/avstring.h" | |
28 | #include "libavutil/intreadwrite.h" | |
29 | #include "libavutil/mathematics.h" | |
30 | #include "libavutil/opt.h" | |
31 | #include "libavutil/time_internal.h" | |
32 | ||
33 | #include "avc.h" | |
34 | #include "avformat.h" | |
35 | #include "avio_internal.h" | |
36 | #include "internal.h" | |
37 | #include "isom.h" | |
38 | #include "os_support.h" | |
39 | #include "url.h" | |
40 | ||
41 | // See ISO/IEC 23009-1:2014 5.3.9.4.4 | |
42 | typedef enum { | |
43 | DASH_TMPL_ID_UNDEFINED = -1, | |
44 | DASH_TMPL_ID_ESCAPE, | |
45 | DASH_TMPL_ID_REP_ID, | |
46 | DASH_TMPL_ID_NUMBER, | |
47 | DASH_TMPL_ID_BANDWIDTH, | |
48 | DASH_TMPL_ID_TIME, | |
49 | } DASHTmplId; | |
50 | ||
51 | typedef struct Segment { | |
52 | char file[1024]; | |
53 | int64_t start_pos; | |
54 | int range_length, index_length; | |
55 | int64_t time; | |
56 | int duration; | |
57 | int n; | |
58 | } Segment; | |
59 | ||
60 | typedef struct OutputStream { | |
61 | AVFormatContext *ctx; | |
62 | int ctx_inited; | |
63 | uint8_t iobuf[32768]; | |
64 | URLContext *out; | |
65 | int packets_written; | |
66 | char initfile[1024]; | |
67 | int64_t init_start_pos; | |
68 | int init_range_length; | |
69 | int nb_segments, segments_size, segment_index; | |
70 | Segment **segments; | |
71 | int64_t first_dts, start_dts, end_dts; | |
72 | int bit_rate; | |
73 | char bandwidth_str[64]; | |
74 | ||
75 | char codec_str[100]; | |
76 | } OutputStream; | |
77 | ||
78 | typedef struct DASHContext { | |
79 | const AVClass *class; /* Class for private options. */ | |
80 | int window_size; | |
81 | int extra_window_size; | |
82 | int min_seg_duration; | |
83 | int remove_at_exit; | |
84 | int use_template; | |
85 | int use_timeline; | |
86 | int single_file; | |
87 | OutputStream *streams; | |
88 | int has_video, has_audio; | |
89 | int last_duration; | |
90 | int total_duration; | |
91 | char availability_start_time[100]; | |
92 | char dirname[1024]; | |
93 | const char *single_file_name; | |
94 | const char *init_seg_name; | |
95 | const char *media_seg_name; | |
96 | } DASHContext; | |
97 | ||
98 | static int dash_write(void *opaque, uint8_t *buf, int buf_size) | |
99 | { | |
100 | OutputStream *os = opaque; | |
101 | if (os->out) | |
102 | ffurl_write(os->out, buf, buf_size); | |
103 | return buf_size; | |
104 | } | |
105 | ||
106 | // RFC 6381 | |
107 | static void set_codec_str(AVFormatContext *s, AVCodecContext *codec, | |
108 | char *str, int size) | |
109 | { | |
110 | const AVCodecTag *tags[2] = { NULL, NULL }; | |
111 | uint32_t tag; | |
112 | if (codec->codec_type == AVMEDIA_TYPE_VIDEO) | |
113 | tags[0] = ff_codec_movvideo_tags; | |
114 | else if (codec->codec_type == AVMEDIA_TYPE_AUDIO) | |
115 | tags[0] = ff_codec_movaudio_tags; | |
116 | else | |
117 | return; | |
118 | ||
119 | tag = av_codec_get_tag(tags, codec->codec_id); | |
120 | if (!tag) | |
121 | return; | |
122 | if (size < 5) | |
123 | return; | |
124 | ||
125 | AV_WL32(str, tag); | |
126 | str[4] = '\0'; | |
127 | if (!strcmp(str, "mp4a") || !strcmp(str, "mp4v")) { | |
128 | uint32_t oti; | |
129 | tags[0] = ff_mp4_obj_type; | |
130 | oti = av_codec_get_tag(tags, codec->codec_id); | |
131 | if (oti) | |
132 | av_strlcatf(str, size, ".%02x", oti); | |
133 | else | |
134 | return; | |
135 | ||
136 | if (tag == MKTAG('m', 'p', '4', 'a')) { | |
137 | if (codec->extradata_size >= 2) { | |
138 | int aot = codec->extradata[0] >> 3; | |
139 | if (aot == 31) | |
140 | aot = ((AV_RB16(codec->extradata) >> 5) & 0x3f) + 32; | |
141 | av_strlcatf(str, size, ".%d", aot); | |
142 | } | |
143 | } else if (tag == MKTAG('m', 'p', '4', 'v')) { | |
144 | // Unimplemented, should output ProfileLevelIndication as a decimal number | |
145 | av_log(s, AV_LOG_WARNING, "Incomplete RFC 6381 codec string for mp4v\n"); | |
146 | } | |
147 | } else if (!strcmp(str, "avc1")) { | |
148 | uint8_t *tmpbuf = NULL; | |
149 | uint8_t *extradata = codec->extradata; | |
150 | int extradata_size = codec->extradata_size; | |
151 | if (!extradata_size) | |
152 | return; | |
153 | if (extradata[0] != 1) { | |
154 | AVIOContext *pb; | |
155 | if (avio_open_dyn_buf(&pb) < 0) | |
156 | return; | |
157 | if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) { | |
158 | avio_close_dyn_buf(pb, &tmpbuf); | |
159 | av_free(tmpbuf); | |
160 | return; | |
161 | } | |
162 | extradata_size = avio_close_dyn_buf(pb, &extradata); | |
163 | tmpbuf = extradata; | |
164 | } | |
165 | ||
166 | if (extradata_size >= 4) | |
167 | av_strlcatf(str, size, ".%02x%02x%02x", | |
168 | extradata[1], extradata[2], extradata[3]); | |
169 | av_free(tmpbuf); | |
170 | } | |
171 | } | |
172 | ||
173 | static void dash_free(AVFormatContext *s) | |
174 | { | |
175 | DASHContext *c = s->priv_data; | |
176 | int i, j; | |
177 | if (!c->streams) | |
178 | return; | |
179 | for (i = 0; i < s->nb_streams; i++) { | |
180 | OutputStream *os = &c->streams[i]; | |
181 | if (os->ctx && os->ctx_inited) | |
182 | av_write_trailer(os->ctx); | |
183 | if (os->ctx && os->ctx->pb) | |
184 | av_free(os->ctx->pb); | |
185 | ffurl_close(os->out); | |
186 | os->out = NULL; | |
187 | if (os->ctx) | |
188 | avformat_free_context(os->ctx); | |
189 | for (j = 0; j < os->nb_segments; j++) | |
190 | av_free(os->segments[j]); | |
191 | av_free(os->segments); | |
192 | } | |
193 | av_freep(&c->streams); | |
194 | } | |
195 | ||
196 | static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c) | |
197 | { | |
198 | int i, start_index = 0, start_number = 1; | |
199 | if (c->window_size) { | |
200 | start_index = FFMAX(os->nb_segments - c->window_size, 0); | |
201 | start_number = FFMAX(os->segment_index - c->window_size, 1); | |
202 | } | |
203 | ||
204 | if (c->use_template) { | |
205 | int timescale = c->use_timeline ? os->ctx->streams[0]->time_base.den : AV_TIME_BASE; | |
206 | avio_printf(out, "\t\t\t\t<SegmentTemplate timescale=\"%d\" ", timescale); | |
207 | if (!c->use_timeline) | |
208 | avio_printf(out, "duration=\"%d\" ", c->last_duration); | |
209 | avio_printf(out, "initialization=\"%s\" media=\"%s\" startNumber=\"%d\">\n", c->init_seg_name, c->media_seg_name, c->use_timeline ? start_number : 1); | |
210 | if (c->use_timeline) { | |
211 | avio_printf(out, "\t\t\t\t\t<SegmentTimeline>\n"); | |
212 | for (i = start_index; i < os->nb_segments; ) { | |
213 | Segment *seg = os->segments[i]; | |
214 | int repeat = 0; | |
215 | avio_printf(out, "\t\t\t\t\t\t<S "); | |
216 | if (i == start_index) | |
217 | avio_printf(out, "t=\"%"PRId64"\" ", seg->time); | |
218 | avio_printf(out, "d=\"%d\" ", seg->duration); | |
219 | while (i + repeat + 1 < os->nb_segments && os->segments[i + repeat + 1]->duration == seg->duration) | |
220 | repeat++; | |
221 | if (repeat > 0) | |
222 | avio_printf(out, "r=\"%d\" ", repeat); | |
223 | avio_printf(out, "/>\n"); | |
224 | i += 1 + repeat; | |
225 | } | |
226 | avio_printf(out, "\t\t\t\t\t</SegmentTimeline>\n"); | |
227 | } | |
228 | avio_printf(out, "\t\t\t\t</SegmentTemplate>\n"); | |
229 | } else if (c->single_file) { | |
230 | avio_printf(out, "\t\t\t\t<BaseURL>%s</BaseURL>\n", os->initfile); | |
231 | avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number); | |
232 | avio_printf(out, "\t\t\t\t\t<Initialization range=\"%"PRId64"-%"PRId64"\" />\n", os->init_start_pos, os->init_start_pos + os->init_range_length - 1); | |
233 | for (i = start_index; i < os->nb_segments; i++) { | |
234 | Segment *seg = os->segments[i]; | |
235 | avio_printf(out, "\t\t\t\t\t<SegmentURL mediaRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->range_length - 1); | |
236 | if (seg->index_length) | |
237 | avio_printf(out, "indexRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->index_length - 1); | |
238 | avio_printf(out, "/>\n"); | |
239 | } | |
240 | avio_printf(out, "\t\t\t\t</SegmentList>\n"); | |
241 | } else { | |
242 | avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number); | |
243 | avio_printf(out, "\t\t\t\t\t<Initialization sourceURL=\"%s\" />\n", os->initfile); | |
244 | for (i = start_index; i < os->nb_segments; i++) { | |
245 | Segment *seg = os->segments[i]; | |
246 | avio_printf(out, "\t\t\t\t\t<SegmentURL media=\"%s\" />\n", seg->file); | |
247 | } | |
248 | avio_printf(out, "\t\t\t\t</SegmentList>\n"); | |
249 | } | |
250 | } | |
251 | ||
252 | static DASHTmplId dash_read_tmpl_id(const char *identifier, char *format_tag, | |
253 | size_t format_tag_size, const char **ptr) { | |
254 | const char *next_ptr; | |
255 | DASHTmplId id_type = DASH_TMPL_ID_UNDEFINED; | |
256 | ||
257 | if (av_strstart(identifier, "$$", &next_ptr)) { | |
258 | id_type = DASH_TMPL_ID_ESCAPE; | |
259 | *ptr = next_ptr; | |
260 | } else if (av_strstart(identifier, "$RepresentationID$", &next_ptr)) { | |
261 | id_type = DASH_TMPL_ID_REP_ID; | |
262 | // default to basic format, as $RepresentationID$ identifiers | |
263 | // are not allowed to have custom format-tags. | |
264 | av_strlcpy(format_tag, "%d", format_tag_size); | |
265 | *ptr = next_ptr; | |
266 | } else { // the following identifiers may have an explicit format_tag | |
267 | if (av_strstart(identifier, "$Number", &next_ptr)) | |
268 | id_type = DASH_TMPL_ID_NUMBER; | |
269 | else if (av_strstart(identifier, "$Bandwidth", &next_ptr)) | |
270 | id_type = DASH_TMPL_ID_BANDWIDTH; | |
271 | else if (av_strstart(identifier, "$Time", &next_ptr)) | |
272 | id_type = DASH_TMPL_ID_TIME; | |
273 | else | |
274 | id_type = DASH_TMPL_ID_UNDEFINED; | |
275 | ||
276 | // next parse the dash format-tag and generate a c-string format tag | |
277 | // (next_ptr now points at the first '%' at the beginning of the format-tag) | |
278 | if (id_type != DASH_TMPL_ID_UNDEFINED) { | |
279 | const char *number_format = DASH_TMPL_ID_TIME ? "lld" : "d"; | |
280 | if (next_ptr[0] == '$') { // no dash format-tag | |
281 | snprintf(format_tag, format_tag_size, "%%%s", number_format); | |
282 | *ptr = &next_ptr[1]; | |
283 | } else { | |
284 | const char *width_ptr; | |
285 | // only tolerate single-digit width-field (i.e. up to 9-digit width) | |
286 | if (av_strstart(next_ptr, "%0", &width_ptr) && | |
287 | av_isdigit(width_ptr[0]) && | |
288 | av_strstart(&width_ptr[1], "d$", &next_ptr)) { | |
289 | // yes, we're using a format tag to build format_tag. | |
290 | snprintf(format_tag, format_tag_size, "%s%c%s", "%0", width_ptr[0], number_format); | |
291 | *ptr = next_ptr; | |
292 | } else { | |
293 | av_log(NULL, AV_LOG_WARNING, "Failed to parse format-tag beginning with %s. Expected either a " | |
294 | "closing '$' character or a format-string like '%%0[width]d', " | |
295 | "where width must be a single digit\n", next_ptr); | |
296 | id_type = DASH_TMPL_ID_UNDEFINED; | |
297 | } | |
298 | } | |
299 | } | |
300 | } | |
301 | return id_type; | |
302 | } | |
303 | ||
304 | static void dash_fill_tmpl_params(char *dst, size_t buffer_size, | |
305 | const char *template, int rep_id, | |
306 | int number, int bit_rate, | |
307 | int64_t time) { | |
308 | int dst_pos = 0; | |
309 | const char *t_cur = template; | |
310 | while (dst_pos < buffer_size - 1 && *t_cur) { | |
311 | char format_tag[7]; // May be "%d", "%0Xd", or "%0Xlld" (for $Time$), where X is in [0-9] | |
312 | int n = 0; | |
313 | DASHTmplId id_type; | |
314 | const char *t_next = strchr(t_cur, '$'); // copy over everything up to the first '$' character | |
315 | if (t_next) { | |
316 | int num_copy_bytes = FFMIN(t_next - t_cur, buffer_size - dst_pos - 1); | |
317 | av_strlcpy(&dst[dst_pos], t_cur, num_copy_bytes + 1); | |
318 | // advance | |
319 | dst_pos += num_copy_bytes; | |
320 | t_cur = t_next; | |
321 | } else { // no more DASH identifiers to substitute - just copy the rest over and break | |
322 | av_strlcpy(&dst[dst_pos], t_cur, buffer_size - dst_pos); | |
323 | break; | |
324 | } | |
325 | ||
326 | if (dst_pos >= buffer_size - 1 || !*t_cur) | |
327 | break; | |
328 | ||
329 | // t_cur is now pointing to a '$' character | |
330 | id_type = dash_read_tmpl_id(t_cur, format_tag, sizeof(format_tag), &t_next); | |
331 | switch (id_type) { | |
332 | case DASH_TMPL_ID_ESCAPE: | |
333 | av_strlcpy(&dst[dst_pos], "$", 2); | |
334 | n = 1; | |
335 | break; | |
336 | case DASH_TMPL_ID_REP_ID: | |
337 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, rep_id); | |
338 | break; | |
339 | case DASH_TMPL_ID_NUMBER: | |
340 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, number); | |
341 | break; | |
342 | case DASH_TMPL_ID_BANDWIDTH: | |
343 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, bit_rate); | |
344 | break; | |
345 | case DASH_TMPL_ID_TIME: | |
346 | n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, time); | |
347 | break; | |
348 | case DASH_TMPL_ID_UNDEFINED: | |
349 | // copy over one byte and advance | |
350 | av_strlcpy(&dst[dst_pos], t_cur, 2); | |
351 | n = 1; | |
352 | t_next = &t_cur[1]; | |
353 | break; | |
354 | } | |
355 | // t_next points just past the processed identifier | |
356 | // n is the number of bytes that were attempted to be written to dst | |
357 | // (may have failed to write all because buffer_size). | |
358 | ||
359 | // advance | |
360 | dst_pos += FFMIN(n, buffer_size - dst_pos - 1); | |
361 | t_cur = t_next; | |
362 | } | |
363 | } | |
364 | ||
365 | static char *xmlescape(const char *str) { | |
366 | int outlen = strlen(str)*3/2 + 6; | |
367 | char *out = av_realloc(NULL, outlen + 1); | |
368 | int pos = 0; | |
369 | if (!out) | |
370 | return NULL; | |
371 | for (; *str; str++) { | |
372 | if (pos + 6 > outlen) { | |
373 | char *tmp; | |
374 | outlen = 2 * outlen + 6; | |
375 | tmp = av_realloc(out, outlen + 1); | |
376 | if (!tmp) { | |
377 | av_free(out); | |
378 | return NULL; | |
379 | } | |
380 | out = tmp; | |
381 | } | |
382 | if (*str == '&') { | |
383 | memcpy(&out[pos], "&", 5); | |
384 | pos += 5; | |
385 | } else if (*str == '<') { | |
386 | memcpy(&out[pos], "<", 4); | |
387 | pos += 4; | |
388 | } else if (*str == '>') { | |
389 | memcpy(&out[pos], ">", 4); | |
390 | pos += 4; | |
391 | } else if (*str == '\'') { | |
392 | memcpy(&out[pos], "'", 6); | |
393 | pos += 6; | |
394 | } else if (*str == '\"') { | |
395 | memcpy(&out[pos], """, 6); | |
396 | pos += 6; | |
397 | } else { | |
398 | out[pos++] = *str; | |
399 | } | |
400 | } | |
401 | out[pos] = '\0'; | |
402 | return out; | |
403 | } | |
404 | ||
405 | static void write_time(AVIOContext *out, int64_t time) | |
406 | { | |
407 | int seconds = time / AV_TIME_BASE; | |
408 | int fractions = time % AV_TIME_BASE; | |
409 | int minutes = seconds / 60; | |
410 | int hours = minutes / 60; | |
411 | seconds %= 60; | |
412 | minutes %= 60; | |
413 | avio_printf(out, "PT"); | |
414 | if (hours) | |
415 | avio_printf(out, "%dH", hours); | |
416 | if (hours || minutes) | |
417 | avio_printf(out, "%dM", minutes); | |
418 | avio_printf(out, "%d.%dS", seconds, fractions / (AV_TIME_BASE / 10)); | |
419 | } | |
420 | ||
421 | static int write_manifest(AVFormatContext *s, int final) | |
422 | { | |
423 | DASHContext *c = s->priv_data; | |
424 | AVIOContext *out; | |
425 | char temp_filename[1024]; | |
426 | int ret, i; | |
427 | AVDictionaryEntry *title = av_dict_get(s->metadata, "title", NULL, 0); | |
428 | ||
429 | snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", s->filename); | |
430 | ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); | |
431 | if (ret < 0) { | |
432 | av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); | |
433 | return ret; | |
434 | } | |
435 | avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); | |
436 | avio_printf(out, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" | |
437 | "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n" | |
438 | "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n" | |
439 | "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n" | |
440 | "\tprofiles=\"urn:mpeg:dash:profile:isoff-live:2011\"\n" | |
441 | "\ttype=\"%s\"\n", final ? "static" : "dynamic"); | |
442 | if (final) { | |
443 | avio_printf(out, "\tmediaPresentationDuration=\""); | |
444 | write_time(out, c->total_duration); | |
445 | avio_printf(out, "\"\n"); | |
446 | } else { | |
447 | int update_period = c->last_duration / AV_TIME_BASE; | |
448 | if (c->use_template && !c->use_timeline) | |
449 | update_period = 500; | |
450 | avio_printf(out, "\tminimumUpdatePeriod=\"PT%dS\"\n", update_period); | |
451 | avio_printf(out, "\tsuggestedPresentationDelay=\"PT%dS\"\n", c->last_duration / AV_TIME_BASE); | |
452 | if (!c->availability_start_time[0] && s->nb_streams > 0 && c->streams[0].nb_segments > 0) { | |
453 | time_t t = time(NULL); | |
454 | struct tm *ptm, tmbuf; | |
455 | ptm = gmtime_r(&t, &tmbuf); | |
456 | if (ptm) { | |
457 | if (!strftime(c->availability_start_time, sizeof(c->availability_start_time), | |
458 | "%Y-%m-%dT%H:%M:%S", ptm)) | |
459 | c->availability_start_time[0] = '\0'; | |
460 | } | |
461 | } | |
462 | if (c->availability_start_time[0]) | |
463 | avio_printf(out, "\tavailabilityStartTime=\"%s\"\n", c->availability_start_time); | |
464 | if (c->window_size && c->use_template) { | |
465 | avio_printf(out, "\ttimeShiftBufferDepth=\""); | |
466 | write_time(out, c->last_duration * c->window_size); | |
467 | avio_printf(out, "\"\n"); | |
468 | } | |
469 | } | |
470 | avio_printf(out, "\tminBufferTime=\""); | |
471 | write_time(out, c->last_duration); | |
472 | avio_printf(out, "\">\n"); | |
473 | avio_printf(out, "\t<ProgramInformation>\n"); | |
474 | if (title) { | |
475 | char *escaped = xmlescape(title->value); | |
476 | avio_printf(out, "\t\t<Title>%s</Title>\n", escaped); | |
477 | av_free(escaped); | |
478 | } | |
479 | avio_printf(out, "\t</ProgramInformation>\n"); | |
480 | if (c->window_size && s->nb_streams > 0 && c->streams[0].nb_segments > 0 && !c->use_template) { | |
481 | OutputStream *os = &c->streams[0]; | |
482 | int start_index = FFMAX(os->nb_segments - c->window_size, 0); | |
483 | int64_t start_time = av_rescale_q(os->segments[start_index]->time, s->streams[0]->time_base, AV_TIME_BASE_Q); | |
484 | avio_printf(out, "\t<Period start=\""); | |
485 | write_time(out, start_time); | |
486 | avio_printf(out, "\">\n"); | |
487 | } else { | |
488 | avio_printf(out, "\t<Period start=\"PT0.0S\">\n"); | |
489 | } | |
490 | ||
491 | if (c->has_video) { | |
492 | avio_printf(out, "\t\t<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n"); | |
493 | for (i = 0; i < s->nb_streams; i++) { | |
494 | AVStream *st = s->streams[i]; | |
495 | OutputStream *os = &c->streams[i]; | |
496 | if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO) | |
497 | continue; | |
498 | avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codec->width, st->codec->height); | |
499 | output_segment_list(&c->streams[i], out, c); | |
500 | avio_printf(out, "\t\t\t</Representation>\n"); | |
501 | } | |
502 | avio_printf(out, "\t\t</AdaptationSet>\n"); | |
503 | } | |
504 | if (c->has_audio) { | |
505 | avio_printf(out, "\t\t<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n"); | |
506 | for (i = 0; i < s->nb_streams; i++) { | |
507 | AVStream *st = s->streams[i]; | |
508 | OutputStream *os = &c->streams[i]; | |
509 | if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) | |
510 | continue; | |
511 | avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codec->sample_rate); | |
512 | avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st->codec->channels); | |
513 | output_segment_list(&c->streams[i], out, c); | |
514 | avio_printf(out, "\t\t\t</Representation>\n"); | |
515 | } | |
516 | avio_printf(out, "\t\t</AdaptationSet>\n"); | |
517 | } | |
518 | avio_printf(out, "\t</Period>\n"); | |
519 | avio_printf(out, "</MPD>\n"); | |
520 | avio_flush(out); | |
521 | avio_close(out); | |
522 | return ff_rename(temp_filename, s->filename, s); | |
523 | } | |
524 | ||
525 | static int dash_write_header(AVFormatContext *s) | |
526 | { | |
527 | DASHContext *c = s->priv_data; | |
528 | int ret = 0, i; | |
529 | AVOutputFormat *oformat; | |
530 | char *ptr; | |
531 | char basename[1024]; | |
532 | ||
533 | if (c->single_file_name) | |
534 | c->single_file = 1; | |
535 | if (c->single_file) | |
536 | c->use_template = 0; | |
537 | ||
538 | av_strlcpy(c->dirname, s->filename, sizeof(c->dirname)); | |
539 | ptr = strrchr(c->dirname, '/'); | |
540 | if (ptr) { | |
541 | av_strlcpy(basename, &ptr[1], sizeof(basename)); | |
542 | ptr[1] = '\0'; | |
543 | } else { | |
544 | c->dirname[0] = '\0'; | |
545 | av_strlcpy(basename, s->filename, sizeof(basename)); | |
546 | } | |
547 | ||
548 | ptr = strrchr(basename, '.'); | |
549 | if (ptr) | |
550 | *ptr = '\0'; | |
551 | ||
552 | oformat = av_guess_format("mp4", NULL, NULL); | |
553 | if (!oformat) { | |
554 | ret = AVERROR_MUXER_NOT_FOUND; | |
555 | goto fail; | |
556 | } | |
557 | ||
558 | c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams); | |
559 | if (!c->streams) { | |
560 | ret = AVERROR(ENOMEM); | |
561 | goto fail; | |
562 | } | |
563 | ||
564 | for (i = 0; i < s->nb_streams; i++) { | |
565 | OutputStream *os = &c->streams[i]; | |
566 | AVFormatContext *ctx; | |
567 | AVStream *st; | |
568 | AVDictionary *opts = NULL; | |
569 | char filename[1024]; | |
570 | ||
571 | os->bit_rate = s->streams[i]->codec->bit_rate ? | |
572 | s->streams[i]->codec->bit_rate : | |
573 | s->streams[i]->codec->rc_max_rate; | |
574 | if (os->bit_rate) { | |
575 | snprintf(os->bandwidth_str, sizeof(os->bandwidth_str), | |
576 | " bandwidth=\"%d\"", os->bit_rate); | |
577 | } else { | |
578 | int level = s->strict_std_compliance >= FF_COMPLIANCE_STRICT ? | |
579 | AV_LOG_ERROR : AV_LOG_WARNING; | |
580 | av_log(s, level, "No bit rate set for stream %d\n", i); | |
581 | if (s->strict_std_compliance >= FF_COMPLIANCE_STRICT) { | |
582 | ret = AVERROR(EINVAL); | |
583 | goto fail; | |
584 | } | |
585 | } | |
586 | ||
587 | ctx = avformat_alloc_context(); | |
588 | if (!ctx) { | |
589 | ret = AVERROR(ENOMEM); | |
590 | goto fail; | |
591 | } | |
592 | os->ctx = ctx; | |
593 | ctx->oformat = oformat; | |
594 | ctx->interrupt_callback = s->interrupt_callback; | |
595 | ||
596 | if (!(st = avformat_new_stream(ctx, NULL))) { | |
597 | ret = AVERROR(ENOMEM); | |
598 | goto fail; | |
599 | } | |
600 | avcodec_copy_context(st->codec, s->streams[i]->codec); | |
601 | st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; | |
602 | st->time_base = s->streams[i]->time_base; | |
603 | ctx->avoid_negative_ts = s->avoid_negative_ts; | |
604 | ||
605 | ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, dash_write, NULL); | |
606 | if (!ctx->pb) { | |
607 | ret = AVERROR(ENOMEM); | |
608 | goto fail; | |
609 | } | |
610 | ||
611 | if (c->single_file) { | |
612 | if (c->single_file_name) | |
613 | dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), c->single_file_name, i, 0, os->bit_rate, 0); | |
614 | else | |
615 | snprintf(os->initfile, sizeof(os->initfile), "%s-stream%d.m4s", basename, i); | |
616 | } else { | |
617 | dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), c->init_seg_name, i, 0, os->bit_rate, 0); | |
618 | } | |
619 | snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile); | |
620 | ret = ffurl_open(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); | |
621 | if (ret < 0) | |
622 | goto fail; | |
623 | os->init_start_pos = 0; | |
624 | ||
625 | av_dict_set(&opts, "movflags", "frag_custom+dash", 0); | |
626 | if ((ret = avformat_write_header(ctx, &opts)) < 0) { | |
627 | goto fail; | |
628 | } | |
629 | os->ctx_inited = 1; | |
630 | avio_flush(ctx->pb); | |
631 | av_dict_free(&opts); | |
632 | ||
633 | if (c->single_file) { | |
634 | os->init_range_length = avio_tell(ctx->pb); | |
635 | } else { | |
636 | ffurl_close(os->out); | |
637 | os->out = NULL; | |
638 | } | |
639 | ||
640 | s->streams[i]->time_base = st->time_base; | |
641 | // If the muxer wants to shift timestamps, request to have them shifted | |
642 | // already before being handed to this muxer, so we don't have mismatches | |
643 | // between the MPD and the actual segments. | |
644 | s->avoid_negative_ts = ctx->avoid_negative_ts; | |
645 | if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) | |
646 | c->has_video = 1; | |
647 | else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) | |
648 | c->has_audio = 1; | |
649 | ||
650 | set_codec_str(s, os->ctx->streams[0]->codec, os->codec_str, sizeof(os->codec_str)); | |
651 | os->first_dts = AV_NOPTS_VALUE; | |
652 | os->segment_index = 1; | |
653 | } | |
654 | ||
655 | if (!c->has_video && c->min_seg_duration <= 0) { | |
656 | av_log(s, AV_LOG_WARNING, "no video stream and no min seg duration set\n"); | |
657 | ret = AVERROR(EINVAL); | |
658 | } | |
659 | ret = write_manifest(s, 0); | |
660 | ||
661 | fail: | |
662 | if (ret) | |
663 | dash_free(s); | |
664 | return ret; | |
665 | } | |
666 | ||
667 | static int add_segment(OutputStream *os, const char *file, | |
668 | int64_t time, int duration, | |
669 | int64_t start_pos, int64_t range_length, | |
670 | int64_t index_length) | |
671 | { | |
672 | int err; | |
673 | Segment *seg; | |
674 | if (os->nb_segments >= os->segments_size) { | |
675 | os->segments_size = (os->segments_size + 1) * 2; | |
676 | if ((err = av_reallocp(&os->segments, sizeof(*os->segments) * | |
677 | os->segments_size)) < 0) { | |
678 | os->segments_size = 0; | |
679 | os->nb_segments = 0; | |
680 | return err; | |
681 | } | |
682 | } | |
683 | seg = av_mallocz(sizeof(*seg)); | |
684 | if (!seg) | |
685 | return AVERROR(ENOMEM); | |
686 | av_strlcpy(seg->file, file, sizeof(seg->file)); | |
687 | seg->time = time; | |
688 | seg->duration = duration; | |
689 | seg->start_pos = start_pos; | |
690 | seg->range_length = range_length; | |
691 | seg->index_length = index_length; | |
692 | os->segments[os->nb_segments++] = seg; | |
693 | os->segment_index++; | |
694 | return 0; | |
695 | } | |
696 | ||
697 | static void write_styp(AVIOContext *pb) | |
698 | { | |
699 | avio_wb32(pb, 24); | |
700 | ffio_wfourcc(pb, "styp"); | |
701 | ffio_wfourcc(pb, "msdh"); | |
702 | avio_wb32(pb, 0); /* minor */ | |
703 | ffio_wfourcc(pb, "msdh"); | |
704 | ffio_wfourcc(pb, "msix"); | |
705 | } | |
706 | ||
707 | static void find_index_range(AVFormatContext *s, const char *dirname, | |
708 | const char *filename, int64_t pos, | |
709 | int *index_length) | |
710 | { | |
711 | char full_path[1024]; | |
712 | uint8_t buf[8]; | |
713 | URLContext *fd; | |
714 | int ret; | |
715 | ||
716 | snprintf(full_path, sizeof(full_path), "%s%s", dirname, filename); | |
717 | ret = ffurl_open(&fd, full_path, AVIO_FLAG_READ, &s->interrupt_callback, NULL); | |
718 | if (ret < 0) | |
719 | return; | |
720 | if (ffurl_seek(fd, pos, SEEK_SET) != pos) { | |
721 | ffurl_close(fd); | |
722 | return; | |
723 | } | |
724 | ret = ffurl_read(fd, buf, 8); | |
725 | ffurl_close(fd); | |
726 | if (ret < 8) | |
727 | return; | |
728 | if (AV_RL32(&buf[4]) != MKTAG('s', 'i', 'd', 'x')) | |
729 | return; | |
730 | *index_length = AV_RB32(&buf[0]); | |
731 | } | |
732 | ||
733 | static int dash_flush(AVFormatContext *s, int final, int stream) | |
734 | { | |
735 | DASHContext *c = s->priv_data; | |
736 | int i, ret = 0; | |
737 | int cur_flush_segment_index = 0; | |
738 | if (stream >= 0) | |
739 | cur_flush_segment_index = c->streams[stream].segment_index; | |
740 | ||
741 | for (i = 0; i < s->nb_streams; i++) { | |
742 | OutputStream *os = &c->streams[i]; | |
743 | char filename[1024] = "", full_path[1024], temp_path[1024]; | |
744 | int64_t start_pos = avio_tell(os->ctx->pb); | |
745 | int range_length, index_length = 0; | |
746 | ||
747 | if (!os->packets_written) | |
748 | continue; | |
749 | ||
750 | // Flush the single stream that got a keyframe right now. | |
751 | // Flush all audio streams as well, in sync with video keyframes, | |
752 | // but not the other video streams. | |
753 | if (stream >= 0 && i != stream) { | |
754 | if (s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) | |
755 | continue; | |
756 | // Make sure we don't flush audio streams multiple times, when | |
757 | // all video streams are flushed one at a time. | |
758 | if (c->has_video && os->segment_index > cur_flush_segment_index) | |
759 | continue; | |
760 | } | |
761 | ||
762 | if (!c->single_file) { | |
763 | dash_fill_tmpl_params(filename, sizeof(filename), c->media_seg_name, i, os->segment_index, os->bit_rate, os->start_dts); | |
764 | snprintf(full_path, sizeof(full_path), "%s%s", c->dirname, filename); | |
765 | snprintf(temp_path, sizeof(temp_path), "%s.tmp", full_path); | |
766 | ret = ffurl_open(&os->out, temp_path, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL); | |
767 | if (ret < 0) | |
768 | break; | |
769 | write_styp(os->ctx->pb); | |
770 | } | |
771 | av_write_frame(os->ctx, NULL); | |
772 | avio_flush(os->ctx->pb); | |
773 | os->packets_written = 0; | |
774 | ||
775 | range_length = avio_tell(os->ctx->pb) - start_pos; | |
776 | if (c->single_file) { | |
777 | find_index_range(s, c->dirname, os->initfile, start_pos, &index_length); | |
778 | } else { | |
779 | ffurl_close(os->out); | |
780 | os->out = NULL; | |
781 | ret = ff_rename(temp_path, full_path, s); | |
782 | if (ret < 0) | |
783 | break; | |
784 | } | |
785 | add_segment(os, filename, os->start_dts, os->end_dts - os->start_dts, start_pos, range_length, index_length); | |
786 | } | |
787 | ||
788 | if (c->window_size || (final && c->remove_at_exit)) { | |
789 | for (i = 0; i < s->nb_streams; i++) { | |
790 | OutputStream *os = &c->streams[i]; | |
791 | int j; | |
792 | int remove = os->nb_segments - c->window_size - c->extra_window_size; | |
793 | if (final && c->remove_at_exit) | |
794 | remove = os->nb_segments; | |
795 | if (remove > 0) { | |
796 | for (j = 0; j < remove; j++) { | |
797 | char filename[1024]; | |
798 | snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->segments[j]->file); | |
799 | unlink(filename); | |
800 | av_free(os->segments[j]); | |
801 | } | |
802 | os->nb_segments -= remove; | |
803 | memmove(os->segments, os->segments + remove, os->nb_segments * sizeof(*os->segments)); | |
804 | } | |
805 | } | |
806 | } | |
807 | ||
808 | if (ret >= 0) | |
809 | ret = write_manifest(s, final); | |
810 | return ret; | |
811 | } | |
812 | ||
813 | static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) | |
814 | { | |
815 | DASHContext *c = s->priv_data; | |
816 | AVStream *st = s->streams[pkt->stream_index]; | |
817 | OutputStream *os = &c->streams[pkt->stream_index]; | |
818 | int64_t seg_end_duration = (os->segment_index) * (int64_t) c->min_seg_duration; | |
819 | int ret; | |
820 | ||
821 | // If forcing the stream to start at 0, the mp4 muxer will set the start | |
822 | // timestamps to 0. Do the same here, to avoid mismatches in duration/timestamps. | |
823 | if (os->first_dts == AV_NOPTS_VALUE && | |
824 | s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) { | |
825 | pkt->pts -= pkt->dts; | |
826 | pkt->dts = 0; | |
827 | } | |
828 | ||
829 | if (os->first_dts == AV_NOPTS_VALUE) | |
830 | os->first_dts = pkt->dts; | |
831 | ||
832 | if ((!c->has_video || st->codec->codec_type == AVMEDIA_TYPE_VIDEO) && | |
833 | pkt->flags & AV_PKT_FLAG_KEY && os->packets_written && | |
834 | av_compare_ts(pkt->dts - os->first_dts, st->time_base, | |
835 | seg_end_duration, AV_TIME_BASE_Q) >= 0) { | |
836 | int64_t prev_duration = c->last_duration; | |
837 | ||
838 | c->last_duration = av_rescale_q(pkt->dts - os->start_dts, | |
839 | st->time_base, | |
840 | AV_TIME_BASE_Q); | |
841 | c->total_duration = av_rescale_q(pkt->dts - os->first_dts, | |
842 | st->time_base, | |
843 | AV_TIME_BASE_Q); | |
844 | ||
845 | if ((!c->use_timeline || !c->use_template) && prev_duration) { | |
846 | if (c->last_duration < prev_duration*9/10 || | |
847 | c->last_duration > prev_duration*11/10) { | |
848 | av_log(s, AV_LOG_WARNING, | |
849 | "Segment durations differ too much, enable use_timeline " | |
850 | "and use_template, or keep a stricter keyframe interval\n"); | |
851 | } | |
852 | } | |
853 | ||
854 | if ((ret = dash_flush(s, 0, pkt->stream_index)) < 0) | |
855 | return ret; | |
856 | } | |
857 | ||
858 | if (!os->packets_written) | |
859 | os->start_dts = pkt->dts; | |
860 | os->end_dts = pkt->dts + pkt->duration; | |
861 | os->packets_written++; | |
862 | return ff_write_chained(os->ctx, 0, pkt, s, 0); | |
863 | } | |
864 | ||
865 | static int dash_write_trailer(AVFormatContext *s) | |
866 | { | |
867 | DASHContext *c = s->priv_data; | |
868 | ||
869 | if (s->nb_streams > 0) { | |
870 | OutputStream *os = &c->streams[0]; | |
871 | // If no segments have been written so far, try to do a crude | |
872 | // guess of the segment duration | |
873 | if (!c->last_duration) | |
874 | c->last_duration = av_rescale_q(os->end_dts - os->start_dts, | |
875 | s->streams[0]->time_base, | |
876 | AV_TIME_BASE_Q); | |
877 | c->total_duration = av_rescale_q(os->end_dts - os->first_dts, | |
878 | s->streams[0]->time_base, | |
879 | AV_TIME_BASE_Q); | |
880 | } | |
881 | dash_flush(s, 1, -1); | |
882 | ||
883 | if (c->remove_at_exit) { | |
884 | char filename[1024]; | |
885 | int i; | |
886 | for (i = 0; i < s->nb_streams; i++) { | |
887 | OutputStream *os = &c->streams[i]; | |
888 | snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile); | |
889 | unlink(filename); | |
890 | } | |
891 | unlink(s->filename); | |
892 | } | |
893 | ||
894 | dash_free(s); | |
895 | return 0; | |
896 | } | |
897 | ||
898 | #define OFFSET(x) offsetof(DASHContext, x) | |
899 | #define E AV_OPT_FLAG_ENCODING_PARAM | |
900 | static const AVOption options[] = { | |
901 | { "window_size", "number of segments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, | |
902 | { "extra_window_size", "number of segments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E }, | |
903 | { "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E }, | |
904 | { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E }, | |
905 | { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, | |
906 | { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E }, | |
907 | { "single_file", "Store all segments in one file, accessed using byte ranges", OFFSET(single_file), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E }, | |
908 | { "single_file_name", "DASH-templated name to be used for baseURL. Implies storing all segments in one file, accessed using byte ranges", OFFSET(single_file_name), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, | |
909 | { "init_seg_name", "DASH-templated name to used for the initialization segment", OFFSET(init_seg_name), AV_OPT_TYPE_STRING, {.str = "init-stream$RepresentationID$.m4s"}, 0, 0, E }, | |
910 | { "media_seg_name", "DASH-templated name to used for the media segments", OFFSET(media_seg_name), AV_OPT_TYPE_STRING, {.str = "chunk-stream$RepresentationID$-$Number%05d$.m4s"}, 0, 0, E }, | |
911 | { NULL }, | |
912 | }; | |
913 | ||
914 | static const AVClass dash_class = { | |
915 | .class_name = "dash muxer", | |
916 | .item_name = av_default_item_name, | |
917 | .option = options, | |
918 | .version = LIBAVUTIL_VERSION_INT, | |
919 | }; | |
920 | ||
921 | AVOutputFormat ff_dash_muxer = { | |
922 | .name = "dash", | |
923 | .long_name = NULL_IF_CONFIG_SMALL("DASH Muxer"), | |
924 | .priv_data_size = sizeof(DASHContext), | |
925 | .audio_codec = AV_CODEC_ID_AAC, | |
926 | .video_codec = AV_CODEC_ID_H264, | |
927 | .flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE | AVFMT_TS_NEGATIVE, | |
928 | .write_header = dash_write_header, | |
929 | .write_packet = dash_write_packet, | |
930 | .write_trailer = dash_write_trailer, | |
931 | .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 }, | |
932 | .priv_class = &dash_class, | |
933 | }; |