gstreamer/sys/kms/gstkmssink.c
Nicolas Dufresne 753c423784 kmssink: Scale up to the screen dimension
In most display sink, the logic is to use as much as possible
of the given window. In this case, the window is the screen,
hence it's logical to scale up.

https://bugzilla.gnome.org/show_bug.cgi?id=767422
2016-09-08 13:23:35 -04:00

1316 lines
32 KiB
C

/* GStreamer
*
* Copyright (C) 2016 Igalia
*
* Authors:
* Víctor Manuel Jáquez Leal <vjaquez@igalia.com>
* Javier Martin <javiermartin@by.com.es>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
/**
* SECTION:element-kmssink
* @short_description: A KMS/DRM based video sink
*
* kmssink is a simple video sink that renders video frames directly
* in a plane of a DRM device.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch-1.0 videotestsrc ! kmssink
* ]|
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/video/video.h>
#include <gst/allocators/gstdmabuf.h>
#include <drm.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#include <string.h>
#include "gstkmssink.h"
#include "gstkmsutils.h"
#include "gstkmsbufferpool.h"
#include "gstkmsallocator.h"
#define GST_PLUGIN_NAME "kmssink"
#define GST_PLUGIN_DESC "Video sink using the Linux kernel mode setting API"
GST_DEBUG_CATEGORY_STATIC (gst_kms_sink_debug);
GST_DEBUG_CATEGORY_STATIC (CAT_PERFORMANCE);
#define GST_CAT_DEFAULT gst_kms_sink_debug
#define parent_class gst_kms_sink_parent_class
G_DEFINE_TYPE_WITH_CODE (GstKMSSink, gst_kms_sink, GST_TYPE_VIDEO_SINK,
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, GST_PLUGIN_NAME, 0,
GST_PLUGIN_DESC);
GST_DEBUG_CATEGORY_GET (CAT_PERFORMANCE, "GST_PERFORMANCE"));
enum
{
PROP_DRIVER_NAME = 1,
PROP_CONNECTOR_ID,
PROP_PLANE_ID,
PROP_N
};
static GParamSpec *g_properties[PROP_N] = { NULL, };
static int
kms_open (gchar ** driver)
{
static const char *drivers[] = { "i915", "radeon", "nouveau", "vmwgfx",
"exynos", "amdgpu", "imx-drm", "rockchip", "atmel-hlcdc"
};
int i, fd = -1;
for (i = 0; i < G_N_ELEMENTS (drivers); i++) {
fd = drmOpen (drivers[i], NULL);
if (fd >= 0) {
if (driver)
*driver = g_strdup (drivers[i]);
break;
}
}
return fd;
}
static drmModePlane *
find_plane_for_crtc (int fd, drmModeRes * res, drmModePlaneRes * pres,
int crtc_id)
{
drmModePlane *plane;
int i, pipe;
plane = NULL;
pipe = -1;
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++) {
plane = drmModeGetPlane (fd, pres->planes[i]);
if (plane->possible_crtcs & (1 << pipe))
return plane;
drmModeFreePlane (plane);
}
return NULL;
}
static drmModeCrtc *
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
connector_is_used (int fd, drmModeRes * res, drmModeConnector * conn)
{
gboolean result;
drmModeCrtc *crtc;
result = FALSE;
crtc = find_crtc_for_connector (fd, res, conn, NULL);
if (crtc) {
result = crtc->buffer_id != 0;
drmModeFreeCrtc (crtc);
}
return result;
}
static drmModeConnector *
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) && connector_is_used (fd, res, conn))
return conn;
drmModeFreeConnector (conn);
}
}
return NULL;
}
static drmModeConnector *
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 (connector_is_used (fd, res, conn))
return conn;
drmModeFreeConnector (conn);
}
}
return NULL;
}
static drmModeConnector *
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 = 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 = find_first_used_connector (fd, res);
return conn;
}
static void
log_drm_version (GstKMSSink * 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
get_drm_caps (GstKMSSink * 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);
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) / async page flip (%s)",
self->has_prime_import ? "" : "",
self->has_async_page_flip ? "" : "");
return TRUE;
}
static gboolean
ensure_allowed_caps (GstKMSSink * 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++) {
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;
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
gst_kms_sink_start (GstBaseSink * bsink)
{
GstKMSSink *self;
drmModeRes *res;
drmModeConnector *conn;
drmModeCrtc *crtc;
drmModePlaneRes *pres;
drmModePlane *plane;
gboolean universal_planes;
gboolean ret;
self = GST_KMS_SINK (bsink);
universal_planes = FALSE;
ret = FALSE;
res = NULL;
conn = NULL;
crtc = NULL;
pres = NULL;
plane = NULL;
if (self->devname)
self->fd = drmOpen (self->devname, NULL);
else
self->fd = kms_open (&self->devname);
if (self->fd < 0)
goto open_failed;
log_drm_version (self);
if (!get_drm_caps (self))
goto bail;
res = drmModeGetResources (self->fd);
if (!res)
goto resources_failed;
if (self->conn_id == -1)
conn = find_main_monitor (self->fd, res);
else
conn = drmModeGetConnector (self->fd, self->conn_id);
if (!conn)
goto connector_failed;
crtc = find_crtc_for_connector (self->fd, res, conn, &self->pipe);
if (!crtc)
goto crtc_failed;
retry_find_plane:
if (universal_planes &&
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 = find_plane_for_crtc (self->fd, res, pres, crtc->crtc_id);
else
plane = drmModeGetPlane (self->fd, self->plane_id);
if (!plane)
goto plane_failed;
/* let's get the available color formats in plane */
if (!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);
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:
{
if (universal_planes) {
GST_ERROR_OBJECT (self, "Could not find a plane for crtc");
goto bail;
} else {
universal_planes = TRUE;
goto retry_find_plane;
}
}
}
static gboolean
gst_kms_sink_stop (GstBaseSink * bsink)
{
GstKMSSink *self;
self = GST_KMS_SINK (bsink);
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) {
drmClose (self->fd);
self->fd = -1;
}
return TRUE;
}
static GstCaps *
gst_kms_sink_get_allowed_caps (GstKMSSink * self)
{
if (!self->allowed_caps)
return NULL; /* base class will return the template caps */
return gst_caps_ref (self->allowed_caps);
}
static GstCaps *
gst_kms_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
{
GstKMSSink *self;
GstCaps *caps, *out_caps;
self = GST_KMS_SINK (bsink);
caps = gst_kms_sink_get_allowed_caps (self);
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 void
ensure_kms_allocator (GstKMSSink * self)
{
if (self->allocator)
return;
self->allocator = gst_kms_allocator_new (self->fd);
}
static GstBufferPool *
gst_kms_sink_create_pool (GstKMSSink * 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_calculate_display_ratio (GstKMSSink * self, GstVideoInfo * vinfo)
{
guint dar_n, dar_d;
guint video_width, video_height;
guint video_par_n, video_par_d;
guint dpy_par_n, dpy_par_d;
video_width = GST_VIDEO_INFO_WIDTH (vinfo);
video_height = GST_VIDEO_INFO_HEIGHT (vinfo);
video_par_n = GST_VIDEO_INFO_PAR_N (vinfo);
video_par_d = GST_VIDEO_INFO_PAR_D (vinfo);
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 */
if (video_height % dar_d == 0) {
GST_DEBUG_OBJECT (self, "keeping video height");
GST_VIDEO_SINK_WIDTH (self) = (guint)
gst_util_uint64_scale_int (video_height, dar_n, dar_d);
GST_VIDEO_SINK_HEIGHT (self) = video_height;
} else if (video_width % dar_n == 0) {
GST_DEBUG_OBJECT (self, "keeping video width");
GST_VIDEO_SINK_WIDTH (self) = video_width;
GST_VIDEO_SINK_HEIGHT (self) = (guint)
gst_util_uint64_scale_int (video_width, dar_d, dar_n);
} else {
GST_DEBUG_OBJECT (self, "approximating while keeping video height");
GST_VIDEO_SINK_WIDTH (self) = (guint)
gst_util_uint64_scale_int (video_height, dar_n, dar_d);
GST_VIDEO_SINK_HEIGHT (self) = video_height;
}
GST_DEBUG_OBJECT (self, "scaling to %dx%d", GST_VIDEO_SINK_WIDTH (self),
GST_VIDEO_SINK_HEIGHT (self));
return TRUE;
}
static gboolean
gst_kms_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
{
GstKMSSink *self;
GstVideoInfo vinfo;
GstBufferPool *newpool, *oldpool;
self = GST_KMS_SINK (bsink);
if (!gst_video_info_from_caps (&vinfo, caps))
goto invalid_format;
if (!gst_kms_sink_calculate_display_ratio (self, &vinfo))
goto no_disp_ratio;
if (GST_VIDEO_SINK_WIDTH (self) <= 0 || GST_VIDEO_SINK_HEIGHT (self) <= 0)
goto invalid_size;
/* create a new pool for the new configuration */
newpool = gst_kms_sink_create_pool (self, caps, GST_VIDEO_INFO_SIZE (&vinfo),
2);
if (!newpool)
goto no_pool;
/* we don't activate the internal pool yet as it may not be needed */
oldpool = self->pool;
self->pool = newpool;
if (oldpool) {
gst_buffer_pool_set_active (oldpool, FALSE);
gst_object_unref (oldpool);
}
self->vinfo = vinfo;
GST_DEBUG_OBJECT (self, "negotiated caps = %" GST_PTR_FORMAT, caps);
return TRUE;
/* ERRORS */
invalid_format:
{
GST_ERROR_OBJECT (self, "caps invalid");
return FALSE;
}
invalid_size:
{
GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
("Invalid image size."));
return FALSE;
}
no_disp_ratio:
{
GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
("Error calculating the output display ratio of the video."));
return FALSE;
}
no_pool:
{
/* Already warned in create_pool */
return FALSE;
}
}
static gboolean
gst_kms_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
{
GstKMSSink *self;
GstCaps *caps;
gboolean need_pool;
GstVideoInfo vinfo;
GstBufferPool *pool;
gsize size;
self = GST_KMS_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;
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;
}
if (pool) {
/* we need at least 2 buffer because we hold on to the last one */
gst_query_add_allocation_pool (query, pool, size, 2, 0);
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;
}
no_pool:
{
/* Already warned in create_pool */
return FALSE;
}
}
static void
gst_kms_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
GstClockTime * start, GstClockTime * end)
{
GstKMSSink *self;
self = GST_KMS_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 (GST_VIDEO_INFO_FPS_N (&self->vinfo) > 0) {
*end = *start +
gst_util_uint64_scale_int (GST_SECOND,
GST_VIDEO_INFO_FPS_D (&self->vinfo),
GST_VIDEO_INFO_FPS_N (&self->vinfo));
}
}
}
}
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 (GstKMSSink * self)
{
gint ret;
gboolean waiting;
drmEventContext evctxt = {
.version = DRM_EVENT_CONTEXT_VERSION,
.page_flip_handler = sync_handler,
.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;
if (!self->has_async_page_flip) {
ret = drmWaitVBlank (self->fd, &vbl);
if (ret)
goto vblank_failed;
} else {
ret = drmModePageFlip (self->fd, self->crtc_id, self->buffer_id,
DRM_MODE_PAGE_FLIP_EVENT, &waiting);
if (ret)
goto pageflip_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)", strerror (-ret),
ret);
return FALSE;
}
pageflip_failed:
{
GST_WARNING_OBJECT (self, "drmModePageFlip failed: %s (%d)",
strerror (-ret), ret);
return FALSE;
}
event_failed:
{
GST_ERROR_OBJECT (self, "drmHandleEvent failed: %s (%d)", strerror (-ret),
ret);
return FALSE;
}
}
static GstMemory *
get_cached_kmsmem (GstMemory * mem)
{
return gst_mini_object_get_qdata (GST_MINI_OBJECT (mem),
g_quark_from_static_string ("kmsmem"));
}
static void
set_cached_kmsmem (GstMemory * mem, GstMemory * kmsmem)
{
return gst_mini_object_set_qdata (GST_MINI_OBJECT (mem),
g_quark_from_static_string ("kmsmem"), kmsmem,
(GDestroyNotify) gst_memory_unref);
}
static gboolean
gst_kms_sink_import_dmabuf (GstKMSSink * 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;
/* 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]);
/* And all memory found must be dmabuf */
if (!gst_is_dmabuf_memory (mems[i]))
return FALSE;
}
kmsmem = (GstKMSMemory *) get_cached_kmsmem (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);
set_cached_kmsmem (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_get_input_buffer (GstKMSSink * self, GstBuffer * inbuf)
{
GstMemory *mem;
GstBuffer *buf;
GstFlowReturn ret;
GstVideoFrame inframe, outframe;
gboolean success;
mem = gst_buffer_peek_memory (inbuf, 0);
if (!mem)
return NULL;
if (gst_is_kms_memory (mem))
return gst_buffer_ref (inbuf);
buf = NULL;
if (gst_kms_sink_import_dmabuf (self, inbuf, &buf))
return buf;
GST_CAT_INFO_OBJECT (CAT_PERFORMANCE, self, "frame copy");
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"));
goto bail;
}
create_buffer_failed:
{
GST_ELEMENT_ERROR (self, STREAM, FAILED, ("allocation failed"),
("failed to create buffer"));
goto bail;
}
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 GstFlowReturn
gst_kms_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
{
gint ret;
GstBuffer *buffer;
guint32 fb_id;
GstKMSSink *self;
GstVideoCropMeta *crop;
GstVideoRectangle src = { 0, };
GstVideoRectangle dst = { 0, };
GstVideoRectangle result;
GstFlowReturn res;
self = GST_KMS_SINK (vsink);
res = GST_FLOW_ERROR;
buffer = gst_kms_sink_get_input_buffer (self, buf);
if (!buffer)
return GST_FLOW_ERROR;
fb_id = gst_kms_memory_get_fb_id (gst_buffer_peek_memory (buffer, 0));
if (fb_id == 0)
goto buffer_invalid;
GST_TRACE_OBJECT (self, "displaying fb %d", fb_id);
if ((crop = gst_buffer_get_video_crop_meta (buffer))) {
GstVideoInfo vinfo = self->vinfo;
vinfo.width = crop->width;
vinfo.height = crop->height;
if (!gst_kms_sink_calculate_display_ratio (self, &vinfo))
goto no_disp_ratio;
src.x = crop->x;
src.y = crop->y;
}
src.w = GST_VIDEO_SINK_WIDTH (self);
src.h = GST_VIDEO_SINK_HEIGHT (self);
dst.w = self->hdisplay;
dst.h = self->vdisplay;
gst_video_sink_center_rect (src, dst, &result, TRUE);
if (crop) {
src.w = crop->width;
src.h = crop->height;
} else {
src.w = GST_VIDEO_INFO_WIDTH (&self->vinfo);
src.h = GST_VIDEO_INFO_HEIGHT (&self->vinfo);
}
GST_TRACE_OBJECT (self,
"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 (self->fd, self->plane_id, self->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)
goto set_plane_failed;
/* Wait for the previous frame to complete redraw */
if (!gst_kms_sink_sync (self))
goto bail;
gst_buffer_replace (&self->last_buffer, buffer);
res = GST_FLOW_OK;
bail:
gst_buffer_unref (buffer);
return res;
/* ERRORS */
buffer_invalid:
{
GST_ERROR_OBJECT (self, "invalid buffer: it doesn't have a fb id");
goto bail;
}
set_plane_failed:
{
GST_DEBUG_OBJECT (self, "result = { %d, %d, %d, %d} / "
"src = { %d, %d, %d %d } / dst = { %d, %d, %d %d }", result.x, result.y,
result.w, result.h, src.x, src.y, src.w, src.h, dst.x, dst.y, dst.w,
dst.h);
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
(NULL), ("drmModeSetPlane failed: %s (%d)", strerror (-ret), ret));
goto bail;
}
no_disp_ratio:
{
GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
("Error calculating the output display ratio of the video."));
goto bail;
}
}
static void
gst_kms_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstKMSSink *sink;
sink = GST_KMS_SINK (object);
switch (prop_id) {
case PROP_DRIVER_NAME:
sink->devname = g_value_dup_string (value);
break;
case PROP_CONNECTOR_ID:
sink->conn_id = g_value_get_int (value);
break;
case PROP_PLANE_ID:
sink->plane_id = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_kms_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstKMSSink *sink;
sink = GST_KMS_SINK (object);
switch (prop_id) {
case PROP_DRIVER_NAME:
g_value_take_string (value, sink->devname);
break;
case PROP_CONNECTOR_ID:
g_value_set_int (value, sink->conn_id);
break;
case PROP_PLANE_ID:
g_value_set_int (value, sink->plane_id);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_kms_sink_finalize (GObject * object)
{
GstKMSSink *sink;
sink = GST_KMS_SINK (object);
g_clear_pointer (&sink->devname, g_free);
gst_poll_free (sink->poll);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_kms_sink_init (GstKMSSink * sink)
{
sink->fd = -1;
sink->conn_id = -1;
sink->plane_id = -1;
gst_poll_fd_init (&sink->pollfd);
sink->poll = gst_poll_new (TRUE);
gst_video_info_init (&sink->vinfo);
}
static void
gst_kms_sink_class_init (GstKMSSinkClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
GstBaseSinkClass *basesink_class;
GstVideoSinkClass *videosink_class;
GstCaps *caps;
gobject_class = G_OBJECT_CLASS (klass);
element_class = GST_ELEMENT_CLASS (klass);
basesink_class = GST_BASE_SINK_CLASS (klass);
videosink_class = GST_VIDEO_SINK_CLASS (klass);
gst_element_class_set_static_metadata (element_class, "KMS video sink",
"Sink/Video", GST_PLUGIN_DESC, "Víctor Jáquez <vjaquez@igalia.com>");
caps = gst_kms_sink_caps_template_fill ();
gst_element_class_add_pad_template (element_class,
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps));
gst_caps_unref (caps);
basesink_class->start = GST_DEBUG_FUNCPTR (gst_kms_sink_start);
basesink_class->stop = GST_DEBUG_FUNCPTR (gst_kms_sink_stop);
basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_kms_sink_set_caps);
basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_kms_sink_get_caps);
basesink_class->propose_allocation = gst_kms_sink_propose_allocation;
basesink_class->get_times = gst_kms_sink_get_times;
videosink_class->show_frame = gst_kms_sink_show_frame;
gobject_class->finalize = gst_kms_sink_finalize;
gobject_class->set_property = gst_kms_sink_set_property;
gobject_class->get_property = gst_kms_sink_get_property;
/**
* 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_properties[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_properties[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_properties[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);
g_object_class_install_properties (gobject_class, PROP_N, g_properties);
}
static gboolean
plugin_init (GstPlugin * plugin)
{
if (!gst_element_register (plugin, GST_PLUGIN_NAME, GST_RANK_SECONDARY,
GST_TYPE_KMS_SINK))
return FALSE;
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, kms,
GST_PLUGIN_DESC, plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
GST_PACKAGE_ORIGIN)