2 * MPEG-DASH ISO BMFF segmenter
3 * Copyright (c) 2014 Martin Storsjo
5 * This file is part of Libav.
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.
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.
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
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"
35 #include "avio_internal.h"
38 #include "os_support.h"
41 // See ISO/IEC 23009-1:2014 5.3.9.4.4
43 DASH_TMPL_ID_UNDEFINED
= -1,
47 DASH_TMPL_ID_BANDWIDTH
,
51 typedef struct Segment
{
54 int range_length
, index_length
;
60 typedef struct OutputStream
{
67 int64_t init_start_pos
;
68 int init_range_length
;
69 int nb_segments
, segments_size
, segment_index
;
71 int64_t first_pts
, start_pts
, max_pts
;
74 char bandwidth_str
[64];
79 typedef struct DASHContext
{
80 const AVClass
*class; /* Class for private options. */
82 int extra_window_size
;
88 OutputStream
*streams
;
89 int has_video
, has_audio
;
90 int64_t last_duration
;
91 int64_t total_duration
;
92 char availability_start_time
[100];
94 const char *single_file_name
;
95 const char *init_seg_name
;
96 const char *media_seg_name
;
99 static int dash_write(void *opaque
, uint8_t *buf
, int buf_size
)
101 OutputStream
*os
= opaque
;
103 ffurl_write(os
->out
, buf
, buf_size
);
108 static void set_codec_str(AVFormatContext
*s
, AVCodecContext
*codec
,
111 const AVCodecTag
*tags
[2] = { NULL
, NULL
};
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
;
120 tag
= av_codec_get_tag(tags
, codec
->codec_id
);
128 if (!strcmp(str
, "mp4a") || !strcmp(str
, "mp4v")) {
130 tags
[0] = ff_mp4_obj_type
;
131 oti
= av_codec_get_tag(tags
, codec
->codec_id
);
133 av_strlcatf(str
, size
, ".%02x", oti
);
137 if (tag
== MKTAG('m', 'p', '4', 'a')) {
138 if (codec
->extradata_size
>= 2) {
139 int aot
= codec
->extradata
[0] >> 3;
141 aot
= ((AV_RB16(codec
->extradata
) >> 5) & 0x3f) + 32;
142 av_strlcatf(str
, size
, ".%d", aot
);
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");
148 } else if (!strcmp(str
, "avc1")) {
149 uint8_t *tmpbuf
= NULL
;
150 uint8_t *extradata
= codec
->extradata
;
151 int extradata_size
= codec
->extradata_size
;
154 if (extradata
[0] != 1) {
156 if (avio_open_dyn_buf(&pb
) < 0)
158 if (ff_isom_write_avcc(pb
, extradata
, extradata_size
) < 0) {
159 ffio_free_dyn_buf(&pb
);
162 extradata_size
= avio_close_dyn_buf(pb
, &extradata
);
166 if (extradata_size
>= 4)
167 av_strlcatf(str
, size
, ".%02x%02x%02x",
168 extradata
[1], extradata
[2], extradata
[3]);
173 static void dash_free(AVFormatContext
*s
)
175 DASHContext
*c
= s
->priv_data
;
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
);
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
);
193 av_freep(&c
->streams
);
196 static void output_segment_list(OutputStream
*os
, AVIOContext
*out
, DASHContext
*c
)
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);
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=\"%"PRId64
"\" ", 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 int64_t cur_time
= 0;
212 avio_printf(out
, "\t\t\t\t\t<SegmentTimeline>\n");
213 for (i
= start_index
; i
< os
->nb_segments
; ) {
214 Segment
*seg
= os
->segments
[i
];
216 avio_printf(out
, "\t\t\t\t\t\t<S ");
217 if (i
== start_index
|| seg
->time
!= cur_time
) {
218 cur_time
= seg
->time
;
219 avio_printf(out
, "t=\"%"PRId64
"\" ", seg
->time
);
221 avio_printf(out
, "d=\"%d\" ", seg
->duration
);
222 while (i
+ repeat
+ 1 < os
->nb_segments
&&
223 os
->segments
[i
+ repeat
+ 1]->duration
== seg
->duration
&&
224 os
->segments
[i
+ repeat
+ 1]->time
== os
->segments
[i
+ repeat
]->time
+ os
->segments
[i
+ repeat
]->duration
)
227 avio_printf(out
, "r=\"%d\" ", repeat
);
228 avio_printf(out
, "/>\n");
230 cur_time
+= (1 + repeat
) * seg
->duration
;
232 avio_printf(out
, "\t\t\t\t\t</SegmentTimeline>\n");
234 avio_printf(out
, "\t\t\t\t</SegmentTemplate>\n");
235 } else if (c
->single_file
) {
236 avio_printf(out
, "\t\t\t\t<BaseURL>%s</BaseURL>\n", os
->initfile
);
237 avio_printf(out
, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%"PRId64
"\" startNumber=\"%d\">\n", AV_TIME_BASE
, c
->last_duration
, start_number
);
238 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);
239 for (i
= start_index
; i
< os
->nb_segments
; i
++) {
240 Segment
*seg
= os
->segments
[i
];
241 avio_printf(out
, "\t\t\t\t\t<SegmentURL mediaRange=\"%"PRId64
"-%"PRId64
"\" ", seg
->start_pos
, seg
->start_pos
+ seg
->range_length
- 1);
242 if (seg
->index_length
)
243 avio_printf(out
, "indexRange=\"%"PRId64
"-%"PRId64
"\" ", seg
->start_pos
, seg
->start_pos
+ seg
->index_length
- 1);
244 avio_printf(out
, "/>\n");
246 avio_printf(out
, "\t\t\t\t</SegmentList>\n");
248 avio_printf(out
, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%"PRId64
"\" startNumber=\"%d\">\n", AV_TIME_BASE
, c
->last_duration
, start_number
);
249 avio_printf(out
, "\t\t\t\t\t<Initialization sourceURL=\"%s\" />\n", os
->initfile
);
250 for (i
= start_index
; i
< os
->nb_segments
; i
++) {
251 Segment
*seg
= os
->segments
[i
];
252 avio_printf(out
, "\t\t\t\t\t<SegmentURL media=\"%s\" />\n", seg
->file
);
254 avio_printf(out
, "\t\t\t\t</SegmentList>\n");
258 static DASHTmplId
dash_read_tmpl_id(const char *identifier
, char *format_tag
,
259 size_t format_tag_size
, const char **ptr
) {
260 const char *next_ptr
;
261 DASHTmplId id_type
= DASH_TMPL_ID_UNDEFINED
;
263 if (av_strstart(identifier
, "$$", &next_ptr
)) {
264 id_type
= DASH_TMPL_ID_ESCAPE
;
266 } else if (av_strstart(identifier
, "$RepresentationID$", &next_ptr
)) {
267 id_type
= DASH_TMPL_ID_REP_ID
;
268 // default to basic format, as $RepresentationID$ identifiers
269 // are not allowed to have custom format-tags.
270 av_strlcpy(format_tag
, "%d", format_tag_size
);
272 } else { // the following identifiers may have an explicit format_tag
273 if (av_strstart(identifier
, "$Number", &next_ptr
))
274 id_type
= DASH_TMPL_ID_NUMBER
;
275 else if (av_strstart(identifier
, "$Bandwidth", &next_ptr
))
276 id_type
= DASH_TMPL_ID_BANDWIDTH
;
277 else if (av_strstart(identifier
, "$Time", &next_ptr
))
278 id_type
= DASH_TMPL_ID_TIME
;
280 id_type
= DASH_TMPL_ID_UNDEFINED
;
282 // next parse the dash format-tag and generate a c-string format tag
283 // (next_ptr now points at the first '%' at the beginning of the format-tag)
284 if (id_type
!= DASH_TMPL_ID_UNDEFINED
) {
285 const char *number_format
= (id_type
== DASH_TMPL_ID_TIME
) ? PRId64
: "d";
286 if (next_ptr
[0] == '$') { // no dash format-tag
287 snprintf(format_tag
, format_tag_size
, "%%%s", number_format
);
290 const char *width_ptr
;
291 // only tolerate single-digit width-field (i.e. up to 9-digit width)
292 if (av_strstart(next_ptr
, "%0", &width_ptr
) &&
293 av_isdigit(width_ptr
[0]) &&
294 av_strstart(&width_ptr
[1], "d$", &next_ptr
)) {
295 // yes, we're using a format tag to build format_tag.
296 snprintf(format_tag
, format_tag_size
, "%s%c%s", "%0", width_ptr
[0], number_format
);
299 av_log(NULL
, AV_LOG_WARNING
, "Failed to parse format-tag beginning with %s. Expected either a "
300 "closing '$' character or a format-string like '%%0[width]d', "
301 "where width must be a single digit\n", next_ptr
);
302 id_type
= DASH_TMPL_ID_UNDEFINED
;
310 static void dash_fill_tmpl_params(char *dst
, size_t buffer_size
,
311 const char *template, int rep_id
,
312 int number
, int bit_rate
,
315 const char *t_cur
= template;
316 while (dst_pos
< buffer_size
- 1 && *t_cur
) {
317 char format_tag
[7]; // May be "%d", "%0Xd", or "%0Xlld" (for $Time$), where X is in [0-9]
320 const char *t_next
= strchr(t_cur
, '$'); // copy over everything up to the first '$' character
322 int num_copy_bytes
= FFMIN(t_next
- t_cur
, buffer_size
- dst_pos
- 1);
323 av_strlcpy(&dst
[dst_pos
], t_cur
, num_copy_bytes
+ 1);
325 dst_pos
+= num_copy_bytes
;
327 } else { // no more DASH identifiers to substitute - just copy the rest over and break
328 av_strlcpy(&dst
[dst_pos
], t_cur
, buffer_size
- dst_pos
);
332 if (dst_pos
>= buffer_size
- 1 || !*t_cur
)
335 // t_cur is now pointing to a '$' character
336 id_type
= dash_read_tmpl_id(t_cur
, format_tag
, sizeof(format_tag
), &t_next
);
338 case DASH_TMPL_ID_ESCAPE
:
339 av_strlcpy(&dst
[dst_pos
], "$", 2);
342 case DASH_TMPL_ID_REP_ID
:
343 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, rep_id
);
345 case DASH_TMPL_ID_NUMBER
:
346 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, number
);
348 case DASH_TMPL_ID_BANDWIDTH
:
349 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, bit_rate
);
351 case DASH_TMPL_ID_TIME
:
352 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, time
);
354 case DASH_TMPL_ID_UNDEFINED
:
355 // copy over one byte and advance
356 av_strlcpy(&dst
[dst_pos
], t_cur
, 2);
361 // t_next points just past the processed identifier
362 // n is the number of bytes that were attempted to be written to dst
363 // (may have failed to write all because buffer_size).
366 dst_pos
+= FFMIN(n
, buffer_size
- dst_pos
- 1);
371 static char *xmlescape(const char *str
) {
372 int outlen
= strlen(str
)*3/2 + 6;
373 char *out
= av_realloc(NULL
, outlen
+ 1);
377 for (; *str
; str
++) {
378 if (pos
+ 6 > outlen
) {
380 outlen
= 2 * outlen
+ 6;
381 tmp
= av_realloc(out
, outlen
+ 1);
389 memcpy(&out
[pos
], "&", 5);
391 } else if (*str
== '<') {
392 memcpy(&out
[pos
], "<", 4);
394 } else if (*str
== '>') {
395 memcpy(&out
[pos
], ">", 4);
397 } else if (*str
== '\'') {
398 memcpy(&out
[pos
], "'", 6);
400 } else if (*str
== '\"') {
401 memcpy(&out
[pos
], """, 6);
411 static void write_time(AVIOContext
*out
, int64_t time
)
413 int seconds
= time
/ AV_TIME_BASE
;
414 int fractions
= time
% AV_TIME_BASE
;
415 int minutes
= seconds
/ 60;
416 int hours
= minutes
/ 60;
419 avio_printf(out
, "PT");
421 avio_printf(out
, "%dH", hours
);
422 if (hours
|| minutes
)
423 avio_printf(out
, "%dM", minutes
);
424 avio_printf(out
, "%d.%dS", seconds
, fractions
/ (AV_TIME_BASE
/ 10));
427 static void format_date_now(char *buf
, int size
)
429 time_t t
= time(NULL
);
430 struct tm
*ptm
, tmbuf
;
431 ptm
= gmtime_r(&t
, &tmbuf
);
433 if (!strftime(buf
, size
, "%Y-%m-%dT%H:%M:%S", ptm
))
438 static int write_manifest(AVFormatContext
*s
, int final
)
440 DASHContext
*c
= s
->priv_data
;
442 char temp_filename
[1024];
444 AVDictionaryEntry
*title
= av_dict_get(s
->metadata
, "title", NULL
, 0);
446 snprintf(temp_filename
, sizeof(temp_filename
), "%s.tmp", s
->filename
);
447 ret
= avio_open2(&out
, temp_filename
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
449 av_log(s
, AV_LOG_ERROR
, "Unable to open %s for writing\n", temp_filename
);
452 avio_printf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
453 avio_printf(out
, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
454 "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
455 "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
456 "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n"
457 "\tprofiles=\"urn:mpeg:dash:profile:isoff-live:2011\"\n"
458 "\ttype=\"%s\"\n", final ?
"static" : "dynamic");
460 avio_printf(out
, "\tmediaPresentationDuration=\"");
461 write_time(out
, c
->total_duration
);
462 avio_printf(out
, "\"\n");
464 int64_t update_period
= c
->last_duration
/ AV_TIME_BASE
;
466 if (c
->use_template
&& !c
->use_timeline
)
468 avio_printf(out
, "\tminimumUpdatePeriod=\"PT%"PRId64
"S\"\n", update_period
);
469 avio_printf(out
, "\tsuggestedPresentationDelay=\"PT%"PRId64
"S\"\n", c
->last_duration
/ AV_TIME_BASE
);
470 if (!c
->availability_start_time
[0] && s
->nb_streams
> 0 && c
->streams
[0].nb_segments
> 0) {
471 format_date_now(c
->availability_start_time
, sizeof(c
->availability_start_time
));
473 if (c
->availability_start_time
[0])
474 avio_printf(out
, "\tavailabilityStartTime=\"%s\"\n", c
->availability_start_time
);
475 format_date_now(now_str
, sizeof(now_str
));
477 avio_printf(out
, "\tpublishTime=\"%s\"\n", now_str
);
478 if (c
->window_size
&& c
->use_template
) {
479 avio_printf(out
, "\ttimeShiftBufferDepth=\"");
480 write_time(out
, c
->last_duration
* c
->window_size
);
481 avio_printf(out
, "\"\n");
484 avio_printf(out
, "\tminBufferTime=\"");
485 write_time(out
, c
->last_duration
);
486 avio_printf(out
, "\">\n");
487 avio_printf(out
, "\t<ProgramInformation>\n");
489 char *escaped
= xmlescape(title
->value
);
490 avio_printf(out
, "\t\t<Title>%s</Title>\n", escaped
);
493 avio_printf(out
, "\t</ProgramInformation>\n");
494 if (c
->window_size
&& s
->nb_streams
> 0 && c
->streams
[0].nb_segments
> 0 && !c
->use_template
) {
495 OutputStream
*os
= &c
->streams
[0];
496 int start_index
= FFMAX(os
->nb_segments
- c
->window_size
, 0);
497 int64_t start_time
= av_rescale_q(os
->segments
[start_index
]->time
, s
->streams
[0]->time_base
, AV_TIME_BASE_Q
);
498 avio_printf(out
, "\t<Period start=\"");
499 write_time(out
, start_time
);
500 avio_printf(out
, "\">\n");
502 avio_printf(out
, "\t<Period start=\"PT0.0S\">\n");
506 avio_printf(out
, "\t\t<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
507 for (i
= 0; i
< s
->nb_streams
; i
++) {
508 AVStream
*st
= s
->streams
[i
];
509 OutputStream
*os
= &c
->streams
[i
];
510 if (st
->codec
->codec_type
!= AVMEDIA_TYPE_VIDEO
)
512 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
);
513 output_segment_list(&c
->streams
[i
], out
, c
);
514 avio_printf(out
, "\t\t\t</Representation>\n");
516 avio_printf(out
, "\t\t</AdaptationSet>\n");
519 avio_printf(out
, "\t\t<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
520 for (i
= 0; i
< s
->nb_streams
; i
++) {
521 AVStream
*st
= s
->streams
[i
];
522 OutputStream
*os
= &c
->streams
[i
];
523 if (st
->codec
->codec_type
!= AVMEDIA_TYPE_AUDIO
)
525 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
);
526 avio_printf(out
, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st
->codec
->channels
);
527 output_segment_list(&c
->streams
[i
], out
, c
);
528 avio_printf(out
, "\t\t\t</Representation>\n");
530 avio_printf(out
, "\t\t</AdaptationSet>\n");
532 avio_printf(out
, "\t</Period>\n");
533 avio_printf(out
, "</MPD>\n");
536 return ff_rename(temp_filename
, s
->filename
);
539 static int dash_write_header(AVFormatContext
*s
)
541 DASHContext
*c
= s
->priv_data
;
543 AVOutputFormat
*oformat
;
547 if (c
->single_file_name
)
552 av_strlcpy(c
->dirname
, s
->filename
, sizeof(c
->dirname
));
553 ptr
= strrchr(c
->dirname
, '/');
555 av_strlcpy(basename
, &ptr
[1], sizeof(basename
));
558 c
->dirname
[0] = '\0';
559 av_strlcpy(basename
, s
->filename
, sizeof(basename
));
562 ptr
= strrchr(basename
, '.');
566 oformat
= av_guess_format("mp4", NULL
, NULL
);
568 ret
= AVERROR_MUXER_NOT_FOUND
;
572 c
->streams
= av_mallocz(sizeof(*c
->streams
) * s
->nb_streams
);
574 ret
= AVERROR(ENOMEM
);
578 for (i
= 0; i
< s
->nb_streams
; i
++) {
579 OutputStream
*os
= &c
->streams
[i
];
580 AVFormatContext
*ctx
;
582 AVDictionary
*opts
= NULL
;
585 os
->bit_rate
= s
->streams
[i
]->codec
->bit_rate
;
587 snprintf(os
->bandwidth_str
, sizeof(os
->bandwidth_str
),
588 " bandwidth=\"%d\"", os
->bit_rate
);
590 int level
= s
->strict_std_compliance
>= FF_COMPLIANCE_STRICT ?
591 AV_LOG_ERROR
: AV_LOG_WARNING
;
592 av_log(s
, level
, "No bit rate set for stream %d\n", i
);
593 if (s
->strict_std_compliance
>= FF_COMPLIANCE_STRICT
) {
594 ret
= AVERROR(EINVAL
);
599 ctx
= avformat_alloc_context();
601 ret
= AVERROR(ENOMEM
);
605 ctx
->oformat
= oformat
;
606 ctx
->interrupt_callback
= s
->interrupt_callback
;
608 if (!(st
= avformat_new_stream(ctx
, NULL
))) {
609 ret
= AVERROR(ENOMEM
);
612 avcodec_copy_context(st
->codec
, s
->streams
[i
]->codec
);
613 st
->sample_aspect_ratio
= s
->streams
[i
]->sample_aspect_ratio
;
614 st
->time_base
= s
->streams
[i
]->time_base
;
615 ctx
->avoid_negative_ts
= s
->avoid_negative_ts
;
617 ctx
->pb
= avio_alloc_context(os
->iobuf
, sizeof(os
->iobuf
), AVIO_FLAG_WRITE
, os
, NULL
, dash_write
, NULL
);
619 ret
= AVERROR(ENOMEM
);
623 if (c
->single_file
) {
624 if (c
->single_file_name
)
625 dash_fill_tmpl_params(os
->initfile
, sizeof(os
->initfile
), c
->single_file_name
, i
, 0, os
->bit_rate
, 0);
627 snprintf(os
->initfile
, sizeof(os
->initfile
), "%s-stream%d.m4s", basename
, i
);
629 dash_fill_tmpl_params(os
->initfile
, sizeof(os
->initfile
), c
->init_seg_name
, i
, 0, os
->bit_rate
, 0);
631 snprintf(filename
, sizeof(filename
), "%s%s", c
->dirname
, os
->initfile
);
632 ret
= ffurl_open(&os
->out
, filename
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
635 os
->init_start_pos
= 0;
637 av_dict_set(&opts
, "movflags", "frag_custom+dash+delay_moov", 0);
638 if ((ret
= avformat_write_header(ctx
, &opts
)) < 0) {
645 av_log(s
, AV_LOG_VERBOSE
, "Representation %d init segment will be written to: %s\n", i
, filename
);
647 s
->streams
[i
]->time_base
= st
->time_base
;
648 // If the muxer wants to shift timestamps, request to have them shifted
649 // already before being handed to this muxer, so we don't have mismatches
650 // between the MPD and the actual segments.
651 s
->avoid_negative_ts
= ctx
->avoid_negative_ts
;
652 if (st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
)
654 else if (st
->codec
->codec_type
== AVMEDIA_TYPE_AUDIO
)
657 set_codec_str(s
, st
->codec
, os
->codec_str
, sizeof(os
->codec_str
));
658 os
->first_pts
= AV_NOPTS_VALUE
;
659 os
->max_pts
= AV_NOPTS_VALUE
;
660 os
->last_dts
= AV_NOPTS_VALUE
;
661 os
->segment_index
= 1;
664 if (!c
->has_video
&& c
->min_seg_duration
<= 0) {
665 av_log(s
, AV_LOG_WARNING
, "no video stream and no min seg duration set\n");
666 ret
= AVERROR(EINVAL
);
668 ret
= write_manifest(s
, 0);
670 av_log(s
, AV_LOG_VERBOSE
, "Manifest written to: %s\n", s
->filename
);
678 static int add_segment(OutputStream
*os
, const char *file
,
679 int64_t time
, int duration
,
680 int64_t start_pos
, int64_t range_length
,
681 int64_t index_length
)
685 if (os
->nb_segments
>= os
->segments_size
) {
686 os
->segments_size
= (os
->segments_size
+ 1) * 2;
687 if ((err
= av_reallocp(&os
->segments
, sizeof(*os
->segments
) *
688 os
->segments_size
)) < 0) {
689 os
->segments_size
= 0;
694 seg
= av_mallocz(sizeof(*seg
));
696 return AVERROR(ENOMEM
);
697 av_strlcpy(seg
->file
, file
, sizeof(seg
->file
));
699 if (seg
->time
< 0) // If pts<0, it is expected to be cut away with an edit list
701 seg
->duration
= duration
;
702 seg
->start_pos
= start_pos
;
703 seg
->range_length
= range_length
;
704 seg
->index_length
= index_length
;
705 os
->segments
[os
->nb_segments
++] = seg
;
710 static void write_styp(AVIOContext
*pb
)
713 ffio_wfourcc(pb
, "styp");
714 ffio_wfourcc(pb
, "msdh");
715 avio_wb32(pb
, 0); /* minor */
716 ffio_wfourcc(pb
, "msdh");
717 ffio_wfourcc(pb
, "msix");
720 static void find_index_range(AVFormatContext
*s
, const char *full_path
,
721 int64_t pos
, int *index_length
)
727 ret
= ffurl_open(&fd
, full_path
, AVIO_FLAG_READ
, &s
->interrupt_callback
, NULL
);
730 if (ffurl_seek(fd
, pos
, SEEK_SET
) != pos
) {
734 ret
= ffurl_read(fd
, buf
, 8);
738 if (AV_RL32(&buf
[4]) != MKTAG('s', 'i', 'd', 'x'))
740 *index_length
= AV_RB32(&buf
[0]);
743 static int update_stream_extradata(AVFormatContext
*s
, OutputStream
*os
,
744 AVCodecContext
*codec
)
748 if (os
->ctx
->streams
[0]->codec
->extradata_size
|| !codec
->extradata_size
)
751 extradata
= av_malloc(codec
->extradata_size
);
754 return AVERROR(ENOMEM
);
756 memcpy(extradata
, codec
->extradata
, codec
->extradata_size
);
758 os
->ctx
->streams
[0]->codec
->extradata
= extradata
;
759 os
->ctx
->streams
[0]->codec
->extradata_size
= codec
->extradata_size
;
761 set_codec_str(s
, codec
, os
->codec_str
, sizeof(os
->codec_str
));
766 static int dash_flush(AVFormatContext
*s
, int final
, int stream
)
768 DASHContext
*c
= s
->priv_data
;
770 int cur_flush_segment_index
= 0;
772 cur_flush_segment_index
= c
->streams
[stream
].segment_index
;
774 for (i
= 0; i
< s
->nb_streams
; i
++) {
775 OutputStream
*os
= &c
->streams
[i
];
776 char filename
[1024] = "", full_path
[1024], temp_path
[1024];
778 int range_length
, index_length
= 0;
780 if (!os
->packets_written
)
783 // Flush the single stream that got a keyframe right now.
784 // Flush all audio streams as well, in sync with video keyframes,
785 // but not the other video streams.
786 if (stream
>= 0 && i
!= stream
) {
787 if (s
->streams
[i
]->codec
->codec_type
!= AVMEDIA_TYPE_AUDIO
)
789 // Make sure we don't flush audio streams multiple times, when
790 // all video streams are flushed one at a time.
791 if (c
->has_video
&& os
->segment_index
> cur_flush_segment_index
)
795 if (!os
->init_range_length
) {
796 av_write_frame(os
->ctx
, NULL
);
797 os
->init_range_length
= avio_tell(os
->ctx
->pb
);
798 if (!c
->single_file
) {
799 ffurl_close(os
->out
);
804 start_pos
= avio_tell(os
->ctx
->pb
);
806 if (!c
->single_file
) {
807 dash_fill_tmpl_params(filename
, sizeof(filename
), c
->media_seg_name
, i
, os
->segment_index
, os
->bit_rate
, os
->start_pts
);
808 snprintf(full_path
, sizeof(full_path
), "%s%s", c
->dirname
, filename
);
809 snprintf(temp_path
, sizeof(temp_path
), "%s.tmp", full_path
);
810 ret
= ffurl_open(&os
->out
, temp_path
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
813 write_styp(os
->ctx
->pb
);
815 snprintf(full_path
, sizeof(full_path
), "%s%s", c
->dirname
, os
->initfile
);
818 av_write_frame(os
->ctx
, NULL
);
819 avio_flush(os
->ctx
->pb
);
820 os
->packets_written
= 0;
822 range_length
= avio_tell(os
->ctx
->pb
) - start_pos
;
823 if (c
->single_file
) {
824 find_index_range(s
, full_path
, start_pos
, &index_length
);
826 ffurl_close(os
->out
);
828 ret
= ff_rename(temp_path
, full_path
);
832 add_segment(os
, filename
, os
->start_pts
, os
->max_pts
- os
->start_pts
, start_pos
, range_length
, index_length
);
833 av_log(s
, AV_LOG_VERBOSE
, "Representation %d media segment %d written to: %s\n", i
, os
->segment_index
, full_path
);
836 if (c
->window_size
|| (final
&& c
->remove_at_exit
)) {
837 for (i
= 0; i
< s
->nb_streams
; i
++) {
838 OutputStream
*os
= &c
->streams
[i
];
840 int remove
= os
->nb_segments
- c
->window_size
- c
->extra_window_size
;
841 if (final
&& c
->remove_at_exit
)
842 remove
= os
->nb_segments
;
844 for (j
= 0; j
< remove
; j
++) {
846 snprintf(filename
, sizeof(filename
), "%s%s", c
->dirname
, os
->segments
[j
]->file
);
848 av_free(os
->segments
[j
]);
850 os
->nb_segments
-= remove
;
851 memmove(os
->segments
, os
->segments
+ remove
, os
->nb_segments
* sizeof(*os
->segments
));
857 ret
= write_manifest(s
, final
);
861 static int dash_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
863 DASHContext
*c
= s
->priv_data
;
864 AVStream
*st
= s
->streams
[pkt
->stream_index
];
865 OutputStream
*os
= &c
->streams
[pkt
->stream_index
];
866 int64_t seg_end_duration
= (os
->segment_index
) * (int64_t) c
->min_seg_duration
;
869 ret
= update_stream_extradata(s
, os
, st
->codec
);
873 // Fill in a heuristic guess of the packet duration, if none is available.
874 // The mp4 muxer will do something similar (for the last packet in a fragment)
875 // if nothing is set (setting it for the other packets doesn't hurt).
876 // By setting a nonzero duration here, we can be sure that the mp4 muxer won't
877 // invoke its heuristic (this doesn't have to be identical to that algorithm),
878 // so that we know the exact timestamps of fragments.
879 if (!pkt
->duration
&& os
->last_dts
!= AV_NOPTS_VALUE
)
880 pkt
->duration
= pkt
->dts
- os
->last_dts
;
881 os
->last_dts
= pkt
->dts
;
883 // If forcing the stream to start at 0, the mp4 muxer will set the start
884 // timestamps to 0. Do the same here, to avoid mismatches in duration/timestamps.
885 if (os
->first_pts
== AV_NOPTS_VALUE
&&
886 s
->avoid_negative_ts
== AVFMT_AVOID_NEG_TS_MAKE_ZERO
) {
887 pkt
->pts
-= pkt
->dts
;
891 if (os
->first_pts
== AV_NOPTS_VALUE
)
892 os
->first_pts
= pkt
->pts
;
894 if ((!c
->has_video
|| st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
) &&
895 pkt
->flags
& AV_PKT_FLAG_KEY
&& os
->packets_written
&&
896 av_compare_ts(pkt
->pts
- os
->first_pts
, st
->time_base
,
897 seg_end_duration
, AV_TIME_BASE_Q
) >= 0) {
898 int64_t prev_duration
= c
->last_duration
;
900 c
->last_duration
= av_rescale_q(pkt
->pts
- os
->start_pts
,
903 c
->total_duration
= av_rescale_q(pkt
->pts
- os
->first_pts
,
907 if ((!c
->use_timeline
|| !c
->use_template
) && prev_duration
) {
908 if (c
->last_duration
< prev_duration
*9/10 ||
909 c
->last_duration
> prev_duration
*11/10) {
910 av_log(s
, AV_LOG_WARNING
,
911 "Segment durations differ too much, enable use_timeline "
912 "and use_template, or keep a stricter keyframe interval\n");
916 if ((ret
= dash_flush(s
, 0, pkt
->stream_index
)) < 0)
920 if (!os
->packets_written
) {
921 // If we wrote a previous segment, adjust the start time of the segment
922 // to the end of the previous one (which is the same as the mp4 muxer
923 // does). This avoids gaps in the timeline.
924 if (os
->max_pts
!= AV_NOPTS_VALUE
)
925 os
->start_pts
= os
->max_pts
;
927 os
->start_pts
= pkt
->pts
;
929 if (os
->max_pts
== AV_NOPTS_VALUE
)
930 os
->max_pts
= pkt
->pts
+ pkt
->duration
;
932 os
->max_pts
= FFMAX(os
->max_pts
, pkt
->pts
+ pkt
->duration
);
933 os
->packets_written
++;
934 return ff_write_chained(os
->ctx
, 0, pkt
, s
);
937 static int dash_write_trailer(AVFormatContext
*s
)
939 DASHContext
*c
= s
->priv_data
;
941 if (s
->nb_streams
> 0) {
942 OutputStream
*os
= &c
->streams
[0];
943 // If no segments have been written so far, try to do a crude
944 // guess of the segment duration
945 if (!c
->last_duration
)
946 c
->last_duration
= av_rescale_q(os
->max_pts
- os
->start_pts
,
947 s
->streams
[0]->time_base
,
949 c
->total_duration
= av_rescale_q(os
->max_pts
- os
->first_pts
,
950 s
->streams
[0]->time_base
,
953 dash_flush(s
, 1, -1);
955 if (c
->remove_at_exit
) {
958 for (i
= 0; i
< s
->nb_streams
; i
++) {
959 OutputStream
*os
= &c
->streams
[i
];
960 snprintf(filename
, sizeof(filename
), "%s%s", c
->dirname
, os
->initfile
);
970 #define OFFSET(x) offsetof(DASHContext, x)
971 #define E AV_OPT_FLAG_ENCODING_PARAM
972 static const AVOption options
[] = {
973 { "window_size", "number of segments kept in the manifest", OFFSET(window_size
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, INT_MAX
, E
},
974 { "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
},
975 { "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration
), AV_OPT_TYPE_INT64
, { .i64
= 5000000 }, 0, INT_MAX
, E
},
976 { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, 1, E
},
977 { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template
), AV_OPT_TYPE_INT
, { .i64
= 1 }, 0, 1, E
},
978 { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline
), AV_OPT_TYPE_INT
, { .i64
= 1 }, 0, 1, E
},
979 { "single_file", "Store all segments in one file, accessed using byte ranges", OFFSET(single_file
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, 1, E
},
980 { "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
},
981 { "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
},
982 { "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
},
986 static const AVClass dash_class
= {
987 .class_name
= "dash muxer",
988 .item_name
= av_default_item_name
,
990 .version
= LIBAVUTIL_VERSION_INT
,
993 AVOutputFormat ff_dash_muxer
= {
995 .long_name
= NULL_IF_CONFIG_SMALL("DASH Muxer"),
996 .priv_data_size
= sizeof(DASHContext
),
997 .audio_codec
= AV_CODEC_ID_AAC
,
998 .video_codec
= AV_CODEC_ID_H264
,
999 .flags
= AVFMT_GLOBALHEADER
| AVFMT_NOFILE
| AVFMT_TS_NEGATIVE
,
1000 .write_header
= dash_write_header
,
1001 .write_packet
= dash_write_packet
,
1002 .write_trailer
= dash_write_trailer
,
1003 .codec_tag
= (const AVCodecTag
* const []){ ff_mp4_obj_type
, 0 },
1004 .priv_class
= &dash_class
,