Merge commit '3d23a5f96ad72961c14ba3a0c2add8f2ab374b61'
authorRodger Combs <rodger.combs@gmail.com>
Tue, 26 Sep 2017 17:10:30 +0000 (14:10 -0300)
committerJames Almer <jamrial@gmail.com>
Tue, 26 Sep 2017 17:10:30 +0000 (14:10 -0300)
* commit '3d23a5f96ad72961c14ba3a0c2add8f2ab374b61':
  dashenc: add support for assigning streams to AdaptationSets

Merged-by: Rodger Combs <rodger.combs@gmail.com>
1  2 
libavformat/dashenc.c

@@@ -24,7 -24,7 +24,8 @@@
  #include <unistd.h>
  #endif
  
 +#include "libavutil/avassert.h"
+ #include "libavutil/avutil.h"
  #include "libavutil/avstring.h"
  #include "libavutil/intreadwrite.h"
  #include "libavutil/mathematics.h"
@@@ -317,21 -450,163 +332,176 @@@ static void format_date_now(char *buf, 
      }
  }
  
 -    avio_printf(out, "\t\t<AdaptationSet id=\"%s\" contentType=\"%s\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n",
+ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index)
+ {
+     DASHContext *c = s->priv_data;
+     AdaptationSet *as = &c->as[as_index];
+     int i;
 -            avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\">\n",
++    avio_printf(out, "\t\t<AdaptationSet id=\"%s\" contentType=\"%s\" segmentAlignment=\"true\" bitstreamSwitching=\"true\"",
+                 as->id, as->media_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio");
++    if (as->media_type == AVMEDIA_TYPE_VIDEO && c->max_frame_rate.num && !c->ambiguous_frame_rate)
++        avio_printf(out, " %s=\"%d/%d\"", (av_cmp_q(c->min_frame_rate, c->max_frame_rate) < 0) ? "maxFrameRate" : "frameRate", c->max_frame_rate.num, c->max_frame_rate.den);
++    avio_printf(out, ">\n");
+     for (i = 0; i < s->nb_streams; i++) {
+         OutputStream *os = &c->streams[i];
+         if (os->as_idx - 1 != as_index)
+             continue;
+         if (as->media_type == AVMEDIA_TYPE_VIDEO) {
++            AVStream *st = s->streams[i];
++            avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\"",
+                 i, os->codec_str, os->bandwidth_str, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height);
++            if (st->avg_frame_rate.num)
++                avio_printf(out, " frameRate=\"%d/%d\"", st->avg_frame_rate.num, st->avg_frame_rate.den);
++            avio_printf(out, ">\n");
+         } else {
+             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, s->streams[i]->codecpar->sample_rate);
+             avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n",
+                 s->streams[i]->codecpar->channels);
+         }
+         output_segment_list(os, out, c);
+         avio_printf(out, "\t\t\t</Representation>\n");
+     }
+     avio_printf(out, "\t\t</AdaptationSet>\n");
+     return 0;
+ }
+ static int add_adaptation_set(AVFormatContext *s, AdaptationSet **as, enum AVMediaType type)
+ {
+     DASHContext *c = s->priv_data;
+     void *mem = av_realloc(c->as, sizeof(*c->as) * (c->nb_as + 1));
+     if (!mem)
+         return AVERROR(ENOMEM);
+     c->as = mem;
+     ++c->nb_as;
+     *as = &c->as[c->nb_as - 1];
+     memset(*as, 0, sizeof(**as));
+     (*as)->media_type = type;
+     return 0;
+ }
+ static int parse_adaptation_sets(AVFormatContext *s)
+ {
+     DASHContext *c = s->priv_data;
+     const char *p = c->adaptation_sets;
+     enum { new_set, parse_id, parsing_streams } state;
+     AdaptationSet *as;
+     int i, n, ret;
+     enum AVMediaType types[] = { AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_UNKNOWN };
+     // default: one AdaptationSet for each media type
+     if (!p) {
+         for (n = 0; types[n] != AVMEDIA_TYPE_UNKNOWN; n++) {
+             int as_idx = 0;
+             for (i = 0; i < s->nb_streams; i++) {
+                 if (s->streams[i]->codecpar->codec_type != types[n])
+                     continue;
+                 if (!as_idx) {
+                     if ((ret = add_adaptation_set(s, &as, types[n])) < 0)
+                         return ret;
+                     as_idx = c->nb_as;
+                     snprintf(as->id, sizeof(as->id), "%d", i);
+                 }
+                 c->streams[i].as_idx = as_idx;
+             }
+         }
+         goto end;
+     }
+     // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on
+     state = new_set;
+     while (*p) {
+         if (*p == ' ') {
+             p++;
+             continue;
+         } else if (state == new_set && av_strstart(p, "id=", &p)) {
+             if ((ret = add_adaptation_set(s, &as, AVMEDIA_TYPE_UNKNOWN)) < 0)
+                 return ret;
+             n = strcspn(p, ",");
+             snprintf(as->id, sizeof(as->id), "%.*s", n, p);
+             p += n;
+             if (*p)
+                 p++;
+             state = parse_id;
+         } else if (state == parse_id && av_strstart(p, "streams=", &p)) {
+             state = parsing_streams;
+         } else if (state == parsing_streams) {
+             AdaptationSet *as = &c->as[c->nb_as - 1];
+             OutputStream *os;
+             char idx_str[8], *end_str;
+             n = strcspn(p, " ,");
+             snprintf(idx_str, sizeof(idx_str), "%.*s", n, p);
+             p += n;
+             i = strtol(idx_str, &end_str, 10);
+             if (idx_str == end_str || i < 0 || i >= s->nb_streams) {
+                 av_log(s, AV_LOG_ERROR, "Selected stream \"%s\" not found!\n", idx_str);
+                 return AVERROR(EINVAL);
+             }
+             os = &c->streams[i];
+             if (as->media_type == AVMEDIA_TYPE_UNKNOWN) {
+                 as->media_type = s->streams[i]->codecpar->codec_type;
+             } else if (as->media_type != s->streams[i]->codecpar->codec_type) {
+                 av_log(s, AV_LOG_ERROR, "Mixing codec types within an AdaptationSet is not allowed\n");
+                 return AVERROR(EINVAL);
+             } else if (os->as_idx) {
+                 av_log(s, AV_LOG_ERROR, "Assigning a stream to more than one AdaptationSet is not allowed\n");
+                 return AVERROR(EINVAL);
+             }
+             os->as_idx = c->nb_as;
+             if (*p == ' ')
+                 state = new_set;
+             if (*p)
+                 p++;
+         } else {
+             return AVERROR(EINVAL);
+         }
+     }
+ end:
+     // check for unassigned streams
+     for (i = 0; i < s->nb_streams; i++) {
+         OutputStream *os = &c->streams[i];
+         if (!os->as_idx) {
+             av_log(s, AV_LOG_ERROR, "Stream %d is not mapped to an AdaptationSet\n", i);
+             return AVERROR(EINVAL);
+         }
+     }
+     return 0;
+ }
  static int write_manifest(AVFormatContext *s, int final)
  {
      DASHContext *c = s->priv_data;
      AVIOContext *out;
      char temp_filename[1024];
-     int ret, i, as_id = 0;
+     int ret, i;
 +    const char *proto = avio_find_protocol_name(s->filename);
 +    int use_rename = proto && !strcmp(proto, "file");
 +    static unsigned int warned_non_file = 0;
      AVDictionaryEntry *title = av_dict_get(s->metadata, "title", NULL, 0);
  
 -    snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", s->filename);
 +    if (!use_rename && !warned_non_file++)
 +        av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporary partial files\n");
 +
 +    snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", s->filename);
      ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL);
      if (ret < 0) {
          av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
@@@ -472,13 -707,20 +607,16 @@@ static int dash_init(AVFormatContext *s
          *ptr = '\0';
  
      oformat = av_guess_format("mp4", NULL, NULL);
 -    if (!oformat) {
 -        ret = AVERROR_MUXER_NOT_FOUND;
 -        goto fail;
 -    }
 +    if (!oformat)
 +        return AVERROR_MUXER_NOT_FOUND;
  
      c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams);
 -    if (!c->streams) {
 -        ret = AVERROR(ENOMEM);
 -        goto fail;
 -    }
 +    if (!c->streams)
 +        return AVERROR(ENOMEM);
  
 -        goto fail;
+     if ((ret = parse_adaptation_sets(s)) < 0)
++        return ret;
      for (i = 0; i < s->nb_streams; i++) {
          OutputStream *os = &c->streams[i];
          AVFormatContext *ctx;
          // already before being handed to this muxer, so we don't have mismatches
          // between the MPD and the actual segments.
          s->avoid_negative_ts = ctx->avoid_negative_ts;
 -        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
 +        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
 +            AVRational avg_frame_rate = s->streams[i]->avg_frame_rate;
 +            if (avg_frame_rate.num > 0) {
 +                if (av_cmp_q(avg_frame_rate, c->min_frame_rate) < 0)
 +                    c->min_frame_rate = avg_frame_rate;
 +                if (av_cmp_q(c->max_frame_rate, avg_frame_rate) < 0)
 +                    c->max_frame_rate = avg_frame_rate;
 +            } else {
 +                c->ambiguous_frame_rate = 1;
 +            }
              c->has_video = 1;
-         } else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
-             c->has_audio = 1;
 +        }
  
          set_codec_str(s, st->codecpar, os->codec_str, sizeof(os->codec_str));
          os->first_pts = AV_NOPTS_VALUE;