#ifdef HAVE_CONFIG_H #include "config.h" #endif /* Our interfaces */ #include #include #include #include #include /* Debugging category */ #include /* for XkbKeycodeToKeysym */ #include #include #include #include #include #include #include #include /* Object header */ #include "ximagesink.h" #include "gstkmsutils.h" #include "gstkmsbufferpool.h" #include "gstkmsallocator.h" /* A random dark color */ #define RKXIMAGE_COLOR_KEY 0x010203 #define RK_COLOR_KEY_EN (1UL << 31) GST_DEBUG_CATEGORY (gst_debug_x_image_sink); #define GST_CAT_DEFAULT gst_debug_x_image_sink #define EMPTY_RECT(r) !((r).x || (r).y || (r).w || (r).h) #define RECT_EQUAL(r1, r2) \ ((r1).x == (r2).x && (r1).y == (r2).y && \ (r1).w == (r2.w) && (r1).h == (r2).h) 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) static void gst_x_image_sink_reset (GstRkXImageSink * ximagesink); static void gst_x_image_sink_xwindow_update_geometry (GstRkXImageSink * ximagesink); static void gst_x_image_sink_expose (GstVideoOverlay * overlay); static void gst_x_image_sink_xwindow_clear (GstRkXImageSink * ximagesink, GstXWindow * xwindow); static GstFlowReturn gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf); static GstStaticPadTemplate gst_x_image_sink_sink_template_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-raw, " "framerate = (fraction) [ 0, MAX ], " "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]") ); enum { PROP_0, PROP_DISPLAY, PROP_SYNCHRONOUS, PROP_HANDLE_EVENTS, PROP_HANDLE_EXPOSE, PROP_WINDOW_WIDTH, PROP_WINDOW_HEIGHT, PROP_DRIVER_NAME, PROP_CONNECTOR_ID, PROP_PLANE_ID }; /* ============================================================= */ /* */ /* Public Methods */ /* */ /* ============================================================= */ /* =========================================== */ /* */ /* Object typing & Creation */ /* */ /* =========================================== */ static void gst_x_image_sink_navigation_init (GstNavigationInterface * iface); static void gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface * iface); #define gst_x_image_sink_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstRkXImageSink, gst_x_image_sink, GST_TYPE_VIDEO_SINK, G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, gst_x_image_sink_navigation_init); G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY, gst_x_image_sink_video_overlay_init)); /* ============================================================= */ /* */ /* Private Methods */ /* */ /* ============================================================= */ /*drm*/ static int drm_plane_get_type (int fd, drmModePlane * plane) { drmModeObjectPropertiesPtr props; drmModePropertyPtr prop; int i, type = -1; props = drmModeObjectGetProperties (fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); if (!props) return -1; for (i = 0; i < props->count_props; i++) { prop = drmModeGetProperty (fd, props->props[i]); if (prop && !strcmp (prop->name, "type")) type = props->prop_values[i]; drmModeFreeProperty (prop); } drmModeFreeObjectProperties (props); return type; } static drmModePlane * drm_find_plane_for_crtc_by_type (int fd, drmModeRes * res, drmModePlaneRes * pres, int crtc_id, int type) { int i, pipe = -1, num_primary = 0; for (i = 0; i < res->count_crtcs; i++) { if (crtc_id == res->crtcs[i]) { pipe = i; break; } } if (pipe == -1) return NULL; for (i = 0; i < pres->count_planes; i++) { drmModePlane *plane = drmModeGetPlane (fd, pres->planes[i]); int plane_type = drm_plane_get_type (fd, plane); int primary = plane_type == DRM_PLANE_TYPE_PRIMARY; num_primary += primary; if ((plane->possible_crtcs & (1 << pipe)) && plane_type == type) { if (!primary || pipe == num_primary - 1) return plane; } drmModeFreePlane (plane); } return NULL; } static drmModeCrtc * drm_find_crtc_for_connector (int fd, drmModeRes * res, drmModeConnector * conn, guint * pipe) { int i; int crtc_id; drmModeEncoder *enc; drmModeCrtc *crtc; crtc_id = -1; for (i = 0; i < res->count_encoders; i++) { enc = drmModeGetEncoder (fd, res->encoders[i]); if (enc) { if (enc->encoder_id == conn->encoder_id) { crtc_id = enc->crtc_id; drmModeFreeEncoder (enc); break; } drmModeFreeEncoder (enc); } } if (crtc_id == -1) return NULL; for (i = 0; i < res->count_crtcs; i++) { crtc = drmModeGetCrtc (fd, res->crtcs[i]); if (crtc) { if (crtc_id == crtc->crtc_id) { if (pipe) *pipe = i; return crtc; } drmModeFreeCrtc (crtc); } } return NULL; } static gboolean drm_connector_is_used (int fd, drmModeRes * res, drmModeConnector * conn) { gboolean result; drmModeCrtc *crtc; result = FALSE; crtc = drm_find_crtc_for_connector (fd, res, conn, NULL); if (crtc) { result = crtc->buffer_id != 0; drmModeFreeCrtc (crtc); } return result; } static drmModeConnector * drm_find_used_connector_by_type (int fd, drmModeRes * res, int type) { int i; drmModeConnector *conn; conn = NULL; for (i = 0; i < res->count_connectors; i++) { conn = drmModeGetConnector (fd, res->connectors[i]); if (conn) { if ((conn->connector_type == type) && drm_connector_is_used (fd, res, conn)) return conn; drmModeFreeConnector (conn); } } return NULL; } static drmModeConnector * drm_find_first_used_connector (int fd, drmModeRes * res) { int i; drmModeConnector *conn; conn = NULL; for (i = 0; i < res->count_connectors; i++) { conn = drmModeGetConnector (fd, res->connectors[i]); if (conn) { if (drm_connector_is_used (fd, res, conn)) return conn; drmModeFreeConnector (conn); } } return NULL; } static drmModeConnector * drm_find_main_monitor (int fd, drmModeRes * res) { /* Find the LVDS and eDP connectors: those are the main screens. */ static const int priority[] = { DRM_MODE_CONNECTOR_LVDS, DRM_MODE_CONNECTOR_eDP }; int i; drmModeConnector *conn; conn = NULL; for (i = 0; !conn && i < G_N_ELEMENTS (priority); i++) conn = drm_find_used_connector_by_type (fd, res, priority[i]); /* if we didn't find a connector, grab the first one in use */ if (!conn) conn = drm_find_first_used_connector (fd, res); return conn; } static void drm_log_version (GstRkXImageSink * self) { #ifndef GST_DISABLE_GST_DEBUG drmVersion *v; v = drmGetVersion (self->fd); if (v) { GST_INFO_OBJECT (self, "DRM v%d.%d.%d [%s — %s — %s]", v->version_major, v->version_minor, v->version_patchlevel, GST_STR_NULL (v->name), GST_STR_NULL (v->desc), GST_STR_NULL (v->date)); drmFreeVersion (v); } else { GST_WARNING_OBJECT (self, "could not get driver information: %s", GST_STR_NULL (self->devname)); } #endif return; } static gboolean drm_get_caps (GstRkXImageSink * self) { gint ret; guint64 has_dumb_buffer; guint64 has_prime; guint64 has_async_page_flip; has_dumb_buffer = 0; ret = drmGetCap (self->fd, DRM_CAP_DUMB_BUFFER, &has_dumb_buffer); if (ret) GST_WARNING_OBJECT (self, "could not get dumb buffer capability"); if (has_dumb_buffer == 0) { GST_ERROR_OBJECT (self, "driver cannot handle dumb buffers"); return FALSE; } has_prime = 0; ret = drmGetCap (self->fd, DRM_CAP_PRIME, &has_prime); if (ret) GST_WARNING_OBJECT (self, "could not get prime capability"); else { self->has_prime_import = (gboolean) (has_prime & DRM_PRIME_CAP_IMPORT); self->has_prime_export = (gboolean) (has_prime & DRM_PRIME_CAP_EXPORT); } has_async_page_flip = 0; ret = drmGetCap (self->fd, DRM_CAP_ASYNC_PAGE_FLIP, &has_async_page_flip); if (ret) GST_WARNING_OBJECT (self, "could not get async page flip capability"); else self->has_async_page_flip = (gboolean) has_async_page_flip; GST_INFO_OBJECT (self, "prime import (%s) / prime export (%s) / async page flip (%s)", self->has_prime_import ? "✓" : "✗", self->has_prime_export ? "✓" : "✗", self->has_async_page_flip ? "✓" : "✗"); return TRUE; } static void check_afbc (GstRkXImageSink * self, drmModePlane * plane, guint32 drmfmt, gboolean * linear, gboolean * afbc) { drmModeObjectPropertiesPtr props; drmModePropertyBlobPtr blob; drmModePropertyPtr prop; drmModeResPtr res; struct drm_format_modifier_blob *header; struct drm_format_modifier *modifiers; guint32 *formats; guint64 value = 0; gint i, j; *linear = *afbc = FALSE; res = drmModeGetResources (self->fd); if (!res) return; props = drmModeObjectGetProperties (self->fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); if (!props) { drmModeFreeResources (res); return; } for (i = 0; i < props->count_props && !value; i++) { prop = drmModeGetProperty (self->fd, props->props[i]); if (!prop) continue; if (!strcmp (prop->name, "IN_FORMATS")) value = props->prop_values[i]; drmModeFreeProperty (prop); } drmModeFreeObjectProperties (props); drmModeFreeResources (res); /* No modifiers */ if (!value) { *linear = TRUE; return; } blob = drmModeGetPropertyBlob (self->fd, value); if (!blob) return; header = blob->data; modifiers = (struct drm_format_modifier *) ((gchar *) header + header->modifiers_offset); formats = (guint32 *) ((gchar *) header + header->formats_offset); for (i = 0; i < header->count_formats; i++) { if (formats[i] != drmfmt) continue; for (j = 0; j < header->count_modifiers; j++) { struct drm_format_modifier *mod = &modifiers[j]; if ((i < mod->offset) || (i > mod->offset + 63)) continue; if (!(mod->formats & (1 << (i - mod->offset)))) continue; if (mod->modifier == DRM_AFBC_MODIFIER) *afbc = TRUE; else if (mod->modifier == DRM_FORMAT_MOD_LINEAR) *linear = TRUE; } } drmModeFreePropertyBlob(blob); } static gboolean drm_ensure_allowed_caps (GstRkXImageSink * self, drmModePlane * plane, drmModeRes * res) { GstCaps *out_caps, *caps; int i; GstVideoFormat fmt; const gchar *format; if (self->allowed_caps) return TRUE; out_caps = gst_caps_new_empty (); if (!out_caps) return FALSE; for (i = 0; i < plane->count_formats; i++) { gboolean linear = FALSE, afbc = FALSE; check_afbc (self, plane, plane->formats[i], &linear, &afbc); if (plane->formats[i] == DRM_FORMAT_YUV420_8BIT) fmt = GST_VIDEO_FORMAT_NV12; else if (plane->formats[i] == DRM_FORMAT_YUV420_10BIT) fmt = GST_VIDEO_FORMAT_NV12_10LE40; else if (afbc && plane->formats[i] == DRM_FORMAT_YUYV) fmt = GST_VIDEO_FORMAT_NV16; else fmt = gst_video_format_from_drm (plane->formats[i]); if (fmt == GST_VIDEO_FORMAT_UNKNOWN) { GST_INFO_OBJECT (self, "ignoring format %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (plane->formats[i])); continue; } format = gst_video_format_to_string (fmt); caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING, format, "width", GST_TYPE_INT_RANGE, res->min_width, res->max_width, "height", GST_TYPE_INT_RANGE, res->min_height, res->max_height, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); if (!caps) continue; if (afbc) { GstCaps *afbc_caps = gst_caps_copy (caps); gst_caps_set_simple (afbc_caps, "arm-afbc", G_TYPE_INT, 1, NULL); if (linear) gst_caps_append (caps, afbc_caps); else gst_caps_replace (&caps, afbc_caps); } out_caps = gst_caps_merge (out_caps, caps); } self->allowed_caps = gst_caps_simplify (out_caps); GST_DEBUG_OBJECT (self, "allowed caps = %" GST_PTR_FORMAT, self->allowed_caps); return TRUE; } static gboolean drm_plane_set_property (GstRkXImageSink * self, drmModePlane * plane, const char *prop_name, uint64_t prop_value) { drmModeObjectPropertiesPtr props; drmModePropertyPtr prop; int i, ret = -1; props = drmModeObjectGetProperties (self->fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); if (!props) return FALSE; for (i = 0; i < props->count_props; i++) { prop = drmModeGetProperty (self->fd, props->props[i]); if (prop && !strcmp (prop->name, prop_name)) { ret = drmModeObjectSetProperty (self->fd, plane->plane_id, DRM_MODE_OBJECT_PLANE, props->props[i], prop_value); } drmModeFreeProperty (prop); } drmModeFreeObjectProperties (props); return ret < 0 ? FALSE : TRUE; } static gboolean drm_prepare_planes (GstRkXImageSink * self, drmModeRes * res, drmModePlaneRes * pres) { drmModePlane *plane; gboolean ret = FALSE; if (drmSetClientCap (self->fd, DRM_CLIENT_CAP_ATOMIC, 1)) return FALSE; /* Apply colorkey to primary plane */ plane = drm_find_plane_for_crtc_by_type (self->fd, res, pres, self->crtc_id, DRM_PLANE_TYPE_PRIMARY); if (!plane) return FALSE; if (!drm_plane_set_property (self, plane, "colorkey", RKXIMAGE_COLOR_KEY | RK_COLOR_KEY_EN)) goto out; GST_DEBUG_OBJECT (self, "applied colorkey = 0x%x to plane %d", RKXIMAGE_COLOR_KEY, plane->plane_id); /* Uper primary plane */ if (!drm_plane_set_property (self, plane, "ZPOS", 1) && !drm_plane_set_property (self, plane, "zpos", 1)) goto out; drmModeFreePlane (plane); /* Lower overlay plane */ plane = drmModeGetPlane (self->fd, self->plane_id); if (!plane) goto out; if (!drm_plane_set_property (self, plane, "ZPOS", 0) && !drm_plane_set_property (self, plane, "zpos", 0)) goto out; GST_DEBUG_OBJECT (self, "set plane %d zpos to 0", plane->plane_id); ret = TRUE; out: drmModeFreePlane (plane); return ret; } /*kms*/ static void ensure_kms_allocator (GstRkXImageSink * self) { if (self->allocator) return; self->allocator = gst_kms_allocator_new (self->fd); } static GstBufferPool * gst_kms_sink_create_pool (GstRkXImageSink * self, GstCaps * caps, gsize size, gint min) { GstBufferPool *pool; GstStructure *config; pool = gst_kms_buffer_pool_new (); if (!pool) goto pool_failed; config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_set_params (config, caps, size, min, 0); gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); ensure_kms_allocator (self); gst_buffer_pool_config_set_allocator (config, self->allocator, NULL); if (!gst_buffer_pool_set_config (pool, config)) goto config_failed; return pool; /* ERRORS */ pool_failed: { GST_ERROR_OBJECT (self, "failed to create buffer pool"); return NULL; } config_failed: { GST_ERROR_OBJECT (self, "failed to set config"); gst_object_unref (pool); return NULL; } } static gboolean gst_kms_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) { GstRkXImageSink *self; GstCaps *caps; gboolean need_pool; GstVideoInfo vinfo; GstBufferPool *pool; GstStructure *s; gsize size; gint value; self = GST_X_IMAGE_SINK (bsink); gst_query_parse_allocation (query, &caps, &need_pool); if (!caps) goto no_caps; if (!gst_video_info_from_caps (&vinfo, caps)) goto invalid_caps; s = gst_caps_get_structure (caps, 0); if (gst_structure_get_int (s, "arm-afbc", &value) && value) goto afbc_caps; size = GST_VIDEO_INFO_SIZE (&vinfo); pool = NULL; if (need_pool) { pool = gst_kms_sink_create_pool (self, caps, size, 0); if (!pool) goto no_pool; /* Only export for pool used upstream */ if (self->has_prime_export) { GstStructure *config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_KMS_PRIME_EXPORT); gst_buffer_pool_set_config (pool, config); } } /* we need at least 2 buffer because we hold on to the last one */ gst_query_add_allocation_pool (query, pool, size, 2, 0); if (pool) gst_object_unref (pool); gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL); return TRUE; /* ERRORS */ no_caps: { GST_DEBUG_OBJECT (bsink, "no caps specified"); return FALSE; } invalid_caps: { GST_DEBUG_OBJECT (bsink, "invalid caps specified"); return FALSE; } afbc_caps: { GST_DEBUG_OBJECT (bsink, "no allocation for AFBC"); return FALSE; } no_pool: { /* Already warned in create_pool */ return FALSE; } } static gboolean gst_kms_sink_import_dmabuf (GstRkXImageSink * self, GstBuffer * inbuf, GstBuffer ** outbuf) { gint prime_fds[GST_VIDEO_MAX_PLANES] = { 0, }; GstVideoMeta *meta; guint i, n_mem, n_planes; GstKMSMemory *kmsmem; guint mems_idx[GST_VIDEO_MAX_PLANES]; gsize mems_skip[GST_VIDEO_MAX_PLANES]; GstMemory *mems[GST_VIDEO_MAX_PLANES]; if (!self->has_prime_import) return FALSE; /* This will eliminate most non-dmabuf out there */ if (!gst_is_dmabuf_memory (gst_buffer_peek_memory (inbuf, 0))) return FALSE; n_planes = GST_VIDEO_INFO_N_PLANES (&self->vinfo); n_mem = gst_buffer_n_memory (inbuf); meta = gst_buffer_get_video_meta (inbuf); GST_TRACE_OBJECT (self, "Found a dmabuf with %u planes and %u memories", n_planes, n_mem); /* We cannot have multiple dmabuf per plane */ if (n_mem > n_planes) return FALSE; g_assert (n_planes != 0); /* Update video info based on video meta */ if (meta) { GST_VIDEO_INFO_WIDTH (&self->vinfo) = meta->width; GST_VIDEO_INFO_HEIGHT (&self->vinfo) = meta->height; for (i = 0; i < meta->n_planes; i++) { GST_VIDEO_INFO_PLANE_OFFSET (&self->vinfo, i) = meta->offset[i]; GST_VIDEO_INFO_PLANE_STRIDE (&self->vinfo, i) = meta->stride[i]; } } /* Find and validate all memories */ for (i = 0; i < n_planes; i++) { guint length; if (!gst_buffer_find_memory (inbuf, GST_VIDEO_INFO_PLANE_OFFSET (&self->vinfo, i), 1, &mems_idx[i], &length, &mems_skip[i])) return FALSE; mems[i] = gst_buffer_peek_memory (inbuf, mems_idx[i]); /* adjust for memory offset, in case data does not * start from byte 0 in the dmabuf fd */ mems_skip[i] += mems[i]->offset; /* And all memory found must be dmabuf */ if (!gst_is_dmabuf_memory (mems[i])) return FALSE; } kmsmem = (GstKMSMemory *) gst_kms_allocator_get_cached (mems[0]); if (kmsmem) { GST_LOG_OBJECT (self, "found KMS mem %p in DMABuf mem %p with fb id = %d", kmsmem, mems[0], kmsmem->fb_id); goto wrap_mem; } for (i = 0; i < n_planes; i++) prime_fds[i] = gst_dmabuf_memory_get_fd (mems[i]); GST_LOG_OBJECT (self, "found these prime ids: %d, %d, %d, %d", prime_fds[0], prime_fds[1], prime_fds[2], prime_fds[3]); kmsmem = gst_kms_allocator_dmabuf_import (self->allocator, prime_fds, n_planes, mems_skip, &self->vinfo); if (!kmsmem) return FALSE; GST_LOG_OBJECT (self, "setting KMS mem %p to DMABuf mem %p with fb id = %d", kmsmem, mems[0], kmsmem->fb_id); gst_kms_allocator_cache (self->allocator, mems[0], GST_MEMORY_CAST (kmsmem)); wrap_mem: *outbuf = gst_buffer_new (); if (!*outbuf) return FALSE; gst_buffer_append_memory (*outbuf, gst_memory_ref (GST_MEMORY_CAST (kmsmem))); gst_buffer_add_parent_buffer_meta (*outbuf, inbuf); return TRUE; } static GstBuffer * gst_kms_sink_copy_to_dumb_buffer (GstRkXImageSink * self, GstBuffer * inbuf) { GstFlowReturn ret; GstVideoFrame inframe, outframe; gboolean success; GstBuffer *buf = NULL; if (GST_VIDEO_INFO_IS_AFBC (&self->vinfo)) { GST_ERROR_OBJECT (self, "unable to copy AFBC"); return NULL; } if (!gst_buffer_pool_set_active (self->pool, TRUE)) goto activate_pool_failed; ret = gst_buffer_pool_acquire_buffer (self->pool, &buf, NULL); if (ret != GST_FLOW_OK) goto create_buffer_failed; if (!gst_video_frame_map (&inframe, &self->vinfo, inbuf, GST_MAP_READ)) goto error_map_src_buffer; if (!gst_video_frame_map (&outframe, &self->vinfo, buf, GST_MAP_WRITE)) goto error_map_dst_buffer; success = gst_video_frame_copy (&outframe, &inframe); gst_video_frame_unmap (&outframe); gst_video_frame_unmap (&inframe); if (!success) goto error_copy_buffer; return buf; bail: { if (buf) gst_buffer_unref (buf); return NULL; } /* ERRORS */ activate_pool_failed: { GST_ELEMENT_ERROR (self, STREAM, FAILED, ("failed to activate buffer pool"), ("failed to activate buffer pool")); return NULL; } create_buffer_failed: { GST_ELEMENT_ERROR (self, STREAM, FAILED, ("allocation failed"), ("failed to create buffer")); return NULL; } error_copy_buffer: { GST_WARNING_OBJECT (self, "failed to upload buffer"); goto bail; } error_map_dst_buffer: { gst_video_frame_unmap (&inframe); /* fall-through */ } error_map_src_buffer: { GST_WARNING_OBJECT (self, "failed to map buffer"); goto bail; } } static GstBuffer * gst_kms_sink_get_input_buffer (GstRkXImageSink * self, GstBuffer * inbuf) { GstMemory *mem; GstBuffer *buf = NULL; mem = gst_buffer_peek_memory (inbuf, 0); if (!mem) return NULL; if (gst_is_kms_memory (mem)) return gst_buffer_ref (inbuf); if (gst_kms_sink_import_dmabuf (self, inbuf, &buf)) goto done; buf = gst_kms_sink_copy_to_dumb_buffer (self, inbuf); done: /* Copy all the non-memory related metas, this way CropMeta will be * available upon GstVideoOverlay::expose calls. */ if (buf) gst_buffer_copy_into (buf, inbuf, GST_BUFFER_COPY_METADATA, 0, -1); return buf; } static void sync_handler (gint fd, guint frame, guint sec, guint usec, gpointer data) { gboolean *waiting; waiting = data; *waiting = FALSE; } static gboolean gst_kms_sink_sync (GstRkXImageSink * self) { gint ret; gboolean waiting; drmEventContext evctxt = { .version = DRM_EVENT_CONTEXT_VERSION, .vblank_handler = sync_handler, }; drmVBlank vbl = { .request = { .type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT, .sequence = 1, .signal = (gulong) & waiting, }, }; if (self->pipe == 1) vbl.request.type |= DRM_VBLANK_SECONDARY; else if (self->pipe > 1) vbl.request.type |= self->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT; waiting = TRUE; ret = drmWaitVBlank (self->fd, &vbl); if (ret) goto vblank_failed; while (waiting) { do { ret = gst_poll_wait (self->poll, 3 * GST_SECOND); } while (ret == -1 && (errno == EAGAIN || errno == EINTR)); ret = drmHandleEvent (self->fd, &evctxt); if (ret) goto event_failed; } return TRUE; /* ERRORS */ vblank_failed: { GST_WARNING_OBJECT (self, "drmWaitVBlank failed: %s (%d)", g_strerror (errno), errno); return FALSE; } event_failed: { GST_ERROR_OBJECT (self, "drmHandleEvent failed: %s (%d)", g_strerror (errno), errno); return FALSE; } } static void gst_kms_sink_drain (GstRkXImageSink * self) { GstParentBufferMeta *parent_meta; GST_DEBUG_OBJECT (self, "draining"); if (!self->last_buffer) return; /* We only need to return the last_buffer if it depends on upstream buffer. * In this case, the last_buffer will have a GstParentBufferMeta set. */ parent_meta = gst_buffer_get_parent_buffer_meta (self->last_buffer); if (parent_meta) { GstBuffer *dumb_buf; dumb_buf = gst_kms_sink_copy_to_dumb_buffer (self, parent_meta->buffer); if (!dumb_buf) dumb_buf = gst_buffer_ref (self->last_buffer); gst_kms_allocator_clear_cache (self->allocator); gst_x_image_sink_show_frame (GST_VIDEO_SINK (self), dumb_buf); gst_buffer_unref (dumb_buf); } } static gboolean gst_kms_sink_query (GstBaseSink * bsink, GstQuery * query) { GstRkXImageSink *self = GST_X_IMAGE_SINK (bsink); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_ALLOCATION: case GST_QUERY_DRAIN: { gst_kms_sink_drain (self); break; } default: break; } return GST_BASE_SINK_CLASS (parent_class)->query (bsink, query); } /*ximagesink*/ static gboolean xwindow_calculate_display_ratio (GstRkXImageSink * self, int *x, int *y, gint * window_width, gint * window_height) { guint dar_n, dar_d; guint video_par_n, video_par_d; guint dpy_par_n, dpy_par_d; gint video_width, video_height; video_width = GST_VIDEO_INFO_WIDTH (&self->vinfo); video_height = GST_VIDEO_INFO_HEIGHT (&self->vinfo); video_par_n = self->par_n; video_par_d = self->par_d; gst_video_calculate_device_ratio (self->hdisplay, self->vdisplay, self->mm_width, self->mm_height, &dpy_par_n, &dpy_par_d); if (!gst_video_calculate_display_ratio (&dar_n, &dar_d, video_width, video_height, video_par_n, video_par_d, dpy_par_n, dpy_par_d)) return FALSE; GST_DEBUG_OBJECT (self, "video calculated display ratio: %d/%d", dar_n, dar_d); /* now find a width x height that respects this display ratio. * prefer those that have one of w/h the same as the incoming video * using wd / hd = dar_n / dar_d */ /* start with same height, because of interlaced video */ /* check hd / dar_d is an integer scale factor, and scale wd with the PAR */ video_width = gst_util_uint64_scale_int (self->xwindow->height, dar_n, dar_d); video_height = gst_util_uint64_scale_int (self->xwindow->width, dar_d, dar_n); if (video_width < *window_width) { *x += (self->xwindow->width - video_width) / 2; *window_width = video_width; *window_height = self->xwindow->height; } else { *y += (self->xwindow->height - video_height) / 2; *window_height = video_height; *window_width = self->xwindow->width; } GST_DEBUG_OBJECT (self, "scaling to %dx%d", *window_width, *window_height); return TRUE; } /* X11 stuff */ static gboolean xwindow_get_window_position (GstRkXImageSink * ximagesink, int *x, int *y) { XWindowAttributes attr; Window child; int tmp_x, tmp_y; static int last_x = 0, last_y = 0; if (x == NULL || y == NULL) { x = &tmp_x; y = &tmp_y; } XGetWindowAttributes (ximagesink->xcontext->disp, ximagesink->xwindow->win, &attr); XTranslateCoordinates (ximagesink->xcontext->disp, ximagesink->xwindow->win, ximagesink->xcontext->root, 0, 0, x, y, &child); if (last_x != *x || last_y != *y) { last_x = *x; last_y = *y; /* moved */ return TRUE; } return FALSE; } static void xwindow_get_render_rectangle (GstRkXImageSink * ximagesink, gint * x, gint * y, gint * width, gint * height) { if (ximagesink->save_rect.w != 0 && ximagesink->save_rect.h != 0) { *width = ximagesink->save_rect.w; *height = ximagesink->save_rect.h; *x += ximagesink->save_rect.x; *y += ximagesink->save_rect.y; } } static void gst_x_image_sink_xwindow_fill_key (GstRkXImageSink * ximagesink, GstXWindow * xwindow, guint32 color) { XSetForeground (ximagesink->xcontext->disp, xwindow->gc, color); XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc, ximagesink->clip_rect.x, ximagesink->clip_rect.y, ximagesink->clip_rect.x + ximagesink->clip_rect.w, ximagesink->clip_rect.y + ximagesink->clip_rect.h); } /* We are called with the x_lock taken */ static void gst_x_image_sink_xwindow_draw_borders (GstRkXImageSink * ximagesink, GstXWindow * xwindow, GstVideoRectangle rect) { g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink)); g_return_if_fail (xwindow != NULL); XSetForeground (ximagesink->xcontext->disp, xwindow->gc, ximagesink->xcontext->black); /* Left border */ if (rect.x > 0) { XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc, 0, 0, rect.x, xwindow->height); } /* Right border */ if ((rect.x + rect.w) < xwindow->width) { XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc, rect.x + rect.w, 0, xwindow->width, xwindow->height); } /* Top border */ if (rect.y > 0) { XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc, 0, 0, xwindow->width, rect.y); } /* Bottom border */ if ((rect.y + rect.h) < xwindow->height) { XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc, 0, rect.y + rect.h, xwindow->width, xwindow->height); } } /* This function puts a GstXImageBuffer on a GstRkXImageSink's window */ static gboolean gst_x_image_sink_ximage_put (GstRkXImageSink * ximagesink, GstBuffer * buf) { GstVideoCropMeta *crop; GstVideoRectangle src = { 0, }; GstVideoRectangle result = { 0, }; GstBuffer *buffer = NULL; gboolean draw_border = FALSE; gboolean res = FALSE; gboolean expose = buf == NULL; guint32 fb_id; gint ret; /* We take the flow_lock. If expose is in there we don't want to run concurrently from the data flow thread */ g_mutex_lock (&ximagesink->flow_lock); if (G_UNLIKELY (ximagesink->xwindow == NULL)) { g_mutex_unlock (&ximagesink->flow_lock); return FALSE; } /* Draw borders when displaying the first frame. After this draw borders only on expose event or caps change (ximagesink->draw_border = TRUE). */ if (expose || !ximagesink->last_buffer || ximagesink->draw_border) draw_border = TRUE; if (buf) buffer = gst_kms_sink_get_input_buffer (ximagesink, buf); else if (ximagesink->last_buffer) buffer = gst_buffer_ref (ximagesink->last_buffer); /* Make sure buf is not used accidentally */ buf = NULL; if (!buffer) goto buffer_invalid; fb_id = gst_kms_memory_get_fb_id (gst_buffer_peek_memory (buffer, 0)); if (fb_id == 0) goto buffer_invalid; GST_TRACE_OBJECT (ximagesink, "displaying fb %d", fb_id); crop = gst_buffer_get_video_crop_meta (buffer); if (crop) { src.x = crop->x; src.y = crop->y; src.w = crop->width; src.h = crop->height; GST_LOG_OBJECT (ximagesink, "crop %dx%d-%dx%d", crop->x, crop->y, crop->width, crop->height); } else { src.w = GST_VIDEO_SINK_WIDTH (ximagesink); src.h = GST_VIDEO_SINK_HEIGHT (ximagesink); } result.w = ximagesink->xwindow->width; result.h = ximagesink->xwindow->height; g_mutex_lock (&ximagesink->x_lock); /* Fill color key when clip rect changed */ if (draw_border || !RECT_EQUAL (ximagesink->clip_rect, result)) { ximagesink->clip_rect = result; gst_x_image_sink_xwindow_fill_key (ximagesink, ximagesink->xwindow, RKXIMAGE_COLOR_KEY); /* Do an extra repaint when handing expose */ if (expose) memset (&ximagesink->clip_rect, 0, sizeof (GstVideoRectangle)); } if (draw_border) { gst_x_image_sink_xwindow_draw_borders (ximagesink, ximagesink->xwindow, result); ximagesink->draw_border = FALSE; } xwindow_get_window_position (ximagesink, &result.x, &result.y); xwindow_get_render_rectangle (ximagesink, &result.x, &result.y, &result.w, &result.h); xwindow_calculate_display_ratio (ximagesink, &result.x, &result.y, &result.w, &result.h); if (GST_VIDEO_INFO_IS_AFBC (&ximagesink->vinfo)) /* The AFBC's width should align to 4 */ src.w &= ~3; GST_TRACE_OBJECT (ximagesink, "drmModeSetPlane at (%i,%i) %ix%i sourcing at (%i,%i) %ix%i", result.x, result.y, result.w, result.h, src.x, src.y, src.w, src.h); ret = drmModeSetPlane (ximagesink->fd, ximagesink->plane_id, ximagesink->crtc_id, fb_id, 0, result.x, result.y, result.w, result.h, /* source/cropping coordinates are given in Q16 */ src.x << 16, src.y << 16, src.w << 16, src.h << 16); if (ret) { GST_ERROR_OBJECT (ximagesink, "drmModesetplane failed: %d", ret); goto out; } /* HACK: Disable vsync might cause tearing */ if (!g_getenv ("KMSSINK_DISABLE_VSYNC")) /* Wait for the previous frame to complete redraw */ if (!gst_kms_sink_sync (ximagesink)) goto out; if (buffer != ximagesink->last_buffer) gst_buffer_replace (&ximagesink->last_buffer, buffer); res = TRUE; out: if (buffer) gst_buffer_unref (buffer); g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); return res; buffer_invalid: gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow); if (buffer) gst_buffer_unref (buffer); g_mutex_unlock (&ximagesink->flow_lock); return TRUE; } static gboolean gst_x_image_sink_xwindow_decorate (GstRkXImageSink * ximagesink, GstXWindow * window) { Atom hints_atom = None; MotifWmHints *hints; g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), FALSE); g_return_val_if_fail (window != NULL, FALSE); g_mutex_lock (&ximagesink->x_lock); hints_atom = XInternAtom (ximagesink->xcontext->disp, "_MOTIF_WM_HINTS", True); if (hints_atom == None) { g_mutex_unlock (&ximagesink->x_lock); return FALSE; } hints = g_malloc0 (sizeof (MotifWmHints)); hints->flags |= MWM_HINTS_DECORATIONS; hints->decorations = 1 << 0; XChangeProperty (ximagesink->xcontext->disp, window->win, hints_atom, hints_atom, 32, PropModeReplace, (guchar *) hints, sizeof (MotifWmHints) / sizeof (long)); XSync (ximagesink->xcontext->disp, FALSE); g_mutex_unlock (&ximagesink->x_lock); g_free (hints); return TRUE; } static void gst_x_image_sink_xwindow_set_title (GstRkXImageSink * ximagesink, GstXWindow * xwindow, const gchar * media_title) { if (media_title) { g_free (ximagesink->media_title); ximagesink->media_title = g_strdup (media_title); } if (xwindow) { /* we have a window */ if (xwindow->internal) { XTextProperty xproperty; XClassHint *hint = XAllocClassHint (); 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 && ximagesink->media_title) { title = title_mem = g_strconcat (ximagesink->media_title, " : ", app_name, NULL); } else if (app_name) { title = app_name; } else if (ximagesink->media_title) { title = ximagesink->media_title; } if (title) { if ((XStringListToTextProperty (((char **) &title), 1, &xproperty)) != 0) { XSetWMName (ximagesink->xcontext->disp, xwindow->win, &xproperty); XFree (xproperty.value); } g_free (title_mem); } if (hint) { hint->res_name = (char *) app_name; hint->res_class = (char *) "GStreamer"; XSetClassHint (ximagesink->xcontext->disp, xwindow->win, hint); } XFree (hint); } } } /* This function handles a GstXWindow creation */ static GstXWindow * gst_x_image_sink_xwindow_new (GstRkXImageSink * ximagesink, gint width, gint height) { GstXWindow *xwindow = NULL; XGCValues values; g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL); xwindow = g_new0 (GstXWindow, 1); xwindow->width = width; xwindow->height = height; xwindow->internal = TRUE; g_mutex_lock (&ximagesink->x_lock); xwindow->win = XCreateSimpleWindow (ximagesink->xcontext->disp, ximagesink->xcontext->root, 0, 0, width, height, 0, 0, ximagesink->xcontext->black); /* We have to do that to prevent X from redrawing the background on ConfigureNotify. This takes away flickering of video when resizing. */ XSetWindowBackgroundPixmap (ximagesink->xcontext->disp, xwindow->win, None); /* set application name as a title */ gst_x_image_sink_xwindow_set_title (ximagesink, xwindow, NULL); if (ximagesink->handle_events) { Atom wm_delete; XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask | StructureNotifyMask | PointerMotionMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask); /* Tell the window manager we'd like delete client messages instead of * being killed */ wm_delete = XInternAtom (ximagesink->xcontext->disp, "WM_DELETE_WINDOW", False); (void) XSetWMProtocols (ximagesink->xcontext->disp, xwindow->win, &wm_delete, 1); } xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, &values); XMapRaised (ximagesink->xcontext->disp, xwindow->win); XSync (ximagesink->xcontext->disp, FALSE); g_mutex_unlock (&ximagesink->x_lock); gst_x_image_sink_xwindow_decorate (ximagesink, xwindow); gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (ximagesink), xwindow->win); return xwindow; } /* This function destroys a GstXWindow */ static void gst_x_image_sink_xwindow_destroy (GstRkXImageSink * ximagesink, GstXWindow * xwindow) { g_return_if_fail (xwindow != NULL); g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink)); g_mutex_lock (&ximagesink->x_lock); /* clean screen */ if (ximagesink->last_fb_id) { drmModeRmFB (ximagesink->fd, ximagesink->last_fb_id); ximagesink->last_fb_id = 0; } /* If we did not create that window we just free the GC and let it live */ if (xwindow->internal) XDestroyWindow (ximagesink->xcontext->disp, xwindow->win); else XSelectInput (ximagesink->xcontext->disp, xwindow->win, 0); XFreeGC (ximagesink->xcontext->disp, xwindow->gc); XSync (ximagesink->xcontext->disp, FALSE); g_mutex_unlock (&ximagesink->x_lock); g_free (xwindow); } static void gst_x_image_sink_xwindow_update_geometry (GstRkXImageSink * ximagesink) { XWindowAttributes attr; gboolean reconfigure; g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink)); /* Update the window geometry */ g_mutex_lock (&ximagesink->x_lock); if (G_UNLIKELY (ximagesink->xwindow == NULL)) { g_mutex_unlock (&ximagesink->x_lock); return; } XGetWindowAttributes (ximagesink->xcontext->disp, ximagesink->xwindow->win, &attr); /* Check if we would suggest a different width/height now */ reconfigure = (ximagesink->xwindow->width != attr.width) || (ximagesink->xwindow->height != attr.height); ximagesink->xwindow->width = attr.width; ximagesink->xwindow->height = attr.height; g_mutex_unlock (&ximagesink->x_lock); if (reconfigure) gst_pad_push_event (GST_BASE_SINK (ximagesink)->sinkpad, gst_event_new_reconfigure ()); } static void gst_x_image_sink_xwindow_clear (GstRkXImageSink * ximagesink, GstXWindow * xwindow) { g_return_if_fail (xwindow != NULL); g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink)); g_mutex_lock (&ximagesink->x_lock); XSetForeground (ximagesink->xcontext->disp, xwindow->gc, ximagesink->xcontext->black); XFillRectangle (ximagesink->xcontext->disp, xwindow->win, xwindow->gc, 0, 0, xwindow->width, xwindow->height); /* clean screen */ if (ximagesink->last_fb_id) { drmModeRmFB (ximagesink->fd, ximagesink->last_fb_id); ximagesink->last_fb_id = 0; } g_mutex_unlock (&ximagesink->x_lock); } /* This function handles XEvents that might be in the queue. It generates GstEvent that will be sent upstream in the pipeline to handle interactivity and navigation.*/ static void gst_x_image_sink_handle_xevents (GstRkXImageSink * ximagesink) { XEvent e; guint pointer_x = 0, pointer_y = 0; gboolean pointer_moved = FALSE; gboolean exposed = FALSE, configured = FALSE; g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink)); /* Then we get all pointer motion events, only the last position is interesting. */ g_mutex_lock (&ximagesink->flow_lock); g_mutex_lock (&ximagesink->x_lock); while (XCheckWindowEvent (ximagesink->xcontext->disp, ximagesink->xwindow->win, PointerMotionMask, &e)) { g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); switch (e.type) { case MotionNotify: pointer_x = e.xmotion.x; pointer_y = e.xmotion.y; pointer_moved = TRUE; break; default: break; } g_mutex_lock (&ximagesink->flow_lock); g_mutex_lock (&ximagesink->x_lock); } if (pointer_moved) { g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); GST_DEBUG ("ximagesink pointer moved over window at %d,%d", pointer_x, pointer_y); gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink), "mouse-move", 0, pointer_x, pointer_y); g_mutex_lock (&ximagesink->flow_lock); g_mutex_lock (&ximagesink->x_lock); } /* We get all remaining events on our window to throw them upstream */ while (XCheckWindowEvent (ximagesink->xcontext->disp, ximagesink->xwindow->win, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask, &e)) { KeySym keysym; const char *key_str = NULL; /* We lock only for the X function call */ g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); switch (e.type) { case ButtonPress: /* Mouse button pressed/released over our window. We send upstream events for interactivity/navigation */ GST_DEBUG ("ximagesink button %d pressed over window at %d,%d", e.xbutton.button, e.xbutton.x, e.xbutton.x); gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink), "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y); break; case ButtonRelease: GST_DEBUG ("ximagesink button %d release over window at %d,%d", e.xbutton.button, e.xbutton.x, e.xbutton.x); gst_navigation_send_mouse_event (GST_NAVIGATION (ximagesink), "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y); break; case KeyPress: case KeyRelease: /* Key pressed/released over our window. We send upstream events for interactivity/navigation */ g_mutex_lock (&ximagesink->x_lock); keysym = XkbKeycodeToKeysym (ximagesink->xcontext->disp, e.xkey.keycode, 0, 0); if (keysym != NoSymbol) { key_str = XKeysymToString (keysym); } else { key_str = "unknown"; } g_mutex_unlock (&ximagesink->x_lock); GST_DEBUG_OBJECT (ximagesink, "key %d pressed over window at %d,%d (%s)", e.xkey.keycode, e.xkey.x, e.xkey.y, key_str); gst_navigation_send_key_event (GST_NAVIGATION (ximagesink), e.type == KeyPress ? "key-press" : "key-release", key_str); break; default: GST_DEBUG_OBJECT (ximagesink, "ximagesink unhandled X event (%d)", e.type); } g_mutex_lock (&ximagesink->flow_lock); g_mutex_lock (&ximagesink->x_lock); } /* Handle Expose */ while (XCheckWindowEvent (ximagesink->xcontext->disp, ximagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) { switch (e.type) { case Expose: exposed = TRUE; break; case ConfigureNotify: g_mutex_unlock (&ximagesink->x_lock); gst_x_image_sink_xwindow_update_geometry (ximagesink); g_mutex_lock (&ximagesink->x_lock); configured = TRUE; break; default: break; } } if (ximagesink->handle_expose && (exposed || configured)) { g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink)); g_mutex_lock (&ximagesink->flow_lock); g_mutex_lock (&ximagesink->x_lock); } /* Handle Display events */ while (XPending (ximagesink->xcontext->disp)) { XNextEvent (ximagesink->xcontext->disp, &e); switch (e.type) { case ClientMessage:{ Atom wm_delete; wm_delete = XInternAtom (ximagesink->xcontext->disp, "WM_DELETE_WINDOW", False); if (wm_delete == (Atom) e.xclient.data.l[0]) { /* Handle window deletion by posting an error on the bus */ GST_ELEMENT_ERROR (ximagesink, RESOURCE, NOT_FOUND, ("Output window was closed"), (NULL)); g_mutex_unlock (&ximagesink->x_lock); gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow); ximagesink->xwindow = NULL; g_mutex_lock (&ximagesink->x_lock); } break; } default: break; } } /* Handle DRM display */ if (ximagesink->paused && xwindow_get_window_position (ximagesink, NULL, NULL)) { /* if window stop moving, redraw display */ g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink)); g_mutex_lock (&ximagesink->flow_lock); g_mutex_lock (&ximagesink->x_lock); } g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); } static gpointer gst_x_image_sink_event_thread (GstRkXImageSink * ximagesink) { g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL); GST_OBJECT_LOCK (ximagesink); while (ximagesink->running) { GST_OBJECT_UNLOCK (ximagesink); if (ximagesink->xwindow) { gst_x_image_sink_handle_xevents (ximagesink); } /* FIXME: do we want to align this with the framerate or anything else? */ g_usleep (G_USEC_PER_SEC / 100); GST_OBJECT_LOCK (ximagesink); } GST_OBJECT_UNLOCK (ximagesink); return NULL; } static void gst_x_image_sink_manage_event_thread (GstRkXImageSink * ximagesink) { GThread *thread = NULL; /* don't start the thread too early */ if (ximagesink->xcontext == NULL) { return; } GST_OBJECT_LOCK (ximagesink); if (ximagesink->handle_expose || ximagesink->handle_events) { if (!ximagesink->event_thread) { /* Setup our event listening thread */ GST_DEBUG_OBJECT (ximagesink, "run xevent thread, expose %d, events %d", ximagesink->handle_expose, ximagesink->handle_events); ximagesink->running = TRUE; ximagesink->event_thread = g_thread_try_new ("ximagesink-events", (GThreadFunc) gst_x_image_sink_event_thread, ximagesink, NULL); } } else { if (ximagesink->event_thread) { GST_DEBUG_OBJECT (ximagesink, "stop xevent thread, expose %d, events %d", ximagesink->handle_expose, ximagesink->handle_events); ximagesink->running = FALSE; /* grab thread and mark it as NULL */ thread = ximagesink->event_thread; ximagesink->event_thread = NULL; } } GST_OBJECT_UNLOCK (ximagesink); /* Wait for our event thread to finish */ if (thread) g_thread_join (thread); } /* This function gets the X Display and global info about it. Everything is stored in our object and will be cleaned when the object is disposed. Note here that caps for supported format are generated without any window or image creation */ static GstXContext * gst_x_image_sink_xcontext_get (GstRkXImageSink * ximagesink) { GstXContext *xcontext = NULL; XPixmapFormatValues *px_formats = NULL; gint nb_formats = 0, i; gint endianness; GstVideoFormat vformat; guint32 alpha_mask; g_return_val_if_fail (GST_IS_X_IMAGE_SINK (ximagesink), NULL); xcontext = g_new0 (GstXContext, 1); g_mutex_lock (&ximagesink->x_lock); xcontext->disp = XOpenDisplay (ximagesink->display_name); if (!xcontext->disp) { g_mutex_unlock (&ximagesink->x_lock); g_free (xcontext); GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE, ("Could not initialise X output"), ("Could not open display")); return NULL; } xcontext->screen = DefaultScreenOfDisplay (xcontext->disp); xcontext->screen_num = DefaultScreen (xcontext->disp); xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num); xcontext->root = DefaultRootWindow (xcontext->disp); xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num); xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num); xcontext->depth = DefaultDepthOfScreen (xcontext->screen); xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num); xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num); xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num); xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num); GST_DEBUG_OBJECT (ximagesink, "X reports %dx%d pixels and %d mm x %d mm", xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm); /* We get supported pixmap formats at supported depth */ px_formats = XListPixmapFormats (xcontext->disp, &nb_formats); if (!px_formats) { XCloseDisplay (xcontext->disp); g_mutex_unlock (&ximagesink->x_lock); g_free (xcontext); GST_ELEMENT_ERROR (ximagesink, RESOURCE, SETTINGS, ("Could not get supported pixmap formats"), (NULL)); return NULL; } /* We get bpp value corresponding to our running depth */ for (i = 0; i < nb_formats; i++) { if (px_formats[i].depth == xcontext->depth) xcontext->bpp = px_formats[i].bits_per_pixel; } XFree (px_formats); endianness = (ImageByteOrder (xcontext->disp) == LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN; /* extrapolate alpha mask */ if (xcontext->depth == 32) { alpha_mask = ~(xcontext->visual->red_mask | xcontext->visual->green_mask | xcontext->visual->blue_mask); } else { alpha_mask = 0; } vformat = gst_video_format_from_masks (xcontext->depth, xcontext->bpp, endianness, xcontext->visual->red_mask, xcontext->visual->green_mask, xcontext->visual->blue_mask, alpha_mask); if (vformat == GST_VIDEO_FORMAT_UNKNOWN) goto unknown_format; g_mutex_unlock (&ximagesink->x_lock); return xcontext; /* ERRORS */ unknown_format: { GST_ERROR_OBJECT (ximagesink, "unknown format"); return NULL; } } /* This function cleans the X context. Closing the Display and unrefing the caps for supported formats. */ static void gst_x_image_sink_xcontext_clear (GstRkXImageSink * ximagesink) { GstXContext *xcontext; g_return_if_fail (GST_IS_X_IMAGE_SINK (ximagesink)); GST_OBJECT_LOCK (ximagesink); if (ximagesink->xcontext == NULL) { GST_OBJECT_UNLOCK (ximagesink); return; } /* Take the xcontext reference and NULL it while we * clean it up, so that any buffer-alloced buffers * arriving after this will be freed correctly */ xcontext = ximagesink->xcontext; ximagesink->xcontext = NULL; GST_OBJECT_UNLOCK (ximagesink); g_mutex_lock (&ximagesink->x_lock); XCloseDisplay (xcontext->disp); g_mutex_unlock (&ximagesink->x_lock); g_free (xcontext); } /* Element stuff */ static GstCaps * gst_x_image_sink_get_allowed_caps (GstRkXImageSink * self) { if (!self->allowed_caps) return NULL; /* base class will return the template caps */ return gst_caps_ref (self->allowed_caps); } static GstCaps * gst_x_image_sink_getcaps (GstBaseSink * bsink, GstCaps * filter) { GstRkXImageSink *ximagesink; GstCaps *caps, *out_caps; ximagesink = GST_X_IMAGE_SINK (bsink); caps = gst_x_image_sink_get_allowed_caps (ximagesink); if (caps && filter) { out_caps = gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (caps); } else { out_caps = caps; } return out_caps; } static gboolean gst_x_image_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) { GstRkXImageSink *ximagesink; GstVideoInfo info; GstBufferPool *newpool, *oldpool; GstStructure *s; gint value; ximagesink = GST_X_IMAGE_SINK (bsink); if (!ximagesink->xcontext) return FALSE; /* We are going to change the internal buffer pool, which means it will no * longer be compatbile with the last_buffer size. Drain now, as we won't be * able to do that later on. */ gst_kms_sink_drain (ximagesink); GST_DEBUG_OBJECT (ximagesink, "given caps %" GST_PTR_FORMAT, caps); if (!gst_video_info_from_caps (&info, caps)) goto invalid_format; /* parse AFBC from caps */ s = gst_caps_get_structure (caps, 0); if (gst_structure_get_int (s, "arm-afbc", &value)) { if (value) GST_VIDEO_INFO_SET_AFBC (&info); else GST_VIDEO_INFO_UNSET_AFBC (&info); } GST_VIDEO_SINK_WIDTH (ximagesink) = info.width; GST_VIDEO_SINK_HEIGHT (ximagesink) = info.height; ximagesink->fps_n = info.fps_n; ximagesink->fps_d = info.fps_d; ximagesink->par_n = info.par_n; ximagesink->par_d = info.par_d; /* Notify application to set xwindow id now */ g_mutex_lock (&ximagesink->flow_lock); if (!ximagesink->xwindow) { g_mutex_unlock (&ximagesink->flow_lock); gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (ximagesink)); } else { g_mutex_unlock (&ximagesink->flow_lock); } /* Creating our window and our image */ if (GST_VIDEO_SINK_WIDTH (ximagesink) <= 0 || GST_VIDEO_SINK_HEIGHT (ximagesink) <= 0) goto invalid_size; /* create a new pool for the new configuration */ newpool = gst_kms_sink_create_pool (ximagesink, caps, GST_VIDEO_INFO_SIZE (&info), 2); if (!newpool) goto no_pool; /* we don't activate the internal pool yet as it may not be needed */ oldpool = ximagesink->pool; ximagesink->pool = newpool; if (oldpool) { gst_buffer_pool_set_active (oldpool, FALSE); gst_object_unref (oldpool); } g_mutex_lock (&ximagesink->flow_lock); if (!ximagesink->xwindow) { ximagesink->xwindow = gst_x_image_sink_xwindow_new (ximagesink, GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink)); } ximagesink->vinfo = info; /* Remember to draw borders for next frame */ ximagesink->draw_border = TRUE; g_mutex_unlock (&ximagesink->flow_lock); return TRUE; /* ERRORS */ invalid_format: { GST_ERROR_OBJECT (ximagesink, "caps invalid"); return FALSE; } invalid_size: { GST_ELEMENT_ERROR (ximagesink, CORE, NEGOTIATION, (NULL), ("Invalid image size.")); return FALSE; } no_pool: { /* Already warned in create_pool */ return FALSE; } } static GstStateChangeReturn gst_x_image_sink_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstRkXImageSink *ximagesink; GstXContext *xcontext = NULL; ximagesink = GST_X_IMAGE_SINK (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: /* Initializing the XContext */ if (ximagesink->xcontext == NULL) { xcontext = gst_x_image_sink_xcontext_get (ximagesink); if (xcontext == NULL) { ret = GST_STATE_CHANGE_FAILURE; goto beach; } GST_OBJECT_LOCK (ximagesink); if (xcontext) ximagesink->xcontext = xcontext; GST_OBJECT_UNLOCK (ximagesink); } /* call XSynchronize with the current value of synchronous */ GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s", ximagesink->synchronous ? "TRUE" : "FALSE"); g_mutex_lock (&ximagesink->x_lock); XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous); g_mutex_unlock (&ximagesink->x_lock); gst_x_image_sink_manage_event_thread (ximagesink); break; case GST_STATE_CHANGE_READY_TO_PAUSED: ximagesink->paused = TRUE; g_mutex_lock (&ximagesink->flow_lock); if (ximagesink->xwindow) gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow); g_mutex_unlock (&ximagesink->flow_lock); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: ximagesink->paused = FALSE; break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: ximagesink->paused = TRUE; break; case GST_STATE_CHANGE_PAUSED_TO_READY: ximagesink->paused = FALSE; ximagesink->fps_n = 0; ximagesink->fps_d = 1; GST_VIDEO_SINK_WIDTH (ximagesink) = 0; GST_VIDEO_SINK_HEIGHT (ximagesink) = 0; break; case GST_STATE_CHANGE_READY_TO_NULL: gst_x_image_sink_reset (ximagesink); break; default: break; } beach: return ret; } static void gst_x_image_sink_get_times (GstBaseSink * bsink, GstBuffer * buf, GstClockTime * start, GstClockTime * end) { GstRkXImageSink *ximagesink; ximagesink = GST_X_IMAGE_SINK (bsink); if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { *start = GST_BUFFER_TIMESTAMP (buf); if (GST_BUFFER_DURATION_IS_VALID (buf)) { *end = *start + GST_BUFFER_DURATION (buf); } else { if (ximagesink->fps_n > 0) { *end = *start + gst_util_uint64_scale_int (GST_SECOND, ximagesink->fps_d, ximagesink->fps_n); } } } } static GstFlowReturn gst_x_image_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf) { GstFlowReturn res = GST_FLOW_OK; GstRkXImageSink *ximagesink; ximagesink = GST_X_IMAGE_SINK (vsink); if (!gst_x_image_sink_ximage_put (ximagesink, buf)) { /* No Window available to put our image into */ GST_WARNING_OBJECT (ximagesink, "could not output image - no window"); res = GST_FLOW_ERROR; } return res; } static gboolean gst_x_image_sink_event (GstBaseSink * sink, GstEvent * event) { GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (sink); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_TAG:{ GstTagList *l; gchar *title = NULL; gst_event_parse_tag (event, &l); gst_tag_list_get_string (l, GST_TAG_TITLE, &title); if (title) { GST_DEBUG_OBJECT (ximagesink, "got tags, title='%s'", title); gst_x_image_sink_xwindow_set_title (ximagesink, ximagesink->xwindow, title); g_free (title); } break; } default: break; } return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); } /* Interfaces stuff */ static void gst_x_image_sink_navigation_send_event (GstNavigation * navigation, GstStructure * structure) { GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (navigation); GstEvent *event = NULL; gint x_offset, y_offset; gdouble x, y; gboolean handled = FALSE; /* We are not converting the pointer coordinates as there's no hardware scaling done here. The only possible scaling is done by videoscale and videoscale will have to catch those events and tranform the coordinates to match the applied scaling. So here we just add the offset if the image is centered in the window. */ /* We take the flow_lock while we look at the window */ g_mutex_lock (&ximagesink->flow_lock); if (!ximagesink->xwindow) { g_mutex_unlock (&ximagesink->flow_lock); gst_structure_free (structure); return; } x_offset = ximagesink->xwindow->width - GST_VIDEO_SINK_WIDTH (ximagesink); y_offset = ximagesink->xwindow->height - GST_VIDEO_SINK_HEIGHT (ximagesink); g_mutex_unlock (&ximagesink->flow_lock); if (x_offset > 0 && gst_structure_get_double (structure, "pointer_x", &x)) { x -= x_offset / 2; gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, x, NULL); } if (y_offset > 0 && gst_structure_get_double (structure, "pointer_y", &y)) { y -= y_offset / 2; gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, y, NULL); } event = gst_event_new_navigation (structure); if (event) { gst_event_ref (event); handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (ximagesink), event); if (!handled) gst_element_post_message (GST_ELEMENT_CAST (ximagesink), gst_navigation_message_new_event (GST_OBJECT_CAST (ximagesink), event)); gst_event_unref (event); } } static void gst_x_image_sink_navigation_init (GstNavigationInterface * iface) { iface->send_event = gst_x_image_sink_navigation_send_event; } static void gst_x_image_sink_set_window_handle (GstVideoOverlay * overlay, guintptr id) { XID xwindow_id = id; GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay); GstXWindow *xwindow = NULL; /* We acquire the stream lock while setting this window in the element. We are basically cleaning tons of stuff replacing the old window, putting images while we do that would surely crash */ g_mutex_lock (&ximagesink->flow_lock); /* If we already use that window return */ if (ximagesink->xwindow && (xwindow_id == ximagesink->xwindow->win)) { g_mutex_unlock (&ximagesink->flow_lock); return; } /* If the element has not initialized the X11 context try to do so */ if (!ximagesink->xcontext && !(ximagesink->xcontext = gst_x_image_sink_xcontext_get (ximagesink))) { g_mutex_unlock (&ximagesink->flow_lock); /* we have thrown a GST_ELEMENT_ERROR now */ return; } /* If a window is there already we destroy it */ if (ximagesink->xwindow) { gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow); ximagesink->xwindow = NULL; } /* If the xid is 0 we go back to an internal window */ if (xwindow_id == 0) { /* If no width/height caps nego did not happen window will be created during caps nego then */ if (GST_VIDEO_SINK_WIDTH (ximagesink) && GST_VIDEO_SINK_HEIGHT (ximagesink)) { xwindow = gst_x_image_sink_xwindow_new (ximagesink, GST_VIDEO_SINK_WIDTH (ximagesink), GST_VIDEO_SINK_HEIGHT (ximagesink)); } } else { xwindow = g_new0 (GstXWindow, 1); xwindow->win = xwindow_id; /* We set the events we want to receive and create a GC. */ g_mutex_lock (&ximagesink->x_lock); xwindow->internal = FALSE; if (ximagesink->handle_events) { XSelectInput (ximagesink->xcontext->disp, xwindow->win, ExposureMask | StructureNotifyMask | PointerMotionMask | KeyPressMask | KeyReleaseMask); } xwindow->gc = XCreateGC (ximagesink->xcontext->disp, xwindow->win, 0, NULL); g_mutex_unlock (&ximagesink->x_lock); } if (xwindow) { ximagesink->xwindow = xwindow; /* Update the window geometry, possibly generating a reconfigure event. */ gst_x_image_sink_xwindow_update_geometry (ximagesink); } g_mutex_unlock (&ximagesink->flow_lock); } static void gst_x_image_sink_expose (GstVideoOverlay * overlay) { GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay); gst_x_image_sink_xwindow_update_geometry (ximagesink); gst_x_image_sink_ximage_put (ximagesink, NULL); } static void gst_x_image_sink_set_event_handling (GstVideoOverlay * overlay, gboolean handle_events) { GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay); ximagesink->handle_events = handle_events; g_mutex_lock (&ximagesink->flow_lock); if (G_UNLIKELY (!ximagesink->xwindow)) { g_mutex_unlock (&ximagesink->flow_lock); return; } g_mutex_lock (&ximagesink->x_lock); if (handle_events) { if (ximagesink->xwindow->internal) { XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, ExposureMask | StructureNotifyMask | PointerMotionMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask); } else { XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, ExposureMask | StructureNotifyMask | PointerMotionMask | KeyPressMask | KeyReleaseMask); } } else { XSelectInput (ximagesink->xcontext->disp, ximagesink->xwindow->win, 0); } g_mutex_unlock (&ximagesink->x_lock); g_mutex_unlock (&ximagesink->flow_lock); } static void gst_x_image_sink_set_render_rectangle (GstVideoOverlay * overlay, gint x, gint y, gint width, gint height) { GstRkXImageSink *ximagesink = GST_X_IMAGE_SINK (overlay); GST_DEBUG_OBJECT (ximagesink, "Set Render Rectangle" "x %d y %d width %d height %d", x, y, width, height); ximagesink->save_rect.w = width; ximagesink->save_rect.h = height; ximagesink->save_rect.x = x; ximagesink->save_rect.y = y; gst_x_image_sink_expose (GST_VIDEO_OVERLAY (ximagesink)); } static void gst_x_image_sink_video_overlay_init (GstVideoOverlayInterface * iface) { iface->set_window_handle = gst_x_image_sink_set_window_handle; iface->expose = gst_x_image_sink_expose; iface->set_render_rectangle = gst_x_image_sink_set_render_rectangle; iface->handle_events = gst_x_image_sink_set_event_handling; } /* =========================================== */ /* */ /* Init & Class init */ /* */ /* =========================================== */ static void gst_x_image_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstRkXImageSink *ximagesink; g_return_if_fail (GST_IS_X_IMAGE_SINK (object)); ximagesink = GST_X_IMAGE_SINK (object); switch (prop_id) { case PROP_DISPLAY: ximagesink->display_name = g_strdup (g_value_get_string (value)); break; case PROP_SYNCHRONOUS: ximagesink->synchronous = g_value_get_boolean (value); if (ximagesink->xcontext) { GST_DEBUG_OBJECT (ximagesink, "XSynchronize called with %s", ximagesink->synchronous ? "TRUE" : "FALSE"); g_mutex_lock (&ximagesink->x_lock); XSynchronize (ximagesink->xcontext->disp, ximagesink->synchronous); g_mutex_unlock (&ximagesink->x_lock); } break; case PROP_HANDLE_EVENTS: gst_x_image_sink_set_event_handling (GST_VIDEO_OVERLAY (ximagesink), g_value_get_boolean (value)); gst_x_image_sink_manage_event_thread (ximagesink); break; case PROP_HANDLE_EXPOSE: ximagesink->handle_expose = g_value_get_boolean (value); gst_x_image_sink_manage_event_thread (ximagesink); break; case PROP_DRIVER_NAME: ximagesink->devname = g_value_dup_string (value); break; case PROP_CONNECTOR_ID: ximagesink->conn_id = g_value_get_int (value); break; case PROP_PLANE_ID: ximagesink->plane_id = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_x_image_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstRkXImageSink *ximagesink; g_return_if_fail (GST_IS_X_IMAGE_SINK (object)); ximagesink = GST_X_IMAGE_SINK (object); switch (prop_id) { case PROP_DISPLAY: g_value_set_string (value, ximagesink->display_name); break; case PROP_SYNCHRONOUS: g_value_set_boolean (value, ximagesink->synchronous); break; case PROP_HANDLE_EVENTS: g_value_set_boolean (value, ximagesink->handle_events); break; case PROP_HANDLE_EXPOSE: g_value_set_boolean (value, ximagesink->handle_expose); break; case PROP_WINDOW_WIDTH: if (ximagesink->xwindow) g_value_set_uint64 (value, ximagesink->xwindow->width); else g_value_set_uint64 (value, 0); break; case PROP_WINDOW_HEIGHT: if (ximagesink->xwindow) g_value_set_uint64 (value, ximagesink->xwindow->height); else g_value_set_uint64 (value, 0); break; case PROP_DRIVER_NAME: g_value_take_string (value, ximagesink->devname); break; case PROP_CONNECTOR_ID: g_value_set_int (value, ximagesink->conn_id); break; case PROP_PLANE_ID: g_value_set_int (value, ximagesink->plane_id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_x_image_sink_reset (GstRkXImageSink * ximagesink) { GThread *thread; GST_OBJECT_LOCK (ximagesink); ximagesink->running = FALSE; /* grab thread and mark it as NULL */ thread = ximagesink->event_thread; ximagesink->event_thread = NULL; GST_OBJECT_UNLOCK (ximagesink); /* Wait for our event thread to finish before we clean up our stuff. */ if (thread) g_thread_join (thread); gst_buffer_replace (&ximagesink->last_buffer, NULL); g_mutex_lock (&ximagesink->flow_lock); if (ximagesink->xwindow) { gst_x_image_sink_xwindow_clear (ximagesink, ximagesink->xwindow); gst_x_image_sink_xwindow_destroy (ximagesink, ximagesink->xwindow); ximagesink->xwindow = NULL; } g_mutex_unlock (&ximagesink->flow_lock); gst_x_image_sink_xcontext_clear (ximagesink); } static void gst_x_image_sink_finalize (GObject * object) { GstRkXImageSink *ximagesink; ximagesink = GST_X_IMAGE_SINK (object); gst_x_image_sink_reset (ximagesink); if (ximagesink->display_name) { g_free (ximagesink->display_name); ximagesink->display_name = NULL; } g_mutex_clear (&ximagesink->x_lock); g_mutex_clear (&ximagesink->flow_lock); g_free (ximagesink->media_title); gst_poll_free (ximagesink->poll); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_x_image_sink_init (GstRkXImageSink * ximagesink) { ximagesink->display_name = NULL; ximagesink->xcontext = NULL; ximagesink->xwindow = NULL; ximagesink->last_buffer = NULL; ximagesink->event_thread = NULL; ximagesink->running = FALSE; ximagesink->fps_n = 0; ximagesink->fps_d = 1; g_mutex_init (&ximagesink->x_lock); g_mutex_init (&ximagesink->flow_lock); ximagesink->synchronous = FALSE; ximagesink->handle_events = TRUE; ximagesink->handle_expose = TRUE; ximagesink->fd = -1; ximagesink->conn_id = -1; ximagesink->plane_id = -1; gst_poll_fd_init (&ximagesink->pollfd); ximagesink->poll = gst_poll_new (TRUE); gst_video_info_init (&ximagesink->vinfo); ximagesink->save_rect.x = 0; ximagesink->save_rect.y = 0; ximagesink->save_rect.w = 0; ximagesink->save_rect.h = 0; ximagesink->paused = FALSE; } static gboolean gst_x_image_sink_start (GstBaseSink * bsink) { GstRkXImageSink *self; drmModeRes *res; drmModeConnector *conn; drmModeCrtc *crtc; drmModePlaneRes *pres; drmModePlane *plane; gboolean ret; self = GST_X_IMAGE_SINK (bsink); ret = FALSE; res = NULL; conn = NULL; crtc = NULL; pres = NULL; plane = NULL; self->fd = open ("/dev/dri/card0", O_RDWR); if (self->fd < 0) goto open_failed; drm_log_version (self); if (!drm_get_caps (self)) goto bail; res = drmModeGetResources (self->fd); if (!res) goto resources_failed; if (self->conn_id == -1) conn = drm_find_main_monitor (self->fd, res); else conn = drmModeGetConnector (self->fd, self->conn_id); if (!conn) goto connector_failed; crtc = drm_find_crtc_for_connector (self->fd, res, conn, &self->pipe); if (!crtc) goto crtc_failed; if (drmSetClientCap (self->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) goto set_cap_failed; pres = drmModeGetPlaneResources (self->fd); if (!pres) goto plane_resources_failed; if (self->plane_id == -1) plane = drm_find_plane_for_crtc_by_type (self->fd, res, pres, crtc->crtc_id, DRM_PLANE_TYPE_OVERLAY); else { plane = drmModeGetPlane (self->fd, self->plane_id); if (drm_plane_get_type (self->fd, plane) == DRM_PLANE_TYPE_PRIMARY) { GST_ERROR_OBJECT (self, "Not support using primary plane"); goto bail; } } if (!plane) goto plane_failed; /* let's get the available color formats in plane */ if (!drm_ensure_allowed_caps (self, plane, res)) goto bail; self->conn_id = conn->connector_id; self->crtc_id = crtc->crtc_id; self->plane_id = plane->plane_id; GST_INFO_OBJECT (self, "connector id = %d / crtc id = %d / plane id = %d", self->conn_id, self->crtc_id, self->plane_id); if (g_getenv ("GST_RKXIMAGE_USE_COLORKEY")) drm_prepare_planes (self, res, pres); self->hdisplay = crtc->mode.hdisplay; self->vdisplay = crtc->mode.vdisplay; self->buffer_id = crtc->buffer_id; self->mm_width = conn->mmWidth; self->mm_height = conn->mmHeight; GST_INFO_OBJECT (self, "display size: pixels = %dx%d / millimeters = %dx%d", self->hdisplay, self->vdisplay, self->mm_width, self->mm_height); self->pollfd.fd = self->fd; gst_poll_add_fd (self->poll, &self->pollfd); gst_poll_fd_ctl_read (self->poll, &self->pollfd, TRUE); ret = TRUE; bail: if (plane) drmModeFreePlane (plane); if (pres) drmModeFreePlaneResources (pres); if (crtc) drmModeFreeCrtc (crtc); if (conn) drmModeFreeConnector (conn); if (res) drmModeFreeResources (res); if (!ret && self->fd >= 0) { drmClose (self->fd); self->fd = -1; } return ret; /* ERRORS */ open_failed: { GST_ERROR_OBJECT (self, "Could not open DRM module %s: %s", GST_STR_NULL (self->devname), strerror (errno)); return FALSE; } resources_failed: { GST_ERROR_OBJECT (self, "drmModeGetResources failed: %s (%d)", strerror (errno), errno); goto bail; } connector_failed: { GST_ERROR_OBJECT (self, "Could not find a valid monitor connector"); goto bail; } crtc_failed: { GST_ERROR_OBJECT (self, "Could not find a crtc for connector"); goto bail; } set_cap_failed: { GST_ERROR_OBJECT (self, "Could not set universal planes capability bit"); goto bail; } plane_resources_failed: { GST_ERROR_OBJECT (self, "drmModeGetPlaneResources failed: %s (%d)", strerror (errno), errno); goto bail; } plane_failed: { GST_ERROR_OBJECT (self, "Could not find a plane for crtc"); goto bail; } } static gboolean gst_x_image_sink_stop (GstBaseSink * bsink) { GstRkXImageSink *self; self = GST_X_IMAGE_SINK (bsink); if (!EMPTY_RECT (self->clip_rect)) gst_x_image_sink_xwindow_fill_key (self, self->xwindow, 0); gst_buffer_replace (&self->last_buffer, NULL); gst_caps_replace (&self->allowed_caps, NULL); gst_object_replace ((GstObject **) & self->pool, NULL); gst_object_replace ((GstObject **) & self->allocator, NULL); gst_poll_remove_fd (self->poll, &self->pollfd); gst_poll_restart (self->poll); gst_poll_fd_init (&self->pollfd); if (self->fd >= 0) { close (self->fd); self->fd = -1; } return TRUE; } static void gst_x_image_sink_class_init (GstRkXImageSinkClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; GstBaseSinkClass *gstbasesink_class; GstVideoSinkClass *videosink_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbasesink_class = (GstBaseSinkClass *) klass; videosink_class = (GstVideoSinkClass *) klass; gobject_class->finalize = gst_x_image_sink_finalize; gobject_class->set_property = gst_x_image_sink_set_property; gobject_class->get_property = gst_x_image_sink_get_property; g_object_class_install_property (gobject_class, PROP_DISPLAY, g_param_spec_string ("display", "Display", "X Display name", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_SYNCHRONOUS, g_param_spec_boolean ("synchronous", "Synchronous", "When enabled, runs the X display in synchronous mode. " "(unrelated to A/V sync, used only for debugging)", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_HANDLE_EVENTS, g_param_spec_boolean ("handle-events", "Handle XEvents", "When enabled, XEvents will be selected and handled", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_HANDLE_EXPOSE, g_param_spec_boolean ("handle-expose", "Handle expose", "When enabled, " "the current frame will always be drawn in response to X Expose " "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstRkXImageSink:window-width * * Actual width of the video window. */ g_object_class_install_property (gobject_class, PROP_WINDOW_WIDTH, g_param_spec_uint64 ("window-width", "window-width", "Width of the window", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GstRkXImageSink:window-height * * Actual height of the video window. */ g_object_class_install_property (gobject_class, PROP_WINDOW_HEIGHT, g_param_spec_uint64 ("window-height", "window-height", "Height of the window", 0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * kmssink:driver-name: * * If you have a system with multiple GPUs, you can choose which GPU * to use setting the DRM device driver name. Otherwise, the first * one from an internal list is used. */ g_object_class_install_property (gobject_class, PROP_DRIVER_NAME, g_param_spec_string ("driver-name", "device name", "DRM device driver name", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); /** * kmssink:connector-id: * * A GPU has several output connectors, for example: LVDS, VGA, * HDMI, etc. By default the first LVDS is tried, then the first * eDP, and at the end, the first connected one. */ g_object_class_install_property (gobject_class, PROP_CONNECTOR_ID, g_param_spec_int ("connector-id", "Connector ID", "DRM connector id", -1, G_MAXINT32, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); /** * kmssink:plane-id: * * There could be several planes associated with a CRTC. * By default the first plane that's possible to use with a given * CRTC is tried. */ g_object_class_install_property (gobject_class, PROP_PLANE_ID, g_param_spec_int ("plane-id", "Plane ID", "DRM plane id", -1, G_MAXINT32, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); gst_element_class_set_static_metadata (gstelement_class, "Video sink", "Sink/Video", "A standard X based videosink", "Julien Moutte "); gst_element_class_add_static_pad_template (gstelement_class, &gst_x_image_sink_sink_template_factory); gstelement_class->change_state = gst_x_image_sink_change_state; gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_x_image_sink_start); gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_x_image_sink_stop); gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_getcaps); gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_x_image_sink_setcaps); gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_x_image_sink_get_times); gstbasesink_class->propose_allocation = GST_DEBUG_FUNCPTR (gst_kms_sink_propose_allocation); gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_kms_sink_query); gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_x_image_sink_event); videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_x_image_sink_show_frame); } static gboolean plugin_init (GstPlugin * plugin) { if (!gst_element_register (plugin, "rkximagesink", GST_RANK_SECONDARY, GST_TYPE_X_IMAGE_SINK)) return FALSE; GST_DEBUG_CATEGORY_INIT (gst_debug_x_image_sink, "rkximagesink", 0, "rkximagesink element"); return TRUE; } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, rkximage, "Rockchip X/DRM Video Sink", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);