diff mbox

[FFmpeg-devel] avformat/matroska: Parse generic encryption info from packets.

Message ID CAO7y9i92iexwKyvJafzkiKk--U45Uo9cJpf+86DXJt9wHum+gw@mail.gmail.com
State New
Headers show

Commit Message

Jacob Trimble July 12, 2018, 11:45 p.m. UTC
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?

Comments

Jacob Trimble July 23, 2018, 8:59 p.m. UTC | #1
On Thu, Jul 12, 2018 at 4:45 PM Jacob Trimble <modmaker@google.com> wrote:
>
> 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?

Ping.
Jacob Trimble Aug. 1, 2018, 8:45 p.m. UTC | #2
On Mon, Jul 23, 2018 at 1:59 PM Jacob Trimble <modmaker@google.com> wrote:
>
> On Thu, Jul 12, 2018 at 4:45 PM Jacob Trimble <modmaker@google.com> wrote:
> >
> > 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?
>
> Ping.

Ping.
Jacob Trimble Aug. 9, 2018, 4:12 p.m. UTC | #3
On Wed, Aug 1, 2018 at 1:45 PM Jacob Trimble <modmaker@google.com> wrote:
>
> On Mon, Jul 23, 2018 at 1:59 PM Jacob Trimble <modmaker@google.com> wrote:
> >
> > On Thu, Jul 12, 2018 at 4:45 PM Jacob Trimble <modmaker@google.com> wrote:
> > >
> > > 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?
> >
> > Ping.
>
> Ping.

Ping (136 lindes changed, in "review" for 28 days...)
diff mbox

Patch

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(-)

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