2 initially based on an example by Tristan Matthews
3 http://tristanswork.blogspot.com/2008/09/fullscreen-video-in-gstreamer-with-gtk.html
7 #include <gst/interfaces/xoverlay.h>
13 static void hide_mouse(GtkWidget
*widget
){
14 GdkWindow
*w
= GDK_WINDOW(widget
->window
);
15 GdkDisplay
*display
= gdk_display_get_default();
16 GdkCursor
*cursor
= gdk_cursor_new_for_display(display
, GDK_BLANK_CURSOR
);
17 gdk_window_set_cursor(w
, cursor
);
18 gdk_cursor_unref (cursor
);
22 mjpeg_branch(GstPipeline
*pipeline
, GstElement
*tee
)
24 /* ! jpegenc ! avimux ! filesink location=mjpeg.avi */
25 GstElement
*queue
= gst_element_factory_make("queue", NULL
);
26 GstElement
*jpegenc
= gst_element_factory_make("jpegenc", NULL
);
27 GstElement
*avimux
= gst_element_factory_make("avimux", NULL
);
28 GstElement
*filesink
= gst_element_factory_make("filesink", NULL
);
29 GstElement
*cs
= gst_element_factory_make("ffmpegcolorspace", NULL
);
30 g_object_set(G_OBJECT(filesink
),
31 "location", option_avi
,
33 gst_bin_add_many(GST_BIN(pipeline
),
40 gst_element_link_many(tee
,
50 post_tee_pipeline(GstPipeline
*pipeline
, GstElement
*tee
, GstElement
*sink
,
51 int rngseed
, int colour
, int timer
, int debug
, char *save
, char *reload
){
52 GstElement
*queue
= gst_element_factory_make("queue", NULL
);
53 GstElement
*sparrow
= gst_element_factory_make("sparrow", NULL
);
54 GstElement
*caps_posteriori
= gst_element_factory_make("capsfilter", NULL
);
55 GstElement
*cs_posteriori
= gst_element_factory_make("ffmpegcolorspace", NULL
);
57 g_object_set(G_OBJECT(caps_posteriori
), "caps",
58 gst_caps_new_simple ("video/x-raw-rgb",
61 g_object_set(G_OBJECT(sparrow
),
68 g_object_set(G_OBJECT(sparrow
),
73 g_object_set(G_OBJECT(sparrow
),
78 gst_bin_add_many (GST_BIN(pipeline
),
86 gst_element_link_many(tee
,
96 pre_tee_pipeline(GstPipeline
*pipeline
){
97 if (pipeline
== NULL
){
98 pipeline
= GST_PIPELINE(gst_pipeline_new("sparrow_pipeline"));
100 GstElement
*src
= gst_element_factory_make("v4l2src", NULL
);
101 GstElement
*caps_priori
= gst_element_factory_make("capsfilter", NULL
);
102 GstElement
*cs_priori
= gst_element_factory_make("ffmpegcolorspace", NULL
);
103 GstElement
*caps_interiori
= gst_element_factory_make("capsfilter", NULL
);
104 GstElement
*tee
= gst_element_factory_make ("tee", NULL
);
106 g_object_set(G_OBJECT(caps_priori
), "caps",
107 gst_caps_new_simple ("video/x-raw-yuv",
108 "format", GST_TYPE_FOURCC
, GST_MAKE_FOURCC('Y', 'U', 'Y', '2'),
111 g_object_set(G_OBJECT(caps_interiori
), "caps",
112 gst_caps_new_simple ("video/x-raw-rgb",
115 gst_bin_add_many(GST_BIN(pipeline
),
123 gst_element_link_many(src
,
134 make_multi_pipeline(windows_t
*windows
, int count
)
136 GstPipeline
*pipeline
= GST_PIPELINE(gst_pipeline_new("sparrow_pipeline"));
137 GstElement
*tee
= pre_tee_pipeline(pipeline
);
141 for (i
= 0; i
< count
; i
++){
142 GstElement
*sink
= windows
->sinks
[i
];
144 //(pipeline, tee, sink, int rngseed, int colour, timer flag, int debug flag)
145 /* timer should only run on one of them. colour >= 3 is undefined */
146 int debug
= option_debug
== i
;
147 int timer
= option_timer
== i
;
148 if (option_reload
!= NULL
){
149 if (option_reload
[i
] == NULL
){
150 g_critical("You can't reload some screens and not others!");
153 reload
= option_reload
[i
];
155 if (option_save
&& option_save
[i
]){
156 save
= option_save
[i
];
158 post_tee_pipeline(pipeline
, tee
, sink
, i
, i
+ 1, timer
, debug
, save
, reload
);
161 /*add a branch saving the video to a file */
162 mjpeg_branch(pipeline
, tee
);
170 bus_call(GstBus
* bus
, GstMessage
*msg
, gpointer data
)
172 windows_t
*windows
= (windows_t
*)data
;
173 if (GST_MESSAGE_TYPE(msg
) == GST_MESSAGE_ELEMENT
&&
174 gst_structure_has_name(msg
->structure
, "prepare-xwindow-id")){
175 g_print("Got prepare-xwindow-id msg. option screens: %d\n", option_screens
);
176 for (int i
= 0; i
< option_screens
; i
++){
177 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(windows
->sinks
[i
]),
178 windows
->xwindows
[i
]);
179 g_print("connected sink %d to window %lu\n", i
, windows
->xwindows
[i
]);
180 hide_mouse(windows
->gtk_windows
[i
]);
186 toggle_fullscreen(GtkWidget
*widget
){
187 GdkWindowState state
= gdk_window_get_state(GDK_WINDOW(widget
->window
));
188 if (state
== GDK_WINDOW_STATE_FULLSCREEN
){
189 gtk_window_unfullscreen(GTK_WINDOW(widget
));
192 gtk_window_fullscreen(GTK_WINDOW(widget
));
197 key_press_event_cb(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
199 g_print("got key %c\n", event
->keyval
);
200 switch (event
->keyval
){
202 toggle_fullscreen(widget
);
205 g_signal_emit_by_name(widget
, "destroy");
213 void destroy_cb(GtkWidget
* widget
, gpointer data
)
215 GMainLoop
*loop
= (GMainLoop
*) data
;
216 g_print("Window destroyed\n");
217 g_main_loop_quit(loop
);
221 video_widget_realize_cb(GtkWidget
*widget
, gpointer data
)
223 windows_t
*windows
= (windows_t
*)data
;
224 int r
= windows
->realised
;
225 if (r
< MAX_SCREENS
){
226 windows
->xwindows
[r
] = GDK_WINDOW_XID(GDK_WINDOW(widget
->window
));
227 g_print("realised window %d with XID %lu\n", r
, windows
->xwindows
[r
]);
230 g_print("wtf, there seem to be %d windows!\n", r
);
238 set_up_window(GMainLoop
*loop
, GtkWidget
*window
, int screen_no
){
239 static const GdkColor black
= {0, 0, 0, 0};
240 gtk_window_set_default_size(GTK_WINDOW(window
), WIDTH
, HEIGHT
);
242 if (option_fullscreen
){
243 gtk_window_fullscreen(GTK_WINDOW(window
));
246 /*if more than one screen is requested, set the screen number.
247 otherwise let it fall were it falls */
248 if (option_screens
> 1 || option_first_screen
){
249 /* "screen" is not the same thing as "monitor" */
250 GdkScreen
* screen
= gdk_screen_get_default();
251 int width
= gdk_screen_get_width(screen
);
252 /* XXX window selection is specific to equally sized windows arranged
253 horizontally. This could be generalised, perhaps using trial and
255 gtk_window_move(GTK_WINDOW(window
),
256 (width
/ 2 * screen_no
+ 200) % width
, 50);
259 // attach key press signal to key press callback
260 gtk_widget_set_events(window
, GDK_KEY_PRESS_MASK
);
261 g_signal_connect(G_OBJECT(window
), "key-press-event", G_CALLBACK(key_press_event_cb
), NULL
);
262 g_signal_connect(G_OBJECT(window
), "destroy", G_CALLBACK(destroy_cb
), loop
);
264 gtk_widget_modify_bg(window
, GTK_STATE_NORMAL
, &black
);
265 gtk_widget_show_all(window
);
270 static GOptionEntry entries
[] =
272 { "full-screen", 'f', 0, G_OPTION_ARG_NONE
, &option_fullscreen
, "run full screen", NULL
},
273 { "fps", 'p', 0, G_OPTION_ARG_INT
, &option_fps
, "speed (Frames per second)", "FPS" },
274 { "screens", 's', 0, G_OPTION_ARG_INT
, &option_screens
, "Use this many screens", "S" },
275 { "first-screen", 0, 0, G_OPTION_ARG_INT
, &option_first_screen
, "Start with this screen", "S" },
276 { "debug", 'd', 0, G_OPTION_ARG_INT
, &option_debug
, "Save screen's debug images in /tmp", "SCREEN" },
277 { "timer", 't', 0, G_OPTION_ARG_INT
, &option_timer
, "Log frame times in /tmp/timer.log", "SCREEN" },
278 { "reload", 'r', 0, G_OPTION_ARG_FILENAME_ARRAY
, &option_reload
,
279 "load calibration data from FILE (one per screen)", "FILE" },
280 { "save", 'S', 0, G_OPTION_ARG_FILENAME_ARRAY
, &option_save
,
281 "save calibration data to FILE (one per screen)", "FILE" },
282 { "avi", 'a', 0, G_OPTION_ARG_FILENAME
, &option_avi
,
283 "save mjpeg video to FILE", "FILE" },
284 // { "overlay", 'o', 0, G_OPTION_ARG_NONE, &option_overlay, "Use some kind of overlay", NULL },
285 { NULL
, 0, 0, 0, NULL
, NULL
, NULL
}
289 gint
main (gint argc
, gchar
*argv
[])
291 //initialise threads before any gtk stuff (because not using gtk_init)
293 /*this is more complicated than plain gtk_init/gst_init, so that options from
294 all over can be gathered and presented together.
296 GOptionGroup
*gst_opts
= gst_init_get_option_group();
297 GOptionGroup
*gtk_opts
= gtk_get_option_group(TRUE
);
298 GOptionContext
*ctx
= g_option_context_new("...!");
299 g_option_context_add_main_entries(ctx
, entries
, NULL
);
300 g_option_context_add_group(ctx
, gst_opts
);
301 g_option_context_add_group(ctx
, gtk_opts
);
302 GError
*error
= NULL
;
303 if (!g_option_context_parse(ctx
, &argc
, &argv
, &error
)){
304 g_print ("Error initializing: %s\n", GST_STR_NULL(error
->message
));
307 g_option_context_free(ctx
);
309 GMainLoop
*loop
= g_main_loop_new(NULL
, FALSE
);
312 windows
.realised
= 0;
315 for (i
= 0; i
< option_screens
; i
++){
316 GtkWidget
*window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
317 g_signal_connect(window
, "realize",
318 G_CALLBACK(video_widget_realize_cb
), &windows
);
319 /* set up sink here */
320 GstElement
*sink
= gst_element_factory_make("ximagesink", NULL
);
321 set_up_window(loop
, window
, i
+ option_first_screen
);
322 windows
.gtk_windows
[i
] = window
;
323 windows
.sinks
[i
] = sink
;
326 GstElement
*pipeline
= (GstElement
*)make_multi_pipeline(&windows
, option_screens
);
328 GstBus
*bus
= gst_pipeline_get_bus(GST_PIPELINE(pipeline
));
329 gst_bus_add_watch(bus
, (GstBusFunc
)bus_call
, &windows
);
330 gst_object_unref(bus
);
332 gst_element_set_state(pipeline
, GST_STATE_PLAYING
);
334 g_main_loop_run(loop
);
336 gst_element_set_state (pipeline
, GST_STATE_NULL
);
337 gst_object_unref (pipeline
);