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 <modma...@google.com> 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 <modma...@google.com> --- 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
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel