2 * MPEG-DASH ISO BMFF segmenter
3 * Copyright (c) 2014 Martin Storsjo
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
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_dts
, start_dts
, end_dts
;
73 char bandwidth_str
[64];
78 typedef struct DASHContext
{
79 const AVClass
*class; /* Class for private options. */
81 int extra_window_size
;
87 OutputStream
*streams
;
88 int has_video
, has_audio
;
91 char availability_start_time
[100];
93 const char *single_file_name
;
94 const char *init_seg_name
;
95 const char *media_seg_name
;
98 static int dash_write(void *opaque
, uint8_t *buf
, int buf_size
)
100 OutputStream
*os
= opaque
;
102 ffurl_write(os
->out
, buf
, buf_size
);
107 static void set_codec_str(AVFormatContext
*s
, AVCodecContext
*codec
,
110 const AVCodecTag
*tags
[2] = { NULL
, NULL
};
112 if (codec
->codec_type
== AVMEDIA_TYPE_VIDEO
)
113 tags
[0] = ff_codec_movvideo_tags
;
114 else if (codec
->codec_type
== AVMEDIA_TYPE_AUDIO
)
115 tags
[0] = ff_codec_movaudio_tags
;
119 tag
= av_codec_get_tag(tags
, codec
->codec_id
);
127 if (!strcmp(str
, "mp4a") || !strcmp(str
, "mp4v")) {
129 tags
[0] = ff_mp4_obj_type
;
130 oti
= av_codec_get_tag(tags
, codec
->codec_id
);
132 av_strlcatf(str
, size
, ".%02x", oti
);
136 if (tag
== MKTAG('m', 'p', '4', 'a')) {
137 if (codec
->extradata_size
>= 2) {
138 int aot
= codec
->extradata
[0] >> 3;
140 aot
= ((AV_RB16(codec
->extradata
) >> 5) & 0x3f) + 32;
141 av_strlcatf(str
, size
, ".%d", aot
);
143 } else if (tag
== MKTAG('m', 'p', '4', 'v')) {
144 // Unimplemented, should output ProfileLevelIndication as a decimal number
145 av_log(s
, AV_LOG_WARNING
, "Incomplete RFC 6381 codec string for mp4v\n");
147 } else if (!strcmp(str
, "avc1")) {
148 uint8_t *tmpbuf
= NULL
;
149 uint8_t *extradata
= codec
->extradata
;
150 int extradata_size
= codec
->extradata_size
;
153 if (extradata
[0] != 1) {
155 if (avio_open_dyn_buf(&pb
) < 0)
157 if (ff_isom_write_avcc(pb
, extradata
, extradata_size
) < 0) {
158 avio_close_dyn_buf(pb
, &tmpbuf
);
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=\"%d\" ", c
->last_duration
);
209 avio_printf(out
, "initialization=\"%s\" media=\"%s\" startNumber=\"%d\">\n", c
->init_seg_name
, c
->media_seg_name
, c
->use_timeline
? start_number
: 1);
210 if (c
->use_timeline
) {
211 avio_printf(out
, "\t\t\t\t\t<SegmentTimeline>\n");
212 for (i
= start_index
; i
< os
->nb_segments
; ) {
213 Segment
*seg
= os
->segments
[i
];
215 avio_printf(out
, "\t\t\t\t\t\t<S ");
216 if (i
== start_index
)
217 avio_printf(out
, "t=\"%"PRId64
"\" ", seg
->time
);
218 avio_printf(out
, "d=\"%d\" ", seg
->duration
);
219 while (i
+ repeat
+ 1 < os
->nb_segments
&& os
->segments
[i
+ repeat
+ 1]->duration
== seg
->duration
)
222 avio_printf(out
, "r=\"%d\" ", repeat
);
223 avio_printf(out
, "/>\n");
226 avio_printf(out
, "\t\t\t\t\t</SegmentTimeline>\n");
228 avio_printf(out
, "\t\t\t\t</SegmentTemplate>\n");
229 } else if (c
->single_file
) {
230 avio_printf(out
, "\t\t\t\t<BaseURL>%s</BaseURL>\n", os
->initfile
);
231 avio_printf(out
, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE
, c
->last_duration
, start_number
);
232 avio_printf(out
, "\t\t\t\t\t<Initialization range=\"%"PRId64
"-%"PRId64
"\" />\n", os
->init_start_pos
, os
->init_start_pos
+ os
->init_range_length
- 1);
233 for (i
= start_index
; i
< os
->nb_segments
; i
++) {
234 Segment
*seg
= os
->segments
[i
];
235 avio_printf(out
, "\t\t\t\t\t<SegmentURL mediaRange=\"%"PRId64
"-%"PRId64
"\" ", seg
->start_pos
, seg
->start_pos
+ seg
->range_length
- 1);
236 if (seg
->index_length
)
237 avio_printf(out
, "indexRange=\"%"PRId64
"-%"PRId64
"\" ", seg
->start_pos
, seg
->start_pos
+ seg
->index_length
- 1);
238 avio_printf(out
, "/>\n");
240 avio_printf(out
, "\t\t\t\t</SegmentList>\n");
242 avio_printf(out
, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE
, c
->last_duration
, start_number
);
243 avio_printf(out
, "\t\t\t\t\t<Initialization sourceURL=\"%s\" />\n", os
->initfile
);
244 for (i
= start_index
; i
< os
->nb_segments
; i
++) {
245 Segment
*seg
= os
->segments
[i
];
246 avio_printf(out
, "\t\t\t\t\t<SegmentURL media=\"%s\" />\n", seg
->file
);
248 avio_printf(out
, "\t\t\t\t</SegmentList>\n");
252 static DASHTmplId
dash_read_tmpl_id(const char *identifier
, char *format_tag
,
253 size_t format_tag_size
, const char **ptr
) {
254 const char *next_ptr
;
255 DASHTmplId id_type
= DASH_TMPL_ID_UNDEFINED
;
257 if (av_strstart(identifier
, "$$", &next_ptr
)) {
258 id_type
= DASH_TMPL_ID_ESCAPE
;
260 } else if (av_strstart(identifier
, "$RepresentationID$", &next_ptr
)) {
261 id_type
= DASH_TMPL_ID_REP_ID
;
262 // default to basic format, as $RepresentationID$ identifiers
263 // are not allowed to have custom format-tags.
264 av_strlcpy(format_tag
, "%d", format_tag_size
);
266 } else { // the following identifiers may have an explicit format_tag
267 if (av_strstart(identifier
, "$Number", &next_ptr
))
268 id_type
= DASH_TMPL_ID_NUMBER
;
269 else if (av_strstart(identifier
, "$Bandwidth", &next_ptr
))
270 id_type
= DASH_TMPL_ID_BANDWIDTH
;
271 else if (av_strstart(identifier
, "$Time", &next_ptr
))
272 id_type
= DASH_TMPL_ID_TIME
;
274 id_type
= DASH_TMPL_ID_UNDEFINED
;
276 // next parse the dash format-tag and generate a c-string format tag
277 // (next_ptr now points at the first '%' at the beginning of the format-tag)
278 if (id_type
!= DASH_TMPL_ID_UNDEFINED
) {
279 const char *number_format
= DASH_TMPL_ID_TIME
? "lld" : "d";
280 if (next_ptr
[0] == '$') { // no dash format-tag
281 snprintf(format_tag
, format_tag_size
, "%%%s", number_format
);
284 const char *width_ptr
;
285 // only tolerate single-digit width-field (i.e. up to 9-digit width)
286 if (av_strstart(next_ptr
, "%0", &width_ptr
) &&
287 av_isdigit(width_ptr
[0]) &&
288 av_strstart(&width_ptr
[1], "d$", &next_ptr
)) {
289 // yes, we're using a format tag to build format_tag.
290 snprintf(format_tag
, format_tag_size
, "%s%c%s", "%0", width_ptr
[0], number_format
);
293 av_log(NULL
, AV_LOG_WARNING
, "Failed to parse format-tag beginning with %s. Expected either a "
294 "closing '$' character or a format-string like '%%0[width]d', "
295 "where width must be a single digit\n", next_ptr
);
296 id_type
= DASH_TMPL_ID_UNDEFINED
;
304 static void dash_fill_tmpl_params(char *dst
, size_t buffer_size
,
305 const char *template, int rep_id
,
306 int number
, int bit_rate
,
309 const char *t_cur
= template;
310 while (dst_pos
< buffer_size
- 1 && *t_cur
) {
311 char format_tag
[7]; // May be "%d", "%0Xd", or "%0Xlld" (for $Time$), where X is in [0-9]
314 const char *t_next
= strchr(t_cur
, '$'); // copy over everything up to the first '$' character
316 int num_copy_bytes
= FFMIN(t_next
- t_cur
, buffer_size
- dst_pos
- 1);
317 av_strlcpy(&dst
[dst_pos
], t_cur
, num_copy_bytes
+ 1);
319 dst_pos
+= num_copy_bytes
;
321 } else { // no more DASH identifiers to substitute - just copy the rest over and break
322 av_strlcpy(&dst
[dst_pos
], t_cur
, buffer_size
- dst_pos
);
326 if (dst_pos
>= buffer_size
- 1 || !*t_cur
)
329 // t_cur is now pointing to a '$' character
330 id_type
= dash_read_tmpl_id(t_cur
, format_tag
, sizeof(format_tag
), &t_next
);
332 case DASH_TMPL_ID_ESCAPE
:
333 av_strlcpy(&dst
[dst_pos
], "$", 2);
336 case DASH_TMPL_ID_REP_ID
:
337 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, rep_id
);
339 case DASH_TMPL_ID_NUMBER
:
340 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, number
);
342 case DASH_TMPL_ID_BANDWIDTH
:
343 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, bit_rate
);
345 case DASH_TMPL_ID_TIME
:
346 n
= snprintf(&dst
[dst_pos
], buffer_size
- dst_pos
, format_tag
, time
);
348 case DASH_TMPL_ID_UNDEFINED
:
349 // copy over one byte and advance
350 av_strlcpy(&dst
[dst_pos
], t_cur
, 2);
355 // t_next points just past the processed identifier
356 // n is the number of bytes that were attempted to be written to dst
357 // (may have failed to write all because buffer_size).
360 dst_pos
+= FFMIN(n
, buffer_size
- dst_pos
- 1);
365 static char *xmlescape(const char *str
) {
366 int outlen
= strlen(str
)*3/2 + 6;
367 char *out
= av_realloc(NULL
, outlen
+ 1);
371 for (; *str
; str
++) {
372 if (pos
+ 6 > outlen
) {
374 outlen
= 2 * outlen
+ 6;
375 tmp
= av_realloc(out
, outlen
+ 1);
383 memcpy(&out
[pos
], "&", 5);
385 } else if (*str
== '<') {
386 memcpy(&out
[pos
], "<", 4);
388 } else if (*str
== '>') {
389 memcpy(&out
[pos
], ">", 4);
391 } else if (*str
== '\'') {
392 memcpy(&out
[pos
], "'", 6);
394 } else if (*str
== '\"') {
395 memcpy(&out
[pos
], """, 6);
405 static void write_time(AVIOContext
*out
, int64_t time
)
407 int seconds
= time
/ AV_TIME_BASE
;
408 int fractions
= time
% AV_TIME_BASE
;
409 int minutes
= seconds
/ 60;
410 int hours
= minutes
/ 60;
413 avio_printf(out
, "PT");
415 avio_printf(out
, "%dH", hours
);
416 if (hours
|| minutes
)
417 avio_printf(out
, "%dM", minutes
);
418 avio_printf(out
, "%d.%dS", seconds
, fractions
/ (AV_TIME_BASE
/ 10));
421 static int write_manifest(AVFormatContext
*s
, int final
)
423 DASHContext
*c
= s
->priv_data
;
425 char temp_filename
[1024];
427 AVDictionaryEntry
*title
= av_dict_get(s
->metadata
, "title", NULL
, 0);
429 snprintf(temp_filename
, sizeof(temp_filename
), "%s.tmp", s
->filename
);
430 ret
= avio_open2(&out
, temp_filename
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
432 av_log(s
, AV_LOG_ERROR
, "Unable to open %s for writing\n", temp_filename
);
435 avio_printf(out
, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
436 avio_printf(out
, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
437 "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
438 "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
439 "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n"
440 "\tprofiles=\"urn:mpeg:dash:profile:isoff-live:2011\"\n"
441 "\ttype=\"%s\"\n", final
? "static" : "dynamic");
443 avio_printf(out
, "\tmediaPresentationDuration=\"");
444 write_time(out
, c
->total_duration
);
445 avio_printf(out
, "\"\n");
447 int update_period
= c
->last_duration
/ AV_TIME_BASE
;
448 if (c
->use_template
&& !c
->use_timeline
)
450 avio_printf(out
, "\tminimumUpdatePeriod=\"PT%dS\"\n", update_period
);
451 avio_printf(out
, "\tsuggestedPresentationDelay=\"PT%dS\"\n", c
->last_duration
/ AV_TIME_BASE
);
452 if (!c
->availability_start_time
[0] && s
->nb_streams
> 0 && c
->streams
[0].nb_segments
> 0) {
453 time_t t
= time(NULL
);
454 struct tm
*ptm
, tmbuf
;
455 ptm
= gmtime_r(&t
, &tmbuf
);
457 if (!strftime(c
->availability_start_time
, sizeof(c
->availability_start_time
),
458 "%Y-%m-%dT%H:%M:%S", ptm
))
459 c
->availability_start_time
[0] = '\0';
462 if (c
->availability_start_time
[0])
463 avio_printf(out
, "\tavailabilityStartTime=\"%s\"\n", c
->availability_start_time
);
464 if (c
->window_size
&& c
->use_template
) {
465 avio_printf(out
, "\ttimeShiftBufferDepth=\"");
466 write_time(out
, c
->last_duration
* c
->window_size
);
467 avio_printf(out
, "\"\n");
470 avio_printf(out
, "\tminBufferTime=\"");
471 write_time(out
, c
->last_duration
);
472 avio_printf(out
, "\">\n");
473 avio_printf(out
, "\t<ProgramInformation>\n");
475 char *escaped
= xmlescape(title
->value
);
476 avio_printf(out
, "\t\t<Title>%s</Title>\n", escaped
);
479 avio_printf(out
, "\t</ProgramInformation>\n");
480 if (c
->window_size
&& s
->nb_streams
> 0 && c
->streams
[0].nb_segments
> 0 && !c
->use_template
) {
481 OutputStream
*os
= &c
->streams
[0];
482 int start_index
= FFMAX(os
->nb_segments
- c
->window_size
, 0);
483 int64_t start_time
= av_rescale_q(os
->segments
[start_index
]->time
, s
->streams
[0]->time_base
, AV_TIME_BASE_Q
);
484 avio_printf(out
, "\t<Period start=\"");
485 write_time(out
, start_time
);
486 avio_printf(out
, "\">\n");
488 avio_printf(out
, "\t<Period start=\"PT0.0S\">\n");
492 avio_printf(out
, "\t\t<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
493 for (i
= 0; i
< s
->nb_streams
; i
++) {
494 AVStream
*st
= s
->streams
[i
];
495 OutputStream
*os
= &c
->streams
[i
];
496 if (s
->streams
[i
]->codec
->codec_type
!= AVMEDIA_TYPE_VIDEO
)
498 avio_printf(out
, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\">\n", i
, os
->codec_str
, os
->bandwidth_str
, st
->codec
->width
, st
->codec
->height
);
499 output_segment_list(&c
->streams
[i
], out
, c
);
500 avio_printf(out
, "\t\t\t</Representation>\n");
502 avio_printf(out
, "\t\t</AdaptationSet>\n");
505 avio_printf(out
, "\t\t<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
506 for (i
= 0; i
< s
->nb_streams
; i
++) {
507 AVStream
*st
= s
->streams
[i
];
508 OutputStream
*os
= &c
->streams
[i
];
509 if (s
->streams
[i
]->codec
->codec_type
!= AVMEDIA_TYPE_AUDIO
)
511 avio_printf(out
, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", i
, os
->codec_str
, os
->bandwidth_str
, st
->codec
->sample_rate
);
512 avio_printf(out
, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st
->codec
->channels
);
513 output_segment_list(&c
->streams
[i
], out
, c
);
514 avio_printf(out
, "\t\t\t</Representation>\n");
516 avio_printf(out
, "\t\t</AdaptationSet>\n");
518 avio_printf(out
, "\t</Period>\n");
519 avio_printf(out
, "</MPD>\n");
522 return ff_rename(temp_filename
, s
->filename
, s
);
525 static int dash_write_header(AVFormatContext
*s
)
527 DASHContext
*c
= s
->priv_data
;
529 AVOutputFormat
*oformat
;
533 if (c
->single_file_name
)
538 av_strlcpy(c
->dirname
, s
->filename
, sizeof(c
->dirname
));
539 ptr
= strrchr(c
->dirname
, '/');
541 av_strlcpy(basename
, &ptr
[1], sizeof(basename
));
544 c
->dirname
[0] = '\0';
545 av_strlcpy(basename
, s
->filename
, sizeof(basename
));
548 ptr
= strrchr(basename
, '.');
552 oformat
= av_guess_format("mp4", NULL
, NULL
);
554 ret
= AVERROR_MUXER_NOT_FOUND
;
558 c
->streams
= av_mallocz(sizeof(*c
->streams
) * s
->nb_streams
);
560 ret
= AVERROR(ENOMEM
);
564 for (i
= 0; i
< s
->nb_streams
; i
++) {
565 OutputStream
*os
= &c
->streams
[i
];
566 AVFormatContext
*ctx
;
568 AVDictionary
*opts
= NULL
;
571 os
->bit_rate
= s
->streams
[i
]->codec
->bit_rate
?
572 s
->streams
[i
]->codec
->bit_rate
:
573 s
->streams
[i
]->codec
->rc_max_rate
;
575 snprintf(os
->bandwidth_str
, sizeof(os
->bandwidth_str
),
576 " bandwidth=\"%d\"", os
->bit_rate
);
578 int level
= s
->strict_std_compliance
>= FF_COMPLIANCE_STRICT
?
579 AV_LOG_ERROR
: AV_LOG_WARNING
;
580 av_log(s
, level
, "No bit rate set for stream %d\n", i
);
581 if (s
->strict_std_compliance
>= FF_COMPLIANCE_STRICT
) {
582 ret
= AVERROR(EINVAL
);
587 ctx
= avformat_alloc_context();
589 ret
= AVERROR(ENOMEM
);
593 ctx
->oformat
= oformat
;
594 ctx
->interrupt_callback
= s
->interrupt_callback
;
596 if (!(st
= avformat_new_stream(ctx
, NULL
))) {
597 ret
= AVERROR(ENOMEM
);
600 avcodec_copy_context(st
->codec
, s
->streams
[i
]->codec
);
601 st
->sample_aspect_ratio
= s
->streams
[i
]->sample_aspect_ratio
;
602 st
->time_base
= s
->streams
[i
]->time_base
;
603 ctx
->avoid_negative_ts
= s
->avoid_negative_ts
;
605 ctx
->pb
= avio_alloc_context(os
->iobuf
, sizeof(os
->iobuf
), AVIO_FLAG_WRITE
, os
, NULL
, dash_write
, NULL
);
607 ret
= AVERROR(ENOMEM
);
611 if (c
->single_file
) {
612 if (c
->single_file_name
)
613 dash_fill_tmpl_params(os
->initfile
, sizeof(os
->initfile
), c
->single_file_name
, i
, 0, os
->bit_rate
, 0);
615 snprintf(os
->initfile
, sizeof(os
->initfile
), "%s-stream%d.m4s", basename
, i
);
617 dash_fill_tmpl_params(os
->initfile
, sizeof(os
->initfile
), c
->init_seg_name
, i
, 0, os
->bit_rate
, 0);
619 snprintf(filename
, sizeof(filename
), "%s%s", c
->dirname
, os
->initfile
);
620 ret
= ffurl_open(&os
->out
, filename
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
623 os
->init_start_pos
= 0;
625 av_dict_set(&opts
, "movflags", "frag_custom+dash", 0);
626 if ((ret
= avformat_write_header(ctx
, &opts
)) < 0) {
633 if (c
->single_file
) {
634 os
->init_range_length
= avio_tell(ctx
->pb
);
636 ffurl_close(os
->out
);
640 s
->streams
[i
]->time_base
= st
->time_base
;
641 // If the muxer wants to shift timestamps, request to have them shifted
642 // already before being handed to this muxer, so we don't have mismatches
643 // between the MPD and the actual segments.
644 s
->avoid_negative_ts
= ctx
->avoid_negative_ts
;
645 if (st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
)
647 else if (st
->codec
->codec_type
== AVMEDIA_TYPE_AUDIO
)
650 set_codec_str(s
, os
->ctx
->streams
[0]->codec
, os
->codec_str
, sizeof(os
->codec_str
));
651 os
->first_dts
= AV_NOPTS_VALUE
;
652 os
->segment_index
= 1;
655 if (!c
->has_video
&& c
->min_seg_duration
<= 0) {
656 av_log(s
, AV_LOG_WARNING
, "no video stream and no min seg duration set\n");
657 ret
= AVERROR(EINVAL
);
659 ret
= write_manifest(s
, 0);
667 static int add_segment(OutputStream
*os
, const char *file
,
668 int64_t time
, int duration
,
669 int64_t start_pos
, int64_t range_length
,
670 int64_t index_length
)
674 if (os
->nb_segments
>= os
->segments_size
) {
675 os
->segments_size
= (os
->segments_size
+ 1) * 2;
676 if ((err
= av_reallocp(&os
->segments
, sizeof(*os
->segments
) *
677 os
->segments_size
)) < 0) {
678 os
->segments_size
= 0;
683 seg
= av_mallocz(sizeof(*seg
));
685 return AVERROR(ENOMEM
);
686 av_strlcpy(seg
->file
, file
, sizeof(seg
->file
));
688 seg
->duration
= duration
;
689 seg
->start_pos
= start_pos
;
690 seg
->range_length
= range_length
;
691 seg
->index_length
= index_length
;
692 os
->segments
[os
->nb_segments
++] = seg
;
697 static void write_styp(AVIOContext
*pb
)
700 ffio_wfourcc(pb
, "styp");
701 ffio_wfourcc(pb
, "msdh");
702 avio_wb32(pb
, 0); /* minor */
703 ffio_wfourcc(pb
, "msdh");
704 ffio_wfourcc(pb
, "msix");
707 static void find_index_range(AVFormatContext
*s
, const char *dirname
,
708 const char *filename
, int64_t pos
,
711 char full_path
[1024];
716 snprintf(full_path
, sizeof(full_path
), "%s%s", dirname
, filename
);
717 ret
= ffurl_open(&fd
, full_path
, AVIO_FLAG_READ
, &s
->interrupt_callback
, NULL
);
720 if (ffurl_seek(fd
, pos
, SEEK_SET
) != pos
) {
724 ret
= ffurl_read(fd
, buf
, 8);
728 if (AV_RL32(&buf
[4]) != MKTAG('s', 'i', 'd', 'x'))
730 *index_length
= AV_RB32(&buf
[0]);
733 static int dash_flush(AVFormatContext
*s
, int final
, int stream
)
735 DASHContext
*c
= s
->priv_data
;
737 int cur_flush_segment_index
= 0;
739 cur_flush_segment_index
= c
->streams
[stream
].segment_index
;
741 for (i
= 0; i
< s
->nb_streams
; i
++) {
742 OutputStream
*os
= &c
->streams
[i
];
743 char filename
[1024] = "", full_path
[1024], temp_path
[1024];
744 int64_t start_pos
= avio_tell(os
->ctx
->pb
);
745 int range_length
, index_length
= 0;
747 if (!os
->packets_written
)
750 // Flush the single stream that got a keyframe right now.
751 // Flush all audio streams as well, in sync with video keyframes,
752 // but not the other video streams.
753 if (stream
>= 0 && i
!= stream
) {
754 if (s
->streams
[i
]->codec
->codec_type
!= AVMEDIA_TYPE_AUDIO
)
756 // Make sure we don't flush audio streams multiple times, when
757 // all video streams are flushed one at a time.
758 if (c
->has_video
&& os
->segment_index
> cur_flush_segment_index
)
762 if (!c
->single_file
) {
763 dash_fill_tmpl_params(filename
, sizeof(filename
), c
->media_seg_name
, i
, os
->segment_index
, os
->bit_rate
, os
->start_dts
);
764 snprintf(full_path
, sizeof(full_path
), "%s%s", c
->dirname
, filename
);
765 snprintf(temp_path
, sizeof(temp_path
), "%s.tmp", full_path
);
766 ret
= ffurl_open(&os
->out
, temp_path
, AVIO_FLAG_WRITE
, &s
->interrupt_callback
, NULL
);
769 write_styp(os
->ctx
->pb
);
771 av_write_frame(os
->ctx
, NULL
);
772 avio_flush(os
->ctx
->pb
);
773 os
->packets_written
= 0;
775 range_length
= avio_tell(os
->ctx
->pb
) - start_pos
;
776 if (c
->single_file
) {
777 find_index_range(s
, c
->dirname
, os
->initfile
, start_pos
, &index_length
);
779 ffurl_close(os
->out
);
781 ret
= ff_rename(temp_path
, full_path
, s
);
785 add_segment(os
, filename
, os
->start_dts
, os
->end_dts
- os
->start_dts
, start_pos
, range_length
, index_length
);
788 if (c
->window_size
|| (final
&& c
->remove_at_exit
)) {
789 for (i
= 0; i
< s
->nb_streams
; i
++) {
790 OutputStream
*os
= &c
->streams
[i
];
792 int remove
= os
->nb_segments
- c
->window_size
- c
->extra_window_size
;
793 if (final
&& c
->remove_at_exit
)
794 remove
= os
->nb_segments
;
796 for (j
= 0; j
< remove
; j
++) {
798 snprintf(filename
, sizeof(filename
), "%s%s", c
->dirname
, os
->segments
[j
]->file
);
800 av_free(os
->segments
[j
]);
802 os
->nb_segments
-= remove
;
803 memmove(os
->segments
, os
->segments
+ remove
, os
->nb_segments
* sizeof(*os
->segments
));
809 ret
= write_manifest(s
, final
);
813 static int dash_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
815 DASHContext
*c
= s
->priv_data
;
816 AVStream
*st
= s
->streams
[pkt
->stream_index
];
817 OutputStream
*os
= &c
->streams
[pkt
->stream_index
];
818 int64_t seg_end_duration
= (os
->segment_index
) * (int64_t) c
->min_seg_duration
;
821 // If forcing the stream to start at 0, the mp4 muxer will set the start
822 // timestamps to 0. Do the same here, to avoid mismatches in duration/timestamps.
823 if (os
->first_dts
== AV_NOPTS_VALUE
&&
824 s
->avoid_negative_ts
== AVFMT_AVOID_NEG_TS_MAKE_ZERO
) {
825 pkt
->pts
-= pkt
->dts
;
829 if (os
->first_dts
== AV_NOPTS_VALUE
)
830 os
->first_dts
= pkt
->dts
;
832 if ((!c
->has_video
|| st
->codec
->codec_type
== AVMEDIA_TYPE_VIDEO
) &&
833 pkt
->flags
& AV_PKT_FLAG_KEY
&& os
->packets_written
&&
834 av_compare_ts(pkt
->dts
- os
->first_dts
, st
->time_base
,
835 seg_end_duration
, AV_TIME_BASE_Q
) >= 0) {
836 int64_t prev_duration
= c
->last_duration
;
838 c
->last_duration
= av_rescale_q(pkt
->dts
- os
->start_dts
,
841 c
->total_duration
= av_rescale_q(pkt
->dts
- os
->first_dts
,
845 if ((!c
->use_timeline
|| !c
->use_template
) && prev_duration
) {
846 if (c
->last_duration
< prev_duration
*9/10 ||
847 c
->last_duration
> prev_duration
*11/10) {
848 av_log(s
, AV_LOG_WARNING
,
849 "Segment durations differ too much, enable use_timeline "
850 "and use_template, or keep a stricter keyframe interval\n");
854 if ((ret
= dash_flush(s
, 0, pkt
->stream_index
)) < 0)
858 if (!os
->packets_written
)
859 os
->start_dts
= pkt
->dts
;
860 os
->end_dts
= pkt
->dts
+ pkt
->duration
;
861 os
->packets_written
++;
862 return ff_write_chained(os
->ctx
, 0, pkt
, s
, 0);
865 static int dash_write_trailer(AVFormatContext
*s
)
867 DASHContext
*c
= s
->priv_data
;
869 if (s
->nb_streams
> 0) {
870 OutputStream
*os
= &c
->streams
[0];
871 // If no segments have been written so far, try to do a crude
872 // guess of the segment duration
873 if (!c
->last_duration
)
874 c
->last_duration
= av_rescale_q(os
->end_dts
- os
->start_dts
,
875 s
->streams
[0]->time_base
,
877 c
->total_duration
= av_rescale_q(os
->end_dts
- os
->first_dts
,
878 s
->streams
[0]->time_base
,
881 dash_flush(s
, 1, -1);
883 if (c
->remove_at_exit
) {
886 for (i
= 0; i
< s
->nb_streams
; i
++) {
887 OutputStream
*os
= &c
->streams
[i
];
888 snprintf(filename
, sizeof(filename
), "%s%s", c
->dirname
, os
->initfile
);
898 #define OFFSET(x) offsetof(DASHContext, x)
899 #define E AV_OPT_FLAG_ENCODING_PARAM
900 static const AVOption options
[] = {
901 { "window_size", "number of segments kept in the manifest", OFFSET(window_size
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, INT_MAX
, E
},
902 { "extra_window_size", "number of segments kept outside of the manifest before removing from disk", OFFSET(extra_window_size
), AV_OPT_TYPE_INT
, { .i64
= 5 }, 0, INT_MAX
, E
},
903 { "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration
), AV_OPT_TYPE_INT64
, { .i64
= 5000000 }, 0, INT_MAX
, E
},
904 { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, 1, E
},
905 { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template
), AV_OPT_TYPE_INT
, { .i64
= 1 }, 0, 1, E
},
906 { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline
), AV_OPT_TYPE_INT
, { .i64
= 1 }, 0, 1, E
},
907 { "single_file", "Store all segments in one file, accessed using byte ranges", OFFSET(single_file
), AV_OPT_TYPE_INT
, { .i64
= 0 }, 0, 1, E
},
908 { "single_file_name", "DASH-templated name to be used for baseURL. Implies storing all segments in one file, accessed using byte ranges", OFFSET(single_file_name
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, E
},
909 { "init_seg_name", "DASH-templated name to used for the initialization segment", OFFSET(init_seg_name
), AV_OPT_TYPE_STRING
, {.str
= "init-stream$RepresentationID$.m4s"}, 0, 0, E
},
910 { "media_seg_name", "DASH-templated name to used for the media segments", OFFSET(media_seg_name
), AV_OPT_TYPE_STRING
, {.str
= "chunk-stream$RepresentationID$-$Number%05d$.m4s"}, 0, 0, E
},
914 static const AVClass dash_class
= {
915 .class_name
= "dash muxer",
916 .item_name
= av_default_item_name
,
918 .version
= LIBAVUTIL_VERSION_INT
,
921 AVOutputFormat ff_dash_muxer
= {
923 .long_name
= NULL_IF_CONFIG_SMALL("DASH Muxer"),
924 .priv_data_size
= sizeof(DASHContext
),
925 .audio_codec
= AV_CODEC_ID_AAC
,
926 .video_codec
= AV_CODEC_ID_H264
,
927 .flags
= AVFMT_GLOBALHEADER
| AVFMT_NOFILE
| AVFMT_TS_NEGATIVE
,
928 .write_header
= dash_write_header
,
929 .write_packet
= dash_write_packet
,
930 .write_trailer
= dash_write_trailer
,
931 .codec_tag
= (const AVCodecTag
* const []){ ff_mp4_obj_type
, 0 },
932 .priv_class
= &dash_class
,