mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-11 09:55:36 +00:00
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:
parent
4a1d8eac31
commit
75c44583ee
3 changed files with 299 additions and 0 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue