dash: Store entire ContentProtection node in protection event data
authorPhilippe Normand <philn@igalia.com>
Mon, 12 Jul 2021 11:08:20 +0000 (12:08 +0100)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Thu, 15 Jul 2021 13:05:54 +0000 (13:05 +0000)
Some manifests use the ContentProtection node to store additional information
such as the license server url. Our MPD parser used to process the
ContentProtection node, extracting Playready PSSH boxes. However for other DRM
systems, only the `value` attribute was passed down to the protection event, so
for example, Widevine data was not parsed at all and "Widevine" was passed to
the event, which is not very useful for decryptors that require a PSSH init
data.

Parsing should now be done by decryptors which will receive the entire
ContentProtection XML node as a string. This gives more "freedom" to the
decryptor which can then detect and parse custom nodes as well.

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

ext/dash/gstmpdparser.c
tests/check/elements/dash_mpd.c

index 8cd2858..f88bb6c 100644 (file)
@@ -503,35 +503,14 @@ error:
 static void
 gst_mpdparser_parse_content_protection_node (GList ** list, xmlNode * a_node)
 {
-  gchar *value = NULL;
-  if (gst_xml_helper_get_prop_string (a_node, "value", &value)) {
-    if (!g_strcmp0 (value, "MSPR 2.0")) {
-      xmlNode *cur_node;
-      for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
-        if (cur_node->type == XML_ELEMENT_NODE) {
-          if (xmlStrcmp (cur_node->name, (xmlChar *) "pro") == 0) {
-            GstMPDDescriptorTypeNode *new_descriptor;
-            new_descriptor = gst_mpd_descriptor_type_node_new ((const gchar *)
-                cur_node->name);
-            *list = g_list_append (*list, new_descriptor);
-
-            gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri",
-                &new_descriptor->schemeIdUri);
-
-            gst_xml_helper_get_node_content (cur_node, &new_descriptor->value);
-            goto beach;
-          }
-        }
-      }
-    } else {
-      gst_mpdparser_parse_descriptor_type (list, a_node);
-    }
-  } else {
-    gst_mpdparser_parse_descriptor_type (list, a_node);
-  }
-beach:
-  if (value)
-    g_free (value);
+  GstMPDDescriptorTypeNode *new_descriptor;
+  new_descriptor = gst_mpd_descriptor_type_node_new ((const gchar *)
+      a_node->name);
+  *list = g_list_append (*list, new_descriptor);
+
+  gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri",
+      &new_descriptor->schemeIdUri);
+  gst_xml_helper_get_node_as_string (a_node, &new_descriptor->value);
 }
 
 static void
index cc5596a..51448e6 100644 (file)
@@ -1392,6 +1392,7 @@ GST_START_TEST
 
   gboolean ret;
   GstMPDClient *mpdclient = gst_mpd_client_new ();
+  gchar *str;
 
   ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml));
   assert_equals_int (ret, TRUE);
@@ -1402,7 +1403,16 @@ GST_START_TEST
   contentProtection =
       (GstMPDDescriptorTypeNode *) representationBase->ContentProtection->data;
   assert_equals_string (contentProtection->schemeIdUri, "TestSchemeIdUri");
-  assert_equals_string (contentProtection->value, "TestValue");
+
+  /* We can't do a simple compare of value (which should be an XML dump
+     of the ContentProtection element), because the whitespace
+     formatting from xmlDump might differ between versions of libxml */
+  str = strstr (contentProtection->value, "<ContentProtection");
+  fail_if (str == NULL);
+  str = strstr (contentProtection->value, "value=\"TestValue\"");
+  fail_if (str == NULL);
+  str = strstr (contentProtection->value, "</ContentProtection>");
+  fail_if (str == NULL);
 
   gst_mpd_client_free (mpdclient);
 }
@@ -1410,6 +1420,60 @@ GST_START_TEST
 GST_END_TEST;
 
 /*
+ * Test parsing Period AdaptationSet RepresentationBase ContentProtection
+ * with custom ContentProtection content.
+ */
+GST_START_TEST
+    (dash_mpdparser_period_adaptationSet_representationBase_contentProtection_with_content)
+{
+  GstMPDPeriodNode *periodNode;
+  GstMPDAdaptationSetNode *adaptationSet;
+  GstMPDRepresentationBaseNode *representationBase;
+  GstMPDDescriptorTypeNode *contentProtection;
+  const gchar *xml =
+      "<?xml version=\"1.0\"?>"
+      "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
+      "     profiles=\"urn:mpeg:dash:profile:isoff-main:2011\">"
+      "     customns=\"foo\""
+      "  <Period>"
+      "    <AdaptationSet>"
+      "      <ContentProtection schemeIdUri=\"TestSchemeIdUri\">"
+      "        <customns:bar>Hello world</customns:bar>"
+      "      </ContentProtection></AdaptationSet></Period></MPD>";
+
+  gboolean ret;
+  GstMPDClient *mpdclient = gst_mpd_client_new ();
+  gchar *str;
+
+  ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml));
+  assert_equals_int (ret, TRUE);
+
+  periodNode = (GstMPDPeriodNode *) mpdclient->mpd_root_node->Periods->data;
+  adaptationSet = (GstMPDAdaptationSetNode *) periodNode->AdaptationSets->data;
+  representationBase = GST_MPD_REPRESENTATION_BASE_NODE (adaptationSet);
+  contentProtection =
+      (GstMPDDescriptorTypeNode *) representationBase->ContentProtection->data;
+  assert_equals_string (contentProtection->schemeIdUri, "TestSchemeIdUri");
+
+  /* We can't do a simple compare of value (which should be an XML dump
+     of the ContentProtection element), because the whitespace
+     formatting from xmlDump might differ between versions of libxml */
+  str = strstr (contentProtection->value, "<ContentProtection");
+  fail_if (str == NULL);
+  str =
+      strstr (contentProtection->value,
+      "<customns:bar>Hello world</customns:bar>");
+  fail_if (str == NULL);
+  str = strstr (contentProtection->value, "</ContentProtection>");
+  fail_if (str == NULL);
+
+  gst_mpd_client_free (mpdclient);
+}
+
+GST_END_TEST;
+
+
+/*
  * Test parsing ContentProtection element that has no value attribute
  */
 GST_START_TEST (dash_mpdparser_contentProtection_no_value)
@@ -6389,6 +6453,8 @@ dash_suite (void)
   tcase_add_test (tc_simpleMPD,
       dash_mpdparser_contentProtection_no_value_no_encoding);
   tcase_add_test (tc_simpleMPD,
+      dash_mpdparser_period_adaptationSet_representationBase_contentProtection_with_content);
+  tcase_add_test (tc_simpleMPD,
       dash_mpdparser_period_adaptationSet_accessibility);
   tcase_add_test (tc_simpleMPD, dash_mpdparser_period_adaptationSet_role);
   tcase_add_test (tc_simpleMPD, dash_mpdparser_period_adaptationSet_rating);