2 * Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
3 * 2022 Víctor Jáquez <vjaquez@igalia.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
26 #include <gst/video/video.h>
28 #include "../key-handler.h"
30 static GMainLoop *loop = NULL;
31 static gint width = 640;
32 static gint height = 480;
33 static guint rc_ctrl = 0;
35 G_LOCK_DEFINE_STATIC (input_lock);
40 GstElement *capsfilter;
49 bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
51 switch (GST_MESSAGE_TYPE (msg)) {
52 case GST_MESSAGE_ERROR:{
56 gst_message_parse_error (msg, &err, &dbg);
57 gst_printerrln ("ERROR %s", err->message);
59 gst_printerrln ("ERROR debug information: %s", dbg);
63 g_main_loop_quit (loop);
66 case GST_MESSAGE_PROPERTY_NOTIFY:{
70 gchar *val_str = NULL;
73 gst_message_parse_property_notify (msg, &obj, &name, &val);
75 if (!GST_IS_VIDEO_ENCODER (obj))
78 obj_name = gst_object_get_name (GST_OBJECT (obj));
80 if (G_VALUE_HOLDS_STRING (val))
81 val_str = g_value_dup_string (val);
82 else if (G_VALUE_TYPE (val) == GST_TYPE_CAPS)
83 val_str = gst_caps_to_string (g_value_get_boxed (val));
84 else if (G_VALUE_HOLDS_BOOLEAN (val) || G_VALUE_HOLDS_INT (val)
85 || G_VALUE_HOLDS_UINT (val) || G_VALUE_HOLDS_ENUM (val))
86 val_str = gst_value_serialize (val);
88 val_str = g_strdup ("(unknown type)");
90 val_str = g_strdup ("(no value)");
93 gst_println ("%s: %s = %s", obj_name, name, val_str);
106 loop_rate_control (GstElement * encoder)
109 g_object_class_find_property (G_OBJECT_GET_CLASS (encoder),
111 GEnumClass *enum_class;
112 gint i, default_value;
117 enum_class = G_PARAM_SPEC_ENUM (pspec)->enum_class;
120 default_value = G_PARAM_SPEC_ENUM (pspec)->default_value;
121 for (i = 0; i < enum_class->n_values; i++) {
122 if (enum_class->values[i].value == default_value) {
129 i = ++rc_ctrl % enum_class->n_values;
130 g_object_set (encoder, "rate-control", enum_class->values[i].value, NULL);
133 static GstPadProbeReturn
134 resolution_change_probe (GstPad * pad, GstPadProbeInfo * info,
137 GstPadProbeReturn ret = GST_PAD_PROBE_OK;
138 TestCallbackData *data = (TestCallbackData *) user_data;
142 if (GST_IS_BUFFER (GST_PAD_PROBE_INFO_DATA (info))) {
143 GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);
144 GstPad *peer = gst_pad_get_peer (pad);
145 GstFlowReturn flow_ret = GST_FLOW_OK;
147 ret = GST_PAD_PROBE_HANDLED;
150 flow_ret = gst_pad_chain (peer, buffer);
152 if (flow_ret != GST_FLOW_OK) {
153 gst_pad_remove_probe (pad, data->probe_id);
156 if (data->prev_width != width || data->prev_height != height) {
157 GstCaps *caps = NULL;
158 gint next_width, next_height;
161 next_height = height;
163 g_object_get (data->capsfilter, "caps", &caps, NULL);
164 caps = gst_caps_make_writable (caps);
165 gst_caps_set_simple (caps,
166 "width", G_TYPE_INT, next_width, "height", G_TYPE_INT,
168 g_object_set (data->capsfilter, "caps", caps, NULL);
169 gst_caps_unref (caps);
171 data->prev_width = next_width;
172 data->prev_height = next_height;
178 G_UNLOCK (input_lock);
184 print_keyboard_help (void)
189 const gchar *key_desc;
190 const gchar *key_help;
194 "right arrow", "Increase Width"}, {
195 "left arrow", "Decrease Width"}, {
196 "up arrow", "Increase Height"}, {
197 "down arrow", "Decrease Height"}, {
198 "r", "Loop rate control"}, {
199 ">", "Increase bitrate by 100 kbps"}, {
200 "<", "Decrease bitrate by 100 kbps"}, {
201 "]", "Increase target usage"}, {
202 "[", "Decrease target usage"}, {
203 "}", "Increase target percentage by 10% (only in VBR)"}, {
204 "{", "Decrease target percentage by 10% (only in VBR)"}, {
205 "I", "Increase QP-I"}, {
206 "i", "Decrease QP-I"}, {
207 "P", "Increase QP-P (only in CQP)"}, {
208 "p", "Decrease QP-P (only in CQP)"}, {
209 "B", "Increase QP-B (only in CQP)"}, {
210 "b", "Decrease QP-B (only in CQP)"}, {
211 "k", "show keyboard shortcuts"}
215 guint i, chars_to_pad, desc_len, max_desc_len = 0;
217 gst_print ("\n\n%s\n\n", "Keyboard controls:");
219 for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
220 desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
221 max_desc_len = MAX (max_desc_len, desc_len);
225 for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
226 chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
227 gst_print ("\t%s", key_controls[i].key_desc);
228 gst_print ("%-*s: ", chars_to_pad, "");
229 gst_print ("%s\n", key_controls[i].key_help);
234 static inline gboolean
235 is_ratectl (GstElement * encoder, guint rc)
239 g_object_get (encoder, "rate-control", &ratectl, NULL);
240 return (ratectl == rc);
244 keyboard_cb (gchar input, gboolean is_ascii, gpointer user_data)
246 TestCallbackData *data = (TestCallbackData *) user_data;
257 height = MAX (height, 16);
261 width = MAX (width, 16);
273 print_keyboard_help ();
277 gst_element_send_event (data->pipeline, gst_event_new_eos ());
278 g_main_loop_quit (loop);
282 loop_rate_control (data->encoder);
287 if (is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
290 g_object_get (data->encoder, "bitrate", &bitrate, NULL);
292 if (bitrate <= 2048000)
293 g_object_set (data->encoder, "bitrate", bitrate, NULL);
299 if (is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
302 g_object_get (data->encoder, "bitrate", &bitrate, NULL);
306 g_object_set (data->encoder, "bitrate", bitrate, NULL);
312 g_object_get (data->encoder, "target-usage", &usage, NULL);
315 g_object_set (data->encoder, "target-usage", usage, NULL);
321 g_object_get (data->encoder, "target-usage", &usage, NULL);
324 g_object_set (data->encoder, "target-usage", usage, NULL);
330 if (!is_ratectl (data->encoder, 0x00000004 /* VA_RC_VBR */ ))
333 g_object_get (data->encoder, "target-percentage", &target, NULL);
336 g_object_set (data->encoder, "target-percentage", target, NULL);
342 if (!is_ratectl (data->encoder, 0x00000004 /* VA_RC_VBR */ ))
345 g_object_get (data->encoder, "target-percentage", &target, NULL);
348 g_object_set (data->encoder, "target-percentage", target, NULL);
354 g_object_get (data->encoder, "qpi", &qpi, NULL);
357 g_object_set (data->encoder, "qpi", qpi, NULL);
363 g_object_get (data->encoder, "qpi", &qpi, NULL);
366 g_object_set (data->encoder, "qpi", qpi, NULL);
372 if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
375 g_object_get (data->encoder, "qpp", &qpp, NULL);
378 g_object_set (data->encoder, "qpp", qpp, NULL);
384 if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
387 g_object_get (data->encoder, "qpp", &qpp, NULL);
390 g_object_set (data->encoder, "qpp", qpp, NULL);
396 if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
399 g_object_get (data->encoder, "qpb", &qpb, NULL);
402 g_object_set (data->encoder, "qpb", qpb, NULL);
408 if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
411 g_object_get (data->encoder, "qpb", &qpb, NULL);
414 g_object_set (data->encoder, "qpb", qpb, NULL);
422 G_UNLOCK (input_lock);
426 main (gint argc, gchar ** argv)
428 GstElement *pipeline;
429 GstElement *src, *capsfilter, *convert, *enc, *dec, *parser, *vpp, *sink;
430 GstElement *queue0, *queue1;
431 GstStateChangeReturn sret;
432 GError *error = NULL;
433 GOptionContext *option_ctx;
436 TestCallbackData data = { 0, };
438 gulong deep_notify_id = 0;
442 const GOptionEntry options[] = {
443 {"codec", 'c', 0, G_OPTION_ARG_STRING, &codec,
444 "Codec to test: [ *h264, h265 ]"},
453 { "h264", "vah264enc", "h264parse", "vah264dec" },
454 { "h265", "vah265enc", "h265parse", "vah265dec" },
458 #define MAKE_ELEMENT_AND_ADD(elem, name) G_STMT_START { \
459 GstElement *_elem = gst_element_factory_make (name, NULL); \
461 gst_printerrln ("%s is not available", name); \
464 gst_println ("Adding element %s", name); \
466 gst_bin_add (GST_BIN (pipeline), elem); \
470 g_option_context_new ("VA video encoder dynamic reconfigure example");
471 g_option_context_add_main_entries (option_ctx, options, NULL);
472 g_option_context_add_group (option_ctx, gst_init_get_option_group ());
473 g_option_context_set_help_enabled (option_ctx, TRUE);
474 if (!g_option_context_parse (option_ctx, &argc, &argv, &error)) {
475 gst_printerrln ("option parsing failed: %s\n", error->message);
476 g_clear_error (&error);
480 g_option_context_free (option_ctx);
481 gst_init (NULL, NULL);
484 codec = g_strdup ("h264");
486 for (idx = 0; idx < G_N_ELEMENTS (elements_map); idx++) {
487 if (g_strcmp0 (elements_map[idx].codec, codec) == 0)
491 if (idx == G_N_ELEMENTS (elements_map)) {
492 gst_printerrln ("Unsupported codec: %s", codec);
497 pipeline = gst_pipeline_new (NULL);
499 MAKE_ELEMENT_AND_ADD (src, "videotestsrc");
500 g_object_set (src, "pattern", 1, NULL);
502 MAKE_ELEMENT_AND_ADD (capsfilter, "capsfilter");
503 MAKE_ELEMENT_AND_ADD (convert, "videoconvert");
504 MAKE_ELEMENT_AND_ADD (enc, elements_map[idx].encoder);
505 MAKE_ELEMENT_AND_ADD (queue0, "queue");
506 MAKE_ELEMENT_AND_ADD (parser, elements_map[idx].parser);
507 MAKE_ELEMENT_AND_ADD (dec, elements_map[idx].decoder);
508 MAKE_ELEMENT_AND_ADD (vpp, "vapostproc");
509 MAKE_ELEMENT_AND_ADD (queue1, "queue");
510 MAKE_ELEMENT_AND_ADD (sink, "autovideosink");
512 if (!gst_element_link_many (src, capsfilter, convert, enc, queue0,
513 parser, dec, vpp, queue1, sink, NULL)) {
514 gst_printerrln ("Failed to link element");
518 caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT,
519 width, "height", G_TYPE_INT, height,
520 "format", G_TYPE_STRING, "I420", NULL);
521 g_object_set (capsfilter, "caps", caps, NULL);
522 gst_caps_unref (caps);
524 g_object_set (convert, "chroma-mode", 3, NULL);
525 g_object_set (convert, "dither", 0, NULL);
527 data.pipeline = pipeline;
528 data.capsfilter = capsfilter;
531 pad = gst_element_get_static_pad (capsfilter, "src");
532 data.probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
533 resolution_change_probe, &data, NULL);
534 gst_object_unref (pad);
535 data.prev_width = width;
536 data.prev_height = height;
538 loop = g_main_loop_new (NULL, FALSE);
541 gst_element_add_property_deep_notify_watch (pipeline, NULL, TRUE);
543 gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_msg, &data);
545 /* run the pipeline */
546 sret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
547 if (sret == GST_STATE_CHANGE_FAILURE) {
548 gst_printerrln ("Pipeline doesn't want to playing");
550 set_key_handler ((KeyInputCallback) keyboard_cb, &data);
551 g_main_loop_run (loop);
552 unset_key_handler ();
555 if (deep_notify_id != 0)
556 g_signal_handler_disconnect (pipeline, deep_notify_id);
558 gst_element_set_state (pipeline, GST_STATE_NULL);
559 gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
561 gst_object_unref (pipeline);
562 g_main_loop_unref (loop);