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