• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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