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>
32
36
41
43
49
63
65 {
67 }
68
70 {
73 for (
i = 0;
i <
s->nb_streams;
i++) {
78 }
80 }
81
83 {
86 double min_buffer_time = 1.0;
87 avio_printf(pb,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
89 avio_printf(pb,
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
90 avio_printf(pb,
" xmlns=\"urn:mpeg:DASH:schema:MPD:2011\"\n");
91 avio_printf(pb,
" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011\"\n");
92 avio_printf(pb,
" type=\"%s\"\n",
w->is_live ?
"dynamic" :
"static");
94 avio_printf(pb,
" mediaPresentationDuration=\"PT%gS\"\n",
96 }
97 avio_printf(pb,
" minBufferTime=\"PT%gS\"\n", min_buffer_time);
99 w->is_live ?
"urn:mpeg:dash:profile:isoff-live:2011" :
"urn:mpeg:dash:profile:webm-on-demand:2012",
100 w->is_live ?
"\n" :
">\n");
102 time_t local_time = time(
NULL);
103 struct tm gmt_buffer;
104 struct tm *gmt =
gmtime_r(&local_time, &gmt_buffer);
105 char gmt_iso[21];
106 if (!strftime(gmt_iso, 21, "%Y-%m-%dT%H:%M:%SZ", gmt)) {
108 }
111 }
112 avio_printf(pb,
" availabilityStartTime=\"%s\"\n", gmt_iso);
113 avio_printf(pb,
" timeShiftBufferDepth=\"PT%gS\"\n",
w->time_shift_buffer_depth);
114 avio_printf(pb,
" minimumUpdatePeriod=\"PT%dS\"",
w->minimum_update_period);
116 if (
w->utc_timing_url) {
118 avio_printf(pb,
" schemeIdUri=\"urn:mpeg:dash:utc:http-iso:2014\"\n");
119 avio_printf(pb,
" value=\"%s\"/>\n",
w->utc_timing_url);
120 }
121 }
122 return 0;
123 }
124
126 {
128 }
129
131 {
135 if (!gold) return 0;
140 }
141 return 1;
142 }
143
145 {
151 if (!gold_track_num) return 0;
157 if (!track_num ||
163 return 0;
164 }
165 }
166 return 1;
167 }
168
169 /*
170 * Writes a Representation within an Adaptation Set. Returns 0 on success and
171 * < 0 on failure.
172 */
174 int output_width, int output_height,
175 int output_sample_rate)
176 {
181 const char *bandwidth_str;
183 if (bandwidth) {
184 bandwidth_str = bandwidth->
value;
185 }
else if (
w->is_live) {
186 // if bandwidth for live was not provided, use a default
188 } else {
190 }
191 avio_printf(pb,
" bandwidth=\"%s\"", bandwidth_str);
199 // For live streams, Codec and Mime Type always go in the Representation tag.
203 // For live streams, subsegments always start with key frames. So this
204 // is always 1.
207 } else {
212 if (!irange || !cues_start || !cues_end || !filename)
214
222 }
224 return 0;
225 }
226
227 /*
228 * Checks if width of all streams are the same. Returns 1 if true, 0 otherwise.
229 */
231 {
234 first_width =
s->streams[as->
streams[0]]->codecpar->width;
236 if (first_width !=
s->streams[as->
streams[
i]]->codecpar->width)
237 return 0;
238 return 1;
239 }
240
241 /*
242 * Checks if height of all streams are the same. Returns 1 if true, 0 otherwise.
243 */
245 {
248 first_height =
s->streams[as->
streams[0]]->codecpar->height;
250 if (first_height !=
s->streams[as->
streams[
i]]->codecpar->height)
251 return 0;
252 return 1;
253 }
254
255 /*
256 * Checks if sample rate of all streams are the same. Returns 1 if true, 0 otherwise.
257 */
259 {
260 int first_sample_rate,
i;
262 first_sample_rate =
s->streams[as->
streams[0]]->codecpar->sample_rate;
264 if (first_sample_rate !=
s->streams[as->
streams[
i]]->codecpar->sample_rate)
265 return 0;
266 return 1;
267 }
268
270 {
273 for (
i = 0;
i <
w->nb_as;
i++) {
275 }
278 }
279
280 /*
281 * Parses a live header filename and returns the position of the '_' and '.'
282 * delimiting <file_description> and <representation_id>.
283 *
284 * Name of the header file should conform to the following pattern:
285 * <file_description>_<representation_id>.hdr where <file_description> can be
286 * anything. The chunks should be named according to the following pattern:
287 * <file_description>_<representation_id>_<chunk_number>.chk
288 */
290 char **period_pos)
291 {
292 *underscore_pos = strrchr(filename, '_');
293 if (!*underscore_pos)
295 *period_pos = strchr(*underscore_pos, '.');
296 if (!*period_pos)
298 return 0;
299 }
300
301 /*
302 * Writes an Adaptation Set. Returns 0 on success and < 0 on failure.
303 */
305 {
313 static const char boolean[2][6] = { "false", "true" };
314 int subsegmentStartsWithSAP = 1;
315
316 // Width, Height and Sample Rate will go in the AdaptationSet tag if they
317 // are the same for all contained Representations. otherwise, they will go
318 // on their respective Representation tag. For live streams, they always go
319 // in the Representation tag.
320 int width_in_as = 1, height_in_as = 1, sample_rate_in_as = 1;
324 } else {
326 }
327
332
334 if (lang)
336
343
348
352 if (!
w->is_live && (!kf || !strncmp(kf->
value,
"0", 1))) subsegmentStartsWithSAP = 0;
353 }
354 avio_printf(pb,
" subsegmentStartsWithSAP=\"%d\"", subsegmentStartsWithSAP);
356
360 char *underscore_pos, *period_pos;
362 if (!filename)
366 *underscore_pos = '0円';
367 avio_printf(pb,
"<ContentComponent id=\"1\" type=\"%s\"/>\n",
372 avio_printf(pb,
" media=\"%s_$RepresentationID$_$Number$.chk\"",
374 avio_printf(pb,
" startNumber=\"%d\"",
w->chunk_start_index);
375 avio_printf(pb,
" initialization=\"%s_$RepresentationID$.hdr\"",
378 *underscore_pos = '_';
379 }
380
382 char buf[25], *representation_id = buf, *underscore_pos, *period_pos;
388 if (!filename)
393 representation_id = underscore_pos + 1;
394 *period_pos = '0円';
395 } else {
396 snprintf(buf,
sizeof(buf),
"%d",
w->representation_id++);
397 }
399 !height_in_as, !sample_rate_in_as);
402 *period_pos = '.';
403 }
405 return 0;
406 }
407
409 {
411 char *p =
w->adaptation_sets;
412 char *q;
413 enum { new_set, parsed_id, parsing_streams }
state;
414 if (!
w->adaptation_sets) {
417 }
418 // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on
420 while (1) {
421 if (*p == '0円') {
422 if (
state == new_set)
423 break;
424 else
426 }
else if (
state == new_set && *p ==
' ') {
427 p++;
428 continue;
429 }
else if (
state == new_set && !strncmp(p,
"id=", 3)) {
430 void *mem =
av_realloc(
w->as,
sizeof(*
w->as) * (
w->nb_as + 1));
431 const char *comma;
436 w->as[
w->nb_as - 1].nb_streams = 0;
437 w->as[
w->nb_as - 1].streams =
NULL;
438 p += 3; // consume "id="
439 q =
w->as[
w->nb_as - 1].id;
440 comma = strchr(p, ',');
441 if (!comma || comma - p >=
sizeof(
w->as[
w->nb_as - 1].id)) {
444 }
445 while (*p != ',') *q++ = *p++;
446 *q = 0;
447 p++;
449 }
else if (
state == parsed_id && !strncmp(p,
"streams=", 8)) {
450 p += 8; // consume "streams="
451 state = parsing_streams;
452 }
else if (
state == parsing_streams) {
454 int64_t num;
459 num = strtoll(p, &q, 10);
460 if (!
av_isdigit(*p) || (*q !=
' ' && *q !=
'0円' && *q !=
',') ||
461 num < 0 || num >=
s->nb_streams) {
464 }
466 if (*q == '0円') break;
467 if (*q ==
' ')
state = new_set;
468 p = ++q;
469 } else {
470 return -1;
471 }
472 }
473 return 0;
474 }
475
477 {
479 double start = 0.0;
482
483 for (
unsigned i = 0;
i <
s->nb_streams;
i++) {
489 }
490
494 }
498 }
503 }
505
506 for (
i = 0;
i <
w->nb_as;
i++) {
510 }
511 }
512
518 }
519
521 {
523 }
524
525 #define OFFSET(x) offsetof(WebMDashMuxContext, x)
527 {
"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 },
532 {
"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 },
535 };
536
542 };
543
545 .
p.
name =
"webm_dash_manifest",
547 .p.mime_type = "application/xml",
548 .p.extensions = "xml",
553 };