attempt to seek to beginning of decodebin, doesn't work
[opo.git] / gtk-app.c
blobcf454f28631a2a1c668b64553493d9648551cc52
1 /*N way video splitter */
2 #include <gst/gst.h>
3 #include <gtk/gtk.h>
4 #include <gst/interfaces/xoverlay.h>
5 #include <gdk/gdk.h>
6 #include <gdk/gdkx.h>
7 #include "gtk-app.h"
9 static GstCaps *
10 make_good_caps(){
11 GstCaps *caps;
12 if (option_autosize && option_content){
13 //automatically find size
14 caps = gst_caps_new_simple("video/x-raw-yuv",
15 NULL);
16 gst_caps_merge(caps, gst_caps_new_simple("video/x-raw-rgb",
17 NULL));
19 else {
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,
23 NULL);
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,
27 NULL));
29 return caps;
32 static void
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),
39 "top", 0,
40 "bottom", 0,
41 "left", crop_left,
42 "right", crop_right,
43 NULL);
45 gst_bin_add_many(GST_BIN(pipeline),
46 queue,
47 crop,
48 sink,
49 NULL);
51 gst_element_link_many(tee,
52 queue,
53 crop,
54 sink,
55 NULL);
59 static void
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);
67 static void
68 drained_cb (GstElement *decodebin, GstPad *pad, GstElement *dummy)
70 gst_element_seek_simple(decodebin,
71 GST_FORMAT_DEFAULT,
72 GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
73 0);
74 g_print("drained\n");
75 //exit(0);
78 static GstElement *
79 pre_tee_pipeline(GstPipeline *pipeline){
80 if (pipeline == NULL){
81 pipeline = GST_PIPELINE(gst_pipeline_new("wha_pipeline"));
83 GstElement *src;
84 if (option_content) {
85 char *uri;
86 if (g_str_has_prefix(option_content, "/")){
87 uri = g_strconcat("file://",
88 option_content,
89 NULL);
91 else {
92 char *cwd = g_get_current_dir();
93 uri = g_strconcat("file://",
94 cwd,
95 "/",
96 option_content,
97 NULL);
98 g_free(cwd);
100 g_print("uri is '%s'\n", uri);
102 src = gst_element_factory_make("uridecodebin", NULL);
103 g_object_set(G_OBJECT(src),
104 "uri", uri,
105 NULL);
106 g_free(uri);
108 else {
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"
114 "kt2", 0,
115 "kx2", 3,
116 "ky2", 3,
117 "kt", 3,
118 "kxy", 2,
119 NULL);
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) {
129 //Can't link it yet!
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);
136 else{
137 GstCaps *caps = make_good_caps();
138 gst_element_link_filtered(src,
139 tee,
140 caps);
142 return tee;
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"))){
162 return GST_BUS_PASS;
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));
168 int done = 0;
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);
177 done = 1;
178 break;
182 if (! done){
183 g_print("couldn't find a window for this sink!\n");
186 gst_message_unref(msg);
187 return GST_BUS_DROP;
190 static void
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));
196 else{
197 gtk_window_fullscreen(GTK_WINDOW(widget));
201 static gboolean
202 key_press_event_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
204 g_print("got key %c\n", event->keyval);
205 switch (event->keyval){
206 case 'f':
207 toggle_fullscreen(widget);
208 break;
209 case 'q':
210 g_signal_emit_by_name(widget, "destroy");
211 break;
212 default:
213 break;
215 return TRUE;
218 static void
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);
227 static void
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);
233 hide_mouse(widget);
237 static void
238 set_up_window(GMainLoop *loop, window_t *w, int screen_no){
239 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
240 w->widget = window;
241 w->sink = gst_element_factory_make("xvimagesink", NULL);
242 w->id = screen_no;
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));
252 int xscreen_no;
253 GdkScreen * xscreen;
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.*/
258 xscreen_no = 0;
260 else{
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),
267 "display", display,
268 NULL);
270 int x, y;
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;
286 else {
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;
293 y = 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);
307 hide_mouse(window);
310 static GstElement *
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
315 int crop_left = 0;
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));
321 int i;
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);
335 return pipeline;
338 static void
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];
349 g_type_init();
350 g_thread_init(NULL);
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));
363 exit (1);
365 g_option_context_free(ctx);
366 /*sanitise options*/
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;
382 option_autosize = 1;
384 if (option_height <= 0){
385 option_height = DEFAULT_HEIGHT;
386 option_autosize = 1;
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);
396 return 0;