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