From 7a2ab45440df546852490dcb04ecf9a79f8cb41d Mon Sep 17 00:00:00 2001 From: Sean D'Epagnier Date: Sun, 6 Jan 2008 22:00:32 +0000 Subject: Add fbdev-based video sink. Linux-only. See bug #506549. Original commit message from CVS: Patch by: Sean D'Epagnier * configure.ac: * sys/Makefile.am: * sys/fbdev/Makefile.am: * sys/fbdev/gstfbdevsink.c: * sys/fbdev/gstfbdevsink.h: Add fbdev-based video sink. Linux-only. See bug #506549. --- sys/Makefile.am | 10 +- sys/fbdev/Makefile.am | 14 ++ sys/fbdev/gstfbdevsink.c | 441 +++++++++++++++++++++++++++++++++++++++++++++++ sys/fbdev/gstfbdevsink.h | 78 +++++++++ 4 files changed, 541 insertions(+), 2 deletions(-) create mode 100644 sys/fbdev/Makefile.am create mode 100644 sys/fbdev/gstfbdevsink.c create mode 100644 sys/fbdev/gstfbdevsink.h (limited to 'sys') diff --git a/sys/Makefile.am b/sys/Makefile.am index 564771eb..4c5cf7f3 100644 --- a/sys/Makefile.am +++ b/sys/Makefile.am @@ -22,6 +22,12 @@ endif # CDROM_DIR= # endif +if USE_FBDEV +FBDEV_DIR=fbdev +else +FBDEV_DIR= +endif + if USE_OPENGL GL_DIR=glsink else @@ -40,6 +46,6 @@ else QT_DIR= endif -SUBDIRS = $(GL_DIR) $(DVB_DIR) $(VCD_DIR) $(QT_DIR) +SUBDIRS = $(FBDEV_DIR) $(GL_DIR) $(DVB_DIR) $(VCD_DIR) $(QT_DIR) -DIST_SUBDIRS = glsink dvb vcd qtwrapper +DIST_SUBDIRS = fbdev glsink dvb vcd qtwrapper diff --git a/sys/fbdev/Makefile.am b/sys/fbdev/Makefile.am new file mode 100644 index 00000000..2fd938d0 --- /dev/null +++ b/sys/fbdev/Makefile.am @@ -0,0 +1,14 @@ +plugin_LTLIBRARIES = libgstfbdevsink.la + +libgstfbdevsink_la_SOURCES = gstfbdevsink.c +libgstfbdevsink_la_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(LIBFBDEV_CFLAGS) +libgstfbdevsink_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + $(GST_BASE_LIBS) \ + $(LIBFBDEV_LIBS) +libgstfbdevsink_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = gstfbdevsink.h diff --git a/sys/fbdev/gstfbdevsink.c b/sys/fbdev/gstfbdevsink.c new file mode 100644 index 00000000..830e919a --- /dev/null +++ b/sys/fbdev/gstfbdevsink.c @@ -0,0 +1,441 @@ +/* GStreamer fbdev plugin + * Copyright (C) 2007 Sean D'Epagnier + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* currently the driver does not switch modes, instead uses current mode. + the video is centered and cropped if needed to fit onscreen. + Whatever bitdepth is set is used, and tested to work for 16, 24, 32 bits +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gstfbdevsink.h" + +/* elementfactory information */ +static const GstElementDetails gst_fbdevsink_details = +GST_ELEMENT_DETAILS ("fbdev video sink", + "Sink/Video", + "A linux framebuffer videosink", + "Sean D'Epagnier "); + +enum +{ + ARG_0, + ARG_DEVICE, +}; + +static void gst_fbdevsink_base_init (gpointer g_class); +static void gst_fbdevsink_class_init (GstFBDEVSinkClass * klass); +static void gst_fbdevsink_get_times (GstBaseSink * basesink, + GstBuffer * buffer, GstClockTime * start, GstClockTime * end); + +static gboolean gst_fbdevsink_setcaps (GstBaseSink * bsink, GstCaps * caps); + +static GstFlowReturn gst_fbdevsink_render (GstBaseSink * bsink, + GstBuffer * buff); +static gboolean gst_fbdevsink_start (GstBaseSink * bsink); +static gboolean gst_fbdevsink_stop (GstBaseSink * bsink); + +static void gst_fbdevsink_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_fbdevsink_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static GstStateChangeReturn +gst_fbdevsink_change_state (GstElement * element, GstStateChange transition); + +static GstCaps *gst_fbdevsink_getcaps (GstBaseSink * bsink); + +static GstVideoSinkClass *parent_class = NULL; + +#define GST_FBDEV_TEMPLATE_CAPS \ + GST_VIDEO_CAPS_RGB_15 \ + ";" GST_VIDEO_CAPS_RGB_16 \ + ";" GST_VIDEO_CAPS_BGR \ + ";" GST_VIDEO_CAPS_BGRx \ + ";" GST_VIDEO_CAPS_xBGR \ + ";" GST_VIDEO_CAPS_RGB \ + ";" GST_VIDEO_CAPS_RGBx \ + ";" GST_VIDEO_CAPS_xRGB \ + +static void +gst_fbdevsink_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_FBDEV_TEMPLATE_CAPS) + ); + + gst_element_class_set_details (element_class, &gst_fbdevsink_details); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); +} + + +static void +gst_fbdevsink_get_times (GstBaseSink * basesink, GstBuffer * buffer, + GstClockTime * start, GstClockTime * end) +{ + GstFBDEVSink *fbdevsink; + + fbdevsink = GST_FBDEVSINK (basesink); + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { + *start = GST_BUFFER_TIMESTAMP (buffer); + if (GST_BUFFER_DURATION_IS_VALID (buffer)) { + *end = *start + GST_BUFFER_DURATION (buffer); + } else { + if (fbdevsink->fps_n > 0) { + *end = *start + + gst_util_uint64_scale_int (GST_SECOND, fbdevsink->fps_d, + fbdevsink->fps_n); + } + } + } +} + +static uint32_t +swapendian (uint32_t val) +{ + return (val & 0xff) << 24 | (val & 0xff00) << 8 + | (val & 0xff0000) >> 8 | (val & 0xff000000) >> 24; +} + +static GstCaps * +gst_fbdevsink_getcaps (GstBaseSink * bsink) +{ + GstFBDEVSink *fbdevsink; + + fbdevsink = GST_FBDEVSINK (bsink); + + if (!fbdevsink->framebuffer) + return gst_caps_from_string (GST_FBDEV_TEMPLATE_CAPS); + + GstCaps *caps; + + uint32_t rmask = ((1 << fbdevsink->varinfo.red.length) - 1) + << fbdevsink->varinfo.red.offset; + uint32_t gmask = ((1 << fbdevsink->varinfo.green.length) - 1) + << fbdevsink->varinfo.green.offset; + uint32_t bmask = ((1 << fbdevsink->varinfo.blue.length) - 1) + << fbdevsink->varinfo.blue.offset; + + int endianness = 0; + + switch (fbdevsink->varinfo.bits_per_pixel) { + case 32: + /* swap endian of masks */ + rmask = swapendian (rmask); + gmask = swapendian (gmask); + bmask = swapendian (bmask); + endianness = 4321; + break; + case 24:{ + /* swap red and blue masks */ + uint32_t t = rmask; + + rmask = bmask; + bmask = t; + endianness = 4321; + break; + } + case 15: + case 16: + endianness = 1234; + break; + default: + /* other bit depths are not supported */ + g_warning ("unsupported bit depth: %d\n", + fbdevsink->varinfo.bits_per_pixel); + return NULL; + } + + /* replace all but width, height, and framerate */ + caps = gst_caps_from_string (GST_VIDEO_CAPS_RGB_15); + gst_caps_set_simple (caps, + "bpp", G_TYPE_INT, fbdevsink->varinfo.bits_per_pixel, + "depth", G_TYPE_INT, fbdevsink->varinfo.bits_per_pixel + - fbdevsink->varinfo.transp.length, + "endianness", G_TYPE_INT, endianness, + "red_mask", G_TYPE_INT, rmask, + "green_mask", G_TYPE_INT, gmask, "blue_mask", G_TYPE_INT, bmask, NULL); + + return caps; +} + +static gboolean +gst_fbdevsink_setcaps (GstBaseSink * bsink, GstCaps * vscapslist) +{ + GstFBDEVSink *fbdevsink; + GstStructure *structure; + + fbdevsink = GST_FBDEVSINK (bsink); + + structure = gst_caps_get_structure (vscapslist, 0); + + const GValue *fps; + + fps = gst_structure_get_value (structure, "framerate"); + fbdevsink->fps_n = gst_value_get_fraction_numerator (fps); + fbdevsink->fps_d = gst_value_get_fraction_denominator (fps); + + gst_structure_get_int (structure, "width", &fbdevsink->width); + gst_structure_get_int (structure, "height", &fbdevsink->height); + + /* calculate centering and scanlengths for the video */ + fbdevsink->bytespp = fbdevsink->fixinfo.line_length / fbdevsink->varinfo.xres; + + fbdevsink->cx = ((int) fbdevsink->varinfo.xres - fbdevsink->width) / 2; + if (fbdevsink->cx < 0) + fbdevsink->cx = 0; + + fbdevsink->cy = ((int) fbdevsink->varinfo.yres - fbdevsink->height) / 2; + if (fbdevsink->cy < 0) + fbdevsink->cy = 0; + + fbdevsink->linelen = fbdevsink->width * fbdevsink->bytespp; + if (fbdevsink->linelen > fbdevsink->fixinfo.line_length) + fbdevsink->linelen = fbdevsink->fixinfo.line_length; + + fbdevsink->lines = fbdevsink->height; + if (fbdevsink->lines > fbdevsink->varinfo.yres) + fbdevsink->lines = fbdevsink->varinfo.yres; + + return TRUE; +} + + +static GstFlowReturn +gst_fbdevsink_render (GstBaseSink * bsink, GstBuffer * buf) +{ + + GstFBDEVSink *fbdevsink; + + fbdevsink = GST_FBDEVSINK (bsink); + + /* optimization could remove this memcpy by allocating the buffer + in framebuffer memory, but would only work when xres matches + the video width */ + int i; + + for (i = 0; i < fbdevsink->lines; i++) + memcpy (fbdevsink->framebuffer + + (i + fbdevsink->cy) * fbdevsink->fixinfo.line_length + + fbdevsink->cx * fbdevsink->bytespp, + GST_BUFFER_DATA (buf) + i * fbdevsink->width * fbdevsink->bytespp, + fbdevsink->linelen); + + return GST_FLOW_OK; +} + +static gboolean +gst_fbdevsink_start (GstBaseSink * bsink) +{ + GstFBDEVSink *fbdevsink; + + fbdevsink = GST_FBDEVSINK (bsink); + + if (!fbdevsink->device) { + fbdevsink->device = g_strdup ("/dev/fb0"); + } + + fbdevsink->fd = open (fbdevsink->device, O_RDWR); + + if (fbdevsink->fd == -1) + return FALSE; + + /* get the fixed screen info */ + if (ioctl (fbdevsink->fd, FBIOGET_FSCREENINFO, &fbdevsink->fixinfo)) + return FALSE; + + /* get the variable screen info */ + if (ioctl (fbdevsink->fd, FBIOGET_VSCREENINFO, &fbdevsink->varinfo)) + return FALSE; + + /* map the framebuffer */ + fbdevsink->framebuffer = mmap (0, fbdevsink->fixinfo.smem_len, + PROT_WRITE, MAP_SHARED, fbdevsink->fd, 0); + if (fbdevsink->framebuffer == MAP_FAILED) + return FALSE; + + return TRUE; +} + +static gboolean +gst_fbdevsink_stop (GstBaseSink * bsink) +{ + GstFBDEVSink *fbdevsink; + + fbdevsink = GST_FBDEVSINK (bsink); + + if (munmap (fbdevsink->framebuffer, fbdevsink->fixinfo.smem_len)) + return FALSE; + + if (close (fbdevsink->fd)) + return FALSE; + + + return TRUE; +} + +static void +gst_fbdevsink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstFBDEVSink *fbdevsink; + + fbdevsink = GST_FBDEVSINK (object); + + switch (prop_id) { + case ARG_DEVICE:{ + g_free (fbdevsink->device); + fbdevsink->device = g_value_dup_string (value); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +gst_fbdevsink_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstFBDEVSink *fbdevsink; + + fbdevsink = GST_FBDEVSINK (object); + + switch (prop_id) { + case ARG_DEVICE:{ + g_value_set_string (value, fbdevsink->device); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_fbdevsink_change_state (GstElement * element, GstStateChange transition) +{ + GstFBDEVSink *fbdevsink; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + g_return_val_if_fail (GST_IS_FBDEVSINK (element), GST_STATE_CHANGE_FAILURE); + fbdevsink = GST_FBDEVSINK (element); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + default: + break; + } + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "fbdevsink", GST_RANK_NONE, + GST_TYPE_FBDEVSINK)) + return FALSE; + + return TRUE; +} + +static void +gst_fbdevsink_class_init (GstFBDEVSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstvs_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstvs_class = (GstBaseSinkClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = gst_fbdevsink_set_property; + gobject_class->get_property = gst_fbdevsink_get_property; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_fbdevsink_change_state); + + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE, + g_param_spec_string ("device", "device", + "The framebuffer device eg: /dev/fb0", NULL, G_PARAM_READWRITE)); + + gstvs_class->set_caps = GST_DEBUG_FUNCPTR (gst_fbdevsink_setcaps); + gstvs_class->get_caps = GST_DEBUG_FUNCPTR (gst_fbdevsink_getcaps); + gstvs_class->get_times = GST_DEBUG_FUNCPTR (gst_fbdevsink_get_times); + gstvs_class->preroll = GST_DEBUG_FUNCPTR (gst_fbdevsink_render); + gstvs_class->render = GST_DEBUG_FUNCPTR (gst_fbdevsink_render); + gstvs_class->start = GST_DEBUG_FUNCPTR (gst_fbdevsink_start); + gstvs_class->stop = GST_DEBUG_FUNCPTR (gst_fbdevsink_stop); + +} + +GType +gst_fbdevsink_get_type (void) +{ + static GType fbdevsink_type = 0; + + if (!fbdevsink_type) { + static const GTypeInfo fbdevsink_info = { + sizeof (GstFBDEVSinkClass), + gst_fbdevsink_base_init, + NULL, + (GClassInitFunc) gst_fbdevsink_class_init, + NULL, + NULL, + sizeof (GstFBDEVSink), + 0, + NULL + }; + + fbdevsink_type = + g_type_register_static (GST_TYPE_BASE_SINK, "GstFBDEVSink", + &fbdevsink_info, 0); + } + return fbdevsink_type; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "fbdevsink", + "linux framebuffer video sink", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/sys/fbdev/gstfbdevsink.h b/sys/fbdev/gstfbdevsink.h new file mode 100644 index 00000000..0e310e5c --- /dev/null +++ b/sys/fbdev/gstfbdevsink.h @@ -0,0 +1,78 @@ +/* GStreamer + * Copyright (C) 2007 Sean D'Epagnier sean@depagnier.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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_FBDEVSINK_H__ +#define __GST_FBDEVSINK_H__ + +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_FBDEVSINK \ + (gst_fbdevsink_get_type()) +#define GST_FBDEVSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FBDEVSINK,GstFBDEVSink)) +#define GST_FBDEVSINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FBDEVSINK,GstFBDEVSinkClass)) +#define GST_IS_FBDEVSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FBDEVSINK)) +#define GST_IS_FBDEVSINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FBDEVSINK)) + +typedef enum { + GST_FBDEVSINK_OPEN = (GST_ELEMENT_FLAG_LAST << 0), + + GST_FBDEVSINK_FLAG_LAST = (GST_ELEMENT_FLAG_LAST << 2), +} GstFBDEVSinkFlags; + +typedef struct _GstFBDEVSink GstFBDEVSink; +typedef struct _GstFBDEVSinkClass GstFBDEVSinkClass; + +struct _GstFBDEVSink { + GstVideoSink videosink; + + struct fb_fix_screeninfo fixinfo; + struct fb_var_screeninfo varinfo; + + int fd; + unsigned char *framebuffer; + + char *device; + + int width, height; + int cx, cy, linelen, lines, bytespp; + + int fps_n, fps_d; +}; + +struct _GstFBDEVSinkClass { + GstBaseSinkClass parent_class; + +}; + +GType gst_fbdevsink_get_type(void); + +G_END_DECLS + +#endif /* __GST_FBDEVSINK_H__ */ -- cgit v1.2.1