Merge branch 'av1-mime' into 'main'

pbutils: av1 mime computation

See merge request gstreamer/gstreamer!6696
This commit is contained in:
Rafael Caricio 2024-05-03 22:17:47 +00:00
commit 47f7359b36
4 changed files with 499 additions and 5 deletions

View file

@ -3147,6 +3147,33 @@ rate.</doc>
</parameter>
</parameters>
</function>
<function name="codec_utils_av1_get_level" c:identifier="gst_codec_utils_av1_get_level" version="1.26">
<doc xml:space="preserve" filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c">Transform a seq_level_idx into the level string</doc>
<source-position filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.h"/>
<return-value transfer-ownership="none" nullable="1">
<doc xml:space="preserve" filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c">the level string or %NULL if the seq_level_idx is unknown</doc>
<type name="utf8" c:type="const gchar*"/>
</return-value>
<parameters>
<parameter name="level_idx" transfer-ownership="none">
<type name="guint8" c:type="guint8"/>
</parameter>
</parameters>
</function>
<function name="codec_utils_av1_get_seq_level_idx" c:identifier="gst_codec_utils_av1_get_seq_level_idx" version="1.26">
<doc xml:space="preserve" filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c">Transform a level string from the caps into the seq_level_idx</doc>
<source-position filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.h"/>
<return-value transfer-ownership="none">
<doc xml:space="preserve" filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c">the seq_level_idx or 31 (max-level) if the level is unknown</doc>
<type name="guint8" c:type="guint8"/>
</return-value>
<parameters>
<parameter name="level" transfer-ownership="none">
<doc xml:space="preserve" filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c">A level string from caps</doc>
<type name="utf8" c:type="const gchar*"/>
</parameter>
</parameters>
</function>
<function name="codec_utils_caps_from_mime_codec" c:identifier="gst_codec_utils_caps_from_mime_codec" version="1.22">
<doc xml:space="preserve" filename="../subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c">Converts a RFC 6381 compatible codec string to #GstCaps. More than one codec
string can be present (separated by `,`).

View file

@ -41,6 +41,7 @@
#include <gst/base/gstbitreader.h>
#include <gst/tag/tag.h>
#include <stdio.h>
#include <string.h>
#ifndef GST_DISABLE_GST_DEBUG
@ -1543,6 +1544,142 @@ gst_codec_utils_h265_caps_set_level_tier_and_profile (GstCaps * caps,
return (level != NULL && tier != NULL && profile != NULL);
}
/**
* gst_codec_utils_av1_get_seq_level_idx:
* @level: A level string from caps
*
* Transform a level string from the caps into the seq_level_idx
*
* Returns: the seq_level_idx or 31 (max-level) if the level is unknown
*
* Since: 1.26
*/
guint8
gst_codec_utils_av1_get_seq_level_idx (const gchar * level)
{
g_return_val_if_fail (level != NULL, 0);
if (!strcmp (level, "2.0"))
return 0;
else if (!strcmp (level, "2.1"))
return 1;
else if (!strcmp (level, "2.2"))
return 2;
else if (!strcmp (level, "2.3"))
return 3;
else if (!strcmp (level, "3.0"))
return 4;
else if (!strcmp (level, "3.1"))
return 5;
else if (!strcmp (level, "3.2"))
return 6;
else if (!strcmp (level, "3.3"))
return 7;
else if (!strcmp (level, "4.0"))
return 8;
else if (!strcmp (level, "4.1"))
return 9;
else if (!strcmp (level, "4.2"))
return 10;
else if (!strcmp (level, "4.3"))
return 11;
else if (!strcmp (level, "5.0"))
return 12;
else if (!strcmp (level, "5.1"))
return 13;
else if (!strcmp (level, "5.2"))
return 14;
else if (!strcmp (level, "5.3"))
return 15;
else if (!strcmp (level, "6.0"))
return 16;
else if (!strcmp (level, "6.1"))
return 17;
else if (!strcmp (level, "6.2"))
return 18;
else if (!strcmp (level, "6.3"))
return 19;
else if (!strcmp (level, "7.0"))
return 20;
else if (!strcmp (level, "7.1"))
return 21;
else if (!strcmp (level, "7.2"))
return 22;
else if (!strcmp (level, "7.3"))
return 23;
GST_WARNING ("Invalid level %s", level);
return 31;
}
/**
* gst_codec_utils_av1_get_level:
* @seq_level_idx: A seq_level_idx
*
* Transform a seq_level_idx into the level string
*
* Returns: (nullable): the level string or %NULL if the seq_level_idx is unknown
*
* Since: 1.26
*/
const gchar *
gst_codec_utils_av1_get_level (guint8 seq_level_idx)
{
switch (seq_level_idx) {
case 0:
return "2.0";
case 1:
return "2.1";
case 2:
return "2.2";
case 3:
return "2.3";
case 4:
return "3.0";
case 5:
return "3.1";
case 6:
return "3.2";
case 7:
return "3.3";
case 8:
return "4.0";
case 9:
return "4.1";
case 10:
return "4.2";
case 11:
return "4.3";
case 12:
return "5.0";
case 13:
return "5.1";
case 14:
return "5.2";
case 15:
return "5.3";
case 16:
return "6.0";
case 17:
return "6.1";
case 18:
return "6.2";
case 19:
return "6.3";
case 20:
return "7.0";
case 21:
return "7.1";
case 22:
return "7.2";
case 23:
return "7.3";
default:
return NULL;
}
}
/**
* gst_codec_utils_mpeg4video_get_profile:
* @vis_obj_seq: (array length=len): Pointer to the visual object
@ -2555,6 +2692,256 @@ done:
return g_string_free (codec_string, FALSE);
}
static GstCaps *
av1_caps_from_mime_codec (gchar ** subcodec)
{
GstCaps *caps = NULL;
gchar tier;
guint seq_level_idx_0;
guint bit_depth, seq_profile, chroma_sample_position,
monochrome, chroma_subsampling_x, chroma_subsampling_y, primaries,
transfer, matrix, full_range, chroma_sampling;
const gchar *level_str;
const gchar *tier_str;
const gchar *profile_str;
const gchar *chroma_format_str;
const gchar *colorimetry_str;
GstVideoColorimetry cinfo = { 0, };
caps = gst_caps_new_empty_simple ("video/x-av1");
if (!subcodec[1])
goto done;
seq_profile = g_ascii_strtoull (subcodec[1], NULL, 10);
if (seq_profile == 0) {
profile_str = "main";
} else if (seq_profile == 1) {
profile_str = "high";
} else if (seq_profile == 2) {
profile_str = "professional";
} else {
GST_ERROR ("Unknown AV1 profile %d", seq_profile);
goto done;
}
gst_caps_set_simple (caps, "profile", G_TYPE_STRING, profile_str, NULL);
if (subcodec[2]) {
if (sscanf (subcodec[2], "%02u%c", &seq_level_idx_0, &tier) != 2) {
GST_ERROR ("Failed to parse level and tier from %s", subcodec[2]);
goto done;
}
} else {
seq_level_idx_0 = 1;
tier = 'M';
}
if (tier == 'H') {
tier_str = "high";
} else if (tier == 'M') {
tier_str = "main";
} else {
GST_ERROR ("Unknown AV1 tier %c", tier);
goto done;
}
gst_caps_set_simple (caps, "tier", G_TYPE_STRING, tier_str, NULL);
level_str = gst_codec_utils_av1_get_level (seq_level_idx_0);
if (level_str) {
gst_caps_set_simple (caps, "level", G_TYPE_STRING, level_str, NULL);
} else {
GST_ERROR ("Unknown AV1 level %d", seq_level_idx_0);
goto done;
}
if (subcodec[3]) {
bit_depth = g_ascii_strtoull (subcodec[3], NULL, 10);
gst_caps_set_simple (caps, "bit-depth-luma", G_TYPE_UINT, bit_depth,
"bit-depth-chroma", G_TYPE_UINT, bit_depth, NULL);
} else {
GST_ERROR ("Failed to parse bit-depth from %s", subcodec[3]);
goto done;
}
/* Verify if all values necessary to continue are present in the subcodec */
if (subcodec[4] && subcodec[5] && subcodec[6]
&& subcodec[7] && subcodec[8] && subcodec[9]) {
monochrome = g_ascii_strtoull (subcodec[4], NULL, 10);
chroma_sampling = g_ascii_strtoull (subcodec[5], NULL, 10);
chroma_subsampling_x = chroma_sampling / 100;
chroma_subsampling_y = (chroma_sampling % 100) / 10;
chroma_sample_position = chroma_sampling % 10;
if (monochrome) {
chroma_format_str = "4:0:0";
} else if (chroma_subsampling_x == 1 && chroma_subsampling_y == 1) {
chroma_format_str = "4:2:0";
} else if (chroma_subsampling_x == 1 && chroma_subsampling_y == 0) {
chroma_format_str = "4:2:2";
} else if (chroma_subsampling_x == 0 && chroma_subsampling_y == 0) {
chroma_format_str = "4:4:4";
} else {
GST_ERROR ("Unknown chroma subsampling %d:%d:%d", chroma_subsampling_x,
chroma_subsampling_y, monochrome);
goto done;
}
primaries = g_ascii_strtoull (subcodec[6], NULL, 10);
transfer = g_ascii_strtoull (subcodec[7], NULL, 10);
matrix = g_ascii_strtoull (subcodec[8], NULL, 10);
full_range = g_ascii_strtoull (subcodec[9], NULL, 10);
} else {
GST_DEBUG
("Using default values for chroma_format, chroma_sample_position, "
"primaries, transfer, matrix, and full_range");
chroma_format_str = "4:2:0";
chroma_sample_position = 0;
primaries = 1;
transfer = 1;
matrix = 1;
full_range = 0;
}
gst_caps_set_simple (caps, "chroma-format", G_TYPE_STRING, chroma_format_str,
NULL);
if (chroma_sample_position == 1) {
gst_caps_set_simple (caps, "chroma-site", G_TYPE_STRING, "v-cosited", NULL);
} else if (chroma_sample_position == 2) {
gst_caps_set_simple (caps, "chroma-site", G_TYPE_STRING,
"v-cosited+h-cosited", NULL);
}
cinfo.range =
full_range ? GST_VIDEO_COLOR_RANGE_0_255 : GST_VIDEO_COLOR_RANGE_16_235;
cinfo.primaries = gst_video_color_primaries_from_iso (primaries);
cinfo.transfer = gst_video_transfer_function_from_iso (transfer);
cinfo.matrix = gst_video_color_matrix_from_iso (matrix);
colorimetry_str = gst_video_colorimetry_to_string (&cinfo);
if (colorimetry_str) {
gst_caps_set_simple (caps, "colorimetry", G_TYPE_STRING, colorimetry_str,
NULL);
} else {
GST_ERROR ("Failed to parse colorimetry from %d %d %d %d", full_range,
matrix, transfer, primaries);
}
done:
return caps;
}
/* https://aomediacodec.github.io/av1-isobmff/#codecsparam */
static char *
av1_caps_get_mime_codec (GstCaps * caps)
{
gchar tier_mime;
guint8 seq_level_idx_0;
guint bit_depth, seq_profile, chroma_sample_position,
monochrome, chroma_subsampling_x, chroma_subsampling_y, primaries,
transfer, matrix, full_range;
GstStructure *caps_st;
GString *codec_string;
const gchar *level_str;
const gchar *tier_str;
const gchar *profile_str;
const gchar *chroma_format_str;
const gchar *chroma_site_str;
const gchar *colorimetry_str;
GstVideoColorimetry cinfo = { 0, };
caps_st = gst_caps_get_structure (caps, 0);
codec_string = g_string_new ("av01");
tier_str = gst_structure_get_string (caps_st, "tier");
if (g_strcmp0 (tier_str, "main") == 0) {
tier_mime = 'M';
} else if (g_strcmp0 (tier_str, "high") == 0) {
tier_mime = 'H';
} else {
GST_WARNING ("Unknown AV1 tier %s, using default 'M'", tier_str);
tier_mime = 'M';
}
level_str = gst_structure_get_string (caps_st, "level");
if (level_str) {
seq_level_idx_0 = gst_codec_utils_av1_get_seq_level_idx (level_str);
} else {
seq_level_idx_0 = 1;
}
profile_str = gst_structure_get_string (caps_st, "profile");
if (g_strcmp0 (profile_str, "main") == 0) {
seq_profile = 0;
} else if (g_strcmp0 (profile_str, "high") == 0) {
seq_profile = 1;
} else if (g_strcmp0 (profile_str, "professional") == 0) {
seq_profile = 2;
} else {
goto done;
}
if (!gst_structure_get_uint (caps_st, "bit-depth-luma", &bit_depth))
goto done;
/* We have all information to compute a minimal mime */
g_string_append_printf (codec_string, ".%d.%02u%c.%02u",
seq_profile, seq_level_idx_0, tier_mime, bit_depth);
chroma_format_str = gst_structure_get_string (caps_st, "chroma-format");
if (g_strcmp0 (chroma_format_str, "4:0:0") == 0) {
monochrome = 1;
chroma_subsampling_x = 1;
chroma_subsampling_y = 1;
} else if (g_strcmp0 (chroma_format_str, "4:2:0") == 0) {
monochrome = 0;
chroma_subsampling_x = 1;
chroma_subsampling_y = 1;
} else if (g_strcmp0 (chroma_format_str, "4:2:2") == 0) {
monochrome = 0;
chroma_subsampling_x = 1;
chroma_subsampling_y = 0;
} else if (g_strcmp0 (chroma_format_str, "4:4:4") == 0) {
monochrome = 0;
chroma_subsampling_x = 0;
chroma_subsampling_y = 0;
} else {
goto done;
}
chroma_sample_position = 0;
chroma_site_str = gst_structure_get_string (caps_st, "chroma-site");
if (g_strcmp0 (chroma_site_str, "v-cosited") == 0) {
chroma_sample_position = 1;
} else if (g_strcmp0 (chroma_site_str, "v-cosited+h-cosited") == 0) {
chroma_sample_position = 2;
}
colorimetry_str = gst_structure_get_string (caps_st, "colorimetry");
if (!colorimetry_str)
goto done;
if (!gst_video_colorimetry_from_string (&cinfo, colorimetry_str))
goto done;
full_range = cinfo.range == GST_VIDEO_COLOR_RANGE_0_255;
primaries = gst_video_color_primaries_to_iso (cinfo.primaries);
transfer = gst_video_transfer_function_to_iso (cinfo.transfer);
matrix = gst_video_color_matrix_to_iso (cinfo.matrix);
if (chroma_subsampling_x != 1 || chroma_subsampling_y != 1
|| chroma_sample_position != 0 || primaries != 1 || transfer != 1
|| matrix != 1 || full_range != 0) {
g_string_append_printf (codec_string,
".%u.%u%u%u.%02u.%02u.%02u.%u", monochrome, chroma_subsampling_x,
chroma_subsampling_y, chroma_sample_position, primaries, transfer,
matrix, full_range);
}
done:
return g_string_free (codec_string, FALSE);
}
/**
* gst_codec_utils_caps_get_mime_codec:
* @caps: A #GstCaps to convert to mime codec
@ -2612,10 +2999,7 @@ gst_codec_utils_caps_get_mime_codec (GstCaps * caps)
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");
mime_codec = av1_caps_get_mime_codec (caps);
} 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. */
@ -2812,9 +3196,11 @@ gst_codec_utils_caps_from_mime_codec_single (const gchar * codec)
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");
caps = av1_caps_from_mime_codec (subcodec);
break;
}
case GST_MAKE_FOURCC ('o', 'p', 'u', 's'):
/* Opus */
caps = gst_caps_new_empty_simple ("audio/x-opus");

View file

@ -96,6 +96,15 @@ GST_PBUTILS_API
gboolean gst_codec_utils_h265_caps_set_level_tier_and_profile (GstCaps * caps,
const guint8 * profile_tier_level,
guint len);
/* AV1 */
GST_PBUTILS_API
guint8 gst_codec_utils_av1_get_seq_level_idx (const gchar * level);
GST_PBUTILS_API
const gchar * gst_codec_utils_av1_get_level (guint8 level_idx);
/* MPEG-4 part 2 */
GST_PBUTILS_API

View file

@ -1475,6 +1475,50 @@ GST_START_TEST (test_pb_utils_caps_mime_codec)
g_free (mime_codec);
gst_caps_unref (caps);
/* av1 with default chroma subsampling, color primaries, color transfer,
* color matrix and luma/chroma encoded*/
caps =
gst_caps_from_string
("video/x-av1, stream-format=(string)obu-stream, alignment=(string)tu, profile=(string)main, width=(int)640, height=(int)480, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)30/1, chroma-format=(string)4:2:0, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, colorimetry=(string)bt709");
mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
fail_unless_equals_string (mime_codec, "av01.0.01M.08");
g_free (mime_codec);
gst_caps_unref (caps);
/* av1 with non-default chroma subsampling */
caps =
gst_caps_from_string
("video/x-av1, stream-format=(string)obu-stream, alignment=(string)tu, profile=(string)main, width=(int)640, height=(int)480, "
"pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)30/1, chroma-format=(string)4:2:2, bit-depth-luma=(uint)8, "
"colorimetry=(string)bt709");
mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
fail_unless_equals_string (mime_codec, "av01.0.01M.08.0.100.01.01.01.0");
g_free (mime_codec);
gst_caps_unref (caps);
/* av1 with non-default level and tier */
caps =
gst_caps_from_string
("video/x-av1, stream-format=(string)obu-stream, alignment=(string)tu, profile=(string)main, width=(int)640, height=(int)480, "
"pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)30/1, chroma-format=(string)4:2:2, bit-depth-luma=(uint)8, "
"colorimetry=(string)bt709, tier=(string)high, level=(string)3.0");
mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
fail_unless_equals_string (mime_codec, "av01.0.04H.08.0.100.01.01.01.0");
g_free (mime_codec);
gst_caps_unref (caps);
/* av1 with missing colorimetry */
caps =
gst_caps_from_string
("video/x-av1, stream-format=(string)obu-stream, alignment=(string)tu, profile=(string)main, width=(int)640, height=(int)480, "
"pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)30/1, "
"chroma-format=(string)4:2:2, bit-depth-luma=(uint)8, "
"bit-depth-chroma=(uint)8, tier=(string)main, level=(string)5.0");
mime_codec = gst_codec_utils_caps_get_mime_codec (caps);
fail_unless_equals_string (mime_codec, "av01.0.12M.08");
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);
@ -1581,6 +1625,33 @@ GST_START_TEST (test_pb_utils_caps_mime_codec)
GST_END_TEST;
GST_START_TEST (test_pb_utils_caps_from_mime_codec)
{
GstCaps *caps = NULL;
/* AV1 minimal caps */
caps = gst_codec_utils_caps_from_mime_codec ("av01");
fail_unless_equals_string (gst_caps_to_string (caps), "video/x-av1");
gst_caps_unref (caps);
/* AV1 with default chroma subsampling, color primaries, color transfer,
* color matrix and luma/chroma encoded */
caps = gst_codec_utils_caps_from_mime_codec ("av01.0.01M.08");
fail_unless_equals_string (gst_caps_to_string (caps),
"video/x-av1, profile=(string)main, tier=(string)main, level=(string)2.1, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, chroma-format=(string)4:2:0, colorimetry=(string)bt709");
gst_caps_unref (caps);
/* av1 with non-default chroma subsampling */
caps =
gst_codec_utils_caps_from_mime_codec ("av01.0.01M.08.0.100.01.01.01.0");
fail_unless_equals_string (gst_caps_to_string (caps),
("video/x-av1, profile=(string)main, tier=(string)main, level=(string)2.1, bit-depth-luma=(uint)8, bit-depth-chroma=(uint)8, chroma-format=(string)4:2:2, colorimetry=(string)bt709"));
gst_caps_unref (caps);
};
GST_END_TEST;
static Suite *
libgstpbutils_suite (void)
{
@ -1603,6 +1674,7 @@ libgstpbutils_suite (void)
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_mime_codec);
tcase_add_test (tc_chain, test_pb_utils_caps_from_mime_codec);
return s;
}