pbutils: Add function to convert caps to MIME codec

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1265>
This commit is contained in:
Ludvig Rappe 2021-08-25 17:03:49 +02:00 committed by GStreamer Marge Bot
parent 4a1d8eac31
commit 75c44583ee
3 changed files with 299 additions and 0 deletions

View 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;
}

View 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__ */

View 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;
}