qtdemux: Add support for AAX encrypted audio streams
authorStefan Brüns <stefan.bruens@rwth-aachen.de>
Sat, 2 May 2020 00:21:00 +0000 (02:21 +0200)
committerStefanBruens <stefan.bruens@rwth-aachen.de>
Wed, 16 Sep 2020 00:59:34 +0000 (00:59 +0000)
This is modelled after the DASH Common Encryption scheme, but is somewhat
simpler as more parts are fixed, i.e. just one encryption scheme.

The output caps are fixed to 'application/x-aavd'. All information
required for decryption are part of the 'adrm' atom, which is passed
on as a property. The property is attached to the buffer.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/577>

gst/isomp4/qtdemux.c

index 2929cb4..146f409 100644 (file)
@@ -111,6 +111,7 @@ GST_DEBUG_CATEGORY (qtdemux_debug);
 #define GST_CAT_DEFAULT qtdemux_debug
 
 typedef struct _QtDemuxCencSampleSetInfo QtDemuxCencSampleSetInfo;
+typedef struct _QtDemuxAavdEncryptionInfo QtDemuxAavdEncryptionInfo;
 
 /* Macros for converting to/from timescale */
 #define QTSTREAMTIME_TO_GSTTIME(stream, value) (gst_util_uint64_scale((value), GST_SECOND, (stream)->timescale))
@@ -234,6 +235,11 @@ struct _QtDemuxCencSampleSetInfo
   GPtrArray *crypto_info;
 };
 
+struct _QtDemuxAavdEncryptionInfo
+{
+  GstStructure *default_properties;
+};
+
 static const gchar *
 qt_demux_state_string (enum QtDemuxState state)
 {
@@ -2526,6 +2532,12 @@ gst_qtdemux_stream_clear (QtDemuxStream * stream)
       if (info->crypto_info)
         g_ptr_array_free (info->crypto_info, TRUE);
     }
+    if (stream->protection_scheme_type == FOURCC_aavd) {
+      QtDemuxAavdEncryptionInfo *info =
+          (QtDemuxAavdEncryptionInfo *) stream->protection_scheme_info;
+      if (info->default_properties)
+        gst_structure_free (info->default_properties);
+    }
     g_free (stream->protection_scheme_info);
     stream->protection_scheme_info = NULL;
   }
@@ -5694,6 +5706,16 @@ gst_qtdemux_push_buffer (GstQTDemux * qtdemux, QtDemuxStream * stream,
       GST_TIME_ARGS (GST_BUFFER_PTS (buf)),
       GST_TIME_ARGS (GST_BUFFER_DURATION (buf)), GST_PAD_NAME (stream->pad));
 
+  if (stream->protected && stream->protection_scheme_type == FOURCC_aavd) {
+    GstStructure *crypto_info;
+    QtDemuxAavdEncryptionInfo *info =
+        (QtDemuxAavdEncryptionInfo *) stream->protection_scheme_info;
+
+    crypto_info = gst_structure_copy (info->default_properties);
+    if (!crypto_info || !gst_buffer_add_protection_meta (buf, crypto_info))
+      GST_ERROR_OBJECT (qtdemux, "failed to attach aavd metadata to buffer");
+  }
+
   if (stream->protected && stream->protection_scheme_type == FOURCC_cenc) {
     GstStructure *crypto_info;
     QtDemuxCencSampleSetInfo *info =
@@ -8271,6 +8293,17 @@ gst_qtdemux_configure_protected_caps (GstQTDemux * qtdemux,
   g_return_val_if_fail (gst_caps_get_size (CUR_STREAM (stream)->caps) == 1,
       FALSE);
 
+  if (stream->protection_scheme_type == FOURCC_aavd) {
+    s = gst_caps_get_structure (CUR_STREAM (stream)->caps, 0);
+    if (!gst_structure_has_name (s, "application/x-aavd")) {
+      gst_structure_set (s,
+          "original-media-type", G_TYPE_STRING, gst_structure_get_name (s),
+          NULL);
+      gst_structure_set_name (s, "application/x-aavd");
+    }
+    return TRUE;
+  }
+
   if (stream->protection_scheme_type != FOURCC_cenc) {
     GST_ERROR_OBJECT (qtdemux,
         "unsupported protection scheme: %" GST_FOURCC_FORMAT,
@@ -10249,6 +10282,42 @@ qtdemux_inspect_transformation_matrix (GstQTDemux * qtdemux,
   }
 }
 
+static gboolean
+qtdemux_parse_protection_aavd (GstQTDemux * qtdemux,
+    QtDemuxStream * stream, GNode * container, guint32 * original_fmt)
+{
+  GNode *adrm;
+  guint32 adrm_size;
+  GstBuffer *adrm_buf = NULL;
+  QtDemuxAavdEncryptionInfo *info;
+
+  adrm = qtdemux_tree_get_child_by_type (container, FOURCC_adrm);
+  if (G_UNLIKELY (!adrm)) {
+    GST_ERROR_OBJECT (qtdemux, "aavd box does not contain mandatory adrm box");
+    return FALSE;
+  }
+  adrm_size = QT_UINT32 (adrm->data);
+  adrm_buf =
+      gst_buffer_new_wrapped (g_memdup (adrm->data, adrm_size), adrm_size);
+
+  stream->protection_scheme_type = FOURCC_aavd;
+
+  if (!stream->protection_scheme_info)
+    stream->protection_scheme_info = g_new0 (QtDemuxAavdEncryptionInfo, 1);
+
+  info = (QtDemuxAavdEncryptionInfo *) stream->protection_scheme_info;
+
+  if (info->default_properties)
+    gst_structure_free (info->default_properties);
+  info->default_properties = gst_structure_new ("application/x-aavd",
+      "encrypted", G_TYPE_BOOLEAN, TRUE,
+      "adrm", GST_TYPE_BUFFER, adrm_buf, NULL);
+  gst_buffer_unref (adrm_buf);
+
+  *original_fmt = FOURCC_mp4a;
+  return TRUE;
+}
+
 /* Parses the boxes defined in ISO/IEC 14496-12 that enable support for
  * protected streams (sinf, frma, schm and schi); if the protection scheme is
  * Common Encryption (cenc), the function will also parse the tenc box (defined
@@ -10697,6 +10766,19 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
     if ((fourcc == FOURCC_drms) || (fourcc == FOURCC_drmi))
       goto error_encrypted;
 
+    if (fourcc == FOURCC_aavd) {
+      if (stream->subtype != FOURCC_soun) {
+        GST_ERROR_OBJECT (qtdemux,
+            "Unexpeced stsd type 'aavd' outside 'soun' track");
+      } else {
+        /* encrypted audio with sound sample description v0 */
+        GNode *enc = qtdemux_tree_get_child_by_type (stsd, fourcc);
+        stream->protected = TRUE;
+        if (!qtdemux_parse_protection_aavd (qtdemux, stream, enc, &fourcc))
+          GST_ERROR_OBJECT (qtdemux, "Failed to parse protection scheme info");
+      }
+    }
+
     if (fourcc == FOURCC_encv || fourcc == FOURCC_enca) {
       /* FIXME this looks wrong, there might be multiple children
        * with the same type */
@@ -12245,16 +12327,22 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak)
               GST_TAG_BITRATE, bitrate, NULL);
       }
 
+      esds = NULL;
       mp4a = qtdemux_tree_get_child_by_index (stsd, stsd_index);
       if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != fourcc) {
-        if (stream->protected && QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca)
-          mp4a = NULL;
-        else if (!stream->protected)
+        if (stream->protected) {
+          if (QTDEMUX_TREE_NODE_FOURCC (mp4a) == FOURCC_aavd) {
+            esds = qtdemux_tree_get_child_by_type (mp4a, FOURCC_esds);
+          }
+          if (QTDEMUX_TREE_NODE_FOURCC (mp4a) != FOURCC_enca) {
+            mp4a = NULL;
+          }
+        } else {
           mp4a = NULL;
+        }
       }
 
       wave = NULL;
-      esds = NULL;
       if (mp4a) {
         wave = qtdemux_tree_get_child_by_type (mp4a, FOURCC_wave);
         if (wave)