1 /*
2 * GStreamer
3 * Copyright (C) 2020 Seungha Yang <seungha@centricular.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 <windows.h>
28
29 static GMainLoop *loop = NULL;
30 static gboolean visible = FALSE;
31 static HWND hwnd = NULL;
32 static gboolean set_handle_on_request = FALSE;
33 static gboolean test_reuse = FALSE;
34
35 static LRESULT CALLBACK
window_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)36 window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
37 {
38 switch (message) {
39 case WM_DESTROY:
40 hwnd = NULL;
41
42 if (loop) {
43 g_main_loop_quit (loop);
44 }
45 return 0;
46 default:
47 break;
48 }
49
50 return DefWindowProc (hWnd, message, wParam, lParam);
51 }
52
53 static gboolean
msg_cb(GIOChannel * source,GIOCondition condition,gpointer data)54 msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
55 {
56 MSG msg;
57
58 if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
59 return G_SOURCE_CONTINUE;
60
61 TranslateMessage (&msg);
62 DispatchMessage (&msg);
63
64 return G_SOURCE_CONTINUE;
65 }
66
67 static gboolean
bus_msg(GstBus * bus,GstMessage * msg,gpointer user_data)68 bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
69 {
70 GstElement *playbin = GST_ELEMENT (user_data);
71
72 switch (GST_MESSAGE_TYPE (msg)) {
73 case GST_MESSAGE_ASYNC_DONE:
74 /* make window visible when we have something to show */
75 if (!visible && hwnd) {
76 ShowWindow (hwnd, SW_SHOW);
77 visible = TRUE;
78 }
79
80 gst_element_set_state (playbin, GST_STATE_PLAYING);
81 break;
82 case GST_MESSAGE_EOS:
83 gst_println ("End of stream");
84 g_main_loop_quit (loop);
85 break;
86 default:
87 break;
88 }
89
90 return TRUE;
91 }
92
93 static GstBusSyncReply
bus_sync_handler(GstBus * bus,GstMessage * msg,gpointer user_data)94 bus_sync_handler (GstBus * bus, GstMessage * msg, gpointer user_data)
95 {
96 if (set_handle_on_request &&
97 gst_is_video_overlay_prepare_window_handle_message (msg)) {
98 GstVideoOverlay *overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (msg));
99
100 gst_println ("Pipeline needs window handle");
101 gst_video_overlay_set_window_handle (overlay, (guintptr) hwnd);
102 gst_message_unref (msg);
103
104 return GST_BUS_DROP;
105 }
106
107 switch (GST_MESSAGE_TYPE (msg)) {
108 case GST_MESSAGE_ERROR:{
109 GError *err;
110 gchar *dbg;
111
112 gst_message_parse_error (msg, &err, &dbg);
113 gst_printerrln ("ERROR %s ", err->message);
114 if (dbg != NULL)
115 gst_printerrln ("ERROR debug information: %s", dbg);
116 g_clear_error (&err);
117 g_free (dbg);
118
119 test_reuse = FALSE;
120
121 g_main_loop_quit (loop);
122 break;
123 }
124 default:
125 break;
126 }
127
128 return GST_BUS_PASS;
129 }
130
131 gint
main(gint argc,gchar ** argv)132 main (gint argc, gchar ** argv)
133 {
134 GstElement *playbin;
135 WNDCLASSEX wc = { 0, };
136 HINSTANCE hinstance = GetModuleHandle (NULL);
137 GIOChannel *msg_io_channel;
138 GOptionContext *option_ctx;
139 GError *error = NULL;
140 gchar *uri = NULL;
141 RECT wr = { 0, 0, 320, 240 };
142 gint exitcode = 0;
143 gboolean ret;
144 GOptionEntry options[] = {
145 {"uri", 0, 0, G_OPTION_ARG_STRING, &uri,
146 "URI to test playback with Win32 overlay", NULL}
147 ,
148 {"set-handle-on-request", 0, 0, G_OPTION_ARG_NONE, &set_handle_on_request,
149 "Set window handle on \"prepare-window-handle\" message", NULL}
150 ,
151 {"repeat", 0, 0, G_OPTION_ARG_NONE, &test_reuse,
152 "Repeat and reuse pipeline per EOS", NULL}
153 ,
154 {NULL}
155 };
156
157 option_ctx =
158 g_option_context_new ("WIN32 video overlay with playbin example");
159 g_option_context_add_main_entries (option_ctx, options, NULL);
160 g_option_context_add_group (option_ctx, gst_init_get_option_group ());
161 ret = g_option_context_parse (option_ctx, &argc, &argv, &error);
162 g_option_context_free (option_ctx);
163
164 if (!ret) {
165 gst_printerrln ("option parsing failed: %s", error->message);
166 g_clear_error (&error);
167 exit (1);
168 }
169
170 if (!uri) {
171 gst_printerrln ("--uri is a required argument");
172 g_clear_error (&error);
173 exit (1);
174 }
175
176 /* prepare window */
177 wc.cbSize = sizeof (WNDCLASSEX);
178 wc.style = CS_HREDRAW | CS_VREDRAW;
179 wc.lpfnWndProc = (WNDPROC) window_proc;
180 wc.hInstance = hinstance;
181 wc.hCursor = LoadCursor (NULL, IDC_ARROW);
182 wc.lpszClassName = "GstWin32VideoOverlayPlaybin";
183 RegisterClassEx (&wc);
184
185 AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE);
186 hwnd = CreateWindowEx (0, wc.lpszClassName,
187 "GstWin32VideoOverlayPlaybin",
188 WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
189 CW_USEDEFAULT, CW_USEDEFAULT,
190 wr.right - wr.left, wr.bottom - wr.top, (HWND) NULL, (HMENU) NULL,
191 hinstance, NULL);
192
193 loop = g_main_loop_new (NULL, FALSE);
194 msg_io_channel = g_io_channel_win32_new_messages (0);
195 g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, NULL);
196
197 /* prepare the pipeline */
198 playbin = gst_element_factory_make ("playbin", NULL);
199
200 if (!playbin) {
201 gst_printerrln ("playbin is not available");
202
203 exitcode = 1;
204 goto terminate;
205 }
206
207 /* User can set window handle on playbin before starting
208 * pipeline without watching "prepare-window-handle" message,
209 * because playbin/playsink will pass the given handle to selected
210 * video sink element later once video sink is prepared.
211 *
212 * But in case that an application wants to delay setting window handle
213 * as much as possible for some reason, the application needs to check
214 * "prepare-window-handle" message
215 * (use gst_is_video_overlay_prepare_window_handle_message() API for check)
216 * via a *sync* message handler and should set window handle on
217 * the *sync* message handler immediately */
218 if (!set_handle_on_request) {
219 GstVideoOverlay *overlay = GST_VIDEO_OVERLAY (playbin);
220
221 gst_println ("Setting window handle now");
222 gst_video_overlay_set_window_handle (overlay, (guintptr) hwnd);
223 } else {
224 gst_println ("Will set window handle on \"prepare-window-handle\" message");
225 }
226
227 gst_bus_add_watch (GST_ELEMENT_BUS (playbin), bus_msg, playbin);
228 gst_bus_set_sync_handler (GST_ELEMENT_BUS (playbin),
229 bus_sync_handler, NULL, NULL);
230 g_object_set (playbin, "uri", uri, NULL);
231
232 do {
233 if (gst_element_set_state (playbin,
234 GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
235 gst_printerrln ("Pipeline doesn't want to pause");
236 gst_bus_remove_watch (GST_ELEMENT_BUS (playbin));
237
238 exitcode = 1;
239 goto terminate;
240 }
241
242 g_main_loop_run (loop);
243 gst_element_set_state (playbin, GST_STATE_NULL);
244 } while (test_reuse);
245
246 gst_bus_remove_watch (GST_ELEMENT_BUS (playbin));
247
248 terminate:
249 if (hwnd)
250 DestroyWindow (hwnd);
251
252 gst_object_unref (playbin);
253 g_io_channel_unref (msg_io_channel);
254 g_main_loop_unref (loop);
255 g_free (uri);
256
257 return exitcode;
258 }
259