FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
webmdashenc.c
Go to the documentation of this file.
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  */
26 
27 #include <stdint.h>
28 #include <string.h>
29 
30 #include "avformat.h"
31 #include "avio_internal.h"
32 #include "matroska.h"
33 
34 #include "libavutil/avstring.h"
35 #include "libavutil/dict.h"
36 #include "libavutil/opt.h"
37 
38 typedef struct AdaptationSet {
39  char id[10];
40  int *streams;
43 
44 typedef struct WebMDashMuxContext {
45  const AVClass *class;
48  int nb_as;
50 
51 static const char *get_codec_name(int codec_id)
52 {
53  switch (codec_id) {
54  case AV_CODEC_ID_VP8:
55  return "vp8";
56  case AV_CODEC_ID_VP9:
57  return "vp9";
58  case AV_CODEC_ID_VORBIS:
59  return "vorbis";
60  case AV_CODEC_ID_OPUS:
61  return "opus";
62  }
63  return NULL;
64 }
65 
67 {
68  int i = 0;
69  double max = 0.0;
70  for (i = 0; i < s->nb_streams; i++) {
72  DURATION, NULL, 0);
73  if (duration == NULL || atof(duration->value) < 0) continue;
74  if (atof(duration->value) > max) max = atof(duration->value);
75  }
76  return max / 1000;
77 }
78 
80 {
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",
89  get_duration(s));
90  avio_printf(s->pb, " minBufferTime=\"PT%gS\"\n",
91  min_buffer_time);
92  avio_printf(s->pb, " profiles=\"urn:webm:dash:profile:webm-on-demand:2012\"");
93  avio_printf(s->pb, ">\n");
94 }
95 
97 {
98  avio_printf(s->pb, "</MPD>");
99 }
100 
102  int i;
104  CUE_TIMESTAMPS, NULL, 0);
105  if (gold == NULL) return 0;
106  for (i = 1; i < as->nb_streams; i++) {
108  CUE_TIMESTAMPS, NULL, 0);
109  if (ts == NULL || strncmp(gold->value, ts->value, strlen(gold->value))) return 0;
110  }
111  return 1;
112 }
113 
115  int i;
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 == NULL) 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;
124  if (track_num == NULL ||
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)) {
129  return 0;
130  }
131  }
132  return 1;
133 }
134 
135 /*
136  * Writes an Adaptation Set. Returns 0 on success and < 0 on failure.
137  */
138 static int write_adaptation_set(AVFormatContext *s, int as_index)
139 {
141  AdaptationSet *as = &w->as[as_index];
142  AVCodecContext *codec = s->streams[as->streams[0]]->codec;
143  int i;
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));
151 
152  lang = av_dict_get(s->streams[as->streams[0]]->metadata, "language", NULL, 0);
153  if (lang != NULL) avio_printf(s->pb, " lang=\"%s\"", lang->value);
154 
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);
158  } else {
159  avio_printf(s->pb, " audioSamplingRate=\"%d\"", codec->sample_rate);
160  }
161 
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)]);
166 
167  for (i = 0; i < as->nb_streams; i++) {
169  CLUSTER_KEYFRAME, NULL, 0);
170  if (kf == NULL || !strncmp(kf->value, "0", 1)) subsegmentStartsWithSAP = 0;
171  }
172  avio_printf(s->pb, " subsegmentStartsWithSAP=\"%d\"", subsegmentStartsWithSAP);
173  avio_printf(s->pb, ">\n");
174 
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 == NULL || cues_start == NULL || cues_end == NULL || filename == NULL ||
183  bandwidth == NULL) {
184  return -1;
185  }
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");
196  }
197  avio_printf(s->pb, "</AdaptationSet>\n");
198  return 0;
199 }
200 
201 static int to_integer(char *p, int len)
202 {
203  int ret;
204  char *q = av_malloc(sizeof(char) * len);
205  if (q == NULL) return -1;
206  strncpy(q, p, len);
207  ret = atoi(q);
208  av_free(q);
209  return ret;
210 }
211 
213 {
215  char *p = w->adaptation_sets;
216  char *q;
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
219  state = new_set;
220  while (p < w->adaptation_sets + strlen(w->adaptation_sets)) {
221  if (*p == ' ')
222  continue;
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++;
231  *q = 0;
232  p++;
233  state = parsed_id;
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];
239  q = p;
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);
244  if (as->streams[as->nb_streams - 1] < 0) return -1;
245  if (*q == '\0') break;
246  if (*q == ' ') state = new_set;
247  p = ++q;
248  } else {
249  return -1;
250  }
251  }
252  return 0;
253 }
254 
256 {
257  int i;
258  double start = 0.0;
261  write_header(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");
266 
267  for (i = 0; i < w->nb_as; i++) {
268  if (write_adaptation_set(s, i) < 0) return -1;
269  }
270 
271  avio_printf(s->pb, "</Period>\n");
272  write_footer(s);
273  return 0;
274 }
275 
277 {
278  return AVERROR_EOF;
279 }
280 
282 {
284  int i;
285  for (i = 0; i < w->nb_as; i++) {
286  av_freep(&w->as[i].streams);
287  }
288  av_freep(&w->as);
289  return 0;
290 }
291 
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 },
295  { NULL },
296 };
297 
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,
302  .option = options,
303  .version = LIBAVUTIL_VERSION_INT,
304 };
305 
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",
310  .extensions = "xml",
311  .priv_data_size = sizeof(WebMDashMuxContext),
315  .priv_class = &webm_dash_class,
316 };
317 #endif