1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 /* Our interfaces */
6 #include <gst/video/video.h>
7 #include <gst/allocators/gstdmabuf.h>
8 #include <gst/video/navigation.h>
9 #include <gst/video/videooverlay.h>
10
11 #include <gst/video/gstvideometa.h>
12
13 /* Debugging category */
14 #include <gst/gstinfo.h>
15
16 /* for XkbKeycodeToKeysym */
17 #include <X11/XKBlib.h>
18
19 #include <drm.h>
20 #include <xf86drm.h>
21 #include <xf86drmMode.h>
22 #include <drm_fourcc.h>
23 #include <fcntl.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 /* Object header */
28 #include "ximagesink.h"
29 #include "gstkmsutils.h"
30 #include "gstkmsbufferpool.h"
31 #include "gstkmsallocator.h"
32
33 /* A random dark color */
34 #define RKXIMAGE_COLOR_KEY 0x010203
35 #define RK_COLOR_KEY_EN (1UL << 31)
36
37 GST_DEBUG_CATEGORY (gst_debug_x_image_sink);
38 #define GST_CAT_DEFAULT gst_debug_x_image_sink
39
40 #define EMPTY_RECT(r) !((r).x || (r).y || (r).w || (r).h)
41 #define RECT_EQUAL(r1, r2) \
42 ((r1).x == (r2).x && (r1).y == (r2).y && \
43 (r1).w == (r2.w) && (r1).h == (r2).h)
44
45 typedef struct
46 {
47 unsigned long flags;
48 unsigned long functions;
49 unsigned long decorations;
50 long input_mode;
51 unsigned long status;
52 }
53 MotifWmHints, MwmHints;
54
55 #define MWM_HINTS_DECORATIONS (1L << 1)
56
57 static void gst_x_image_sink_reset (GstRkXImageSink * ximagesink);
58 static void gst_x_image_sink_xwindow_update_geometry (GstRkXImageSink *
59 ximagesink);
60 static void gst_x_image_sink_expose (GstVideoOverlay * overlay);
61 static void gst_x_image_sink_xwindow_clear (GstRkXImageSink * ximagesink,
62 GstXWindow * xwindow);
63 static GstFlowReturn
64 gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf);
65
66 static GstStaticPadTemplate gst_x_image_sink_sink_template_factory =
67 GST_STATIC_PAD_TEMPLATE ("sink",
68 GST_PAD_SINK,
69 GST_PAD_ALWAYS,
70 GST_STATIC_CAPS ("video/x-raw, "
71 "framerate = (fraction) [ 0, MAX ], "
72 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
73 );
74
75 enum
76 {
77 PROP_0,
78 PROP_DISPLAY,
79 PROP_SYNCHRONOUS,
80 PROP_HANDLE_EVENTS,
81 PROP_HANDLE_EXPOSE,
82 PROP_WINDOW_WIDTH,
83 PROP_WINDOW_HEIGHT,
84 PROP_DRIVER_NAME,
85 PROP_BUS_ID,
86 PROP_CONNECTOR_ID,
87 PROP_PLANE_ID,
88 PROP_FORCE_ASPECT_RATIO,
89 };
90
91 /* ============================================================= */
92 /* */
93 /* Public Methods */
94 /* */
95 /* ============================================================= */
96
97 /* =========================================== */
98 /* */
99 /* Object typing & Creation */
100 /* */
101 /* =========================================== */
102 static void gst_x_image_sink_navigation_init (GstNavigationInterface * iface);
103 static void gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface *
104 iface);
105 #define gst_x_image_sink_parent_class parent_class
106 G_DEFINE_TYPE_WITH_CODE (GstRkXImageSink, gst_x_image_sink, GST_TYPE_VIDEO_SINK,
107 G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
108 gst_x_image_sink_navigation_init);
109 G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY,
110 gst_x_image_sink_video_overlay_init));
111
112 /* ============================================================= */
113 /* */
114 /* Private Methods */
115 /* */
116 /* ============================================================= */
117 /*drm*/
118
119 static int
drm_plane_get_type(int fd,drmModePlane * plane)120 drm_plane_get_type (int fd, drmModePlane * plane)
121 {
122 drmModeObjectPropertiesPtr props;
123 drmModePropertyPtr prop;
124 int i, type = -1;
125
126 props = drmModeObjectGetProperties (fd, plane->plane_id,
127 DRM_MODE_OBJECT_PLANE);
128 if (!props)
129 return -1;
130
131 for (i = 0; i < props->count_props; i++) {
132 prop = drmModeGetProperty (fd, props->props[i]);
133 if (prop && !strcmp (prop->name, "type"))
134 type = props->prop_values[i];
135 drmModeFreeProperty (prop);
136 }
137
138 drmModeFreeObjectProperties (props);
139 return type;
140 }
141
142 static drmModePlane *
drm_find_plane_for_crtc_by_type(int fd,drmModeRes * res,drmModePlaneRes * pres,int crtc_id,int type)143 drm_find_plane_for_crtc_by_type (int fd, drmModeRes * res,
144 drmModePlaneRes * pres, int crtc_id, int type)
145 {
146 int i, pipe = -1, num_primary = 0;
147
148 for (i = 0; i < res->count_crtcs; i++) {
149 if (crtc_id == res->crtcs[i]) {
150 pipe = i;
151 break;
152 }
153 }
154
155 if (pipe == -1)
156 return NULL;
157
158 for (i = 0; i < pres->count_planes; i++) {
159 drmModePlane *plane = drmModeGetPlane (fd, pres->planes[i]);
160 int plane_type = drm_plane_get_type (fd, plane);
161 int primary = plane_type == DRM_PLANE_TYPE_PRIMARY;
162
163 num_primary += primary;
164 if ((plane->possible_crtcs & (1 << pipe)) && plane_type == type) {
165 if (!primary || pipe == num_primary - 1)
166 return plane;
167 }
168 drmModeFreePlane (plane);
169 }
170
171 return NULL;
172 }
173
174 static drmModeCrtc *
drm_find_crtc_for_connector(int fd,drmModeRes * res,drmModeConnector * conn,guint * pipe)175 drm_find_crtc_for_connector (int fd, drmModeRes * res, drmModeConnector * conn,
176 guint * pipe)
177 {
178 int i;
179 int crtc_id;
180 drmModeEncoder *enc;
181 drmModeCrtc *crtc;
182
183 crtc_id = -1;
184 for (i = 0; i < res->count_encoders; i++) {
185 enc = drmModeGetEncoder (fd, res->encoders[i]);
186 if (enc) {
187 if (enc->encoder_id == conn->encoder_id) {
188 crtc_id = enc->crtc_id;
189 drmModeFreeEncoder (enc);
190 break;
191 }
192 drmModeFreeEncoder (enc);
193 }
194 }
195
196 if (crtc_id == -1)
197 return NULL;
198
199 for (i = 0; i < res->count_crtcs; i++) {
200 crtc = drmModeGetCrtc (fd, res->crtcs[i]);
201 if (crtc) {
202 if (crtc_id == crtc->crtc_id) {
203 if (pipe)
204 *pipe = i;
205 return crtc;
206 }
207 drmModeFreeCrtc (crtc);
208 }
209 }
210
211 return NULL;
212 }
213
214 static gboolean
drm_connector_is_used(int fd,drmModeRes * res,drmModeConnector * conn)215 drm_connector_is_used (int fd, drmModeRes * res, drmModeConnector * conn)
216 {
217 gboolean result;
218 drmModeCrtc *crtc;
219
220 result = FALSE;
221 crtc = drm_find_crtc_for_connector (fd, res, conn, NULL);
222 if (crtc) {
223 result = crtc->buffer_id != 0;
224 drmModeFreeCrtc (crtc);
225 }
226
227 return result;
228 }
229
230 static drmModeConnector *
drm_find_used_connector_by_type(int fd,drmModeRes * res,int type)231 drm_find_used_connector_by_type (int fd, drmModeRes * res, int type)
232 {
233 int i;
234 drmModeConnector *conn;
235
236 conn = NULL;
237 for (i = 0; i < res->count_connectors; i++) {
238 conn = drmModeGetConnector (fd, res->connectors[i]);
239 if (conn) {
240 if ((conn->connector_type == type)
241 && drm_connector_is_used (fd, res, conn))
242 return conn;
243 drmModeFreeConnector (conn);
244 }
245 }
246
247 return NULL;
248 }
249
250 static drmModeConnector *
drm_find_first_used_connector(int fd,drmModeRes * res)251 drm_find_first_used_connector (int fd, drmModeRes * res)
252 {
253 int i;
254 drmModeConnector *conn;
255
256 conn = NULL;
257 for (i = 0; i < res->count_connectors; i++) {
258 conn = drmModeGetConnector (fd, res->connectors[i]);
259 if (conn) {
260 if (drm_connector_is_used (fd, res, conn))
261 return conn;
262 drmModeFreeConnector (conn);
263 }
264 }
265
266 return NULL;
267 }
268
269 static drmModeConnector *
drm_find_main_monitor(int fd,drmModeRes * res)270 drm_find_main_monitor (int fd, drmModeRes * res)
271 {
272 /* Find the LVDS and eDP connectors: those are the main screens. */
273 static const int priority[] = { DRM_MODE_CONNECTOR_LVDS,
274 DRM_MODE_CONNECTOR_eDP
275 };
276 int i;
277 drmModeConnector *conn;
278
279 conn = NULL;
280 for (i = 0; !conn && i < G_N_ELEMENTS (priority); i++)
281 conn = drm_find_used_connector_by_type (fd, res, priority[i]);
282
283 /* if we didn't find a connector, grab the first one in use */
284 if (!conn)
285 conn = drm_find_first_used_connector (fd, res);
286
287 return conn;
288 }
289
290 static void
drm_log_version(GstRkXImageSink * self)291 drm_log_version (GstRkXImageSink * self)
292 {
293 #ifndef GST_DISABLE_GST_DEBUG
294 drmVersion *v;
295
296 v = drmGetVersion (self->fd);
297 if (v) {
298 GST_INFO_OBJECT (self, "DRM v%d.%d.%d [%s — %s — %s]", v->version_major,
299 v->version_minor, v->version_patchlevel, GST_STR_NULL (v->name),
300 GST_STR_NULL (v->desc), GST_STR_NULL (v->date));
301 drmFreeVersion (v);
302 } else {
303 GST_WARNING_OBJECT (self, "could not get driver information: %s",
304 GST_STR_NULL (self->devname));
305 }
306 #endif
307 return;
308 }
309
310 static gboolean
drm_get_caps(GstRkXImageSink * self)311 drm_get_caps (GstRkXImageSink * self)
312 {
313 gint ret;
314 guint64 has_dumb_buffer;
315 guint64 has_prime;
316 guint64 has_async_page_flip;
317
318 has_dumb_buffer = 0;
319 ret = drmGetCap (self->fd, DRM_CAP_DUMB_BUFFER, &has_dumb_buffer);
320 if (ret)
321 GST_WARNING_OBJECT (self, "could not get dumb buffer capability");
322 if (has_dumb_buffer == 0) {
323 GST_ERROR_OBJECT (self, "driver cannot handle dumb buffers");
324 return FALSE;
325 }
326
327 has_prime = 0;
328 ret = drmGetCap (self->fd, DRM_CAP_PRIME, &has_prime);
329 if (ret)
330 GST_WARNING_OBJECT (self, "could not get prime capability");
331 else {
332 self->has_prime_import = (gboolean) (has_prime & DRM_PRIME_CAP_IMPORT);
333 self->has_prime_export = (gboolean) (has_prime & DRM_PRIME_CAP_EXPORT);
334 }
335
336 has_async_page_flip = 0;
337 ret = drmGetCap (self->fd, DRM_CAP_ASYNC_PAGE_FLIP, &has_async_page_flip);
338 if (ret)
339 GST_WARNING_OBJECT (self, "could not get async page flip capability");
340 else
341 self->has_async_page_flip = (gboolean) has_async_page_flip;
342
343 GST_INFO_OBJECT (self,
344 "prime import (%s) / prime export (%s) / async page flip (%s)",
345 self->has_prime_import ? "✓" : "✗",
346 self->has_prime_export ? "✓" : "✗",
347 self->has_async_page_flip ? "✓" : "✗");
348
349 return TRUE;
350 }
351
352 static void
check_afbc(GstRkXImageSink * self,drmModePlane * plane,guint32 drmfmt,gboolean * linear,gboolean * afbc)353 check_afbc (GstRkXImageSink * self, drmModePlane * plane, guint32 drmfmt,
354 gboolean * linear, gboolean * afbc)
355 {
356 drmModeObjectPropertiesPtr props;
357 drmModePropertyBlobPtr blob;
358 drmModePropertyPtr prop;
359 drmModeResPtr res;
360 struct drm_format_modifier_blob *header;
361 struct drm_format_modifier *modifiers;
362 guint32 *formats;
363 guint64 value = 0;
364 gint i, j;
365
366 *linear = *afbc = FALSE;
367
368 res = drmModeGetResources (self->fd);
369 if (!res)
370 return;
371
372 props = drmModeObjectGetProperties (self->fd, plane->plane_id,
373 DRM_MODE_OBJECT_PLANE);
374 if (!props) {
375 drmModeFreeResources (res);
376 return;
377 }
378
379 for (i = 0; i < props->count_props && !value; i++) {
380 prop = drmModeGetProperty (self->fd, props->props[i]);
381 if (!prop)
382 continue;
383
384 if (!strcmp (prop->name, "IN_FORMATS"))
385 value = props->prop_values[i];
386
387 drmModeFreeProperty (prop);
388 }
389
390 drmModeFreeObjectProperties (props);
391 drmModeFreeResources (res);
392
393 /* No modifiers */
394 if (!value) {
395 *linear = TRUE;
396 return;
397 }
398
399 blob = drmModeGetPropertyBlob (self->fd, value);
400 if (!blob)
401 return;
402
403 header = blob->data;
404 modifiers = (struct drm_format_modifier *)
405 ((gchar *) header + header->modifiers_offset);
406 formats = (guint32 *) ((gchar *) header + header->formats_offset);
407
408 for (i = 0; i < header->count_formats; i++) {
409 if (formats[i] != drmfmt)
410 continue;
411
412 for (j = 0; j < header->count_modifiers; j++) {
413 struct drm_format_modifier *mod = &modifiers[j];
414
415 if ((i < mod->offset) || (i > mod->offset + 63))
416 continue;
417 if (!(mod->formats & (1 << (i - mod->offset))))
418 continue;
419
420 if (mod->modifier == DRM_AFBC_MODIFIER)
421 *afbc = TRUE;
422 else if (mod->modifier == DRM_FORMAT_MOD_LINEAR)
423 *linear = TRUE;
424 }
425 }
426
427 drmModeFreePropertyBlob(blob);
428 }
429
430 static gboolean
drm_ensure_allowed_caps(GstRkXImageSink * self,drmModePlane * plane,drmModeRes * res)431 drm_ensure_allowed_caps (GstRkXImageSink * self, drmModePlane * plane,
432 drmModeRes * res)
433 {
434 GstCaps *out_caps, *caps;
435 int i;
436 GstVideoFormat fmt;
437 const gchar *format;
438
439 if (self->allowed_caps)
440 return TRUE;
441
442 out_caps = gst_caps_new_empty ();
443 if (!out_caps)
444 return FALSE;
445
446 for (i = 0; i < plane->count_formats; i++) {
447 gboolean linear = FALSE, afbc = FALSE;
448
449 check_afbc (self, plane, plane->formats[i], &linear, &afbc);
450
451 if (plane->formats[i] == DRM_FORMAT_YUV420_8BIT)
452 fmt = GST_VIDEO_FORMAT_NV12;
453 else if (plane->formats[i] == DRM_FORMAT_YUV420_10BIT)
454 fmt = GST_VIDEO_FORMAT_NV12_10LE40;
455 else if (afbc && plane->formats[i] == DRM_FORMAT_YUYV)
456 fmt = GST_VIDEO_FORMAT_NV16;
457 else
458 fmt = gst_video_format_from_drm (plane->formats[i]);
459
460 if (fmt == GST_VIDEO_FORMAT_UNKNOWN) {
461 GST_INFO_OBJECT (self, "ignoring format %" GST_FOURCC_FORMAT,
462 GST_FOURCC_ARGS (plane->formats[i]));
463 continue;
464 }
465
466 format = gst_video_format_to_string (fmt);
467 caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, format,
468 "width", GST_TYPE_INT_RANGE, res->min_width, res->max_width,
469 "height", GST_TYPE_INT_RANGE, res->min_height, res->max_height,
470 "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
471 if (!caps)
472 continue;
473
474 if (afbc) {
475 GstCaps *afbc_caps = gst_caps_copy (caps);
476 gst_caps_set_simple (afbc_caps, "arm-afbc", G_TYPE_INT, 1, NULL);
477
478 if (linear) {
479 gst_caps_append (caps, afbc_caps);
480 } else {
481 gst_caps_replace (&caps, afbc_caps);
482 gst_caps_unref (afbc_caps);
483 }
484 }
485
486 out_caps = gst_caps_merge (out_caps, caps);
487 }
488
489 self->allowed_caps = gst_caps_simplify (out_caps);
490
491 GST_DEBUG_OBJECT (self, "allowed caps = %" GST_PTR_FORMAT,
492 self->allowed_caps);
493
494 return TRUE;
495 }
496
497 static gboolean
drm_plane_set_property(GstRkXImageSink * self,drmModePlane * plane,const char * prop_name,uint64_t prop_value)498 drm_plane_set_property (GstRkXImageSink * self, drmModePlane * plane,
499 const char *prop_name, uint64_t prop_value)
500 {
501 drmModeObjectPropertiesPtr props;
502 drmModePropertyPtr prop;
503 int i, ret = -1;
504
505 props = drmModeObjectGetProperties (self->fd, plane->plane_id,
506 DRM_MODE_OBJECT_PLANE);
507 if (!props)
508 return FALSE;
509
510 for (i = 0; i < props->count_props; i++) {
511 prop = drmModeGetProperty (self->fd, props->props[i]);
512 if (prop && !strcmp (prop->name, prop_name)) {
513 ret = drmModeObjectSetProperty (self->fd, plane->plane_id,
514 DRM_MODE_OBJECT_PLANE, props->props[i], prop_value);
515 }
516 drmModeFreeProperty (prop);
517 }
518
519 drmModeFreeObjectProperties (props);
520 return ret < 0 ? FALSE : TRUE;
521 }
522
523 static gboolean
drm_prepare_planes(GstRkXImageSink * self,drmModeRes * res,drmModePlaneRes * pres)524 drm_prepare_planes (GstRkXImageSink * self, drmModeRes * res,
525 drmModePlaneRes * pres)
526 {
527 drmModePlane *plane;
528 gboolean ret = FALSE;
529
530 if (drmSetClientCap (self->fd, DRM_CLIENT_CAP_ATOMIC, 1))
531 return FALSE;
532
533 /* Apply colorkey to primary plane */
534 plane = drm_find_plane_for_crtc_by_type (self->fd, res, pres,
535 self->crtc_id, DRM_PLANE_TYPE_PRIMARY);
536 if (!plane)
537 return FALSE;
538
539 if (!drm_plane_set_property (self, plane, "colorkey",
540 RKXIMAGE_COLOR_KEY | RK_COLOR_KEY_EN))
541 goto out;
542
543 GST_DEBUG_OBJECT (self, "applied colorkey = 0x%x to plane %d",
544 RKXIMAGE_COLOR_KEY, plane->plane_id);
545
546 /* Uper primary plane */
547 if (!drm_plane_set_property (self, plane, "ZPOS", 1) &&
548 !drm_plane_set_property (self, plane, "zpos", 1))
549 goto out;
550
551 drmModeFreePlane (plane);
552
553 /* Lower overlay plane */
554 plane = drmModeGetPlane (self->fd, self->plane_id);
555 if (!plane)
556 goto out;
557
558 if (!drm_plane_set_property (self, plane, "ZPOS", 0) &&
559 !drm_plane_set_property (self, plane, "zpos", 0))
560 goto out;
561
562 GST_DEBUG_OBJECT (self, "set plane %d zpos to 0", plane->plane_id);
563
564 ret = TRUE;
565
566 out:
567 drmModeFreePlane (plane);
568 return ret;
569 }
570
571 /*kms*/
572 static void
ensure_kms_allocator(GstRkXImageSink * self)573 ensure_kms_allocator (GstRkXImageSink * self)
574 {
575 if (self->allocator)
576 return;
577 self->allocator = gst_kms_allocator_new (self->fd);
578 }
579
580 static GstBufferPool *
gst_kms_sink_create_pool(GstRkXImageSink * self,GstCaps * caps,gsize size,gint min)581 gst_kms_sink_create_pool (GstRkXImageSink * self, GstCaps * caps, gsize size,
582 gint min)
583 {
584 GstBufferPool *pool;
585 GstStructure *config;
586
587 pool = gst_kms_buffer_pool_new ();
588 if (!pool)
589 goto pool_failed;
590
591 config = gst_buffer_pool_get_config (pool);
592 gst_buffer_pool_config_set_params (config, caps, size, min, 0);
593 gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
594
595 ensure_kms_allocator (self);
596 gst_buffer_pool_config_set_allocator (config, self->allocator, NULL);
597
598 if (!gst_buffer_pool_set_config (pool, config))
599 goto config_failed;
600
601 return pool;
602
603 /* ERRORS */
604 pool_failed:
605 {
606 GST_ERROR_OBJECT (self, "failed to create buffer pool");
607 return NULL;
608 }
609 config_failed:
610 {
611 GST_ERROR_OBJECT (self, "failed to set config");
612 gst_object_unref (pool);
613 return NULL;
614 }
615 }
616
617 static gboolean
gst_kms_sink_propose_allocation(GstBaseSink * bsink,GstQuery * query)618 gst_kms_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
619 {
620 GstRkXImageSink *self;
621 GstCaps *caps;
622 gboolean need_pool;
623 GstVideoInfo vinfo;
624 GstBufferPool *pool;
625 GstStructure *s;
626 gsize size;
627 gint value;
628
629 self = GST_X_IMAGE_SINK (bsink);
630
631 gst_query_parse_allocation (query, &caps, &need_pool);
632 if (!caps)
633 goto no_caps;
634 if (!gst_video_info_from_caps (&vinfo, caps))
635 goto invalid_caps;
636
637 s = gst_caps_get_structure (caps, 0);
638 if (gst_structure_get_int (s, "arm-afbc", &value) && value)
639 goto afbc_caps;
640
641 size = GST_VIDEO_INFO_SIZE (&vinfo);
642
643 pool = NULL;
644 if (need_pool) {
645 pool = gst_kms_sink_create_pool (self, caps, size, 0);
646 if (!pool)
647 goto no_pool;
648
649 /* Only export for pool used upstream */
650 if (self->has_prime_export) {
651 GstStructure *config = gst_buffer_pool_get_config (pool);
652 gst_buffer_pool_config_add_option (config,
653 GST_BUFFER_POOL_OPTION_KMS_PRIME_EXPORT);
654 gst_buffer_pool_set_config (pool, config);
655 }
656 }
657
658 /* we need at least 2 buffer because we hold on to the last one */
659 gst_query_add_allocation_pool (query, pool, size, 2, 0);
660 if (pool)
661 gst_object_unref (pool);
662
663 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
664 gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
665
666 return TRUE;
667
668 /* ERRORS */
669 no_caps:
670 {
671 GST_DEBUG_OBJECT (bsink, "no caps specified");
672 return FALSE;
673 }
674 invalid_caps:
675 {
676 GST_DEBUG_OBJECT (bsink, "invalid caps specified");
677 return FALSE;
678 }
679 afbc_caps:
680 {
681 GST_DEBUG_OBJECT (bsink, "no allocation for AFBC");
682 return FALSE;
683 }
684 no_pool:
685 {
686 /* Already warned in create_pool */
687 return FALSE;
688 }
689 }
690
691 static gboolean
gst_kms_sink_import_dmabuf(GstRkXImageSink * self,GstBuffer * inbuf,GstBuffer ** outbuf)692 gst_kms_sink_import_dmabuf (GstRkXImageSink * self, GstBuffer * inbuf,
693 GstBuffer ** outbuf)
694 {
695 gint prime_fds[GST_VIDEO_MAX_PLANES] = { 0, };
696 GstVideoMeta *meta;
697 guint i, n_mem, n_planes;
698 GstKMSMemory *kmsmem;
699 guint mems_idx[GST_VIDEO_MAX_PLANES];
700 gsize mems_skip[GST_VIDEO_MAX_PLANES];
701 GstMemory *mems[GST_VIDEO_MAX_PLANES];
702
703 if (!self->has_prime_import)
704 return FALSE;
705
706 /* This will eliminate most non-dmabuf out there */
707 if (!gst_is_dmabuf_memory (gst_buffer_peek_memory (inbuf, 0)))
708 return FALSE;
709
710 n_planes = GST_VIDEO_INFO_N_PLANES (&self->vinfo);
711 n_mem = gst_buffer_n_memory (inbuf);
712 meta = gst_buffer_get_video_meta (inbuf);
713
714 GST_TRACE_OBJECT (self, "Found a dmabuf with %u planes and %u memories",
715 n_planes, n_mem);
716
717 /* We cannot have multiple dmabuf per plane */
718 if (n_mem > n_planes)
719 return FALSE;
720 g_assert (n_planes != 0);
721
722 /* Update video info based on video meta */
723 if (meta) {
724 GST_VIDEO_INFO_WIDTH (&self->vinfo) = meta->width;
725 GST_VIDEO_INFO_HEIGHT (&self->vinfo) = meta->height;
726
727 for (i = 0; i < meta->n_planes; i++) {
728 GST_VIDEO_INFO_PLANE_OFFSET (&self->vinfo, i) = meta->offset[i];
729 GST_VIDEO_INFO_PLANE_STRIDE (&self->vinfo, i) = meta->stride[i];
730 }
731 }
732
733 /* Find and validate all memories */
734 for (i = 0; i < n_planes; i++) {
735 guint length;
736
737 if (!gst_buffer_find_memory (inbuf,
738 GST_VIDEO_INFO_PLANE_OFFSET (&self->vinfo, i), 1,
739 &mems_idx[i], &length, &mems_skip[i]))
740 return FALSE;
741
742 mems[i] = gst_buffer_peek_memory (inbuf, mems_idx[i]);
743
744 /* adjust for memory offset, in case data does not
745 * start from byte 0 in the dmabuf fd */
746 mems_skip[i] += mems[i]->offset;
747
748 /* And all memory found must be dmabuf */
749 if (!gst_is_dmabuf_memory (mems[i]))
750 return FALSE;
751 }
752
753 kmsmem = (GstKMSMemory *) gst_kms_allocator_get_cached (mems[0]);
754 if (kmsmem) {
755 GST_LOG_OBJECT (self, "found KMS mem %p in DMABuf mem %p with fb id = %d",
756 kmsmem, mems[0], kmsmem->fb_id);
757 goto wrap_mem;
758 }
759
760 for (i = 0; i < n_planes; i++)
761 prime_fds[i] = gst_dmabuf_memory_get_fd (mems[i]);
762
763 GST_LOG_OBJECT (self, "found these prime ids: %d, %d, %d, %d", prime_fds[0],
764 prime_fds[1], prime_fds[2], prime_fds[3]);
765
766 kmsmem = gst_kms_allocator_dmabuf_import (self->allocator,
767 prime_fds, n_planes, mems_skip, &self->vinfo);
768 if (!kmsmem)
769 return FALSE;
770
771 GST_LOG_OBJECT (self, "setting KMS mem %p to DMABuf mem %p with fb id = %d",
772 kmsmem, mems[0], kmsmem->fb_id);
773 gst_kms_allocator_cache (self->allocator, mems[0], GST_MEMORY_CAST (kmsmem));
774
775 wrap_mem:
776 *outbuf = gst_buffer_new ();
777 if (!*outbuf)
778 return FALSE;
779 gst_buffer_append_memory (*outbuf, gst_memory_ref (GST_MEMORY_CAST (kmsmem)));
780 gst_buffer_add_parent_buffer_meta (*outbuf, inbuf);
781
782 return TRUE;
783 }
784
785 static GstBuffer *
gst_kms_sink_copy_to_dumb_buffer(GstRkXImageSink * self,GstBuffer * inbuf)786 gst_kms_sink_copy_to_dumb_buffer (GstRkXImageSink * self, GstBuffer * inbuf)
787 {
788 GstFlowReturn ret;
789 GstVideoFrame inframe, outframe;
790 gboolean success;
791 GstBuffer *buf = NULL;
792
793 if (GST_VIDEO_INFO_IS_AFBC (&self->vinfo)) {
794 GST_ERROR_OBJECT (self, "unable to copy AFBC");
795 return NULL;
796 }
797
798 if (!gst_buffer_pool_set_active (self->pool, TRUE))
799 goto activate_pool_failed;
800
801 ret = gst_buffer_pool_acquire_buffer (self->pool, &buf, NULL);
802 if (ret != GST_FLOW_OK)
803 goto create_buffer_failed;
804
805 if (!gst_video_frame_map (&inframe, &self->vinfo, inbuf, GST_MAP_READ))
806 goto error_map_src_buffer;
807
808 if (!gst_video_frame_map (&outframe, &self->vinfo, buf, GST_MAP_WRITE))
809 goto error_map_dst_buffer;
810
811 success = gst_video_frame_copy (&outframe, &inframe);
812 gst_video_frame_unmap (&outframe);
813 gst_video_frame_unmap (&inframe);
814 if (!success)
815 goto error_copy_buffer;
816
817 return buf;
818
819 bail:
820 {
821 if (buf)
822 gst_buffer_unref (buf);
823 return NULL;
824 }
825
826 /* ERRORS */
827 activate_pool_failed:
828 {
829 GST_ELEMENT_ERROR (self, STREAM, FAILED, ("failed to activate buffer pool"),
830 ("failed to activate buffer pool"));
831 return NULL;
832 }
833 create_buffer_failed:
834 {
835 GST_ELEMENT_ERROR (self, STREAM, FAILED, ("allocation failed"),
836 ("failed to create buffer"));
837 return NULL;
838 }
839 error_copy_buffer:
840 {
841 GST_WARNING_OBJECT (self, "failed to upload buffer");
842 goto bail;
843 }
844 error_map_dst_buffer:
845 {
846 gst_video_frame_unmap (&inframe);
847 /* fall-through */
848 }
849 error_map_src_buffer:
850 {
851 GST_WARNING_OBJECT (self, "failed to map buffer");
852 goto bail;
853 }
854 }
855
856 static GstBuffer *
gst_kms_sink_get_input_buffer(GstRkXImageSink * self,GstBuffer * inbuf)857 gst_kms_sink_get_input_buffer (GstRkXImageSink * self, GstBuffer * inbuf)
858 {
859 GstMemory *mem;
860 GstBuffer *buf = NULL;
861
862 mem = gst_buffer_peek_memory (inbuf, 0);
863 if (!mem)
864 return NULL;
865
866 if (gst_is_kms_memory (mem))
867 return gst_buffer_ref (inbuf);
868
869 if (gst_kms_sink_import_dmabuf (self, inbuf, &buf))
870 goto done;
871
872 buf = gst_kms_sink_copy_to_dumb_buffer (self, inbuf);
873
874 done:
875 /* Copy all the non-memory related metas, this way CropMeta will be
876 * available upon GstVideoOverlay::expose calls. */
877 if (buf)
878 gst_buffer_copy_into (buf, inbuf, GST_BUFFER_COPY_METADATA, 0, -1);
879
880 return buf;
881 }
882
883 static void
sync_handler(gint fd,guint frame,guint sec,guint usec,gpointer data)884 sync_handler (gint fd, guint frame, guint sec, guint usec, gpointer data)
885 {
886 gboolean *waiting;
887
888 waiting = data;
889 *waiting = FALSE;
890 }
891
892 static gboolean
gst_kms_sink_sync(GstRkXImageSink * self)893 gst_kms_sink_sync (GstRkXImageSink * self)
894 {
895 gint ret;
896 gboolean waiting;
897 drmEventContext evctxt = {
898 .version = DRM_EVENT_CONTEXT_VERSION,
899 .vblank_handler = sync_handler,
900 };
901 drmVBlank vbl = {
902 .request = {
903 .type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT,
904 .sequence = 1,
905 .signal = (gulong) & waiting,
906 },
907 };
908
909 if (self->pipe == 1)
910 vbl.request.type |= DRM_VBLANK_SECONDARY;
911 else if (self->pipe > 1)
912 vbl.request.type |= self->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT;
913
914 waiting = TRUE;
915 ret = drmWaitVBlank (self->fd, &vbl);
916 if (ret)
917 goto vblank_failed;
918
919 while (waiting) {
920 do {
921 ret = gst_poll_wait (self->poll, 3 * GST_SECOND);
922 } while (ret == -1 && (errno == EAGAIN || errno == EINTR));
923
924 ret = drmHandleEvent (self->fd, &evctxt);
925 if (ret)
926 goto event_failed;
927 }
928
929 return TRUE;
930
931 /* ERRORS */
932 vblank_failed:
933 {
934 GST_WARNING_OBJECT (self, "drmWaitVBlank failed: %s (%d)",
935 g_strerror (errno), errno);
936 return FALSE;
937 }
938 event_failed:
939 {
940 GST_ERROR_OBJECT (self, "drmHandleEvent failed: %s (%d)",
941 g_strerror (errno), errno);
942 return FALSE;
943 }
944 }
945
946 static void
gst_kms_sink_drain(GstRkXImageSink * self)947 gst_kms_sink_drain (GstRkXImageSink * self)
948 {
949 GstParentBufferMeta *parent_meta;
950
951 GST_DEBUG_OBJECT (self, "draining");
952
953 if (!self->last_buffer)
954 return;
955
956 /* We only need to return the last_buffer if it depends on upstream buffer.
957 * In this case, the last_buffer will have a GstParentBufferMeta set. */
958 parent_meta = gst_buffer_get_parent_buffer_meta (self->last_buffer);
959 if (parent_meta) {
960 GstBuffer *dumb_buf;
961 dumb_buf = gst_kms_sink_copy_to_dumb_buffer (self, parent_meta->buffer);
962 if (!dumb_buf)
963 dumb_buf = gst_buffer_ref (self->last_buffer);
964
965 gst_kms_allocator_clear_cache (self->allocator);
966 gst_x_image_sink_show_frame (GST_VIDEO_SINK (self), dumb_buf);
967 gst_buffer_unref (dumb_buf);
968 }
969 }
970
971 static gboolean
gst_kms_sink_query(GstBaseSink * bsink,GstQuery * query)972 gst_kms_sink_query (GstBaseSink * bsink, GstQuery * query)
973 {
974 GstRkXImageSink *self = GST_X_IMAGE_SINK (bsink);
975
976 switch (GST_QUERY_TYPE (query)) {
977 case GST_QUERY_ALLOCATION:
978 case GST_QUERY_DRAIN:
979 {
980 gst_kms_sink_drain (self);
981 break;
982 }
983 default:
984 break;
985 }
986
987 return GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
988 }
989
990 /*ximagesink*/
991 static gboolean
xwindow_calculate_display_ratio(GstRkXImageSink * self,int * x,int * y,gint * window_width,gint * window_height)992 xwindow_calculate_display_ratio (GstRkXImageSink * self, int *x, int *y,
993 gint * window_width, gint * window_height)
994 {
995 guint dar_n, dar_d;
996 guint video_par_n, video_par_d;
997 guint dpy_par_n, dpy_par_d;
998 gint video_width, video_height;
999
1000 video_width = GST_VIDEO_INFO_WIDTH (&self->vinfo);
1001 video_height = GST_VIDEO_INFO_HEIGHT (&self->vinfo);
1002
1003 video_par_n = self->par_n;
1004 video_par_d = self->par_d;
1005
1006 if (self->keep_aspect) {
1007 *window_width = video_width;
1008 *window_height = video_height;
1009 goto out;
1010 }
1011
1012 gst_video_calculate_device_ratio (self->hdisplay, self->vdisplay,
1013 self->mm_width, self->mm_height, &dpy_par_n, &dpy_par_d);
1014
1015 if (!gst_video_calculate_display_ratio (&dar_n, &dar_d, video_width,
1016 video_height, video_par_n, video_par_d, dpy_par_n, dpy_par_d))
1017 return FALSE;
1018
1019 GST_DEBUG_OBJECT (self, "video calculated display ratio: %d/%d", dar_n,
1020 dar_d);
1021
1022 /* now find a width x height that respects this display ratio.
1023 * prefer those that have one of w/h the same as the incoming video
1024 * using wd / hd = dar_n / dar_d */
1025
1026 /* start with same height, because of interlaced video */
1027 /* check hd / dar_d is an integer scale factor, and scale wd with the PAR */
1028 video_width = gst_util_uint64_scale_int (self->xwindow->height, dar_n, dar_d);
1029 video_height = gst_util_uint64_scale_int (self->xwindow->width, dar_d, dar_n);
1030 if (video_width < *window_width) {
1031 *x += (self->xwindow->width - video_width) / 2;
1032 *window_width = video_width;
1033 *window_height = self->xwindow->height;
1034 } else {
1035 *y += (self->xwindow->height - video_height) / 2;
1036 *window_height = video_height;
1037 *window_width = self->xwindow->width;
1038 }
1039
1040 out:
1041 GST_DEBUG_OBJECT (self, "scaling to %dx%d", *window_width, *window_height);
1042
1043 return TRUE;
1044 }
1045
1046 /* X11 stuff */
1047
1048 static gboolean
xwindow_get_window_position(GstRkXImageSink * ximagesink,int * x,int * y)1049 xwindow_get_window_position (GstRkXImageSink * ximagesink, int *x, int *y)
1050 {
1051 XWindowAttributes attr;
1052 Window child;
1053 int tmp_x, tmp_y;
1054 static int last_x = 0, last_y = 0;
1055
1056 if (x == NULL || y == NULL) {
1057 x = &tmp_x;
1058 y = &tmp_y;
1059 }
1060
1061 XGetWindowAttributes (ximagesink->xcontext->disp,
1062 ximagesink->xwindow->win, &attr);
1063
1064 XTranslateCoordinates (ximagesink->xcontext->disp, ximagesink->xwindow->win,
1065 ximagesink->xcontext->root, 0, 0, x, y, &child);
1066
1067 if (last_x != *x || last_y != *y) {
1068 last_x = *x;
1069 last_y = *y;
1070 /* moved */
1071 return TRUE;
1072 }
1073
1074 return FALSE;
1075 }
1076
1077 static void
xwindow_get_render_rectangle(GstRkXImageSink * ximagesink,gint * x,gint * y,gint * width,gint * height)1078 xwindow_get_render_rectangle (GstRkXImageSink * ximagesink,
1079 gint * x, gint * y, gint * width, gint * height)
1080 {
1081 if (ximagesink->save_rect.w != 0 && ximagesink->save_rect.h != 0) {
1082 *width = ximagesink->save_rect.w;
1083 *height = ximagesink->save_rect.h;
1084 *x += ximagesink->save_rect.x;
1085 *y += ximagesink->save_rect.y;
1086 }
1087 }
1088
1089 static void
gst_x_image_sink_xwindow_fill_key(GstRkXImageSink * ximagesink,GstXWindow * xwindow,guint32 color)1090 gst_x_image_sink_xwindow_fill_key (GstRkXImageSink * ximagesink,
1091 GstXWindow * xwindow, guint32 color)
1092 {
1093 XSetForeground (ximagesink->xcontext->disp, xwindow->gc, color);
1094 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
1095 ximagesink->clip_rect.x, ximagesink->clip_rect.y,
1096 ximagesink->clip_rect.x + ximagesink->clip_rect.w,
1097 ximagesink->clip_rect.y + ximagesink->clip_rect.h);
1098 }
1099
1100 /* We are called with the x_lock taken */
1101 static void
gst_x_image_sink_xwindow_draw_borders(GstRkXImageSink * ximagesink,GstXWindow * xwindow,GstVideoRectangle rect)1102 gst_x_image_sink_xwindow_draw_borders (GstRkXImageSink * ximagesink,
1103 GstXWindow * xwindow, GstVideoRectangle rect)
1104 {
1105 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
1106 g_return_if_fail (xwindow != NULL);
1107
1108 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
1109 ximagesink->xcontext->black);
1110
1111 /* Left border */
1112 if (rect.x > 0) {
1113 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
1114 0, 0, rect.x, xwindow->height);
1115 }
1116
1117 /* Right border */
1118 if ((rect.x + rect.w) < xwindow->width) {
1119 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
1120 rect.x + rect.w, 0, xwindow->width, xwindow->height);
1121 }
1122
1123 /* Top border */
1124 if (rect.y > 0) {
1125 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
1126 0, 0, xwindow->width, rect.y);
1127 }
1128
1129 /* Bottom border */
1130 if ((rect.y + rect.h) < xwindow->height) {
1131 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
1132 0, rect.y + rect.h, xwindow->width, xwindow->height);
1133 }
1134 }
1135
1136 /* This function puts a GstXImageBuffer on a GstRkXImageSink's window */
1137 static gboolean
gst_x_image_sink_ximage_put(GstRkXImageSink * ximagesink,GstBuffer * buf)1138 gst_x_image_sink_ximage_put (GstRkXImageSink * ximagesink, GstBuffer * buf)
1139 {
1140 GstVideoCropMeta *crop;
1141 GstVideoRectangle src = { 0, };
1142 GstVideoRectangle result = { 0, };
1143 GstBuffer *buffer = NULL;
1144 gboolean draw_border = FALSE;
1145 gboolean res = FALSE;
1146 gboolean expose = buf == NULL;
1147 guint32 fb_id;
1148 gint ret;
1149
1150 /* We take the flow_lock. If expose is in there we don't want to run
1151 concurrently from the data flow thread */
1152 g_mutex_lock (&ximagesink->flow_lock);
1153
1154 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
1155 g_mutex_unlock (&ximagesink->flow_lock);
1156 return FALSE;
1157 }
1158
1159 /* Draw borders when displaying the first frame. After this
1160 draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */
1161 if (expose || !ximagesink->last_buffer || ximagesink->draw_border)
1162 draw_border = TRUE;
1163
1164 if (buf)
1165 buffer = gst_kms_sink_get_input_buffer (ximagesink, buf);
1166 else if (ximagesink->last_buffer)
1167 buffer = gst_buffer_ref (ximagesink->last_buffer);
1168
1169 /* Make sure buf is not used accidentally */
1170 buf = NULL;
1171
1172 if (!buffer)
1173 goto buffer_invalid;
1174 fb_id = gst_kms_memory_get_fb_id (gst_buffer_peek_memory (buffer, 0));
1175 if (fb_id == 0)
1176 goto buffer_invalid;
1177
1178 GST_TRACE_OBJECT (ximagesink, "displaying fb %d", fb_id);
1179
1180 crop = gst_buffer_get_video_crop_meta (buffer);
1181 if (crop) {
1182 src.x = crop->x;
1183 src.y = crop->y;
1184 src.w = crop->width;
1185 src.h = crop->height;
1186 GST_LOG_OBJECT (ximagesink,
1187 "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height);
1188 } else {
1189 src.w = GST_VIDEO_SINK_WIDTH (ximagesink);
1190 src.h = GST_VIDEO_SINK_HEIGHT (ximagesink);
1191 }
1192 result.w = ximagesink->xwindow->width;
1193 result.h = ximagesink->xwindow->height;
1194
1195 g_mutex_lock (&ximagesink->x_lock);
1196
1197 /* Fill color key when clip rect changed */
1198 if (draw_border || !RECT_EQUAL (ximagesink->clip_rect, result)) {
1199 ximagesink->clip_rect = result;
1200 gst_x_image_sink_xwindow_fill_key (ximagesink,
1201 ximagesink->xwindow, RKXIMAGE_COLOR_KEY);
1202
1203 /* Do an extra repaint when handing expose */
1204 if (expose)
1205 memset (&ximagesink->clip_rect, 0, sizeof (GstVideoRectangle));
1206 }
1207
1208 if (draw_border) {
1209 gst_x_image_sink_xwindow_draw_borders (ximagesink, ximagesink->xwindow,
1210 result);
1211 ximagesink->draw_border = FALSE;
1212 }
1213
1214 xwindow_get_window_position (ximagesink, &result.x, &result.y);
1215
1216 xwindow_get_render_rectangle (ximagesink, &result.x, &result.y, &result.w,
1217 &result.h);
1218 xwindow_calculate_display_ratio (ximagesink, &result.x, &result.y, &result.w,
1219 &result.h);
1220
1221 if (GST_VIDEO_INFO_IS_AFBC (&ximagesink->vinfo))
1222 /* The AFBC's width should align to 4 */
1223 src.w &= ~3;
1224
1225 GST_TRACE_OBJECT (ximagesink,
1226 "drmModeSetPlane at (%i,%i) %ix%i sourcing at (%i,%i) %ix%i",
1227 result.x, result.y, result.w, result.h, src.x, src.y, src.w, src.h);
1228
1229 ret = drmModeSetPlane (ximagesink->fd, ximagesink->plane_id,
1230 ximagesink->crtc_id, fb_id, 0, result.x, result.y, result.w, result.h,
1231 /* source/cropping coordinates are given in Q16 */
1232 src.x << 16, src.y << 16, src.w << 16, src.h << 16);
1233
1234 if (ret) {
1235 GST_ERROR_OBJECT (ximagesink, "drmModesetplane failed: %d", ret);
1236 goto out;
1237 }
1238
1239
1240 /* HACK: Disable vsync might cause tearing */
1241 if (!g_getenv ("KMSSINK_DISABLE_VSYNC"))
1242 /* Wait for the previous frame to complete redraw */
1243 if (!gst_kms_sink_sync (ximagesink))
1244 goto out;
1245
1246 if (buffer != ximagesink->last_buffer)
1247 gst_buffer_replace (&ximagesink->last_buffer, buffer);
1248
1249 res = TRUE;
1250
1251 out:
1252 if (buffer)
1253 gst_buffer_unref (buffer);
1254 g_mutex_unlock (&ximagesink->x_lock);
1255 g_mutex_unlock (&ximagesink->flow_lock);
1256 return res;
1257
1258 buffer_invalid:
1259 gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
1260 if (buffer)
1261 gst_buffer_unref (buffer);
1262 g_mutex_unlock (&ximagesink->flow_lock);
1263
1264 return TRUE;
1265 }
1266
1267 static gboolean
gst_x_image_sink_xwindow_decorate(GstRkXImageSink * ximagesink,GstXWindow * window)1268 gst_x_image_sink_xwindow_decorate (GstRkXImageSink * ximagesink,
1269 GstXWindow * window)
1270 {
1271 Atom hints_atom = None;
1272 MotifWmHints *hints;
1273
1274 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), FALSE);
1275 g_return_val_if_fail (window != NULL, FALSE);
1276
1277 g_mutex_lock (&ximagesink->x_lock);
1278
1279 hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS",
1280 True);
1281 if (hints_atom == None) {
1282 g_mutex_unlock (&ximagesink->x_lock);
1283 return FALSE;
1284 }
1285
1286 hints = g_malloc0 (sizeof (MotifWmHints));
1287
1288 hints->flags |= MWM_HINTS_DECORATIONS;
1289 hints->decorations = 1 << 0;
1290
1291 XChangeProperty (ximagesink->xcontext->disp, window->win,
1292 hints_atom, hints_atom, 32, PropModeReplace,
1293 (guchar *) hints, sizeof (MotifWmHints) / sizeof (long));
1294
1295 XSync (ximagesink->xcontext->disp, FALSE);
1296
1297 g_mutex_unlock (&ximagesink->x_lock);
1298
1299 g_free (hints);
1300
1301 return TRUE;
1302 }
1303
1304 static void
gst_x_image_sink_xwindow_set_title(GstRkXImageSink * ximagesink,GstXWindow * xwindow,const gchar * media_title)1305 gst_x_image_sink_xwindow_set_title (GstRkXImageSink * ximagesink,
1306 GstXWindow * xwindow, const gchar * media_title)
1307 {
1308 if (media_title) {
1309 g_free (ximagesink->media_title);
1310 ximagesink->media_title = g_strdup (media_title);
1311 }
1312 if (xwindow) {
1313 /* we have a window */
1314 if (xwindow->internal) {
1315 XTextProperty xproperty;
1316 XClassHint *hint = XAllocClassHint ();
1317 const gchar *app_name;
1318 const gchar *title = NULL;
1319 gchar *title_mem = NULL;
1320
1321 /* set application name as a title */
1322 app_name = g_get_application_name ();
1323
1324 if (app_name && ximagesink->media_title) {
1325 title = title_mem = g_strconcat (ximagesink->media_title, " : ",
1326 app_name, NULL);
1327 } else if (app_name) {
1328 title = app_name;
1329 } else if (ximagesink->media_title) {
1330 title = ximagesink->media_title;
1331 }
1332
1333 if (title) {
1334 if ((XStringListToTextProperty (((char **) &title), 1,
1335 &xproperty)) != 0) {
1336 XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty);
1337 XFree (xproperty.value);
1338 }
1339
1340 g_free (title_mem);
1341 }
1342
1343 if (hint) {
1344 hint->res_name = (char *) app_name;
1345 hint->res_class = (char *) "GStreamer";
1346 XSetClassHint (ximagesink->xcontext->disp, xwindow->win, hint);
1347 }
1348 XFree (hint);
1349 }
1350 }
1351 }
1352
1353 /* This function handles a GstXWindow creation */
1354 static GstXWindow *
gst_x_image_sink_xwindow_new(GstRkXImageSink * ximagesink,gint width,gint height)1355 gst_x_image_sink_xwindow_new (GstRkXImageSink * ximagesink, gint width,
1356 gint height)
1357 {
1358 GstXWindow *xwindow = NULL;
1359 XGCValues values;
1360
1361 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
1362
1363 xwindow = g_new0 (GstXWindow, 1);
1364
1365 xwindow->width = width;
1366 xwindow->height = height;
1367 xwindow->internal = TRUE;
1368
1369 g_mutex_lock (&ximagesink->x_lock);
1370
1371 xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp,
1372 ximagesink->xcontext->root,
1373 0, 0, width, height, 0, 0, ximagesink->xcontext->black);
1374
1375 /* We have to do that to prevent X from redrawing the background on
1376 ConfigureNotify. This takes away flickering of video when resizing. */
1377 XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None);
1378
1379 /* set application name as a title */
1380 gst_x_image_sink_xwindow_set_title (ximagesink, xwindow, NULL);
1381
1382 if (ximagesink->handle_events) {
1383 Atom wm_delete;
1384
1385 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
1386 StructureNotifyMask | PointerMotionMask | KeyPressMask |
1387 KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
1388
1389 /* Tell the window manager we'd like delete client messages instead of
1390 * being killed */
1391 wm_delete = XInternAtom (ximagesink->xcontext->disp,
1392 "WM_DELETE_WINDOW", False);
1393 (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win,
1394 &wm_delete, 1);
1395 }
1396
1397 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win,
1398 0, &values);
1399
1400 XMapRaised (ximagesink->xcontext->disp, xwindow->win);
1401
1402 XSync (ximagesink->xcontext->disp, FALSE);
1403
1404 g_mutex_unlock (&ximagesink->x_lock);
1405
1406 gst_x_image_sink_xwindow_decorate (ximagesink, xwindow);
1407
1408 gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink),
1409 xwindow->win);
1410
1411 return xwindow;
1412 }
1413
1414 /* This function destroys a GstXWindow */
1415 static void
gst_x_image_sink_xwindow_destroy(GstRkXImageSink * ximagesink,GstXWindow * xwindow)1416 gst_x_image_sink_xwindow_destroy (GstRkXImageSink * ximagesink,
1417 GstXWindow * xwindow)
1418 {
1419 g_return_if_fail (xwindow != NULL);
1420 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
1421
1422 g_mutex_lock (&ximagesink->x_lock);
1423
1424 /* clean screen */
1425 if (ximagesink->last_fb_id) {
1426 drmModeRmFB (ximagesink->fd, ximagesink->last_fb_id);
1427 ximagesink->last_fb_id = 0;
1428 }
1429
1430 /* If we did not create that window we just free the GC and let it live */
1431 if (xwindow->internal)
1432 XDestroyWindow (ximagesink->xcontext->disp, xwindow->win);
1433 else
1434 XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0);
1435
1436 XFreeGC (ximagesink->xcontext->disp, xwindow->gc);
1437
1438 XSync (ximagesink->xcontext->disp, FALSE);
1439
1440 g_mutex_unlock (&ximagesink->x_lock);
1441
1442 g_free (xwindow);
1443 }
1444
1445 static void
gst_x_image_sink_xwindow_update_geometry(GstRkXImageSink * ximagesink)1446 gst_x_image_sink_xwindow_update_geometry (GstRkXImageSink * ximagesink)
1447 {
1448 XWindowAttributes attr;
1449 gboolean reconfigure;
1450
1451 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
1452
1453 /* Update the window geometry */
1454 g_mutex_lock (&ximagesink->x_lock);
1455 if (G_UNLIKELY (ximagesink->xwindow == NULL)) {
1456 g_mutex_unlock (&ximagesink->x_lock);
1457 return;
1458 }
1459
1460 XGetWindowAttributes (ximagesink->xcontext->disp,
1461 ximagesink->xwindow->win, &attr);
1462
1463 /* Check if we would suggest a different width/height now */
1464 reconfigure = (ximagesink->xwindow->width != attr.width)
1465 || (ximagesink->xwindow->height != attr.height);
1466 ximagesink->xwindow->width = attr.width;
1467 ximagesink->xwindow->height = attr.height;
1468
1469 g_mutex_unlock (&ximagesink->x_lock);
1470
1471 if (reconfigure)
1472 gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad,
1473 gst_event_new_reconfigure ());
1474 }
1475
1476 static void
gst_x_image_sink_xwindow_clear(GstRkXImageSink * ximagesink,GstXWindow * xwindow)1477 gst_x_image_sink_xwindow_clear (GstRkXImageSink * ximagesink,
1478 GstXWindow * xwindow)
1479 {
1480 g_return_if_fail (xwindow != NULL);
1481 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
1482
1483 g_mutex_lock (&ximagesink->x_lock);
1484
1485 XSetForeground (ximagesink->xcontext->disp, xwindow->gc,
1486 ximagesink->xcontext->black);
1487
1488 XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc,
1489 0, 0, xwindow->width, xwindow->height);
1490
1491 /* clean screen */
1492 if (ximagesink->last_fb_id) {
1493 drmModeRmFB (ximagesink->fd, ximagesink->last_fb_id);
1494 ximagesink->last_fb_id = 0;
1495 }
1496
1497 g_mutex_unlock (&ximagesink->x_lock);
1498 }
1499
1500 /* This function handles XEvents that might be in the queue. It generates
1501 GstEvent that will be sent upstream in the pipeline to handle interactivity
1502 and navigation.*/
1503 static void
gst_x_image_sink_handle_xevents(GstRkXImageSink * ximagesink)1504 gst_x_image_sink_handle_xevents (GstRkXImageSink * ximagesink)
1505 {
1506 XEvent e;
1507 guint pointer_x = 0, pointer_y = 0;
1508 gboolean pointer_moved = FALSE;
1509 gboolean exposed = FALSE, configured = FALSE;
1510
1511 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
1512
1513 /* Then we get all pointer motion events, only the last position is
1514 interesting. */
1515 g_mutex_lock (&ximagesink->flow_lock);
1516 g_mutex_lock (&ximagesink->x_lock);
1517 while (XCheckWindowEvent (ximagesink->xcontext->disp,
1518 ximagesink->xwindow->win, PointerMotionMask, &e)) {
1519 g_mutex_unlock (&ximagesink->x_lock);
1520 g_mutex_unlock (&ximagesink->flow_lock);
1521
1522 switch (e.type) {
1523 case MotionNotify:
1524 pointer_x = e.xmotion.x;
1525 pointer_y = e.xmotion.y;
1526 pointer_moved = TRUE;
1527 break;
1528 default:
1529 break;
1530 }
1531 g_mutex_lock (&ximagesink->flow_lock);
1532 g_mutex_lock (&ximagesink->x_lock);
1533 }
1534
1535 if (pointer_moved) {
1536 g_mutex_unlock (&ximagesink->x_lock);
1537 g_mutex_unlock (&ximagesink->flow_lock);
1538
1539 GST_DEBUG ("ximagesink pointer moved over window at %d,%d",
1540 pointer_x, pointer_y);
1541 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
1542 "mouse-move", 0, pointer_x, pointer_y);
1543
1544 g_mutex_lock (&ximagesink->flow_lock);
1545 g_mutex_lock (&ximagesink->x_lock);
1546 }
1547
1548 /* We get all remaining events on our window to throw them upstream */
1549 while (XCheckWindowEvent (ximagesink->xcontext->disp,
1550 ximagesink->xwindow->win,
1551 KeyPressMask | KeyReleaseMask |
1552 ButtonPressMask | ButtonReleaseMask, &e)) {
1553 KeySym keysym;
1554 const char *key_str = NULL;
1555
1556 /* We lock only for the X function call */
1557 g_mutex_unlock (&ximagesink->x_lock);
1558 g_mutex_unlock (&ximagesink->flow_lock);
1559
1560 switch (e.type) {
1561 case ButtonPress:
1562 /* Mouse button pressed/released over our window. We send upstream
1563 events for interactivity/navigation */
1564 GST_DEBUG ("ximagesink button %d pressed over window at %d,%d",
1565 e.xbutton.button, e.xbutton.x, e.xbutton.x);
1566 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
1567 "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y);
1568 break;
1569 case ButtonRelease:
1570 GST_DEBUG ("ximagesink button %d release over window at %d,%d",
1571 e.xbutton.button, e.xbutton.x, e.xbutton.x);
1572 gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink),
1573 "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y);
1574 break;
1575 case KeyPress:
1576 case KeyRelease:
1577 /* Key pressed/released over our window. We send upstream
1578 events for interactivity/navigation */
1579 g_mutex_lock (&ximagesink->x_lock);
1580 keysym = XkbKeycodeToKeysym (ximagesink->xcontext->disp,
1581 e.xkey.keycode, 0, 0);
1582 if (keysym != NoSymbol) {
1583 key_str = XKeysymToString (keysym);
1584 } else {
1585 key_str = "unknown";
1586 }
1587 g_mutex_unlock (&ximagesink->x_lock);
1588 GST_DEBUG_OBJECT (ximagesink,
1589 "key %d pressed over window at %d,%d (%s)",
1590 e.xkey.keycode, e.xkey.x, e.xkey.y, key_str);
1591 gst_navigation_send_key_event (GST_NAVIGATION (ximagesink),
1592 e.type == KeyPress ? "key-press" : "key-release", key_str);
1593 break;
1594 default:
1595 GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)",
1596 e.type);
1597 }
1598 g_mutex_lock (&ximagesink->flow_lock);
1599 g_mutex_lock (&ximagesink->x_lock);
1600 }
1601
1602 /* Handle Expose */
1603 while (XCheckWindowEvent (ximagesink->xcontext->disp,
1604 ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) {
1605 switch (e.type) {
1606 case Expose:
1607 exposed = TRUE;
1608 break;
1609 case ConfigureNotify:
1610 g_mutex_unlock (&ximagesink->x_lock);
1611 gst_x_image_sink_xwindow_update_geometry (ximagesink);
1612 g_mutex_lock (&ximagesink->x_lock);
1613 configured = TRUE;
1614 break;
1615 default:
1616 break;
1617 }
1618
1619 }
1620
1621 if (ximagesink->handle_expose && (exposed || configured)) {
1622 g_mutex_unlock (&ximagesink->x_lock);
1623 g_mutex_unlock (&ximagesink->flow_lock);
1624
1625 gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink));
1626
1627 g_mutex_lock (&ximagesink->flow_lock);
1628 g_mutex_lock (&ximagesink->x_lock);
1629 }
1630
1631 /* Handle Display events */
1632 while (XPending (ximagesink->xcontext->disp)) {
1633 XNextEvent (ximagesink->xcontext->disp, &e);
1634
1635 switch (e.type) {
1636 case ClientMessage:{
1637 Atom wm_delete;
1638
1639 wm_delete = XInternAtom (ximagesink->xcontext->disp,
1640 "WM_DELETE_WINDOW", False);
1641 if (wm_delete == (Atom) e.xclient.data.l[0]) {
1642 /* Handle window deletion by posting an error on the bus */
1643 GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND,
1644 ("Output window was closed"), (NULL));
1645
1646 g_mutex_unlock (&ximagesink->x_lock);
1647 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
1648 ximagesink->xwindow = NULL;
1649 g_mutex_lock (&ximagesink->x_lock);
1650 }
1651 break;
1652 }
1653 default:
1654 break;
1655 }
1656 }
1657
1658 /* Handle DRM display */
1659 if (ximagesink->paused
1660 && xwindow_get_window_position (ximagesink, NULL, NULL)) {
1661 /* if window stop moving, redraw display */
1662 g_mutex_unlock (&ximagesink->x_lock);
1663 g_mutex_unlock (&ximagesink->flow_lock);
1664
1665 gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink));
1666 g_mutex_lock (&ximagesink->flow_lock);
1667 g_mutex_lock (&ximagesink->x_lock);
1668 }
1669
1670 g_mutex_unlock (&ximagesink->x_lock);
1671 g_mutex_unlock (&ximagesink->flow_lock);
1672 }
1673
1674 static gpointer
gst_x_image_sink_event_thread(GstRkXImageSink * ximagesink)1675 gst_x_image_sink_event_thread (GstRkXImageSink * ximagesink)
1676 {
1677 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
1678
1679 GST_OBJECT_LOCK (ximagesink);
1680 while (ximagesink->running) {
1681 GST_OBJECT_UNLOCK (ximagesink);
1682
1683 if (ximagesink->xwindow) {
1684 gst_x_image_sink_handle_xevents (ximagesink);
1685 }
1686 /* FIXME: do we want to align this with the framerate or anything else? */
1687 g_usleep (G_USEC_PER_SEC / 100);
1688
1689 GST_OBJECT_LOCK (ximagesink);
1690 }
1691 GST_OBJECT_UNLOCK (ximagesink);
1692
1693 return NULL;
1694 }
1695
1696 static void
gst_x_image_sink_manage_event_thread(GstRkXImageSink * ximagesink)1697 gst_x_image_sink_manage_event_thread (GstRkXImageSink * ximagesink)
1698 {
1699 GThread *thread = NULL;
1700
1701 /* don't start the thread too early */
1702 if (ximagesink->xcontext == NULL) {
1703 return;
1704 }
1705
1706 GST_OBJECT_LOCK (ximagesink);
1707 if (ximagesink->handle_expose || ximagesink->handle_events) {
1708 if (!ximagesink->event_thread) {
1709 /* Setup our event listening thread */
1710 GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d",
1711 ximagesink->handle_expose, ximagesink->handle_events);
1712 ximagesink->running = TRUE;
1713 ximagesink->event_thread = g_thread_try_new ("ximagesink-events",
1714 (GThreadFunc) gst_x_image_sink_event_thread, ximagesink, NULL);
1715 }
1716 } else {
1717 if (ximagesink->event_thread) {
1718 GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d",
1719 ximagesink->handle_expose, ximagesink->handle_events);
1720 ximagesink->running = FALSE;
1721 /* grab thread and mark it as NULL */
1722 thread = ximagesink->event_thread;
1723 ximagesink->event_thread = NULL;
1724 }
1725 }
1726 GST_OBJECT_UNLOCK (ximagesink);
1727
1728 /* Wait for our event thread to finish */
1729 if (thread)
1730 g_thread_join (thread);
1731
1732 }
1733
1734 /* This function gets the X Display and global info about it. Everything is
1735 stored in our object and will be cleaned when the object is disposed. Note
1736 here that caps for supported format are generated without any window or
1737 image creation */
1738 static GstXContext *
gst_x_image_sink_xcontext_get(GstRkXImageSink * ximagesink)1739 gst_x_image_sink_xcontext_get (GstRkXImageSink * ximagesink)
1740 {
1741 GstXContext *xcontext = NULL;
1742 XPixmapFormatValues *px_formats = NULL;
1743 gint nb_formats = 0, i;
1744 gint endianness;
1745 GstVideoFormat vformat;
1746 guint32 alpha_mask;
1747
1748 g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL);
1749
1750 xcontext = g_new0 (GstXContext, 1);
1751
1752 g_mutex_lock (&ximagesink->x_lock);
1753
1754 xcontext->disp = XOpenDisplay (ximagesink->display_name);
1755
1756 if (!xcontext->disp) {
1757 g_mutex_unlock (&ximagesink->x_lock);
1758 g_free (xcontext);
1759 GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
1760 ("Could not initialise X output"), ("Could not open display"));
1761 return NULL;
1762 }
1763
1764 xcontext->screen = DefaultScreenOfDisplay (xcontext->disp);
1765 xcontext->screen_num = DefaultScreen (xcontext->disp);
1766 xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num);
1767 xcontext->root = DefaultRootWindow (xcontext->disp);
1768 xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num);
1769 xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num);
1770 xcontext->depth = DefaultDepthOfScreen (xcontext->screen);
1771
1772 xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num);
1773 xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num);
1774 xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num);
1775 xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num);
1776
1777 GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm",
1778 xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm);
1779
1780 /* We get supported pixmap formats at supported depth */
1781 px_formats = XListPixmapFormats (xcontext->disp, &nb_formats);
1782
1783 if (!px_formats) {
1784 XCloseDisplay (xcontext->disp);
1785 g_mutex_unlock (&ximagesink->x_lock);
1786 g_free (xcontext);
1787 GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS,
1788 ("Could not get supported pixmap formats"), (NULL));
1789 return NULL;
1790 }
1791
1792 /* We get bpp value corresponding to our running depth */
1793 for (i = 0; i < nb_formats; i++) {
1794 if (px_formats[i].depth == xcontext->depth)
1795 xcontext->bpp = px_formats[i].bits_per_pixel;
1796 }
1797
1798 XFree (px_formats);
1799
1800 endianness = (ImageByteOrder (xcontext->disp) ==
1801 LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN;
1802
1803 /* extrapolate alpha mask */
1804 if (xcontext->depth == 32) {
1805 alpha_mask = ~(xcontext->visual->red_mask
1806 | xcontext->visual->green_mask | xcontext->visual->blue_mask);
1807 } else {
1808 alpha_mask = 0;
1809 }
1810
1811 vformat =
1812 gst_video_format_from_masks (xcontext->depth, xcontext->bpp, endianness,
1813 xcontext->visual->red_mask, xcontext->visual->green_mask,
1814 xcontext->visual->blue_mask, alpha_mask);
1815
1816 if (vformat == GST_VIDEO_FORMAT_UNKNOWN)
1817 goto unknown_format;
1818
1819 g_mutex_unlock (&ximagesink->x_lock);
1820
1821 return xcontext;
1822
1823 /* ERRORS */
1824 unknown_format:
1825 {
1826 GST_ERROR_OBJECT (ximagesink, "unknown format");
1827 return NULL;
1828 }
1829 }
1830
1831 /* This function cleans the X context. Closing the Display and unrefing the
1832 caps for supported formats. */
1833 static void
gst_x_image_sink_xcontext_clear(GstRkXImageSink * ximagesink)1834 gst_x_image_sink_xcontext_clear (GstRkXImageSink * ximagesink)
1835 {
1836 GstXContext *xcontext;
1837
1838 g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink));
1839
1840 GST_OBJECT_LOCK (ximagesink);
1841 if (ximagesink->xcontext == NULL) {
1842 GST_OBJECT_UNLOCK (ximagesink);
1843 return;
1844 }
1845
1846 /* Take the xcontext reference and NULL it while we
1847 * clean it up, so that any buffer-alloced buffers
1848 * arriving after this will be freed correctly */
1849 xcontext = ximagesink->xcontext;
1850 ximagesink->xcontext = NULL;
1851
1852 GST_OBJECT_UNLOCK (ximagesink);
1853
1854 g_mutex_lock (&ximagesink->x_lock);
1855
1856 XCloseDisplay (xcontext->disp);
1857
1858 g_mutex_unlock (&ximagesink->x_lock);
1859
1860 g_free (xcontext);
1861 }
1862
1863 /* Element stuff */
1864
1865 static GstCaps *
gst_x_image_sink_get_allowed_caps(GstRkXImageSink * self)1866 gst_x_image_sink_get_allowed_caps (GstRkXImageSink * self)
1867 {
1868 if (!self->allowed_caps)
1869 return NULL; /* base class will return the template caps */
1870 return gst_caps_ref (self->allowed_caps);
1871 }
1872
1873 static GstCaps *
gst_x_image_sink_getcaps(GstBaseSink * bsink,GstCaps * filter)1874 gst_x_image_sink_getcaps (GstBaseSink * bsink, GstCaps * filter)
1875 {
1876 GstRkXImageSink *ximagesink;
1877 GstCaps *caps, *out_caps;
1878
1879 ximagesink = GST_X_IMAGE_SINK (bsink);
1880
1881 caps = gst_x_image_sink_get_allowed_caps (ximagesink);
1882 if (caps && filter) {
1883 out_caps = gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1884 gst_caps_unref (caps);
1885 } else {
1886 out_caps = caps;
1887 }
1888
1889 return out_caps;
1890 }
1891
1892 static gboolean
gst_x_image_sink_setcaps(GstBaseSink * bsink,GstCaps * caps)1893 gst_x_image_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
1894 {
1895 GstRkXImageSink *ximagesink;
1896 GstVideoInfo info;
1897 GstBufferPool *newpool, *oldpool;
1898 GstStructure *s;
1899 gint value;
1900
1901 ximagesink = GST_X_IMAGE_SINK (bsink);
1902
1903 if (!ximagesink->xcontext)
1904 return FALSE;
1905
1906 /* We are going to change the internal buffer pool, which means it will no
1907 * longer be compatbile with the last_buffer size. Drain now, as we won't be
1908 * able to do that later on. */
1909 gst_kms_sink_drain (ximagesink);
1910
1911 GST_DEBUG_OBJECT (ximagesink, "given caps %" GST_PTR_FORMAT, caps);
1912
1913 if (!gst_video_info_from_caps (&info, caps))
1914 goto invalid_format;
1915
1916 /* parse AFBC from caps */
1917 s = gst_caps_get_structure (caps, 0);
1918 if (gst_structure_get_int (s, "arm-afbc", &value)) {
1919 if (value)
1920 GST_VIDEO_INFO_SET_AFBC (&info);
1921 else
1922 GST_VIDEO_INFO_UNSET_AFBC (&info);
1923 }
1924
1925 GST_VIDEO_SINK_WIDTH (ximagesink) = info.width;
1926 GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height;
1927 ximagesink->fps_n = info.fps_n;
1928 ximagesink->fps_d = info.fps_d;
1929 ximagesink->par_n = info.par_n;
1930 ximagesink->par_d = info.par_d;
1931
1932 /* Notify application to set xwindow id now */
1933 g_mutex_lock (&ximagesink->flow_lock);
1934 if (!ximagesink->xwindow) {
1935 g_mutex_unlock (&ximagesink->flow_lock);
1936 gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink));
1937 } else {
1938 g_mutex_unlock (&ximagesink->flow_lock);
1939 }
1940
1941 /* Creating our window and our image */
1942 if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 ||
1943 GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0)
1944 goto invalid_size;
1945
1946 /* create a new pool for the new configuration */
1947 newpool = gst_kms_sink_create_pool (ximagesink, caps,
1948 GST_VIDEO_INFO_SIZE (&info), 2);
1949 if (!newpool)
1950 goto no_pool;
1951
1952 /* we don't activate the internal pool yet as it may not be needed */
1953 oldpool = ximagesink->pool;
1954 ximagesink->pool = newpool;
1955
1956 if (oldpool) {
1957 gst_buffer_pool_set_active (oldpool, FALSE);
1958 gst_object_unref (oldpool);
1959 }
1960
1961 g_mutex_lock (&ximagesink->flow_lock);
1962 if (!ximagesink->xwindow) {
1963 ximagesink->xwindow = gst_x_image_sink_xwindow_new (ximagesink,
1964 GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink));
1965 }
1966
1967 ximagesink->vinfo = info;
1968
1969 /* Remember to draw borders for next frame */
1970 ximagesink->draw_border = TRUE;
1971
1972 g_mutex_unlock (&ximagesink->flow_lock);
1973
1974 return TRUE;
1975
1976 /* ERRORS */
1977 invalid_format:
1978 {
1979 GST_ERROR_OBJECT (ximagesink, "caps invalid");
1980 return FALSE;
1981 }
1982 invalid_size:
1983 {
1984 GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL),
1985 ("Invalid image size."));
1986 return FALSE;
1987 }
1988 no_pool:
1989 {
1990 /* Already warned in create_pool */
1991 return FALSE;
1992 }
1993 }
1994
1995 static GstStateChangeReturn
gst_x_image_sink_change_state(GstElement * element,GstStateChange transition)1996 gst_x_image_sink_change_state (GstElement * element, GstStateChange transition)
1997 {
1998 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1999 GstRkXImageSink *ximagesink;
2000 GstXContext *xcontext = NULL;
2001
2002 ximagesink = GST_X_IMAGE_SINK (element);
2003
2004 switch (transition) {
2005 case GST_STATE_CHANGE_NULL_TO_READY:
2006 /* Initializing the XContext */
2007 if (ximagesink->xcontext == NULL) {
2008 xcontext = gst_x_image_sink_xcontext_get (ximagesink);
2009 if (xcontext == NULL) {
2010 ret = GST_STATE_CHANGE_FAILURE;
2011 goto beach;
2012 }
2013 GST_OBJECT_LOCK (ximagesink);
2014 if (xcontext)
2015 ximagesink->xcontext = xcontext;
2016 GST_OBJECT_UNLOCK (ximagesink);
2017 }
2018
2019 /* call XSynchronize with the current value of synchronous */
2020 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
2021 ximagesink->synchronous ? "TRUE" : "FALSE");
2022 g_mutex_lock (&ximagesink->x_lock);
2023 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
2024 g_mutex_unlock (&ximagesink->x_lock);
2025 gst_x_image_sink_manage_event_thread (ximagesink);
2026 break;
2027 case GST_STATE_CHANGE_READY_TO_PAUSED:
2028 ximagesink->paused = TRUE;
2029 g_mutex_lock (&ximagesink->flow_lock);
2030 if (ximagesink->xwindow)
2031 gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
2032 g_mutex_unlock (&ximagesink->flow_lock);
2033 break;
2034 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2035 ximagesink->paused = FALSE;
2036 break;
2037 default:
2038 break;
2039 }
2040
2041 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2042
2043 switch (transition) {
2044 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2045 ximagesink->paused = TRUE;
2046 break;
2047 case GST_STATE_CHANGE_PAUSED_TO_READY:
2048 ximagesink->paused = FALSE;
2049 ximagesink->fps_n = 0;
2050 ximagesink->fps_d = 1;
2051 GST_VIDEO_SINK_WIDTH (ximagesink) = 0;
2052 GST_VIDEO_SINK_HEIGHT (ximagesink) = 0;
2053 break;
2054 case GST_STATE_CHANGE_READY_TO_NULL:
2055 gst_x_image_sink_reset (ximagesink);
2056 break;
2057 default:
2058 break;
2059 }
2060
2061 beach:
2062 return ret;
2063 }
2064
2065 static void
gst_x_image_sink_get_times(GstBaseSink * bsink,GstBuffer * buf,GstClockTime * start,GstClockTime * end)2066 gst_x_image_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
2067 GstClockTime * start, GstClockTime * end)
2068 {
2069 GstRkXImageSink *ximagesink;
2070
2071 ximagesink = GST_X_IMAGE_SINK (bsink);
2072
2073 if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
2074 *start = GST_BUFFER_TIMESTAMP (buf);
2075 if (GST_BUFFER_DURATION_IS_VALID (buf)) {
2076 *end = *start + GST_BUFFER_DURATION (buf);
2077 } else {
2078 if (ximagesink->fps_n > 0) {
2079 *end = *start +
2080 gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d,
2081 ximagesink->fps_n);
2082 }
2083 }
2084 }
2085 }
2086
2087 static GstFlowReturn
gst_x_image_sink_show_frame(GstVideoSink * vsink,GstBuffer * buf)2088 gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
2089 {
2090 GstFlowReturn res = GST_FLOW_OK;
2091 GstRkXImageSink *ximagesink;
2092
2093 ximagesink = GST_X_IMAGE_SINK (vsink);
2094
2095 if (!gst_x_image_sink_ximage_put (ximagesink, buf)) {
2096 /* No Window available to put our image into */
2097 GST_WARNING_OBJECT (ximagesink, "could not output image - no window");
2098 res = GST_FLOW_ERROR;
2099 }
2100
2101 return res;
2102 }
2103
2104 static gboolean
gst_x_image_sink_event(GstBaseSink * sink,GstEvent * event)2105 gst_x_image_sink_event (GstBaseSink * sink, GstEvent * event)
2106 {
2107 GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (sink);
2108
2109 switch (GST_EVENT_TYPE (event)) {
2110 case GST_EVENT_TAG:{
2111 GstTagList *l;
2112 gchar *title = NULL;
2113
2114 gst_event_parse_tag (event, &l);
2115 gst_tag_list_get_string (l, GST_TAG_TITLE, &title);
2116
2117 if (title) {
2118 GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title);
2119 gst_x_image_sink_xwindow_set_title (ximagesink, ximagesink->xwindow,
2120 title);
2121
2122 g_free (title);
2123 }
2124 break;
2125 }
2126 default:
2127 break;
2128 }
2129
2130 return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
2131 }
2132
2133 /* Interfaces stuff */
2134 static void
gst_x_image_sink_navigation_send_event(GstNavigation * navigation,GstStructure * structure)2135 gst_x_image_sink_navigation_send_event (GstNavigation * navigation,
2136 GstStructure * structure)
2137 {
2138 GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (navigation);
2139 GstEvent *event = NULL;
2140 gint x_offset, y_offset;
2141 gdouble x, y;
2142 gboolean handled = FALSE;
2143
2144 /* We are not converting the pointer coordinates as there's no hardware
2145 scaling done here. The only possible scaling is done by videoscale and
2146 videoscale will have to catch those events and tranform the coordinates
2147 to match the applied scaling. So here we just add the offset if the image
2148 is centered in the window. */
2149
2150 /* We take the flow_lock while we look at the window */
2151 g_mutex_lock (&ximagesink->flow_lock);
2152
2153 if (!ximagesink->xwindow) {
2154 g_mutex_unlock (&ximagesink->flow_lock);
2155 gst_structure_free (structure);
2156 return;
2157 }
2158
2159 x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink);
2160 y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink);
2161
2162 g_mutex_unlock (&ximagesink->flow_lock);
2163
2164 if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) {
2165 x -= x_offset / 2;
2166 gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL);
2167 }
2168 if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) {
2169 y -= y_offset / 2;
2170 gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL);
2171 }
2172
2173 event = gst_event_new_navigation (structure);
2174 if (event) {
2175 gst_event_ref (event);
2176 handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (ximagesink), event);
2177
2178 if (!handled)
2179 gst_element_post_message (GST_ELEMENT_CAST (ximagesink),
2180 gst_navigation_message_new_event (GST_OBJECT_CAST (ximagesink),
2181 event));
2182
2183 gst_event_unref (event);
2184 }
2185 }
2186
2187 static void
gst_x_image_sink_navigation_init(GstNavigationInterface * iface)2188 gst_x_image_sink_navigation_init (GstNavigationInterface * iface)
2189 {
2190 iface->send_event = gst_x_image_sink_navigation_send_event;
2191 }
2192
2193 static void
gst_x_image_sink_set_window_handle(GstVideoOverlay * overlay,guintptr id)2194 gst_x_image_sink_set_window_handle (GstVideoOverlay * overlay, guintptr id)
2195 {
2196 XID xwindow_id = id;
2197 GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
2198 GstXWindow *xwindow = NULL;
2199
2200 /* We acquire the stream lock while setting this window in the element.
2201 We are basically cleaning tons of stuff replacing the old window, putting
2202 images while we do that would surely crash */
2203 g_mutex_lock (&ximagesink->flow_lock);
2204
2205 /* If we already use that window return */
2206 if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) {
2207 g_mutex_unlock (&ximagesink->flow_lock);
2208 return;
2209 }
2210
2211 /* If the element has not initialized the X11 context try to do so */
2212 if (!ximagesink->xcontext &&
2213 !(ximagesink->xcontext = gst_x_image_sink_xcontext_get (ximagesink))) {
2214 g_mutex_unlock (&ximagesink->flow_lock);
2215 /* we have thrown a GST_ELEMENT_ERROR now */
2216 return;
2217 }
2218
2219 /* If a window is there already we destroy it */
2220 if (ximagesink->xwindow) {
2221 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
2222 ximagesink->xwindow = NULL;
2223 }
2224
2225 /* If the xid is 0 we go back to an internal window */
2226 if (xwindow_id == 0) {
2227 /* If no width/height caps nego did not happen window will be created
2228 during caps nego then */
2229 if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) {
2230 xwindow = gst_x_image_sink_xwindow_new (ximagesink,
2231 GST_VIDEO_SINK_WIDTH (ximagesink),
2232 GST_VIDEO_SINK_HEIGHT (ximagesink));
2233 }
2234 } else {
2235 xwindow = g_new0 (GstXWindow, 1);
2236
2237 xwindow->win = xwindow_id;
2238
2239 /* We set the events we want to receive and create a GC. */
2240 g_mutex_lock (&ximagesink->x_lock);
2241 xwindow->internal = FALSE;
2242 if (ximagesink->handle_events) {
2243 XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask |
2244 StructureNotifyMask | PointerMotionMask | KeyPressMask |
2245 KeyReleaseMask);
2246 }
2247
2248 xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL);
2249 g_mutex_unlock (&ximagesink->x_lock);
2250 }
2251
2252 if (xwindow) {
2253 ximagesink->xwindow = xwindow;
2254 /* Update the window geometry, possibly generating a reconfigure event. */
2255 gst_x_image_sink_xwindow_update_geometry (ximagesink);
2256 }
2257
2258 g_mutex_unlock (&ximagesink->flow_lock);
2259 }
2260
2261 static void
gst_x_image_sink_expose(GstVideoOverlay * overlay)2262 gst_x_image_sink_expose (GstVideoOverlay * overlay)
2263 {
2264 GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
2265
2266 gst_x_image_sink_xwindow_update_geometry (ximagesink);
2267 gst_x_image_sink_ximage_put (ximagesink, NULL);
2268 }
2269
2270 static void
gst_x_image_sink_set_event_handling(GstVideoOverlay * overlay,gboolean handle_events)2271 gst_x_image_sink_set_event_handling (GstVideoOverlay * overlay,
2272 gboolean handle_events)
2273 {
2274 GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
2275
2276 ximagesink->handle_events = handle_events;
2277
2278 g_mutex_lock (&ximagesink->flow_lock);
2279
2280 if (G_UNLIKELY (!ximagesink->xwindow)) {
2281 g_mutex_unlock (&ximagesink->flow_lock);
2282 return;
2283 }
2284
2285 g_mutex_lock (&ximagesink->x_lock);
2286
2287 if (handle_events) {
2288 if (ximagesink->xwindow->internal) {
2289 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
2290 ExposureMask | StructureNotifyMask | PointerMotionMask |
2291 KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);
2292 } else {
2293 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win,
2294 ExposureMask | StructureNotifyMask | PointerMotionMask |
2295 KeyPressMask | KeyReleaseMask);
2296 }
2297 } else {
2298 XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0);
2299 }
2300
2301 g_mutex_unlock (&ximagesink->x_lock);
2302
2303 g_mutex_unlock (&ximagesink->flow_lock);
2304 }
2305
2306 static void
gst_x_image_sink_set_render_rectangle(GstVideoOverlay * overlay,gint x,gint y,gint width,gint height)2307 gst_x_image_sink_set_render_rectangle (GstVideoOverlay * overlay,
2308 gint x, gint y, gint width, gint height)
2309 {
2310 GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay);
2311 GST_DEBUG_OBJECT (ximagesink, "Set Render Rectangle"
2312 "x %d y %d width %d height %d", x, y, width, height);
2313
2314 ximagesink->save_rect.w = width;
2315 ximagesink->save_rect.h = height;
2316 ximagesink->save_rect.x = x;
2317 ximagesink->save_rect.y = y;
2318
2319 gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink));
2320 }
2321
2322 static void
gst_x_image_sink_video_overlay_init(GstVideoOverlayInterface * iface)2323 gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface * iface)
2324 {
2325 iface->set_window_handle = gst_x_image_sink_set_window_handle;
2326 iface->expose = gst_x_image_sink_expose;
2327 iface->set_render_rectangle = gst_x_image_sink_set_render_rectangle;
2328 iface->handle_events = gst_x_image_sink_set_event_handling;
2329 }
2330
2331 /* =========================================== */
2332 /* */
2333 /* Init & Class init */
2334 /* */
2335 /* =========================================== */
2336
2337 static void
gst_x_image_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)2338 gst_x_image_sink_set_property (GObject * object, guint prop_id,
2339 const GValue * value, GParamSpec * pspec)
2340 {
2341 GstRkXImageSink *ximagesink;
2342
2343 g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
2344
2345 ximagesink = GST_X_IMAGE_SINK (object);
2346
2347 switch (prop_id) {
2348 case PROP_DISPLAY:
2349 ximagesink->display_name = g_strdup (g_value_get_string (value));
2350 break;
2351 case PROP_SYNCHRONOUS:
2352 ximagesink->synchronous = g_value_get_boolean (value);
2353 if (ximagesink->xcontext) {
2354 GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s",
2355 ximagesink->synchronous ? "TRUE" : "FALSE");
2356 g_mutex_lock (&ximagesink->x_lock);
2357 XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous);
2358 g_mutex_unlock (&ximagesink->x_lock);
2359 }
2360 break;
2361 case PROP_HANDLE_EVENTS:
2362 gst_x_image_sink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink),
2363 g_value_get_boolean (value));
2364 gst_x_image_sink_manage_event_thread (ximagesink);
2365 break;
2366 case PROP_HANDLE_EXPOSE:
2367 ximagesink->handle_expose = g_value_get_boolean (value);
2368 gst_x_image_sink_manage_event_thread (ximagesink);
2369 break;
2370 case PROP_DRIVER_NAME:
2371 g_free (ximagesink->devname);
2372 ximagesink->devname = g_value_dup_string (value);
2373 break;
2374 case PROP_BUS_ID:
2375 g_free (ximagesink->bus_id);
2376 ximagesink->bus_id = g_value_dup_string (value);
2377 break;
2378 case PROP_CONNECTOR_ID:
2379 ximagesink->conn_id = g_value_get_int (value);
2380 break;
2381 case PROP_PLANE_ID:
2382 ximagesink->plane_id = g_value_get_int (value);
2383 break;
2384 case PROP_FORCE_ASPECT_RATIO:
2385 ximagesink->keep_aspect = g_value_get_boolean (value);
2386 break;
2387 default:
2388 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2389 break;
2390 }
2391 }
2392
2393 static void
gst_x_image_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)2394 gst_x_image_sink_get_property (GObject * object, guint prop_id,
2395 GValue * value, GParamSpec * pspec)
2396 {
2397 GstRkXImageSink *ximagesink;
2398
2399 g_return_if_fail (GST_IS_X_IMAGE_SINK (object));
2400
2401 ximagesink = GST_X_IMAGE_SINK (object);
2402
2403 switch (prop_id) {
2404 case PROP_DISPLAY:
2405 g_value_set_string (value, ximagesink->display_name);
2406 break;
2407 case PROP_SYNCHRONOUS:
2408 g_value_set_boolean (value, ximagesink->synchronous);
2409 break;
2410 case PROP_HANDLE_EVENTS:
2411 g_value_set_boolean (value, ximagesink->handle_events);
2412 break;
2413 case PROP_HANDLE_EXPOSE:
2414 g_value_set_boolean (value, ximagesink->handle_expose);
2415 break;
2416 case PROP_WINDOW_WIDTH:
2417 if (ximagesink->xwindow)
2418 g_value_set_uint64 (value, ximagesink->xwindow->width);
2419 else
2420 g_value_set_uint64 (value, 0);
2421 break;
2422 case PROP_WINDOW_HEIGHT:
2423 if (ximagesink->xwindow)
2424 g_value_set_uint64 (value, ximagesink->xwindow->height);
2425 else
2426 g_value_set_uint64 (value, 0);
2427 break;
2428 case PROP_DRIVER_NAME:
2429 g_value_set_string (value, ximagesink->devname);
2430 break;
2431 case PROP_BUS_ID:
2432 g_value_set_string (value, ximagesink->bus_id);
2433 break;
2434 case PROP_CONNECTOR_ID:
2435 g_value_set_int (value, ximagesink->conn_id);
2436 break;
2437 case PROP_PLANE_ID:
2438 g_value_set_int (value, ximagesink->plane_id);
2439 break;
2440 case PROP_FORCE_ASPECT_RATIO:
2441 g_value_set_boolean (value, ximagesink->keep_aspect);
2442 break;
2443 default:
2444 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2445 break;
2446 }
2447 }
2448
2449 static void
gst_x_image_sink_reset(GstRkXImageSink * ximagesink)2450 gst_x_image_sink_reset (GstRkXImageSink * ximagesink)
2451 {
2452 GThread *thread;
2453
2454 GST_OBJECT_LOCK (ximagesink);
2455 ximagesink->running = FALSE;
2456 /* grab thread and mark it as NULL */
2457 thread = ximagesink->event_thread;
2458 ximagesink->event_thread = NULL;
2459 GST_OBJECT_UNLOCK (ximagesink);
2460
2461 /* Wait for our event thread to finish before we clean up our stuff. */
2462 if (thread)
2463 g_thread_join (thread);
2464
2465 gst_buffer_replace (&ximagesink->last_buffer, NULL);
2466
2467 g_mutex_lock (&ximagesink->flow_lock);
2468
2469 if (ximagesink->xwindow) {
2470 gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow);
2471 gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow);
2472 ximagesink->xwindow = NULL;
2473 }
2474 g_mutex_unlock (&ximagesink->flow_lock);
2475
2476 gst_x_image_sink_xcontext_clear (ximagesink);
2477 }
2478
2479 static void
gst_x_image_sink_finalize(GObject * object)2480 gst_x_image_sink_finalize (GObject * object)
2481 {
2482 GstRkXImageSink *ximagesink;
2483
2484 ximagesink = GST_X_IMAGE_SINK (object);
2485
2486 gst_x_image_sink_reset (ximagesink);
2487
2488 if (ximagesink->display_name) {
2489 g_free (ximagesink->display_name);
2490 ximagesink->display_name = NULL;
2491 }
2492 g_mutex_clear (&ximagesink->x_lock);
2493 g_mutex_clear (&ximagesink->flow_lock);
2494
2495 g_free (ximagesink->media_title);
2496
2497 gst_poll_free (ximagesink->poll);
2498
2499 g_clear_pointer (&ximagesink->devname, g_free);
2500 g_clear_pointer (&ximagesink->bus_id, g_free);
2501
2502 G_OBJECT_CLASS (parent_class)->finalize (object);
2503 }
2504
2505 static void
gst_x_image_sink_init(GstRkXImageSink * ximagesink)2506 gst_x_image_sink_init (GstRkXImageSink * ximagesink)
2507 {
2508 ximagesink->display_name = NULL;
2509 ximagesink->xcontext = NULL;
2510 ximagesink->xwindow = NULL;
2511 ximagesink->last_buffer = NULL;
2512
2513 ximagesink->event_thread = NULL;
2514 ximagesink->running = FALSE;
2515
2516 ximagesink->fps_n = 0;
2517 ximagesink->fps_d = 1;
2518
2519 ximagesink->keep_aspect = TRUE;
2520
2521 g_mutex_init (&ximagesink->x_lock);
2522 g_mutex_init (&ximagesink->flow_lock);
2523
2524 ximagesink->synchronous = FALSE;
2525 ximagesink->handle_events = TRUE;
2526 ximagesink->handle_expose = TRUE;
2527
2528 ximagesink->fd = -1;
2529 ximagesink->conn_id = -1;
2530 ximagesink->plane_id = -1;
2531 gst_poll_fd_init (&ximagesink->pollfd);
2532 ximagesink->poll = gst_poll_new (TRUE);
2533 gst_video_info_init (&ximagesink->vinfo);
2534
2535 ximagesink->save_rect.x = 0;
2536 ximagesink->save_rect.y = 0;
2537 ximagesink->save_rect.w = 0;
2538 ximagesink->save_rect.h = 0;
2539
2540 ximagesink->paused = FALSE;
2541
2542 ximagesink->devname = g_strdup ("rockchip");
2543 }
2544
2545 static gboolean
gst_x_image_sink_start(GstBaseSink * bsink)2546 gst_x_image_sink_start (GstBaseSink * bsink)
2547 {
2548 GstRkXImageSink *self;
2549 drmModeRes *res;
2550 drmModeConnector *conn;
2551 drmModeCrtc *crtc;
2552 drmModePlaneRes *pres;
2553 drmModePlane *plane;
2554 gboolean ret;
2555
2556 self = GST_X_IMAGE_SINK (bsink);
2557 ret = FALSE;
2558 res = NULL;
2559 conn = NULL;
2560 crtc = NULL;
2561 pres = NULL;
2562 plane = NULL;
2563
2564 if (self->devname || self->bus_id)
2565 self->fd = drmOpen (self->devname, self->bus_id);
2566
2567 if (self->fd < 0)
2568 self->fd = open ("/dev/dri/card0", O_RDWR | O_CLOEXEC);
2569
2570 if (self->fd < 0)
2571 goto open_failed;
2572
2573 drm_log_version (self);
2574 if (!drm_get_caps (self))
2575 goto bail;
2576
2577 res = drmModeGetResources (self->fd);
2578 if (!res)
2579 goto resources_failed;
2580
2581 if (self->conn_id == -1)
2582 conn = drm_find_main_monitor (self->fd, res);
2583 else
2584 conn = drmModeGetConnector (self->fd, self->conn_id);
2585 if (!conn)
2586 goto connector_failed;
2587
2588 crtc = drm_find_crtc_for_connector (self->fd, res, conn, &self->pipe);
2589 if (!crtc)
2590 goto crtc_failed;
2591
2592 if (drmSetClientCap (self->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1))
2593 goto set_cap_failed;
2594
2595 pres = drmModeGetPlaneResources (self->fd);
2596 if (!pres)
2597 goto plane_resources_failed;
2598
2599 if (self->plane_id == -1)
2600 plane = drm_find_plane_for_crtc_by_type (self->fd, res, pres,
2601 crtc->crtc_id, DRM_PLANE_TYPE_OVERLAY);
2602 else {
2603 plane = drmModeGetPlane (self->fd, self->plane_id);
2604 if (drm_plane_get_type (self->fd, plane) == DRM_PLANE_TYPE_PRIMARY) {
2605 GST_ERROR_OBJECT (self, "Not support using primary plane");
2606 goto bail;
2607 }
2608 }
2609 if (!plane)
2610 goto plane_failed;
2611
2612 /* let's get the available color formats in plane */
2613 if (!drm_ensure_allowed_caps (self, plane, res))
2614 goto bail;
2615
2616 self->conn_id = conn->connector_id;
2617 self->crtc_id = crtc->crtc_id;
2618 self->plane_id = plane->plane_id;
2619
2620 GST_INFO_OBJECT (self, "connector id = %d / crtc id = %d / plane id = %d",
2621 self->conn_id, self->crtc_id, self->plane_id);
2622
2623 if (g_getenv ("GST_RKXIMAGE_USE_COLORKEY"))
2624 drm_prepare_planes (self, res, pres);
2625
2626 self->hdisplay = crtc->mode.hdisplay;
2627 self->vdisplay = crtc->mode.vdisplay;
2628 self->buffer_id = crtc->buffer_id;
2629
2630 self->mm_width = conn->mmWidth;
2631 self->mm_height = conn->mmHeight;
2632
2633 GST_INFO_OBJECT (self, "display size: pixels = %dx%d / millimeters = %dx%d",
2634 self->hdisplay, self->vdisplay, self->mm_width, self->mm_height);
2635
2636 self->pollfd.fd = self->fd;
2637 gst_poll_add_fd (self->poll, &self->pollfd);
2638 gst_poll_fd_ctl_read (self->poll, &self->pollfd, TRUE);
2639
2640 ret = TRUE;
2641
2642 bail:
2643 if (plane)
2644 drmModeFreePlane (plane);
2645 if (pres)
2646 drmModeFreePlaneResources (pres);
2647 if (crtc)
2648 drmModeFreeCrtc (crtc);
2649 if (conn)
2650 drmModeFreeConnector (conn);
2651 if (res)
2652 drmModeFreeResources (res);
2653
2654 if (!ret && self->fd >= 0) {
2655 drmClose (self->fd);
2656 self->fd = -1;
2657 }
2658
2659 return ret;
2660
2661 /* ERRORS */
2662 open_failed:
2663 {
2664 GST_ERROR_OBJECT (self, "Could not open DRM module %s: %s",
2665 GST_STR_NULL (self->devname), strerror (errno));
2666 return FALSE;
2667 }
2668 resources_failed:
2669 {
2670 GST_ERROR_OBJECT (self, "drmModeGetResources failed: %s (%d)",
2671 strerror (errno), errno);
2672 goto bail;
2673 }
2674
2675 connector_failed:
2676 {
2677 GST_ERROR_OBJECT (self, "Could not find a valid monitor connector");
2678 goto bail;
2679 }
2680
2681 crtc_failed:
2682 {
2683 GST_ERROR_OBJECT (self, "Could not find a crtc for connector");
2684 goto bail;
2685 }
2686
2687 set_cap_failed:
2688 {
2689 GST_ERROR_OBJECT (self, "Could not set universal planes capability bit");
2690 goto bail;
2691 }
2692
2693 plane_resources_failed:
2694 {
2695 GST_ERROR_OBJECT (self, "drmModeGetPlaneResources failed: %s (%d)",
2696 strerror (errno), errno);
2697 goto bail;
2698 }
2699
2700 plane_failed:
2701 {
2702 GST_ERROR_OBJECT (self, "Could not find a plane for crtc");
2703 goto bail;
2704 }
2705 }
2706
2707 static gboolean
gst_x_image_sink_stop(GstBaseSink * bsink)2708 gst_x_image_sink_stop (GstBaseSink * bsink)
2709 {
2710 GstRkXImageSink *self;
2711
2712 self = GST_X_IMAGE_SINK (bsink);
2713
2714 if (!EMPTY_RECT (self->clip_rect))
2715 gst_x_image_sink_xwindow_fill_key (self, self->xwindow, 0);
2716
2717 gst_buffer_replace (&self->last_buffer, NULL);
2718 gst_caps_replace (&self->allowed_caps, NULL);
2719 gst_object_replace ((GstObject **) & self->pool, NULL);
2720 gst_object_replace ((GstObject **) & self->allocator, NULL);
2721
2722 gst_poll_remove_fd (self->poll, &self->pollfd);
2723 gst_poll_restart (self->poll);
2724 gst_poll_fd_init (&self->pollfd);
2725
2726 if (self->fd >= 0) {
2727 close (self->fd);
2728 self->fd = -1;
2729 }
2730
2731 return TRUE;
2732 }
2733
2734
2735 static void
gst_x_image_sink_class_init(GstRkXImageSinkClass * klass)2736 gst_x_image_sink_class_init (GstRkXImageSinkClass * klass)
2737 {
2738 GObjectClass *gobject_class;
2739 GstElementClass *gstelement_class;
2740 GstBaseSinkClass *gstbasesink_class;
2741 GstVideoSinkClass *videosink_class;
2742
2743 gobject_class = (GObjectClass *) klass;
2744 gstelement_class = (GstElementClass *) klass;
2745 gstbasesink_class = (GstBaseSinkClass *) klass;
2746 videosink_class = (GstVideoSinkClass *) klass;
2747
2748 gobject_class->finalize = gst_x_image_sink_finalize;
2749 gobject_class->set_property = gst_x_image_sink_set_property;
2750 gobject_class->get_property = gst_x_image_sink_get_property;
2751
2752 g_object_class_install_property (gobject_class, PROP_DISPLAY,
2753 g_param_spec_string ("display", "Display", "X Display name",
2754 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2755 g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS,
2756 g_param_spec_boolean ("synchronous", "Synchronous",
2757 "When enabled, runs the X display in synchronous mode. "
2758 "(unrelated to A/V sync, used only for debugging)", FALSE,
2759 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2760 g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS,
2761 g_param_spec_boolean ("handle-events", "Handle XEvents",
2762 "When enabled, XEvents will be selected and handled", TRUE,
2763 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2764 g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE,
2765 g_param_spec_boolean ("handle-expose", "Handle expose",
2766 "When enabled, "
2767 "the current frame will always be drawn in response to X Expose "
2768 "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2769
2770 /**
2771 * GstRkXImageSink:window-width
2772 *
2773 * Actual width of the video window.
2774 */
2775 g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH,
2776 g_param_spec_uint64 ("window-width", "window-width",
2777 "Width of the window", 0, G_MAXUINT64, 0,
2778 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2779
2780 /**
2781 * GstRkXImageSink:window-height
2782 *
2783 * Actual height of the video window.
2784 */
2785 g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT,
2786 g_param_spec_uint64 ("window-height", "window-height",
2787 "Height of the window", 0, G_MAXUINT64, 0,
2788 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
2789
2790 /**
2791 * kmssink:driver-name:
2792 *
2793 * If you have a system with multiple GPUs, you can choose which GPU
2794 * to use setting the DRM device driver name. Otherwise, the first
2795 * one from an internal list is used.
2796 */
2797 g_object_class_install_property (gobject_class, PROP_DRIVER_NAME,
2798 g_param_spec_string ("driver-name", "device name",
2799 "DRM device driver name", NULL,
2800 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT));
2801
2802 /**
2803 * kmssink:bus-id:
2804 *
2805 * If you have a system with multiple displays for the same driver-name,
2806 * you can choose which display to use by setting the DRM bus ID. Otherwise,
2807 * the driver decides which one.
2808 */
2809 g_object_class_install_property (gobject_class, PROP_BUS_ID,
2810 g_param_spec_string ("bus-id", "Bus ID", "DRM bus ID", NULL,
2811 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT));
2812
2813 /**
2814 * kmssink:connector-id:
2815 *
2816 * A GPU has several output connectors, for example: LVDS, VGA,
2817 * HDMI, etc. By default the first LVDS is tried, then the first
2818 * eDP, and at the end, the first connected one.
2819 */
2820 g_object_class_install_property (gobject_class, PROP_CONNECTOR_ID,
2821 g_param_spec_int ("connector-id", "Connector ID", "DRM connector id", -1,
2822 G_MAXINT32, -1,
2823 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT));
2824
2825 /**
2826 * kmssink:plane-id:
2827 *
2828 * There could be several planes associated with a CRTC.
2829 * By default the first plane that's possible to use with a given
2830 * CRTC is tried.
2831 */
2832 g_object_class_install_property (gobject_class, PROP_PLANE_ID,
2833 g_param_spec_int ("plane-id", "Plane ID", "DRM plane id", -1, G_MAXINT32,
2834 -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT));
2835
2836 g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
2837 g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
2838 "When enabled, scaling will respect original aspect ratio", TRUE,
2839 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
2840
2841 gst_element_class_set_static_metadata (gstelement_class,
2842 "Video sink", "Sink/Video",
2843 "A standard X based videosink", "Julien Moutte <julien@moutte.net>");
2844
2845 gst_element_class_add_static_pad_template (gstelement_class,
2846 &gst_x_image_sink_sink_template_factory);
2847
2848 gstelement_class->change_state = gst_x_image_sink_change_state;
2849
2850 gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_x_image_sink_start);
2851 gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_x_image_sink_stop);
2852 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_getcaps);
2853 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_setcaps);
2854 gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_x_image_sink_get_times);
2855 gstbasesink_class->propose_allocation =
2856 GST_DEBUG_FUNCPTR (gst_kms_sink_propose_allocation);
2857 gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_kms_sink_query);
2858 gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_x_image_sink_event);
2859
2860 videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_x_image_sink_show_frame);
2861 }
2862
2863 static gboolean
plugin_init(GstPlugin * plugin)2864 plugin_init (GstPlugin * plugin)
2865 {
2866 if (!gst_element_register (plugin, "rkximagesink",
2867 GST_RANK_SECONDARY, GST_TYPE_X_IMAGE_SINK))
2868 return FALSE;
2869
2870 GST_DEBUG_CATEGORY_INIT (gst_debug_x_image_sink, "rkximagesink", 0,
2871 "rkximagesink element");
2872
2873 return TRUE;
2874 }
2875
2876
2877 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
2878 GST_VERSION_MINOR,
2879 rkximage,
2880 "Rockchip X/DRM Video Sink",
2881 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
2882