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