FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
dashenc.c
Go to the documentation of this file.
1 /*
2  * MPEG-DASH ISO BMFF segmenter
3  * Copyright (c) 2014 Martin Storsjo
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 #include "config.h"
23 #if HAVE_UNISTD_H
24 #include <unistd.h>
25 #endif
26 
27 #include "libavutil/avstring.h"
28 #include "libavutil/intreadwrite.h"
29 #include "libavutil/mathematics.h"
30 #include "libavutil/opt.h"
32 
33 #include "avc.h"
34 #include "avformat.h"
35 #include "avio_internal.h"
36 #include "internal.h"
37 #include "isom.h"
38 #include "os_support.h"
39 #include "url.h"
40 
41 // See ISO/IEC 23009-1:2014 5.3.9.4.4
42 typedef enum {
49 } DASHTmplId;
50 
51 typedef struct Segment {
52  char file[1024];
53  int64_t start_pos;
55  int64_t time;
56  int duration;
57  int n;
58 } Segment;
59 
60 typedef struct OutputStream {
63  uint8_t iobuf[32768];
66  char initfile[1024];
67  int64_t init_start_pos;
72  int bit_rate;
73  char bandwidth_str[64];
74 
75  char codec_str[100];
76 } OutputStream;
77 
78 typedef struct DASHContext {
79  const AVClass *class; /* Class for private options. */
92  char dirname[1024];
93  const char *single_file_name;
94  const char *init_seg_name;
95  const char *media_seg_name;
96 } DASHContext;
97 
98 static int dash_write(void *opaque, uint8_t *buf, int buf_size)
99 {
100  OutputStream *os = opaque;
101  if (os->out)
102  ffurl_write(os->out, buf, buf_size);
103  return buf_size;
104 }
105 
106 // RFC 6381
108  char *str, int size)
109 {
110  const AVCodecTag *tags[2] = { NULL, NULL };
111  uint32_t tag;
112  if (codec->codec_type == AVMEDIA_TYPE_VIDEO)
113  tags[0] = ff_codec_movvideo_tags;
114  else if (codec->codec_type == AVMEDIA_TYPE_AUDIO)
115  tags[0] = ff_codec_movaudio_tags;
116  else
117  return;
118 
119  tag = av_codec_get_tag(tags, codec->codec_id);
120  if (!tag)
121  return;
122  if (size < 5)
123  return;
124 
125  AV_WL32(str, tag);
126  str[4] = '\0';
127  if (!strcmp(str, "mp4a") || !strcmp(str, "mp4v")) {
128  uint32_t oti;
129  tags[0] = ff_mp4_obj_type;
130  oti = av_codec_get_tag(tags, codec->codec_id);
131  if (oti)
132  av_strlcatf(str, size, ".%02x", oti);
133  else
134  return;
135 
136  if (tag == MKTAG('m', 'p', '4', 'a')) {
137  if (codec->extradata_size >= 2) {
138  int aot = codec->extradata[0] >> 3;
139  if (aot == 31)
140  aot = ((AV_RB16(codec->extradata) >> 5) & 0x3f) + 32;
141  av_strlcatf(str, size, ".%d", aot);
142  }
143  } else if (tag == MKTAG('m', 'p', '4', 'v')) {
144  // Unimplemented, should output ProfileLevelIndication as a decimal number
145  av_log(s, AV_LOG_WARNING, "Incomplete RFC 6381 codec string for mp4v\n");
146  }
147  } else if (!strcmp(str, "avc1")) {
148  uint8_t *tmpbuf = NULL;
149  uint8_t *extradata = codec->extradata;
150  int extradata_size = codec->extradata_size;
151  if (!extradata_size)
152  return;
153  if (extradata[0] != 1) {
154  AVIOContext *pb;
155  if (avio_open_dyn_buf(&pb) < 0)
156  return;
157  if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) {
158  avio_close_dyn_buf(pb, &tmpbuf);
159  av_free(tmpbuf);
160  return;
161  }
162  extradata_size = avio_close_dyn_buf(pb, &extradata);
163  tmpbuf = extradata;
164  }
165 
166  if (extradata_size >= 4)
167  av_strlcatf(str, size, ".%02x%02x%02x",
168  extradata[1], extradata[2], extradata[3]);
169  av_free(tmpbuf);
170  }
171 }
172 
174 {
175  DASHContext *c = s->priv_data;
176  int i, j;
177  if (!c->streams)
178  return;
179  for (i = 0; i < s->nb_streams; i++) {
180  OutputStream *os = &c->streams[i];
181  if (os->ctx && os->ctx_inited)
182  av_write_trailer(os->ctx);
183  if (os->ctx && os->ctx->pb)
184  av_free(os->ctx->pb);
185  ffurl_close(os->out);
186  os->out = NULL;
187  if (os->ctx)
189  for (j = 0; j < os->nb_segments; j++)
190  av_free(os->segments[j]);
191  av_free(os->segments);
192  }
193  av_freep(&c->streams);
194 }
195 
197 {
198  int i, start_index = 0, start_number = 1;
199  if (c->window_size) {
200  start_index = FFMAX(os->nb_segments - c->window_size, 0);
201  start_number = FFMAX(os->segment_index - c->window_size, 1);
202  }
203 
204  if (c->use_template) {
205  int timescale = c->use_timeline ? os->ctx->streams[0]->time_base.den : AV_TIME_BASE;
206  avio_printf(out, "\t\t\t\t<SegmentTemplate timescale=\"%d\" ", timescale);
207  if (!c->use_timeline)
208  avio_printf(out, "duration=\"%d\" ", c->last_duration);
209  avio_printf(out, "initialization=\"%s\" media=\"%s\" startNumber=\"%d\">\n", c->init_seg_name, c->media_seg_name, c->use_timeline ? start_number : 1);
210  if (c->use_timeline) {
211  avio_printf(out, "\t\t\t\t\t<SegmentTimeline>\n");
212  for (i = start_index; i < os->nb_segments; ) {
213  Segment *seg = os->segments[i];
214  int repeat = 0;
215  avio_printf(out, "\t\t\t\t\t\t<S ");
216  if (i == start_index)
217  avio_printf(out, "t=\"%"PRId64"\" ", seg->time);
218  avio_printf(out, "d=\"%d\" ", seg->duration);
219  while (i + repeat + 1 < os->nb_segments && os->segments[i + repeat + 1]->duration == seg->duration)
220  repeat++;
221  if (repeat > 0)
222  avio_printf(out, "r=\"%d\" ", repeat);
223  avio_printf(out, "/>\n");
224  i += 1 + repeat;
225  }
226  avio_printf(out, "\t\t\t\t\t</SegmentTimeline>\n");
227  }
228  avio_printf(out, "\t\t\t\t</SegmentTemplate>\n");
229  } else if (c->single_file) {
230  avio_printf(out, "\t\t\t\t<BaseURL>%s</BaseURL>\n", os->initfile);
231  avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number);
232  avio_printf(out, "\t\t\t\t\t<Initialization range=\"%"PRId64"-%"PRId64"\" />\n", os->init_start_pos, os->init_start_pos + os->init_range_length - 1);
233  for (i = start_index; i < os->nb_segments; i++) {
234  Segment *seg = os->segments[i];
235  avio_printf(out, "\t\t\t\t\t<SegmentURL mediaRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->range_length - 1);
236  if (seg->index_length)
237  avio_printf(out, "indexRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->index_length - 1);
238  avio_printf(out, "/>\n");
239  }
240  avio_printf(out, "\t\t\t\t</SegmentList>\n");
241  } else {
242  avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%d\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number);
243  avio_printf(out, "\t\t\t\t\t<Initialization sourceURL=\"%s\" />\n", os->initfile);
244  for (i = start_index; i < os->nb_segments; i++) {
245  Segment *seg = os->segments[i];
246  avio_printf(out, "\t\t\t\t\t<SegmentURL media=\"%s\" />\n", seg->file);
247  }
248  avio_printf(out, "\t\t\t\t</SegmentList>\n");
249  }
250 }
251 
252 static DASHTmplId dash_read_tmpl_id(const char *identifier, char *format_tag,
253  size_t format_tag_size, const char **ptr) {
254  const char *next_ptr;
256 
257  if (av_strstart(identifier, "$$", &next_ptr)) {
258  id_type = DASH_TMPL_ID_ESCAPE;
259  *ptr = next_ptr;
260  } else if (av_strstart(identifier, "$RepresentationID$", &next_ptr)) {
261  id_type = DASH_TMPL_ID_REP_ID;
262  // default to basic format, as $RepresentationID$ identifiers
263  // are not allowed to have custom format-tags.
264  av_strlcpy(format_tag, "%d", format_tag_size);
265  *ptr = next_ptr;
266  } else { // the following identifiers may have an explicit format_tag
267  if (av_strstart(identifier, "$Number", &next_ptr))
268  id_type = DASH_TMPL_ID_NUMBER;
269  else if (av_strstart(identifier, "$Bandwidth", &next_ptr))
270  id_type = DASH_TMPL_ID_BANDWIDTH;
271  else if (av_strstart(identifier, "$Time", &next_ptr))
272  id_type = DASH_TMPL_ID_TIME;
273  else
274  id_type = DASH_TMPL_ID_UNDEFINED;
275 
276  // next parse the dash format-tag and generate a c-string format tag
277  // (next_ptr now points at the first '%' at the beginning of the format-tag)
278  if (id_type != DASH_TMPL_ID_UNDEFINED) {
279  const char *number_format = DASH_TMPL_ID_TIME ? "lld" : "d";
280  if (next_ptr[0] == '$') { // no dash format-tag
281  snprintf(format_tag, format_tag_size, "%%%s", number_format);
282  *ptr = &next_ptr[1];
283  } else {
284  const char *width_ptr;
285  // only tolerate single-digit width-field (i.e. up to 9-digit width)
286  if (av_strstart(next_ptr, "%0", &width_ptr) &&
287  av_isdigit(width_ptr[0]) &&
288  av_strstart(&width_ptr[1], "d$", &next_ptr)) {
289  // yes, we're using a format tag to build format_tag.
290  snprintf(format_tag, format_tag_size, "%s%c%s", "%0", width_ptr[0], number_format);
291  *ptr = next_ptr;
292  } else {
293  av_log(NULL, AV_LOG_WARNING, "Failed to parse format-tag beginning with %s. Expected either a "
294  "closing '$' character or a format-string like '%%0[width]d', "
295  "where width must be a single digit\n", next_ptr);
296  id_type = DASH_TMPL_ID_UNDEFINED;
297  }
298  }
299  }
300  }
301  return id_type;
302 }
303 
304 static void dash_fill_tmpl_params(char *dst, size_t buffer_size,
305  const char *template, int rep_id,
306  int number, int bit_rate,
307  int64_t time) {
308  int dst_pos = 0;
309  const char *t_cur = template;
310  while (dst_pos < buffer_size - 1 && *t_cur) {
311  char format_tag[7]; // May be "%d", "%0Xd", or "%0Xlld" (for $Time$), where X is in [0-9]
312  int n = 0;
313  DASHTmplId id_type;
314  const char *t_next = strchr(t_cur, '$'); // copy over everything up to the first '$' character
315  if (t_next) {
316  int num_copy_bytes = FFMIN(t_next - t_cur, buffer_size - dst_pos - 1);
317  av_strlcpy(&dst[dst_pos], t_cur, num_copy_bytes + 1);
318  // advance
319  dst_pos += num_copy_bytes;
320  t_cur = t_next;
321  } else { // no more DASH identifiers to substitute - just copy the rest over and break
322  av_strlcpy(&dst[dst_pos], t_cur, buffer_size - dst_pos);
323  break;
324  }
325 
326  if (dst_pos >= buffer_size - 1 || !*t_cur)
327  break;
328 
329  // t_cur is now pointing to a '$' character
330  id_type = dash_read_tmpl_id(t_cur, format_tag, sizeof(format_tag), &t_next);
331  switch (id_type) {
332  case DASH_TMPL_ID_ESCAPE:
333  av_strlcpy(&dst[dst_pos], "$", 2);
334  n = 1;
335  break;
336  case DASH_TMPL_ID_REP_ID:
337  n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, rep_id);
338  break;
339  case DASH_TMPL_ID_NUMBER:
340  n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, number);
341  break;
343  n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, bit_rate);
344  break;
345  case DASH_TMPL_ID_TIME:
346  n = snprintf(&dst[dst_pos], buffer_size - dst_pos, format_tag, time);
347  break;
349  // copy over one byte and advance
350  av_strlcpy(&dst[dst_pos], t_cur, 2);
351  n = 1;
352  t_next = &t_cur[1];
353  break;
354  }
355  // t_next points just past the processed identifier
356  // n is the number of bytes that were attempted to be written to dst
357  // (may have failed to write all because buffer_size).
358 
359  // advance
360  dst_pos += FFMIN(n, buffer_size - dst_pos - 1);
361  t_cur = t_next;
362  }
363 }
364 
365 static char *xmlescape(const char *str) {
366  int outlen = strlen(str)*3/2 + 6;
367  char *out = av_realloc(NULL, outlen + 1);
368  int pos = 0;
369  if (!out)
370  return NULL;
371  for (; *str; str++) {
372  if (pos + 6 > outlen) {
373  char *tmp;
374  outlen = 2 * outlen + 6;
375  tmp = av_realloc(out, outlen + 1);
376  if (!tmp) {
377  av_free(out);
378  return NULL;
379  }
380  out = tmp;
381  }
382  if (*str == '&') {
383  memcpy(&out[pos], "&amp;", 5);
384  pos += 5;
385  } else if (*str == '<') {
386  memcpy(&out[pos], "&lt;", 4);
387  pos += 4;
388  } else if (*str == '>') {
389  memcpy(&out[pos], "&gt;", 4);
390  pos += 4;
391  } else if (*str == '\'') {
392  memcpy(&out[pos], "&apos;", 6);
393  pos += 6;
394  } else if (*str == '\"') {
395  memcpy(&out[pos], "&quot;", 6);
396  pos += 6;
397  } else {
398  out[pos++] = *str;
399  }
400  }
401  out[pos] = '\0';
402  return out;
403 }
404 
405 static void write_time(AVIOContext *out, int64_t time)
406 {
407  int seconds = time / AV_TIME_BASE;
408  int fractions = time % AV_TIME_BASE;
409  int minutes = seconds / 60;
410  int hours = minutes / 60;
411  seconds %= 60;
412  minutes %= 60;
413  avio_printf(out, "PT");
414  if (hours)
415  avio_printf(out, "%dH", hours);
416  if (hours || minutes)
417  avio_printf(out, "%dM", minutes);
418  avio_printf(out, "%d.%dS", seconds, fractions / (AV_TIME_BASE / 10));
419 }
420 
421 static int write_manifest(AVFormatContext *s, int final)
422 {
423  DASHContext *c = s->priv_data;
424  AVIOContext *out;
425  char temp_filename[1024];
426  int ret, i;
427  AVDictionaryEntry *title = av_dict_get(s->metadata, "title", NULL, 0);
428 
429  snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", s->filename);
430  ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
431  if (ret < 0) {
432  av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
433  return ret;
434  }
435  avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
436  avio_printf(out, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
437  "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
438  "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
439  "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n"
440  "\tprofiles=\"urn:mpeg:dash:profile:isoff-live:2011\"\n"
441  "\ttype=\"%s\"\n", final ? "static" : "dynamic");
442  if (final) {
443  avio_printf(out, "\tmediaPresentationDuration=\"");
444  write_time(out, c->total_duration);
445  avio_printf(out, "\"\n");
446  } else {
447  int update_period = c->last_duration / AV_TIME_BASE;
448  if (c->use_template && !c->use_timeline)
449  update_period = 500;
450  avio_printf(out, "\tminimumUpdatePeriod=\"PT%dS\"\n", update_period);
451  avio_printf(out, "\tsuggestedPresentationDelay=\"PT%dS\"\n", c->last_duration / AV_TIME_BASE);
452  if (!c->availability_start_time[0] && s->nb_streams > 0 && c->streams[0].nb_segments > 0) {
453  time_t t = time(NULL);
454  struct tm *ptm, tmbuf;
455  ptm = gmtime_r(&t, &tmbuf);
456  if (ptm) {
457  if (!strftime(c->availability_start_time, sizeof(c->availability_start_time),
458  "%Y-%m-%dT%H:%M:%S", ptm))
459  c->availability_start_time[0] = '\0';
460  }
461  }
462  if (c->availability_start_time[0])
463  avio_printf(out, "\tavailabilityStartTime=\"%s\"\n", c->availability_start_time);
464  if (c->window_size && c->use_template) {
465  avio_printf(out, "\ttimeShiftBufferDepth=\"");
466  write_time(out, c->last_duration * c->window_size);
467  avio_printf(out, "\"\n");
468  }
469  }
470  avio_printf(out, "\tminBufferTime=\"");
471  write_time(out, c->last_duration);
472  avio_printf(out, "\">\n");
473  avio_printf(out, "\t<ProgramInformation>\n");
474  if (title) {
475  char *escaped = xmlescape(title->value);
476  avio_printf(out, "\t\t<Title>%s</Title>\n", escaped);
477  av_free(escaped);
478  }
479  avio_printf(out, "\t</ProgramInformation>\n");
480  if (c->window_size && s->nb_streams > 0 && c->streams[0].nb_segments > 0 && !c->use_template) {
481  OutputStream *os = &c->streams[0];
482  int start_index = FFMAX(os->nb_segments - c->window_size, 0);
483  int64_t start_time = av_rescale_q(os->segments[start_index]->time, s->streams[0]->time_base, AV_TIME_BASE_Q);
484  avio_printf(out, "\t<Period start=\"");
485  write_time(out, start_time);
486  avio_printf(out, "\">\n");
487  } else {
488  avio_printf(out, "\t<Period start=\"PT0.0S\">\n");
489  }
490 
491  if (c->has_video) {
492  avio_printf(out, "\t\t<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
493  for (i = 0; i < s->nb_streams; i++) {
494  AVStream *st = s->streams[i];
495  OutputStream *os = &c->streams[i];
497  continue;
498  avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codec->width, st->codec->height);
499  output_segment_list(&c->streams[i], out, c);
500  avio_printf(out, "\t\t\t</Representation>\n");
501  }
502  avio_printf(out, "\t\t</AdaptationSet>\n");
503  }
504  if (c->has_audio) {
505  avio_printf(out, "\t\t<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n");
506  for (i = 0; i < s->nb_streams; i++) {
507  AVStream *st = s->streams[i];
508  OutputStream *os = &c->streams[i];
510  continue;
511  avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/mp4\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", i, os->codec_str, os->bandwidth_str, st->codec->sample_rate);
512  avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st->codec->channels);
513  output_segment_list(&c->streams[i], out, c);
514  avio_printf(out, "\t\t\t</Representation>\n");
515  }
516  avio_printf(out, "\t\t</AdaptationSet>\n");
517  }
518  avio_printf(out, "\t</Period>\n");
519  avio_printf(out, "</MPD>\n");
520  avio_flush(out);
521  avio_close(out);
522  return ff_rename(temp_filename, s->filename, s);
523 }
524 
526 {
527  DASHContext *c = s->priv_data;
528  int ret = 0, i;
529  AVOutputFormat *oformat;
530  char *ptr;
531  char basename[1024];
532 
533  if (c->single_file_name)
534  c->single_file = 1;
535  if (c->single_file)
536  c->use_template = 0;
537 
538  av_strlcpy(c->dirname, s->filename, sizeof(c->dirname));
539  ptr = strrchr(c->dirname, '/');
540  if (ptr) {
541  av_strlcpy(basename, &ptr[1], sizeof(basename));
542  ptr[1] = '\0';
543  } else {
544  c->dirname[0] = '\0';
545  av_strlcpy(basename, s->filename, sizeof(basename));
546  }
547 
548  ptr = strrchr(basename, '.');
549  if (ptr)
550  *ptr = '\0';
551 
552  oformat = av_guess_format("mp4", NULL, NULL);
553  if (!oformat) {
555  goto fail;
556  }
557 
558  c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams);
559  if (!c->streams) {
560  ret = AVERROR(ENOMEM);
561  goto fail;
562  }
563 
564  for (i = 0; i < s->nb_streams; i++) {
565  OutputStream *os = &c->streams[i];
566  AVFormatContext *ctx;
567  AVStream *st;
568  AVDictionary *opts = NULL;
569  char filename[1024];
570 
571  os->bit_rate = s->streams[i]->codec->bit_rate ?
572  s->streams[i]->codec->bit_rate :
573  s->streams[i]->codec->rc_max_rate;
574  if (os->bit_rate) {
575  snprintf(os->bandwidth_str, sizeof(os->bandwidth_str),
576  " bandwidth=\"%d\"", os->bit_rate);
577  } else {
580  av_log(s, level, "No bit rate set for stream %d\n", i);
582  ret = AVERROR(EINVAL);
583  goto fail;
584  }
585  }
586 
587  ctx = avformat_alloc_context();
588  if (!ctx) {
589  ret = AVERROR(ENOMEM);
590  goto fail;
591  }
592  os->ctx = ctx;
593  ctx->oformat = oformat;
595 
596  if (!(st = avformat_new_stream(ctx, NULL))) {
597  ret = AVERROR(ENOMEM);
598  goto fail;
599  }
602  st->time_base = s->streams[i]->time_base;
604 
605  ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf), AVIO_FLAG_WRITE, os, NULL, dash_write, NULL);
606  if (!ctx->pb) {
607  ret = AVERROR(ENOMEM);
608  goto fail;
609  }
610 
611  if (c->single_file) {
612  if (c->single_file_name)
613  dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), c->single_file_name, i, 0, os->bit_rate, 0);
614  else
615  snprintf(os->initfile, sizeof(os->initfile), "%s-stream%d.m4s", basename, i);
616  } else {
617  dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), c->init_seg_name, i, 0, os->bit_rate, 0);
618  }
619  snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
620  ret = ffurl_open(&os->out, filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
621  if (ret < 0)
622  goto fail;
623  os->init_start_pos = 0;
624 
625  av_dict_set(&opts, "movflags", "frag_custom+dash", 0);
626  if ((ret = avformat_write_header(ctx, &opts)) < 0) {
627  goto fail;
628  }
629  os->ctx_inited = 1;
630  avio_flush(ctx->pb);
631  av_dict_free(&opts);
632 
633  if (c->single_file) {
634  os->init_range_length = avio_tell(ctx->pb);
635  } else {
636  ffurl_close(os->out);
637  os->out = NULL;
638  }
639 
640  s->streams[i]->time_base = st->time_base;
641  // If the muxer wants to shift timestamps, request to have them shifted
642  // already before being handed to this muxer, so we don't have mismatches
643  // between the MPD and the actual segments.
645  if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
646  c->has_video = 1;
647  else if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO)
648  c->has_audio = 1;
649 
650  set_codec_str(s, os->ctx->streams[0]->codec, os->codec_str, sizeof(os->codec_str));
652  os->segment_index = 1;
653  }
654 
655  if (!c->has_video && c->min_seg_duration <= 0) {
656  av_log(s, AV_LOG_WARNING, "no video stream and no min seg duration set\n");
657  ret = AVERROR(EINVAL);
658  }
659  ret = write_manifest(s, 0);
660 
661 fail:
662  if (ret)
663  dash_free(s);
664  return ret;
665 }
666 
667 static int add_segment(OutputStream *os, const char *file,
668  int64_t time, int duration,
669  int64_t start_pos, int64_t range_length,
670  int64_t index_length)
671 {
672  int err;
673  Segment *seg;
674  if (os->nb_segments >= os->segments_size) {
675  os->segments_size = (os->segments_size + 1) * 2;
676  if ((err = av_reallocp(&os->segments, sizeof(*os->segments) *
677  os->segments_size)) < 0) {
678  os->segments_size = 0;
679  os->nb_segments = 0;
680  return err;
681  }
682  }
683  seg = av_mallocz(sizeof(*seg));
684  if (!seg)
685  return AVERROR(ENOMEM);
686  av_strlcpy(seg->file, file, sizeof(seg->file));
687  seg->time = time;
688  seg->duration = duration;
689  seg->start_pos = start_pos;
690  seg->range_length = range_length;
691  seg->index_length = index_length;
692  os->segments[os->nb_segments++] = seg;
693  os->segment_index++;
694  return 0;
695 }
696 
697 static void write_styp(AVIOContext *pb)
698 {
699  avio_wb32(pb, 24);
700  ffio_wfourcc(pb, "styp");
701  ffio_wfourcc(pb, "msdh");
702  avio_wb32(pb, 0); /* minor */
703  ffio_wfourcc(pb, "msdh");
704  ffio_wfourcc(pb, "msix");
705 }
706 
707 static void find_index_range(AVFormatContext *s, const char *dirname,
708  const char *filename, int64_t pos,
709  int *index_length)
710 {
711  char full_path[1024];
712  uint8_t buf[8];
713  URLContext *fd;
714  int ret;
715 
716  snprintf(full_path, sizeof(full_path), "%s%s", dirname, filename);
717  ret = ffurl_open(&fd, full_path, AVIO_FLAG_READ, &s->interrupt_callback, NULL);
718  if (ret < 0)
719  return;
720  if (ffurl_seek(fd, pos, SEEK_SET) != pos) {
721  ffurl_close(fd);
722  return;
723  }
724  ret = ffurl_read(fd, buf, 8);
725  ffurl_close(fd);
726  if (ret < 8)
727  return;
728  if (AV_RL32(&buf[4]) != MKTAG('s', 'i', 'd', 'x'))
729  return;
730  *index_length = AV_RB32(&buf[0]);
731 }
732 
733 static int dash_flush(AVFormatContext *s, int final, int stream)
734 {
735  DASHContext *c = s->priv_data;
736  int i, ret = 0;
737  int cur_flush_segment_index = 0;
738  if (stream >= 0)
739  cur_flush_segment_index = c->streams[stream].segment_index;
740 
741  for (i = 0; i < s->nb_streams; i++) {
742  OutputStream *os = &c->streams[i];
743  char filename[1024] = "", full_path[1024], temp_path[1024];
744  int64_t start_pos = avio_tell(os->ctx->pb);
745  int range_length, index_length = 0;
746 
747  if (!os->packets_written)
748  continue;
749 
750  // Flush the single stream that got a keyframe right now.
751  // Flush all audio streams as well, in sync with video keyframes,
752  // but not the other video streams.
753  if (stream >= 0 && i != stream) {
755  continue;
756  // Make sure we don't flush audio streams multiple times, when
757  // all video streams are flushed one at a time.
758  if (c->has_video && os->segment_index > cur_flush_segment_index)
759  continue;
760  }
761 
762  if (!c->single_file) {
763  dash_fill_tmpl_params(filename, sizeof(filename), c->media_seg_name, i, os->segment_index, os->bit_rate, os->start_dts);
764  snprintf(full_path, sizeof(full_path), "%s%s", c->dirname, filename);
765  snprintf(temp_path, sizeof(temp_path), "%s.tmp", full_path);
766  ret = ffurl_open(&os->out, temp_path, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL);
767  if (ret < 0)
768  break;
769  write_styp(os->ctx->pb);
770  }
771  av_write_frame(os->ctx, NULL);
772  avio_flush(os->ctx->pb);
773  os->packets_written = 0;
774 
775  range_length = avio_tell(os->ctx->pb) - start_pos;
776  if (c->single_file) {
777  find_index_range(s, c->dirname, os->initfile, start_pos, &index_length);
778  } else {
779  ffurl_close(os->out);
780  os->out = NULL;
781  ret = ff_rename(temp_path, full_path, s);
782  if (ret < 0)
783  break;
784  }
785  add_segment(os, filename, os->start_dts, os->end_dts - os->start_dts, start_pos, range_length, index_length);
786  }
787 
788  if (c->window_size || (final && c->remove_at_exit)) {
789  for (i = 0; i < s->nb_streams; i++) {
790  OutputStream *os = &c->streams[i];
791  int j;
792  int remove = os->nb_segments - c->window_size - c->extra_window_size;
793  if (final && c->remove_at_exit)
794  remove = os->nb_segments;
795  if (remove > 0) {
796  for (j = 0; j < remove; j++) {
797  char filename[1024];
798  snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->segments[j]->file);
799  unlink(filename);
800  av_free(os->segments[j]);
801  }
802  os->nb_segments -= remove;
803  memmove(os->segments, os->segments + remove, os->nb_segments * sizeof(*os->segments));
804  }
805  }
806  }
807 
808  if (ret >= 0)
809  ret = write_manifest(s, final);
810  return ret;
811 }
812 
814 {
815  DASHContext *c = s->priv_data;
816  AVStream *st = s->streams[pkt->stream_index];
817  OutputStream *os = &c->streams[pkt->stream_index];
818  int64_t seg_end_duration = (os->segment_index) * (int64_t) c->min_seg_duration;
819  int ret;
820 
821  // If forcing the stream to start at 0, the mp4 muxer will set the start
822  // timestamps to 0. Do the same here, to avoid mismatches in duration/timestamps.
823  if (os->first_dts == AV_NOPTS_VALUE &&
825  pkt->pts -= pkt->dts;
826  pkt->dts = 0;
827  }
828 
829  if (os->first_dts == AV_NOPTS_VALUE)
830  os->first_dts = pkt->dts;
831 
832  if ((!c->has_video || st->codec->codec_type == AVMEDIA_TYPE_VIDEO) &&
833  pkt->flags & AV_PKT_FLAG_KEY && os->packets_written &&
834  av_compare_ts(pkt->dts - os->first_dts, st->time_base,
835  seg_end_duration, AV_TIME_BASE_Q) >= 0) {
836  int64_t prev_duration = c->last_duration;
837 
838  c->last_duration = av_rescale_q(pkt->dts - os->start_dts,
839  st->time_base,
841  c->total_duration = av_rescale_q(pkt->dts - os->first_dts,
842  st->time_base,
844 
845  if ((!c->use_timeline || !c->use_template) && prev_duration) {
846  if (c->last_duration < prev_duration*9/10 ||
847  c->last_duration > prev_duration*11/10) {
849  "Segment durations differ too much, enable use_timeline "
850  "and use_template, or keep a stricter keyframe interval\n");
851  }
852  }
853 
854  if ((ret = dash_flush(s, 0, pkt->stream_index)) < 0)
855  return ret;
856  }
857 
858  if (!os->packets_written)
859  os->start_dts = pkt->dts;
860  os->end_dts = pkt->dts + pkt->duration;
861  os->packets_written++;
862  return ff_write_chained(os->ctx, 0, pkt, s, 0);
863 }
864 
866 {
867  DASHContext *c = s->priv_data;
868 
869  if (s->nb_streams > 0) {
870  OutputStream *os = &c->streams[0];
871  // If no segments have been written so far, try to do a crude
872  // guess of the segment duration
873  if (!c->last_duration)
875  s->streams[0]->time_base,
878  s->streams[0]->time_base,
880  }
881  dash_flush(s, 1, -1);
882 
883  if (c->remove_at_exit) {
884  char filename[1024];
885  int i;
886  for (i = 0; i < s->nb_streams; i++) {
887  OutputStream *os = &c->streams[i];
888  snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
889  unlink(filename);
890  }
891  unlink(s->filename);
892  }
893 
894  dash_free(s);
895  return 0;
896 }
897 
898 #define OFFSET(x) offsetof(DASHContext, x)
899 #define E AV_OPT_FLAG_ENCODING_PARAM
900 static const AVOption options[] = {
901  { "window_size", "number of segments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E },
902  { "extra_window_size", "number of segments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E },
903  { "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E },
904  { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
905  { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E },
906  { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E },
907  { "single_file", "Store all segments in one file, accessed using byte ranges", OFFSET(single_file), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
908  { "single_file_name", "DASH-templated name to be used for baseURL. Implies storing all segments in one file, accessed using byte ranges", OFFSET(single_file_name), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
909  { "init_seg_name", "DASH-templated name to used for the initialization segment", OFFSET(init_seg_name), AV_OPT_TYPE_STRING, {.str = "init-stream$RepresentationID$.m4s"}, 0, 0, E },
910  { "media_seg_name", "DASH-templated name to used for the media segments", OFFSET(media_seg_name), AV_OPT_TYPE_STRING, {.str = "chunk-stream$RepresentationID$-$Number%05d$.m4s"}, 0, 0, E },
911  { NULL },
912 };
913 
914 static const AVClass dash_class = {
915  .class_name = "dash muxer",
916  .item_name = av_default_item_name,
917  .option = options,
918  .version = LIBAVUTIL_VERSION_INT,
919 };
920 
922  .name = "dash",
923  .long_name = NULL_IF_CONFIG_SMALL("DASH Muxer"),
924  .priv_data_size = sizeof(DASHContext),
925  .audio_codec = AV_CODEC_ID_AAC,
926  .video_codec = AV_CODEC_ID_H264,
931  .codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
932  .priv_class = &dash_class,
933 };