diff --git a/girs/GstPbutils-1.0.gir b/girs/GstPbutils-1.0.gir
index bb1678cdaa..a8b7dac51f 100644
--- a/girs/GstPbutils-1.0.gir
+++ b/girs/GstPbutils-1.0.gir
@@ -3517,6 +3517,110 @@ is expected to have the same format as for gst_codec_utils_h264_get_profile().
+
+ Sets the level, tier and profile in @caps if it can be determined from
+@decoder_configuration. See gst_codec_utils_h266_get_level(),
+gst_codec_utils_h266_get_tier() and gst_codec_utils_h266_get_profile()
+for more details on the parameters.
+
+
+ %TRUE if the level, tier, profile could be set, %FALSE otherwise.
+
+
+
+
+ the #GstCaps to which the level, tier and profile are to be added
+
+
+
+ Pointer to the VvcDecoderConfigurationRecord struct as defined in ISO/IEC 14496-15
+
+
+
+
+
+ Length of the data available in @decoder_configuration.
+
+
+
+
+
+ Converts the level indication (general_level_idc) in the stream's
+ptl_record structure into a string.
+
+
+ The level as a const string, or %NULL if there is an error.
+
+
+
+
+ Pointer to the VvcPTLRecord structure as defined in ISO/IEC 14496-15.
+
+
+
+
+
+ Length of the data available in @ptl_record.
+
+
+
+
+
+ Transform a level string from the caps into the level_idc
+
+
+ the level_idc or 0 if the level is unknown
+
+
+
+
+ A level string from caps
+
+
+
+
+
+ Converts the profile indication (general_profile_idc) in the stream's
+ptl_record structure into a string.
+
+
+ The profile as a const string, or %NULL if there is an error.
+
+
+
+
+ Pointer to the VvcPTLRecord structure as defined in ISO/IEC 14496-15.
+
+
+
+
+
+ Length of the data available in @ptl_record
+
+
+
+
+
+ Converts the tier indication (general_tier_flag) in the stream's
+ptl_record structure into a string.
+
+
+ The tier as a const string, or %NULL if there is an error.
+
+
+
+
+ Pointer to the VvcPTLRecord structure as defined in ISO/IEC 14496-15.
+
+
+
+
+
+ Length of the data available in @ptl_record.
+
+
+
+
Sets the level and profile in @caps if it can be determined from
@vis_obj_seq. See gst_codec_utils_mpeg4video_get_level() and
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 563784aae7..4cd10c81f6 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
@@ -1544,6 +1544,304 @@ gst_codec_utils_h265_caps_set_level_tier_and_profile (GstCaps * caps,
return (level != NULL && tier != NULL && profile != NULL);
}
+/**
+ * gst_codec_utils_h266_get_profile:
+ * @ptl_record: (array length=len): Pointer to the VvcPTLRecord structure as defined in ISO/IEC 14496-15.
+ * @len: Length of the data available in @ptl_record
+ *
+ * Converts the profile indication (general_profile_idc) in the stream's
+ * ptl_record structure into a string.
+ *
+ * Returns: (nullable): The profile as a const string, or %NULL if there is an error.
+ *
+ * Since: 1.26
+ */
+const gchar *
+gst_codec_utils_h266_get_profile (const guint8 * ptl_record, guint len)
+{
+ gint profile_idc;
+
+ g_return_val_if_fail (ptl_record != NULL, NULL);
+
+ if (len < 2)
+ return NULL;
+
+ GST_MEMDUMP ("VvcPTLRecord", ptl_record, len);
+
+ profile_idc = (ptl_record[1] & 0xFE) >> 1;
+
+ if (!profile_idc)
+ return NULL;
+
+ switch (profile_idc) {
+ case 1:
+ return "main-10";
+ break;
+ case 2:
+ return "main-12";
+ break;
+ case 10:
+ return "main-12-intra";
+ break;
+ case 17:
+ return "multilayer-main-10";
+ break;
+ case 33:
+ return "main-444-10";
+ break;
+ case 34:
+ return "main-444-12";
+ break;
+ case 35:
+ return "main-444-16";
+ break;
+ case 42:
+ return "main-444-12-intra";
+ break;
+ case 43:
+ return "main-444-16-intra";
+ break;
+ case 49:
+ return "multilayer-main-444-10";
+ break;
+ case 65:
+ return "main-10-still-picture";
+ break;
+ case 66:
+ return "main-12-still-picture";
+ break;
+ case 97:
+ return "main-444-10-still-picture";
+ break;
+ case 98:
+ return "main-444-12-still-picture";
+ break;
+ case 99:
+ return "main-444-16-still-picture";
+ break;
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * gst_codec_utils_h266_get_tier:
+ * @ptl_record: (array length=len): Pointer to the VvcPTLRecord structure as defined in ISO/IEC 14496-15.
+ * @len: Length of the data available in @ptl_record.
+ *
+ * Converts the tier indication (general_tier_flag) in the stream's
+ * ptl_record structure into a string.
+ *
+ * Returns: (nullable): The tier as a const string, or %NULL if there is an error.
+ *
+ * Since: 1.26
+ */
+const gchar *
+gst_codec_utils_h266_get_tier (const guint8 * ptl_record, guint len)
+{
+ const gchar *tier = NULL;
+ gint tier_flag = 0;
+
+ g_return_val_if_fail (ptl_record != NULL, NULL);
+
+ if (len < 2)
+ return NULL;
+
+ GST_MEMDUMP ("VvcPTLRecord", ptl_record, len);
+
+ tier_flag = ptl_record[1] & 0x01;
+
+ if (tier_flag)
+ tier = "high";
+ else
+ tier = "main";
+
+ return tier;
+}
+
+/**
+ * gst_codec_utils_h266_get_level:
+ * @ptl_record: (array length=len): Pointer to the VvcPTLRecord structure as defined in ISO/IEC 14496-15.
+ * @len: Length of the data available in @ptl_record.
+ *
+ * Converts the level indication (general_level_idc) in the stream's
+ * ptl_record structure into a string.
+ *
+ * Returns: (nullable): The level as a const string, or %NULL if there is an error.
+ *
+ * Since: 1.26
+ */
+const gchar *
+gst_codec_utils_h266_get_level (const guint8 * ptl_record, guint len)
+{
+ guint8 level_idc;
+
+ g_return_val_if_fail (ptl_record != NULL, NULL);
+
+ if (len < 3)
+ return NULL;
+
+ GST_MEMDUMP ("VvcPTLRecord", ptl_record, len);
+
+ level_idc = ptl_record[2];
+
+ if (!level_idc)
+ return NULL;
+
+ switch (level_idc) {
+ case 16:
+ return "1";
+ break;
+ case 32:
+ return "2";
+ break;
+ case 35:
+ return "2.1";
+ break;
+ case 48:
+ return "3";
+ break;
+ case 51:
+ return "3.1";
+ break;
+ case 64:
+ return "4";
+ break;
+ case 67:
+ return "4.1";
+ break;
+ case 80:
+ return "5";
+ break;
+ case 83:
+ return "5.1";
+ break;
+ case 86:
+ return "5.2";
+ break;
+ case 96:
+ return "6";
+ break;
+ case 99:
+ return "6.1";
+ break;
+ case 102:
+ return "6.2";
+ break;
+ case 105:
+ return "6.3";
+ break;
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * gst_codec_utils_h266_get_level_idc:
+ * @level: A level string from caps
+ *
+ * Transform a level string from the caps into the level_idc
+ *
+ * Returns: the level_idc or 0 if the level is unknown
+ *
+ * Since: 1.26
+ */
+guint8
+gst_codec_utils_h266_get_level_idc (const gchar * level)
+{
+ g_return_val_if_fail (level != NULL, 0);
+
+ if (!strcmp (level, "1"))
+ return 16;
+ else if (!strcmp (level, "2"))
+ return 32;
+ else if (!strcmp (level, "2.1"))
+ return 35;
+ else if (!strcmp (level, "3"))
+ return 48;
+ else if (!strcmp (level, "3.1"))
+ return 51;
+ else if (!strcmp (level, "4"))
+ return 64;
+ else if (!strcmp (level, "4.1"))
+ return 67;
+ else if (!strcmp (level, "5"))
+ return 80;
+ else if (!strcmp (level, "5.1"))
+ return 83;
+ else if (!strcmp (level, "5.2"))
+ return 86;
+ else if (!strcmp (level, "6"))
+ return 96;
+ else if (!strcmp (level, "6.1"))
+ return 99;
+ else if (!strcmp (level, "6.2"))
+ return 102;
+ else if (!strcmp (level, "6.3"))
+ return 105;
+
+
+ GST_WARNING ("Invalid level %s", level);
+ return 0;
+}
+
+/**
+ * gst_codec_utils_h266_caps_set_level_tier_and_profile:
+ * @caps: the #GstCaps to which the level, tier and profile are to be added
+ * @decoder_configuration: (array length=len): Pointer to the VvcDecoderConfigurationRecord struct as defined in ISO/IEC 14496-15
+ * @len: Length of the data available in @decoder_configuration.
+ *
+ * Sets the level, tier and profile in @caps if it can be determined from
+ * @decoder_configuration. See gst_codec_utils_h266_get_level(),
+ * gst_codec_utils_h266_get_tier() and gst_codec_utils_h266_get_profile()
+ * for more details on the parameters.
+ *
+ * Returns: %TRUE if the level, tier, profile could be set, %FALSE otherwise.
+ *
+ * Since: 1.26
+ */
+gboolean
+gst_codec_utils_h266_caps_set_level_tier_and_profile (GstCaps * caps,
+ const guint8 * decoder_configuration, guint len)
+{
+ const gchar *level, *tier, *profile;
+ gboolean ptl_present_flag;
+ const guint8 *ptl_record;
+
+ g_return_val_if_fail (GST_IS_CAPS (caps), FALSE);
+ g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), FALSE);
+ g_return_val_if_fail (GST_SIMPLE_CAPS_HAS_NAME (caps, "video/x-h266"), FALSE);
+ g_return_val_if_fail (decoder_configuration != NULL, FALSE);
+
+ if (len < 5)
+ return FALSE;
+
+ ptl_present_flag = decoder_configuration[0] & 0x01;
+ if (!ptl_present_flag)
+ return FALSE;
+
+ ptl_record = decoder_configuration + 4;
+ len -= 4;
+
+ level = gst_codec_utils_h266_get_level (ptl_record, len);
+ if (level != NULL)
+ gst_caps_set_simple (caps, "level", G_TYPE_STRING, level, NULL);
+
+ tier = gst_codec_utils_h266_get_tier (ptl_record, len);
+ if (tier != NULL)
+ gst_caps_set_simple (caps, "tier", G_TYPE_STRING, tier, NULL);
+
+ profile = gst_codec_utils_h266_get_profile (ptl_record, len);
+ if (profile != NULL)
+ gst_caps_set_simple (caps, "profile", G_TYPE_STRING, profile, NULL);
+
+ GST_LOG ("profile : %s", (profile) ? profile : "---");
+ GST_LOG ("tier : %s", (tier) ? tier : "---");
+ GST_LOG ("level : %s", (level) ? level : "---");
+
+ return (level != NULL && tier != NULL && profile != NULL);
+}
+
/**
* gst_codec_utils_av1_get_seq_level_idx:
* @level: A level string from caps
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 076b45f187..0da17a8328 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
@@ -97,6 +97,28 @@ gboolean gst_codec_utils_h265_caps_set_level_tier_and_profile (GstCaps
const guint8 * profile_tier_level,
guint len);
+/* H.266 */
+
+GST_PBUTILS_API
+const gchar * gst_codec_utils_h266_get_profile (const guint8 * ptl_record,
+ guint len);
+
+GST_PBUTILS_API
+const gchar * gst_codec_utils_h266_get_tier (const guint8 * ptl_record,
+ guint len);
+
+GST_PBUTILS_API
+const gchar * gst_codec_utils_h266_get_level (const guint8 * ptl_record,
+ guint len);
+
+GST_PBUTILS_API
+guint8 gst_codec_utils_h266_get_level_idc (const gchar * level);
+
+GST_PBUTILS_API
+gboolean gst_codec_utils_h266_caps_set_level_tier_and_profile (GstCaps * caps,
+ const guint8 * decoder_configuration,
+ guint len);
+
/* AV1 */
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 d6ed343011..8329ccb800 100644
--- a/subprojects/gst-plugins-base/tests/check/libs/pbutils.c
+++ b/subprojects/gst-plugins-base/tests/check/libs/pbutils.c
@@ -1118,6 +1118,65 @@ GST_START_TEST (test_pb_utils_h264_get_profile_flags_level)
GST_END_TEST;
+GST_START_TEST (test_pb_utils_h266_caps_set_level_tier_and_profile)
+{
+ GstCaps *caps;
+ GstStructure *s;
+ const guint8 good_config[] =
+ { 0xff, 0x00, 0x61, 0x1f, 0x01, 0x02, 0x50, 0x80 };
+ const guint8 short_config[] = { 0xff, 0x00, 0x61, 0x1f };
+ const guint8 no_ptl_config[] = { 0xfe, 0xba, 0xbe, 0xef };
+ const guint8 short_config_no_level[] = { 0xff, 0x00, 0x61, 0x1f, 0x01, 0x02 };
+
+ /* Happy path */
+ caps = gst_caps_new_empty_simple ("video/x-h266");
+ fail_unless (gst_codec_utils_h266_caps_set_level_tier_and_profile (caps,
+ good_config, sizeof (good_config)));
+ s = gst_caps_get_structure (caps, 0);
+
+ fail_unless_equals_string (gst_structure_get_string (s, "profile"),
+ "main-10");
+ fail_unless_equals_string (gst_structure_get_string (s, "level"), "5");
+ fail_unless_equals_string (gst_structure_get_string (s, "tier"), "main");
+ gst_caps_unref (caps);
+
+ /* Too short to have profile-tier-level */
+ caps = gst_caps_new_empty_simple ("video/x-h266");
+ fail_unless (!gst_codec_utils_h266_caps_set_level_tier_and_profile (caps,
+ short_config, sizeof (short_config)));
+ s = gst_caps_get_structure (caps, 0);
+
+ fail_unless_equals_string (gst_structure_get_string (s, "profile"), NULL);
+ fail_unless_equals_string (gst_structure_get_string (s, "level"), NULL);
+ fail_unless_equals_string (gst_structure_get_string (s, "tier"), NULL);
+ gst_caps_unref (caps);
+
+ /* ptl_present_flag = FALSE */
+ caps = gst_caps_new_empty_simple ("video/x-h266");
+ fail_unless (!gst_codec_utils_h266_caps_set_level_tier_and_profile (caps,
+ no_ptl_config, sizeof (no_ptl_config)));
+ s = gst_caps_get_structure (caps, 0);
+
+ fail_unless_equals_string (gst_structure_get_string (s, "profile"), NULL);
+ fail_unless_equals_string (gst_structure_get_string (s, "level"), NULL);
+ fail_unless_equals_string (gst_structure_get_string (s, "tier"), NULL);
+ gst_caps_unref (caps);
+
+ /* Too short to parse level */
+ caps = gst_caps_new_empty_simple ("video/x-h266");
+ fail_unless (!gst_codec_utils_h266_caps_set_level_tier_and_profile (caps,
+ short_config_no_level, sizeof (short_config_no_level)));
+ s = gst_caps_get_structure (caps, 0);
+
+ fail_unless_equals_string (gst_structure_get_string (s, "profile"),
+ "main-10");
+ fail_unless_equals_string (gst_structure_get_string (s, "level"), NULL);
+ fail_unless_equals_string (gst_structure_get_string (s, "tier"), "main");
+ gst_caps_unref (caps);
+}
+
+GST_END_TEST;
+
#define PROFILE_TIER_LEVEL_LEN 11
static void
@@ -1712,6 +1771,8 @@ libgstpbutils_suite (void)
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);
+ tcase_add_test (tc_chain, test_pb_utils_h266_caps_set_level_tier_and_profile);
+
return s;
}