xvimagesink.c 120 KB
Newer Older
1
/* GStreamer
2
 * Copyright (C) <2005> Julien Moutte <julien@moutte.net>
3
 *               <2009>,<2010> Stefan Kost <stefan.kost@nokia.com>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
20

Julien Moutte's avatar
Julien Moutte committed
21
22
23
24
/**
 * SECTION:element-xvimagesink
 *
 * XvImageSink renders video frames to a drawable (XWindow) on a local display
25
26
 * using the XVideo extension. Rendering to a remote display is theorically
 * possible but i doubt that the XVideo extension is actually available when
Julien Moutte's avatar
Julien Moutte committed
27
28
 * connecting to a remote display. This element can receive a Window ID from the
 * application through the XOverlay interface and will then render video frames
29
 * in this drawable. If no Window ID was provided by the application, the
Julien Moutte's avatar
Julien Moutte committed
30
 * element will create its own internal window and render into it.
31
32
 *
 * <refsect2>
Julien Moutte's avatar
Julien Moutte committed
33
34
 * <title>Scaling</title>
 * <para>
35
 * The XVideo extension, when it's available, handles hardware accelerated
Julien Moutte's avatar
Julien Moutte committed
36
37
 * scaling of video frames. This means that the element will just accept
 * incoming video frames no matter their geometry and will then put them to the
38
 * drawable scaling them on the fly. Using the #GstXvImageSink:force-aspect-ratio
Julien Moutte's avatar
Julien Moutte committed
39
40
41
 * property it is possible to enforce scaling with a constant aspect ratio,
 * which means drawing black borders around the video frame.
 * </para>
42
43
 * </refsect2>
 * <refsect2>
Julien Moutte's avatar
Julien Moutte committed
44
45
46
 * <title>Events</title>
 * <para>
 * XvImageSink 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
Julien Moutte's avatar
Julien Moutte committed
48
49
50
51
52
53
54
55
 * events and window state related events. Input events will be translated to
 * navigation events and pushed upstream for other elements to react on them.
 * This includes events such as pointer moves, key press/release, clicks etc...
 * Other events are used to handle the drawable appearance even when the data
 * is not flowing (GST_STATE_PAUSED). That means that even when the element is
 * paused, it will receive expose events from the drawable and draw the latest
 * frame with correct borders/aspect-ratio.
 * </para>
56
57
 * </refsect2>
 * <refsect2>
Julien Moutte's avatar
Julien Moutte committed
58
59
60
 * <title>Pixel aspect ratio</title>
 * <para>
 * When changing state to GST_STATE_READY, XvImageSink will open a connection to
61
 * the display specified in the #GstXvImageSink:display property or the
Julien Moutte's avatar
Julien Moutte committed
62
63
 * default display if nothing specified. Once this connection is open it will
 * inspect the display configuration including the physical display geometry and
64
 * then calculate the pixel aspect ratio. When receiving video frames with a
Julien Moutte's avatar
Julien Moutte committed
65
66
67
68
 * different pixel aspect ratio, XvImageSink will use hardware scaling to
 * display the video frames correctly on display's pixel aspect ratio.
 * Sometimes the calculated pixel aspect ratio can be wrong, it is
 * then possible to enforce a specific pixel aspect ratio using the
69
 * #GstXvImageSink:pixel-aspect-ratio property.
Julien Moutte's avatar
Julien Moutte committed
70
 * </para>
71
72
 * </refsect2>
 * <refsect2>
Julien Moutte's avatar
Julien Moutte committed
73
 * <title>Examples</title>
74
 * |[
Julien Moutte's avatar
Julien Moutte committed
75
 * gst-launch -v videotestsrc ! xvimagesink
76
 * ]| A pipeline to test hardware scaling.
Julien Moutte's avatar
Julien Moutte committed
77
 * When the test video signal appears you can resize the window and see that
78
79
 * video frames are scaled through hardware (no extra CPU cost).
 * |[
Julien Moutte's avatar
Julien Moutte committed
80
 * gst-launch -v videotestsrc ! xvimagesink force-aspect-ratio=true
81
82
83
84
 * ]| Same pipeline with #GstXvImageSink:force-aspect-ratio property set to true
 * You can observe the borders drawn around the scaled image respecting aspect
 * ratio.
 * |[
Julien Moutte's avatar
Julien Moutte committed
85
 * gst-launch -v videotestsrc ! navigationtest ! xvimagesink
86
 * ]| A pipeline to test navigation events.
Julien Moutte's avatar
Julien Moutte committed
87
 * While moving the mouse pointer over the test signal you will see a black box
88
 * following the mouse pointer. If you press the mouse button somewhere on the
Julien Moutte's avatar
Julien Moutte committed
89
90
91
92
 * video and release it somewhere else a green box will appear where you pressed
 * the button and a red one where you released it. (The navigationtest element
 * is part of gst-plugins-good.) You can observe here that even if the images
 * are scaled through hardware the pointer coordinates are converted back to the
93
94
 * original video frame geometry so that the box can be drawn to the correct
 * position. This also handles borders correctly, limiting coordinates to the
Julien Moutte's avatar
Julien Moutte committed
95
 * image area
96
 * |[
Julien Moutte's avatar
Julien Moutte committed
97
 * gst-launch -v videotestsrc ! video/x-raw-yuv, pixel-aspect-ratio=(fraction)4/3 ! xvimagesink
98
 * ]| This is faking a 4/3 pixel aspect ratio caps on video frames produced by
Julien Moutte's avatar
Julien Moutte committed
99
 * videotestsrc, in most cases the pixel aspect ratio of the display will be
100
 * 1/1. This means that XvImageSink will have to do the scaling to convert
Julien Moutte's avatar
Julien Moutte committed
101
 * incoming frames to a size that will match the display pixel aspect ratio
102
 * (from 320x240 to 320x180 in this case). Note that you might have to escape
Julien Moutte's avatar
Julien Moutte committed
103
 * some characters for your shell like '\(fraction\)'.
104
 * |[
Julien Moutte's avatar
Julien Moutte committed
105
 * gst-launch -v videotestsrc ! xvimagesink hue=100 saturation=-100 brightness=100
106
 * ]| Demonstrates how to use the colorbalance interface.
Julien Moutte's avatar
Julien Moutte committed
107
108
109
 * </refsect2>
 */

110
111
/* for developers: there are two useful tools : xvinfo and xvattr */

112
113
114
115
116
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* Our interfaces */
117
118
119
#include <gst/interfaces/navigation.h>
#include <gst/interfaces/xoverlay.h>
#include <gst/interfaces/colorbalance.h>
120
#include <gst/interfaces/propertyprobe.h>
121
122
/* Helper functions */
#include <gst/video/video.h>
123
124
125
126

/* Object header */
#include "xvimagesink.h"

127
128
129
130
/* Debugging category */
#include <gst/gstinfo.h>
GST_DEBUG_CATEGORY_STATIC (gst_debug_xvimagesink);
#define GST_CAT_DEFAULT gst_debug_xvimagesink
131
GST_DEBUG_CATEGORY_STATIC (GST_CAT_PERFORMANCE);
132

133
134
135
136
137
138
139
140
141
142
143
144
typedef struct
{
  unsigned long flags;
  unsigned long functions;
  unsigned long decorations;
  long input_mode;
  unsigned long status;
}
MotifWmHints, MwmHints;

#define MWM_HINTS_DECORATIONS   (1L << 1)

145
146
static void gst_xvimagesink_reset (GstXvImageSink * xvimagesink);

147
static GstBufferClass *xvimage_buffer_parent_class = NULL;
148
149
static void gst_xvimage_buffer_finalize (GstXvImageBuffer * xvimage);

150
static void gst_xvimagesink_xwindow_update_geometry (GstXvImageSink *
151
    xvimagesink);
152
153
154
155
static gint gst_xvimagesink_get_format_from_caps (GstXvImageSink * xvimagesink,
    GstCaps * caps);
static void gst_xvimagesink_expose (GstXOverlay * overlay);

156
157
/* Default template - initiated with class struct to allow gst-register to work
   without X running */
David Schleef's avatar
David Schleef committed
158
static GstStaticPadTemplate gst_xvimagesink_sink_template_factory =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
159
160
161
162
    GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-raw-rgb, "
163
        "framerate = (fraction) [ 0, MAX ], "
164
165
        "width = (int) [ 1, MAX ], "
        "height = (int) [ 1, MAX ]; "
166
        "video/x-raw-yuv, "
167
        "framerate = (fraction) [ 0, MAX ], "
168
        "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
169
170
171
172
    );

enum
{
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  PROP_0,
  PROP_CONTRAST,
  PROP_BRIGHTNESS,
  PROP_HUE,
  PROP_SATURATION,
  PROP_DISPLAY,
  PROP_SYNCHRONOUS,
  PROP_PIXEL_ASPECT_RATIO,
  PROP_FORCE_ASPECT_RATIO,
  PROP_HANDLE_EVENTS,
  PROP_DEVICE,
  PROP_DEVICE_NAME,
  PROP_HANDLE_EXPOSE,
  PROP_DOUBLE_BUFFER,
  PROP_AUTOPAINT_COLORKEY,
  PROP_COLORKEY,
  PROP_DRAW_BORDERS,
  PROP_WINDOW_WIDTH,
  PROP_WINDOW_HEIGHT
192
193
};

Stefan Kost's avatar
Stefan Kost committed
194
static void gst_xvimagesink_init_interfaces (GType type);
195
196
197
198

GST_BOILERPLATE_FULL (GstXvImageSink, gst_xvimagesink, GstVideoSink,
    GST_TYPE_VIDEO_SINK, gst_xvimagesink_init_interfaces);

199
200
201
202
203
204
205

/* ============================================================= */
/*                                                               */
/*                       Private Methods                         */
/*                                                               */
/* ============================================================= */

206
207
208
209
210
211
/* xvimage buffers */

#define GST_TYPE_XVIMAGE_BUFFER (gst_xvimage_buffer_get_type())

#define GST_IS_XVIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_XVIMAGE_BUFFER))
#define GST_XVIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_XVIMAGE_BUFFER, GstXvImageBuffer))
212
#define GST_XVIMAGE_BUFFER_CAST(obj) ((GstXvImageBuffer *)(obj))
213
214
215

/* This function destroys a GstXvImage handling XShm availability */
static void
Wim Taymans's avatar
Wim Taymans committed
216
gst_xvimage_buffer_destroy (GstXvImageBuffer * xvimage)
217
218
219
{
  GstXvImageSink *xvimagesink;

220
221
  GST_DEBUG_OBJECT (xvimage, "Destroying buffer");

222
  xvimagesink = xvimage->xvimagesink;
223
  if (G_UNLIKELY (xvimagesink == NULL))
Wim Taymans's avatar
Wim Taymans committed
224
    goto no_sink;
225
226
227

  g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));

228
229
  GST_OBJECT_LOCK (xvimagesink);

230
231
232
233
  /* If the destroyed image is the current one we destroy our reference too */
  if (xvimagesink->cur_image == xvimage)
    xvimagesink->cur_image = NULL;

234
  /* We might have some buffers destroyed after changing state to NULL */
235
236
237
238
239
240
241
242
243
  if (xvimagesink->xcontext == NULL) {
    GST_DEBUG_OBJECT (xvimagesink, "Destroying XvImage after Xcontext");
#ifdef HAVE_XSHM
    /* Need to free the shared memory segment even if the x context
     * was already cleaned up */
    if (xvimage->SHMInfo.shmaddr != ((void *) -1)) {
      shmdt (xvimage->SHMInfo.shmaddr);
    }
#endif
244
245
246
    goto beach;
  }

247
248
249
250
251
  g_mutex_lock (xvimagesink->x_lock);

#ifdef HAVE_XSHM
  if (xvimagesink->xcontext->use_xshm) {
    if (xvimage->SHMInfo.shmaddr != ((void *) -1)) {
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
252
      GST_DEBUG_OBJECT (xvimagesink, "XServer ShmDetaching from 0x%x id 0x%lx",
253
          xvimage->SHMInfo.shmid, xvimage->SHMInfo.shmseg);
254
255
      XShmDetach (xvimagesink->xcontext->disp, &xvimage->SHMInfo);
      XSync (xvimagesink->xcontext->disp, FALSE);
256

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
      shmdt (xvimage->SHMInfo.shmaddr);
    }
    if (xvimage->xvimage)
      XFree (xvimage->xvimage);
  } else
#endif /* HAVE_XSHM */
  {
    if (xvimage->xvimage) {
      if (xvimage->xvimage->data) {
        g_free (xvimage->xvimage->data);
      }
      XFree (xvimage->xvimage);
    }
  }

  XSync (xvimagesink->xcontext->disp, FALSE);

  g_mutex_unlock (xvimagesink->x_lock);
Wim Taymans's avatar
Wim Taymans committed
275

276
beach:
277
  GST_OBJECT_UNLOCK (xvimagesink);
278
279
280
  xvimage->xvimagesink = NULL;
  gst_object_unref (xvimagesink);

281
282
  GST_MINI_OBJECT_CLASS (xvimage_buffer_parent_class)->finalize (GST_MINI_OBJECT
      (xvimage));
283

Wim Taymans's avatar
Wim Taymans committed
284
285
286
287
288
289
290
291
292
293
294
295
296
  return;

no_sink:
  {
    GST_WARNING ("no sink found");
    return;
  }
}

static void
gst_xvimage_buffer_finalize (GstXvImageBuffer * xvimage)
{
  GstXvImageSink *xvimagesink;
297
  gboolean running;
Wim Taymans's avatar
Wim Taymans committed
298
299

  xvimagesink = xvimage->xvimagesink;
300
  if (G_UNLIKELY (xvimagesink == NULL))
Wim Taymans's avatar
Wim Taymans committed
301
302
    goto no_sink;

303
304
  g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));

305
306
307
308
  GST_OBJECT_LOCK (xvimagesink);
  running = xvimagesink->running;
  GST_OBJECT_UNLOCK (xvimagesink);

Wim Taymans's avatar
Wim Taymans committed
309
  /* If our geometry changed we can't reuse that image. */
310
311
312
313
  if (running == FALSE) {
    GST_LOG_OBJECT (xvimage, "destroy image as sink is shutting down");
    gst_xvimage_buffer_destroy (xvimage);
  } else if ((xvimage->width != xvimagesink->video_width) ||
Wim Taymans's avatar
Wim Taymans committed
314
      (xvimage->height != xvimagesink->video_height)) {
315
316
    GST_LOG_OBJECT (xvimage,
        "destroy image as its size changed %dx%d vs current %dx%d",
Wim Taymans's avatar
Wim Taymans committed
317
318
319
320
321
        xvimage->width, xvimage->height,
        xvimagesink->video_width, xvimagesink->video_height);
    gst_xvimage_buffer_destroy (xvimage);
  } else {
    /* In that case we can reuse the image and add it to our image pool. */
322
    GST_LOG_OBJECT (xvimage, "recycling image in pool");
Wim Taymans's avatar
Wim Taymans committed
323
    /* need to increment the refcount again to recycle */
324
    gst_buffer_ref (GST_BUFFER_CAST (xvimage));
Wim Taymans's avatar
Wim Taymans committed
325
326
327
328
329
330
331
332
333
334
335
336
    g_mutex_lock (xvimagesink->pool_lock);
    xvimagesink->image_pool = g_slist_prepend (xvimagesink->image_pool,
        xvimage);
    g_mutex_unlock (xvimagesink->pool_lock);
  }
  return;

no_sink:
  {
    GST_WARNING ("no sink found");
    return;
  }
337
338
}

Wim Taymans's avatar
Wim Taymans committed
339
340
341
342
343
344
345
346
static void
gst_xvimage_buffer_free (GstXvImageBuffer * xvimage)
{
  /* make sure it is not recycled */
  xvimage->width = -1;
  xvimage->height = -1;
  gst_buffer_unref (GST_BUFFER (xvimage));
}
347
348

static void
349
gst_xvimage_buffer_init (GstXvImageBuffer * xvimage, gpointer g_class)
350
{
351
#ifdef HAVE_XSHM
352
353
  xvimage->SHMInfo.shmaddr = ((void *) -1);
  xvimage->SHMInfo.shmid = -1;
354
#endif
355
356
357
358
359
360
361
}

static void
gst_xvimage_buffer_class_init (gpointer g_class, gpointer class_data)
{
  GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class);

362
363
  xvimage_buffer_parent_class = g_type_class_peek_parent (g_class);

364
365
366
367
  mini_object_class->finalize = (GstMiniObjectFinalizeFunction)
      gst_xvimage_buffer_finalize;
}

368
static GType
369
370
371
372
373
374
375
376
377
378
379
380
381
382
gst_xvimage_buffer_get_type (void)
{
  static GType _gst_xvimage_buffer_type;

  if (G_UNLIKELY (_gst_xvimage_buffer_type == 0)) {
    static const GTypeInfo xvimage_buffer_info = {
      sizeof (GstBufferClass),
      NULL,
      NULL,
      gst_xvimage_buffer_class_init,
      NULL,
      NULL,
      sizeof (GstXvImageBuffer),
      0,
383
      (GInstanceInitFunc) gst_xvimage_buffer_init,
384
385
386
387
388
389
390
391
      NULL
    };
    _gst_xvimage_buffer_type = g_type_register_static (GST_TYPE_BUFFER,
        "GstXvImageBuffer", &xvimage_buffer_info, 0);
  }
  return _gst_xvimage_buffer_type;
}

392
393
/* X11 stuff */

394
395
static gboolean error_caught = FALSE;

Julien Moutte's avatar
Julien Moutte committed
396
static int
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
397
gst_xvimagesink_handle_xerror (Display * display, XErrorEvent * xevent)
Julien Moutte's avatar
Julien Moutte committed
398
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
399
400
  char error_msg[1024];

Julien Moutte's avatar
Julien Moutte committed
401
  XGetErrorText (display, xevent->error_code, error_msg, 1024);
402
  GST_DEBUG ("xvimagesink triggered an XError. error: %s", error_msg);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
403
  error_caught = TRUE;
Julien Moutte's avatar
Julien Moutte committed
404
405
406
  return 0;
}

407
#ifdef HAVE_XSHM
Julien Moutte's avatar
Julien Moutte committed
408
409
410
/* This function checks that it is actually really possible to create an image
   using XShm */
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
411
gst_xvimagesink_check_xshm_calls (GstXContext * xcontext)
Julien Moutte's avatar
Julien Moutte committed
412
{
413
414
415
  XvImage *xvimage;
  XShmSegmentInfo SHMInfo;
  gint size;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
416
  int (*handler) (Display *, XErrorEvent *);
Tim Ringenbach's avatar
Tim Ringenbach committed
417
  gboolean result = FALSE;
418
  gboolean did_attach = FALSE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
419

Julien Moutte's avatar
Julien Moutte committed
420
  g_return_val_if_fail (xcontext != NULL, FALSE);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
421

422
423
424
425
426
427
428
  /* Sync to ensure any older errors are already processed */
  XSync (xcontext->disp, FALSE);

  /* Set defaults so we don't free these later unnecessarily */
  SHMInfo.shmaddr = ((void *) -1);
  SHMInfo.shmid = -1;

Julien Moutte's avatar
Julien Moutte committed
429
  /* Setting an error handler to catch failure */
Tim Ringenbach's avatar
Tim Ringenbach committed
430
  error_caught = FALSE;
Julien Moutte's avatar
Julien Moutte committed
431
  handler = XSetErrorHandler (gst_xvimagesink_handle_xerror);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
432

Julien Moutte's avatar
Julien Moutte committed
433
  /* Trying to create a 1x1 picture */
434
  GST_DEBUG ("XvShmCreateImage of 1x1");
435
  xvimage = XvShmCreateImage (xcontext->disp, xcontext->xv_port_id,
436
      xcontext->im_format, NULL, 1, 1, &SHMInfo);
437
438
439
440

  /* Might cause an error, sync to ensure it is noticed */
  XSync (xcontext->disp, FALSE);
  if (!xvimage || error_caught) {
441
442
443
    GST_WARNING ("could not XvShmCreateImage a 1x1 image");
    goto beach;
  }
444
  size = xvimage->data_size;
Julien Moutte's avatar
Julien Moutte committed
445

446
447
448
  SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777);
  if (SHMInfo.shmid == -1) {
    GST_WARNING ("could not get shared memory of %d bytes", size);
449
450
451
    goto beach;
  }

452
  SHMInfo.shmaddr = shmat (SHMInfo.shmid, NULL, 0);
453
  if (SHMInfo.shmaddr == ((void *) -1)) {
454
    GST_WARNING ("Failed to shmat: %s", g_strerror (errno));
455
    /* Clean up the shared memory segment */
456
    shmctl (SHMInfo.shmid, IPC_RMID, NULL);
457
458
459
    goto beach;
  }

460
461
  xvimage->data = SHMInfo.shmaddr;
  SHMInfo.readOnly = FALSE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
462

463
  if (XShmAttach (xcontext->disp, &SHMInfo) == 0) {
464
    GST_WARNING ("Failed to XShmAttach");
Jan Schmidt's avatar
Jan Schmidt committed
465
466
    /* Clean up the shared memory segment */
    shmctl (SHMInfo.shmid, IPC_RMID, NULL);
467
468
    goto beach;
  }
Julien Moutte's avatar
Julien Moutte committed
469

470
471
472
  /* Sync to ensure we see any errors we caused */
  XSync (xcontext->disp, FALSE);

Jan Schmidt's avatar
Jan Schmidt committed
473
474
475
476
  /* Delete the shared memory segment as soon as everyone is attached.
   * This way, it will be deleted as soon as we detach later, and not
   * leaked if we crash. */
  shmctl (SHMInfo.shmid, IPC_RMID, NULL);
477

478
  if (!error_caught) {
Jan Schmidt's avatar
Jan Schmidt committed
479
480
481
    GST_DEBUG ("XServer ShmAttached to 0x%x, id 0x%lx", SHMInfo.shmid,
        SHMInfo.shmseg);

482
483
484
    did_attach = TRUE;
    /* store whether we succeeded in result */
    result = TRUE;
Jan Schmidt's avatar
Jan Schmidt committed
485
486
487
  } else {
    GST_WARNING ("MIT-SHM extension check failed at XShmAttach. "
        "Not using shared memory.");
488
  }
489
490

beach:
491
492
493
494
  /* Sync to ensure we swallow any errors we caused and reset error_caught */
  XSync (xcontext->disp, FALSE);

  error_caught = FALSE;
Tim Ringenbach's avatar
Tim Ringenbach committed
495
  XSetErrorHandler (handler);
496

497
  if (did_attach) {
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
498
    GST_DEBUG ("XServer ShmDetaching from 0x%x id 0x%lx",
499
        SHMInfo.shmid, SHMInfo.shmseg);
500
501
502
    XShmDetach (xcontext->disp, &SHMInfo);
    XSync (xcontext->disp, FALSE);
  }
503
504
  if (SHMInfo.shmaddr != ((void *) -1))
    shmdt (SHMInfo.shmaddr);
505
506
  if (xvimage)
    XFree (xvimage);
Tim Ringenbach's avatar
Tim Ringenbach committed
507
  return result;
Julien Moutte's avatar
Julien Moutte committed
508
}
509
#endif /* HAVE_XSHM */
Julien Moutte's avatar
Julien Moutte committed
510

511
/* This function handles GstXvImage creation depending on XShm availability */
512
static GstXvImageBuffer *
513
gst_xvimagesink_xvimage_new (GstXvImageSink * xvimagesink, GstCaps * caps)
514
{
515
  GstXvImageBuffer *xvimage = NULL;
516
  GstStructure *structure = NULL;
517
  gboolean succeeded = FALSE;
518
  int (*handler) (Display *, XErrorEvent *);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
519

520
  g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL);
521

522
523
524
  if (caps == NULL)
    return NULL;

525
  xvimage = (GstXvImageBuffer *) gst_mini_object_new (GST_TYPE_XVIMAGE_BUFFER);
526
  GST_DEBUG_OBJECT (xvimage, "Creating new XvImageBuffer");
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
527

528
529
530
531
532
533
534
535
536
537
538
  structure = gst_caps_get_structure (caps, 0);

  if (!gst_structure_get_int (structure, "width", &xvimage->width) ||
      !gst_structure_get_int (structure, "height", &xvimage->height)) {
    GST_WARNING ("failed getting geometry from caps %" GST_PTR_FORMAT, caps);
  }

  GST_LOG_OBJECT (xvimagesink, "creating %dx%d", xvimage->width,
      xvimage->height);

  xvimage->im_format = gst_xvimagesink_get_format_from_caps (xvimagesink, caps);
539
  if (xvimage->im_format == -1) {
540
541
    GST_WARNING_OBJECT (xvimagesink, "failed to get format from caps %"
        GST_PTR_FORMAT, caps);
542
543
544
    GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
        ("Failed to create output image buffer of %dx%d pixels",
            xvimage->width, xvimage->height), ("Invalid input caps"));
545
    goto beach_unlocked;
546
  }
547
  xvimage->xvimagesink = gst_object_ref (xvimagesink);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
548

549
550
  g_mutex_lock (xvimagesink->x_lock);

551
552
553
554
  /* Setting an error handler to catch failure */
  error_caught = FALSE;
  handler = XSetErrorHandler (gst_xvimagesink_handle_xerror);

555
#ifdef HAVE_XSHM
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
556
  if (xvimagesink->xcontext->use_xshm) {
557
558
    int expected_size;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
559
    xvimage->xvimage = XvShmCreateImage (xvimagesink->xcontext->disp,
560
        xvimagesink->xcontext->xv_port_id,
561
562
        xvimage->im_format, NULL,
        xvimage->width, xvimage->height, &xvimage->SHMInfo);
563
    if (!xvimage->xvimage || error_caught) {
564
      g_mutex_unlock (xvimagesink->x_lock);
565
566

      /* Reset error flag */
567
      error_caught = FALSE;
568
569
570

      /* Push a warning */
      GST_ELEMENT_WARNING (xvimagesink, RESOURCE, WRITE,
571
572
573
574
          ("Failed to create output image buffer of %dx%d pixels",
              xvimage->width, xvimage->height),
          ("could not XvShmCreateImage a %dx%d image",
              xvimage->width, xvimage->height));
575
576
577
578
579
580
581

      /* Retry without XShm */
      xvimagesink->xcontext->use_xshm = FALSE;

      /* Hold X mutex again to try without XShm */
      g_mutex_lock (xvimagesink->x_lock);
      goto no_xshm;
582
583
584
    }

    /* we have to use the returned data_size for our shm size */
585
    xvimage->size = xvimage->xvimage->data_size;
586
587
    GST_LOG_OBJECT (xvimagesink, "XShm image size is %" G_GSIZE_FORMAT,
        xvimage->size);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
588

589
590
    /* calculate the expected size.  This is only for sanity checking the
     * number we get from X. */
591
    switch (xvimage->im_format) {
592
      case GST_MAKE_FOURCC ('I', '4', '2', '0'):
593
      case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):
594
595
596
597
598
599
600
601
602
      {
        gint pitches[3];
        gint offsets[3];
        guint plane;

        offsets[0] = 0;
        pitches[0] = GST_ROUND_UP_4 (xvimage->width);
        offsets[1] = offsets[0] + pitches[0] * GST_ROUND_UP_2 (xvimage->height);
        pitches[1] = GST_ROUND_UP_8 (xvimage->width) / 2;
603
604
        offsets[2] =
            offsets[1] + pitches[1] * GST_ROUND_UP_2 (xvimage->height) / 2;
605
606
        pitches[2] = GST_ROUND_UP_8 (pitches[0]) / 2;

607
608
        expected_size =
            offsets[2] + pitches[2] * GST_ROUND_UP_2 (xvimage->height) / 2;
609
610

        for (plane = 0; plane < xvimage->xvimage->num_planes; plane++) {
611
612
613
          GST_DEBUG_OBJECT (xvimagesink,
              "Plane %u has a expected pitch of %d bytes, " "offset of %d",
              plane, pitches[plane], offsets[plane]);
614
        }
615
        break;
616
      }
617
618
619
620
621
622
623
624
625
626
627
628
629
630
      case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'):
      case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'):
        expected_size = xvimage->height * GST_ROUND_UP_4 (xvimage->width * 2);
        break;
      default:
        expected_size = 0;
        break;
    }
    if (expected_size != 0 && xvimage->size != expected_size) {
      GST_WARNING_OBJECT (xvimagesink,
          "unexpected XShm image size (got %" G_GSIZE_FORMAT ", expected %d)",
          xvimage->size, expected_size);
    }

631
632
633
634
635
636
637
638
639
640
641
    /* Be verbose about our XvImage stride */
    {
      guint plane;

      for (plane = 0; plane < xvimage->xvimage->num_planes; plane++) {
        GST_DEBUG_OBJECT (xvimagesink, "Plane %u has a pitch of %d bytes, "
            "offset of %d", plane, xvimage->xvimage->pitches[plane],
            xvimage->xvimage->offsets[plane]);
      }
    }

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
642
    xvimage->SHMInfo.shmid = shmget (IPC_PRIVATE, xvimage->size,
643
        IPC_CREAT | 0777);
644
    if (xvimage->SHMInfo.shmid == -1) {
645
      g_mutex_unlock (xvimagesink->x_lock);
646
647
648
      GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
          ("Failed to create output image buffer of %dx%d pixels",
              xvimage->width, xvimage->height),
649
650
          ("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
              xvimage->size));
651
      goto beach_unlocked;
652
    }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
653

654
    xvimage->SHMInfo.shmaddr = shmat (xvimage->SHMInfo.shmid, NULL, 0);
655
    if (xvimage->SHMInfo.shmaddr == ((void *) -1)) {
656
      g_mutex_unlock (xvimagesink->x_lock);
657
658
659
      GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
          ("Failed to create output image buffer of %dx%d pixels",
              xvimage->width, xvimage->height),
660
          ("Failed to shmat: %s", g_strerror (errno)));
661
      /* Clean up the shared memory segment */
662
      shmctl (xvimage->SHMInfo.shmid, IPC_RMID, NULL);
663
      goto beach_unlocked;
664
    }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
665

666
    xvimage->xvimage->data = xvimage->SHMInfo.shmaddr;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
667
668
    xvimage->SHMInfo.readOnly = FALSE;

669
    if (XShmAttach (xvimagesink->xcontext->disp, &xvimage->SHMInfo) == 0) {
Jan Schmidt's avatar
Jan Schmidt committed
670
671
672
      /* Clean up the shared memory segment */
      shmctl (xvimage->SHMInfo.shmid, IPC_RMID, NULL);

673
      g_mutex_unlock (xvimagesink->x_lock);
674
675
676
      GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
          ("Failed to create output image buffer of %dx%d pixels",
              xvimage->width, xvimage->height), ("Failed to XShmAttach"));
677
      goto beach_unlocked;
678
    }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
679
680

    XSync (xvimagesink->xcontext->disp, FALSE);
Jan Schmidt's avatar
Jan Schmidt committed
681
682
683
684
685
686

    /* Delete the shared memory segment as soon as we everyone is attached.
     * This way, it will be deleted as soon as we detach later, and not
     * leaked if we crash. */
    shmctl (xvimage->SHMInfo.shmid, IPC_RMID, NULL);

Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
687
    GST_DEBUG_OBJECT (xvimagesink, "XServer ShmAttached to 0x%x, id 0x%lx",
688
        xvimage->SHMInfo.shmid, xvimage->SHMInfo.shmseg);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
689
  } else
690
  no_xshm:
Julien Moutte's avatar
Julien Moutte committed
691
#endif /* HAVE_XSHM */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
692
693
  {
    xvimage->xvimage = XvCreateImage (xvimagesink->xcontext->disp,
694
        xvimagesink->xcontext->xv_port_id,
695
        xvimage->im_format, NULL, xvimage->width, xvimage->height);
696
    if (!xvimage->xvimage || error_caught) {
697
      g_mutex_unlock (xvimagesink->x_lock);
698
699
700
701
      /* Reset error handler */
      error_caught = FALSE;
      XSetErrorHandler (handler);
      /* Push an error */
702
703
704
705
706
      GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE,
          ("Failed to create outputimage buffer of %dx%d pixels",
              xvimage->width, xvimage->height),
          ("could not XvCreateImage a %dx%d image",
              xvimage->width, xvimage->height));
707
      goto beach_unlocked;
708
    }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
709

710
    /* we have to use the returned data_size for our image size */
711
712
    xvimage->size = xvimage->xvimage->data_size;
    xvimage->xvimage->data = g_malloc (xvimage->size);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
713
714
715

    XSync (xvimagesink->xcontext->disp, FALSE);
  }
716
717
718
719
720

  /* Reset error handler */
  error_caught = FALSE;
  XSetErrorHandler (handler);

721
  succeeded = TRUE;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
722

723
724
725
  GST_BUFFER_DATA (xvimage) = (guchar *) xvimage->xvimage->data;
  GST_BUFFER_SIZE (xvimage) = xvimage->size;

726
727
  g_mutex_unlock (xvimagesink->x_lock);

728
beach_unlocked:
729
  if (!succeeded) {
Wim Taymans's avatar
Wim Taymans committed
730
    gst_xvimage_buffer_free (xvimage);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
731
732
    xvimage = NULL;
  }
733

734
735
736
  return xvimage;
}

737
738
739
740
741
/* We are called with the x_lock taken */
static void
gst_xvimagesink_xwindow_draw_borders (GstXvImageSink * xvimagesink,
    GstXWindow * xwindow, GstVideoRectangle rect)
{
742
743
  gint t1, t2;

744
745
746
747
748
749
750
  g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink));
  g_return_if_fail (xwindow != NULL);

  XSetForeground (xvimagesink->xcontext->disp, xwindow->gc,
      xvimagesink->xcontext->black);

  /* Left border */
751
  if (rect.x > xvimagesink->render_rect.x) {
752
    XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
753
754
        xvimagesink->render_rect.x, xvimagesink->render_rect.y,
        rect.x - xvimagesink->render_rect.x, xvimagesink->render_rect.h);
755
756
757
  }

  /* Right border */
758
759
760
  t1 = rect.x + rect.w;
  t2 = xvimagesink->render_rect.x + xvimagesink->render_rect.w;
  if (t1 < t2) {
761
    XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
762
        t1, xvimagesink->render_rect.y, t2 - t1, xvimagesink->render_rect.h);
763
764
765
  }

  /* Top border */
766
  if (rect.y > xvimagesink->render_rect.y) {
767
    XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
768
769
        xvimagesink->render_rect.x, xvimagesink->render_rect.y,
        xvimagesink->render_rect.w, rect.y - xvimagesink->render_rect.y);
770
771
772
  }

  /* Bottom border */
773
774
775
  t1 = rect.y + rect.h;
  t2 = xvimagesink->render_rect.y + xvimagesink->render_rect.h;
  if (t1 < t2) {
776
    XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc,
777
        xvimagesink->render_rect.x, t1, xvimagesink->render_rect.w, t2 - t1);
778
779
780
  }
}

781
782
783
/* This function puts a GstXvImage on a GstXvImageSink's window. Returns FALSE
 * if no window was available  */
static gboolean
784
785
gst_xvimagesink_xvimage_put (GstXvImageSink * xvimagesink,
    GstXvImageBuffer * xvimage)
786
{
787
  GstVideoRectangle result;
788
  gboolean draw_border = FALSE;
789

790
  /* We take the flow_lock. If expose is in there we don't want to run
791
792
793
     concurrently from the data flow thread */
  g_mutex_lock (xvimagesink->flow_lock);

794
795
  if (G_UNLIKELY (xvimagesink->xwindow == NULL)) {
    g_mutex_unlock (xvimagesink->flow_lock);
796
    return FALSE;
797
798
  }

799
  /* Draw borders when displaying the first frame. After this
800
     draw borders only on expose event or after a size change. */
801
  if (!xvimagesink->cur_image || xvimagesink->redraw_border) {
802
803
804
    draw_border = TRUE;
  }

805
  /* Store a reference to the last image we put, lose the previous one */
806
  if (xvimage && xvimagesink->cur_image != xvimage) {
807
    if (xvimagesink->cur_image) {
808
      GST_LOG_OBJECT (xvimagesink, "unreffing %p", xvimagesink->cur_image);
809
      gst_buffer_unref (GST_BUFFER_CAST (xvimagesink->cur_image));
810
    }
811
    GST_LOG_OBJECT (xvimagesink, "reffing %p as our current image", xvimage);
812
    xvimagesink->cur_image =
813
        GST_XVIMAGE_BUFFER_CAST (gst_buffer_ref (GST_BUFFER_CAST (xvimage)));
814
815
  }

816
817
818
  /* Expose sends a NULL image, we take the latest frame */
  if (!xvimage) {
    if (xvimagesink->cur_image) {
819
      draw_border = TRUE;
820
821
822
      xvimage = xvimagesink->cur_image;
    } else {
      g_mutex_unlock (xvimagesink->flow_lock);
823
      return TRUE;
824
825
826
    }
  }

827
  if (xvimagesink->keep_aspect) {
828
829
830
831
832
833
834
835
836
    GstVideoRectangle src, dst;

    /* We use the calculated geometry from _setcaps as a source to respect
       source and screen pixel aspect ratios. */
    src.w = GST_VIDEO_SINK_WIDTH (xvimagesink);
    src.h = GST_VIDEO_SINK_HEIGHT (xvimagesink);
    dst.w = xvimagesink->render_rect.w;
    dst.h = xvimagesink->render_rect.h;

837
    gst_video_sink_center_rect (src, dst, &result, TRUE);
838
839
    result.x += xvimagesink->render_rect.x;
    result.y += xvimagesink->render_rect.y;
840
  } else {
841
    memcpy (&result, &xvimagesink->render_rect, sizeof (GstVideoRectangle));
842
  }
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
843

844
  g_mutex_lock (xvimagesink->x_lock);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
845

846
  if (draw_border && xvimagesink->draw_borders) {
847
848
    gst_xvimagesink_xwindow_draw_borders (xvimagesink, xvimagesink->xwindow,
        result);
849
    xvimagesink->redraw_border = FALSE;
850
  }
851

852
  /* We scale to the window's geometry */
853
#ifdef HAVE_XSHM
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
854
  if (xvimagesink->xcontext->use_xshm) {
855
    GST_LOG_OBJECT (xvimagesink,
856
857
        "XvShmPutImage with image %dx%d and window %dx%d, from xvimage %"
        GST_PTR_FORMAT,
858
        xvimage->width, xvimage->height,
859
        xvimagesink->render_rect.w, xvimagesink->render_rect.h, xvimage);
860

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
861
    XvShmPutImage (xvimagesink->xcontext->disp,
862
863
864
        xvimagesink->xcontext->xv_port_id,
        xvimagesink->xwindow->win,
        xvimagesink->xwindow->gc, xvimage->xvimage,
865
866
        xvimagesink->disp_x, xvimagesink->disp_y,
        xvimagesink->disp_width, xvimagesink->disp_height,
867
        result.x, result.y, result.w, result.h, FALSE);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
868
  } else
Julien Moutte's avatar
Julien Moutte committed
869
#endif /* HAVE_XSHM */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
870
871
  {
    XvPutImage (xvimagesink->xcontext->disp,
872
873
874
        xvimagesink->xcontext->xv_port_id,
        xvimagesink->xwindow->win,
        xvimagesink->xwindow->gc, xvimage->xvimage,
875
876
        xvimagesink->disp_x, xvimagesink->disp_y,
        xvimagesink->disp_width, xvimagesink->disp_height,
877
        result.x, result.y, result.w, result.h);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
878
879
880
881
  }

  XSync (xvimagesink->xcontext->disp, FALSE);

882
  g_mutex_unlock (xvimagesink->x_lock);
883
884

  g_mutex_unlock (xvimagesink->flow_lock);
885
886

  return TRUE;
887
888
}

889
890
891
892
893
894
895
896
897
898
static gboolean
gst_xvimagesink_xwindow_decorate (GstXvImageSink * xvimagesink,
    GstXWindow * window)
{
  Atom hints_atom = None;
  MotifWmHints *hints;

  g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), FALSE);
  g_return_val_if_fail (window != NULL, FALSE);

899
900
  g_mutex_lock (xvimagesink->x_lock);

901
902
  hints_atom = XInternAtom (xvimagesink->xcontext->disp, "_MOTIF_WM_HINTS",
      True);
903
  if (hints_atom == None) {
904
    g_mutex_unlock (xvimagesink->x_lock);
905
906
907
    return FALSE;
  }

908
909
  hints = g_malloc0 (sizeof (MotifWmHints));

910
911
912
913
914
915
916
917
918
  hints->flags |= MWM_HINTS_DECORATIONS;
  hints->decorations = 1 << 0;

  XChangeProperty (xvimagesink->xcontext->disp, window->win,
      hints_atom, hints_atom, 32, PropModeReplace,
      (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));

  XSync (xvimagesink->xcontext->disp, FALSE);

919
920
  g_mutex_unlock (xvimagesink->x_lock);

921
922
923
924
925
  g_free (hints);

  return TRUE;
}

926
927
static void
gst_xvimagesink_xwindow_set_title (GstXvImageSink * xvimagesink,
928
    GstXWindow * xwindow, const gchar * media_title)
929
930
931
932
933
{
  if (media_title) {
    g_free (xvimagesink->media_title);
    xvimagesink->media_title = g_strdup (media_title);
  }
934
  if (xwindow) {
935
    /* we have a window */
936
    if (xwindow->internal) {
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
      XTextProperty xproperty;
      const gchar *app_name;
      const gchar *title = NULL;
      gchar *title_mem = NULL;

      /* set application name as a title */
      app_name = g_get_application_name ();

      if (app_name && xvimagesink->media_title) {
        title = title_mem = g_strconcat (xvimagesink->media_title, " : ",
            app_name, NULL);
      } else if (app_name) {
        title = app_name;
      } else if (xvimagesink->media_title) {
        title = xvimagesink->media_title;
      }

      if (title) {
        if ((XStringListToTextProperty (((char **) &title), 1,
956
                    &xproperty)) != 0) {
957
          XSetWMName (xvimagesink->xcontext->disp, xwindow->win, &xproperty);
958
959
          XFree (xproperty.value);
        }
960
961
962
963