ffmpeg | branch: master | Rodger Combs <rodger.co...@gmail.com> | Tue Sep 26 14:10:30 2017 -0300| [777d53c793a2f19b9f87d935fcb16f07ceae0dca] | committer: James Almer
Merge commit '3d23a5f96ad72961c14ba3a0c2add8f2ab374b61' * commit '3d23a5f96ad72961c14ba3a0c2add8f2ab374b61': dashenc: add support for assigning streams to AdaptationSets Merged-by: Rodger Combs <rodger.co...@gmail.com> > http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=777d53c793a2f19b9f87d935fcb16f07ceae0dca --- libavformat/dashenc.c | 223 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 180 insertions(+), 43 deletions(-) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 089a3e7b01..3719a1ea01 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -25,6 +25,7 @@ #endif #include "libavutil/avassert.h" +#include "libavutil/avutil.h" #include "libavutil/avstring.h" #include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" @@ -50,9 +51,14 @@ typedef struct Segment { int n; } Segment; +typedef struct AdaptationSet { + char id[10]; + enum AVMediaType media_type; +} AdaptationSet; + typedef struct OutputStream { AVFormatContext *ctx; - int ctx_inited; + int ctx_inited, as_idx; uint8_t iobuf[32768]; AVIOContext *out; int packets_written; @@ -71,6 +77,9 @@ typedef struct OutputStream { typedef struct DASHContext { const AVClass *class; /* Class for private options. */ + char *adaptation_sets; + AdaptationSet *as; + int nb_as; int window_size; int extra_window_size; int min_seg_duration; @@ -79,7 +88,7 @@ typedef struct DASHContext { int use_timeline; int single_file; OutputStream *streams; - int has_video, has_audio; + int has_video; int64_t last_duration; int64_t total_duration; char availability_start_time[100]; @@ -170,6 +179,12 @@ static void dash_free(AVFormatContext *s) { DASHContext *c = s->priv_data; int i, j; + + if (c->as) { + av_freep(&c->as); + c->nb_as = 0; + } + if (!c->streams) return; for (i = 0; i < s->nb_streams; i++) { @@ -317,12 +332,167 @@ static void format_date_now(char *buf, int size) } } +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<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; @@ -393,44 +563,9 @@ static int write_manifest(AVFormatContext *s, int final) avio_printf(out, "\t<Period id=\"0\" start=\"PT0.0S\">\n"); } - if (c->has_video) { - avio_printf(out, "\t\t<AdaptationSet id=\"%d\" contentType=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\"", as_id++); - if (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++) { - AVStream *st = s->streams[i]; - OutputStream *os = &c->streams[i]; - - if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) - continue; - - 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, st->codecpar->width, st->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"); - - output_segment_list(&c->streams[i], out, c); - avio_printf(out, "\t\t\t</Representation>\n"); - } - avio_printf(out, "\t\t</AdaptationSet>\n"); - } - if (c->has_audio) { - avio_printf(out, "\t\t<AdaptationSet id=\"%d\" contentType=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\n", as_id++); - for (i = 0; i < s->nb_streams; i++) { - AVStream *st = s->streams[i]; - OutputStream *os = &c->streams[i]; - - if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) - continue; - - 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->codecpar->sample_rate); - avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", st->codecpar->channels); - output_segment_list(&c->streams[i], out, c); - avio_printf(out, "\t\t\t</Representation>\n"); - } - avio_printf(out, "\t\t</AdaptationSet>\n"); + for (i = 0; i < c->nb_as; i++) { + if ((ret = write_adaptation_set(s, out, i)) < 0) + return ret; } avio_printf(out, "\t</Period>\n"); avio_printf(out, "</MPD>\n"); @@ -479,6 +614,9 @@ static int dash_init(AVFormatContext *s) if (!c->streams) return AVERROR(ENOMEM); + if ((ret = parse_adaptation_sets(s)) < 0) + return ret; + for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; AVFormatContext *ctx; @@ -559,8 +697,6 @@ static int dash_init(AVFormatContext *s) 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)); @@ -926,6 +1062,7 @@ static int dash_check_bitstream(struct AVFormatContext *s, const AVPacket *avpkt #define OFFSET(x) offsetof(DASHContext, x) #define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { + { "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 }, { "window_size", "number of segments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E }, { "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 }, { "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT64, { .i64 = 5000000 }, 0, INT_MAX, E }, ====================================================================== diff --cc libavformat/dashenc.c index 089a3e7b01,e69e63e9ab..3719a1ea01 --- a/libavformat/dashenc.c +++ b/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, } } + 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<AdaptationSet id=\"%s\" contentType=\"%s\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">\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) { - avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/mp4\" codecs=\"%s\"%s width=\"%d\" height=\"%d\">\n", ++ 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); + if ((ret = parse_adaptation_sets(s)) < 0) - goto fail; ++ return ret; + for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; AVFormatContext *ctx; @@@ -548,20 -798,8 +686,18 @@@ // 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; _______________________________________________ ffmpeg-cvslog mailing list ffmpeg-cvslog@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-cvslog