tidying and messing
[opo.git] / opo.c
blobbb820fb26e6df13873ae23c9ef5b893db5fb5924
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 "opo.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;
33 static void
34 post_tee_pipeline(GstBin *bin, GstElement *tee, GstElement *sink,
35 int crop_left, int crop_right){
36 GstElement *queue = gst_element_factory_make("queue", NULL);
37 GstElement *crop = gst_element_factory_make("videocrop", NULL);
39 g_object_set(G_OBJECT(crop),
40 "top", 0,
41 "bottom", 0,
42 "left", crop_left,
43 "right", crop_right,
44 NULL);
46 gst_bin_add_many(bin,
47 queue,
48 crop,
49 sink,
50 NULL);
52 gst_element_link_many(tee,
53 queue,
54 crop,
55 sink,
56 NULL);
61 static void hide_mouse(GtkWidget *widget){
62 GdkWindow *w = GDK_WINDOW(widget->window);
63 GdkDisplay *display = gdk_display_get_default();
64 GdkCursor *cursor = gdk_cursor_new_for_display(display, GDK_BLANK_CURSOR);
65 gdk_window_set_cursor(w, cursor);
66 gdk_cursor_unref (cursor);
71 static GstBusSyncReply
72 sync_bus_call(GstBus *bus, GstMessage *msg, gpointer data)
74 // ignore anything but 'prepare-xwindow-id' element messages
75 g_print("SYNC call with %s\n", GST_MESSAGE_TYPE_NAME(msg));
77 if ((GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) ||
78 (! gst_structure_has_name(msg->structure, "prepare-xwindow-id"))){
79 return GST_BUS_PASS;
81 window_t *windows = (window_t *)data;
82 g_print("Got prepare-xwindow-id msg. \n");
83 //connect this one up with the right window.
84 GstElement *sink = GST_ELEMENT(GST_MESSAGE_SRC(msg));
85 int done = 0;
87 g_print("found sink %p\n", sink);
88 for (int i = 0; i < option_screens; i++){
89 const window_t *w = windows + i;
90 if (w->sink == sink){
91 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(sink), w->xid);
92 g_print("connected sink %d to window %lu\n", i, w->xid);
93 hide_mouse(w->widget);
94 done = 1;
95 break;
99 if (! done){
100 g_print("couldn't find a window for this sink!\n");
103 gst_message_unref(msg);
104 return GST_BUS_DROP;
108 static void
109 set_up_loop(GstElement *pipeline){
110 g_print("loooooping\n");
111 if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
112 GST_SEEK_TYPE_SET, 0,
113 //GST_SEEK_TYPE_END, -2 * NS_PER_FRAME,
114 GST_SEEK_TYPE_SET, (guint64)50 * NS_PER_FRAME
115 )) {
116 g_print ("Seek failed!\n");
120 static int looping = 0;
123 static void
124 manage_state_change(GstMessage *msg, GstElement *pipeline){
125 GstState old_state, new_state;
126 gst_message_parse_state_changed(msg, &old_state, &new_state, NULL);
127 if (msg->src == GST_OBJECT(pipeline)){
128 g_print("pipeline state change\n");
129 if (new_state == GST_STATE_PLAYING && ! looping){
130 /* the pipeline is ready for the loop to be set*/
131 set_up_loop(pipeline);
132 looping = 1;
135 g_print ("Element %s changed state from %s to %s.\n",
136 GST_OBJECT_NAME (msg->src),
137 gst_element_state_get_name(old_state),
138 gst_element_state_get_name(new_state));
142 static void
143 about_to_finish_cb(GstElement *pipeline, char *uri)
145 g_print("would be starting again with %s\n", uri);
148 g_object_set(G_OBJECT(pipeline),
149 "uri", uri,
150 NULL);
152 if (!gst_element_seek(pipeline, 1.0, GST_FORMAT_TIME, 0,
153 GST_SEEK_TYPE_SET, 0,
154 //GST_SEEK_TYPE_END, -2 * NS_PER_FRAME,
155 GST_SEEK_TYPE_SET, (guint64)50 * NS_PER_FRAME
156 )) {
157 g_print ("Seek failed!\n");
162 static gboolean
163 async_bus_call(GstBus *bus, GstMessage *msg, GstElement *pipeline)
165 g_print("async call with %s\n", GST_MESSAGE_TYPE_NAME(msg));
166 switch(GST_MESSAGE_TYPE(msg)){
167 case GST_MESSAGE_SEGMENT_DONE:
168 g_print("segment done!\n");
169 break;
170 case GST_MESSAGE_STREAM_STATUS:
171 g_print("stream status changed!\n");
172 break;
173 case GST_MESSAGE_STATE_CHANGED:
174 manage_state_change(msg, pipeline);
175 break;
176 default:
177 break;
179 return TRUE;
182 static GstBusSyncReply
183 state_changed_cb(GstBus *bus, GstMessage *message,
184 GstPipeline *pipeline){
185 g_print("eggs STATE CHANGED!\n");
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 inline char *
311 attempt_filename_to_uri(char *filename){
312 char *uri;
313 if (g_str_has_prefix(filename, "/")){
314 uri = g_strconcat("file://",
315 option_content,
316 NULL);
318 else {
319 char *cwd = g_get_current_dir();
320 uri = g_strconcat("file://",
321 cwd,
322 "/",
323 option_content,
324 NULL);
325 g_free(cwd);
327 return uri;
332 static GstPipeline *
333 pre_tee_pipeline(){
334 GstPipeline *pipeline;
335 if (option_content) {
336 char *uri;
337 if( g_str_has_prefix(option_content, "file://") ||
338 g_str_has_prefix(option_content, "http://") /* || others */
340 uri = option_content;
342 else{
343 uri = attempt_filename_to_uri(option_content);
345 g_print("uri is '%s'\n", uri);
346 pipeline = GST_PIPELINE(gst_element_factory_make("playbin2", NULL));
347 g_object_set(G_OBJECT(pipeline),
348 "uri", uri,
349 "volume", 0.5,
350 NULL);
351 g_signal_connect(pipeline, "about-to-finish",
352 G_CALLBACK(about_to_finish_cb), uri);
353 //g_free(uri);
354 //set_up_lopo(GST_ELEMENT(pipeline));
356 else {
357 pipeline = GST_PIPELINE(gst_pipeline_new("test_pipeline"));
358 char * src_name = (option_fake) ? "videotestsrc" : "v4l2src";
359 GstElement *src = gst_element_factory_make(src_name, "videosource");
360 if (option_fake == 2){//set some properties for an interesting picture
361 g_object_set(G_OBJECT(src),
362 "pattern", 14, //"zone-plate"
363 "kt2", 0,
364 "kx2", 3,
365 "ky2", 3,
366 "kt", 3,
367 "kxy", 2,
368 NULL);
370 gst_bin_add(GST_BIN(pipeline), src);
372 return pipeline;
375 static GstBin *
376 tee_bin(GMainLoop *loop, window_t *windows){
377 GstBin *bin = GST_BIN(gst_bin_new("teebin"));
378 GstElement *tee = gst_element_factory_make("tee", NULL);
379 gst_bin_add(bin, tee);
380 GstPad *teesink = gst_element_get_pad(tee, "sink");
381 GstPad *ghost = gst_ghost_pad_new("sink", teesink);
382 gst_element_add_pad(GST_ELEMENT(bin), ghost);
383 //XXX unref pad?
385 /* construct the various arms
386 crop _left/_right are amount to cut, not coordinate of cut
388 int input_width = option_screens * option_width;
389 int crop_left = 0;
390 int crop_right = input_width - option_width;
392 int i;
393 for (i = 0; i < option_screens; i++){
394 window_t *w = windows + i;
395 set_up_window(loop, w, i);
396 post_tee_pipeline(bin, tee, w->sink, crop_left, crop_right);
397 crop_left += option_width;
398 crop_right -= option_width;
400 return bin;
407 static GstPipeline *
408 gstreamer_start(GMainLoop *loop, window_t windows[MAX_SCREENS])
410 GstPipeline *pipeline = pre_tee_pipeline();
411 GstBin *teebin = tee_bin(loop, windows);
414 if (option_content) {
415 g_object_set(G_OBJECT(pipeline),
416 "video-sink", GST_ELEMENT(teebin),
417 NULL);
419 else {
420 gst_bin_add(GST_BIN(pipeline), GST_ELEMENT(teebin));
421 GstElement *videosrc = gst_bin_get_by_name(GST_BIN(pipeline), "videosource");
422 GstCaps *caps = make_good_caps();
423 gst_element_link_filtered(videosrc, GST_ELEMENT(teebin), caps);
424 gst_object_unref(caps);
427 GstBus *bus = gst_pipeline_get_bus(pipeline);
428 gst_bus_set_sync_handler(bus, (GstBusSyncHandler)sync_bus_call, windows);
429 gst_bus_add_watch(bus, (GstBusFunc)async_bus_call, pipeline);
431 gst_object_unref(bus);
433 gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
435 return pipeline;
439 static void
440 gstreamer_stop(GstElement *pipeline)
442 gst_element_set_state(pipeline, GST_STATE_NULL);
443 gst_object_unref(pipeline);
446 gint main (gint argc, gchar *argv[])
448 //initialise threads before any gtk stuff (because not using gtk_init)
449 static window_t windows[MAX_SCREENS];
450 g_type_init();
451 g_thread_init(NULL);
452 /*this is more complicated than plain gtk_init/gst_init, so that options from
453 all over can be gathered and presented together.
455 GOptionGroup *gst_opts = gst_init_get_option_group();
456 GOptionGroup *gtk_opts = gtk_get_option_group(TRUE);
457 GOptionContext *ctx = g_option_context_new("...!");
458 g_option_context_add_main_entries(ctx, entries, NULL);
459 g_option_context_add_group(ctx, gst_opts);
460 g_option_context_add_group(ctx, gtk_opts);
461 GError *error = NULL;
462 if (!g_option_context_parse(ctx, &argc, &argv, &error)){
463 g_print ("Error initializing: %s\n", GST_STR_NULL(error->message));
464 exit (1);
466 g_option_context_free(ctx);
467 /*sanitise options*/
468 if (option_x_screens > MAX_X_SCREENS)
469 option_x_screens = MAX_X_SCREENS;
470 if (option_x_screens < MIN_X_SCREENS)
471 option_x_screens = MIN_X_SCREENS;
472 if (option_x_screens > MAX_X_SCREENS)
473 option_screens = MAX_SCREENS;
474 if (option_screens < MIN_SCREENS)
475 option_screens = MIN_SCREENS;
476 if (option_width > MAX_PIXELS)
477 option_width = MAX_PIXELS;
478 if (option_height > MAX_PIXELS)
479 option_height = MAX_PIXELS;
480 /*setting width, height <= 0 makes sizing automatic (default) */
481 if (option_width <= 0){
482 option_width = DEFAULT_WIDTH;
483 option_autosize = 1;
485 if (option_height <= 0){
486 option_height = DEFAULT_HEIGHT;
487 option_autosize = 1;
490 GMainLoop *loop = g_main_loop_new(NULL, FALSE);
492 GstPipeline *pipeline = gstreamer_start(loop, windows);
494 g_main_loop_run(loop);
496 gstreamer_stop(GST_ELEMENT(pipeline));
497 return 0;