example: vaenc-dynamic-reconfigure: Support H265.
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / tests / examples / va / vaenc-dynamic-reconfigure.c
1 /* GStreamer
2  * Copyright (C) 2022 Seungha Yang <seungha@centricular.com>
3  *               2022 Víctor Jáquez <vjaquez@igalia.com>
4  *
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.
9  *
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.
14  *
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.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <gst/gst.h>
26 #include <gst/video/video.h>
27 #include <stdlib.h>
28 #include "../key-handler.h"
29
30 static GMainLoop *loop = NULL;
31 static gint width = 640;
32 static gint height = 480;
33 static guint rc_ctrl = 0;
34
35 G_LOCK_DEFINE_STATIC (input_lock);
36
37 typedef struct
38 {
39   GstElement *pipeline;
40   GstElement *capsfilter;
41   GstElement *encoder;
42   gulong probe_id;
43
44   gint prev_width;
45   gint prev_height;
46 } TestCallbackData;
47
48 static gboolean
49 bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
50 {
51   switch (GST_MESSAGE_TYPE (msg)) {
52     case GST_MESSAGE_ERROR:{
53       GError *err;
54       gchar *dbg;
55
56       gst_message_parse_error (msg, &err, &dbg);
57       gst_printerrln ("ERROR %s", err->message);
58       if (dbg != NULL)
59         gst_printerrln ("ERROR debug information: %s", dbg);
60       g_clear_error (&err);
61       g_free (dbg);
62
63       g_main_loop_quit (loop);
64       break;
65     }
66     case GST_MESSAGE_PROPERTY_NOTIFY:{
67       const GValue *val;
68       const gchar *name;
69       GstObject *obj;
70       gchar *val_str = NULL;
71       gchar *obj_name;
72
73       gst_message_parse_property_notify (msg, &obj, &name, &val);
74
75       if (!GST_IS_VIDEO_ENCODER (obj))
76         break;
77
78       obj_name = gst_object_get_name (GST_OBJECT (obj));
79       if (val) {
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);
87         else
88           val_str = g_strdup ("(unknown type)");
89       } else {
90         val_str = g_strdup ("(no value)");
91       }
92
93       gst_println ("%s: %s = %s", obj_name, name, val_str);
94       g_free (obj_name);
95       g_free (val_str);
96       break;
97     }
98     default:
99       break;
100   }
101
102   return TRUE;
103 }
104
105 static void
106 loop_rate_control (GstElement * encoder)
107 {
108   GParamSpec *pspec =
109       g_object_class_find_property (G_OBJECT_GET_CLASS (encoder),
110       "rate-control");
111   GEnumClass *enum_class;
112   gint i, default_value;
113
114   if (!pspec)
115     return;
116
117   enum_class = G_PARAM_SPEC_ENUM (pspec)->enum_class;
118
119   if (rc_ctrl == 0) {
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) {
123         rc_ctrl = i;
124         break;
125       }
126     }
127   }
128
129   i = ++rc_ctrl % enum_class->n_values;
130   g_object_set (encoder, "rate-control", enum_class->values[i].value, NULL);
131 }
132
133 static GstPadProbeReturn
134 resolution_change_probe (GstPad * pad, GstPadProbeInfo * info,
135     gpointer user_data)
136 {
137   GstPadProbeReturn ret = GST_PAD_PROBE_OK;
138   TestCallbackData *data = (TestCallbackData *) user_data;
139
140   G_LOCK (input_lock);
141
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;
146
147     ret = GST_PAD_PROBE_HANDLED;
148
149     if (peer) {
150       flow_ret = gst_pad_chain (peer, buffer);
151
152       if (flow_ret != GST_FLOW_OK) {
153         gst_pad_remove_probe (pad, data->probe_id);
154         data->probe_id = 0;
155       } else {
156         if (data->prev_width != width || data->prev_height != height) {
157           GstCaps *caps = NULL;
158           gint next_width, next_height;
159
160           next_width = width;
161           next_height = height;
162
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,
167               next_height, NULL);
168           g_object_set (data->capsfilter, "caps", caps, NULL);
169           gst_caps_unref (caps);
170
171           data->prev_width = next_width;
172           data->prev_height = next_height;
173         }
174       }
175     }
176   }
177
178   G_UNLOCK (input_lock);
179
180   return ret;
181 }
182
183 static void
184 print_keyboard_help (void)
185 {
186   /* *INDENT-OFF* */
187   static struct
188   {
189     const gchar *key_desc;
190     const gchar *key_help;
191   } key_controls[] = {
192     {
193     "q", "Quit"}, {
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"}
212   };
213   /* *INDENT-ON* */
214
215   guint i, chars_to_pad, desc_len, max_desc_len = 0;
216
217   gst_print ("\n\n%s\n\n", "Keyboard controls:");
218
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);
222   }
223   ++max_desc_len;
224
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);
230   }
231   gst_print ("\n");
232 }
233
234 static inline gboolean
235 is_ratectl (GstElement * encoder, guint rc)
236 {
237   guint ratectl = 0;
238
239   g_object_get (encoder, "rate-control", &ratectl, NULL);
240   return (ratectl == rc);
241 }
242
243 static void
244 keyboard_cb (gchar input, gboolean is_ascii, gpointer user_data)
245 {
246   TestCallbackData *data = (TestCallbackData *) user_data;
247
248   G_LOCK (input_lock);
249
250   if (!is_ascii) {
251     switch (input) {
252       case KB_ARROW_UP:
253         height += 2;
254         break;
255       case KB_ARROW_DOWN:
256         height -= 2;
257         height = MAX (height, 16);
258         break;
259       case KB_ARROW_LEFT:
260         width -= 2;
261         width = MAX (width, 16);
262         break;
263       case KB_ARROW_RIGHT:
264         width += 2;
265         break;
266       default:
267         break;
268     }
269   } else {
270     switch (input) {
271       case 'k':
272       case 'K':
273         print_keyboard_help ();
274         break;
275       case 'q':
276       case 'Q':
277         gst_element_send_event (data->pipeline, gst_event_new_eos ());
278         g_main_loop_quit (loop);
279         break;
280       case 'r':
281       case 'R':
282         loop_rate_control (data->encoder);
283         break;
284       case '>':{
285         guint bitrate;
286
287         if (is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
288           break;
289
290         g_object_get (data->encoder, "bitrate", &bitrate, NULL);
291         bitrate += 100;
292         if (bitrate <= 2048000)
293           g_object_set (data->encoder, "bitrate", bitrate, NULL);
294         break;
295       }
296       case '<':{
297         gint bitrate;
298
299         if (is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
300           break;
301
302         g_object_get (data->encoder, "bitrate", &bitrate, NULL);
303         bitrate -= 100;
304         if (bitrate < 0)
305           bitrate = 0;
306         g_object_set (data->encoder, "bitrate", bitrate, NULL);
307         break;
308       }
309       case ']':{
310         guint usage;
311
312         g_object_get (data->encoder, "target-usage", &usage, NULL);
313         usage += 1;
314         if (usage <= 7)
315           g_object_set (data->encoder, "target-usage", usage, NULL);
316         break;
317       }
318       case '[':{
319         guint usage;
320
321         g_object_get (data->encoder, "target-usage", &usage, NULL);
322         usage -= 1;
323         if (usage >= 1)
324           g_object_set (data->encoder, "target-usage", usage, NULL);
325         break;
326       }
327       case '}':{
328         guint target;
329
330         if (!is_ratectl (data->encoder, 0x00000004 /* VA_RC_VBR */ ))
331           break;
332
333         g_object_get (data->encoder, "target-percentage", &target, NULL);
334         target += 10;
335         if (target <= 100)
336           g_object_set (data->encoder, "target-percentage", target, NULL);
337         break;
338       }
339       case '{':{
340         guint target;
341
342         if (!is_ratectl (data->encoder, 0x00000004 /* VA_RC_VBR */ ))
343           break;
344
345         g_object_get (data->encoder, "target-percentage", &target, NULL);
346         target -= 10;
347         if (target >= 50)
348           g_object_set (data->encoder, "target-percentage", target, NULL);
349         break;
350       }
351       case 'I':{
352         guint qpi;
353
354         g_object_get (data->encoder, "qpi", &qpi, NULL);
355         qpi += 1;
356         if (qpi <= 51)
357           g_object_set (data->encoder, "qpi", qpi, NULL);
358         break;
359       }
360       case 'i':{
361         gint qpi;
362
363         g_object_get (data->encoder, "qpi", &qpi, NULL);
364         qpi -= 1;
365         if (qpi >= 0)
366           g_object_set (data->encoder, "qpi", qpi, NULL);
367         break;
368       }
369       case 'P':{
370         guint qpp;
371
372         if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
373           break;
374
375         g_object_get (data->encoder, "qpp", &qpp, NULL);
376         qpp += 1;
377         if (qpp <= 51)
378           g_object_set (data->encoder, "qpp", qpp, NULL);
379         break;
380       }
381       case 'p':{
382         gint qpp;
383
384         if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
385           break;
386
387         g_object_get (data->encoder, "qpp", &qpp, NULL);
388         qpp -= 1;
389         if (qpp >= 0)
390           g_object_set (data->encoder, "qpp", qpp, NULL);
391         break;
392       }
393       case 'B':{
394         guint qpb;
395
396         if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
397           break;
398
399         g_object_get (data->encoder, "qpb", &qpb, NULL);
400         qpb += 1;
401         if (qpb <= 51)
402           g_object_set (data->encoder, "qpb", qpb, NULL);
403         break;
404       }
405       case 'b':{
406         gint qpb;
407
408         if (!is_ratectl (data->encoder, 0x00000010 /* VA_RC_CQP */ ))
409           break;
410
411         g_object_get (data->encoder, "qpb", &qpb, NULL);
412         qpb -= 1;
413         if (qpb >= 0)
414           g_object_set (data->encoder, "qpb", qpb, NULL);
415         break;
416       }
417       default:
418         break;
419     }
420   }
421
422   G_UNLOCK (input_lock);
423 }
424
425 gint
426 main (gint argc, gchar ** argv)
427 {
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;
434   GstCaps *caps;
435   GstPad *pad;
436   TestCallbackData data = { 0, };
437   gchar *codec = NULL;
438   gulong deep_notify_id = 0;
439   guint idx;
440
441   /* *INDENT-OFF* */
442   const GOptionEntry options[] = {
443     {"codec", 'c', 0, G_OPTION_ARG_STRING, &codec,
444         "Codec to test: [ *h264, h265 ]"},
445     {NULL}
446   };
447   const struct {
448     const char *codec;
449     const char *encoder;
450     const char *parser;
451     const char *decoder;
452   } elements_map[] = {
453     { "h264", "vah264enc", "h264parse", "vah264dec" },
454     { "h265", "vah265enc", "h265parse", "vah265dec" },
455   };
456   /* *INDENT-ON* */
457
458 #define MAKE_ELEMENT_AND_ADD(elem, name) G_STMT_START { \
459   GstElement *_elem = gst_element_factory_make (name, NULL); \
460   if (!_elem) { \
461     gst_printerrln ("%s is not available", name); \
462     exit (1); \
463   } \
464   gst_println ("Adding element %s", name); \
465   elem = _elem; \
466   gst_bin_add (GST_BIN (pipeline), elem); \
467 } G_STMT_END
468
469   option_ctx =
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);
477     exit (1);
478   }
479
480   g_option_context_free (option_ctx);
481   gst_init (NULL, NULL);
482
483   if (!codec)
484     codec = g_strdup ("h264");
485
486   for (idx = 0; idx < G_N_ELEMENTS (elements_map); idx++) {
487     if (g_strcmp0 (elements_map[idx].codec, codec) == 0)
488       break;
489   }
490
491   if (idx == G_N_ELEMENTS (elements_map)) {
492     gst_printerrln ("Unsupported codec: %s", codec);
493     exit (1);
494   }
495   g_free (codec);
496
497   pipeline = gst_pipeline_new (NULL);
498
499   MAKE_ELEMENT_AND_ADD (src, "videotestsrc");
500   g_object_set (src, "pattern", 1, NULL);
501
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");
511
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");
515     exit (1);
516   }
517
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);
523
524   g_object_set (convert, "chroma-mode", 3, NULL);
525   g_object_set (convert, "dither", 0, NULL);
526
527   data.pipeline = pipeline;
528   data.capsfilter = capsfilter;
529   data.encoder = enc;
530
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;
537
538   loop = g_main_loop_new (NULL, FALSE);
539
540   deep_notify_id =
541       gst_element_add_property_deep_notify_watch (pipeline, NULL, TRUE);
542
543   gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_msg, &data);
544
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");
549   } else {
550     set_key_handler ((KeyInputCallback) keyboard_cb, &data);
551     g_main_loop_run (loop);
552     unset_key_handler ();
553   }
554
555   if (deep_notify_id != 0)
556     g_signal_handler_disconnect (pipeline, deep_notify_id);
557
558   gst_element_set_state (pipeline, GST_STATE_NULL);
559   gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
560
561   gst_object_unref (pipeline);
562   g_main_loop_unref (loop);
563
564   return 0;
565 }