1070 lines
26 KiB
C
1070 lines
26 KiB
C
/*
|
|
* Copyright 2022 Rockchip Electronics Co., Ltd
|
|
* Author: Jeffy Chen <jeffy.chen@rock-chips.com>
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <xf86drm.h>
|
|
#include <xf86drmMode.h>
|
|
#include <drm_fourcc.h>
|
|
|
|
#include <gst/video/video.h>
|
|
#include <gst/base/gstpushsrc.h>
|
|
#include <gst/allocators/gstdmabuf.h>
|
|
|
|
#ifndef DRM_FORMAT_NV12_10
|
|
#define DRM_FORMAT_NV12_10 fourcc_code('N', 'A', '1', '2')
|
|
#endif
|
|
|
|
#ifndef DRM_FORMAT_NV15
|
|
#define DRM_FORMAT_NV15 fourcc_code('N', 'V', '1', '5')
|
|
#endif
|
|
|
|
#define DEFAULT_PROP_FRAMERATE_LIMIT 120
|
|
|
|
static gboolean DEFAULT_PROP_DMA_FEATURE = FALSE;
|
|
|
|
#define GST_TYPE_KMS_SRC (gst_kms_src_get_type())
|
|
G_DECLARE_FINAL_TYPE (GstKmsSrc, gst_kms_src, GST, KMS_SRC, GstPushSrc);
|
|
|
|
struct _GstKmsSrc {
|
|
GstBaseSrc element;
|
|
|
|
GstAllocator *allocator;
|
|
GstVideoInfo info;
|
|
|
|
gchar *devname;
|
|
gchar *bus_id;
|
|
|
|
gint fd;
|
|
|
|
guint crtc_id;
|
|
guint encoder_id;
|
|
guint connector_id;
|
|
guint plane_id;
|
|
guint fb_id;
|
|
|
|
gboolean dma_feature;
|
|
|
|
guint framerate_limit;
|
|
guint fps_n;
|
|
guint fps_d;
|
|
gboolean sync_fb;
|
|
gboolean sync_vblank;
|
|
|
|
GstPoll *poll;
|
|
GstPollFD pollfd;
|
|
|
|
guint last_fb_id;
|
|
GstClockTime last_frame_time;
|
|
|
|
GstClockTime start_time;
|
|
};
|
|
|
|
#define parent_class gst_kms_src_parent_class
|
|
G_DEFINE_TYPE (GstKmsSrc, gst_kms_src, GST_TYPE_PUSH_SRC);
|
|
|
|
#define GST_KMS_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
|
|
GST_TYPE_KMS_SRC, GstKmsSrc))
|
|
|
|
#define GST_CAT_DEFAULT kms_src_debug
|
|
GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
|
|
|
|
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
enum
|
|
{
|
|
PROP_DRIVER_NAME = 1,
|
|
PROP_BUS_ID,
|
|
PROP_CRTC_ID,
|
|
PROP_ENCODER_ID,
|
|
PROP_CONNECTOR_ID,
|
|
PROP_PLANE_ID,
|
|
PROP_FB_ID,
|
|
PROP_DMA_FEATURE,
|
|
PROP_FRAMERATE_LIMIT,
|
|
PROP_SYNC_FB,
|
|
PROP_SYNC_VBLANK,
|
|
PROP_LAST,
|
|
};
|
|
|
|
struct kmssrc_fb {
|
|
guint handles[4];
|
|
guint pitches[4];
|
|
guint offsets[4];
|
|
guint width;
|
|
guint height;
|
|
guint fourcc;
|
|
};
|
|
|
|
static void
|
|
gst_kms_src_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstKmsSrc *self = GST_KMS_SRC (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DRIVER_NAME:
|
|
g_free (self->devname);
|
|
self->devname = g_value_dup_string (value);
|
|
break;
|
|
case PROP_BUS_ID:
|
|
g_free (self->bus_id);
|
|
self->bus_id = g_value_dup_string (value);
|
|
break;
|
|
case PROP_CRTC_ID:
|
|
self->crtc_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_ENCODER_ID:
|
|
self->encoder_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_CONNECTOR_ID:
|
|
self->connector_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_PLANE_ID:
|
|
self->plane_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_FB_ID:
|
|
self->fb_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_DMA_FEATURE:
|
|
self->dma_feature = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_FRAMERATE_LIMIT:
|
|
self->framerate_limit = g_value_get_uint (value);
|
|
break;
|
|
case PROP_SYNC_FB:
|
|
self->sync_fb = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_SYNC_VBLANK:
|
|
self->sync_vblank = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_kms_src_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstKmsSrc *self = GST_KMS_SRC (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DRIVER_NAME:
|
|
g_value_set_string (value, self->devname);
|
|
break;
|
|
case PROP_BUS_ID:
|
|
g_value_set_string (value, self->bus_id);
|
|
break;
|
|
case PROP_CRTC_ID:
|
|
g_value_set_uint (value, self->crtc_id);
|
|
break;
|
|
case PROP_ENCODER_ID:
|
|
g_value_set_uint (value, self->encoder_id);
|
|
break;
|
|
case PROP_CONNECTOR_ID:
|
|
g_value_set_uint (value, self->connector_id);
|
|
break;
|
|
case PROP_PLANE_ID:
|
|
g_value_set_uint (value, self->plane_id);
|
|
break;
|
|
case PROP_FB_ID:
|
|
g_value_set_uint (value, self->fb_id);
|
|
break;
|
|
case PROP_DMA_FEATURE:
|
|
g_value_set_boolean (value, self->dma_feature);
|
|
break;
|
|
case PROP_FRAMERATE_LIMIT:
|
|
g_value_set_uint (value, self->framerate_limit);
|
|
break;
|
|
case PROP_SYNC_FB:
|
|
g_value_set_boolean (value, self->sync_fb);
|
|
break;
|
|
case PROP_SYNC_VBLANK:
|
|
g_value_set_boolean (value, self->sync_vblank);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_crtc_fb (GstKmsSrc * self, guint crtc_id)
|
|
{
|
|
drmModeCrtcPtr crtc;
|
|
guint fb_id;
|
|
|
|
crtc = drmModeGetCrtc (self->fd, crtc_id);
|
|
if (!crtc)
|
|
return 0;
|
|
|
|
fb_id = crtc->buffer_id;
|
|
|
|
drmModeFreeCrtc (crtc);
|
|
return fb_id;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_encoder_fb (GstKmsSrc * self, guint encoder_id)
|
|
{
|
|
drmModeEncoderPtr encoder;
|
|
guint fb_id;
|
|
|
|
encoder = drmModeGetEncoder (self->fd, encoder_id);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
fb_id = gst_kms_src_get_crtc_fb (self, encoder->crtc_id);
|
|
|
|
drmModeFreeEncoder (encoder);
|
|
return fb_id;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_connector_fb (GstKmsSrc * self, guint connector_id)
|
|
{
|
|
drmModeConnectorPtr connector;
|
|
guint fb_id;
|
|
|
|
connector = drmModeGetConnector (self->fd, connector_id);
|
|
if (!connector)
|
|
return 0;
|
|
|
|
fb_id = gst_kms_src_get_encoder_fb (self, connector->encoder_id);
|
|
|
|
drmModeFreeConnector (connector);
|
|
return fb_id;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_plane_fb (GstKmsSrc * self, guint plane_id)
|
|
{
|
|
drmModePlanePtr plane;
|
|
guint fb_id;
|
|
|
|
plane = drmModeGetPlane (self->fd, plane_id);
|
|
if (!plane)
|
|
return 0;
|
|
|
|
fb_id = plane->fb_id;
|
|
|
|
drmModeFreePlane (plane);
|
|
return fb_id;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_fb_id (GstKmsSrc * self)
|
|
{
|
|
if (self->fb_id)
|
|
return self->fb_id;
|
|
|
|
if (self->plane_id)
|
|
return gst_kms_src_get_plane_fb (self, self->plane_id);
|
|
|
|
if (self->connector_id)
|
|
return gst_kms_src_get_connector_fb (self, self->connector_id);
|
|
|
|
if (self->encoder_id)
|
|
return gst_kms_src_get_encoder_fb (self, self->encoder_id);
|
|
|
|
if (self->crtc_id)
|
|
return gst_kms_src_get_crtc_fb (self, self->crtc_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_encoder_crtc (GstKmsSrc * self, guint encoder_id)
|
|
{
|
|
drmModeEncoderPtr encoder;
|
|
guint crtc_id;
|
|
|
|
encoder = drmModeGetEncoder (self->fd, encoder_id);
|
|
if (!encoder)
|
|
return 0;
|
|
|
|
crtc_id = encoder->crtc_id;
|
|
|
|
drmModeFreeEncoder (encoder);
|
|
return crtc_id;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_connector_crtc (GstKmsSrc * self, guint connector_id)
|
|
{
|
|
drmModeConnectorPtr connector;
|
|
guint crtc_id;
|
|
|
|
connector = drmModeGetConnector (self->fd, connector_id);
|
|
if (!connector)
|
|
return 0;
|
|
|
|
crtc_id = gst_kms_src_get_encoder_crtc (self, connector->encoder_id);
|
|
|
|
drmModeFreeConnector (connector);
|
|
return crtc_id;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_plane_crtc (GstKmsSrc * self, guint plane_id)
|
|
{
|
|
drmModePlanePtr plane;
|
|
guint crtc_id;
|
|
|
|
plane = drmModeGetPlane (self->fd, plane_id);
|
|
if (!plane)
|
|
return 0;
|
|
|
|
crtc_id = plane->crtc_id;
|
|
|
|
drmModeFreePlane (plane);
|
|
return crtc_id;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_crtc_id (GstKmsSrc * self)
|
|
{
|
|
if (self->crtc_id)
|
|
return self->crtc_id;
|
|
|
|
if (self->plane_id)
|
|
return gst_kms_src_get_plane_crtc (self, self->plane_id);
|
|
|
|
if (self->connector_id)
|
|
return gst_kms_src_get_connector_crtc (self, self->connector_id);
|
|
|
|
if (self->encoder_id)
|
|
return gst_kms_src_get_encoder_crtc (self, self->encoder_id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_crtc_pipe (GstKmsSrc * self, guint crtc_id)
|
|
{
|
|
drmModeResPtr res;
|
|
guint crtc_pipe = 0;
|
|
gint i;
|
|
|
|
if (!crtc_id)
|
|
return 0;
|
|
|
|
res = drmModeGetResources (self->fd);
|
|
if (!res)
|
|
return 0;
|
|
|
|
for (i = 0; i < res->count_crtcs; i++) {
|
|
if (res->crtcs[i] == crtc_id) {
|
|
crtc_pipe = i;
|
|
break;
|
|
}
|
|
}
|
|
drmModeFreeResources (res);
|
|
return crtc_pipe;
|
|
}
|
|
|
|
static void
|
|
sync_handler (gint fd, guint frame, guint sec, guint usec, gpointer data)
|
|
{
|
|
gboolean *waiting;
|
|
|
|
(void) fd;
|
|
(void) frame;
|
|
(void) sec;
|
|
(void) usec;
|
|
|
|
waiting = data;
|
|
*waiting = FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_kms_src_sync_vblank (GstKmsSrc * self)
|
|
{
|
|
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,
|
|
},
|
|
};
|
|
guint crtc_id = gst_kms_src_get_crtc_id (self);
|
|
guint crtc_pipe = gst_kms_src_get_crtc_pipe (self, crtc_id);
|
|
gint ret;
|
|
|
|
if (crtc_pipe == 1)
|
|
vbl.request.type |= DRM_VBLANK_SECONDARY;
|
|
else if (crtc_pipe > 1)
|
|
vbl.request.type |= crtc_pipe << DRM_VBLANK_HIGH_CRTC_SHIFT;
|
|
|
|
waiting = TRUE;
|
|
ret = drmWaitVBlank (self->fd, &vbl);
|
|
if (ret)
|
|
return FALSE;
|
|
|
|
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)
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "sync vblank with CRTC: %d(%d)", crtc_id, crtc_pipe);
|
|
return TRUE;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_get_next_fb_id (GstKmsSrc * self)
|
|
{
|
|
GstClockTimeDiff diff;
|
|
gboolean sync_fb = self->sync_fb && !self->fb_id;
|
|
guint duration = 0;
|
|
guint fb_id;
|
|
|
|
if (self->sync_vblank)
|
|
gst_kms_src_sync_vblank (self);
|
|
|
|
if (self->fps_d && self->fps_n)
|
|
duration =
|
|
gst_util_uint64_scale (GST_SECOND, self->fps_d, self->fps_n);
|
|
else if (self->framerate_limit)
|
|
duration = GST_SECOND / self->framerate_limit;
|
|
|
|
while (1) {
|
|
diff = GST_CLOCK_DIFF (self->last_frame_time, gst_util_get_timestamp ());
|
|
|
|
fb_id = gst_kms_src_get_fb_id (self);
|
|
if (!fb_id)
|
|
return 0;
|
|
|
|
if (!sync_fb || fb_id != self->last_fb_id) {
|
|
sync_fb = FALSE;
|
|
|
|
if (diff >= duration)
|
|
return fb_id;
|
|
}
|
|
|
|
g_usleep (1000);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef HAS_DRM_CLOSE_HANDLE
|
|
/* From libdrm 2.4.109 : xf86drm.c */
|
|
static int
|
|
drmCloseBufferHandle(int fd, uint32_t handle)
|
|
{
|
|
struct drm_gem_close args;
|
|
|
|
memset(&args, 0, sizeof(args));
|
|
args.handle = handle;
|
|
return drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &args);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
gst_kms_src_free_fb (GstKmsSrc * self, struct kmssrc_fb * fb)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (fb->handles[i])
|
|
drmCloseBufferHandle (self->fd, fb->handles[i]);
|
|
|
|
fb->handles[i] = 0;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_kms_src_update_info (GstKmsSrc * self, struct kmssrc_fb * fb)
|
|
{
|
|
GstVideoInfo *info = &self->info;
|
|
GstVideoFormat format;
|
|
guint i;
|
|
|
|
#define KMSSRC_CASE_FOURCC(fourcc, gst) \
|
|
case DRM_FORMAT_ ## fourcc: format = GST_VIDEO_FORMAT_ ## gst; break;
|
|
|
|
switch (fb->fourcc) {
|
|
KMSSRC_CASE_FOURCC (ARGB8888, BGRA);
|
|
KMSSRC_CASE_FOURCC (XRGB8888, BGRx);
|
|
KMSSRC_CASE_FOURCC (ABGR8888, RGBA);
|
|
KMSSRC_CASE_FOURCC (XBGR8888, RGBx);
|
|
KMSSRC_CASE_FOURCC (RGB888, BGR);
|
|
KMSSRC_CASE_FOURCC (BGR888, RGB);
|
|
KMSSRC_CASE_FOURCC (YUV422, Y42B);
|
|
KMSSRC_CASE_FOURCC (NV16, NV16);
|
|
KMSSRC_CASE_FOURCC (NV61, NV61);
|
|
KMSSRC_CASE_FOURCC (UYVY, UYVY);
|
|
KMSSRC_CASE_FOURCC (YVYU, YVYU);
|
|
KMSSRC_CASE_FOURCC (YUYV, YUY2);
|
|
KMSSRC_CASE_FOURCC (YUV420, I420);
|
|
KMSSRC_CASE_FOURCC (YVU420, YV12);
|
|
KMSSRC_CASE_FOURCC (NV12, NV12);
|
|
KMSSRC_CASE_FOURCC (NV21, NV21);
|
|
KMSSRC_CASE_FOURCC (RGB565, RGB16);
|
|
KMSSRC_CASE_FOURCC (BGR565, BGR16);
|
|
#ifdef HAVE_NV12_10LE40
|
|
KMSSRC_CASE_FOURCC (NV15, NV12_10LE40);
|
|
KMSSRC_CASE_FOURCC (NV12_10, NV12_10LE40);
|
|
#endif
|
|
default:
|
|
GST_ERROR_OBJECT (self, "format not supported %x", fb->fourcc);
|
|
return FALSE;
|
|
}
|
|
|
|
gst_video_info_set_format (info, format, fb->width, fb->height);
|
|
|
|
GST_VIDEO_INFO_SIZE (info) = 0;
|
|
for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) {
|
|
GST_VIDEO_INFO_PLANE_STRIDE (info, i) = fb->pitches[i];
|
|
GST_VIDEO_INFO_PLANE_OFFSET (info, i) = fb->offsets[i];
|
|
|
|
GST_DEBUG_OBJECT (self, "plane %d, stride %d, offset %" G_GSIZE_FORMAT, i,
|
|
GST_VIDEO_INFO_PLANE_STRIDE (info, i),
|
|
GST_VIDEO_INFO_PLANE_OFFSET (info, i));
|
|
|
|
GST_VIDEO_INFO_SIZE (info) += fb->pitches[i] * fb->height;
|
|
}
|
|
GST_DEBUG_OBJECT (self, "size %" G_GSIZE_FORMAT, GST_VIDEO_INFO_SIZE (info));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_kms_src_get_fb (GstKmsSrc * self, guint fb_id, struct kmssrc_fb * kmssrc_fb)
|
|
{
|
|
drmModeFBPtr fb;
|
|
#ifdef HAS_DRM_MODE_FB2
|
|
drmModeFB2Ptr fb2;
|
|
guint i;
|
|
#endif
|
|
|
|
memset (kmssrc_fb, 0, sizeof (*kmssrc_fb));
|
|
|
|
#ifdef HAS_DRM_MODE_FB2
|
|
fb2 = drmModeGetFB2 (self->fd, fb_id);
|
|
if (fb2) {
|
|
for (i = 0; i < 4; i++) {
|
|
kmssrc_fb->handles[i] = fb2->handles[i];
|
|
kmssrc_fb->pitches[i] = fb2->pitches[i];
|
|
kmssrc_fb->offsets[i] = fb2->offsets[i];
|
|
}
|
|
kmssrc_fb->fourcc = fb2->pixel_format;
|
|
kmssrc_fb->width = fb2->width;
|
|
kmssrc_fb->height = fb2->height;
|
|
drmModeFreeFB2 (fb2);
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
fb = drmModeGetFB (self->fd, fb_id);
|
|
if (!fb)
|
|
return FALSE;
|
|
|
|
kmssrc_fb->handles[0] = fb->handle;
|
|
kmssrc_fb->pitches[0] = fb->pitch;
|
|
|
|
switch (fb->bpp) {
|
|
case 32:
|
|
if (fb->depth == 32)
|
|
kmssrc_fb->fourcc = DRM_FORMAT_ARGB8888;
|
|
else
|
|
kmssrc_fb->fourcc = DRM_FORMAT_XRGB8888;
|
|
break;
|
|
case 16:
|
|
kmssrc_fb->fourcc = DRM_FORMAT_RGB565;
|
|
break;
|
|
default:
|
|
GST_ERROR_OBJECT (self, "FB format unsupported");
|
|
gst_kms_src_free_fb (self, kmssrc_fb);
|
|
drmModeFreeFB (fb);
|
|
return FALSE;
|
|
}
|
|
|
|
kmssrc_fb->width = fb->width;
|
|
kmssrc_fb->height = fb->height;
|
|
drmModeFreeFB (fb);
|
|
return TRUE;
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_kms_src_import_drm_fb (GstKmsSrc * self, guint fb_id)
|
|
{
|
|
GstVideoInfo *info = &self->info;
|
|
GstBuffer *buf = NULL;
|
|
GstMemory *mem;
|
|
struct kmssrc_fb fb;
|
|
struct stat st[4];
|
|
gint i, dmafd[4], size[4];
|
|
|
|
if (!gst_kms_src_get_fb (self, fb_id, &fb)) {
|
|
GST_ERROR_OBJECT (self, "could not get DRM FB %d", fb_id);
|
|
return NULL;
|
|
}
|
|
|
|
if (!gst_kms_src_update_info (self, &fb))
|
|
goto err;
|
|
|
|
buf = gst_buffer_new ();
|
|
if (!buf)
|
|
goto err;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (!fb.handles[i])
|
|
break;
|
|
|
|
size[i] = fb.pitches[i] * fb.height;
|
|
|
|
GST_DEBUG_OBJECT (self, "importing DRM handle %d(%d) for %dx%d",
|
|
fb.handles[i], i, fb.pitches[i], fb.height);
|
|
|
|
if (drmPrimeHandleToFD (self->fd, fb.handles[i], DRM_CLOEXEC | DRM_RDWR,
|
|
&dmafd[i]) < 0) {
|
|
GST_ERROR_OBJECT (self, "could not import DRM handle %d", fb.handles[i]);
|
|
goto err;
|
|
}
|
|
|
|
fstat (dmafd[i], &st[i]);
|
|
if (i && !memcmp (&st[i], &st[i - 1], sizeof (struct stat))) {
|
|
/* Reuse the old fd */
|
|
close (dmafd[i]);
|
|
dmafd[i] = dmafd[i - 1];
|
|
|
|
/* Check for contig planes */
|
|
if (fb.offsets[i] == fb.offsets[i - 1] + size[i - 1]) {
|
|
gsize mem_size, mem_offset;
|
|
|
|
mem_size = gst_memory_get_sizes (mem, &mem_offset, NULL);
|
|
gst_memory_resize (mem, mem_offset, mem_size + size[i]);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
mem = gst_fd_allocator_alloc (self->allocator, dmafd[i], st[i].st_size,
|
|
GST_FD_MEMORY_FLAG_NONE);
|
|
if (!mem) {
|
|
close (dmafd[i]);
|
|
goto err;
|
|
}
|
|
|
|
gst_memory_resize (mem, fb.offsets[i], size[i]);
|
|
gst_buffer_append_memory (buf, mem);
|
|
}
|
|
|
|
gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE,
|
|
GST_VIDEO_INFO_FORMAT (info),
|
|
GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info),
|
|
GST_VIDEO_INFO_N_PLANES (info), info->offset, info->stride);
|
|
|
|
gst_kms_src_free_fb (self, &fb);
|
|
return buf;
|
|
err:
|
|
if (buf)
|
|
gst_buffer_unref (buf);
|
|
|
|
gst_kms_src_free_fb (self, &fb);
|
|
return NULL;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_kms_src_create (GstPushSrc * src, GstBuffer ** ret)
|
|
{
|
|
GstKmsSrc *self = GST_KMS_SRC (src);
|
|
GstBuffer *buf;
|
|
guint fb_id;
|
|
|
|
GST_DEBUG_OBJECT (self, "creating buffer");
|
|
|
|
fb_id = gst_kms_src_get_next_fb_id (self);
|
|
if (!fb_id) {
|
|
GST_ERROR_OBJECT (self, "could not get valid FB");
|
|
goto err;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "importing DRM FB %d (old: %d)",
|
|
fb_id, self->last_fb_id);
|
|
|
|
buf = gst_kms_src_import_drm_fb (self, fb_id);
|
|
if (!buf) {
|
|
GST_ERROR_OBJECT (self, "could not import FB %d", fb_id);
|
|
goto err;
|
|
}
|
|
|
|
self->last_frame_time = gst_util_get_timestamp ();
|
|
self->last_fb_id = fb_id;
|
|
|
|
GST_BUFFER_DTS (buf) = GST_BUFFER_PTS (buf) =
|
|
self->last_frame_time - self->start_time;
|
|
|
|
*ret = buf;
|
|
|
|
return GST_FLOW_OK;
|
|
err:
|
|
if (self->last_fb_id)
|
|
return GST_FLOW_EOS;
|
|
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
/* Based on gst-plugins-good/sys/ximage/gstximagesrc.c */
|
|
static gboolean
|
|
gst_kms_src_set_caps (GstBaseSrc * basesrc, GstCaps * caps)
|
|
{
|
|
GstKmsSrc *self = GST_KMS_SRC (basesrc);
|
|
GstVideoInfo *info = &self->info;
|
|
GstStructure *structure;
|
|
const GValue *new_fps;
|
|
|
|
/* The only thing that can change is the framerate downstream wants */
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
new_fps = gst_structure_get_value (structure, "framerate");
|
|
if (!new_fps)
|
|
return FALSE;
|
|
|
|
/* Store this FPS for use when generating buffers */
|
|
info->fps_n = self->fps_n = gst_value_get_fraction_numerator (new_fps);
|
|
info->fps_d = self->fps_d = gst_value_get_fraction_denominator (new_fps);
|
|
|
|
GST_DEBUG_OBJECT (self, "peer wants %d/%d fps", self->fps_n, self->fps_d);
|
|
|
|
if (self->framerate_limit * self->fps_d > self->fps_n)
|
|
GST_WARNING_OBJECT (self, "framerate-limit ignored");
|
|
|
|
if (self->sync_fb)
|
|
GST_WARNING_OBJECT (self, "sync-fb enabled, framerate might be inaccurate");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_kms_src_get_caps (GstBaseSrc * basesrc, GstCaps * filter)
|
|
{
|
|
GstKmsSrc *self = GST_KMS_SRC (basesrc);
|
|
GstStructure *s;
|
|
GstCaps *caps;
|
|
struct kmssrc_fb fb;
|
|
guint fb_id;
|
|
|
|
fb_id = gst_kms_src_get_fb_id (self);
|
|
if (!fb_id)
|
|
return gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (basesrc));
|
|
|
|
if (!gst_kms_src_get_fb (self, fb_id, &fb)) {
|
|
GST_ERROR_OBJECT (self, "could not get DRM FB %d", fb_id);
|
|
return NULL;
|
|
}
|
|
|
|
if (!gst_kms_src_update_info (self, &fb)) {
|
|
gst_kms_src_free_fb (self, &fb);
|
|
return NULL;
|
|
}
|
|
|
|
gst_kms_src_free_fb (self, &fb);
|
|
|
|
caps = gst_video_info_to_caps (&self->info);
|
|
|
|
if (self->dma_feature)
|
|
gst_caps_set_features (caps, 0,
|
|
gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_DMABUF, NULL));
|
|
|
|
if (filter) {
|
|
GstCaps *intersection;
|
|
|
|
intersection =
|
|
gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (caps);
|
|
caps = intersection;
|
|
}
|
|
|
|
s = gst_caps_get_structure (caps, 0);
|
|
gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT,
|
|
1, NULL);
|
|
|
|
GST_DEBUG_OBJECT (self, "returning caps: %" GST_PTR_FORMAT, caps);
|
|
return caps;
|
|
}
|
|
|
|
static gboolean
|
|
gst_kms_src_stop (GstBaseSrc * basesrc)
|
|
{
|
|
GstKmsSrc *self = GST_KMS_SRC (basesrc);
|
|
|
|
gst_poll_remove_fd (self->poll, &self->pollfd);
|
|
gst_poll_restart (self->poll);
|
|
gst_poll_fd_init (&self->pollfd);
|
|
|
|
if (self->allocator)
|
|
g_object_unref (self->allocator);
|
|
|
|
if (self->fd >= 0)
|
|
drmClose (self->fd);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static guint
|
|
gst_kms_src_find_best_crtc (GstKmsSrc * self)
|
|
{
|
|
drmModeCrtcPtr crtc;
|
|
drmModeResPtr res;
|
|
guint crtc_id;
|
|
gint i;
|
|
|
|
res = drmModeGetResources (self->fd);
|
|
if (!res)
|
|
return 0;
|
|
|
|
for (i = 0, crtc_id = 0; i < res->count_crtcs; i++) {
|
|
crtc = drmModeGetCrtc (self->fd, res->crtcs[i]);
|
|
if (crtc && crtc->mode_valid) {
|
|
drmModeFreeCrtc (crtc);
|
|
crtc_id = res->crtcs[i];
|
|
break;
|
|
}
|
|
drmModeFreeCrtc (crtc);
|
|
}
|
|
drmModeFreeResources (res);
|
|
|
|
if (crtc_id)
|
|
GST_DEBUG_OBJECT (self, "using best CRTC %d", crtc_id);
|
|
|
|
return crtc_id;
|
|
}
|
|
|
|
static gboolean
|
|
gst_kms_src_start (GstBaseSrc * basesrc)
|
|
{
|
|
GstKmsSrc *self = GST_KMS_SRC (basesrc);
|
|
|
|
self->allocator = gst_dmabuf_allocator_new ();
|
|
if (!self->allocator)
|
|
return FALSE;
|
|
|
|
if (self->devname || self->bus_id)
|
|
self->fd = drmOpen (self->devname, self->bus_id);
|
|
|
|
if (self->fd < 0)
|
|
self->fd = open ("/dev/dri/card0", O_RDWR | O_CLOEXEC);
|
|
|
|
if (self->fd < 0) {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ_WRITE,
|
|
("Could not open DRM module %s", GST_STR_NULL (self->devname)),
|
|
("reason: %s (%d)", g_strerror (errno), errno));
|
|
gst_kms_src_stop (basesrc);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!self->fb_id && !self->plane_id && !self->connector_id &&
|
|
!self->encoder_id && !self->crtc_id) {
|
|
self->crtc_id = gst_kms_src_find_best_crtc (self);
|
|
if (!self->crtc_id) {
|
|
GST_ERROR_OBJECT (self, "could not find a valid CRTC");
|
|
gst_kms_src_stop (basesrc);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
self->last_fb_id = 0;
|
|
self->last_frame_time = gst_util_get_timestamp ();
|
|
self->start_time = gst_util_get_timestamp ();
|
|
|
|
self->pollfd.fd = self->fd;
|
|
gst_poll_add_fd (self->poll, &self->pollfd);
|
|
gst_poll_fd_ctl_read (self->poll, &self->pollfd, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_kms_src_finalize (GObject * object)
|
|
{
|
|
GstKmsSrc *self;
|
|
|
|
self = GST_KMS_SRC (object);
|
|
g_clear_pointer (&self->devname, g_free);
|
|
g_clear_pointer (&self->bus_id, g_free);
|
|
|
|
gst_poll_free (self->poll);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_kms_src_init (GstKmsSrc * self)
|
|
{
|
|
self->devname = g_strdup ("rockchip");
|
|
|
|
self->fd = -1;
|
|
|
|
self->crtc_id = 0;
|
|
self->encoder_id = 0;
|
|
self->connector_id = 0;
|
|
self->plane_id = 0;
|
|
self->fb_id = 0;
|
|
|
|
self->framerate_limit = DEFAULT_PROP_FRAMERATE_LIMIT;
|
|
self->sync_fb = TRUE;
|
|
self->sync_vblank = TRUE;
|
|
self->fps_n = 0;
|
|
self->fps_d = 1;
|
|
|
|
gst_video_info_init (&self->info);
|
|
|
|
gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME);
|
|
gst_base_src_set_live (GST_BASE_SRC (self), TRUE);
|
|
|
|
gst_poll_fd_init (&self->pollfd);
|
|
self->poll = gst_poll_new (TRUE);
|
|
}
|
|
|
|
static void
|
|
gst_kms_src_class_init (GstKmsSrcClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstBaseSrcClass *gstbase_src_class;
|
|
GstPushSrcClass *gstpush_src_class;
|
|
const gchar *env;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gstelement_class = GST_ELEMENT_CLASS (klass);
|
|
gstbase_src_class = GST_BASE_SRC_CLASS (klass);
|
|
gstpush_src_class = GST_PUSH_SRC_CLASS (klass);
|
|
|
|
gobject_class->finalize = gst_kms_src_finalize;
|
|
gobject_class->set_property = gst_kms_src_set_property;
|
|
gobject_class->get_property = gst_kms_src_get_property;
|
|
|
|
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));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BUS_ID,
|
|
g_param_spec_string ("bus-id", "Bus ID", "DRM bus ID", NULL,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_CRTC_ID,
|
|
g_param_spec_uint ("crtc-id", "DRM crtc ID",
|
|
"DRM crtc ID (0 = unspecified)",
|
|
0, G_MAXINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_ENCODER_ID,
|
|
g_param_spec_uint ("encoder-id", "DRM plane ID",
|
|
"DRM encoder ID (0 = unspecified)",
|
|
0, G_MAXINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_CONNECTOR_ID,
|
|
g_param_spec_uint ("connector-id", "DRM connector ID",
|
|
"DRM connector ID (0 = unspecified)",
|
|
0, G_MAXINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_PLANE_ID,
|
|
g_param_spec_uint ("plane-id", "DRM plane ID",
|
|
"DRM plane ID (0 = unspecified)",
|
|
0, G_MAXINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FB_ID,
|
|
g_param_spec_uint ("fb-id", "DRM FB ID",
|
|
"DRM FB ID (0 = unspecified)",
|
|
0, G_MAXINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
env = g_getenv ("GST_KMSSRC_DMA_FEATURE");
|
|
if (env && !strcmp (env, "1"))
|
|
DEFAULT_PROP_DMA_FEATURE = TRUE;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DMA_FEATURE,
|
|
g_param_spec_boolean ("dma-feature", "DMA feature",
|
|
"Enable GST DMA feature", DEFAULT_PROP_DMA_FEATURE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FRAMERATE_LIMIT,
|
|
g_param_spec_uint ("framerate-limit", "Limited framerate",
|
|
"Limited framerate",
|
|
0, G_MAXINT, DEFAULT_PROP_FRAMERATE_LIMIT,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SYNC_FB,
|
|
g_param_spec_boolean ("sync-fb", "Sync with FB flip",
|
|
"Sync with FB flip", TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SYNC_VBLANK,
|
|
g_param_spec_boolean ("sync-vblank", "Sync with vblank",
|
|
"Sync with vblank", TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class,
|
|
"KMS Video Source",
|
|
"Source/Video",
|
|
"KMS Video Source",
|
|
"Jeffy Chen <jeffy.chen@rock-chips.com>");
|
|
|
|
gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
|
|
|
|
gstbase_src_class->set_caps = GST_DEBUG_FUNCPTR (gst_kms_src_set_caps);
|
|
gstbase_src_class->get_caps = GST_DEBUG_FUNCPTR (gst_kms_src_get_caps);
|
|
gstbase_src_class->start = GST_DEBUG_FUNCPTR (gst_kms_src_start);
|
|
gstbase_src_class->stop = GST_DEBUG_FUNCPTR (gst_kms_src_stop);
|
|
gstpush_src_class->create = GST_DEBUG_FUNCPTR (gst_kms_src_create);
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
if (!gst_element_register (plugin, "kmssrc", GST_RANK_NONE,
|
|
gst_kms_src_get_type ()))
|
|
return FALSE;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "kmssrc", 0, "KmsSrc");
|
|
return TRUE;
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
kmssrc,
|
|
"KMS Src Plugin",
|
|
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
|