pbutils: Add function to parse RFC 6381 codecs field
authorEdward Hervey <edward@centricular.com>
Tue, 8 Mar 2022 08:46:33 +0000 (09:46 +0100)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Fri, 11 Mar 2022 07:14:33 +0000 (07:14 +0000)
This is the opposite of `gst_codec_utils_caps_get_mime_codec()`, which allows
elements to get the `GstCaps`

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1894>

subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c
subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.h
subprojects/gst-plugins-base/tests/check/libs/pbutils.c

index 78bdb7a..797fc88 100644 (file)
@@ -2556,3 +2556,243 @@ gst_codec_utils_caps_get_mime_codec (GstCaps * caps)
 done:
   return mime_codec;
 }
+
+static GstCaps *
+gst_codec_utils_caps_from_mime_codec_single (const gchar * codec)
+{
+  GstCaps *caps = NULL;
+  gchar **subcodec = NULL;
+  gchar *subcodec0;
+  guint32 codec_fourcc;
+
+  GST_DEBUG ("Analyzing codec '%s'", codec);
+
+  /* rfc 6381 3.3
+   *
+   * For the ISO Base Media File Format, and the QuickTime movie file
+   * format, the first element of a 'codecs' parameter value is a sample
+   * description entry four-character code as registered by the MP4
+   * Registration Authority [MP4RA].
+   *
+   * See Also : http://mp4ra.org/#/codecs
+   */
+  if (strlen (codec) < 4) {
+    GST_WARNING ("Invalid codec (smaller than 4 characters) : '%s'", codec);
+    goto beach;
+  }
+
+  subcodec = g_strsplit (codec, ".", 0);
+  subcodec0 = subcodec[0];
+
+  if (subcodec0 == NULL)
+    goto beach;
+
+  /* Skip any leading spaces */
+  while (*subcodec0 == ' ')
+    subcodec0++;
+
+  if (strlen (subcodec0) < 4) {
+    GST_WARNING ("Invalid codec (smaller than 4 characters) : '%s'", subcodec0);
+    goto beach;
+  }
+
+  GST_LOG ("subcodec[0] '%s'", subcodec0);
+
+  codec_fourcc = GST_READ_UINT32_LE (subcodec0);
+  switch (codec_fourcc) {
+    case GST_MAKE_FOURCC ('a', 'v', 'c', '1'):
+    case GST_MAKE_FOURCC ('a', 'v', 'c', '2'):
+    case GST_MAKE_FOURCC ('a', 'v', 'c', '3'):
+    case GST_MAKE_FOURCC ('a', 'v', 'c', '4'):
+    {
+      guint8 sps[3];
+      guint64 spsint64;
+
+      /* ISO 14496-15 Annex E : Sub-parameters for the MIME type “codecs”
+       * parameter */
+      caps = gst_caps_new_empty_simple ("video/x-h264");
+
+      if (subcodec[1]) {
+        /* The second element is the hexadecimal representation of the following
+         * three bytes in the (subset) sequence parameter set Network
+         * Abstraction Layer (NAL) unit specified in [AVC]:
+         * * profile_idc
+         * * constraint_set flags
+         * * level_idc
+         * */
+        spsint64 = g_ascii_strtoull (subcodec[1], NULL, 16);
+        sps[0] = spsint64 >> 16;
+        sps[1] = (spsint64 >> 8) & 0xff;
+        sps[2] = spsint64 & 0xff;
+        gst_codec_utils_h264_caps_set_level_and_profile (caps,
+            (const guint8 *) &sps, 3);
+      }
+    }
+      break;
+    case GST_MAKE_FOURCC ('m', 'p', '4', 'a'):
+    {
+      guint64 oti;
+
+      if (!subcodec[1])
+        break;
+      oti = g_ascii_strtoull (subcodec[1], NULL, 16);
+      /* For mp4a, mp4v and mp4s, the second element is the hexadecimal
+       * representation of the MP4 Registration Authority
+       * ObjectTypeIndication */
+      switch (oti) {
+        case 0x40:
+        {
+          guint64 audio_oti;
+          const gchar *profile = NULL;
+
+          /* MPEG-4 Audio (ISO/IEC 14496-3 */
+          caps =
+              gst_caps_new_simple ("audio/mpeg", "mpegversion", G_TYPE_INT, 4,
+              NULL);
+
+          if (!subcodec[2])
+            break;
+          /* If present, last element is the audio object type */
+          audio_oti = g_ascii_strtoull (subcodec[2], NULL, 16);
+
+          switch (audio_oti) {
+            case 1:
+              profile = "main";
+              break;
+            case 2:
+              profile = "lc";
+              break;
+            case 3:
+              profile = "ssr";
+              break;
+            case 4:
+              profile = "ltp";
+              break;
+            default:
+              GST_WARNING ("Unhandled MPEG-4 Audio Object Type: 0x%"
+                  G_GUINT64_FORMAT "x", audio_oti);
+              break;
+          }
+          if (profile)
+            gst_caps_set_simple (caps, "profile", G_TYPE_STRING, profile, NULL);
+          break;
+        }
+        default:
+          GST_WARNING ("Unknown ObjectTypeIndication 0x%" G_GUINT64_FORMAT "x",
+              oti);
+          break;
+      }
+    }
+      break;
+    case GST_MAKE_FOURCC ('h', 'e', 'v', '1'):
+    case GST_MAKE_FOURCC ('h', 'v', 'c', '1'):
+    {
+      /* ISO 14496-15 Annex E : Sub-parameters for the MIME type “codecs”
+       * parameter */
+      caps = gst_caps_new_empty_simple ("video/x-h265");
+
+      /* FIXME : Extract information from the following component */
+      break;
+    }
+      /* Following are not defined in rfc 6831 but are registered MP4RA codecs */
+    case GST_MAKE_FOURCC ('a', 'c', '-', '3'):
+      /* ETSI TS 102 366 v1.4.1 - Digital Audio Compression (AC-3, Enhanced AC-3) Standard, Annex F */
+      caps = gst_caps_new_empty_simple ("audio/x-ac3");
+      break;
+    case GST_MAKE_FOURCC ('e', 'c', '+', '3'):
+      GST_FIXME
+          ("Signalling of ATMOS ('ec+3') isn't defined yet. Falling back to EAC3 caps");
+      /* withdrawn, unused, do not use (was enhanced AC-3 audio with JOC) */
+    case GST_MAKE_FOURCC ('e', 'c', '-', '3'):
+      /* ETSI TS 102 366 v1.4.1 - Digital Audio Compression (AC-3, Enhanced AC-3) Standard, Annex F */
+      caps = gst_caps_new_empty_simple ("audio/x-eac3");
+      break;
+    case GST_MAKE_FOURCC ('s', 't', 'p', 'p'):
+      /* IMSC1-conformant TTM XML */
+      caps = gst_caps_new_empty_simple ("application/ttml+xml");
+      break;
+    case GST_MAKE_FOURCC ('w', 'v', 't', 't'):
+      /* WebVTT subtitles */
+      caps = gst_caps_new_empty_simple ("application/x-subtitle-vtt");
+      break;
+    case GST_MAKE_FOURCC ('v', 'p', '0', '8'):
+      /* VP8 */
+      caps = gst_caps_new_empty_simple ("video/x-vp8");
+      break;
+    case GST_MAKE_FOURCC ('v', 'p', '0', '9'):
+      /* VP9 */
+      caps = gst_caps_new_empty_simple ("video/x-vp9");
+      break;
+    case GST_MAKE_FOURCC ('a', 'v', '0', '1'):
+      /* AV1 */
+      caps = gst_caps_new_empty_simple ("video/x-av1");
+      break;
+    case GST_MAKE_FOURCC ('o', 'p', 'u', 's'):
+      /* Opus */
+      caps = gst_caps_new_empty_simple ("audio/x-opus");
+      break;
+    case GST_MAKE_FOURCC ('u', 'l', 'a', 'w'):
+      /* ulaw */
+      caps = gst_caps_new_empty_simple ("audio/x-mulaw");
+      break;
+    case GST_MAKE_FOURCC ('g', '7', '2', '6'):
+      /* ulaw */
+      caps =
+          gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, "g726",
+          NULL);
+      break;
+    default:
+      GST_WARNING ("Unknown codec '%s' please file a bug", codec);
+      break;
+  }
+
+beach:
+  if (subcodec != NULL)
+    g_strfreev (subcodec);
+  return caps;
+}
+
+/**
+ * gst_codec_utils_caps_from_mime_codec:
+ * @codecs_field: A mime codec string field
+ *
+ * Converts a RFC 6381 compatible codec string to #GstCaps. More than one codec
+ * string can be present (separated by `,`).
+ *
+ * Registered codecs can be found at http://mp4ra.org/#/codecs
+ *
+ * Returns: (transfer full) (nullable): The corresponding #GstCaps or %NULL
+ *
+ * Since: 1.22
+ */
+GstCaps *
+gst_codec_utils_caps_from_mime_codec (const gchar * codecs_field)
+{
+  gchar **codecs = NULL;
+  GstCaps *caps = NULL;
+  guint i;
+
+  g_return_val_if_fail (codecs_field != NULL, NULL);
+
+  GST_LOG ("codecs_field '%s'", codecs_field);
+
+  codecs = g_strsplit (codecs_field, ",", 0);
+  if (codecs == NULL) {
+    GST_WARNING ("Invalid 'codecs' field : '%s'", codecs_field);
+    goto beach;
+  }
+
+  for (i = 0; codecs[i]; i++) {
+    const gchar *codec = codecs[i];
+    if (caps == NULL)
+      caps = gst_codec_utils_caps_from_mime_codec_single (codec);
+    else
+      gst_caps_append (caps,
+          gst_codec_utils_caps_from_mime_codec_single (codec));
+  }
+
+beach:
+  g_strfreev (codecs);
+  GST_LOG ("caps %" GST_PTR_FORMAT, caps);
+  return caps;
+}
index 1aa7246..43398c4 100644 (file)
@@ -156,6 +156,8 @@ gboolean  gst_codec_utils_opus_parse_header (GstBuffer * header,
 GST_PBUTILS_API
 gchar * gst_codec_utils_caps_get_mime_codec (GstCaps * caps);
 
+GST_PBUTILS_API
+GstCaps * gst_codec_utils_caps_from_mime_codec (const gchar *codecs_field);
 
 G_END_DECLS
 
index a922299..801ab96 100644 (file)
@@ -1359,9 +1359,10 @@ static const guint8 h265_sample_codec_data[] = {
   0x44, 0x01, 0xc0, 0x2c, 0xbc, 0x14, 0xc9
 };
 
-GST_START_TEST (test_pb_utils_caps_get_mime_codec)
+GST_START_TEST (test_pb_utils_caps_mime_codec)
 {
   GstCaps *caps = NULL;
+  GstCaps *caps2 = NULL;
   gchar *mime_codec = NULL;
   GstBuffer *buffer = NULL;
   guint8 *codec_data = NULL;
@@ -1371,6 +1372,9 @@ GST_START_TEST (test_pb_utils_caps_get_mime_codec)
   caps = gst_caps_new_empty_simple ("video/x-h264");
   mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
   fail_unless_equals_string (mime_codec, "avc1");
+  caps2 = gst_codec_utils_caps_from_mime_codec (mime_codec);
+  fail_unless (gst_caps_is_equal_fixed (caps, caps2));
+  gst_caps_unref (caps2);
   g_free (mime_codec);
   gst_caps_unref (caps);
 
@@ -1412,6 +1416,9 @@ GST_START_TEST (test_pb_utils_caps_get_mime_codec)
   caps = gst_caps_new_empty_simple ("video/x-av1");
   mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
   fail_unless_equals_string (mime_codec, "av01");
+  caps2 = gst_codec_utils_caps_from_mime_codec (mime_codec);
+  fail_unless (gst_caps_is_equal_fixed (caps, caps2));
+  gst_caps_unref (caps2);
   g_free (mime_codec);
   gst_caps_unref (caps);
 
@@ -1419,6 +1426,9 @@ GST_START_TEST (test_pb_utils_caps_get_mime_codec)
   caps = gst_caps_new_empty_simple ("video/x-vp8");
   mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
   fail_unless_equals_string (mime_codec, "vp08");
+  caps2 = gst_codec_utils_caps_from_mime_codec (mime_codec);
+  fail_unless (gst_caps_is_equal_fixed (caps, caps2));
+  gst_caps_unref (caps2);
   g_free (mime_codec);
   gst_caps_unref (caps);
 
@@ -1426,6 +1436,9 @@ GST_START_TEST (test_pb_utils_caps_get_mime_codec)
   caps = gst_caps_new_empty_simple ("video/x-vp9");
   mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
   fail_unless_equals_string (mime_codec, "vp09");
+  caps2 = gst_codec_utils_caps_from_mime_codec (mime_codec);
+  fail_unless (gst_caps_is_equal_fixed (caps, caps2));
+  gst_caps_unref (caps2);
   g_free (mime_codec);
   gst_caps_unref (caps);
 
@@ -1462,6 +1475,9 @@ GST_START_TEST (test_pb_utils_caps_get_mime_codec)
   caps = gst_caps_new_empty_simple ("audio/x-opus");
   mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
   fail_unless_equals_string (mime_codec, "opus");
+  caps2 = gst_codec_utils_caps_from_mime_codec (mime_codec);
+  fail_unless (gst_caps_is_equal_fixed (caps, caps2));
+  gst_caps_unref (caps2);
   g_free (mime_codec);
   gst_caps_unref (caps);
 
@@ -1469,6 +1485,9 @@ GST_START_TEST (test_pb_utils_caps_get_mime_codec)
   caps = gst_caps_new_empty_simple ("audio/x-mulaw");
   mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
   fail_unless_equals_string (mime_codec, "ulaw");
+  caps2 = gst_codec_utils_caps_from_mime_codec (mime_codec);
+  fail_unless (gst_caps_is_equal_fixed (caps, caps2));
+  gst_caps_unref (caps2);
   g_free (mime_codec);
   gst_caps_unref (caps);
 
@@ -1478,6 +1497,9 @@ GST_START_TEST (test_pb_utils_caps_get_mime_codec)
       NULL);
   mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
   fail_unless_equals_string (mime_codec, "g726");
+  caps2 = gst_codec_utils_caps_from_mime_codec (mime_codec);
+  fail_unless (gst_caps_is_equal_fixed (caps, caps2));
+  gst_caps_unref (caps2);
   g_free (mime_codec);
   gst_caps_unref (caps);
 }
@@ -1504,7 +1526,7 @@ libgstpbutils_suite (void)
   tcase_add_test (tc_chain, test_pb_utils_h264_profiles);
   tcase_add_test (tc_chain, test_pb_utils_h264_get_profile_flags_level);
   tcase_add_test (tc_chain, test_pb_utils_h265_profiles);
-  tcase_add_test (tc_chain, test_pb_utils_caps_get_mime_codec);
+  tcase_add_test (tc_chain, test_pb_utils_caps_mime_codec);
   return s;
 }