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