x264enc: Implement getcaps that restricts the input format depending on the output profile

Also add some x264enc profile tests

https://bugzilla.gnome.org/show_bug.cgi?id=708326
This commit is contained in:
Olivier Crête 2013-09-18 19:13:45 -04:00
parent c5312c7105
commit b82703170a
2 changed files with 190 additions and 45 deletions

View File

@ -412,8 +412,9 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
"width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ], " "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ], "
"stream-format = (string) { avc, byte-stream }, " "stream-format = (string) { avc, byte-stream }, "
"alignment = (string) au, " "alignment = (string) au, "
"profile = (string) { high-10, high, main, baseline, " "profile = (string) { high-4:4:4, high-4:2:2, high-10, high, main,"
"constrained-baseline, high-10-intra }") " baseline, constrained-baseline, high-4:4:4-intra, high-4:2:2-intra,"
" high-10-intra }")
); );
static void gst_x264_enc_finalize (GObject * object); static void gst_x264_enc_finalize (GObject * object);
@ -498,53 +499,51 @@ set_value (GValue * val, gint count, ...)
g_value_unset (&sval); g_value_unset (&sval);
} }
static GstCaps * static void
gst_x264_enc_get_supported_input_caps (void) gst_x264_enc_add_x264_chroma_format (GstStructure * s,
int x264_chroma_format_local)
{ {
GValue fmt = G_VALUE_INIT; GValue fmt = G_VALUE_INIT;
GstCaps *caps;
caps = gst_caps_new_empty_simple ("video/x-raw");
if (x264_bit_depth == 8) { if (x264_bit_depth == 8) {
GST_INFO ("This x264 build supports 8-bit depth"); GST_INFO ("This x264 build supports 8-bit depth");
if (x264_chroma_format == 0) { if (x264_chroma_format_local == 0) {
set_value (&fmt, 5, "I420", "YV12", "Y42B", "Y444", "NV12"); set_value (&fmt, 5, "I420", "YV12", "Y42B", "Y444", "NV12");
} else if (x264_chroma_format == X264_CSP_I420) { } else if (x264_chroma_format_local == X264_CSP_I420) {
set_value (&fmt, 3, "I420", "YV12", "NV12"); set_value (&fmt, 3, "I420", "YV12", "NV12");
} else if (x264_chroma_format == X264_CSP_I422) { } else if (x264_chroma_format_local == X264_CSP_I422) {
set_value (&fmt, 1, "Y42B"); set_value (&fmt, 1, "Y42B");
} else if (x264_chroma_format == X264_CSP_I444) { } else if (x264_chroma_format_local == X264_CSP_I444) {
set_value (&fmt, 1, "Y444"); set_value (&fmt, 1, "Y444");
} else { } else {
GST_ERROR ("Unsupported chroma format %d", x264_chroma_format); GST_ERROR ("Unsupported chroma format %d", x264_chroma_format_local);
} }
} else if (x264_bit_depth == 10) { } else if (x264_bit_depth == 10) {
GST_INFO ("This x264 build supports 10-bit depth"); GST_INFO ("This x264 build supports 10-bit depth");
if (G_BYTE_ORDER == G_LITTLE_ENDIAN) { if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
if (x264_chroma_format == 0) { if (x264_chroma_format_local == 0) {
set_value (&fmt, 3, "I420_10LE", "I422_10LE", "Y444_10LE"); set_value (&fmt, 3, "I420_10LE", "I422_10LE", "Y444_10LE");
} else if (x264_chroma_format == X264_CSP_I420) { } else if (x264_chroma_format_local == X264_CSP_I420) {
set_value (&fmt, 1, "I420_10LE"); set_value (&fmt, 1, "I420_10LE");
} else if (x264_chroma_format == X264_CSP_I422) { } else if (x264_chroma_format_local == X264_CSP_I422) {
set_value (&fmt, 1, "Y422_10LE"); set_value (&fmt, 1, "I422_10LE");
} else if (x264_chroma_format == X264_CSP_I444) { } else if (x264_chroma_format_local == X264_CSP_I444) {
set_value (&fmt, 1, "Y444_10LE"); set_value (&fmt, 1, "Y444_10LE");
} else { } else {
GST_ERROR ("Unsupported chroma format %d", x264_chroma_format); GST_ERROR ("Unsupported chroma format %d", x264_chroma_format_local);
} }
} else { } else {
if (x264_chroma_format == 0) { if (x264_chroma_format_local == 0) {
set_value (&fmt, 3, "I420_10BE", "I422_10BE", "Y444_10BE"); set_value (&fmt, 3, "I420_10BE", "I422_10BE", "Y444_10BE");
} else if (x264_chroma_format == X264_CSP_I420) { } else if (x264_chroma_format_local == X264_CSP_I420) {
set_value (&fmt, 1, "I420_10BE"); set_value (&fmt, 1, "I420_10BE");
} else if (x264_chroma_format == X264_CSP_I422) { } else if (x264_chroma_format_local == X264_CSP_I422) {
set_value (&fmt, 1, "Y422_10BE"); set_value (&fmt, 1, "I422_10BE");
} else if (x264_chroma_format == X264_CSP_I444) { } else if (x264_chroma_format_local == X264_CSP_I444) {
set_value (&fmt, 1, "Y444_10BE"); set_value (&fmt, 1, "Y444_10BE");
} else { } else {
GST_ERROR ("Unsupported chroma format %d", x264_chroma_format); GST_ERROR ("Unsupported chroma format %d", x264_chroma_format_local);
} }
} }
} else { } else {
@ -553,28 +552,135 @@ gst_x264_enc_get_supported_input_caps (void)
} }
if (G_VALUE_TYPE (&fmt) != G_TYPE_INVALID) if (G_VALUE_TYPE (&fmt) != G_TYPE_INVALID)
gst_structure_take_value (gst_caps_get_structure (caps, 0), "format", &fmt); gst_structure_take_value (s, "format", &fmt);
}
gst_caps_set_simple (caps, static GstCaps *
gst_x264_enc_get_supported_input_caps (void)
{
GstCaps *caps;
caps = gst_caps_new_simple ("video/x-raw",
"framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1,
"width", GST_TYPE_INT_RANGE, 16, G_MAXINT, "width", GST_TYPE_INT_RANGE, 16, G_MAXINT,
"height", GST_TYPE_INT_RANGE, 16, G_MAXINT, NULL); "height", GST_TYPE_INT_RANGE, 16, G_MAXINT, NULL);
gst_x264_enc_add_x264_chroma_format (gst_caps_get_structure (caps, 0),
x264_chroma_format);
GST_DEBUG ("returning %" GST_PTR_FORMAT, caps); GST_DEBUG ("returning %" GST_PTR_FORMAT, caps);
return caps; return caps;
} }
static void
check_formats (const gchar * str, gboolean * has_420, gboolean * has_422,
gboolean * has_444)
{
if (g_str_has_prefix (str, "high-4:4:4"))
*has_444 = TRUE;
else if (g_str_has_prefix (str, "high-4:2:2"))
*has_422 = TRUE;
else
*has_420 = TRUE;
}
/* allowed input caps depending on whether libx264 was built for 8 or 10 bits */ /* allowed input caps depending on whether libx264 was built for 8 or 10 bits */
static GstCaps * static GstCaps *
gst_x264_enc_sink_getcaps (GstVideoEncoder * enc, GstCaps * filter) gst_x264_enc_sink_getcaps (GstVideoEncoder * enc, GstCaps * filter)
{ {
GstCaps *supported_incaps, *caps; GstCaps *supported_incaps;
GstCaps *allowed;
GstCaps *filter_caps, *fcaps;
gint i, j, k;
supported_incaps = gst_x264_enc_get_supported_input_caps (); supported_incaps = gst_x264_enc_get_supported_input_caps ();
caps = gst_video_encoder_proxy_getcaps (enc, supported_incaps, filter);
/* Allow downstream to specify width/height/framerate/PAR constraints
* and forward them upstream for video converters to handle
*/
if (!supported_incaps)
supported_incaps = gst_pad_get_pad_template_caps (enc->sinkpad);
allowed = gst_pad_get_allowed_caps (enc->srcpad);
if (!allowed || gst_caps_is_empty (allowed) || gst_caps_is_any (allowed)) {
fcaps = supported_incaps;
goto done;
}
GST_LOG_OBJECT (enc, "template caps %" GST_PTR_FORMAT, supported_incaps);
GST_LOG_OBJECT (enc, "allowed caps %" GST_PTR_FORMAT, allowed);
filter_caps = gst_caps_new_empty ();
for (i = 0; i < gst_caps_get_size (supported_incaps); i++) {
GQuark q_name =
gst_structure_get_name_id (gst_caps_get_structure (supported_incaps,
i));
for (j = 0; j < gst_caps_get_size (allowed); j++) {
const GstStructure *allowed_s = gst_caps_get_structure (allowed, j);
const GValue *val;
GstStructure *s;
s = gst_structure_new_id_empty (q_name);
if ((val = gst_structure_get_value (allowed_s, "width")))
gst_structure_set_value (s, "width", val);
if ((val = gst_structure_get_value (allowed_s, "height")))
gst_structure_set_value (s, "height", val);
if ((val = gst_structure_get_value (allowed_s, "framerate")))
gst_structure_set_value (s, "framerate", val);
if ((val = gst_structure_get_value (allowed_s, "pixel-aspect-ratio")))
gst_structure_set_value (s, "pixel-aspect-ratio", val);
if ((val = gst_structure_get_value (allowed_s, "profile"))) {
gboolean has_420 = FALSE;
gboolean has_422 = FALSE;
gboolean has_444 = FALSE;
if (G_VALUE_HOLDS_STRING (val)) {
check_formats (g_value_get_string (val), &has_420, &has_422,
&has_444);
} else if (GST_VALUE_HOLDS_LIST (val)) {
for (k = 0; k < gst_value_list_get_size (val); k++) {
const GValue *vlist = gst_value_list_get_value (val, k);
if (G_VALUE_HOLDS_STRING (vlist))
check_formats (g_value_get_string (vlist), &has_420, &has_422,
&has_444);
}
}
if (has_444 && has_422 && has_420)
gst_x264_enc_add_x264_chroma_format (s, 0);
else if (has_444)
gst_x264_enc_add_x264_chroma_format (s, X264_CSP_I444);
else if (has_422)
gst_x264_enc_add_x264_chroma_format (s, X264_CSP_I422);
else if (has_420)
gst_x264_enc_add_x264_chroma_format (s, X264_CSP_I420);
}
filter_caps = gst_caps_merge_structure (filter_caps, s);
}
}
fcaps = gst_caps_intersect (filter_caps, supported_incaps);
gst_caps_unref (filter_caps);
gst_caps_unref (supported_incaps); gst_caps_unref (supported_incaps);
return caps; if (filter) {
GST_LOG_OBJECT (enc, "intersecting with %" GST_PTR_FORMAT, filter);
filter_caps = gst_caps_intersect (fcaps, filter);
gst_caps_unref (fcaps);
fcaps = filter_caps;
}
done:
gst_caps_replace (&allowed, NULL);
GST_LOG_OBJECT (enc, "proxy caps %" GST_PTR_FORMAT, fcaps);
return fcaps;
} }
static void static void
@ -1712,15 +1818,19 @@ gst_x264_enc_set_format (GstVideoEncoder * video_enc,
/* FIXME - if libx264 ever adds support for FMO, ASO or redundant slices /* FIXME - if libx264 ever adds support for FMO, ASO or redundant slices
* make sure constrained profile has a separate case which disables * make sure constrained profile has a separate case which disables
* those */ * those */
if (g_str_has_suffix (profile, "-intra")) {
encoder->peer_intra_profile = TRUE;
}
if (!strcmp (profile, "constrained-baseline") || if (!strcmp (profile, "constrained-baseline") ||
!strcmp (profile, "baseline")) { !strcmp (profile, "baseline")) {
encoder->peer_profile = "baseline"; encoder->peer_profile = "baseline";
} else if (!strcmp (profile, "high-10-intra")) { } else if (g_str_has_prefix (profile, "high-10")) {
encoder->peer_intra_profile = TRUE;
encoder->peer_profile = "high10"; encoder->peer_profile = "high10";
} else if (!strcmp (profile, "high-10")) { } else if (g_str_has_prefix (profile, "high-4:2:2")) {
encoder->peer_profile = "high10"; encoder->peer_profile = "high422";
} else if (!strcmp (profile, "high")) { } else if (g_str_has_prefix (profile, "high-4:4:4")) {
encoder->peer_profile = "high444";
} else if (g_str_has_prefix (profile, "high")) {
encoder->peer_profile = "high"; encoder->peer_profile = "high";
} else if (!strcmp (profile, "main")) { } else if (!strcmp (profile, "main")) {
encoder->peer_profile = "main"; encoder->peer_profile = "main";

View File

@ -30,7 +30,7 @@
static GstPad *mysrcpad, *mysinkpad; static GstPad *mysrcpad, *mysinkpad;
#define VIDEO_CAPS_STRING "video/x-raw, " \ #define VIDEO_CAPS_STRING "video/x-raw, " \
"format = (string) I420, " \ "format = (string) { I420, Y42B, Y444 }, " \
"width = (int) 384, " \ "width = (int) 384, " \
"height = (int) 288, " \ "height = (int) 288, " \
"framerate = (fraction) 25/1" "framerate = (fraction) 25/1"
@ -40,7 +40,8 @@ static GstPad *mysrcpad, *mysinkpad;
"height = (int) 288, " \ "height = (int) 288, " \
"framerate = (fraction) 25/1" "framerate = (fraction) 25/1"
static GstElement * static GstElement *
setup_x264enc (const gchar * profile, const gchar * stream_format) setup_x264enc (const gchar * profile, const gchar * stream_format,
const gchar * input_format)
{ {
GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK, GST_PAD_SINK,
@ -68,6 +69,7 @@ setup_x264enc (const gchar * profile, const gchar * stream_format)
gst_pad_set_active (mysinkpad, TRUE); gst_pad_set_active (mysinkpad, TRUE);
caps = gst_caps_from_string (VIDEO_CAPS_STRING); caps = gst_caps_from_string (VIDEO_CAPS_STRING);
gst_caps_set_simple (caps, "format", G_TYPE_STRING, input_format, NULL);
gst_check_setup_events (mysrcpad, x264enc, caps, GST_FORMAT_TIME); gst_check_setup_events (mysrcpad, x264enc, caps, GST_FORMAT_TIME);
gst_caps_unref (caps); gst_caps_unref (caps);
@ -90,11 +92,12 @@ cleanup_x264enc (GstElement * x264enc)
} }
static void static void
check_caps (GstCaps * caps, gint profile_id) check_caps (GstCaps * caps, const gchar * profile, gint profile_id)
{ {
GstStructure *s; GstStructure *s;
const GValue *sf, *avcc; const GValue *sf, *avcc, *pf;
const gchar *stream_format; const gchar *stream_format;
const gchar *caps_profile;
fail_unless (caps != NULL); fail_unless (caps != NULL);
@ -125,10 +128,18 @@ check_caps (GstCaps * caps, gint profile_id)
} else { } else {
fail_if (TRUE, "unexpected stream-format in caps: %s", stream_format); fail_if (TRUE, "unexpected stream-format in caps: %s", stream_format);
} }
pf = gst_structure_get_value (s, "profile");
fail_unless (pf != NULL);
fail_unless (G_VALUE_HOLDS_STRING (pf));
caps_profile = g_value_get_string (pf);
fail_unless (caps_profile != NULL);
fail_unless (!strcmp (caps_profile, profile));
} }
static void static void
test_video_profile (const gchar * profile, gint profile_id) test_video_profile (const gchar * profile, gint profile_id,
const gchar * input_format)
{ {
GstElement *x264enc; GstElement *x264enc;
GstBuffer *inbuffer, *outbuffer; GstBuffer *inbuffer, *outbuffer;
@ -138,13 +149,19 @@ test_video_profile (const gchar * profile, gint profile_id)
gsize size; gsize size;
int i, num_buffers; int i, num_buffers;
x264enc = setup_x264enc (profile, "avc"); x264enc = setup_x264enc (profile, "avc", input_format);
fail_unless (gst_element_set_state (x264enc, fail_unless (gst_element_set_state (x264enc,
GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS,
"could not set to playing"); "could not set to playing");
/* corresponds to I420 buffer for the size mentioned in the caps */ /* corresponds to I420 buffer for the size mentioned in the caps */
if (!strcmp (input_format, "I420"))
inbuffer = gst_buffer_new_and_alloc (384 * 288 * 3 / 2); inbuffer = gst_buffer_new_and_alloc (384 * 288 * 3 / 2);
else if (!strcmp (input_format, "Y42B"))
inbuffer = gst_buffer_new_and_alloc (384 * 288 * 2);
else if (!strcmp (input_format, "Y444"))
inbuffer = gst_buffer_new_and_alloc (384 * 288 * 3);
/* makes valgrind's memcheck happier */ /* makes valgrind's memcheck happier */
gst_buffer_memset (inbuffer, 0, 0, -1); gst_buffer_memset (inbuffer, 0, 0, -1);
GST_BUFFER_TIMESTAMP (inbuffer) = 0; GST_BUFFER_TIMESTAMP (inbuffer) = 0;
@ -162,7 +179,7 @@ test_video_profile (const gchar * profile, gint profile_id)
GstCaps *outcaps; GstCaps *outcaps;
outcaps = gst_pad_get_current_caps (mysinkpad); outcaps = gst_pad_get_current_caps (mysinkpad);
check_caps (outcaps, profile_id); check_caps (outcaps, profile, profile_id);
gst_caps_unref (outcaps); gst_caps_unref (outcaps);
} }
@ -237,25 +254,41 @@ test_video_profile (const gchar * profile, gint profile_id)
GST_START_TEST (test_video_baseline) GST_START_TEST (test_video_baseline)
{ {
test_video_profile ("constrained-baseline", 0x42); test_video_profile ("constrained-baseline", 0x42, "I420");
} }
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_video_main) GST_START_TEST (test_video_main)
{ {
test_video_profile ("main", 0x4d); test_video_profile ("main", 0x4d, "I420");
} }
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_video_high) GST_START_TEST (test_video_high)
{ {
test_video_profile ("high", 0x64); test_video_profile ("high", 0x64, "I420");
} }
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_video_high422)
{
test_video_profile ("high-4:2:2", 0x7A, "Y42B");
}
GST_END_TEST;
GST_START_TEST (test_video_high444)
{
test_video_profile ("high-4:4:4", 0xF4, "Y444");
}
GST_END_TEST;
Suite * Suite *
x264enc_suite (void) x264enc_suite (void)
{ {
@ -266,6 +299,8 @@ x264enc_suite (void)
tcase_add_test (tc_chain, test_video_baseline); tcase_add_test (tc_chain, test_video_baseline);
tcase_add_test (tc_chain, test_video_main); tcase_add_test (tc_chain, test_video_main);
tcase_add_test (tc_chain, test_video_high); tcase_add_test (tc_chain, test_video_high);
tcase_add_test (tc_chain, test_video_high422);
tcase_add_test (tc_chain, test_video_high444);
return s; return s;
} }