From patchwork Thu Jul 12 23:45:17 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacob Trimble X-Patchwork-Id: 9706 Delivered-To: ffmpegpatchwork@gmail.com Received: by 2002:a02:104:0:0:0:0:0 with SMTP id c4-v6csp156578jad; Thu, 12 Jul 2018 16:45:37 -0700 (PDT) X-Google-Smtp-Source: AAOMgpfQle6ksjkop/46szR6i4OOw7yM2bLRAWuXcZv/+Ud766x0k4Dp5NCPeB6nPTdsiM6BcwHh X-Received: by 2002:adf:8466:: with SMTP id 93-v6mr2696241wrf.274.1531439137495; Thu, 12 Jul 2018 16:45:37 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1531439137; cv=none; d=google.com; s=arc-20160816; b=dNP/B6IDR/QnbV7HGWfUKDHWW0PrBlUPYVJtU5RAJG97BoGE7eYxn58R5nWa4DyJ61 l7pUd/szlCF4IGkXfFbYnuKBclLXLrWI/FqifKjCP6qBXX6RBN/mANY+fvJZqHXY9+h6 Wf74yumTClntpIqCP18GQduEK+OonqEbVGEOgi1BBycl0V6Av+P1S0alDy8pg1CVcd7k kzHLHfKsmKoczPMdjaMw3IAubxe9v2T9KP6BUiSOVmw1DQzhQhr9yY3dL0K/0mxqI6Op UZfqKDLpEDZ6tkXfkGugJqgVtaAiUd9zRAZA7UaQzYxtPD7wymh2CX9Jts80kXfwCxnG 9HCw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:reply-to:list-subscribe:list-help:list-post :list-archive:list-unsubscribe:list-id:precedence:subject:to :message-id:date:from:mime-version:dkim-signature:delivered-to :arc-authentication-results; bh=ERTlnffNXBRMxxsmA96nkEwlGe6Vbxztb9iFlQHnsVE=; b=Yzw1qQVznJEkNgqlDogbd+e9lf/ITFFA6ixf1GETEe18sKqTmTLuBqBkVwJXX/TA4L ICu9mPao/LQB+Sz0nGNbmltTTjQ87v6Uc7f6Mts/vPQm99DMrlUyBgjP3QhzsO9SAoma nuYp5immgt7hqpBd5vYDnCrvaBMGy4z3J7TdBGcyY16FfUgG6bQVfU34uPwF9WAoIr8p 1e0QJEWMdxpOdWCfXDxcQSSXLeifenOwnjNO6amgRV4PBux4wtQ47UN1HdYmh9nLJ6NQ pStFBn+NFQIQxdyE7LVgAArMBP6LBnM857TxSrZqKxHI7Gth75mud6cYTxnN3seUzm5T TmWg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20161025 header.b=Psh5QaRe; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id o15-v6si20552302wrs.13.2018.07.12.16.45.37; Thu, 12 Jul 2018 16:45:37 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@google.com header.s=20161025 header.b=Psh5QaRe; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 5F33C68A6D1; Fri, 13 Jul 2018 02:45:27 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lj1-f178.google.com (mail-lj1-f178.google.com [209.85.208.178]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 9759068A4C8 for ; Fri, 13 Jul 2018 02:45:21 +0300 (EEST) Received: by mail-lj1-f178.google.com with SMTP id l15-v6so3110322lji.6 for ; Thu, 12 Jul 2018 16:45:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=mime-version:from:date:message-id:subject:to; bh=IsOLdqyUCJq9GOIMWmpRijXxJToK8kUewgoJq1XOVzE=; b=Psh5QaRelgmnqIrOvG9CBEOmh5k3WZgIYiAq01pn/GGaXzD9t+oN2lUIEW286QFu9r qlfIlAHl+1YBguf/XCqt5VaBnX+V+wTp8B/9mPx+JsUDB1pVWrtUezrtA3NjdUoJ0C0n NFi7jRPqi8p9ZPzgMQZyQJSXhw4JHY3mossdzZsmhsi0hKJCrnluA1TN/mHoiGpuW+nV cqVBTyH4ZA8+6QhXF4RAHkRHIl276Hfkem6kmeNZ+sU3Cx/JhPEajNpALTx4i77ti7ot mLMk7h0jlI+uaWLtYKOizspGts+YwOe1B/gbyskXXHtL8NTXTxam90WkeUm6B7M2bnaG lWkA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=IsOLdqyUCJq9GOIMWmpRijXxJToK8kUewgoJq1XOVzE=; b=NcRKJotyGoIl2jlynq5CaZKYib11YaovADdhCMNx/VskT6BcmT1WdxjVnzRZwMbPen ljnkHZR05mKATYQ8OsXzhnoZVTWfKWc4l5UNkkLkkHFKefQtuI/WOXJVVBph8n0jC9OZ suzGRK5WXhG/UybQ5wzMd1zPGa5CeSoXJzJVcP7ZHfFJCBlariQ2Ue1BT4oTuLZKLdyW FyTo4ARf4Opt2C8FR1jADaA2sEWSaKQRynYWabaJYD168ts/JOtNCsG7V1BSktMdeZu8 586oX7DcfsO7t8lALoHw/jwQ8aXFI6CHdwrhCDoUS28lTevnsScYgKBnEXXuuR942jfo tVHg== X-Gm-Message-State: AOUpUlGhVM5J9b274N170rEjIkB31Z0fn9hi6LRLGAOit3DD1PaSEvOp rXyRwVL+yjPCK+RSj3Y/dZ/J/r2Mztn/JMLBKWnS+iyK/NU= X-Received: by 2002:a2e:9941:: with SMTP id r1-v6mr1885039ljj.53.1531439128976; Thu, 12 Jul 2018 16:45:28 -0700 (PDT) MIME-Version: 1.0 From: Jacob Trimble Date: Thu, 12 Jul 2018 16:45:17 -0700 Message-ID: To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PATCH] avformat/matroska: Parse generic encryption info from packets. 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 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" The attached patch adds parsing for WebM encryption info into the AVEncryptionInfo side-data. The AVEncryptionInitInfo will be handled in another patch. Spec: https://www.webmproject.org/docs/webm-encryption/ I am currently seeing a problem with this when using Opus audio. In read_frame_internal, it will try to parse the resulting packet. For video, which uses subsample encryption, it is able to parse the headers; but for Opus, which uses full-sample encryption, it fails to parse the headers. This causes the read_frame_internal to drop the packet. I have traced a workaround to opus_parse in opus_parser.c: instead of setting poutbuf to NULL, set it to the buffer and just pass the packet to the app to handle it. The frame will be decrypted before passing to the decoder. I can't just disable parsing in the demuxer because I want to parse the packets for clear content and when using subsample encryption. Does anyone have any other ideas to work around this? Is there a way to allow parsing but ignore errors? From 48cabb86f030bfcf1d163db2c361ab8ce743d8b3 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Wed, 11 Jul 2018 11:08:18 -0700 Subject: [PATCH] avformat/matroska: Parse generic encryption info from packets. This parses the WebM generic encryption info out of the packets and adds it to the AV_PKT_DATA_ENCRYPTION_INFO side data. This also changes the data/size fields in the resulting packets so they point to the actual frame data instead of the encryption header. Since this is a breaking change with changing the packet pointer, this parsing is only done if a demuxer option is set. Signed-off-by: Jacob Trimble --- libavformat/matroskadec.c | 136 +++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c index 1ded431b80..e66e660ad4 100644 --- a/libavformat/matroskadec.c +++ b/libavformat/matroskadec.c @@ -367,6 +367,9 @@ typedef struct MatroskaDemuxContext { /* Bandwidth value for WebM DASH Manifest */ int bandwidth; + + /* Parsing encryption info flag */ + int parse_encryption; } MatroskaDemuxContext; typedef struct MatroskaBlock { @@ -3179,6 +3182,111 @@ static int matroska_parse_webvtt(MatroskaDemuxContext *matroska, return 0; } +static int matroska_parse_webm_encryption_info(MatroskaDemuxContext *matroska, MatroskaTrackEncryption *encryption, + AVPacket *pkt) { + uint8_t signal_byte; + uint8_t *side_data; + size_t side_data_size; + int has_subsamples, partition_count, subsample_count, header_size, res = 0; + AVEncryptionInfo *info; + + if (encryption->algo != 5) { + av_log(matroska->ctx, AV_LOG_ERROR, + "Only AES encryption is supported.\n"); + return AVERROR_PATCHWELCOME; + } + if (encryption->key_id.size == 0) { + av_log(matroska->ctx, AV_LOG_ERROR, "No key ID given.\n"); + return AVERROR_INVALIDDATA; + } + + if (pkt->size == 0) { + av_log(matroska->ctx, AV_LOG_ERROR, + "Not enough packet data for encryption header.\n"); + return AVERROR_INVALIDDATA; + } + + signal_byte = pkt->data[0]; + has_subsamples = signal_byte & 0x2; + if (signal_byte & 0xfc) { + av_log(matroska->ctx, AV_LOG_ERROR, "Reserved bit set.\n"); + return AVERROR_PATCHWELCOME; + } + if (!(signal_byte & 0x1)) { + // Frame in clear, skip signal byte. + pkt->data++; + pkt->size--; + return 0; + } + + if (has_subsamples) { + partition_count = pkt->data[9]; + subsample_count = partition_count / 2 + 1; + header_size = 10 + partition_count * 4; + } else { + partition_count = 0; + subsample_count = 0; + header_size = 9; + } + if (pkt->size < header_size) { + av_log(matroska->ctx, AV_LOG_ERROR, + "Not enough packet data for encryption header.\n"); + return AVERROR_INVALIDDATA; + } + + info = av_encryption_info_alloc(subsample_count, encryption->key_id.size, + /* iv_size */ 16); + if (!info) + return AVERROR(ENOMEM); + + info->scheme = MKBETAG('c','e','n','c'); + // Copy the 8-byte IV into the high bytes of |info->iv|, the low bytes should already be set to 0. + memcpy(info->iv, pkt->data + 1, 8); + memcpy(info->key_id, encryption->key_id.data, encryption->key_id.size); + + if (has_subsamples) { + uint32_t partition_offset = 0; + for (int i = 0; i < partition_count + 1; i++) { + const uint32_t next_partition_offset = + i == partition_count ? pkt->size - header_size : AV_RB32(pkt->data + 10 + i * 4); + if (next_partition_offset < partition_offset) { + av_log(matroska->ctx, AV_LOG_ERROR, + "Partition offsets out of order.\n"); + res = AVERROR_INVALIDDATA; + goto error; + } + if (next_partition_offset > pkt->size - header_size) { + av_log(matroska->ctx, AV_LOG_ERROR, + "Partition offset past end of frame data.\n"); + res = AVERROR_INVALIDDATA; + goto error; + } + + if (i % 2 == 0) + info->subsamples[i / 2].bytes_of_clear_data = next_partition_offset - partition_offset; + else + info->subsamples[i / 2].bytes_of_protected_data = next_partition_offset - partition_offset; + partition_offset = next_partition_offset; + } + } + + side_data = av_encryption_info_add_side_data(info, &side_data_size); + if (side_data) { + res = av_packet_add_side_data(pkt, AV_PKT_DATA_ENCRYPTION_INFO, side_data, side_data_size); + if (res < 0) + av_free(side_data); + } else { + res = AVERROR(ENOMEM); + } + + pkt->data += header_size; + pkt->size -= header_size; + +error: + av_encryption_info_free(info); + return res; +} + static int matroska_parse_frame(MatroskaDemuxContext *matroska, MatroskaTrack *track, AVStream *st, AVBufferRef *buf, uint8_t *data, int pkt_size, @@ -3241,6 +3349,14 @@ static int matroska_parse_frame(MatroskaDemuxContext *matroska, pkt->flags = is_keyframe; pkt->stream_index = st->index; + if (matroska->parse_encryption && encodings && encodings->type == 1) { + res = matroska_parse_webm_encryption_info(matroska, &encodings->encryption, pkt); + if (res < 0) { + av_packet_unref(pkt); + return res; + } + } + if (additional_size > 0) { uint8_t *side_data = av_packet_new_side_data(pkt, AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, @@ -4010,16 +4126,29 @@ static int webm_dash_manifest_read_packet(AVFormatContext *s, AVPacket *pkt) } #define OFFSET(x) offsetof(MatroskaDemuxContext, x) -static const AVOption options[] = { +static const AVOption dash_options[] = { { "live", "flag indicating that the input is a live file that only has the headers.", OFFSET(is_live), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, { "bandwidth", "bandwidth of this stream to be specified in the DASH manifest.", OFFSET(bandwidth), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM }, + { "parse_encryption", "flag indicating the demuxer should parse generic encryption info.", OFFSET(parse_encryption), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, + { NULL }, +}; + +static const AVOption matroska_options[] = { + { "parse_encryption", "flag indicating the demuxer should parse generic encryption info.", OFFSET(parse_encryption), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM }, { NULL }, }; static const AVClass webm_dash_class = { .class_name = "WebM DASH Manifest demuxer", .item_name = av_default_item_name, - .option = options, + .option = dash_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static const AVClass matroska_class = { + .class_name = "Matroska / WebM demuxer", + .item_name = av_default_item_name, + .option = matroska_options, .version = LIBAVUTIL_VERSION_INT, }; @@ -4033,7 +4162,8 @@ AVInputFormat ff_matroska_demuxer = { .read_packet = matroska_read_packet, .read_close = matroska_read_close, .read_seek = matroska_read_seek, - .mime_type = "audio/webm,audio/x-matroska,video/webm,video/x-matroska" + .mime_type = "audio/webm,audio/x-matroska,video/webm,video/x-matroska", + .priv_class = &matroska_class, }; AVInputFormat ff_webm_dash_manifest_demuxer = { -- 2.18.0.203.gfac676dfb9-goog