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/video.h>
27
28 #include <windows.h>
29 #include <string.h>
30 #include "d3d11videosink-kb.h"
31
32 static GMainLoop *loop = NULL;
33 static gboolean visible = FALSE;
34 static gboolean test_reuse = FALSE;
35 static HWND hwnd = NULL;
36
37 typedef struct
38 {
39 GstElement *pipeline;
40 GstElement *sink;
41 gboolean fullscreen;
42 gboolean force_aspect_ratio;
43 } CallbackData;
44
45 static LRESULT CALLBACK
window_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)46 window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
47 {
48 switch (message) {
49 case WM_DESTROY:
50 hwnd = NULL;
51 g_print ("destroy\n");
52 if (loop) {
53 g_main_loop_quit (loop);
54 }
55 return 0;
56 default:
57 break;
58 }
59
60 return DefWindowProc (hWnd, message, wParam, lParam);
61 }
62
63 static void
keyboard_cb(gchar key_input,CallbackData * data)64 keyboard_cb (gchar key_input, CallbackData * data)
65 {
66 gchar key;
67
68 key = g_ascii_tolower (key_input);
69
70 switch (key) {
71 case 'q':
72 case 'Q':
73 gst_element_send_event (data->pipeline, gst_event_new_eos ());
74 if (hwnd)
75 PostMessage (hwnd, WM_CLOSE, 0, 0);
76 else
77 g_main_loop_quit (loop);
78 break;
79 case 27: /* ESC */
80 gst_element_send_event (data->pipeline, gst_event_new_eos ());
81 if (hwnd)
82 PostMessage (hwnd, WM_CLOSE, 0, 0);
83 else
84 g_main_loop_quit (loop);
85 break;
86 case ' ':
87 data->fullscreen = !data->fullscreen;
88 gst_print ("change to %s mode\n", data->fullscreen ?
89 "fullscreen" : "windowed");
90 g_object_set (data->sink, "fullscreen", data->fullscreen, NULL);
91 break;
92 case 'f':
93 data->force_aspect_ratio = !data->force_aspect_ratio;
94 g_object_set (data->sink,
95 "force-aspect-ratio", data->force_aspect_ratio, NULL);
96 break;
97 default:
98 break;
99 }
100 }
101
102 static gboolean
bus_msg(GstBus * bus,GstMessage * msg,CallbackData * data)103 bus_msg (GstBus * bus, GstMessage * msg, CallbackData * data)
104 {
105 GstElement *pipeline = data->pipeline;
106 switch (GST_MESSAGE_TYPE (msg)) {
107 case GST_MESSAGE_ASYNC_DONE:
108 /* make window visible when we have something to show */
109 if (!visible && hwnd) {
110 ShowWindow (hwnd, SW_SHOW);
111 visible = TRUE;
112 }
113
114 gst_element_set_state (pipeline, GST_STATE_PLAYING);
115 break;
116 case GST_MESSAGE_ERROR:{
117 GError *err;
118 gchar *dbg;
119
120 gst_message_parse_error (msg, &err, &dbg);
121 g_printerr ("ERROR %s \n", err->message);
122 if (dbg != NULL)
123 g_printerr ("ERROR debug information: %s\n", dbg);
124 g_clear_error (&err);
125 g_free (dbg);
126 test_reuse = FALSE;
127
128 g_main_loop_quit (loop);
129 break;
130 }
131 case GST_MESSAGE_ELEMENT:
132 {
133 GstNavigationMessageType mtype = gst_navigation_message_get_type (msg);
134 if (mtype == GST_NAVIGATION_MESSAGE_EVENT) {
135 GstEvent *ev = NULL;
136
137 if (gst_navigation_message_parse_event (msg, &ev)) {
138 GstNavigationEventType e_type = gst_navigation_event_get_type (ev);
139 if (e_type == GST_NAVIGATION_EVENT_KEY_PRESS) {
140 const gchar *key;
141
142 if (gst_navigation_event_parse_key_event (ev, &key)) {
143 if (strcmp (key, "space") == 0 || strcmp (key, "Space") == 0) {
144 keyboard_cb (' ', data);
145 } else {
146 keyboard_cb (key[0], data);
147 }
148 }
149 }
150 }
151 if (ev)
152 gst_event_unref (ev);
153 }
154 break;
155 }
156 default:
157 break;
158 }
159
160 return TRUE;
161 }
162
163 static gboolean
msg_cb(GIOChannel * source,GIOCondition condition,gpointer data)164 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
165 {
166 MSG msg;
167
168 if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
169 return G_SOURCE_CONTINUE;
170
171 TranslateMessage (&msg);
172 DispatchMessage (&msg);
173
174 return G_SOURCE_CONTINUE;
175 }
176
177 static gboolean
timeout_cb(gpointer user_data)178 timeout_cb (gpointer user_data)
179 {
180 g_main_loop_quit ((GMainLoop *) user_data);
181
182 return G_SOURCE_REMOVE;
183 }
184
185 static void
print_keyboard_help(void)186 print_keyboard_help (void)
187 {
188 static struct
189 {
190 const gchar *key_desc;
191 const gchar *key_help;
192 } key_controls[] = {
193 {
194 "q or ESC", "Quit"}, {
195 "SPACE", "Toggle fullscreen mode"}, {
196 "f", "Toggle force-aspect-ratio"}
197 };
198 guint i, chars_to_pad, desc_len, max_desc_len = 0;
199
200 gst_print ("\n%s\n", "Keyboard controls:");
201
202 for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
203 desc_len = g_utf8_strlen (key_controls[i].key_desc, -1);
204 max_desc_len = MAX (max_desc_len, desc_len);
205 }
206 ++max_desc_len;
207
208 for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) {
209 chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1);
210 gst_print ("\t%s", key_controls[i].key_desc);
211 gst_print ("%-*s: ", chars_to_pad, "");
212 gst_print ("%s\n", key_controls[i].key_help);
213 }
214 gst_print ("\n");
215 }
216
217 gint
main(gint argc,gchar ** argv)218 main (gint argc, gchar ** argv)
219 {
220 GstElement *pipeline, *src, *sink;
221 GstStateChangeReturn sret;
222 WNDCLASSEX wc = { 0, };
223 HINSTANCE hinstance = GetModuleHandle (NULL);
224 GIOChannel *msg_io_channel = NULL;
225 GOptionContext *option_ctx;
226 GError *error = NULL;
227 RECT wr = { 0, 0, 320, 240 };
228 gint exitcode = 0;
229 gboolean ret;
230 gboolean use_overlay = FALSE;
231 gboolean start_fullscreen = FALSE;
232 GOptionEntry options[] = {
233 {"use-overlay", 0, 0, G_OPTION_ARG_NONE, &use_overlay,
234 "Test reuse video sink element", NULL}
235 ,
236 {"repeat", 0, 0, G_OPTION_ARG_NONE, &test_reuse,
237 "Test reuse video sink element", NULL}
238 ,
239 {"start-fullscreen", 0, 0, G_OPTION_ARG_NONE, &start_fullscreen,
240 "Run pipeline in fullscreen mode", NULL}
241 ,
242 {NULL}
243 };
244 gint num_repeat = 0;
245 CallbackData cb_data = { 0, };
246
247 option_ctx = g_option_context_new ("WIN32 video overlay example");
248 g_option_context_add_main_entries (option_ctx, options, NULL);
249 g_option_context_add_group (option_ctx, gst_init_get_option_group ());
250 ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
251 g_option_context_free (option_ctx);
252
253 if (!ret) {
254 g_printerr ("option parsing failed: %s\n", error->message);
255 g_clear_error (&error);
256 exit (1);
257 }
258
259 print_keyboard_help ();
260
261 loop = g_main_loop_new (NULL, FALSE);
262
263 if (use_overlay) {
264 /* prepare window */
265 wc.cbSize = sizeof (WNDCLASSEX);
266 wc.style = CS_HREDRAW | CS_VREDRAW;
267 wc.lpfnWndProc = (WNDPROC) window_proc;
268 wc.hInstance = hinstance;
269 wc.hCursor = LoadCursor (NULL, IDC_ARROW);
270 wc.lpszClassName = "GstD3D11VideoSinkExample";
271 RegisterClassEx (&wc);
272
273 AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
274
275 hwnd = CreateWindowEx (0, wc.lpszClassName, "GstD3D11VideoSinkExample",
276 WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
277 CW_USEDEFAULT, CW_USEDEFAULT,
278 wr.right - wr.left, wr.bottom - wr.top, (HWND) NULL, (HMENU) NULL,
279 hinstance, NULL);
280
281 msg_io_channel = g_io_channel_win32_new_messages (0);
282 g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, NULL);
283 }
284
285 /* prepare the pipeline */
286 pipeline = gst_pipeline_new ("d3d11videosink-pipeline");
287 src = gst_element_factory_make ("videotestsrc", NULL);
288 sink = gst_element_factory_make ("d3d11videosink", NULL);
289
290 cb_data.fullscreen = start_fullscreen;
291 cb_data.force_aspect_ratio = TRUE;
292 cb_data.pipeline = pipeline;
293 cb_data.sink = sink;
294
295 g_object_set (sink, "fullscreen-toggle-mode", 0x2 | 0x4, NULL);
296
297 gst_bin_add_many (GST_BIN (pipeline), src, sink, NULL);
298 gst_element_link (src, sink);
299
300 gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), (GstBusFunc) bus_msg,
301 &cb_data);
302
303 gst_d3d11_video_sink_kb_set_key_handler ((GstD3D11VideoSinkKbFunc)
304 keyboard_cb, &cb_data);
305
306 if (start_fullscreen)
307 g_object_set (sink, "fullscreen", TRUE, NULL);
308
309 do {
310 gst_print ("Running loop %d\n", num_repeat++);
311
312 if (use_overlay) {
313 gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (sink),
314 (guintptr) hwnd);
315 }
316
317 sret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
318 if (sret == GST_STATE_CHANGE_FAILURE) {
319 g_printerr ("Pipeline doesn't want to pause\n");
320 break;
321 } else {
322 /* add timer to repeat and reuse pipeline */
323 if (test_reuse) {
324 GSource *timeout_source = g_timeout_source_new_seconds (3);
325
326 g_source_set_callback (timeout_source,
327 (GSourceFunc) timeout_cb, loop, NULL);
328 g_source_attach (timeout_source, NULL);
329 g_source_unref (timeout_source);
330 }
331
332 g_main_loop_run (loop);
333 }
334 gst_element_set_state (pipeline, GST_STATE_NULL);
335
336 visible = FALSE;
337 } while (test_reuse);
338
339 gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline));
340
341 terminate:
342 if (hwnd)
343 DestroyWindow (hwnd);
344
345 gst_object_unref (pipeline);
346 if (msg_io_channel)
347 g_io_channel_unref (msg_io_channel);
348 g_main_loop_unref (loop);
349
350 return exitcode;
351 }
352