1 /*
2 * WebM DASH Manifest XML muxer
3 * Copyright (c) 2014 Vignesh Venkatasubramanian
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 /*
23 * WebM DASH Specification:
24 * https://sites.google.com/a/webmproject.org/wiki/adaptive-streaming/webm-dash-specification
25 * ISO DASH Specification:
26 * http://standards.iso.org/ittf/PubliclyAvailableStandards/c065274_ISO_IEC_23009-1_2014.zip
27 */
28
30 #include <stdint.h>
31 #include <string.h>
33
37
43
45
51
65
67 {
69 }
70
72 {
75 for (
i = 0;
i <
s->nb_streams;
i++) {
80 }
82 }
83
85 {
88 double min_buffer_time = 1.0;
89 avio_printf(pb,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
91 avio_printf(pb,
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
92 avio_printf(pb,
" xmlns=\"urn:mpeg:DASH:schema:MPD:2011\"\n");
93 avio_printf(pb,
" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011\"\n");
94 avio_printf(pb,
" type=\"%s\"\n",
w->is_live ?
"dynamic" :
"static");
96 avio_printf(pb,
" mediaPresentationDuration=\"PT%gS\"\n",
98 }
99 avio_printf(pb,
" minBufferTime=\"PT%gS\"\n", min_buffer_time);
101 w->is_live ?
"urn:mpeg:dash:profile:isoff-live:2011" :
"urn:mpeg:dash:profile:webm-on-demand:2012",
102 w->is_live ?
"\n" :
">\n");
104 time_t local_time = time(
NULL);
105 struct tm gmt_buffer;
106 struct tm *gmt =
gmtime_r(&local_time, &gmt_buffer);
107 char gmt_iso[21];
108 if (!strftime(gmt_iso, 21, "%Y-%m-%dT%H:%M:%SZ", gmt)) {
110 }
113 }
114 avio_printf(pb,
" availabilityStartTime=\"%s\"\n", gmt_iso);
115 avio_printf(pb,
" timeShiftBufferDepth=\"PT%gS\"\n",
w->time_shift_buffer_depth);
116 avio_printf(pb,
" minimumUpdatePeriod=\"PT%dS\"",
w->minimum_update_period);
118 if (
w->utc_timing_url) {
120 avio_printf(pb,
" schemeIdUri=\"urn:mpeg:dash:utc:http-iso:2014\"\n");
121 avio_printf(pb,
" value=\"%s\"/>\n",
w->utc_timing_url);
122 }
123 }
124 return 0;
125 }
126
128 {
130 }
131
133 {
137 if (!gold) return 0;
142 }
143 return 1;
144 }
145
147 {
153 if (!gold_track_num) return 0;
159 if (!track_num ||
165 return 0;
166 }
167 }
168 return 1;
169 }
170
171 /*
172 * Writes a Representation within an Adaptation Set. Returns 0 on success and
173 * < 0 on failure.
174 */
176 int output_width, int output_height,
177 int output_sample_rate)
178 {
183 const char *bandwidth_str;
185 if (bandwidth) {
186 bandwidth_str = bandwidth->
value;
187 }
else if (
w->is_live) {
188 // if bandwidth for live was not provided, use a default
190 } else {
192 }
193 avio_printf(pb,
" bandwidth=\"%s\"", bandwidth_str);
201 // For live streams, Codec and Mime Type always go in the Representation tag.
205 // For live streams, subsegments always start with key frames. So this
206 // is always 1.
209 } else {
214 if (!irange || !cues_start || !cues_end || !filename)
216
224 }
226 return 0;
227 }
228
229 /*
230 * Checks if width of all streams are the same. Returns 1 if true, 0 otherwise.
231 */
233 {
236 first_width =
s->streams[as->
streams[0]]->codecpar->width;
238 if (first_width !=
s->streams[as->
streams[
i]]->codecpar->width)
239 return 0;
240 return 1;
241 }
242
243 /*
244 * Checks if height of all streams are the same. Returns 1 if true, 0 otherwise.
245 */
247 {
250 first_height =
s->streams[as->
streams[0]]->codecpar->height;
252 if (first_height !=
s->streams[as->
streams[
i]]->codecpar->height)
253 return 0;
254 return 1;
255 }
256
257 /*
258 * Checks if sample rate of all streams are the same. Returns 1 if true, 0 otherwise.
259 */
261 {
262 int first_sample_rate,
i;
264 first_sample_rate =
s->streams[as->
streams[0]]->codecpar->sample_rate;
266 if (first_sample_rate !=
s->streams[as->
streams[
i]]->codecpar->sample_rate)
267 return 0;
268 return 1;
269 }
270
272 {
275 for (
i = 0;
i <
w->nb_as;
i++) {
277 }
280 }
281
282 /*
283 * Parses a live header filename and returns the position of the '_' and '.'
284 * delimiting <file_description> and <representation_id>.
285 *
286 * Name of the header file should conform to the following pattern:
287 * <file_description>_<representation_id>.hdr where <file_description> can be
288 * anything. The chunks should be named according to the following pattern:
289 * <file_description>_<representation_id>_<chunk_number>.chk
290 */
292 char **period_pos)
293 {
294 *underscore_pos = strrchr(filename, '_');
295 if (!*underscore_pos)
297 *period_pos = strchr(*underscore_pos, '.');
298 if (!*period_pos)
300 return 0;
301 }
302
303 /*
304 * Writes an Adaptation Set. Returns 0 on success and < 0 on failure.
305 */
307 {
315 static const char boolean[2][6] = { "false", "true" };
316 int subsegmentStartsWithSAP = 1;
317
318 // Width, Height and Sample Rate will go in the AdaptationSet tag if they
319 // are the same for all contained Representations. otherwise, they will go
320 // on their respective Representation tag. For live streams, they always go
321 // in the Representation tag.
322 int width_in_as = 1, height_in_as = 1, sample_rate_in_as = 1;
326 } else {
328 }
329
334
336 if (lang)
338
345
350
354 if (!
w->is_live && (!kf || !strncmp(kf->
value,
"0", 1))) subsegmentStartsWithSAP = 0;
355 }
356 avio_printf(pb,
" subsegmentStartsWithSAP=\"%d\"", subsegmentStartsWithSAP);
358
362 char *underscore_pos, *period_pos;
364 if (!filename)
368 *underscore_pos = '0円';
369 avio_printf(pb,
"<ContentComponent id=\"1\" type=\"%s\"/>\n",
374 avio_printf(pb,
" media=\"%s_$RepresentationID$_$Number$.chk\"",
376 avio_printf(pb,
" startNumber=\"%d\"",
w->chunk_start_index);
377 avio_printf(pb,
" initialization=\"%s_$RepresentationID$.hdr\"",
380 *underscore_pos = '_';
381 }
382
384 char buf[25], *representation_id = buf, *underscore_pos, *period_pos;
390 if (!filename)
395 representation_id = underscore_pos + 1;
396 *period_pos = '0円';
397 } else {
398 snprintf(buf,
sizeof(buf),
"%d",
w->representation_id++);
399 }
401 !height_in_as, !sample_rate_in_as);
404 *period_pos = '.';
405 }
407 return 0;
408 }
409
411 {
413 char *
p =
w->adaptation_sets;
414 char *q;
415 enum { new_set, parsed_id, parsing_streams }
state;
416 if (!
w->adaptation_sets) {
419 }
420 // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on
422 while (1) {
424 if (
state == new_set)
425 break;
426 else
428 }
else if (
state == new_set && *
p ==
' ') {
430 continue;
431 }
else if (
state == new_set && !strncmp(
p,
"id=", 3)) {
432 void *mem =
av_realloc(
w->as,
sizeof(*
w->as) * (
w->nb_as + 1));
433 const char *comma;
438 w->as[
w->nb_as - 1].nb_streams = 0;
439 w->as[
w->nb_as - 1].streams =
NULL;
440 p += 3;
// consume "id="
441 q =
w->as[
w->nb_as - 1].id;
442 comma = strchr(
p,
',');
443 if (!comma || comma -
p >=
sizeof(
w->as[
w->nb_as - 1].id)) {
446 }
447 while (*
p !=
',') *q++ = *
p++;
448 *q = 0;
451 }
else if (
state == parsed_id && !strncmp(
p,
"streams=", 8)) {
452 p += 8;
// consume "streams="
453 state = parsing_streams;
454 }
else if (
state == parsing_streams) {
461 num = strtoll(
p, &q, 10);
462 if (!
av_isdigit(*
p) || (*q !=
' ' && *q !=
'0円' && *q !=
',') ||
463 num < 0 || num >=
s->nb_streams) {
466 }
468 if (*q == '0円') break;
469 if (*q ==
' ')
state = new_set;
471 } else {
472 return -1;
473 }
474 }
475 return 0;
476 }
477
479 {
481 double start = 0.0;
484
485 for (
unsigned i = 0;
i <
s->nb_streams;
i++) {
491 }
492
496 }
500 }
505 }
507
508 for (
i = 0;
i <
w->nb_as;
i++) {
512 }
513 }
514
520 }
521
523 {
525 }
526
527 #define OFFSET(x) offsetof(WebMDashMuxContext, x)
529 {
"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 },
534 {
"time_shift_buffer_depth",
"Smallest time (in seconds) shifting buffer for which any Representation is guaranteed to be available.",
OFFSET(time_shift_buffer_depth),
AV_OPT_TYPE_DOUBLE, { .dbl = 60.0 }, 1.0, DBL_MAX,
AV_OPT_FLAG_ENCODING_PARAM },
537 };
538
544 };
545
547 .
p.
name =
"webm_dash_manifest",
549 .p.mime_type = "application/xml",
550 .p.extensions = "xml",
555 };