FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
sidxindex.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014 Martin Storsjo
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include <stdio.h>
22 #include <string.h>
23 
24 #include "libavformat/avformat.h"
25 #include "libavutil/avstring.h"
26 #include "libavutil/intreadwrite.h"
27 #include "libavutil/mathematics.h"
28 
29 static int usage(const char *argv0, int ret)
30 {
31  fprintf(stderr, "%s -out foo.mpd file1\n", argv0);
32  return ret;
33 }
34 
35 struct Track {
36  const char *name;
37  int64_t duration;
38  int bitrate;
39  int track_id;
40  int is_audio, is_video;
41  int width, height;
42  int sample_rate, channels;
43  int timescale;
44  char codec_str[30];
46 };
47 
48 struct Tracks {
49  int nb_tracks;
50  int64_t duration;
51  struct Track **tracks;
53 };
54 
55 static void set_codec_str(AVCodecContext *codec, char *str, int size)
56 {
57  switch (codec->codec_id) {
58  case AV_CODEC_ID_H264:
59  snprintf(str, size, "avc1");
60  if (codec->extradata_size >= 4 && codec->extradata[0] == 1) {
61  av_strlcatf(str, size, ".%02x%02x%02x",
62  codec->extradata[1], codec->extradata[2], codec->extradata[3]);
63  }
64  break;
65  case AV_CODEC_ID_AAC:
66  snprintf(str, size, "mp4a.40"); // 0x40 is the mp4 object type for AAC
67  if (codec->extradata_size >= 2) {
68  int aot = codec->extradata[0] >> 3;
69  if (aot == 31)
70  aot = ((AV_RB16(codec->extradata) >> 5) & 0x3f) + 32;
71  av_strlcatf(str, size, ".%d", aot);
72  }
73  break;
74  }
75 }
76 
77 static int find_sidx(struct Tracks *tracks, int start_index,
78  const char *file)
79 {
80  int err = 0;
81  AVIOContext *f = NULL;
82  int i;
83 
84  if ((err = avio_open2(&f, file, AVIO_FLAG_READ, NULL, NULL)) < 0)
85  goto fail;
86 
87  while (!f->eof_reached) {
88  int64_t pos = avio_tell(f);
89  int32_t size, tag;
90 
91  size = avio_rb32(f);
92  tag = avio_rb32(f);
93  if (size < 8)
94  break;
95  if (tag == MKBETAG('s', 'i', 'd', 'x')) {
96  for (i = start_index; i < tracks->nb_tracks; i++) {
97  struct Track *track = tracks->tracks[i];
98  if (!track->sidx_start) {
99  track->sidx_start = pos;
100  track->sidx_length = size;
101  } else if (pos == track->sidx_start + track->sidx_length) {
102  track->sidx_length = pos + size - track->sidx_start;
103  }
104  }
105  }
106  if (avio_seek(f, pos + size, SEEK_SET) != pos + size)
107  break;
108  }
109 
110 fail:
111  if (f)
112  avio_close(f);
113  return err;
114 }
115 
116 static int handle_file(struct Tracks *tracks, const char *file)
117 {
118  AVFormatContext *ctx = NULL;
119  int err = 0, i, orig_tracks = tracks->nb_tracks;
120  char errbuf[50], *ptr;
121  struct Track *track;
122 
123  err = avformat_open_input(&ctx, file, NULL, NULL);
124  if (err < 0) {
125  av_strerror(err, errbuf, sizeof(errbuf));
126  fprintf(stderr, "Unable to open %s: %s\n", file, errbuf);
127  return 1;
128  }
129 
130  err = avformat_find_stream_info(ctx, NULL);
131  if (err < 0) {
132  av_strerror(err, errbuf, sizeof(errbuf));
133  fprintf(stderr, "Unable to identify %s: %s\n", file, errbuf);
134  goto fail;
135  }
136 
137  if (ctx->nb_streams < 1) {
138  fprintf(stderr, "No streams found in %s\n", file);
139  goto fail;
140  }
141  if (ctx->nb_streams > 1)
142  tracks->multiple_tracks_per_file = 1;
143 
144  for (i = 0; i < ctx->nb_streams; i++) {
145  struct Track **temp;
146  AVStream *st = ctx->streams[i];
147 
148  if (st->codec->bit_rate == 0) {
149  fprintf(stderr, "Skipping track %d in %s as it has zero bitrate\n",
150  st->id, file);
151  continue;
152  }
153 
154  track = av_mallocz(sizeof(*track));
155  if (!track) {
156  err = AVERROR(ENOMEM);
157  goto fail;
158  }
159  temp = av_realloc(tracks->tracks,
160  sizeof(*tracks->tracks) * (tracks->nb_tracks + 1));
161  if (!temp) {
162  av_free(track);
163  err = AVERROR(ENOMEM);
164  goto fail;
165  }
166  tracks->tracks = temp;
167  tracks->tracks[tracks->nb_tracks] = track;
168 
169  track->name = file;
170  if ((ptr = strrchr(file, '/')))
171  track->name = ptr + 1;
172 
173  track->bitrate = st->codec->bit_rate;
174  track->track_id = st->id;
175  track->timescale = st->time_base.den;
176  track->duration = st->duration;
177  track->is_audio = st->codec->codec_type == AVMEDIA_TYPE_AUDIO;
178  track->is_video = st->codec->codec_type == AVMEDIA_TYPE_VIDEO;
179 
180  if (!track->is_audio && !track->is_video) {
181  fprintf(stderr,
182  "Track %d in %s is neither video nor audio, skipping\n",
183  track->track_id, file);
184  av_freep(&tracks->tracks[tracks->nb_tracks]);
185  continue;
186  }
187 
188  tracks->duration = FFMAX(tracks->duration,
190  track->timescale, AV_ROUND_UP));
191 
192  if (track->is_audio) {
193  track->channels = st->codec->channels;
194  track->sample_rate = st->codec->sample_rate;
195  }
196  if (track->is_video) {
197  track->width = st->codec->width;
198  track->height = st->codec->height;
199  }
200  set_codec_str(st->codec, track->codec_str, sizeof(track->codec_str));
201 
202  tracks->nb_tracks++;
203  }
204 
205  avformat_close_input(&ctx);
206 
207  err = find_sidx(tracks, orig_tracks, file);
208 
209 fail:
210  if (ctx)
211  avformat_close_input(&ctx);
212  return err;
213 }
214 
215 static void write_time(FILE *out, int64_t time, int decimals, enum AVRounding round)
216 {
217  int seconds = time / AV_TIME_BASE;
218  int fractions = time % AV_TIME_BASE;
219  int minutes = seconds / 60;
220  int hours = minutes / 60;
221  fractions = av_rescale_rnd(fractions, pow(10, decimals), AV_TIME_BASE, round);
222  seconds %= 60;
223  minutes %= 60;
224  fprintf(out, "PT");
225  if (hours)
226  fprintf(out, "%dH", hours);
227  if (hours || minutes)
228  fprintf(out, "%dM", minutes);
229  fprintf(out, "%d.%0*dS", seconds, decimals, fractions);
230 }
231 
232 static int output_mpd(struct Tracks *tracks, const char *filename)
233 {
234  FILE *out;
235  int i, j, ret = 0;
236  struct Track **adaptation_sets_buf[2] = { NULL };
237  struct Track ***adaptation_sets;
238  int nb_tracks_buf[2] = { 0 };
239  int *nb_tracks;
240  int set, nb_sets;
241 
242  if (!tracks->multiple_tracks_per_file) {
243  adaptation_sets = adaptation_sets_buf;
244  nb_tracks = nb_tracks_buf;
245  nb_sets = 2;
246  for (i = 0; i < 2; i++) {
247  adaptation_sets[i] = av_malloc(sizeof(*adaptation_sets[i]) * tracks->nb_tracks);
248  if (!adaptation_sets[i]) {
249  ret = AVERROR(ENOMEM);
250  goto err;
251  }
252  }
253  for (i = 0; i < tracks->nb_tracks; i++) {
254  int set_index = -1;
255  if (tracks->tracks[i]->is_video)
256  set_index = 0;
257  else if (tracks->tracks[i]->is_audio)
258  set_index = 1;
259  else
260  continue;
261  adaptation_sets[set_index][nb_tracks[set_index]++] = tracks->tracks[i];
262  }
263  } else {
264  adaptation_sets = &tracks->tracks;
265  nb_tracks = &tracks->nb_tracks;
266  nb_sets = 1;
267  }
268 
269  out = fopen(filename, "w");
270  if (!out) {
271  ret = AVERROR(errno);
272  perror(filename);
273  return ret;
274  }
275  fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
276  fprintf(out, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
277  "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
278  "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
279  "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n"
280  "\tprofiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\"\n"
281  "\ttype=\"static\"\n");
282  fprintf(out, "\tmediaPresentationDuration=\"");
283  write_time(out, tracks->duration, 1, AV_ROUND_DOWN);
284  fprintf(out, "\"\n");
285  fprintf(out, "\tminBufferTime=\"PT5S\">\n");
286 
287  fprintf(out, "\t<Period start=\"PT0.0S\">\n");
288 
289  for (set = 0; set < nb_sets; set++) {
290  if (nb_tracks[set] == 0)
291  continue;
292  fprintf(out, "\t\t<AdaptationSet segmentAlignment=\"true\">\n");
293  if (nb_sets == 1) {
294  for (i = 0; i < nb_tracks[set]; i++) {
295  struct Track *track = adaptation_sets[set][i];
296  if (strcmp(track->name, adaptation_sets[set][0]->name))
297  break;
298  fprintf(out, "\t\t\t<ContentComponent id=\"%d\" contentType=\"%s\" />\n", track->track_id, track->is_audio ? "audio" : "video");
299  }
300  }
301 
302  for (i = 0; i < nb_tracks[set]; ) {
303  struct Track *first_track = adaptation_sets[set][i];
304  int width = 0, height = 0, sample_rate = 0, channels = 0, bitrate = 0;
305  fprintf(out, "\t\t\t<Representation id=\"%d\" codecs=\"", i);
306  for (j = i; j < nb_tracks[set]; j++) {
307  struct Track *track = adaptation_sets[set][j];
308  if (strcmp(track->name, first_track->name))
309  break;
310  if (track->is_audio) {
311  sample_rate = track->sample_rate;
312  channels = track->channels;
313  }
314  if (track->is_video) {
315  width = track->width;
316  height = track->height;
317  }
318  bitrate += track->bitrate;
319  if (j > i)
320  fprintf(out, ",");
321  fprintf(out, "%s", track->codec_str);
322  }
323  fprintf(out, "\" mimeType=\"%s/mp4\" bandwidth=\"%d\"",
324  width ? "video" : "audio", bitrate);
325  if (width > 0 && height > 0)
326  fprintf(out, " width=\"%d\" height=\"%d\"", width, height);
327  if (sample_rate > 0)
328  fprintf(out, " audioSamplingRate=\"%d\"", sample_rate);
329  fprintf(out, ">\n");
330  if (channels > 0)
331  fprintf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", channels);
332  fprintf(out, "\t\t\t\t<BaseURL>%s</BaseURL>\n", first_track->name);
333  fprintf(out, "\t\t\t\t<SegmentBase indexRange=\"%"PRId64"-%"PRId64"\" />\n", first_track->sidx_start, first_track->sidx_start + first_track->sidx_length - 1);
334  fprintf(out, "\t\t\t</Representation>\n");
335  i = j;
336  }
337  fprintf(out, "\t\t</AdaptationSet>\n");
338  }
339  fprintf(out, "\t</Period>\n");
340  fprintf(out, "</MPD>\n");
341 
342  fclose(out);
343 err:
344  for (i = 0; i < 2; i++)
345  av_free(adaptation_sets_buf[i]);
346  return ret;
347 }
348 
349 static void clean_tracks(struct Tracks *tracks)
350 {
351  int i;
352  for (i = 0; i < tracks->nb_tracks; i++) {
353  av_freep(&tracks->tracks[i]);
354  }
355  av_freep(&tracks->tracks);
356  tracks->nb_tracks = 0;
357 }
358 
359 int main(int argc, char **argv)
360 {
361  const char *out = NULL;
362  struct Tracks tracks = { 0 };
363  int i;
364 
365  av_register_all();
366 
367  for (i = 1; i < argc; i++) {
368  if (!strcmp(argv[i], "-out")) {
369  out = argv[i + 1];
370  i++;
371  } else if (argv[i][0] == '-') {
372  return usage(argv[0], 1);
373  } else {
374  if (handle_file(&tracks, argv[i]))
375  return 1;
376  }
377  }
378  if (!tracks.nb_tracks || !out)
379  return usage(argv[0], 1);
380 
381  output_mpd(&tracks, out);
382 
383  clean_tracks(&tracks);
384 
385  return 0;
386 }