• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * GStreamer
3  * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.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/videooverlay.h>
27 #include <gst/video/gstvideosink.h>
28 #include <windows.h>
29 #include <string.h>
30 
31 static GMainLoop *loop = NULL;
32 static GMainLoop *pipeline_loop = NULL;
33 static gboolean visible = FALSE;
34 static gboolean test_reuse = FALSE;
35 static HWND hwnd = NULL;
36 static gboolean test_fullscreen = FALSE;
37 static gboolean fullscreen = FALSE;
38 static gchar *video_sink = NULL;
39 static GstElement *sink = NULL;
40 static gboolean run_thread = FALSE;
41 
42 static LONG prev_style = 0;
43 static RECT prev_rect = { 0, };
44 
45 static gint x = 0;
46 static gint y = 0;
47 static gint width = 320;
48 static gint height = 240;
49 
50 typedef struct
51 {
52   GThread *thread;
53   HANDLE event_handle;
54   HANDLE console_handle;
55   gboolean closing;
56   GMutex lock;
57 } Win32KeyHandler;
58 
59 static Win32KeyHandler *win32_key_handler = NULL;
60 
61 #define DEFAULT_VIDEO_SINK "d3d11videosink"
62 
63 static gboolean
get_monitor_size(RECT * rect)64 get_monitor_size (RECT * rect)
65 {
66   HMONITOR monitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTONEAREST);
67   MONITORINFOEX monitor_info;
68   DEVMODE dev_mode;
69 
70   monitor_info.cbSize = sizeof (monitor_info);
71   if (!GetMonitorInfo (monitor, (LPMONITORINFO) & monitor_info)) {
72     return FALSE;
73   }
74 
75   dev_mode.dmSize = sizeof (dev_mode);
76   dev_mode.dmDriverExtra = sizeof (POINTL);
77   dev_mode.dmFields = DM_POSITION;
78   if (!EnumDisplaySettings
79       (monitor_info.szDevice, ENUM_CURRENT_SETTINGS, &dev_mode)) {
80     return FALSE;
81   }
82 
83   SetRect (rect, 0, 0, dev_mode.dmPelsWidth, dev_mode.dmPelsHeight);
84 
85   return TRUE;
86 }
87 
88 static void
switch_fullscreen_mode(void)89 switch_fullscreen_mode (void)
90 {
91   if (!hwnd)
92     return;
93 
94   fullscreen = !fullscreen;
95 
96   gst_print ("Full screen %s\n", fullscreen ? "on" : "off");
97 
98   if (!fullscreen) {
99     /* Restore the window's attributes and size */
100     SetWindowLong (hwnd, GWL_STYLE, prev_style);
101 
102     SetWindowPos (hwnd, HWND_NOTOPMOST,
103         prev_rect.left,
104         prev_rect.top,
105         prev_rect.right - prev_rect.left,
106         prev_rect.bottom - prev_rect.top, SWP_FRAMECHANGED | SWP_NOACTIVATE);
107 
108     ShowWindow (hwnd, SW_NORMAL);
109   } else {
110     RECT fullscreen_rect;
111 
112     /* show window before change style */
113     ShowWindow (hwnd, SW_SHOW);
114 
115     /* Save the old window rect so we can restore it when exiting
116      * fullscreen mode */
117     GetWindowRect (hwnd, &prev_rect);
118     prev_style = GetWindowLong (hwnd, GWL_STYLE);
119 
120     if (!get_monitor_size (&fullscreen_rect)) {
121       g_warning ("Couldn't get monitor size");
122 
123       fullscreen = !fullscreen;
124       return;
125     }
126 
127     /* Make the window borderless so that the client area can fill the screen */
128     SetWindowLong (hwnd, GWL_STYLE,
129         prev_style &
130         ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU |
131             WS_THICKFRAME));
132 
133     SetWindowPos (hwnd, HWND_NOTOPMOST,
134         fullscreen_rect.left,
135         fullscreen_rect.top,
136         fullscreen_rect.right,
137         fullscreen_rect.bottom, SWP_FRAMECHANGED | SWP_NOACTIVATE);
138 
139     ShowWindow (hwnd, SW_MAXIMIZE);
140   }
141 }
142 
143 static LRESULT CALLBACK
window_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)144 window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
145 {
146   switch (message) {
147     case WM_DESTROY:
148       hwnd = NULL;
149 
150       if (loop)
151         g_main_loop_quit (loop);
152 
153       if (pipeline_loop)
154         g_main_loop_quit (pipeline_loop);
155 
156       return 0;
157     case WM_KEYUP:
158       if (!test_fullscreen)
159         break;
160 
161       if (wParam == VK_SPACE)
162         switch_fullscreen_mode ();
163       break;
164     case WM_RBUTTONUP:
165       if (!test_fullscreen)
166         break;
167 
168       switch_fullscreen_mode ();
169       break;
170     default:
171       break;
172   }
173 
174   return DefWindowProc (hWnd, message, wParam, lParam);
175 }
176 
177 static gboolean
bus_msg(GstBus * bus,GstMessage * msg,gpointer user_data)178 bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
179 {
180   GstElement *pipeline = GST_ELEMENT (user_data);
181   switch (GST_MESSAGE_TYPE (msg)) {
182     case GST_MESSAGE_ASYNC_DONE:
183       /* make window visible when we have something to show */
184       if (!visible && hwnd) {
185         ShowWindow (hwnd, SW_SHOW);
186         visible = TRUE;
187       }
188 
189       gst_element_set_state (pipeline, GST_STATE_PLAYING);
190       break;
191     case GST_MESSAGE_ERROR:{
192       GError *err;
193       gchar *dbg;
194 
195       gst_message_parse_error (msg, &err, &dbg);
196       g_printerr ("ERROR %s \n", err->message);
197       if (dbg != NULL)
198         g_printerr ("ERROR debug information: %s\n", dbg);
199       g_clear_error (&err);
200       g_free (dbg);
201       test_reuse = FALSE;
202 
203       g_main_loop_quit (loop);
204       break;
205     }
206     default:
207       break;
208   }
209 
210   return TRUE;
211 }
212 
213 static gboolean
msg_cb(GIOChannel * source,GIOCondition condition,gpointer data)214 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
215 {
216   MSG msg;
217 
218   if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
219     return G_SOURCE_CONTINUE;
220 
221   TranslateMessage (&msg);
222   DispatchMessage (&msg);
223 
224   return G_SOURCE_CONTINUE;
225 }
226 
227 static gboolean
timeout_cb(gpointer user_data)228 timeout_cb (gpointer user_data)
229 {
230   g_main_loop_quit ((GMainLoop *) user_data);
231 
232   return G_SOURCE_REMOVE;
233 }
234 
235 static gpointer
pipeline_runner_func(gpointer user_data)236 pipeline_runner_func (gpointer user_data)
237 {
238   GstElement *pipeline, *src;
239   GstStateChangeReturn sret;
240   gint num_repeat = 0;
241   GMainContext *context = NULL;
242   GMainLoop *this_loop;
243 
244   if (run_thread) {
245     /* We are in runner thread, create our loop */
246     context = g_main_context_new ();
247     pipeline_loop = g_main_loop_new (context, FALSE);
248 
249     g_main_context_push_thread_default (context);
250 
251     this_loop = pipeline_loop;
252   } else {
253     this_loop = loop;
254   }
255 
256   /* prepare the pipeline */
257   pipeline = gst_pipeline_new ("win32-overlay");
258   src = gst_element_factory_make ("videotestsrc", NULL);
259   sink = gst_element_factory_make (video_sink, NULL);
260 
261   if (!sink) {
262     g_printerr ("%s element is not available\n", video_sink);
263     exit (1);
264   }
265 
266   gst_object_ref_sink (sink);
267 
268   gst_bin_add_many (GST_BIN (pipeline), src, sink, NULL);
269   gst_element_link (src, sink);
270 
271   gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_msg, pipeline);
272 
273   do {
274     gst_print ("Running loop %d\n", num_repeat++);
275 
276     gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink),
277         (guintptr) hwnd);
278 
279     sret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
280     if (sret == GST_STATE_CHANGE_FAILURE) {
281       g_printerr ("Pipeline doesn't want to pause\n");
282       break;
283     } else {
284       /* add timer to repeat and reuse pipeline  */
285       if (test_reuse) {
286         GSource *timeout_source = g_timeout_source_new_seconds (3);
287 
288         g_source_set_callback (timeout_source,
289             (GSourceFunc) timeout_cb, this_loop, NULL);
290         g_source_attach (timeout_source, NULL);
291         g_source_unref (timeout_source);
292       }
293 
294       g_main_loop_run (this_loop);
295     }
296     gst_element_set_state (pipeline, GST_STATE_NULL);
297 
298     visible = FALSE;
299   } while (test_reuse);
300 
301   gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
302   gst_object_unref (pipeline);
303 
304   if (run_thread) {
305     g_main_context_pop_thread_default (context);
306     g_main_context_unref (context);
307 
308     g_main_loop_quit (loop);
309     g_main_loop_unref (pipeline_loop);
310   }
311 
312   return NULL;
313 }
314 
315 static void
print_keyboard_help(void)316 print_keyboard_help (void)
317 {
318   /* *INDENT-OFF* */
319   static struct
320   {
321     const gchar *key_desc;
322     const gchar *key_help;
323   } key_controls[] = {
324     {
325       "\342\206\222", "move overlay to right-hand side"}, {
326       "\342\206\220", "move overlay to left-hand side"}, {
327       "\342\206\221", "move overlay to upward"}, {
328       "\342\206\223", "move overlay to downward"}, {
329       ">", "increase overlay width"}, {
330       "<", "decrease overlay width"}, {
331       "+", "increase overlay height"}, {
332       "-", "decrease overlay height"}, {
333       "r", "reset render rectangle"}, {
334       "e", "expose overlay"}, {
335       "k", "show keyboard shortcuts"},
336   };
337   /* *INDENT-ON* */
338 
339   guint i, chars_to_pad, desc_len, max_desc_len = 0;
340 
341   gst_print ("\n\n%s\n\n", "Interactive mode - keyboard controls:");
342 
343   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
344     desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
345     max_desc_len = MAX (max_desc_len, desc_len);
346   }
347   ++max_desc_len;
348 
349   for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
350     chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
351     gst_print ("\t%s", key_controls[i].key_desc);
352     gst_print ("%-*s: ", chars_to_pad, "");
353     gst_print ("%s\n", key_controls[i].key_help);
354   }
355   gst_print ("\n");
356 }
357 
358 static gboolean
win32_kb_source_cb(Win32KeyHandler * handler)359 win32_kb_source_cb (Win32KeyHandler * handler)
360 {
361   HANDLE h_input = handler->console_handle;
362   INPUT_RECORD buffer;
363   DWORD n;
364 
365   if (PeekConsoleInput (h_input, &buffer, 1, &n) && n == 1) {
366     ReadConsoleInput (h_input, &buffer, 1, &n);
367 
368     if (buffer.EventType == KEY_EVENT && buffer.Event.KeyEvent.bKeyDown) {
369       gchar key_val[2] = { 0 };
370 
371       switch (buffer.Event.KeyEvent.wVirtualKeyCode) {
372         case VK_RIGHT:
373           gst_println ("Move xpos to %d", x++);
374           gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
375               x, y, width, height);
376           break;
377         case VK_LEFT:
378           gst_println ("Move xpos to %d", x--);
379           gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
380               x, y, width, height);
381           break;
382         case VK_UP:
383           gst_println ("Move ypos to %d", y--);
384           gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
385               x, y, width, height);
386           break;
387         case VK_DOWN:
388           gst_println ("Move ypos to %d", y++);
389           gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
390               x, y, width, height);
391           break;
392         default:
393           key_val[0] = buffer.Event.KeyEvent.uChar.AsciiChar;
394           switch (key_val[0]) {
395             case '<':
396               gst_println ("Decrease width to %d", width--);
397               gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
398                   x, y, width, height);
399               break;
400             case '>':
401               gst_println ("Increase width to %d", width++);
402               gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
403                   x, y, width, height);
404               break;
405             case '+':
406               gst_println ("Increase height to %d", height++);
407               gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
408                   x, y, width, height);
409               break;
410             case '-':
411               gst_println ("Decrease height to %d", height--);
412               gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
413                   x, y, width, height);
414               break;
415             case 'r':
416               gst_println ("Reset render rectangle by setting -1 width/height");
417               gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (sink),
418                   x, y, -1, -1);
419               break;
420             case 'e':
421               gst_println ("Expose overlay");
422               gst_video_overlay_expose (GST_VIDEO_OVERLAY (sink));
423               break;
424             case 'k':
425               print_keyboard_help ();
426               break;
427             default:
428               break;
429           }
430           break;
431       }
432     }
433   }
434 
435   return G_SOURCE_REMOVE;
436 }
437 
438 static gpointer
win32_kb_thread(gpointer user_data)439 win32_kb_thread (gpointer user_data)
440 {
441   Win32KeyHandler *handler = (Win32KeyHandler *) user_data;
442   HANDLE handles[2];
443 
444   handles[0] = handler->event_handle;
445   handles[1] = handler->console_handle;
446 
447   while (TRUE) {
448     DWORD ret = WaitForMultipleObjects (2, handles, FALSE, INFINITE);
449     static guint i = 0;
450 
451     if (ret == WAIT_FAILED) {
452       g_warning ("WaitForMultipleObject Failed");
453       return NULL;
454     }
455 
456     g_mutex_lock (&handler->lock);
457     if (handler->closing) {
458       g_mutex_unlock (&handler->lock);
459 
460       return NULL;
461     }
462     g_mutex_unlock (&handler->lock);
463 
464     g_idle_add ((GSourceFunc) win32_kb_source_cb, handler);
465   }
466 
467   return NULL;
468 }
469 
470 gint
main(gint argc,gchar ** argv)471 main (gint argc, gchar ** argv)
472 {
473   WNDCLASSEX wc = { 0, };
474   HINSTANCE hinstance = GetModuleHandle (NULL);
475   GIOChannel *msg_io_channel;
476   GOptionContext *option_ctx;
477   GError *error = NULL;
478   gchar *title = NULL;
479   RECT wr = { 0, 0, 320, 240 };
480   gint exitcode = 0;
481   gboolean ret;
482   GThread *thread = NULL;
483   GOptionEntry options[] = {
484     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
485         "Video sink to use (default is glimagesink)", NULL}
486     ,
487     {"repeat", 0, 0, G_OPTION_ARG_NONE, &test_reuse,
488         "Test reuse video sink element", NULL}
489     ,
490     {"fullscreen", 0, 0, G_OPTION_ARG_NONE, &test_fullscreen,
491         "Test full screen (borderless topmost) mode switching via "
492           "\"SPACE\" key or \"right mouse button\" click", NULL}
493     ,
494     {"run-thread", 0, 0, G_OPTION_ARG_NONE, &run_thread,
495         "Run pipeline from non-window thread", NULL}
496     ,
497     {NULL}
498   };
499 
500   option_ctx = g_option_context_new ("WIN32 video overlay example");
501   g_option_context_add_main_entries (option_ctx, options, NULL);
502   g_option_context_add_group (option_ctx, gst_init_get_option_group ());
503   ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
504   g_option_context_free (option_ctx);
505 
506   if (!ret) {
507     g_printerr ("option parsing failed: %s\n", error->message);
508     g_clear_error (&error);
509     exit (1);
510   }
511 
512   /* prepare window */
513   wc.cbSize = sizeof (WNDCLASSEX);
514   wc.style = CS_HREDRAW | CS_VREDRAW;
515   wc.lpfnWndProc = (WNDPROC) window_proc;
516   wc.hInstance = hinstance;
517   wc.hCursor = LoadCursor (NULL, IDC_ARROW);
518   wc.lpszClassName = "GstWIN32VideoOverlay";
519   RegisterClassEx (&wc);
520 
521   if (!video_sink)
522     video_sink = g_strdup (DEFAULT_VIDEO_SINK);
523 
524   title = g_strdup_printf ("%s - Win32-VideoOverlay", video_sink);
525 
526   AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
527   hwnd = CreateWindowEx (0, wc.lpszClassName,
528       title,
529       WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
530       CW_USEDEFAULT, CW_USEDEFAULT,
531       wr.right - wr.left, wr.bottom - wr.top, (HWND) NULL, (HMENU) NULL,
532       hinstance, NULL);
533 
534   loop = g_main_loop_new (NULL, FALSE);
535   msg_io_channel = g_io_channel_win32_new_messages (0);
536   g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, NULL);
537 
538   {
539     SECURITY_ATTRIBUTES attrs;
540 
541     attrs.nLength = sizeof (SECURITY_ATTRIBUTES);
542     attrs.lpSecurityDescriptor = NULL;
543     attrs.bInheritHandle = FALSE;
544 
545     win32_key_handler = g_new0 (Win32KeyHandler, 1);
546 
547     /* create cancellable event handle */
548     win32_key_handler->event_handle = CreateEvent (&attrs, TRUE, FALSE, NULL);
549     win32_key_handler->console_handle = GetStdHandle (STD_INPUT_HANDLE);
550     g_mutex_init (&win32_key_handler->lock);
551     win32_key_handler->thread =
552         g_thread_new ("key-handler", win32_kb_thread, win32_key_handler);
553   }
554 
555   gst_println ("Press 'k' to see a list of keyboard shortcuts");
556 
557   if (run_thread) {
558     thread = g_thread_new ("pipeline-thread",
559         (GThreadFunc) pipeline_runner_func, NULL);
560     g_main_loop_run (loop);
561   } else {
562     pipeline_runner_func (NULL);
563   }
564 
565 terminate:
566   if (hwnd)
567     DestroyWindow (hwnd);
568 
569   if (win32_key_handler) {
570     g_mutex_lock (&win32_key_handler->lock);
571     win32_key_handler->closing = TRUE;
572     g_mutex_unlock (&win32_key_handler->lock);
573 
574     SetEvent (win32_key_handler->event_handle);
575     g_thread_join (win32_key_handler->thread);
576     CloseHandle (win32_key_handler->event_handle);
577 
578     g_mutex_clear (&win32_key_handler->lock);
579     g_free (win32_key_handler);
580   }
581 
582   gst_clear_object (&sink);
583   g_io_channel_unref (msg_io_channel);
584   g_main_loop_unref (loop);
585   g_free (title);
586   g_free (video_sink);
587 
588   return exitcode;
589 }
590