summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudvig Rappe <ludvigr@axis.com>2021-08-25 17:03:49 +0200
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>2021-08-30 08:49:33 +0000
commit75c44583eeb0013b82d3bb8d4705e7468abba489 (patch)
tree0cf1d063d64669b794fd78a7b4484e45677b1a0b
parent4a1d8eac31408a5e6e01183a6217899538ac0628 (diff)
downloadgstreamer-plugins-base-75c44583eeb0013b82d3bb8d4705e7468abba489.tar.gz
pbutils: Add function to convert caps to MIME codec
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1265>
-rw-r--r--gst-libs/gst/pbutils/codec-utils.c175
-rw-r--r--gst-libs/gst/pbutils/codec-utils.h5
-rw-r--r--tests/check/libs/pbutils.c119
3 files changed, 299 insertions, 0 deletions
diff --git a/gst-libs/gst/pbutils/codec-utils.c b/gst-libs/gst/pbutils/codec-utils.c
index 7f585d1d1..5d0f5655c 100644
--- a/gst-libs/gst/pbutils/codec-utils.c
+++ b/gst-libs/gst/pbutils/codec-utils.c
@@ -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;
+}
diff --git a/gst-libs/gst/pbutils/codec-utils.h b/gst-libs/gst/pbutils/codec-utils.h
index 36bf0d619..11ef18eb4 100644
--- a/gst-libs/gst/pbutils/codec-utils.h
+++ b/gst-libs/gst/pbutils/codec-utils.h
@@ -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__ */
diff --git a/tests/check/libs/pbutils.c b/tests/check/libs/pbutils.c
index 846a26329..e43c5b679 100644
--- a/tests/check/libs/pbutils.c
+++ b/tests/check/libs/pbutils.c
@@ -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;
}