1 /* This file is part of Opo, a small Video Whale
3 Copyright (C) 2011 Douglas Bagnall
5 Opo is free software: you can redistribute it and/or modify it under
6 the terms of the GNU General Public License as published by the Free
7 Software Foundation, either version 3 of the License, or (at your
8 option) any later version.
10 Opo is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
13 License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include <gst/interfaces/xoverlay.h>
26 static void gstreamer_stop(GstElement
*pipeline
);
27 static GstPipeline
*gstreamer_start(GMainLoop
*loop
, window_t
*windows
);
33 if (option_autosize
&& option_content
){
34 //automatically find size
35 caps
= gst_caps_new_simple("video/x-raw-yuv",
37 gst_caps_merge(caps
, gst_caps_new_simple("video/x-raw-rgb",
41 caps
= gst_caps_new_simple("video/x-raw-yuv",
42 "width", G_TYPE_INT
, option_screens
* option_width
,
43 "height", G_TYPE_INT
, option_height
,
45 gst_caps_merge(caps
, gst_caps_new_simple("video/x-raw-rgb",
46 "width", G_TYPE_INT
, option_screens
* option_width
,
47 "height", G_TYPE_INT
, option_height
,
54 attempt_filename_to_uri(char *filename
){
56 if (g_str_has_prefix(filename
, "/")){
57 uri
= g_strconcat("file://",
62 char *cwd
= g_get_current_dir();
63 uri
= g_strconcat("file://",
73 static void hide_mouse(GtkWidget
*widget
){
74 GdkWindow
*w
= GDK_WINDOW(widget
->window
);
75 GdkDisplay
*display
= gdk_display_get_default();
76 GdkCursor
*cursor
= gdk_cursor_new_for_display(display
, GDK_BLANK_CURSOR
);
77 gdk_window_set_cursor(w
, cursor
);
78 gdk_cursor_unref (cursor
);
83 post_tee_pipeline(GstBin
*bin
, GstElement
*tee
, GstElement
*sink
,
84 int crop_left
, int crop_right
){
85 GstElement
*queue
= gst_element_factory_make("queue", NULL
);
86 GstElement
*crop
= gst_element_factory_make("videocrop", NULL
);
88 g_object_set(G_OBJECT(crop
),
101 gst_element_link_many(tee
,
111 static GstBusSyncReply
112 sync_bus_call(GstBus
*bus
, GstMessage
*msg
, gpointer data
)
114 //g_print("SYNC call with %s\n", GST_MESSAGE_TYPE_NAME(msg));
115 // ignore anything but 'prepare-xwindow-id' element messages
116 if ((GST_MESSAGE_TYPE(msg
) != GST_MESSAGE_ELEMENT
) ||
117 (! gst_structure_has_name(msg
->structure
, "prepare-xwindow-id"))){
120 window_t
*windows
= (window_t
*)data
;
121 g_print("Got prepare-xwindow-id msg. \n");
122 //connect this one up with the right window.
123 GstElement
*sink
= GST_ELEMENT(GST_MESSAGE_SRC(msg
));
126 g_print("found sink %p\n", sink
);
127 for (int i
= 0; i
< option_screens
; i
++){
128 const window_t
*w
= windows
+ i
;
129 if (w
->sink
== sink
){
130 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink
), w
->xid
);
131 g_print("connected sink %d to window %lu\n", i
, w
->xid
);
132 hide_mouse(w
->widget
);
139 g_print("couldn't find a window for this sink!\n");
142 gst_message_unref(msg
);
148 set_up_loop(GstElement
*source
, int flags
){
149 g_print("loooooping\n");
150 gint64 end
, loop_end
;
151 GstFormat nanosec_format
= GST_FORMAT_TIME
;
152 gst_element_query_duration(source
,
156 if (option_loop_end
){
157 loop_end
= (gint64
)option_loop_end
* BILLION
;
160 loop_end
= end
- 25 * NS_PER_FRAME
;
162 g_print("stream ends at %f seconds\nlooping at %f seconds\n",
163 end
/ (double)BILLION
, loop_end
/ (double)BILLION
);
164 if (!gst_element_seek(source
, 1.0, GST_FORMAT_TIME
,
166 GST_SEEK_TYPE_SET
, NS_PER_FRAME
,
167 GST_SEEK_TYPE_SET
, loop_end
169 g_print ("Seek failed!\n");
173 static gboolean looping
= FALSE
;
176 manage_state_change(GstMessage
*msg
, GstElement
*pipeline
){
177 GstState old_state
, new_state
, pending_state
;
178 gst_message_parse_state_changed(msg
, &old_state
, &new_state
, &pending_state
);
179 if (msg
->src
== GST_OBJECT(pipeline
)){
180 //g_print("pipeline state change\n");
181 if (new_state
== GST_STATE_PAUSED
&& ! looping
){
182 /* the pipeline is ready for the initial loop to be set*/
183 set_up_loop(pipeline
, GST_SEEK_FLAG_FLUSH
| GST_SEEK_FLAG_SEGMENT
);
188 g_print ("Element %s changed state from %s to %s, pending %s.\n",
189 GST_OBJECT_NAME (msg->src),
190 gst_element_state_get_name(old_state),
191 gst_element_state_get_name(new_state),
192 gst_element_state_get_name(pending_state));
197 async_bus_call(GstBus
*bus
, GstMessage
*msg
, GstElement
*pipeline
)
199 //g_print("async call with %s\n", GST_MESSAGE_TYPE_NAME(msg));
200 switch(GST_MESSAGE_TYPE(msg
)){
201 case GST_MESSAGE_SEGMENT_DONE
:
202 set_up_loop(pipeline
, GST_SEEK_FLAG_FLUSH
| GST_SEEK_FLAG_SEGMENT
);
204 case GST_MESSAGE_STATE_CHANGED
:
205 manage_state_change(msg
, pipeline
);
215 about_to_finish_cb(GstElement
*pipeline
, char *uri
)
217 g_print("would be starting again with %s\n", uri
);
218 g_object_set(G_OBJECT(pipeline
),
224 toggle_fullscreen(GtkWidget
*widget
){
225 GdkWindowState state
= gdk_window_get_state(GDK_WINDOW(widget
->window
));
226 if (state
== GDK_WINDOW_STATE_FULLSCREEN
){
227 gtk_window_unfullscreen(GTK_WINDOW(widget
));
230 gtk_window_fullscreen(GTK_WINDOW(widget
));
235 key_press_event_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
237 g_print("got key %c\n", event
->keyval
);
238 switch (event
->keyval
){
240 toggle_fullscreen(widget
);
243 g_signal_emit_by_name(widget
, "destroy");
252 destroy_cb(GtkWidget
*widget
, gpointer data
)
254 GMainLoop
*loop
= (GMainLoop
*) data
;
255 g_print("Window destroyed\n");
256 g_main_loop_quit(loop
);
257 gtk_widget_destroy(widget
);
261 video_widget_realize_cb(GtkWidget
*widget
, gpointer data
)
263 window_t
*w
= (window_t
*)data
;
264 w
->xid
= GDK_WINDOW_XID(GDK_WINDOW(widget
->window
));
265 g_print("realised window %d with XID %lu\n", w
->id
, w
->xid
);
269 /*create the X window, but not the xvimagesink*/
271 set_up_window(GMainLoop
*loop
, window_t
*w
, int screen_no
){
272 GtkWidget
*window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
275 g_signal_connect(w
->widget
, "realize", G_CALLBACK(video_widget_realize_cb
), w
);
277 static const GdkColor black
= {0, 0, 0, 0};
278 //XXX need to resize once video size is known
279 gtk_window_set_default_size(GTK_WINDOW(window
), option_width
, option_height
);
281 if (option_fullscreen
){
282 gtk_window_fullscreen(GTK_WINDOW(window
));
286 if (option_x_screens
<= 1){
287 xscreen
= gdk_screen_get_default();
288 /*Xscreen number might not be 0, but 0 is right assumption for
289 calculations below.*/
293 xscreen_no
= screen_no
* option_x_screens
/ option_screens
;
294 /* record the display/screen number, so that xvimagesink can be top it later */
295 g_snprintf(w
->display
, sizeof(w
->display
), ":0.%d", xscreen_no
);
296 xscreen
= gdk_display_get_screen(gdk_display_get_default(), xscreen_no
);
297 gtk_window_set_screen(GTK_WINDOW(window
), xscreen
);
300 int windows_per_xscreen
= option_screens
/ option_x_screens
;
301 int window_no
= screen_no
% windows_per_xscreen
;
302 int monitors
= gdk_screen_get_n_monitors(xscreen
);
303 if (option_force_multiscreen
){
304 /*Ask gtk to find the appropriate monitor (assuming each Xscreen has the
305 same number of monitors). */
306 if (window_no
>= monitors
){
307 g_print("asking for monitor %d which does not exist! (monitors %d)\n",
308 window_no
, monitors
);
310 GdkRectangle monitor_shape
;
311 gdk_screen_get_monitor_geometry(xscreen
, window_no
, &monitor_shape
);
312 x
= monitor_shape
.x
+ 1;
313 y
= monitor_shape
.y
+ 1;
316 /*simple placement heuristic, places windows evenly across display.
317 This should work with equally sized monitors/projectors, and allows
318 testing on a single monitor.
320 int full_screen_width
= gdk_screen_get_width(xscreen
);
321 x
= window_no
* (full_screen_width
/ windows_per_xscreen
) + 1;
325 gtk_window_move(GTK_WINDOW(window
), x
, y
);
326 g_print("putting window %d on screen :0.%d at %d,%d\n",
327 screen_no
, xscreen_no
, x
, y
);
329 // attach key press signal to key press callback
330 gtk_widget_set_events(window
, GDK_KEY_PRESS_MASK
);
331 g_signal_connect(G_OBJECT(window
), "key-press-event", G_CALLBACK(key_press_event_cb
), NULL
);
332 g_signal_connect(G_OBJECT(window
), "destroy", G_CALLBACK(destroy_cb
), loop
);
334 gtk_widget_modify_bg(window
, GTK_STATE_NORMAL
, &black
);
335 gtk_widget_show_all(window
);
342 uri_pre_tee_pipeline(void){
343 GstPipeline
*pipeline
;
345 if( g_str_has_prefix(option_content
, "file://") ||
346 g_str_has_prefix(option_content
, "http://") /* || others */
348 uri
= option_content
;
351 uri
= attempt_filename_to_uri(option_content
);
353 g_print("uri is '%s'\n", uri
);
354 pipeline
= GST_PIPELINE(gst_element_factory_make("playbin2", NULL
));
355 g_object_set(G_OBJECT(pipeline
),
359 g_signal_connect(pipeline
, "about-to-finish",
360 G_CALLBACK(about_to_finish_cb
), uri
);
365 test_pre_tee_pipeline(void){
366 GstPipeline
*pipeline
;
367 pipeline
= GST_PIPELINE(gst_pipeline_new("test_pipeline"));
368 char * src_name
= (option_fake
) ? "videotestsrc" : "v4l2src";
369 GstElement
*src
= gst_element_factory_make(src_name
, "videosource");
370 if (option_fake
== 2){//set some properties for an interesting picture
371 g_object_set(G_OBJECT(src
),
372 "pattern", 14, //"zone-plate"
380 gst_bin_add(GST_BIN(pipeline
), src
);
385 tee_bin(GMainLoop
*loop
, window_t
*windows
){
386 GstBin
*bin
= GST_BIN(gst_bin_new("teebin"));
387 GstElement
*tee
= gst_element_factory_make("tee", NULL
);
388 gst_bin_add(bin
, tee
);
389 GstPad
*teesink
= gst_element_get_pad(tee
, "sink");
390 GstPad
*ghost
= gst_ghost_pad_new("sink", teesink
);
391 gst_element_add_pad(GST_ELEMENT(bin
), ghost
);
393 /* construct the various arms
394 crop _left/_right are amount to cut, not coordinate of cut
396 int input_width
= option_screens
* option_width
;
398 int crop_right
= input_width
- option_width
;
401 for (i
= 0; i
< option_screens
; i
++){
402 window_t
*w
= windows
+ i
;
403 set_up_window(loop
, w
, i
);
404 w
->sink
= gst_element_factory_make("xvimagesink", NULL
);
405 g_object_set(G_OBJECT(w
->sink
),
406 "display", w
->display
,
408 post_tee_pipeline(bin
, tee
, w
->sink
, crop_left
, crop_right
);
409 crop_left
+= option_width
;
410 crop_right
-= option_width
;
418 gstreamer_start(GMainLoop
*loop
, window_t
*windows
)
420 GstPipeline
*pipeline
;
422 pipeline
= uri_pre_tee_pipeline();
425 pipeline
= test_pre_tee_pipeline();
427 GstBin
*teebin
= tee_bin(loop
, windows
);
429 if (option_content
) {
430 g_object_set(G_OBJECT(pipeline
),
431 "video-sink", GST_ELEMENT(teebin
),
435 gst_bin_add(GST_BIN(pipeline
), GST_ELEMENT(teebin
));
436 GstElement
*videosrc
= gst_bin_get_by_name(GST_BIN(pipeline
), "videosource");
437 GstCaps
*caps
= make_good_caps();
438 gst_element_link_filtered(videosrc
, GST_ELEMENT(teebin
), caps
);
439 gst_object_unref(caps
);
442 GstBus
*bus
= gst_pipeline_get_bus(pipeline
);
443 gst_bus_set_sync_handler(bus
, (GstBusSyncHandler
)sync_bus_call
, windows
);
444 gst_bus_add_watch(bus
, (GstBusFunc
)async_bus_call
, pipeline
);
446 gst_object_unref(bus
);
448 gst_element_set_state(GST_ELEMENT(pipeline
), GST_STATE_PLAYING
);
455 gstreamer_stop(GstElement
*pipeline
)
457 gst_element_set_state(pipeline
, GST_STATE_NULL
);
458 gst_object_unref(pipeline
);
461 gint
main (gint argc
, gchar
*argv
[])
463 //initialise threads before any gtk stuff (because not using gtk_init)
466 /*this is more complicated than plain gtk_init/gst_init, so that options from
467 all over can be gathered and presented together.
469 GOptionGroup
*gst_opts
= gst_init_get_option_group();
470 GOptionGroup
*gtk_opts
= gtk_get_option_group(TRUE
);
471 GOptionContext
*ctx
= g_option_context_new("...!");
472 g_option_context_add_main_entries(ctx
, entries
, NULL
);
473 g_option_context_add_group(ctx
, gst_opts
);
474 g_option_context_add_group(ctx
, gtk_opts
);
475 GError
*error
= NULL
;
476 if (!g_option_context_parse(ctx
, &argc
, &argv
, &error
)){
477 g_print ("Error initializing: %s\n", GST_STR_NULL(error
->message
));
480 g_option_context_free(ctx
);
482 if (option_x_screens
> MAX_X_SCREENS
)
483 option_x_screens
= MAX_X_SCREENS
;
484 if (option_x_screens
< MIN_X_SCREENS
)
485 option_x_screens
= MIN_X_SCREENS
;
486 if (option_x_screens
> MAX_X_SCREENS
)
487 option_screens
= MAX_SCREENS
;
488 if (option_screens
< MIN_SCREENS
)
489 option_screens
= MIN_SCREENS
;
490 if (option_width
> MAX_PIXELS
)
491 option_width
= MAX_PIXELS
;
492 if (option_height
> MAX_PIXELS
)
493 option_height
= MAX_PIXELS
;
494 /*setting width, height <= 0 makes sizing automatic (default) */
495 if (option_width
<= 0){
496 option_width
= DEFAULT_WIDTH
;
499 if (option_height
<= 0){
500 option_height
= DEFAULT_HEIGHT
;
503 window_t
*windows
= g_malloc0(sizeof(window_t
) * option_screens
);
505 GMainLoop
*loop
= g_main_loop_new(NULL
, FALSE
);
507 GstPipeline
*pipeline
= gstreamer_start(loop
, windows
);
509 g_main_loop_run(loop
);
511 gstreamer_stop(GST_ELEMENT(pipeline
));