1 /* GStreamer
2 * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <gst/gst.h>
25 #include <gst/video/video.h>
26
27 #include "nvcodec.h"
28
29 #define DEFAULT_VIDEO_SINK "autovideosink"
30
31 static GMainLoop *loop = NULL;
32 static gint width = 320;
33 static gint height = 240;
34 static gint bitrate = 2000;
35
36 typedef struct
37 {
38 GstElement *pipeline;
39 GstElement *capsfilter;
40 GstElement *nvenc;
41 gulong probe_id;
42
43 gint prev_width;
44 gint prev_height;
45 } TestCallbackData;
46
47 static void
restore_terminal(void)48 restore_terminal (void)
49 {
50 gst_nvcodec_kb_set_key_handler (NULL, NULL);
51 }
52
53 static void
print_keyboard_help(void)54 print_keyboard_help (void)
55 {
56 static struct
57 {
58 const gchar *key_desc;
59 const gchar *key_help;
60 } key_controls[] = {
61 {
62 "q or ESC", "Quit"}, {
63 "right arrow", "Increase Width"}, {
64 "left arrow", "Decrease Width"}, {
65 "up arrow", "Increase Height"}, {
66 "down arrow", "Decrease Height"}, {
67 ">", "Increase encoding bitrate by 100 kbit/sec"}, {
68 "<", "Decrease encoding bitrate by 100 kbit/sec"}, {
69 "k", "show keyboard shortcuts"}
70 };
71 guint i, chars_to_pad, desc_len, max_desc_len = 0;
72
73 g_print ("\n\n%s\n\n", "Keyboard controls:");
74
75 for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
76 desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
77 max_desc_len = MAX (max_desc_len, desc_len);
78 }
79 ++max_desc_len;
80
81 for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
82 chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
83 g_print ("\t%s", key_controls[i].key_desc);
84 g_print ("%-*s: ", chars_to_pad, "");
85 g_print ("%s\n", key_controls[i].key_help);
86 }
87 g_print ("\n");
88 }
89
90 static void
keyboard_cb(const gchar * key_input,gpointer user_data)91 keyboard_cb (const gchar * key_input, gpointer user_data)
92 {
93 TestCallbackData *data = (TestCallbackData *) user_data;
94 gchar key = '\0';
95
96 /* only want to switch/case on single char, not first char of string */
97 if (key_input[0] != '\0' && key_input[1] == '\0')
98 key = g_ascii_tolower (key_input[0]);
99
100 switch (key) {
101 case 'k':
102 print_keyboard_help ();
103 break;
104 case 'q':
105 case 'Q':
106 gst_element_send_event (data->pipeline, gst_event_new_eos ());
107 g_main_loop_quit (loop);
108 break;
109 case 27: /* ESC */
110 if (key_input[1] == '\0') {
111 gst_element_send_event (data->pipeline, gst_event_new_eos ());
112 g_main_loop_quit (loop);
113 }
114 break;
115 case '>':
116 bitrate += 100;
117 bitrate = MIN (bitrate, 2048000);
118 g_print ("Increase encoding bitrate to %d\n", bitrate);
119 g_object_set (G_OBJECT (data->nvenc), "bitrate", bitrate, NULL);
120 break;
121 case '<':
122 bitrate -= 100;
123 bitrate = MAX (bitrate, 100);
124 g_print ("Decrease encoding bitrate to %d\n", bitrate);
125 g_object_set (G_OBJECT (data->nvenc), "bitrate", bitrate, NULL);
126 break;
127 default:{
128 if (strcmp (key_input, GST_NVCODEC_KB_ARROW_RIGHT) == 0) {
129 g_print ("Increase width to %d\n", ++width);
130 } else if (strcmp (key_input, GST_NVCODEC_KB_ARROW_LEFT) == 0) {
131 g_print ("Decrease width to %d\n", --width);
132 } else if (strcmp (key_input, GST_NVCODEC_KB_ARROW_UP) == 0) {
133 g_print ("Increase height to %d\n", ++height);
134 } else if (strcmp (key_input, GST_NVCODEC_KB_ARROW_DOWN) == 0) {
135 g_print ("Decrease height to %d\n", --height);
136 }
137
138 break;
139 }
140 }
141 }
142
143 static gboolean
bus_msg(GstBus * bus,GstMessage * msg,gpointer user_data)144 bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
145 {
146 TestCallbackData *data = (TestCallbackData *) user_data;
147 GstElement *pipeline = data->pipeline;
148
149 switch (GST_MESSAGE_TYPE (msg)) {
150 case GST_MESSAGE_STATE_CHANGED:
151 if (GST_MESSAGE_SRC (msg) == GST_OBJECT_CAST (pipeline)) {
152 gchar *state_transition_name;
153 GstState old, new, pending;
154
155 gst_message_parse_state_changed (msg, &old, &new, &pending);
156
157 state_transition_name = g_strdup_printf ("%s_%s",
158 gst_element_state_get_name (old), gst_element_state_get_name (new));
159
160 /* dump graph for (some) pipeline state changes */
161 {
162 gchar *dump_name = g_strconcat ("nvcodec.", state_transition_name,
163 NULL);
164 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
165 GST_DEBUG_GRAPH_SHOW_ALL, dump_name);
166 g_free (dump_name);
167 }
168 g_free (state_transition_name);
169 }
170 break;
171 case GST_MESSAGE_ERROR:{
172 GError *err;
173 gchar *dbg;
174
175 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
176 GST_DEBUG_GRAPH_SHOW_ALL, "nvcodec.error");
177
178 gst_message_parse_error (msg, &err, &dbg);
179 g_printerr ("ERROR %s \n", err->message);
180 if (dbg != NULL)
181 g_printerr ("ERROR debug information: %s\n", dbg);
182 g_clear_error (&err);
183 g_free (dbg);
184
185 g_main_loop_quit (loop);
186 break;
187 }
188 case GST_MESSAGE_ELEMENT:
189 {
190 GstNavigationMessageType mtype = gst_navigation_message_get_type (msg);
191 if (mtype == GST_NAVIGATION_MESSAGE_EVENT) {
192 GstEvent *ev = NULL;
193
194 if (gst_navigation_message_parse_event (msg, &ev)) {
195 GstNavigationEventType e_type = gst_navigation_event_get_type (ev);
196 if (e_type == GST_NAVIGATION_EVENT_KEY_PRESS) {
197 const gchar *key;
198
199 if (gst_navigation_event_parse_key_event (ev, &key)) {
200 GST_INFO ("Key press: %s", key);
201
202 if (strcmp (key, "Left") == 0)
203 key = GST_NVCODEC_KB_ARROW_LEFT;
204 else if (strcmp (key, "Right") == 0)
205 key = GST_NVCODEC_KB_ARROW_RIGHT;
206 else if (strcmp (key, "Up") == 0)
207 key = GST_NVCODEC_KB_ARROW_UP;
208 else if (strcmp (key, "Down") == 0)
209 key = GST_NVCODEC_KB_ARROW_DOWN;
210 else if (strlen (key) > 1)
211 break;
212
213 keyboard_cb (key, user_data);
214 }
215 }
216 }
217 if (ev)
218 gst_event_unref (ev);
219 }
220 break;
221 }
222 default:
223 break;
224 }
225
226 return TRUE;
227 }
228
229 static gboolean
check_nvcodec_available(void)230 check_nvcodec_available (void)
231 {
232 gboolean ret = TRUE;
233 GstElement *elem;
234
235 elem = gst_element_factory_make ("nvh264enc", NULL);
236 if (!elem) {
237 GST_WARNING ("nvh264enc is not available, possibly driver load failure");
238 return FALSE;
239 }
240
241 /* GST_STATE_READY is meaning that driver could be loaded */
242 if (gst_element_set_state (elem,
243 GST_STATE_PAUSED) != GST_STATE_CHANGE_SUCCESS) {
244 GST_WARNING ("cannot open device");
245 ret = FALSE;
246 }
247
248 gst_element_set_state (elem, GST_STATE_NULL);
249 gst_object_unref (elem);
250
251 if (ret) {
252 elem = gst_element_factory_make ("nvh264dec", NULL);
253 if (!elem) {
254 GST_WARNING ("nvh264dec is not available, possibly driver load failure");
255 return FALSE;
256 }
257
258 /* GST_STATE_READY is meaning that driver could be loaded */
259 if (gst_element_set_state (elem,
260 GST_STATE_PAUSED) != GST_STATE_CHANGE_SUCCESS) {
261 GST_WARNING ("cannot open device");
262 ret = FALSE;
263 }
264
265 gst_element_set_state (elem, GST_STATE_NULL);
266 gst_object_unref (elem);
267 }
268
269 return ret;
270 }
271
272 static GstPadProbeReturn
resolution_change_probe(GstPad * pad,GstPadProbeInfo * info,gpointer user_data)273 resolution_change_probe (GstPad * pad, GstPadProbeInfo * info,
274 gpointer user_data)
275 {
276 GstPadProbeReturn ret = GST_PAD_PROBE_OK;
277 TestCallbackData *data = (TestCallbackData *) user_data;
278
279 if (GST_IS_BUFFER (GST_PAD_PROBE_INFO_DATA (info))) {
280 GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);
281 GstPad *peer = gst_pad_get_peer (pad);
282 GstFlowReturn flow_ret = GST_FLOW_OK;
283
284 ret = GST_PAD_PROBE_HANDLED;
285
286 if (peer) {
287 flow_ret = gst_pad_chain (peer, buffer);
288
289 if (flow_ret != GST_FLOW_OK) {
290 gst_pad_remove_probe (pad, data->probe_id);
291 data->probe_id = 0;
292 } else {
293 if (data->prev_width != width || data->prev_height != height) {
294 GstCaps *caps = NULL;
295 gint next_width, next_height;
296
297 next_width = width;
298 next_height = height;
299
300 g_object_get (data->capsfilter, "caps", &caps, NULL);
301 caps = gst_caps_make_writable (caps);
302 gst_caps_set_simple (caps,
303 "width", G_TYPE_INT, next_width, "height", G_TYPE_INT,
304 next_height, NULL);
305 g_object_set (data->capsfilter, "caps", caps, NULL);
306 gst_caps_unref (caps);
307
308 data->prev_width = next_width;
309 data->prev_height = next_height;
310 }
311 }
312 }
313 }
314
315 return ret;
316 }
317
318 gint
main(gint argc,gchar ** argv)319 main (gint argc, gchar ** argv)
320 {
321 GstElement *pipeline, *src, *convert, *capsfilter, *queue, *sink, *parse;
322 GstElement *enc, *dec;
323 GstStateChangeReturn sret;
324 GError *error = NULL;
325 gboolean use_gl = FALSE;
326 gint exitcode = 1;
327 GOptionContext *option_ctx;
328 GstCaps *caps;
329 TestCallbackData data = { 0, };
330 GstPad *pad;
331
332 GOptionEntry options[] = {
333 {"use-gl", 0, 0, G_OPTION_ARG_NONE, &use_gl,
334 "Use OpenGL memory as input to the nvenc", NULL}
335 ,
336 {NULL}
337 };
338
339 option_ctx = g_option_context_new ("nvcodec dynamic reconfigure example");
340 g_option_context_add_main_entries (option_ctx, options, NULL);
341 g_option_context_set_help_enabled (option_ctx, TRUE);
342 if (!g_option_context_parse (option_ctx, &argc, &argv, &error)) {
343 g_printerr ("option parsing failed: %s\n", error->message);
344 g_clear_error (&error);
345 exit (1);
346 }
347
348 g_option_context_free (option_ctx);
349 gst_init (NULL, NULL);
350
351 if (!check_nvcodec_available ()) {
352 g_printerr ("Cannot load nvcodec plugin");
353 exit (1);
354 }
355
356 /* prepare the pipeline */
357 loop = g_main_loop_new (NULL, FALSE);
358
359 pipeline = gst_pipeline_new ("nvcodec-example");
360
361 if (use_gl)
362 src = gst_element_factory_make ("gltestsrc", NULL);
363 else
364 src = gst_element_factory_make ("videotestsrc", NULL);
365
366 if (!src) {
367 g_printerr ("%s element is not available\n",
368 use_gl ? "gltestsrc" : "videotestsrc");
369 goto terminate;
370 }
371
372 gst_bin_add (GST_BIN (pipeline), src);
373
374 if (use_gl)
375 convert = gst_element_factory_make ("glcolorconvert", NULL);
376 else
377 convert = gst_element_factory_make ("videoconvert", NULL);
378
379 if (!convert) {
380 g_printerr ("%s element is not available\n",
381 use_gl ? "glcolorconvert" : "videoconvert");
382 goto terminate;
383 }
384
385 gst_bin_add (GST_BIN (pipeline), convert);
386
387 if (use_gl) {
388 sink = gst_element_factory_make ("glimagesink", NULL);
389 } else {
390 sink = gst_element_factory_make (DEFAULT_VIDEO_SINK, NULL);
391 }
392
393 if (!sink) {
394 g_printerr ("%s element is not available\n",
395 use_gl ? "glimagesink" : DEFAULT_VIDEO_SINK);
396 goto terminate;
397 }
398
399 gst_bin_add (GST_BIN (pipeline), sink);
400
401 capsfilter = gst_element_factory_make ("capsfilter", NULL);
402 queue = gst_element_factory_make ("queue", NULL);
403 enc = gst_element_factory_make ("nvh264enc", NULL);
404 parse = gst_element_factory_make ("h264parse", NULL);
405
406 /* vbr with target bitrate */
407 g_object_set (G_OBJECT (enc), "rc-mode", 4, "bitrate", bitrate, NULL);
408
409 dec = gst_element_factory_make ("nvh264dec", NULL);
410
411 gst_bin_add_many (GST_BIN (pipeline), capsfilter, queue, enc, parse, dec,
412 NULL);
413
414 if (!use_gl) {
415 GstElement *sink_convert = gst_element_factory_make ("videoconvert", NULL);
416 gst_bin_add (GST_BIN (pipeline), sink_convert);
417
418 gst_element_link_many (src,
419 convert, capsfilter, enc, parse, dec, queue, sink_convert, sink, NULL);
420 } else {
421 gst_element_link_many (src,
422 convert, capsfilter, enc, parse, dec, queue, sink, NULL);
423 }
424
425 caps = gst_caps_from_string ("video/x-raw,format=NV12");
426
427 if (use_gl) {
428 gst_caps_set_features_simple (caps,
429 gst_caps_features_from_string ("memory:GLMemory"));
430 }
431
432 g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
433 gst_caps_unref (caps);
434
435 data.pipeline = pipeline;
436 data.capsfilter = capsfilter;
437 data.nvenc = enc;
438
439 pad = gst_element_get_static_pad (convert, "src");
440 data.probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
441 (GstPadProbeCallback) resolution_change_probe, &data, NULL);
442 gst_object_unref (pad);
443 data.prev_width = width;
444 data.prev_height = height;
445
446 gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_msg, &data);
447
448 if (gst_nvcodec_kb_set_key_handler (keyboard_cb, &data)) {
449 g_print ("Press 'k' to see a list of keyboard shortcuts.\n");
450 atexit (restore_terminal);
451 }
452
453 /* run the pipeline */
454 sret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
455 if (sret == GST_STATE_CHANGE_FAILURE) {
456 g_printerr ("Pipeline doesn't want to playing\n");
457 } else {
458 g_main_loop_run (loop);
459 }
460
461 gst_element_set_state (pipeline, GST_STATE_NULL);
462 gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
463
464 exitcode = 0;
465
466 terminate:
467
468 gst_object_unref (pipeline);
469 g_main_loop_unref (loop);
470
471 return exitcode;
472 }
473