Merge branch 'sdp-h264-profile-level-id' into 'main'

sdp: Improve h.264 caps <-> media accuracy

See merge request gstreamer/gstreamer!3616
This commit is contained in:
Philippe Normand 2024-05-03 20:25:32 +00:00
commit 9766a56225
2 changed files with 166 additions and 8 deletions

View file

@ -3563,8 +3563,8 @@ gst_sdp_media_add_rtcp_fb_attributes_from_media (const GstSDPMedia * media,
static void
gst_sdp_media_caps_adjust_h264 (GstCaps * caps)
{
long int spsint;
guint8 sps[2];
gint64 spsint;
guint8 sps[3];
const gchar *profile_level_id;
GstStructure *s = gst_caps_get_structure (caps, 0);
@ -3576,19 +3576,109 @@ gst_sdp_media_caps_adjust_h264 (GstCaps * caps)
if (!profile_level_id)
return;
spsint = strtol (profile_level_id, NULL, 16);
spsint = g_ascii_strtoll (profile_level_id, NULL, 16);
sps[0] = spsint >> 16;
sps[1] = spsint >> 8;
sps[1] = (spsint >> 8) & 0xff;
sps[2] = spsint & 0xff;
GST_DEBUG ("'level-asymmetry-allowed' is set so we shouldn't care about "
"'profile-level-id' and only set a 'profile' instead");
"'profile-level-id' and only set a 'profile' and 'level' instead");
gst_structure_set (s, "profile", G_TYPE_STRING,
gst_codec_utils_h264_get_profile (sps, 2), NULL);
gst_codec_utils_h264_get_profile (sps, 3), "level", G_TYPE_STRING,
gst_codec_utils_h264_get_level (sps, 3), NULL);
gst_structure_remove_fields (s, "level-asymmetry-allowed", "profile-level-id",
NULL);
}
static gchar *
gst_sdp_media_h264_profile_level_id_from_profile_and_level (const gchar *
profile, const gchar * level)
{
gint csf0, csf1, csf2, csf3, csf4, csf5, profile_iop, reserved_zero_2bits;
gint profile_idc = 0;
guint8 level_idc = 0;
csf0 = csf1 = csf2 = csf3 = csf4 = csf5 = reserved_zero_2bits = profile_iop =
0;
g_return_val_if_fail (profile != NULL, NULL);
if (level != NULL) {
level_idc = gst_codec_utils_h264_get_level_idc (level);
if (!strcmp (level, "1b")) {
csf3 = 1;
}
}
if (!strcmp (profile, "baseline")) {
profile_idc = 66;
} else if (!strcmp (profile, "constrained-baseline")) {
profile_idc = 66;
csf1 = 1;
} else if (!strcmp (profile, "main")) {
profile_idc = 77;
} else if (!strcmp (profile, "extended")) {
profile_idc = 88;
} else if (!strcmp (profile, "high")) {
profile_idc = 100;
} else if (!strcmp (profile, "constrained-high")) {
profile_idc = 100;
csf4 = 1;
csf5 = 1;
} else if (!strcmp (profile, "progressive-high")) {
profile_idc = 100;
csf4 = 1;
} else if (!strcmp (profile, "high-10")) {
profile_idc = 110;
} else if (!strcmp (profile, "high-10-intra")) {
profile_idc = 110;
csf3 = 1;
} else if (!strcmp (profile, "progressive-high-10")) {
profile_idc = 110;
csf4 = 1;
} else if (g_str_has_prefix (profile, "high-4:2:2")) {
profile_idc = 122;
if (!strcmp (profile, "high-4:2:2-intra")) {
csf3 = 1;
}
} else if (g_str_has_prefix (profile, "high-4:4:4")) {
profile_idc = 244;
if (!strcmp (profile, "high-4:4:4-intra")) {
csf3 = 1;
}
} else if (!strcmp (profile, "cavlc-4:4:4-intra")) {
profile_idc = 44;
} else if (!strcmp (profile, "multiview-high")) {
profile_idc = 118;
} else if (!strcmp (profile, "stereo-high")) {
profile_idc = 128;
} else if (!strcmp (profile, "scalable-baseline")) {
profile_idc = 83;
} else if (!strcmp (profile, "scalable-constrained-baseline")) {
profile_idc = 83;
csf5 = 1;
} else if (!strcmp (profile, "scalable-high")) {
profile_idc = 86;
} else if (!strcmp (profile, "scalable-high-intra")) {
profile_idc = 86;
csf3 = 1;
} else if (!strcmp (profile, "scalable-constrained-high")) {
profile_idc = 86;
csf5 = 1;
} else {
GST_FIXME ("H.264 profile %s to profile-level-id translation unimplemented",
profile);
return NULL;
}
profile_iop |=
(csf0 << 7) | (csf1 << 6) | (csf2 << 5) | (csf3 << 4) | (csf4 << 3) |
(csf5 << 2) | (reserved_zero_2bits << 1);
return g_strdup_printf ("%02X%02X%02X", profile_idc, profile_iop, level_idc);
}
/**
* gst_sdp_media_get_caps_from_media:
* @media: a #GstSDPMedia
@ -4073,10 +4163,19 @@ gst_sdp_media_set_media_from_caps (const GstCaps * caps, GstSDPMedia * media)
if ((fval = gst_structure_get_string (s, fname))) {
/* "profile" is our internal representation of the notion of
* "level-asymmetry-allowed" with caps, convert it back to the SDP
* representation */
* "level-asymmetry-allowed" and "profile-level-id" with caps, convert it
* back to the SDP representation */
if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "H264")
&& !g_strcmp0 (fname, "profile")) {
gchar *profile_level_id =
gst_sdp_media_h264_profile_level_id_from_profile_and_level (fval,
gst_structure_get_string (s, "level"));
if (profile_level_id != NULL) {
g_string_append_printf (fmtp, "%sprofile-level-id=%s",
first ? "" : ";", profile_level_id);
g_free (profile_level_id);
first = FALSE;
}
fname = "level-asymmetry-allowed";
fval = "1";
}

View file

@ -27,6 +27,7 @@
#include <gst/check/gstcheck.h>
#include <gst/sdp/gstsdpmessage.h>
#include <gst/pbutils/pbutils.h>
/* *INDENT-OFF* */
static const gchar *sdp = "v=0\r\n"
@ -763,6 +764,11 @@ GST_START_TEST (media_from_caps_h264_with_profile_asymmetry_allowed)
const GstSDPMedia *result_video;
GstStructure *s_video;
GstCaps *caps_video;
GstSDPMedia media;
GstSDPResult ret = GST_SDP_OK;
const gchar *fmtp;
gboolean profile_level_id_found = FALSE;
gchar **pairs;
gst_sdp_message_new (&message);
gst_sdp_message_parse_buffer ((guint8 *) h264_sdp, length, message);
@ -777,7 +783,60 @@ GST_START_TEST (media_from_caps_h264_with_profile_asymmetry_allowed)
fail_if (gst_structure_has_field (s_video, "profile-level-id"));
fail_unless_equals_string (gst_structure_get_string (s_video, "profile"),
"constrained-baseline");
fail_unless_equals_string (gst_structure_get_string (s_video, "level"),
"3.1");
/* Check that the conversion from caps to SDP preserved the profile and level
* caps fields, through the profile-level-id SDP sub-attribute of the fmtp
* attribute. */
memset (&media, 0, sizeof (media));
fail_unless_equals_int (GST_SDP_OK, gst_sdp_media_init (&media));
ret = gst_sdp_media_set_media_from_caps (caps_video, &media);
fail_unless (ret == GST_SDP_OK);
fmtp = gst_sdp_media_get_attribute_val (&media, "fmtp");
fail_unless (fmtp != NULL);
pairs = g_strsplit (fmtp, ";", 0);
for (int i = 0; pairs[i]; i++) {
gchar *valpos;
const gchar *val, *key;
valpos = strstr (pairs[i], "=");
if (valpos) {
/* we have a '=' and thus a value, remove the '=' with \0 */
*valpos = '\0';
/* value is everything between '=' and ';'. We split the pairs at ;
* boundaries so we can take the remainder of the value. Some servers
* put spaces around the value which we strip off here. Alternatively
* we could strip those spaces in the depayloaders should these spaces
* actually carry any meaning in the future. */
val = g_strstrip (valpos + 1);
} else {
/* simple <param>;.. is translated into <param>=1;... */
val = "1";
}
/* strip the key of spaces, convert key to lowercase but not the value. */
key = g_strstrip (pairs[i]);
if (!strcmp (key, "profile-level-id")) {
gint64 spsint;
guint8 sps[3];
spsint = g_ascii_strtoll (val, NULL, 16);
sps[0] = spsint >> 16;
sps[1] = (spsint >> 8) & 0xff;
sps[2] = spsint & 0xff;
fail_unless_equals_string (gst_codec_utils_h264_get_profile (sps, 3),
"constrained-baseline");
fail_unless_equals_string (gst_codec_utils_h264_get_level (sps, 3),
"3.1");
profile_level_id_found = TRUE;
break;
}
}
g_strfreev (pairs);
fail_unless (profile_level_id_found);
gst_sdp_media_uninit (&media);
gst_caps_unref (caps_video);
gst_sdp_message_free (message);
}