1 /* GStreamer
2 * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-ximagesink
22 * @title: ximagesink
23 *
24 * XImageSink renders video frames to a drawable (XWindow) on a local or remote
25 * display. This element can receive a Window ID from the application through
26 * the #GstVideoOverlay interface and will then render video frames in this
27 * drawable. If no Window ID was provided by the application, the element will
28 * create its own internal window and render into it.
29 *
30 * ## Scaling
31 *
32 * As standard XImage rendering to a drawable is not scaled, XImageSink will use
33 * reverse caps negotiation to try to get scaled video frames for the drawable.
34 * This is accomplished by asking the peer pad if it accepts some different caps
35 * which in most cases implies that there is a scaling element in the pipeline,
36 * or that an element generating the video frames can generate them with a
37 * different geometry. This mechanism is handled during buffer allocations, for
38 * each allocation request the video sink will check the drawable geometry, look
39 * at the #GstXImageSink:force-aspect-ratio property, calculate the geometry of
40 * desired video frames and then check that the peer pad accept those new caps.
41 * If it does it will then allocate a buffer in video memory with this new
42 * geometry and return it with the new caps.
43 *
44 * ## Events
45 *
46 * XImageSink creates a thread to handle events coming from the drawable. There
47 * are several kind of events that can be grouped in 2 big categories: input
48 * events and window state related events. Input events will be translated to
49 * navigation events and pushed upstream for other elements to react on them.
50 * This includes events such as pointer moves, key press/release, clicks etc...
51 * Other events are used to handle the drawable appearance even when the data
52 * is not flowing (GST_STATE_PAUSED). That means that even when the element is
53 * paused, it will receive expose events from the drawable and draw the latest
54 * frame with correct borders/aspect-ratio.
55 *
56 * ## Pixel aspect ratio
57 *
58 * When changing state to GST_STATE_READY, XImageSink will open a connection to
59 * the display specified in the #GstXImageSink:display property or the default
60 * display if nothing specified. Once this connection is open it will inspect
61 * the display configuration including the physical display geometry and
62 * then calculate the pixel aspect ratio. When caps negotiation will occur, the
63 * video sink will set the calculated pixel aspect ratio on the caps to make
64 * sure that incoming video frames will have the correct pixel aspect ratio for
65 * this display. Sometimes the calculated pixel aspect ratio can be wrong, it is
66 * then possible to enforce a specific pixel aspect ratio using the
67 * #GstXImageSink:pixel-aspect-ratio property.
68 *
69 * ## Examples
70 * |[
71 * gst-launch-1.0 -v videotestsrc ! queue ! ximagesink
72 * ]|
73 * A pipeline to test reverse negotiation. When the test video signal appears
74 * you can resize the window and see that scaled buffers of the desired size are
75 * going to arrive with a short delay. This illustrates how buffers of desired
76 * size are allocated along the way. If you take away the queue, scaling will
77 * happen almost immediately.
78 * |[
79 * gst-launch-1.0 -v videotestsrc ! navigationtest ! videoconvert ! ximagesink
80 * ]|
81 * A pipeline to test navigation events.
82 * While moving the mouse pointer over the test signal you will see a black box
83 * following the mouse pointer. If you press the mouse button somewhere on the
84 * video and release it somewhere else a green box will appear where you pressed
85 * the button and a red one where you released it. (The navigationtest element
86 * is part of gst-plugins-good.)
87 * |[
88 * gst-launch-1.0 -v videotestsrc ! video/x-raw, pixel-aspect-ratio=(fraction)4/3 ! videoscale ! ximagesink
89 * ]|
90 * This is faking a 4/3 pixel aspect ratio caps on video frames produced by
91 * videotestsrc, in most cases the pixel aspect ratio of the display will be
92 * 1/1. This means that videoscale will have to do the scaling to convert
93 * incoming frames to a size that will match the display pixel aspect ratio
94 * (from 320x240 to 320x180 in this case). Note that you might have to escape
95 * some characters for your shell like '\(fraction\)'.
96 *
97 */
98
99 #ifdef HAVE_CONFIG_H
100 #include "config.h"
101 #endif
102
103 /* Our interfaces */
104 #include <gst/video/navigation.h>
105 #include <gst/video/videooverlay.h>
106
107 #include <gst/video/gstvideometa.h>
108
109 /* Object header */
110 #include "ximagesink.h"
111
112 /* Debugging category */
113 #include <gst/gstinfo.h>
114
115 /* for XkbKeycodeToKeysym */
116 #include <X11/XKBlib.h>
117
118 GST_DEBUG_CATEGORY_EXTERN (gst_debug_x_image_pool);
119 GST_DEBUG_CATEGORY (gst_debug_x_image_sink);
120 GST_DEBUG_CATEGORY_STATIC (CAT_PERFORMANCE);
121
122 #define GST_CAT_DEFAULT gst_debug_x_image_sink
123
124 typedef struct
125 {
126 unsigned long flags;
127 unsigned long functions;
128 unsigned long decorations;
129 long input_mode;
130 unsigned long status;
131 }
132 MotifWmHints, MwmHints;
133
134 #define MWM_HINTS_DECORATIONS (1L << 1)
135
136 static void gst_x_image_sink_reset (GstXImageSink * ximagesink);
137 static void gst_x_image_sink_xwindow_update_geometry (GstXImageSink *
138 ximagesink);
139 static void gst_x_image_sink_expose (GstVideoOverlay * overlay);
140
141 static GstStaticPadTemplate gst_x_image_sink_sink_template_factory =
142 GST_STATIC_PAD_TEMPLATE ("sink",
143 GST_PAD_SINK,
144 GST_PAD_ALWAYS,
145 GST_STATIC_CAPS ("video/x-raw, "
146 "framerate = (fraction) [ 0, MAX ], "
147 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
148 );
149
150 enum
151 {
152 PROP_0,
153 PROP_DISPLAY,
154 PROP_SYNCHRONOUS,
155 PROP_PIXEL_ASPECT_RATIO,
156 PROP_FORCE_ASPECT_RATIO,
157 PROP_HANDLE_EVENTS,
158 PROP_HANDLE_EXPOSE,
159 PROP_WINDOW_WIDTH,
160 PROP_WINDOW_HEIGHT
161 };
162
163 /* ============================================================= */
164 /* */
165 /* Public Methods */
166 /* */
167 /* ============================================================= */
168
169 /* =========================================== */
170 /* */
171 /* Object typing & Creation */
172 /* */
173 /* =========================================== */
174 static void gst_x_image_sink_navigation_init (GstNavigationInterface * iface);
175 static void gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface *
176 iface);
177 #define gst_x_image_sink_parent_class parent_class
178 G_DEFINE_TYPE_WITH_CODE (GstXImageSink, gst_x_image_sink, GST_TYPE_VIDEO_SINK,
179 G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
180 gst_x_image_sink_navigation_init);
181 G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
182 gst_x_image_sink_video_overlay_init));
183
184 #define _do_init \
185 GST_DEBUG_CATEGORY_INIT (gst_debug_x_image_sink, "ximagesink", 0, "ximagesink element");\
186 GST_DEBUG_CATEGORY_INIT (gst_debug_x_image_pool, "ximagepool", 0, "ximagepool object");\
187 GST_DEBUG_CATEGORY_GET (CAT_PERFORMANCE, "GST_PERFORMANCE");
188 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (ximagesink, "ximagesink",
189 GST_RANK_SECONDARY, GST_TYPE_X_IMAGE_SINK, _do_init);
190
191 /* ============================================================= */
192 /* */
193 /* Private Methods */
194 /* */
195 /* ============================================================= */
196
197 /* X11 stuff */
198
199 /* We are called with the x_lock taken */
200 static void
gst_x_image_sink_xwindow_draw_borders(GstXImageSink * ximagesink,GstXWindow * xwindow,GstVideoRectangle rect)201 gst_x_image_sink_xwindow_draw_borders (GstXImageSink * ximagesink,
202 GstXWindow * xwindow, GstVideoRectangle rect)
203 {
204 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
205 g_return_if_fail (xwindow != NULL);
206
207 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
208 ximagesink->xcontext->black);
209
210 /* Left border */
211 if (rect.x > 0) {
212 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
213 0, 0, rect.x, xwindow->height);
214 }
215
216 /* Right border */
217 if ((rect.x + rect.w) < xwindow->width) {
218 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
219 rect.x + rect.w, 0, xwindow->width, xwindow->height);
220 }
221
222 /* Top border */
223 if (rect.y > 0) {
224 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
225 0, 0, xwindow->width, rect.y);
226 }
227
228 /* Bottom border */
229 if ((rect.y + rect.h) < xwindow->height) {
230 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
231 0, rect.y + rect.h, xwindow->width, xwindow->height);
232 }
233 }
234
235 /* This function puts a GstXImageBuffer on a GstXImageSink's window */
236 static gboolean
gst_x_image_sink_ximage_put(GstXImageSink * ximagesink,GstBuffer * ximage)237 gst_x_image_sink_ximage_put (GstXImageSink * ximagesink, GstBuffer * ximage)
238 {
239 GstXImageMemory *mem;
240 GstVideoCropMeta *crop;
241 GstVideoRectangle src = { 0, };
242 GstVideoRectangle dst = { 0, };
243 GstVideoRectangle result;
244 gboolean draw_border = FALSE;
245
246 /* We take the flow_lock. If expose is in there we don't want to run
247 concurrently from the data flow thread */
248 g_mutex_lock (&ximagesink->flow_lock);
249
250 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
251 g_mutex_unlock (&ximagesink->flow_lock);
252 return FALSE;
253 }
254
255 /* Draw borders when displaying the first frame. After this
256 draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */
257 if (!ximagesink->cur_image || ximagesink->draw_border) {
258 draw_border = TRUE;
259 }
260
261 /* Store a reference to the last image we put, lose the previous one */
262 if (ximage && ximagesink->cur_image != ximage) {
263 if (ximagesink->cur_image) {
264 GST_LOG_OBJECT (ximagesink, "unreffing %p", ximagesink->cur_image);
265 gst_buffer_unref (ximagesink->cur_image);
266 }
267 GST_LOG_OBJECT (ximagesink, "reffing %p as our current image", ximage);
268 ximagesink->cur_image = gst_buffer_ref (ximage);
269 }
270
271 /* Expose sends a NULL image, we take the latest frame */
272 if (!ximage) {
273 draw_border = TRUE;
274 if (ximagesink->cur_image) {
275 ximage = ximagesink->cur_image;
276 } else {
277 g_mutex_unlock (&ximagesink->flow_lock);
278 return TRUE;
279 }
280 }
281
282 mem = (GstXImageMemory *) gst_buffer_peek_memory (ximage, 0);
283 crop = gst_buffer_get_video_crop_meta (ximage);
284
285 if (crop) {
286 src.x = crop->x + mem->x;
287 src.y = crop->y + mem->y;
288 src.w = crop->width;
289 src.h = crop->height;
290 GST_LOG_OBJECT (ximagesink,
291 "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height);
292 } else {
293 src.x = mem->x;
294 src.y = mem->y;
295 src.w = mem->width;
296 src.h = mem->height;
297 }
298 dst.w = ximagesink->xwindow->width;
299 dst.h = ximagesink->xwindow->height;
300
301 gst_video_sink_center_rect (src, dst, &result, FALSE);
302
303 g_mutex_lock (&ximagesink->x_lock);
304
305 if (draw_border) {
306 gst_x_image_sink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
307 result);
308 ximagesink->draw_border = FALSE;
309 }
310 #ifdef HAVE_XSHM
311 if (ximagesink->xcontext->use_xshm) {
312 GST_LOG_OBJECT (ximagesink,
313 "XShmPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
314 ximage, 0, 0, result.x, result.y, result.w, result.h,
315 ximagesink->xwindow->width, ximagesink->xwindow->height);
316 XShmPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
317 ximagesink->xwindow->gc, mem->ximage, src.x, src.y, result.x, result.y,
318 result.w, result.h, FALSE);
319 } else
320 #endif /* HAVE_XSHM */
321 {
322 GST_LOG_OBJECT (ximagesink,
323 "XPutImage on %p, src: %d, %d - dest: %d, %d, dim: %dx%d, win %dx%d",
324 ximage, 0, 0, result.x, result.y, result.w, result.h,
325 ximagesink->xwindow->width, ximagesink->xwindow->height);
326 XPutImage (ximagesink->xcontext->disp, ximagesink->xwindow->win,
327 ximagesink->xwindow->gc, mem->ximage, src.x, src.y, result.x, result.y,
328 result.w, result.h);
329 }
330
331 XSync (ximagesink->xcontext->disp, FALSE);
332
333 g_mutex_unlock (&ximagesink->x_lock);
334
335 g_mutex_unlock (&ximagesink->flow_lock);
336
337 return TRUE;
338 }
339
340 static gboolean
gst_x_image_sink_xwindow_decorate(GstXImageSink * ximagesink,GstXWindow * window)341 gst_x_image_sink_xwindow_decorate (GstXImageSink * ximagesink,
342 GstXWindow * window)
343 {
344 Atom hints_atom = None;
345 MotifWmHints *hints;
346
347 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), FALSE);
348 g_return_val_if_fail (window != NULL, FALSE);
349
350 g_mutex_lock (&ximagesink->x_lock);
351
352 hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS",
353 True);
354 if (hints_atom == None) {
355 g_mutex_unlock (&ximagesink->x_lock);
356 return FALSE;
357 }
358
359 hints = g_malloc0 (sizeof (MotifWmHints));
360
361 hints->flags |= MWM_HINTS_DECORATIONS;
362 hints->decorations = 1 << 0;
363
364 XChangeProperty (ximagesink->xcontext->disp, window->win,
365 hints_atom, hints_atom, 32, PropModeReplace,
366 (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
367
368 XSync (ximagesink->xcontext->disp, FALSE);
369
370 g_mutex_unlock (&ximagesink->x_lock);
371
372 g_free (hints);
373
374 return TRUE;
375 }
376
377 static void
gst_x_image_sink_xwindow_set_title(GstXImageSink * ximagesink,GstXWindow * xwindow,const gchar * media_title)378 gst_x_image_sink_xwindow_set_title (GstXImageSink * ximagesink,
379 GstXWindow * xwindow, const gchar * media_title)
380 {
381 if (media_title) {
382 g_free (ximagesink->media_title);
383 ximagesink->media_title = g_strdup (media_title);
384 }
385 if (xwindow) {
386 /* we have a window */
387 if (xwindow->internal) {
388 XTextProperty xproperty;
389 XClassHint *hint = XAllocClassHint ();
390 const gchar *app_name;
391 const gchar *title = NULL;
392 gchar *title_mem = NULL;
393
394 /* set application name as a title */
395 app_name = g_get_application_name ();
396
397 if (app_name && ximagesink->media_title) {
398 title = title_mem = g_strconcat (ximagesink->media_title, " : ",
399 app_name, NULL);
400 } else if (app_name) {
401 title = app_name;
402 } else if (ximagesink->media_title) {
403 title = ximagesink->media_title;
404 }
405
406 if (title) {
407 if ((XStringListToTextProperty (((char **) &title), 1,
408 &xproperty)) != 0) {
409 Atom _NET_WM_NAME =
410 XInternAtom (ximagesink->xcontext->disp, "_NET_WM_NAME", 0);
411 Atom UTF8_STRING =
412 XInternAtom (ximagesink->xcontext->disp, "UTF8_STRING", 0);
413 XChangeProperty (ximagesink->xcontext->disp, xwindow->win,
414 _NET_WM_NAME, UTF8_STRING, 8, 0, (unsigned char *) title,
415 strlen (title));
416 XSync (ximagesink->xcontext->disp, False);
417
418 XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
419 XFree (xproperty.value);
420 }
421
422 g_free (title_mem);
423 }
424
425 if (hint) {
426 hint->res_name = (char *) app_name;
427 hint->res_class = (char *) "GStreamer";
428 XSetClassHint (ximagesink->xcontext->disp, xwindow->win, hint);
429 }
430 XFree (hint);
431 }
432 }
433 }
434
435 /* This function handles a GstXWindow creation */
436 static GstXWindow *
gst_x_image_sink_xwindow_new(GstXImageSink * ximagesink,gint width,gint height)437 gst_x_image_sink_xwindow_new (GstXImageSink * ximagesink, gint width,
438 gint height)
439 {
440 GstXWindow *xwindow = NULL;
441 XGCValues values;
442
443 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
444
445 xwindow = g_new0 (GstXWindow, 1);
446
447 xwindow->width = width;
448 xwindow->height = height;
449 xwindow->internal = TRUE;
450
451 g_mutex_lock (&ximagesink->x_lock);
452
453 xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
454 ximagesink->xcontext->root,
455 0, 0, width, height, 0, 0, ximagesink->xcontext->black);
456
457 /* We have to do that to prevent X from redrawing the background on
458 ConfigureNotify. This takes away flickering of video when resizing. */
459 XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
460
461 /* set application name as a title */
462 gst_x_image_sink_xwindow_set_title (ximagesink, xwindow, NULL);
463
464 if (ximagesink->handle_events) {
465 Atom wm_delete;
466
467 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
468 StructureNotifyMask | PointerMotionMask | KeyPressMask |
469 KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
470
471 /* Tell the window manager we'd like delete client messages instead of
472 * being killed */
473 wm_delete = XInternAtom (ximagesink->xcontext->disp,
474 "WM_DELETE_WINDOW", False);
475 (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
476 &wm_delete, 1);
477 }
478
479 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
480 0, &values);
481
482 XMapRaised (ximagesink->xcontext->disp, xwindow->win);
483
484 XSync (ximagesink->xcontext->disp, FALSE);
485
486 g_mutex_unlock (&ximagesink->x_lock);
487
488 gst_x_image_sink_xwindow_decorate (ximagesink, xwindow);
489
490 gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink),
491 xwindow->win);
492
493 return xwindow;
494 }
495
496 /* This function destroys a GstXWindow */
497 static void
gst_x_image_sink_xwindow_destroy(GstXImageSink * ximagesink,GstXWindow * xwindow)498 gst_x_image_sink_xwindow_destroy (GstXImageSink * ximagesink,
499 GstXWindow * xwindow)
500 {
501 g_return_if_fail (xwindow != NULL);
502 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
503
504 g_mutex_lock (&ximagesink->x_lock);
505
506 /* If we did not create that window we just free the GC and let it live */
507 if (xwindow->internal)
508 XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
509 else
510 XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
511
512 XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
513
514 XSync (ximagesink->xcontext->disp, FALSE);
515
516 g_mutex_unlock (&ximagesink->x_lock);
517
518 g_free (xwindow);
519 }
520
521 static void
gst_x_image_sink_xwindow_update_geometry(GstXImageSink * ximagesink)522 gst_x_image_sink_xwindow_update_geometry (GstXImageSink * ximagesink)
523 {
524 XWindowAttributes attr;
525 gboolean reconfigure;
526
527 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
528
529 /* Update the window geometry */
530 g_mutex_lock (&ximagesink->x_lock);
531 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
532 g_mutex_unlock (&ximagesink->x_lock);
533 return;
534 }
535
536 XGetWindowAttributes (ximagesink->xcontext->disp,
537 ximagesink->xwindow->win, &attr);
538
539 /* Check if we would suggest a different width/height now */
540 reconfigure = (ximagesink->xwindow->width != attr.width)
541 || (ximagesink->xwindow->height != attr.height);
542 ximagesink->xwindow->width = attr.width;
543 ximagesink->xwindow->height = attr.height;
544
545 g_mutex_unlock (&ximagesink->x_lock);
546
547 if (reconfigure)
548 gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad,
549 gst_event_new_reconfigure ());
550 }
551
552 static void
gst_x_image_sink_xwindow_clear(GstXImageSink * ximagesink,GstXWindow * xwindow)553 gst_x_image_sink_xwindow_clear (GstXImageSink * ximagesink,
554 GstXWindow * xwindow)
555 {
556 g_return_if_fail (xwindow != NULL);
557 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
558
559 g_mutex_lock (&ximagesink->x_lock);
560
561 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
562 ximagesink->xcontext->black);
563
564 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
565 0, 0, xwindow->width, xwindow->height);
566
567 XSync (ximagesink->xcontext->disp, FALSE);
568
569 g_mutex_unlock (&ximagesink->x_lock);
570 }
571
572 /* This function handles XEvents that might be in the queue. It generates
573 GstEvent that will be sent upstream in the pipeline to handle interactivity
574 and navigation.*/
575 static void
gst_x_image_sink_handle_xevents(GstXImageSink * ximagesink)576 gst_x_image_sink_handle_xevents (GstXImageSink * ximagesink)
577 {
578 XEvent e;
579 gint pointer_x = 0, pointer_y = 0;
580 gboolean pointer_moved = FALSE;
581 gboolean exposed = FALSE, configured = FALSE;
582
583 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
584
585 /* Then we get all pointer motion events, only the last position is
586 interesting. */
587 g_mutex_lock (&ximagesink->flow_lock);
588 g_mutex_lock (&ximagesink->x_lock);
589 while (XCheckWindowEvent (ximagesink->xcontext->disp,
590 ximagesink->xwindow->win, PointerMotionMask, &e)) {
591 g_mutex_unlock (&ximagesink->x_lock);
592 g_mutex_unlock (&ximagesink->flow_lock);
593
594 switch (e.type) {
595 case MotionNotify:
596 pointer_x = e.xmotion.x;
597 pointer_y = e.xmotion.y;
598 pointer_moved = TRUE;
599 break;
600 default:
601 break;
602 }
603 g_mutex_lock (&ximagesink->flow_lock);
604 g_mutex_lock (&ximagesink->x_lock);
605 }
606
607 if (pointer_moved) {
608 g_mutex_unlock (&ximagesink->x_lock);
609 g_mutex_unlock (&ximagesink->flow_lock);
610
611 GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
612 pointer_x, pointer_y);
613 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
614 "mouse-move", 0, pointer_x, pointer_y);
615
616 g_mutex_lock (&ximagesink->flow_lock);
617 g_mutex_lock (&ximagesink->x_lock);
618 }
619
620 /* We get all remaining events on our window to throw them upstream */
621 while (XCheckWindowEvent (ximagesink->xcontext->disp,
622 ximagesink->xwindow->win,
623 KeyPressMask | KeyReleaseMask |
624 ButtonPressMask | ButtonReleaseMask, &e)) {
625 KeySym keysym;
626 const char *key_str = NULL;
627
628 /* We lock only for the X function call */
629 g_mutex_unlock (&ximagesink->x_lock);
630 g_mutex_unlock (&ximagesink->flow_lock);
631
632 switch (e.type) {
633 case ButtonPress:
634 /* Mouse button pressed/released over our window. We send upstream
635 events for interactivity/navigation */
636 GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
637 e.xbutton.button, e.xbutton.x, e.xbutton.x);
638 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
639 "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
640 break;
641 case ButtonRelease:
642 GST_DEBUG ("ximagesink button %d release over window at %d,%d",
643 e.xbutton.button, e.xbutton.x, e.xbutton.x);
644 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
645 "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
646 break;
647 case KeyPress:
648 case KeyRelease:
649 /* Key pressed/released over our window. We send upstream
650 events for interactivity/navigation */
651 g_mutex_lock (&ximagesink->x_lock);
652 if (ximagesink->xcontext->use_xkb) {
653 keysym = XkbKeycodeToKeysym (ximagesink->xcontext->disp,
654 e.xkey.keycode, 0, 0);
655 } else {
656 keysym = XKeycodeToKeysym (ximagesink->xcontext->disp,
657 e.xkey.keycode, 0);
658 }
659 if (keysym != NoSymbol) {
660 key_str = XKeysymToString (keysym);
661 } else {
662 key_str = "unknown";
663 }
664 g_mutex_unlock (&ximagesink->x_lock);
665 GST_DEBUG_OBJECT (ximagesink,
666 "key %d pressed over window at %d,%d (%s)",
667 e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
668 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
669 e.type == KeyPress ? "key-press" : "key-release", key_str);
670 break;
671 default:
672 GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
673 e.type);
674 }
675 g_mutex_lock (&ximagesink->flow_lock);
676 g_mutex_lock (&ximagesink->x_lock);
677 }
678
679 /* Handle Expose */
680 while (XCheckWindowEvent (ximagesink->xcontext->disp,
681 ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
682 switch (e.type) {
683 case Expose:
684 exposed = TRUE;
685 break;
686 case ConfigureNotify:
687 g_mutex_unlock (&ximagesink->x_lock);
688 gst_x_image_sink_xwindow_update_geometry (ximagesink);
689 g_mutex_lock (&ximagesink->x_lock);
690 configured = TRUE;
691 break;
692 default:
693 break;
694 }
695 }
696
697 if (ximagesink->handle_expose && (exposed || configured)) {
698 g_mutex_unlock (&ximagesink->x_lock);
699 g_mutex_unlock (&ximagesink->flow_lock);
700
701 gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink));
702
703 g_mutex_lock (&ximagesink->flow_lock);
704 g_mutex_lock (&ximagesink->x_lock);
705 }
706
707 /* Handle Display events */
708 while (XPending (ximagesink->xcontext->disp)) {
709 XNextEvent (ximagesink->xcontext->disp, &e);
710
711 switch (e.type) {
712 case ClientMessage:{
713 Atom wm_delete;
714
715 wm_delete = XInternAtom (ximagesink->xcontext->disp,
716 "WM_DELETE_WINDOW", False);
717 if (wm_delete == (Atom) e.xclient.data.l[0]) {
718 /* Handle window deletion by posting an error on the bus */
719 GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
720 ("Output window was closed"), (NULL));
721
722 g_mutex_unlock (&ximagesink->x_lock);
723 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
724 ximagesink->xwindow = NULL;
725 g_mutex_lock (&ximagesink->x_lock);
726 }
727 break;
728 }
729 default:
730 break;
731 }
732 }
733
734 g_mutex_unlock (&ximagesink->x_lock);
735 g_mutex_unlock (&ximagesink->flow_lock);
736 }
737
738 static gpointer
gst_x_image_sink_event_thread(GstXImageSink * ximagesink)739 gst_x_image_sink_event_thread (GstXImageSink * ximagesink)
740 {
741 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
742
743 GST_OBJECT_LOCK (ximagesink);
744 while (ximagesink->running) {
745 GST_OBJECT_UNLOCK (ximagesink);
746
747 if (ximagesink->xwindow) {
748 gst_x_image_sink_handle_xevents (ximagesink);
749 }
750 /* FIXME: do we want to align this with the framerate or anything else? */
751 g_usleep (G_USEC_PER_SEC / 20);
752
753 GST_OBJECT_LOCK (ximagesink);
754 }
755 GST_OBJECT_UNLOCK (ximagesink);
756
757 return NULL;
758 }
759
760 static void
gst_x_image_sink_manage_event_thread(GstXImageSink * ximagesink)761 gst_x_image_sink_manage_event_thread (GstXImageSink * ximagesink)
762 {
763 GThread *thread = NULL;
764
765 /* don't start the thread too early */
766 if (ximagesink->xcontext == NULL) {
767 return;
768 }
769
770 GST_OBJECT_LOCK (ximagesink);
771 if (ximagesink->handle_expose || ximagesink->handle_events) {
772 if (!ximagesink->event_thread) {
773 /* Setup our event listening thread */
774 GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
775 ximagesink->handle_expose, ximagesink->handle_events);
776 ximagesink->running = TRUE;
777 ximagesink->event_thread = g_thread_try_new ("ximagesink-events",
778 (GThreadFunc) gst_x_image_sink_event_thread, ximagesink, NULL);
779 }
780 } else {
781 if (ximagesink->event_thread) {
782 GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
783 ximagesink->handle_expose, ximagesink->handle_events);
784 ximagesink->running = FALSE;
785 /* grab thread and mark it as NULL */
786 thread = ximagesink->event_thread;
787 ximagesink->event_thread = NULL;
788 }
789 }
790 GST_OBJECT_UNLOCK (ximagesink);
791
792 /* Wait for our event thread to finish */
793 if (thread)
794 g_thread_join (thread);
795
796 }
797
798
799 /* This function calculates the pixel aspect ratio based on the properties
800 * in the xcontext structure and stores it there. */
801 static void
gst_x_image_sink_calculate_pixel_aspect_ratio(GstXContext * xcontext)802 gst_x_image_sink_calculate_pixel_aspect_ratio (GstXContext * xcontext)
803 {
804 static const gint par[][2] = {
805 {1, 1}, /* regular screen */
806 {16, 15}, /* PAL TV */
807 {11, 10}, /* 525 line Rec.601 video */
808 {54, 59}, /* 625 line Rec.601 video */
809 {64, 45}, /* 1280x1024 on 16:9 display */
810 {5, 3}, /* 1280x1024 on 4:3 display */
811 {4, 3} /* 800x600 on 16:9 display */
812 };
813 gint i;
814 gint index;
815 gdouble ratio;
816 gdouble delta;
817
818 #define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1])))
819
820 /* first calculate the "real" ratio based on the X values;
821 * which is the "physical" w/h divided by the w/h in pixels of the display */
822 ratio = (gdouble) (xcontext->widthmm * xcontext->height)
823 / (xcontext->heightmm * xcontext->width);
824
825 /* DirectFB's X in 720x576 reports the physical dimensions wrong, so
826 * override here */
827 if (xcontext->width == 720 && xcontext->height == 576) {
828 ratio = 4.0 * 576 / (3.0 * 720);
829 }
830 GST_DEBUG ("calculated pixel aspect ratio: %f", ratio);
831
832 /* now find the one from par[][2] with the lowest delta to the real one */
833 delta = DELTA (0);
834 index = 0;
835
836 for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) {
837 gdouble this_delta = DELTA (i);
838
839 if (this_delta < delta) {
840 index = i;
841 delta = this_delta;
842 }
843 }
844
845 GST_DEBUG ("Decided on index %d (%d/%d)", index,
846 par[index][0], par[index][1]);
847
848 g_free (xcontext->par);
849 xcontext->par = g_new0 (GValue, 1);
850 g_value_init (xcontext->par, GST_TYPE_FRACTION);
851 gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]);
852 GST_DEBUG ("set xcontext PAR to %d/%d",
853 gst_value_get_fraction_numerator (xcontext->par),
854 gst_value_get_fraction_denominator (xcontext->par));
855 }
856
857 /* This function gets the X Display and global info about it. Everything is
858 stored in our object and will be cleaned when the object is disposed. Note
859 here that caps for supported format are generated without any window or
860 image creation */
861 static GstXContext *
gst_x_image_sink_xcontext_get(GstXImageSink * ximagesink)862 gst_x_image_sink_xcontext_get (GstXImageSink * ximagesink)
863 {
864 GstXContext *xcontext = NULL;
865 XPixmapFormatValues *px_formats = NULL;
866 gint nb_formats = 0, i;
867 gint endianness;
868 GstVideoFormat vformat;
869 guint32 alpha_mask;
870 int opcode, event, err;
871 int major = XkbMajorVersion;
872 int minor = XkbMinorVersion;
873
874 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
875
876 xcontext = g_new0 (GstXContext, 1);
877
878 g_mutex_lock (&ximagesink->x_lock);
879
880 xcontext->disp = XOpenDisplay (ximagesink->display_name);
881
882 if (!xcontext->disp) {
883 g_mutex_unlock (&ximagesink->x_lock);
884 g_free (xcontext);
885 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
886 ("Could not initialise X output"), ("Could not open display"));
887 return NULL;
888 }
889
890 xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
891 xcontext->screen_num = DefaultScreen (xcontext->disp);
892 xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
893 xcontext->root = DefaultRootWindow (xcontext->disp);
894 xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
895 xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
896 xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
897
898 xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
899 xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
900 xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
901 xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
902
903 GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
904 xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
905
906 gst_x_image_sink_calculate_pixel_aspect_ratio (xcontext);
907
908 /* We get supported pixmap formats at supported depth */
909 px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
910
911 if (!px_formats) {
912 XCloseDisplay (xcontext->disp);
913 g_mutex_unlock (&ximagesink->x_lock);
914 g_free (xcontext->par);
915 g_free (xcontext);
916 GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS,
917 ("Could not get supported pixmap formats"), (NULL));
918 return NULL;
919 }
920
921 /* We get bpp value corresponding to our running depth */
922 for (i = 0; i < nb_formats; i++) {
923 if (px_formats[i].depth == xcontext->depth)
924 xcontext->bpp = px_formats[i].bits_per_pixel;
925 }
926
927 XFree (px_formats);
928
929 endianness = (ImageByteOrder (xcontext->disp) ==
930 LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
931
932 /* Search for XShm extension support */
933 #ifdef HAVE_XSHM
934 if (XShmQueryExtension (xcontext->disp) &&
935 gst_x_image_sink_check_xshm_calls (ximagesink, xcontext)) {
936 xcontext->use_xshm = TRUE;
937 GST_DEBUG ("ximagesink is using XShm extension");
938 } else
939 #endif /* HAVE_XSHM */
940 {
941 xcontext->use_xshm = FALSE;
942 GST_DEBUG ("ximagesink is not using XShm extension");
943 }
944 if (XkbQueryExtension (xcontext->disp, &opcode, &event, &err, &major, &minor)) {
945 xcontext->use_xkb = TRUE;
946 GST_DEBUG ("ximagesink is using Xkb extension");
947 } else {
948 xcontext->use_xkb = FALSE;
949 GST_DEBUG ("ximagesink is not using Xkb extension");
950 }
951
952 /* extrapolate alpha mask */
953 if (xcontext->depth == 32) {
954 alpha_mask = ~(xcontext->visual->red_mask
955 | xcontext->visual->green_mask | xcontext->visual->blue_mask);
956 } else {
957 alpha_mask = 0;
958 }
959
960 vformat =
961 gst_video_format_from_masks (xcontext->depth, xcontext->bpp, endianness,
962 xcontext->visual->red_mask, xcontext->visual->green_mask,
963 xcontext->visual->blue_mask, alpha_mask);
964
965 if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
966 goto unknown_format;
967
968 /* update object's par with calculated one if not set yet */
969 if (!ximagesink->par) {
970 ximagesink->par = g_new0 (GValue, 1);
971 gst_value_init_and_copy (ximagesink->par, xcontext->par);
972 GST_DEBUG_OBJECT (ximagesink, "set calculated PAR on object's PAR");
973 }
974 xcontext->caps = gst_caps_new_simple ("video/x-raw",
975 "format", G_TYPE_STRING, gst_video_format_to_string (vformat),
976 "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
977 "height", GST_TYPE_INT_RANGE, 1, G_MAXINT,
978 "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
979 if (ximagesink->par) {
980 int nom, den;
981
982 nom = gst_value_get_fraction_numerator (ximagesink->par);
983 den = gst_value_get_fraction_denominator (ximagesink->par);
984 gst_caps_set_simple (xcontext->caps, "pixel-aspect-ratio",
985 GST_TYPE_FRACTION, nom, den, NULL);
986 }
987
988 g_mutex_unlock (&ximagesink->x_lock);
989
990 return xcontext;
991
992 /* ERRORS */
993 unknown_format:
994 {
995 GST_ERROR_OBJECT (ximagesink, "unknown format");
996 return NULL;
997 }
998 }
999
1000 /* This function cleans the X context. Closing the Display and unrefing the
1001 caps for supported formats. */
1002 static void
gst_x_image_sink_xcontext_clear(GstXImageSink * ximagesink)1003 gst_x_image_sink_xcontext_clear (GstXImageSink * ximagesink)
1004 {
1005 GstXContext *xcontext;
1006
1007 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
1008
1009 GST_OBJECT_LOCK (ximagesink);
1010 if (ximagesink->xcontext == NULL) {
1011 GST_OBJECT_UNLOCK (ximagesink);
1012 return;
1013 }
1014
1015 /* Take the xcontext reference and NULL it while we
1016 * clean it up, so that any buffer-alloced buffers
1017 * arriving after this will be freed correctly */
1018 xcontext = ximagesink->xcontext;
1019 ximagesink->xcontext = NULL;
1020
1021 GST_OBJECT_UNLOCK (ximagesink);
1022
1023 gst_caps_unref (xcontext->caps);
1024 g_free (xcontext->par);
1025 g_free (ximagesink->par);
1026 ximagesink->par = NULL;
1027
1028 if (xcontext->last_caps)
1029 gst_caps_replace (&xcontext->last_caps, NULL);
1030
1031 g_mutex_lock (&ximagesink->x_lock);
1032
1033 XCloseDisplay (xcontext->disp);
1034
1035 g_mutex_unlock (&ximagesink->x_lock);
1036
1037 g_free (xcontext);
1038 }
1039
1040 /* Element stuff */
1041
1042 static GstCaps *
gst_x_image_sink_getcaps(GstBaseSink * bsink,GstCaps * filter)1043 gst_x_image_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1044 {
1045 GstXImageSink *ximagesink;
1046 GstCaps *caps;
1047 int i;
1048
1049 ximagesink = GST_X_IMAGE_SINK (bsink);
1050
1051 g_mutex_lock (&ximagesink->x_lock);
1052 if (ximagesink->xcontext) {
1053 GstCaps *caps;
1054
1055 caps = gst_caps_ref (ximagesink->xcontext->caps);
1056
1057 if (filter) {
1058 GstCaps *intersection;
1059
1060 intersection =
1061 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1062 gst_caps_unref (caps);
1063 caps = intersection;
1064 }
1065
1066 if (gst_caps_is_empty (caps)) {
1067 g_mutex_unlock (&ximagesink->x_lock);
1068 return caps;
1069 }
1070
1071 if (ximagesink->xwindow && ximagesink->xwindow->width) {
1072 GstStructure *s0, *s1;
1073
1074 caps = gst_caps_make_writable (caps);
1075
1076 /* There can only be a single structure because the xcontext
1077 * caps only have a single structure */
1078 s0 = gst_caps_get_structure (caps, 0);
1079 s1 = gst_structure_copy (gst_caps_get_structure (caps, 0));
1080
1081 gst_structure_set (s0, "width", G_TYPE_INT, ximagesink->xwindow->width,
1082 "height", G_TYPE_INT, ximagesink->xwindow->height, NULL);
1083 gst_caps_append_structure (caps, s1);
1084
1085 /* This will not change the order but will remove the
1086 * fixed width/height caps again if not possible
1087 * upstream */
1088 if (filter) {
1089 GstCaps *intersection;
1090
1091 intersection =
1092 gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1093 gst_caps_unref (caps);
1094 caps = intersection;
1095 }
1096 }
1097
1098 g_mutex_unlock (&ximagesink->x_lock);
1099 return caps;
1100 }
1101 g_mutex_unlock (&ximagesink->x_lock);
1102
1103 /* get a template copy and add the pixel aspect ratio */
1104 caps = gst_pad_get_pad_template_caps (GST_BASE_SINK (ximagesink)->sinkpad);
1105 if (ximagesink->par) {
1106 caps = gst_caps_make_writable (caps);
1107 for (i = 0; i < gst_caps_get_size (caps); ++i) {
1108 GstStructure *structure = gst_caps_get_structure (caps, i);
1109 int nom, den;
1110
1111 nom = gst_value_get_fraction_numerator (ximagesink->par);
1112 den = gst_value_get_fraction_denominator (ximagesink->par);
1113 gst_structure_set (structure, "pixel-aspect-ratio",
1114 GST_TYPE_FRACTION, nom, den, NULL);
1115 }
1116 }
1117
1118 if (filter) {
1119 GstCaps *intersection;
1120
1121 intersection =
1122 gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1123 gst_caps_unref (caps);
1124 caps = intersection;
1125 }
1126
1127 return caps;
1128 }
1129
1130 static GstBufferPool *
gst_x_image_sink_create_pool(GstXImageSink * ximagesink,GstCaps * caps,gsize size,gint min)1131 gst_x_image_sink_create_pool (GstXImageSink * ximagesink, GstCaps * caps,
1132 gsize size, gint min)
1133 {
1134 static GstAllocationParams params = { 0, 15, 0, 0, };
1135 GstBufferPool *pool;
1136 GstStructure *config;
1137
1138 /* create a new pool for the new configuration */
1139 pool = gst_ximage_buffer_pool_new (ximagesink);
1140
1141 config = gst_buffer_pool_get_config (pool);
1142 gst_buffer_pool_config_set_params (config, caps, size, min, 0);
1143 gst_buffer_pool_config_set_allocator (config, NULL, ¶ms);
1144
1145 if (!gst_buffer_pool_set_config (pool, config))
1146 goto config_failed;
1147
1148 return pool;
1149
1150 config_failed:
1151 {
1152 GST_WARNING_OBJECT (ximagesink, "failed setting config");
1153 gst_object_unref (pool);
1154 return NULL;
1155 }
1156 }
1157
1158 static gboolean
gst_x_image_sink_setcaps(GstBaseSink * bsink,GstCaps * caps)1159 gst_x_image_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1160 {
1161 GstXImageSink *ximagesink;
1162 GstStructure *structure;
1163 GstVideoInfo info;
1164 GstBufferPool *newpool, *oldpool;
1165 const GValue *par;
1166
1167 ximagesink = GST_X_IMAGE_SINK (bsink);
1168
1169 if (!ximagesink->xcontext)
1170 return FALSE;
1171
1172 GST_DEBUG_OBJECT (ximagesink,
1173 "sinkconnect possible caps %" GST_PTR_FORMAT " with given caps %"
1174 GST_PTR_FORMAT, ximagesink->xcontext->caps, caps);
1175
1176 /* We intersect those caps with our template to make sure they are correct */
1177 if (!gst_caps_can_intersect (ximagesink->xcontext->caps, caps))
1178 goto incompatible_caps;
1179
1180 if (!gst_video_info_from_caps (&info, caps))
1181 goto invalid_format;
1182
1183 structure = gst_caps_get_structure (caps, 0);
1184 /* if the caps contain pixel-aspect-ratio, they have to match ours,
1185 * otherwise linking should fail */
1186 par = gst_structure_get_value (structure, "pixel-aspect-ratio");
1187 if (par) {
1188 if (ximagesink->par) {
1189 if (gst_value_compare (par, ximagesink->par) != GST_VALUE_EQUAL) {
1190 goto wrong_aspect;
1191 }
1192 } else if (ximagesink->xcontext->par) {
1193 if (gst_value_compare (par, ximagesink->xcontext->par) != GST_VALUE_EQUAL) {
1194 goto wrong_aspect;
1195 }
1196 }
1197 }
1198
1199 GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1200 GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1201 ximagesink->fps_n = info.fps_n;
1202 ximagesink->fps_d = info.fps_d;
1203
1204 /* Notify application to set xwindow id now */
1205 g_mutex_lock (&ximagesink->flow_lock);
1206 if (!ximagesink->xwindow) {
1207 g_mutex_unlock (&ximagesink->flow_lock);
1208 gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1209 } else {
1210 g_mutex_unlock (&ximagesink->flow_lock);
1211 }
1212
1213 /* Creating our window and our image */
1214 if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1215 GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1216 goto invalid_size;
1217
1218 g_mutex_lock (&ximagesink->flow_lock);
1219 if (!ximagesink->xwindow) {
1220 ximagesink->xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1221 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1222 }
1223
1224 ximagesink->info = info;
1225
1226 /* Remember to draw borders for next frame */
1227 ximagesink->draw_border = TRUE;
1228
1229 /* create a new internal pool for the new configuration */
1230 newpool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 2);
1231
1232 /* we don't activate the internal pool yet as it may not be needed */
1233 oldpool = ximagesink->pool;
1234 ximagesink->pool = newpool;
1235 g_mutex_unlock (&ximagesink->flow_lock);
1236
1237 /* deactivate and unref the old internal pool */
1238 if (oldpool) {
1239 gst_buffer_pool_set_active (oldpool, FALSE);
1240 gst_object_unref (oldpool);
1241 }
1242
1243 return TRUE;
1244
1245 /* ERRORS */
1246 incompatible_caps:
1247 {
1248 GST_ERROR_OBJECT (ximagesink, "caps incompatible");
1249 return FALSE;
1250 }
1251 invalid_format:
1252 {
1253 GST_ERROR_OBJECT (ximagesink, "caps invalid");
1254 return FALSE;
1255 }
1256 wrong_aspect:
1257 {
1258 GST_INFO_OBJECT (ximagesink, "pixel aspect ratio does not match");
1259 return FALSE;
1260 }
1261 invalid_size:
1262 {
1263 GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1264 ("Invalid image size."));
1265 return FALSE;
1266 }
1267 }
1268
1269 static GstStateChangeReturn
gst_x_image_sink_change_state(GstElement * element,GstStateChange transition)1270 gst_x_image_sink_change_state (GstElement * element, GstStateChange transition)
1271 {
1272 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1273 GstXImageSink *ximagesink;
1274 GstXContext *xcontext = NULL;
1275
1276 ximagesink = GST_X_IMAGE_SINK (element);
1277
1278 switch (transition) {
1279 case GST_STATE_CHANGE_NULL_TO_READY:
1280 /* Initializing the XContext */
1281 if (ximagesink->xcontext == NULL) {
1282 xcontext = gst_x_image_sink_xcontext_get (ximagesink);
1283 if (xcontext == NULL) {
1284 ret = GST_STATE_CHANGE_FAILURE;
1285 goto beach;
1286 }
1287 GST_OBJECT_LOCK (ximagesink);
1288 if (xcontext)
1289 ximagesink->xcontext = xcontext;
1290 GST_OBJECT_UNLOCK (ximagesink);
1291 }
1292
1293 /* call XSynchronize with the current value of synchronous */
1294 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1295 ximagesink->synchronous ? "TRUE" : "FALSE");
1296 g_mutex_lock (&ximagesink->x_lock);
1297 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1298 g_mutex_unlock (&ximagesink->x_lock);
1299 gst_x_image_sink_manage_event_thread (ximagesink);
1300 break;
1301 case GST_STATE_CHANGE_READY_TO_PAUSED:
1302 g_mutex_lock (&ximagesink->flow_lock);
1303 if (ximagesink->xwindow)
1304 gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1305 g_mutex_unlock (&ximagesink->flow_lock);
1306 break;
1307 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1308 break;
1309 default:
1310 break;
1311 }
1312
1313 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1314
1315 switch (transition) {
1316 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1317 break;
1318 case GST_STATE_CHANGE_PAUSED_TO_READY:
1319 ximagesink->fps_n = 0;
1320 ximagesink->fps_d = 1;
1321 GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
1322 GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
1323 g_mutex_lock (&ximagesink->flow_lock);
1324 if (ximagesink->pool)
1325 gst_buffer_pool_set_active (ximagesink->pool, FALSE);
1326 g_mutex_unlock (&ximagesink->flow_lock);
1327 break;
1328 case GST_STATE_CHANGE_READY_TO_NULL:
1329 gst_x_image_sink_reset (ximagesink);
1330 break;
1331 default:
1332 break;
1333 }
1334
1335 beach:
1336 return ret;
1337 }
1338
1339 static void
gst_x_image_sink_get_times(GstBaseSink * bsink,GstBuffer * buf,GstClockTime * start,GstClockTime * end)1340 gst_x_image_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
1341 GstClockTime * start, GstClockTime * end)
1342 {
1343 GstXImageSink *ximagesink;
1344
1345 ximagesink = GST_X_IMAGE_SINK (bsink);
1346
1347 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
1348 *start = GST_BUFFER_TIMESTAMP (buf);
1349 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
1350 *end = *start + GST_BUFFER_DURATION (buf);
1351 } else {
1352 if (ximagesink->fps_n > 0) {
1353 *end = *start +
1354 gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
1355 ximagesink->fps_n);
1356 }
1357 }
1358 }
1359 }
1360
1361 static GstFlowReturn
gst_x_image_sink_show_frame(GstVideoSink * vsink,GstBuffer * buf)1362 gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
1363 {
1364 GstFlowReturn res;
1365 GstXImageSink *ximagesink;
1366 GstXImageMemory *mem;
1367 GstBuffer *to_put = NULL;
1368
1369 ximagesink = GST_X_IMAGE_SINK (vsink);
1370
1371 if (gst_buffer_n_memory (buf) == 1
1372 && (mem = (GstXImageMemory *) gst_buffer_peek_memory (buf, 0))
1373 && g_strcmp0 (mem->parent.allocator->mem_type, "ximage") == 0
1374 && mem->sink == ximagesink) {
1375 /* If this buffer has been allocated using our buffer management we simply
1376 put the ximage which is in the PRIVATE pointer */
1377 GST_LOG_OBJECT (ximagesink, "buffer from our pool, writing directly");
1378 to_put = buf;
1379 res = GST_FLOW_OK;
1380 } else {
1381 GstVideoFrame src, dest;
1382 GstBufferPoolAcquireParams params = { 0, };
1383
1384 /* Else we have to copy the data into our private image, */
1385 /* if we have one... */
1386 GST_LOG_OBJECT (ximagesink, "buffer not from our pool, copying");
1387
1388 /* an internal pool should have been created in setcaps */
1389 if (G_UNLIKELY (ximagesink->pool == NULL))
1390 goto no_pool;
1391
1392 if (!gst_buffer_pool_set_active (ximagesink->pool, TRUE))
1393 goto activate_failed;
1394
1395 /* take a buffer from our pool, if there is no buffer in the pool something
1396 * is seriously wrong, waiting for the pool here might deadlock when we try
1397 * to go to PAUSED because we never flush the pool. */
1398 params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT;
1399 res = gst_buffer_pool_acquire_buffer (ximagesink->pool, &to_put, ¶ms);
1400 if (res != GST_FLOW_OK)
1401 goto no_buffer;
1402
1403 GST_CAT_LOG_OBJECT (CAT_PERFORMANCE, ximagesink,
1404 "slow copy into bufferpool buffer %p", to_put);
1405
1406 if (!gst_video_frame_map (&src, &ximagesink->info, buf, GST_MAP_READ))
1407 goto invalid_buffer;
1408
1409 if (!gst_video_frame_map (&dest, &ximagesink->info, to_put, GST_MAP_WRITE)) {
1410 gst_video_frame_unmap (&src);
1411 goto invalid_buffer;
1412 }
1413
1414 gst_video_frame_copy (&dest, &src);
1415
1416 gst_video_frame_unmap (&dest);
1417 gst_video_frame_unmap (&src);
1418 }
1419
1420 if (!gst_x_image_sink_ximage_put (ximagesink, to_put))
1421 goto no_window;
1422
1423 done:
1424 if (to_put != buf)
1425 gst_buffer_unref (to_put);
1426
1427 return res;
1428
1429 /* ERRORS */
1430 no_pool:
1431 {
1432 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1433 ("Internal error: can't allocate images"),
1434 ("We don't have a bufferpool negotiated"));
1435 return GST_FLOW_ERROR;
1436 }
1437 no_buffer:
1438 {
1439 /* No image available. That's very bad ! */
1440 GST_WARNING_OBJECT (ximagesink, "could not create image");
1441 return GST_FLOW_OK;
1442 }
1443 invalid_buffer:
1444 {
1445 /* No Window available to put our image into */
1446 GST_WARNING_OBJECT (ximagesink, "could not map image");
1447 res = GST_FLOW_OK;
1448 goto done;
1449 }
1450 no_window:
1451 {
1452 /* No Window available to put our image into */
1453 GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
1454 res = GST_FLOW_ERROR;
1455 goto done;
1456 }
1457 activate_failed:
1458 {
1459 GST_ERROR_OBJECT (ximagesink, "failed to activate bufferpool.");
1460 res = GST_FLOW_ERROR;
1461 goto done;
1462 }
1463 }
1464
1465 static gboolean
gst_x_image_sink_event(GstBaseSink * sink,GstEvent * event)1466 gst_x_image_sink_event (GstBaseSink * sink, GstEvent * event)
1467 {
1468 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (sink);
1469
1470 switch (GST_EVENT_TYPE (event)) {
1471 case GST_EVENT_TAG:{
1472 GstTagList *l;
1473 gchar *title = NULL;
1474
1475 gst_event_parse_tag (event, &l);
1476 gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
1477
1478 if (title) {
1479 GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
1480 gst_x_image_sink_xwindow_set_title (ximagesink, ximagesink->xwindow,
1481 title);
1482
1483 g_free (title);
1484 }
1485 break;
1486 }
1487 default:
1488 break;
1489 }
1490 return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1491 }
1492
1493 static gboolean
gst_x_image_sink_propose_allocation(GstBaseSink * bsink,GstQuery * query)1494 gst_x_image_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
1495 {
1496 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (bsink);
1497 GstBufferPool *pool = NULL;
1498 GstCaps *caps;
1499 GstVideoInfo info;
1500 guint size;
1501 gboolean need_pool;
1502
1503 gst_query_parse_allocation (query, &caps, &need_pool);
1504
1505 if (caps == NULL)
1506 goto no_caps;
1507
1508 if (!gst_video_info_from_caps (&info, caps))
1509 goto invalid_caps;
1510
1511 /* the normal size of a frame */
1512 size = info.size;
1513
1514 if (need_pool) {
1515 pool = gst_x_image_sink_create_pool (ximagesink, caps, info.size, 0);
1516
1517 if (pool == NULL)
1518 goto no_pool;
1519 }
1520
1521 /* we need at least 2 buffer because we hold on to the last one */
1522 gst_query_add_allocation_pool (query, pool, size, 2, 0);
1523 if (pool)
1524 gst_object_unref (pool);
1525
1526 /* we also support various metadata */
1527 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
1528 gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
1529
1530 return TRUE;
1531
1532 /* ERRORS */
1533 no_caps:
1534 {
1535 GST_DEBUG_OBJECT (bsink, "no caps specified");
1536 return FALSE;
1537 }
1538 invalid_caps:
1539 {
1540 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
1541 return FALSE;
1542 }
1543 no_pool:
1544 {
1545 /* Already warned in create_pool */
1546 return FALSE;
1547 }
1548 }
1549
1550 /* Interfaces stuff */
1551 static void
gst_x_image_sink_navigation_send_event(GstNavigation * navigation,GstStructure * structure)1552 gst_x_image_sink_navigation_send_event (GstNavigation * navigation,
1553 GstStructure * structure)
1554 {
1555 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (navigation);
1556 GstEvent *event = NULL;
1557 gint x_offset, y_offset;
1558 gdouble x, y;
1559 gboolean handled = FALSE;
1560
1561 /* We are not converting the pointer coordinates as there's no hardware
1562 scaling done here. The only possible scaling is done by videoscale and
1563 videoscale will have to catch those events and transform the coordinates
1564 to match the applied scaling. So here we just add the offset if the image
1565 is centered in the window. */
1566
1567 /* We take the flow_lock while we look at the window */
1568 g_mutex_lock (&ximagesink->flow_lock);
1569
1570 if (!ximagesink->xwindow) {
1571 g_mutex_unlock (&ximagesink->flow_lock);
1572 gst_structure_free (structure);
1573 return;
1574 }
1575
1576 x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
1577 y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
1578
1579 g_mutex_unlock (&ximagesink->flow_lock);
1580
1581 if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
1582 x -= x_offset / 2;
1583 gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
1584 }
1585 if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
1586 y -= y_offset / 2;
1587 gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
1588 }
1589
1590 event = gst_event_new_navigation (structure);
1591 if (event) {
1592 gst_event_ref (event);
1593 handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (ximagesink), event);
1594
1595 if (!handled)
1596 gst_element_post_message (GST_ELEMENT_CAST (ximagesink),
1597 gst_navigation_message_new_event (GST_OBJECT_CAST (ximagesink),
1598 event));
1599
1600 gst_event_unref (event);
1601 }
1602 }
1603
1604 static void
gst_x_image_sink_navigation_init(GstNavigationInterface * iface)1605 gst_x_image_sink_navigation_init (GstNavigationInterface * iface)
1606 {
1607 iface->send_event = gst_x_image_sink_navigation_send_event;
1608 }
1609
1610 static void
gst_x_image_sink_set_window_handle(GstVideoOverlay * overlay,guintptr id)1611 gst_x_image_sink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
1612 {
1613 XID xwindow_id = id;
1614 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1615 GstXWindow *xwindow = NULL;
1616
1617 /* We acquire the stream lock while setting this window in the element.
1618 We are basically cleaning tons of stuff replacing the old window, putting
1619 images while we do that would surely crash */
1620 g_mutex_lock (&ximagesink->flow_lock);
1621
1622 /* If we already use that window return */
1623 if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
1624 g_mutex_unlock (&ximagesink->flow_lock);
1625 return;
1626 }
1627
1628 /* If the element has not initialized the X11 context try to do so */
1629 if (!ximagesink->xcontext &&
1630 !(ximagesink->xcontext = gst_x_image_sink_xcontext_get (ximagesink))) {
1631 g_mutex_unlock (&ximagesink->flow_lock);
1632 /* we have thrown a GST_ELEMENT_ERROR now */
1633 return;
1634 }
1635
1636 /* If a window is there already we destroy it */
1637 if (ximagesink->xwindow) {
1638 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1639 ximagesink->xwindow = NULL;
1640 }
1641
1642 /* If the xid is 0 we go back to an internal window */
1643 if (xwindow_id == 0) {
1644 /* If no width/height caps nego did not happen window will be created
1645 during caps nego then */
1646 if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
1647 xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1648 GST_VIDEO_SINK_WIDTH (ximagesink),
1649 GST_VIDEO_SINK_HEIGHT (ximagesink));
1650 }
1651 } else {
1652 xwindow = g_new0 (GstXWindow, 1);
1653
1654 xwindow->win = xwindow_id;
1655
1656 /* We set the events we want to receive and create a GC. */
1657 g_mutex_lock (&ximagesink->x_lock);
1658 xwindow->internal = FALSE;
1659 if (ximagesink->handle_events) {
1660 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1661 StructureNotifyMask | PointerMotionMask | KeyPressMask |
1662 KeyReleaseMask);
1663 }
1664
1665 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
1666 g_mutex_unlock (&ximagesink->x_lock);
1667 }
1668
1669 if (xwindow) {
1670 ximagesink->xwindow = xwindow;
1671 /* Update the window geometry, possibly generating a reconfigure event. */
1672 gst_x_image_sink_xwindow_update_geometry (ximagesink);
1673 }
1674
1675 g_mutex_unlock (&ximagesink->flow_lock);
1676 }
1677
1678 static void
gst_x_image_sink_expose(GstVideoOverlay * overlay)1679 gst_x_image_sink_expose (GstVideoOverlay * overlay)
1680 {
1681 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1682
1683 gst_x_image_sink_xwindow_update_geometry (ximagesink);
1684 gst_x_image_sink_ximage_put (ximagesink, NULL);
1685 }
1686
1687 static void
gst_x_image_sink_set_event_handling(GstVideoOverlay * overlay,gboolean handle_events)1688 gst_x_image_sink_set_event_handling (GstVideoOverlay * overlay,
1689 gboolean handle_events)
1690 {
1691 GstXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
1692
1693 ximagesink->handle_events = handle_events;
1694
1695 g_mutex_lock (&ximagesink->flow_lock);
1696
1697 if (G_UNLIKELY (!ximagesink->xwindow)) {
1698 g_mutex_unlock (&ximagesink->flow_lock);
1699 return;
1700 }
1701
1702 g_mutex_lock (&ximagesink->x_lock);
1703
1704 if (handle_events) {
1705 if (ximagesink->xwindow->internal) {
1706 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1707 ExposureMask | StructureNotifyMask | PointerMotionMask |
1708 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1709 } else {
1710 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1711 ExposureMask | StructureNotifyMask | PointerMotionMask |
1712 KeyPressMask | KeyReleaseMask);
1713 }
1714 } else {
1715 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
1716 }
1717
1718 g_mutex_unlock (&ximagesink->x_lock);
1719
1720 g_mutex_unlock (&ximagesink->flow_lock);
1721 }
1722
1723 static void
gst_x_image_sink_video_overlay_init(GstVideoOverlayInterface * iface)1724 gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface * iface)
1725 {
1726 iface->set_window_handle = gst_x_image_sink_set_window_handle;
1727 iface->expose = gst_x_image_sink_expose;
1728 iface->handle_events = gst_x_image_sink_set_event_handling;
1729 }
1730
1731 /* =========================================== */
1732 /* */
1733 /* Init & Class init */
1734 /* */
1735 /* =========================================== */
1736
1737 static void
gst_x_image_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1738 gst_x_image_sink_set_property (GObject * object, guint prop_id,
1739 const GValue * value, GParamSpec * pspec)
1740 {
1741 GstXImageSink *ximagesink;
1742
1743 g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1744
1745 ximagesink = GST_X_IMAGE_SINK (object);
1746
1747 switch (prop_id) {
1748 case PROP_DISPLAY:
1749 ximagesink->display_name = g_strdup (g_value_get_string (value));
1750 break;
1751 case PROP_SYNCHRONOUS:
1752 ximagesink->synchronous = g_value_get_boolean (value);
1753 if (ximagesink->xcontext) {
1754 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
1755 ximagesink->synchronous ? "TRUE" : "FALSE");
1756 g_mutex_lock (&ximagesink->x_lock);
1757 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
1758 g_mutex_unlock (&ximagesink->x_lock);
1759 }
1760 break;
1761 case PROP_FORCE_ASPECT_RATIO:
1762 ximagesink->keep_aspect = g_value_get_boolean (value);
1763 break;
1764 case PROP_PIXEL_ASPECT_RATIO:
1765 {
1766 GValue *tmp;
1767
1768 tmp = g_new0 (GValue, 1);
1769 g_value_init (tmp, GST_TYPE_FRACTION);
1770
1771 if (!g_value_transform (value, tmp)) {
1772 GST_WARNING_OBJECT (ximagesink,
1773 "Could not transform string to aspect ratio");
1774 g_free (tmp);
1775 } else {
1776 GST_DEBUG_OBJECT (ximagesink, "set PAR to %d/%d",
1777 gst_value_get_fraction_numerator (tmp),
1778 gst_value_get_fraction_denominator (tmp));
1779 g_free (ximagesink->par);
1780 ximagesink->par = tmp;
1781 }
1782 }
1783 break;
1784 case PROP_HANDLE_EVENTS:
1785 gst_x_image_sink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
1786 g_value_get_boolean (value));
1787 gst_x_image_sink_manage_event_thread (ximagesink);
1788 break;
1789 case PROP_HANDLE_EXPOSE:
1790 ximagesink->handle_expose = g_value_get_boolean (value);
1791 gst_x_image_sink_manage_event_thread (ximagesink);
1792 break;
1793 default:
1794 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1795 break;
1796 }
1797 }
1798
1799 static void
gst_x_image_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1800 gst_x_image_sink_get_property (GObject * object, guint prop_id,
1801 GValue * value, GParamSpec * pspec)
1802 {
1803 GstXImageSink *ximagesink;
1804
1805 g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
1806
1807 ximagesink = GST_X_IMAGE_SINK (object);
1808
1809 switch (prop_id) {
1810 case PROP_DISPLAY:
1811 g_value_set_string (value, ximagesink->display_name);
1812 break;
1813 case PROP_SYNCHRONOUS:
1814 g_value_set_boolean (value, ximagesink->synchronous);
1815 break;
1816 case PROP_FORCE_ASPECT_RATIO:
1817 g_value_set_boolean (value, ximagesink->keep_aspect);
1818 break;
1819 case PROP_PIXEL_ASPECT_RATIO:
1820 if (ximagesink->par)
1821 g_value_transform (ximagesink->par, value);
1822 break;
1823 case PROP_HANDLE_EVENTS:
1824 g_value_set_boolean (value, ximagesink->handle_events);
1825 break;
1826 case PROP_HANDLE_EXPOSE:
1827 g_value_set_boolean (value, ximagesink->handle_expose);
1828 break;
1829 case PROP_WINDOW_WIDTH:
1830 if (ximagesink->xwindow)
1831 g_value_set_uint64 (value, ximagesink->xwindow->width);
1832 else
1833 g_value_set_uint64 (value, 0);
1834 break;
1835 case PROP_WINDOW_HEIGHT:
1836 if (ximagesink->xwindow)
1837 g_value_set_uint64 (value, ximagesink->xwindow->height);
1838 else
1839 g_value_set_uint64 (value, 0);
1840 break;
1841 default:
1842 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1843 break;
1844 }
1845 }
1846
1847 static void
gst_x_image_sink_reset(GstXImageSink * ximagesink)1848 gst_x_image_sink_reset (GstXImageSink * ximagesink)
1849 {
1850 GThread *thread;
1851
1852 GST_OBJECT_LOCK (ximagesink);
1853 ximagesink->running = FALSE;
1854 /* grab thread and mark it as NULL */
1855 thread = ximagesink->event_thread;
1856 ximagesink->event_thread = NULL;
1857 GST_OBJECT_UNLOCK (ximagesink);
1858
1859 /* Wait for our event thread to finish before we clean up our stuff. */
1860 if (thread)
1861 g_thread_join (thread);
1862
1863 if (ximagesink->cur_image) {
1864 gst_buffer_unref (ximagesink->cur_image);
1865 ximagesink->cur_image = NULL;
1866 }
1867
1868 g_mutex_lock (&ximagesink->flow_lock);
1869
1870 if (ximagesink->pool) {
1871 gst_object_unref (ximagesink->pool);
1872 ximagesink->pool = NULL;
1873 }
1874
1875 if (ximagesink->xwindow) {
1876 gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1877 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1878 ximagesink->xwindow = NULL;
1879 }
1880 g_mutex_unlock (&ximagesink->flow_lock);
1881
1882 gst_x_image_sink_xcontext_clear (ximagesink);
1883 }
1884
1885 static void
gst_x_image_sink_finalize(GObject * object)1886 gst_x_image_sink_finalize (GObject * object)
1887 {
1888 GstXImageSink *ximagesink;
1889
1890 ximagesink = GST_X_IMAGE_SINK (object);
1891
1892 gst_x_image_sink_reset (ximagesink);
1893
1894 if (ximagesink->display_name) {
1895 g_free (ximagesink->display_name);
1896 ximagesink->display_name = NULL;
1897 }
1898 if (ximagesink->par) {
1899 g_free (ximagesink->par);
1900 ximagesink->par = NULL;
1901 }
1902 g_mutex_clear (&ximagesink->x_lock);
1903 g_mutex_clear (&ximagesink->flow_lock);
1904
1905 g_free (ximagesink->media_title);
1906
1907 G_OBJECT_CLASS (parent_class)->finalize (object);
1908 }
1909
1910 static void
gst_x_image_sink_init(GstXImageSink * ximagesink)1911 gst_x_image_sink_init (GstXImageSink * ximagesink)
1912 {
1913 ximagesink->display_name = NULL;
1914 ximagesink->xcontext = NULL;
1915 ximagesink->xwindow = NULL;
1916 ximagesink->cur_image = NULL;
1917
1918 ximagesink->event_thread = NULL;
1919 ximagesink->running = FALSE;
1920
1921 ximagesink->fps_n = 0;
1922 ximagesink->fps_d = 1;
1923
1924 g_mutex_init (&ximagesink->x_lock);
1925 g_mutex_init (&ximagesink->flow_lock);
1926
1927 ximagesink->par = NULL;
1928
1929 ximagesink->pool = NULL;
1930
1931 ximagesink->synchronous = FALSE;
1932 ximagesink->keep_aspect = TRUE;
1933 ximagesink->handle_events = TRUE;
1934 ximagesink->handle_expose = TRUE;
1935 }
1936
1937 static void
gst_x_image_sink_class_init(GstXImageSinkClass * klass)1938 gst_x_image_sink_class_init (GstXImageSinkClass * klass)
1939 {
1940 GObjectClass *gobject_class;
1941 GstElementClass *gstelement_class;
1942 GstBaseSinkClass *gstbasesink_class;
1943 GstVideoSinkClass *videosink_class;
1944
1945 gobject_class = (GObjectClass *) klass;
1946 gstelement_class = (GstElementClass *) klass;
1947 gstbasesink_class = (GstBaseSinkClass *) klass;
1948 videosink_class = (GstVideoSinkClass *) klass;
1949
1950 gobject_class->finalize = gst_x_image_sink_finalize;
1951 gobject_class->set_property = gst_x_image_sink_set_property;
1952 gobject_class->get_property = gst_x_image_sink_get_property;
1953
1954 g_object_class_install_property (gobject_class, PROP_DISPLAY,
1955 g_param_spec_string ("display", "Display", "X Display name",
1956 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1957 g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
1958 g_param_spec_boolean ("synchronous", "Synchronous",
1959 "When enabled, runs the X display in synchronous mode. "
1960 "(unrelated to A/V sync, used only for debugging)", FALSE,
1961 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1962 g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
1963 g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
1964 "When enabled, reverse caps negotiation (scaling) will respect "
1965 "original aspect ratio", TRUE,
1966 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1967 g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
1968 g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio",
1969 "The pixel aspect ratio of the device", "1/1",
1970 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1971 g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
1972 g_param_spec_boolean ("handle-events", "Handle XEvents",
1973 "When enabled, XEvents will be selected and handled", TRUE,
1974 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1975 g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
1976 g_param_spec_boolean ("handle-expose", "Handle expose",
1977 "When enabled, "
1978 "the current frame will always be drawn in response to X Expose "
1979 "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1980
1981 /**
1982 * GstXImageSink:window-width
1983 *
1984 * Actual width of the video window.
1985 */
1986 g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
1987 g_param_spec_uint64 ("window-width", "window-width",
1988 "Width of the window", 0, G_MAXUINT64, 0,
1989 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1990
1991 /**
1992 * GstXImageSink:window-height
1993 *
1994 * Actual height of the video window.
1995 */
1996 g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
1997 g_param_spec_uint64 ("window-height", "window-height",
1998 "Height of the window", 0, G_MAXUINT64, 0,
1999 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2000
2001 gst_element_class_set_static_metadata (gstelement_class,
2002 "Video sink", "Sink/Video",
2003 "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
2004
2005 gst_element_class_add_static_pad_template (gstelement_class,
2006 &gst_x_image_sink_sink_template_factory);
2007
2008 gstelement_class->change_state = gst_x_image_sink_change_state;
2009
2010 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_getcaps);
2011 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_setcaps);
2012 gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_x_image_sink_get_times);
2013 gstbasesink_class->propose_allocation =
2014 GST_DEBUG_FUNCPTR (gst_x_image_sink_propose_allocation);
2015 gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_x_image_sink_event);
2016
2017 videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_x_image_sink_show_frame);
2018 }
2019