2 * WebM DASH Manifest XML muxer
3 * Copyright (c) 2014 Vignesh Venkatasubramanian
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
23 * WebM DASH Specification:
24 * https://sites.google.com/a/webmproject.org/wiki/adaptive-streaming/webm-dash-specification
31 #include "avio_internal.h"
34 #include "libavutil/avstring.h"
35 #include "libavutil/dict.h"
36 #include "libavutil/opt.h"
38 typedef struct AdaptationSet
{
44 typedef struct WebMDashMuxContext
{
46 char *adaptation_sets
;
51 static const char *get_codec_name(int codec_id
)
58 case AV_CODEC_ID_VORBIS
:
60 case AV_CODEC_ID_OPUS
:
66 static double get_duration(AVFormatContext
*s
)
70 for (i
= 0; i
< s
->nb_streams
; i
++) {
71 AVDictionaryEntry
*duration
= av_dict_get(s
->streams
[i
]->metadata
,
73 if (!duration
|| atof(duration
->value
) < 0) continue;
74 if (atof(duration
->value
) > max
) max
= atof(duration
->value
);
79 static void write_header(AVFormatContext
*s
)
81 double min_buffer_time
= 1.0;
82 avio_printf(s
->pb
, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
83 avio_printf(s
->pb
, "<MPD\n");
84 avio_printf(s
->pb
, " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
85 avio_printf(s
->pb
, " xmlns=\"urn:mpeg:DASH:schema:MPD:2011\"\n");
86 avio_printf(s
->pb
, " xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011\"\n");
87 avio_printf(s
->pb
, " type=\"static\"\n");
88 avio_printf(s
->pb
, " mediaPresentationDuration=\"PT%gS\"\n",
90 avio_printf(s
->pb
, " minBufferTime=\"PT%gS\"\n",
92 avio_printf(s
->pb
, " profiles=\"urn:webm:dash:profile:webm-on-demand:2012\"");
93 avio_printf(s
->pb
, ">\n");
96 static void write_footer(AVFormatContext
*s
)
98 avio_printf(s
->pb
, "</MPD>");
101 static int subsegment_alignment(AVFormatContext
*s
, AdaptationSet
*as
) {
103 AVDictionaryEntry
*gold
= av_dict_get(s
->streams
[as
->streams
[0]]->metadata
,
104 CUE_TIMESTAMPS
, NULL
, 0);
106 for (i
= 1; i
< as
->nb_streams
; i
++) {
107 AVDictionaryEntry
*ts
= av_dict_get(s
->streams
[as
->streams
[i
]]->metadata
,
108 CUE_TIMESTAMPS
, NULL
, 0);
109 if (!ts
|| strncmp(gold
->value
, ts
->value
, strlen(gold
->value
))) return 0;
114 static int bitstream_switching(AVFormatContext
*s
, AdaptationSet
*as
) {
116 AVDictionaryEntry
*gold_track_num
= av_dict_get(s
->streams
[as
->streams
[0]]->metadata
,
117 TRACK_NUMBER
, NULL
, 0);
118 AVCodecContext
*gold_codec
= s
->streams
[as
->streams
[0]]->codec
;
119 if (!gold_track_num
) return 0;
120 for (i
= 1; i
< as
->nb_streams
; i
++) {
121 AVDictionaryEntry
*track_num
= av_dict_get(s
->streams
[as
->streams
[i
]]->metadata
,
122 TRACK_NUMBER
, NULL
, 0);
123 AVCodecContext
*codec
= s
->streams
[as
->streams
[i
]]->codec
;
125 strncmp(gold_track_num
->value
, track_num
->value
, strlen(gold_track_num
->value
)) ||
126 gold_codec
->codec_id
!= codec
->codec_id
||
127 gold_codec
->extradata_size
!= codec
->extradata_size
||
128 memcmp(gold_codec
->extradata
, codec
->extradata
, codec
->extradata_size
)) {
136 * Writes an Adaptation Set. Returns 0 on success and < 0 on failure.
138 static int write_adaptation_set(AVFormatContext
*s
, int as_index
)
140 WebMDashMuxContext
*w
= s
->priv_data
;
141 AdaptationSet
*as
= &w
->as
[as_index
];
142 AVCodecContext
*codec
= s
->streams
[as
->streams
[0]]->codec
;
144 static const char boolean
[2][6] = { "false", "true" };
145 int subsegmentStartsWithSAP
= 1;
146 AVDictionaryEntry
*lang
;
147 avio_printf(s
->pb
, "<AdaptationSet id=\"%s\"", as
->id
);
148 avio_printf(s
->pb
, " mimeType=\"%s/webm\"",
149 codec
->codec_type
== AVMEDIA_TYPE_VIDEO
? "video" : "audio");
150 avio_printf(s
->pb
, " codecs=\"%s\"", get_codec_name(codec
->codec_id
));
152 lang
= av_dict_get(s
->streams
[as
->streams
[0]]->metadata
, "language", NULL
, 0);
153 if (lang
) avio_printf(s
->pb
, " lang=\"%s\"", lang
->value
);
155 if (codec
->codec_type
== AVMEDIA_TYPE_VIDEO
) {
156 avio_printf(s
->pb
, " width=\"%d\"", codec
->width
);
157 avio_printf(s
->pb
, " height=\"%d\"", codec
->height
);
159 avio_printf(s
->pb
, " audioSamplingRate=\"%d\"", codec
->sample_rate
);
162 avio_printf(s
->pb
, " bitstreamSwitching=\"%s\"",
163 boolean
[bitstream_switching(s
, as
)]);
164 avio_printf(s
->pb
, " subsegmentAlignment=\"%s\"",
165 boolean
[subsegment_alignment(s
, as
)]);
167 for (i
= 0; i
< as
->nb_streams
; i
++) {
168 AVDictionaryEntry
*kf
= av_dict_get(s
->streams
[as
->streams
[i
]]->metadata
,
169 CLUSTER_KEYFRAME
, NULL
, 0);
170 if (!kf
|| !strncmp(kf
->value
, "0", 1)) subsegmentStartsWithSAP
= 0;
172 avio_printf(s
->pb
, " subsegmentStartsWithSAP=\"%d\"", subsegmentStartsWithSAP
);
173 avio_printf(s
->pb
, ">\n");
175 for (i
= 0; i
< as
->nb_streams
; i
++) {
176 AVStream
*stream
= s
->streams
[as
->streams
[i
]];
177 AVDictionaryEntry
*irange
= av_dict_get(stream
->metadata
, INITIALIZATION_RANGE
, NULL
, 0);
178 AVDictionaryEntry
*cues_start
= av_dict_get(stream
->metadata
, CUES_START
, NULL
, 0);
179 AVDictionaryEntry
*cues_end
= av_dict_get(stream
->metadata
, CUES_END
, NULL
, 0);
180 AVDictionaryEntry
*filename
= av_dict_get(stream
->metadata
, FILENAME
, NULL
, 0);
181 AVDictionaryEntry
*bandwidth
= av_dict_get(stream
->metadata
, BANDWIDTH
, NULL
, 0);
182 if (!irange
|| cues_start
== NULL
|| cues_end
== NULL
|| filename
== NULL
||
186 avio_printf(s
->pb
, "<Representation id=\"%d\"", i
);
187 avio_printf(s
->pb
, " bandwidth=\"%s\"", bandwidth
->value
);
188 avio_printf(s
->pb
, ">\n");
189 avio_printf(s
->pb
, "<BaseURL>%s</BaseURL>\n", filename
->value
);
190 avio_printf(s
->pb
, "<SegmentBase\n");
191 avio_printf(s
->pb
, " indexRange=\"%s-%s\">\n", cues_start
->value
, cues_end
->value
);
192 avio_printf(s
->pb
, "<Initialization\n");
193 avio_printf(s
->pb
, " range=\"0-%s\" />\n", irange
->value
);
194 avio_printf(s
->pb
, "</SegmentBase>\n");
195 avio_printf(s
->pb
, "</Representation>\n");
197 avio_printf(s
->pb
, "</AdaptationSet>\n");
201 static int to_integer(char *p
, int len
)
204 char *q
= av_malloc(sizeof(char) * len
);
206 av_strlcpy(q
, p
, len
);
212 static int parse_adaptation_sets(AVFormatContext
*s
)
214 WebMDashMuxContext
*w
= s
->priv_data
;
215 char *p
= w
->adaptation_sets
;
217 enum { new_set
, parsed_id
, parsing_streams
} state
;
218 // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on
220 while (p
< w
->adaptation_sets
+ strlen(w
->adaptation_sets
)) {
223 else if (state
== new_set
&& !strncmp(p
, "id=", 3)) {
224 w
->as
= av_realloc(w
->as
, sizeof(*w
->as
) * ++w
->nb_as
);
225 if (w
->as
== NULL
) return -1;
226 w
->as
[w
->nb_as
- 1].nb_streams
= 0;
227 w
->as
[w
->nb_as
- 1].streams
= NULL
;
228 p
+= 3; // consume "id="
229 q
= w
->as
[w
->nb_as
- 1].id
;
230 while (*p
!= ',') *q
++ = *p
++;
234 } else if (state
== parsed_id
&& !strncmp(p
, "streams=", 8)) {
235 p
+= 8; // consume "streams="
236 state
= parsing_streams
;
237 } else if (state
== parsing_streams
) {
238 struct AdaptationSet
*as
= &w
->as
[w
->nb_as
- 1];
240 while (*q
!= '\0' && *q
!= ',' && *q
!= ' ') q
++;
241 as
->streams
= av_realloc(as
->streams
, sizeof(*as
->streams
) * ++as
->nb_streams
);
242 if (as
->streams
== NULL
) return -1;
243 as
->streams
[as
->nb_streams
- 1] = to_integer(p
, q
- p
+ 1);
244 if (as
->streams
[as
->nb_streams
- 1] < 0) return -1;
245 if (*q
== '\0') break;
246 if (*q
== ' ') state
= new_set
;
255 static int webm_dash_manifest_write_header(AVFormatContext
*s
)
259 WebMDashMuxContext
*w
= s
->priv_data
;
260 parse_adaptation_sets(s
);
262 avio_printf(s
->pb
, "<Period id=\"0\"");
263 avio_printf(s
->pb
, " start=\"PT%gS\"", start
);
264 avio_printf(s
->pb
, " duration=\"PT%gS\"", get_duration(s
));
265 avio_printf(s
->pb
, " >\n");
267 for (i
= 0; i
< w
->nb_as
; i
++) {
268 if (write_adaptation_set(s
, i
) < 0) return -1;
271 avio_printf(s
->pb
, "</Period>\n");
276 static int webm_dash_manifest_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
281 static int webm_dash_manifest_write_trailer(AVFormatContext
*s
)
283 WebMDashMuxContext
*w
= s
->priv_data
;
285 for (i
= 0; i
< w
->nb_as
; i
++) {
286 av_freep(&w
->as
[i
].streams
);
292 #define OFFSET(x) offsetof(WebMDashMuxContext, x)
293 static const AVOption options
[] = {
294 { "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
},
298 #if CONFIG_WEBM_DASH_MANIFEST_MUXER
299 static const AVClass webm_dash_class
= {
300 .class_name
= "WebM DASH Manifest muxer",
301 .item_name
= av_default_item_name
,
303 .version
= LIBAVUTIL_VERSION_INT
,
306 AVOutputFormat ff_webm_dash_manifest_muxer
= {
307 .name
= "webm_dash_manifest",
308 .long_name
= NULL_IF_CONFIG_SMALL("WebM DASH Manifest"),
309 .mime_type
= "application/xml",
311 .priv_data_size
= sizeof(WebMDashMuxContext
),
312 .write_header
= webm_dash_manifest_write_header
,
313 .write_packet
= webm_dash_manifest_write_packet
,
314 .write_trailer
= webm_dash_manifest_write_trailer
,
315 .priv_class
= &webm_dash_class
,