pbutils: Add function to convert caps to MIME codec
authorLudvig Rappe <ludvigr@axis.com>
Wed, 25 Aug 2021 15:03:49 +0000 (17:03 +0200)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Mon, 30 Aug 2021 08:49:33 +0000 (08:49 +0000)
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1265>

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

index 7f585d1..5d0f565 100644 (file)
@@ -2257,3 +2257,178 @@ done:
 
   return ret;
 }
+
+static gboolean
+h264_caps_structure_get_profile_flags_level (GstStructure * caps_st,
+    guint8 * profile, guint8 * flags, guint8 * level)
+{
+  const GValue *codec_data_value = NULL;
+  GstBuffer *codec_data = NULL;
+  GstMapInfo map;
+  gboolean ret = FALSE;
+
+  codec_data_value = gst_structure_get_value (caps_st, "codec_data");
+  if (!codec_data_value) {
+    GST_DEBUG
+        ("video/x-h264 pad did not have codec_data set, cannot parse profile, flags and level");
+    return FALSE;
+  } else {
+    guint8 *data = NULL;
+    gsize size;
+
+    codec_data = gst_value_get_buffer (codec_data_value);
+    if (!gst_buffer_map (codec_data, &map, GST_MAP_READ)) {
+      return FALSE;
+    }
+    data = map.data;
+    size = map.size;
+
+    if (!gst_codec_utils_h264_get_profile_flags_level (data, (guint) size,
+            profile, flags, level)) {
+      GST_WARNING
+          ("Failed to parse profile, flags and level from h264 codec data");
+      goto done;
+    }
+  }
+
+  ret = TRUE;
+
+done:
+  gst_buffer_unmap (codec_data, &map);
+
+  return ret;
+}
+
+static gboolean
+aac_caps_structure_get_audio_object_type (GstStructure * caps_st,
+    guint8 * audio_object_type)
+{
+  gboolean ret = FALSE;
+  const GValue *codec_data_value = NULL;
+  GstBuffer *codec_data = NULL;
+  GstMapInfo map;
+  guint8 *data = NULL;
+  gsize size;
+  GstBitReader br;
+
+  codec_data_value = gst_structure_get_value (caps_st, "codec_data");
+  if (!codec_data_value) {
+    GST_DEBUG
+        ("audio/mpeg pad did not have codec_data set, cannot parse audio object type");
+    return FALSE;
+  }
+
+  codec_data = gst_value_get_buffer (codec_data_value);
+  if (!gst_buffer_map (codec_data, &map, GST_MAP_READ)) {
+    return FALSE;
+  }
+  data = map.data;
+  size = map.size;
+
+  if (size < 2) {
+    GST_WARNING ("aac codec data is too small");
+    goto done;
+  }
+
+  gst_bit_reader_init (&br, data, size);
+  ret = gst_codec_utils_aac_get_audio_object_type (&br, audio_object_type);
+
+done:
+  gst_buffer_unmap (codec_data, &map);
+
+  return ret;
+}
+
+/**
+ * gst_codec_utils_caps_get_mime_codec:
+ * @caps: A #GstCaps to convert to mime codec
+ *
+ * Converts @caps to a RFC 6381 compatible codec string if possible.
+ *
+ * Useful for providing the 'codecs' field inside the 'Content-Type' HTTP
+ * header for containerized formats, such as mp4 or matroska.
+ *
+ * Returns: (transfer full): a RFC 6381 compatible codec string or %NULL
+ *
+ * Since: 1.20
+ */
+gchar *
+gst_codec_utils_caps_get_mime_codec (GstCaps * caps)
+{
+  gchar *mime_codec = NULL;
+  GstStructure *caps_st = NULL;
+  const gchar *media_type = NULL;
+
+  g_return_val_if_fail (caps != NULL, NULL);
+  g_return_val_if_fail (gst_caps_is_fixed (caps), NULL);
+
+  caps_st = gst_caps_get_structure (caps, 0);
+  if (caps_st == NULL) {
+    GST_WARNING ("Failed to get structure from caps");
+    goto done;
+  }
+
+  media_type = gst_structure_get_name (caps_st);
+
+  if (g_strcmp0 (media_type, "video/x-h264") == 0) {
+    /* avc1.AABBCC
+     *   AA = profile
+     *   BB = constraint set flags
+     *   CC = level
+     */
+    guint8 profile = 0;
+    guint8 flags = 0;
+    guint8 level = 0;
+
+    if (!h264_caps_structure_get_profile_flags_level (caps_st, &profile, &flags,
+            &level)) {
+      GST_DEBUG
+          ("h264 caps did not contain 'codec_data', cannot determine detailed codecs info");
+      mime_codec = g_strdup ("avc1");
+    } else {
+      mime_codec = g_strdup_printf ("avc1.%02X%02X%02X", profile, flags, level);
+    }
+  } else if (g_strcmp0 (media_type, "video/x-h265") == 0) {
+    /* TODO: this simple "hev1" is not complete and should contain more info
+     * similar to how avc1 does.
+     * However, as of writing there are no browsers that support h265 and the
+     * format of how to describe h265 codec info is badly documented.
+     * Examples exist online, but no public documentation seem to exist,
+     * however the paywalled ISO/IEC 14496-15 has it. */
+    mime_codec = g_strdup ("hev1");
+  } else if (g_strcmp0 (media_type, "video/x-av1") == 0) {
+    /* TODO: Some browsers won't play the video unless more codec information is
+     * available in the mime codec for av1. This is documented in
+     * https://aomediacodec.github.io/av1-isobmff/#codecsparam */
+    mime_codec = g_strdup ("av01");
+  } else if (g_strcmp0 (media_type, "video/x-vp8") == 0) {
+    /* TODO: most browsers won't play the video unless more codec information is
+     * available in the mime codec for vp8. */
+    mime_codec = g_strdup ("vp08");
+  } else if (g_strcmp0 (media_type, "video/x-vp9") == 0) {
+    /* TODO: most browsers won't play the video unless more codec information is
+     * available in the mime codec for vp9. This is documented in
+     * https://www.webmproject.org/vp9/mp4/ */
+    mime_codec = g_strdup ("vp09");
+  } else if (g_strcmp0 (media_type, "audio/mpeg") == 0) {
+    guint8 audio_object_type = 0;
+    if (aac_caps_structure_get_audio_object_type (caps_st, &audio_object_type)) {
+      mime_codec = g_strdup_printf ("mp4a.40.%u", audio_object_type);
+    } else {
+      mime_codec = g_strdup ("mp4a.40");
+    }
+  } else if (g_strcmp0 (media_type, "audio/x-opus") == 0) {
+    mime_codec = g_strdup ("opus");
+  } else if (g_strcmp0 (media_type, "audio/x-mulaw") == 0) {
+    mime_codec = g_strdup ("ulaw");
+  } else if (g_strcmp0 (media_type, "audio/x-adpcm") == 0) {
+    if (g_strcmp0 (gst_structure_get_string (caps_st, "layout"), "g726") == 0) {
+      mime_codec = g_strdup ("g726");
+    }
+  } else if (g_strcmp0 (media_type, "audio/x-raw") == 0) {
+    mime_codec = g_strdup ("raw");
+  }
+
+done:
+  return mime_codec;
+}
index 36bf0d6..11ef18e 100644 (file)
@@ -152,6 +152,11 @@ gboolean  gst_codec_utils_opus_parse_header (GstBuffer * header,
                                              guint16   * pre_skip,
                                              gint16    * output_gain);
 
+/* General */
+GST_PBUTILS_API
+gchar * gst_codec_utils_caps_get_mime_codec (GstCaps * caps);
+
+
 G_END_DECLS
 
 #endif /* __GST_PB_UTILS_CODEC_UTILS_H__ */
index 846a263..e43c5b6 100644 (file)
@@ -1343,6 +1343,124 @@ GST_START_TEST (test_pb_utils_h265_profiles)
 
 GST_END_TEST;
 
+GST_START_TEST (test_pb_utils_caps_get_mime_codec)
+{
+  GstCaps *caps = NULL;
+  gchar *mime_codec = NULL;
+  GstBuffer *buffer = NULL;
+  guint8 *codec_data = NULL;
+  gsize codec_data_len;
+
+  /* h264 without codec data */
+  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");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* h264 with codec data */
+  codec_data_len = sizeof (guint8) * 7;
+  codec_data = g_malloc0 (codec_data_len);
+  codec_data[0] = 0x01;
+  codec_data[1] = 0x64;
+  codec_data[2] = 0x00;
+  codec_data[3] = 0x32;
+  /* seven bytes is the minumum for a valid h264 codec_data, but in
+   * gst_codec_utils_h264_get_profile_flags_level we only parse the first four
+   * bytes */
+  buffer = gst_buffer_new_wrapped (codec_data, codec_data_len);
+  caps =
+      gst_caps_new_simple ("video/x-h264", "codec_data", GST_TYPE_BUFFER,
+      buffer, NULL);
+  mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
+  fail_unless_equals_string (mime_codec, "avc1.640032");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+  gst_buffer_unref (buffer);
+
+  /* h265 */
+  caps = gst_caps_new_empty_simple ("video/x-h265");
+  mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
+  fail_unless_equals_string (mime_codec, "hev1");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* av1 */
+  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");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* vp8 */
+  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");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* vp9 */
+  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");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* aac without codec data */
+  caps = gst_caps_new_empty_simple ("audio/mpeg");
+  mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
+  fail_unless_equals_string (mime_codec, "mp4a.40");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* aac with codec data */
+  codec_data_len = sizeof (guint8) * 2;
+  codec_data = g_malloc0 (codec_data_len);
+  codec_data[0] = 0x11;
+  codec_data[1] = 0x88;
+  buffer = gst_buffer_new_wrapped (codec_data, codec_data_len);
+  caps =
+      gst_caps_new_simple ("audio/mpeg", "codec_data", GST_TYPE_BUFFER, buffer,
+      NULL);
+  mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
+  fail_unless_equals_string (mime_codec, "mp4a.40.2");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+  gst_buffer_unref (buffer);
+
+  /* opus */
+  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");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* mulaw */
+  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");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* g726 */
+  caps =
+      gst_caps_new_simple ("audio/x-adpcm", "layout", G_TYPE_STRING, "g726",
+      NULL);
+  mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
+  fail_unless_equals_string (mime_codec, "g726");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+
+  /* raw */
+  caps = gst_caps_new_empty_simple ("audio/x-raw");
+  mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
+  fail_unless_equals_string (mime_codec, "raw");
+  g_free (mime_codec);
+  gst_caps_unref (caps);
+}
+
+GST_END_TEST;
+
 static Suite *
 libgstpbutils_suite (void)
 {
@@ -1361,6 +1479,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);
   return s;
 }