diff --git a/girs/GstPbutils-1.0.gir b/girs/GstPbutils-1.0.gir
index 312faed9df..61b5f4a26c 100644
--- a/girs/GstPbutils-1.0.gir
+++ b/girs/GstPbutils-1.0.gir
@@ -3147,6 +3147,34 @@ rate.
+
+ Transform a seq_level_idx into the level string
+
+
+ the level string or %NULL if the seq_level_idx is unknown
+
+
+
+
+ A seq_level_idx
+
+
+
+
+
+ Transform a level string from the caps into the seq_level_idx
+
+
+ the seq_level_idx or 31 (max-level) if the level is unknown
+
+
+
+
+ A level string from caps
+
+
+
+
Converts a RFC 6381 compatible codec string to #GstCaps. More than one codec
string can be present (separated by `,`).
diff --git a/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c b/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c
index 71d918034d..e62d9eb31c 100644
--- a/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c
+++ b/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.c
@@ -41,6 +41,7 @@
#include
#include
+#include
#include
#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;
+ 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_WARNING ("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_WARNING ("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_WARNING ("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_WARNING ("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_WARNING ("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_WARNING ("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_WARNING ("Failed to parse colorimetry from %u %u %u %u", full_range,
+ matrix, transfer, primaries);
+ }
+ g_free (colorimetry_str);
+
+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");
diff --git a/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.h b/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.h
index 43398c4eaa..87127e43a6 100644
--- a/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.h
+++ b/subprojects/gst-plugins-base/gst-libs/gst/pbutils/codec-utils.h
@@ -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 seq_level_idx);
+
/* MPEG-4 part 2 */
GST_PBUTILS_API
diff --git a/subprojects/gst-plugins-base/tests/check/libs/pbutils.c b/subprojects/gst-plugins-base/tests/check/libs/pbutils.c
index 1e14c9d7c0..ae52205ca0 100644
--- a/subprojects/gst-plugins-base/tests/check/libs/pbutils.c
+++ b/subprojects/gst-plugins-base/tests/check/libs/pbutils.c
@@ -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,40 @@ 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;
+ gchar *caps_str = NULL;
+
+ /* AV1 minimal caps */
+ caps = gst_codec_utils_caps_from_mime_codec ("av01");
+ caps_str = gst_caps_to_string (caps);
+ fail_unless_equals_string (caps_str, "video/x-av1");
+ gst_caps_unref (caps);
+ g_free (caps_str);
+
+ /* 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");
+ caps_str = gst_caps_to_string (caps);
+ fail_unless_equals_string (caps_str,
+ "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);
+ g_free (caps_str);
+
+ /* av1 with non-default chroma subsampling */
+ caps =
+ gst_codec_utils_caps_from_mime_codec ("av01.0.01M.08.0.100.01.01.01.0");
+ caps_str = gst_caps_to_string (caps);
+ fail_unless_equals_string (caps_str,
+ ("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);
+ g_free (caps_str);
+
+};
+
+GST_END_TEST;
+
static Suite *
libgstpbutils_suite (void)
{
@@ -1603,6 +1681,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;
}