From patchwork Thu Nov 8 16:57:22 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrey Semashev X-Patchwork-Id: 10955 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 7C37E446F01 for ; Thu, 8 Nov 2018 19:04:02 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id E1793689F1F; Thu, 8 Nov 2018 19:03:33 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lj1-f194.google.com (mail-lj1-f194.google.com [209.85.208.194]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 16381689A11 for ; Thu, 8 Nov 2018 19:03:27 +0200 (EET) Received: by mail-lj1-f194.google.com with SMTP id q186-v6so18640907ljb.5 for ; Thu, 08 Nov 2018 09:04:02 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=weJxValk3orBv1N5O5K8d3GSvA6WrhEFJteal+sO800=; b=XD1CPlNE0STqM8R1FcBidFNKnCGIP3vYtr9ZvZPX0UpUDQ+j/qRwOlTFC4k4L6snFK 9WwXCkWcX497sl22O/2ZcKqlllgSt609mHPzrQjQ/KRiScgScNegPBum/UMBgV5F9XuQ 98D6cRSQKzQ1M92JFjsaAbqS3E9BO1U12nri13k4cZPYZNC7HTz0VRb3Rc++GNVL1Ad2 mLU6WJmuxJmkOew7cCHfx8cbo5YlOs7m6rddfa1WirkNbjZcgIfh3x4kcljpVu04gtAL KTwk+sgVpWswvCBE+BfLF1y946gypzHFGz4oKC4SUvlzGf5aE08FSdLKaQViefp7s0jk vPGg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=weJxValk3orBv1N5O5K8d3GSvA6WrhEFJteal+sO800=; b=kA6DPXsAJ2YWuP29bq8h2f9iNo8q7vFFfhs54HktGabllDB0gGQ1CmP5Xbid8tH7WD mVGtHf4GZOyVceFcRjSa9xgLlVVLagQABLzdo9fpH/YGjhahm11Vb8gA6iwERPnUG1bX tIwFoy+womhk1O2gsk0Qgmu9OXbij8wyCytEf615pJuIRyo8MhV9RRj4l7HMDYiUTIO9 Sfd3/EXYUavF8hJ2pjm56S1XjsrLX/EgqvhCI3F011IGlijm7EVbyCURypsjnIyFiZby oNRnHIVEb3G3Xxt+eElYhfbEovIwAwKc2WBYENQWSAEt3xUwRWnK5vkYgH+GI8odR7qN +CuQ== X-Gm-Message-State: AGRZ1gKV0Fn/qXMhgsaSkW+UBX78tAxGrLwbB1MNn5WunjMXLmccqy92 3/yVb9I0Fs95/9yU7XQiV1kyW/p+ X-Google-Smtp-Source: AJdET5fjfsnaRg355if5l+7khusqqyeUczD4aIecJqpCbQ8RO4t60v+nou6i6oR0as6Qku7wRtOeNA== X-Received: by 2002:a2e:3012:: with SMTP id w18-v6mr3284046ljw.75.1541696280034; Thu, 08 Nov 2018 08:58:00 -0800 (PST) Received: from localhost.localdomain (broadband-37-110-31-10.ip.moscow.rt.ru. [37.110.31.10]) by smtp.gmail.com with ESMTPSA id z8-v6sm743439lfh.52.2018.11.08.08.57.58 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 08 Nov 2018 08:57:59 -0800 (PST) From: Andrey Semashev To: ffmpeg-devel@ffmpeg.org Date: Thu, 8 Nov 2018 19:57:22 +0300 Message-Id: <20181108165722.7338-1-andrey.semashev@gmail.com> X-Mailer: git-send-email 2.19.1 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] lavf/dashenc: Add support for per-stream container type selection. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Andrey Semashev Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" This commit restores the ability to create DASH streams with codecs that require different containers that was lost after commit 2efdbf7367989cf9d296c25fa3d2aff8d6e25fdd. It extends the dash_segment_type option syntax to allow to specify segment container types for individual streams, in addition to the default container type that is applied to all streams. The extended syntax is backward compatible with the previous syntax. --- doc/muxers.texi | 8 ++- libavformat/dashenc.c | 161 +++++++++++++++++++++++++++++++++++------- 2 files changed, 140 insertions(+), 29 deletions(-) diff --git a/doc/muxers.texi b/doc/muxers.texi index 62f4091e31..4418b38c76 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -289,8 +289,12 @@ Set container format (mp4/webm) options using a @code{:} separated list of key=value parameters. Values containing @code{:} special characters must be escaped. -@item dash_segment_type @var{dash_segment_type} -Possible values: +@item -dash_segment_type @var{dash_segment_type} +Sets the container type for dash segment files. Syntax is " :a,b,c :d,e" where is +the container type and a, b, c, d and e are the indices of the mapped streams. When no indices are specified, +the container type is set for all streams. + +Possible container type values: @item mp4 If this flag is set, the dash segment files will be in in ISOBMFF format. This is the default format. diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index f8b3d106d5..626dc76413 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -84,6 +84,8 @@ typedef struct OutputStream { int64_t first_pts, start_pts, max_pts; int64_t last_dts, last_pts; int bit_rate; + SegmentType segment_type; + const char *format_name; char codec_str[100]; int written_len; @@ -131,8 +133,7 @@ typedef struct DASHContext { int64_t timeout; int index_correction; char *format_options_str; - SegmentType segment_type; - const char *format_name; + const char *segment_types_str; } DASHContext; static struct codec_string { @@ -188,14 +189,6 @@ static void dashenc_io_close(AVFormatContext *s, AVIOContext **pb, char *filenam } } -static const char *get_format_str(SegmentType segment_type) { - int i; - for (i = 0; i < SEGMENT_TYPE_NB; i++) - if (formats[i].segment_type == segment_type) - return formats[i].str; - return NULL; -} - static int check_file_extension(const char *filename, const char *extension) { char *dot; if (!filename || !extension) @@ -375,6 +368,8 @@ static void dash_free(AVFormatContext *s) c->nb_as = 0; } + av_freep(&c->segment_types_str); + if (!c->streams) return; for (i = 0; i < s->nb_streams; i++) { @@ -621,13 +616,13 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind if (as->media_type == AVMEDIA_TYPE_VIDEO) { AVStream *st = s->streams[i]; avio_printf(out, "\t\t\tformat_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height); + i, os->format_name, os->codec_str, 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\n", - i, c->format_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->sample_rate); + i, os->format_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->sample_rate); avio_printf(out, "\t\t\t\t\n", s->streams[i]->codecpar->channels); } @@ -773,6 +768,120 @@ end: return 0; } +static inline void set_all_segment_types(AVFormatContext *s, int format_idx) +{ + DASHContext *c = s->priv_data; + for (int i = 0; i < s->nb_streams; ++i) { + OutputStream *os = &c->streams[i]; + os->segment_type = formats[format_idx].segment_type; + os->format_name = formats[format_idx].str; + } +} + +static int parse_segment_types(AVFormatContext *s) +{ + DASHContext *c = s->priv_data; + const char *p = c->segment_types_str; + enum { type_expected, type_parsed, parsing_streams } state; + int i, format_idx; + + // Set the default container type in case if some streams are not mentioned in the string + set_all_segment_types(s, 0); + + if (!p) + return 0; + + // Parse per-stream container types: mp4 webm:0,1,2 and so on + state = type_expected; + format_idx = 0; + while (*p) { + if (*p == ' ') { + if (state == type_parsed) + set_all_segment_types(s, format_idx); + + state = type_expected; + ++p; + continue; + } + + if (state == type_expected) { + for (i = 0; i < SEGMENT_TYPE_NB; i++) { + if (av_strstart(p, formats[i].str, &p)) { + state = type_parsed; + format_idx = i; + break; + } + } + + if (state != type_parsed) { + av_log(s, AV_LOG_ERROR, "DASH segment type not recognized at position %d of \"%s\"\n", (int)(p - c->segment_types_str + 1), c->segment_types_str); + return AVERROR_MUXER_NOT_FOUND; + } + + continue; + } + + if (state == type_parsed) { + if (*p != ':') { + av_log(s, AV_LOG_ERROR, "Unexpected character at position %d of \"%s\", a colon is expected\n", (int)(p - c->segment_types_str + 1), c->segment_types_str); + return AVERROR(EINVAL); + } + + state = parsing_streams; + ++p; + continue; + } + + if (state == parsing_streams) { + while (*p && *p != ' ') { + if (*p == ',') { + ++p; + } else if (*p == 'a' || *p == 'v') { + // Select all streams of the given type + enum AVMediaType type = (*p == 'v') ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO; + + for (i = 0; i < s->nb_streams; ++i) { + if (s->streams[i]->codecpar->codec_type == type) { + OutputStream *os = &c->streams[i]; + os->segment_type = formats[format_idx].segment_type; + os->format_name = formats[format_idx].str; + } + } + + ++p; + } else { + // Select a stream by index + OutputStream *os; + const char* end_p = p; + i = strtol(p, (char**)&end_p, 10); + if (p == end_p) { + av_log(s, AV_LOG_ERROR, "Failed to parse stream index at position %d of \"%s\"\n", (int)(p - c->segment_types_str + 1), c->segment_types_str); + return AVERROR(EINVAL); + } + if (i < 0 || i >= s->nb_streams) { + av_log(s, AV_LOG_ERROR, "Selected stream %d not found!\n", i); + return AVERROR(EINVAL); + } + + os = &c->streams[i]; + os->segment_type = formats[format_idx].segment_type; + os->format_name = formats[format_idx].str; + + p = end_p; + } + } + + state = type_expected; + } + } + + // Complete segment type setup if no streams are specified after the container type + if (state == type_parsed) + set_all_segment_types(s, format_idx); + + return 0; +} + static int write_manifest(AVFormatContext *s, int final) { DASHContext *c = s->priv_data; @@ -992,6 +1101,9 @@ static int dash_init(AVFormatContext *s) if ((ret = parse_adaptation_sets(s)) < 0) return ret; + if ((ret = parse_segment_types(s)) < 0) + return ret; + for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; AdaptationSet *as = &c->as[os->as_idx - 1]; @@ -1017,13 +1129,10 @@ static int dash_init(AVFormatContext *s) if (!ctx) return AVERROR(ENOMEM); - c->format_name = get_format_str(c->segment_type); - if (!c->format_name) - return AVERROR_MUXER_NOT_FOUND; - if (c->segment_type == SEGMENT_TYPE_WEBM) { - if ((!c->single_file && check_file_extension(c->init_seg_name, c->format_name) != 0) || - (!c->single_file && check_file_extension(c->media_seg_name, c->format_name) != 0) || - (c->single_file && check_file_extension(c->single_file_name, c->format_name) != 0)) { + if (os->segment_type == SEGMENT_TYPE_WEBM) { + if ((!c->single_file && check_file_extension(c->init_seg_name, os->format_name) != 0) || + (!c->single_file && check_file_extension(c->media_seg_name, os->format_name) != 0) || + (c->single_file && check_file_extension(c->single_file_name, os->format_name) != 0)) { av_log(s, AV_LOG_WARNING, "One or many segment file names doesn't end with .webm. " "Override -init_seg_name and/or -media_seg_name and/or " @@ -1031,7 +1140,7 @@ static int dash_init(AVFormatContext *s) } } - ctx->oformat = av_guess_format(c->format_name, NULL, NULL); + ctx->oformat = av_guess_format(os->format_name, NULL, NULL); if (!ctx->oformat) return AVERROR_MUXER_NOT_FOUND; os->ctx = ctx; @@ -1075,7 +1184,7 @@ static int dash_init(AVFormatContext *s) return ret; } - if (c->segment_type == SEGMENT_TYPE_MP4) { + if (os->segment_type == SEGMENT_TYPE_MP4) { if (c->streaming) av_dict_set(&opts, "movflags", "frag_every_frame+dash+delay_moov+global_sidx", 0); else @@ -1140,7 +1249,7 @@ static int dash_write_header(AVFormatContext *s) // Flush init segment // Only for WebM segment, since for mp4 delay_moov is set and // the init segment is thus flushed after the first packets. - if (c->segment_type == SEGMENT_TYPE_WEBM && + if (os->segment_type == SEGMENT_TYPE_WEBM && (ret = flush_init_segment(s, os)) < 0) return ret; } @@ -1311,7 +1420,7 @@ static int dash_flush(AVFormatContext *s, int final, int stream) } if (!c->single_file) { - if (c->segment_type == SEGMENT_TYPE_MP4 && !os->written_len) + if (os->segment_type == SEGMENT_TYPE_MP4 && !os->written_len) write_styp(os->ctx->pb); } else { snprintf(os->full_path, sizeof(os->full_path), "%s%s", c->dirname, os->initfile); @@ -1501,7 +1610,7 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) } //write out the data immediately in streaming mode - if (c->streaming && c->segment_type == SEGMENT_TYPE_MP4) { + if (c->streaming && os->segment_type == SEGMENT_TYPE_MP4) { int len = 0; uint8_t *buf = NULL; if (!os->written_len) @@ -1597,9 +1706,7 @@ static const AVOption options[] = { { "timeout", "set timeout for socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_DURATION, { .i64 = -1 }, -1, INT_MAX, .flags = E }, { "index_correction", "Enable/Disable segment index correction logic", OFFSET(index_correction), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, { "format_options","set list of options for the container format (mp4/webm) used for dash", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, - { "dash_segment_type", "set dash segment files type", OFFSET(segment_type), AV_OPT_TYPE_INT, {.i64 = SEGMENT_TYPE_MP4 }, 0, SEGMENT_TYPE_NB - 1, E, "segment_type"}, - { "mp4", "make segment file in ISOBMFF format", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_MP4 }, 0, UINT_MAX, E, "segment_type"}, - { "webm", "make segment file in WebM format", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_WEBM }, 0, UINT_MAX, E, "segment_type"}, + { "dash_segment_type", "DASH segment files type. Syntax: \" :\" where is one of mp4 or webm and are comma-separated stream ids", OFFSET(segment_types_str), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, { NULL }, };