From 48cabb86f030bfcf1d163db2c361ab8ce743d8b3 Mon Sep 17 00:00:00 2001
From: Jacob Trimble <modmaker@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 <modmaker@google.com>
---
libavformat/matroskadec.c | 136 +++++++++++++++++++++++++++++++++++++-
1 file changed, 133 insertions(+), 3 deletions(-)
@@ -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