1 /*N way video splitter */
4 #include <gst/interfaces/xoverlay.h>
12 if (option_autosize
&& option_content
){
13 //automatically find size
14 caps
= gst_caps_new_simple("video/x-raw-yuv",
16 gst_caps_merge(caps
, gst_caps_new_simple("video/x-raw-rgb",
20 caps
= gst_caps_new_simple("video/x-raw-yuv",
21 "width", G_TYPE_INT
, option_screens
* option_width
,
22 "height", G_TYPE_INT
, option_height
,
24 gst_caps_merge(caps
, gst_caps_new_simple("video/x-raw-rgb",
25 "width", G_TYPE_INT
, option_screens
* option_width
,
26 "height", G_TYPE_INT
, option_height
,
33 post_tee_pipeline(GstPipeline
*pipeline
, GstElement
*tee
, GstElement
*sink
,
34 int crop_left
, int crop_right
){
35 GstElement
*queue
= gst_element_factory_make("queue", NULL
);
36 GstElement
*crop
= gst_element_factory_make("videocrop", NULL
);
38 g_object_set(G_OBJECT(crop
),
45 gst_bin_add_many(GST_BIN(pipeline
),
51 gst_element_link_many(tee
,
60 pad_added_cb (GstElement
*decodebin
, GstPad
*pad
, GstElement
*tee
)
62 GstPad
*tee_pad
= gst_element_get_static_pad(tee
, "sink");
63 gst_pad_link(pad
, tee_pad
);
64 gst_object_unref(tee_pad
);
68 drained_cb (GstElement
*decodebin
, GstPad
*pad
, GstElement
*dummy
)
70 gst_element_seek_simple(decodebin
,
72 GST_SEEK_FLAG_FLUSH
| GST_SEEK_FLAG_KEY_UNIT
,
79 pre_tee_pipeline(GstPipeline
*pipeline
){
80 if (pipeline
== NULL
){
81 pipeline
= GST_PIPELINE(gst_pipeline_new("wha_pipeline"));
86 if (g_str_has_prefix(option_content
, "/")){
87 uri
= g_strconcat("file://",
92 char *cwd
= g_get_current_dir();
93 uri
= g_strconcat("file://",
100 g_print("uri is '%s'\n", uri
);
102 src
= gst_element_factory_make("uridecodebin", NULL
);
103 g_object_set(G_OBJECT(src
),
109 char * src_name
= (option_fake
) ? "videotestsrc" : "v4l2src";
110 src
= gst_element_factory_make(src_name
, NULL
);
111 if (option_fake
== 2){//set some properties for an interesting picture
112 g_object_set(G_OBJECT(src
),
113 "pattern", 14, //"zone-plate"
123 GstElement
*tee
= gst_element_factory_make("tee", NULL
);
125 gst_bin_add(GST_BIN(pipeline
), src
);
126 gst_bin_add(GST_BIN(pipeline
), tee
);
128 if (option_content
) {
130 g_signal_connect(src
, "pad-added",
131 G_CALLBACK(pad_added_cb
), tee
);
133 g_signal_connect(src
, "drained",
134 G_CALLBACK(drained_cb
), NULL
);
137 GstCaps
*caps
= make_good_caps();
138 gst_element_link_filtered(src
,
146 static void hide_mouse(GtkWidget
*widget
){
147 GdkWindow
*w
= GDK_WINDOW(widget
->window
);
148 GdkDisplay
*display
= gdk_display_get_default();
149 GdkCursor
*cursor
= gdk_cursor_new_for_display(display
, GDK_BLANK_CURSOR
);
150 gdk_window_set_cursor(w
, cursor
);
151 gdk_cursor_unref (cursor
);
156 static GstBusSyncReply
157 sync_bus_call(GstBus
*bus
, GstMessage
*msg
, gpointer data
)
159 // ignore anything but 'prepare-xwindow-id' element messages
160 if ((GST_MESSAGE_TYPE(msg
) != GST_MESSAGE_ELEMENT
) ||
161 (! gst_structure_has_name(msg
->structure
, "prepare-xwindow-id"))){
164 window_t
*windows
= (window_t
*)data
;
165 g_print("Got prepare-xwindow-id msg. \n");
166 //connect this one up with the right window.
167 GstElement
*sink
= GST_ELEMENT(GST_MESSAGE_SRC(msg
));
170 g_print("found sink %p\n", sink
);
171 for (int i
= 0; i
< option_screens
; i
++){
172 const window_t
*w
= windows
+ i
;
173 if (w
->sink
== sink
){
174 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink
), w
->xid
);
175 g_print("connected sink %d to window %lu\n", i
, w
->xid
);
176 hide_mouse(w
->widget
);
183 g_print("couldn't find a window for this sink!\n");
186 gst_message_unref(msg
);
191 toggle_fullscreen(GtkWidget
*widget
){
192 GdkWindowState state
= gdk_window_get_state(GDK_WINDOW(widget
->window
));
193 if (state
== GDK_WINDOW_STATE_FULLSCREEN
){
194 gtk_window_unfullscreen(GTK_WINDOW(widget
));
197 gtk_window_fullscreen(GTK_WINDOW(widget
));
202 key_press_event_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
204 g_print("got key %c\n", event
->keyval
);
205 switch (event
->keyval
){
207 toggle_fullscreen(widget
);
210 g_signal_emit_by_name(widget
, "destroy");
219 destroy_cb(GtkWidget
*widget
, gpointer data
)
221 GMainLoop
*loop
= (GMainLoop
*) data
;
222 g_print("Window destroyed\n");
223 g_main_loop_quit(loop
);
224 gtk_widget_destroy(widget
);
228 video_widget_realize_cb(GtkWidget
*widget
, gpointer data
)
230 window_t
*w
= (window_t
*)data
;
231 w
->xid
= GDK_WINDOW_XID(GDK_WINDOW(widget
->window
));
232 g_print("realised window %d with XID %lu\n", w
->id
, w
->xid
);
238 set_up_window(GMainLoop
*loop
, window_t
*w
, int screen_no
){
239 GtkWidget
*window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
241 w
->sink
= gst_element_factory_make("xvimagesink", NULL
);
243 g_signal_connect(w
->widget
, "realize", G_CALLBACK(video_widget_realize_cb
), w
);
245 static const GdkColor black
= {0, 0, 0, 0};
246 //XXX need to resize once video size is known
247 gtk_window_set_default_size(GTK_WINDOW(window
), option_width
, option_height
);
249 if (option_fullscreen
){
250 gtk_window_fullscreen(GTK_WINDOW(window
));
254 if (option_x_screens
<= 1){
255 xscreen
= gdk_screen_get_default();
256 /*Xscreen number might not be 0, but 0 is right assumption for
257 calculations below.*/
261 xscreen_no
= screen_no
* option_x_screens
/ option_screens
;
262 char display
[sizeof(":0.00")];
263 g_snprintf(display
, sizeof(display
), ":0.%d", xscreen_no
);
264 xscreen
= gdk_display_get_screen(gdk_display_get_default(), xscreen_no
);
265 gtk_window_set_screen(GTK_WINDOW(window
), xscreen
);
266 g_object_set(G_OBJECT(w
->sink
),
271 int windows_per_xscreen
= option_screens
/ option_x_screens
;
272 int window_no
= screen_no
% windows_per_xscreen
;
273 int monitors
= gdk_screen_get_n_monitors(xscreen
);
274 if (option_force_multiscreen
){
275 /*Ask gtk to find the appropriate monitor (assuming each Xscreen has the
276 same number of monitors). */
277 if (window_no
>= monitors
){
278 g_print("asking for monitor %d which does not exist! (monitors %d)\n",
279 window_no
, monitors
);
281 GdkRectangle monitor_shape
;
282 gdk_screen_get_monitor_geometry(xscreen
, window_no
, &monitor_shape
);
283 x
= monitor_shape
.x
+ 1;
284 y
= monitor_shape
.y
+ 1;
287 /*simple placement heuristic, places windows evenly across display.
288 This should work with equally sized monitors/projectors, and allows
289 testing on a single monitor.
291 int full_screen_width
= gdk_screen_get_width(xscreen
);
292 x
= window_no
* (full_screen_width
/ windows_per_xscreen
) + 1;
296 gtk_window_move(GTK_WINDOW(window
), x
, y
);
297 g_print("putting window %d on screen :0.%d at %d,%d\n",
298 screen_no
, xscreen_no
, x
, y
);
300 // attach key press signal to key press callback
301 gtk_widget_set_events(window
, GDK_KEY_PRESS_MASK
);
302 g_signal_connect(G_OBJECT(window
), "key-press-event", G_CALLBACK(key_press_event_cb
), NULL
);
303 g_signal_connect(G_OBJECT(window
), "destroy", G_CALLBACK(destroy_cb
), loop
);
305 gtk_widget_modify_bg(window
, GTK_STATE_NORMAL
, &black
);
306 gtk_widget_show_all(window
);
311 gstreamer_start(GMainLoop
*loop
, window_t windows
[MAX_SCREENS
])
313 int input_width
= option_screens
* option_width
;
314 //crop _left/_right are amount to cut, not coordinate of cut
316 int crop_right
= input_width
- option_width
;
318 GstElement
*pipeline
= gst_pipeline_new("e_wha");
319 GstElement
*tee
= pre_tee_pipeline(GST_PIPELINE(pipeline
));
322 for (i
= 0; i
< option_screens
; i
++){
323 window_t
*w
= windows
+ i
;
324 set_up_window(loop
, w
, i
);
325 post_tee_pipeline(GST_PIPELINE(pipeline
), tee
, w
->sink
, crop_left
, crop_right
);
326 crop_left
+= option_width
;
327 crop_right
-= option_width
;
330 GstBus
*bus
= gst_pipeline_get_bus(GST_PIPELINE(pipeline
));
331 gst_bus_set_sync_handler(bus
, (GstBusSyncHandler
)sync_bus_call
, windows
);
332 gst_object_unref(bus
);
334 gst_element_set_state(pipeline
, GST_STATE_PLAYING
);
339 gstreamer_stop(GstElement
*pipeline
)
341 gst_element_set_state(pipeline
, GST_STATE_NULL
);
342 gst_object_unref(pipeline
);
345 gint
main (gint argc
, gchar
*argv
[])
347 //initialise threads before any gtk stuff (because not using gtk_init)
348 static window_t windows
[MAX_SCREENS
];
351 /*this is more complicated than plain gtk_init/gst_init, so that options from
352 all over can be gathered and presented together.
354 GOptionGroup
*gst_opts
= gst_init_get_option_group();
355 GOptionGroup
*gtk_opts
= gtk_get_option_group(TRUE
);
356 GOptionContext
*ctx
= g_option_context_new("...!");
357 g_option_context_add_main_entries(ctx
, entries
, NULL
);
358 g_option_context_add_group(ctx
, gst_opts
);
359 g_option_context_add_group(ctx
, gtk_opts
);
360 GError
*error
= NULL
;
361 if (!g_option_context_parse(ctx
, &argc
, &argv
, &error
)){
362 g_print ("Error initializing: %s\n", GST_STR_NULL(error
->message
));
365 g_option_context_free(ctx
);
367 if (option_x_screens
> MAX_X_SCREENS
)
368 option_x_screens
= MAX_X_SCREENS
;
369 if (option_x_screens
< MIN_X_SCREENS
)
370 option_x_screens
= MIN_X_SCREENS
;
371 if (option_x_screens
> MAX_X_SCREENS
)
372 option_screens
= MAX_SCREENS
;
373 if (option_screens
< MIN_SCREENS
)
374 option_screens
= MIN_SCREENS
;
375 if (option_width
> MAX_PIXELS
)
376 option_width
= MAX_PIXELS
;
377 if (option_height
> MAX_PIXELS
)
378 option_height
= MAX_PIXELS
;
379 /*setting width, height <= 0 makes sizing automatic (default) */
380 if (option_width
<= 0){
381 option_width
= DEFAULT_WIDTH
;
384 if (option_height
<= 0){
385 option_height
= DEFAULT_HEIGHT
;
389 GMainLoop
*loop
= g_main_loop_new(NULL
, FALSE
);
391 GstElement
*pipeline
= gstreamer_start(loop
, windows
);
393 g_main_loop_run(loop
);
395 gstreamer_stop(pipeline
);