1 /* GStreamer fbdev plugin
2  * Copyright (C) 2007 Sean D'Epagnier <sean@depagnier.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /* currently the driver does not switch modes, instead uses current mode.
21    the video is centered and cropped if needed to fit onscreen.
22    Whatever bitdepth is set is used, and tested to work for 16, 24, 32 bits
23 */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <signal.h>
30 #include <string.h>
31 #include <sys/time.h>
32 #include <stdlib.h>
33 
34 #include <fcntl.h>
35 #include <sys/ioctl.h>
36 #include <sys/mman.h>
37 
38 #ifdef HAVE_UNISTD_H
39 #include <unistd.h>
40 #endif
41 
42 #ifdef HAVE_STDINT_H
43 #include <stdint.h>
44 #endif
45 
46 #include "gstfbdevsink.h"
47 
48 enum
49 {
50   ARG_0,
51   ARG_DEVICE
52 };
53 
54 #if 0
55 static void gst_fbdevsink_get_times (GstBaseSink * basesink,
56     GstBuffer * buffer, GstClockTime * start, GstClockTime * end);
57 #endif
58 
59 static GstFlowReturn gst_fbdevsink_show_frame (GstVideoSink * videosink,
60     GstBuffer * buff);
61 
62 static gboolean gst_fbdevsink_start (GstBaseSink * bsink);
63 static gboolean gst_fbdevsink_stop (GstBaseSink * bsink);
64 
65 static GstCaps *gst_fbdevsink_getcaps (GstBaseSink * bsink, GstCaps * filter);
66 static gboolean gst_fbdevsink_setcaps (GstBaseSink * bsink, GstCaps * caps);
67 
68 static void gst_fbdevsink_finalize (GObject * object);
69 static void gst_fbdevsink_set_property (GObject * object,
70     guint prop_id, const GValue * value, GParamSpec * pspec);
71 static void gst_fbdevsink_get_property (GObject * object,
72     guint prop_id, GValue * value, GParamSpec * pspec);
73 static GstStateChangeReturn gst_fbdevsink_change_state (GstElement * element,
74     GstStateChange transition);
75 
76 #define VIDEO_CAPS "{ RGB, BGR, BGRx, xBGR, RGB, RGBx, xRGB, RGB15, RGB16 }"
77 
78 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
79     GST_PAD_SINK,
80     GST_PAD_ALWAYS,
81     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_CAPS))
82     );
83 
84 #define parent_class gst_fbdevsink_parent_class
85 G_DEFINE_TYPE (GstFBDEVSink, gst_fbdevsink, GST_TYPE_VIDEO_SINK);
86 
87 static void
gst_fbdevsink_init(GstFBDEVSink * fbdevsink)88 gst_fbdevsink_init (GstFBDEVSink * fbdevsink)
89 {
90   /* nothing to do here yet */
91 }
92 
93 #if 0
94 static void
95 gst_fbdevsink_get_times (GstBaseSink * basesink, GstBuffer * buffer,
96     GstClockTime * start, GstClockTime * end)
97 {
98   GstFBDEVSink *fbdevsink;
99 
100   fbdevsink = GST_FBDEVSINK (basesink);
101 
102   if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
103     *start = GST_BUFFER_TIMESTAMP (buffer);
104     if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
105       *end = *start + GST_BUFFER_DURATION (buffer);
106     } else {
107       if (fbdevsink->fps_n > 0) {
108         *end = *start +
109             gst_util_uint64_scale_int (GST_SECOND, fbdevsink->fps_d,
110             fbdevsink->fps_n);
111       }
112     }
113   }
114 }
115 #endif
116 
117 static GstCaps *
gst_fbdevsink_getcaps(GstBaseSink * bsink,GstCaps * filter)118 gst_fbdevsink_getcaps (GstBaseSink * bsink, GstCaps * filter)
119 {
120   GstFBDEVSink *fbdevsink;
121   GstVideoFormat format;
122   GstCaps *caps;
123   uint32_t rmask;
124   uint32_t gmask;
125   uint32_t bmask;
126   uint32_t tmask;
127   int endianness, depth, bpp;
128 
129   fbdevsink = GST_FBDEVSINK (bsink);
130 
131   caps = gst_static_pad_template_get_caps (&sink_template);
132 
133   /* FIXME: locking */
134   if (!fbdevsink->framebuffer)
135     goto done;
136 
137   bpp = fbdevsink->varinfo.bits_per_pixel;
138 
139   rmask = ((1 << fbdevsink->varinfo.red.length) - 1)
140       << fbdevsink->varinfo.red.offset;
141   gmask = ((1 << fbdevsink->varinfo.green.length) - 1)
142       << fbdevsink->varinfo.green.offset;
143   bmask = ((1 << fbdevsink->varinfo.blue.length) - 1)
144       << fbdevsink->varinfo.blue.offset;
145   tmask = ((1 << fbdevsink->varinfo.transp.length) - 1)
146       << fbdevsink->varinfo.transp.offset;
147 
148   depth = fbdevsink->varinfo.red.length + fbdevsink->varinfo.green.length
149       + fbdevsink->varinfo.blue.length;
150 
151   switch (fbdevsink->varinfo.bits_per_pixel) {
152     case 32:
153       /* swap endianness of masks */
154       rmask = GUINT32_SWAP_LE_BE (rmask);
155       gmask = GUINT32_SWAP_LE_BE (gmask);
156       bmask = GUINT32_SWAP_LE_BE (bmask);
157       tmask = GUINT32_SWAP_LE_BE (tmask);
158       depth += fbdevsink->varinfo.transp.length;
159       endianness = G_BIG_ENDIAN;
160       break;
161     case 24:{
162       /* swap red and blue masks */
163       tmask = rmask;
164       rmask = bmask;
165       bmask = tmask;
166       tmask = 0;
167       endianness = G_BIG_ENDIAN;
168       break;
169     }
170     case 15:
171     case 16:
172       tmask = 0;
173       endianness = G_LITTLE_ENDIAN;
174       break;
175     default:
176       goto unsupported_bpp;
177   }
178 
179   format = gst_video_format_from_masks (depth, bpp, endianness, rmask, gmask,
180       bmask, tmask);
181 
182   if (format == GST_VIDEO_FORMAT_UNKNOWN)
183     goto unknown_format;
184 
185   caps = gst_caps_make_writable (caps);
186   gst_caps_set_simple (caps, "format", G_TYPE_STRING,
187       gst_video_format_to_string (format), NULL);
188 
189 done:
190 
191   if (filter != NULL) {
192     GstCaps *icaps;
193 
194     icaps = gst_caps_intersect (caps, filter);
195     gst_caps_unref (caps);
196     caps = icaps;
197   }
198 
199   return caps;
200 
201 /* ERRORS */
202 unsupported_bpp:
203   {
204     GST_WARNING_OBJECT (bsink, "unsupported bit depth: %d", bpp);
205     return NULL;
206   }
207 unknown_format:
208   {
209     GST_WARNING_OBJECT (bsink, "could not map fbdev format to GstVideoFormat: "
210         "depth=%u, bpp=%u, endianness=%u, rmask=0x%08x, gmask=0x%08x, "
211         "bmask=0x%08x, tmask=0x%08x", depth, bpp, endianness, rmask, gmask,
212         bmask, tmask);
213     return NULL;
214   }
215 }
216 
217 static gboolean
gst_fbdevsink_setcaps(GstBaseSink * bsink,GstCaps * vscapslist)218 gst_fbdevsink_setcaps (GstBaseSink * bsink, GstCaps * vscapslist)
219 {
220   GstFBDEVSink *fbdevsink;
221   GstStructure *structure;
222   const GValue *fps;
223 
224   fbdevsink = GST_FBDEVSINK (bsink);
225 
226   structure = gst_caps_get_structure (vscapslist, 0);
227 
228   fps = gst_structure_get_value (structure, "framerate");
229   fbdevsink->fps_n = gst_value_get_fraction_numerator (fps);
230   fbdevsink->fps_d = gst_value_get_fraction_denominator (fps);
231 
232   gst_structure_get_int (structure, "width", &fbdevsink->width);
233   gst_structure_get_int (structure, "height", &fbdevsink->height);
234 
235   /* calculate centering and scanlengths for the video */
236   fbdevsink->bytespp =
237       fbdevsink->fixinfo.line_length / fbdevsink->varinfo.xres_virtual;
238 
239   fbdevsink->cx = ((int) fbdevsink->varinfo.xres - fbdevsink->width) / 2;
240   if (fbdevsink->cx < 0)
241     fbdevsink->cx = 0;
242 
243   fbdevsink->cy = ((int) fbdevsink->varinfo.yres - fbdevsink->height) / 2;
244   if (fbdevsink->cy < 0)
245     fbdevsink->cy = 0;
246 
247   fbdevsink->linelen = fbdevsink->width * fbdevsink->bytespp;
248   if (fbdevsink->linelen > fbdevsink->fixinfo.line_length)
249     fbdevsink->linelen = fbdevsink->fixinfo.line_length;
250 
251   fbdevsink->lines = fbdevsink->height;
252   if (fbdevsink->lines > fbdevsink->varinfo.yres)
253     fbdevsink->lines = fbdevsink->varinfo.yres;
254 
255   return TRUE;
256 }
257 
258 
259 static GstFlowReturn
gst_fbdevsink_show_frame(GstVideoSink * videosink,GstBuffer * buf)260 gst_fbdevsink_show_frame (GstVideoSink * videosink, GstBuffer * buf)
261 {
262 
263   GstFBDEVSink *fbdevsink;
264   GstMapInfo map;
265   int i;
266 
267   fbdevsink = GST_FBDEVSINK (videosink);
268 
269   /* optimization could remove this memcpy by allocating the buffer
270      in framebuffer memory, but would only work when xres matches
271      the video width */
272   if (!gst_buffer_map (buf, &map, GST_MAP_READ))
273     return GST_FLOW_ERROR;
274 
275   for (i = 0; i < fbdevsink->lines; i++) {
276     memcpy (fbdevsink->framebuffer
277         + (i + fbdevsink->cy) * fbdevsink->fixinfo.line_length
278         + fbdevsink->cx * fbdevsink->bytespp,
279         map.data + i * fbdevsink->width * fbdevsink->bytespp,
280         fbdevsink->linelen);
281   }
282 
283   gst_buffer_unmap (buf, &map);
284 
285   return GST_FLOW_OK;
286 }
287 
288 static gboolean
gst_fbdevsink_start(GstBaseSink * bsink)289 gst_fbdevsink_start (GstBaseSink * bsink)
290 {
291   GstFBDEVSink *fbdevsink;
292 
293   fbdevsink = GST_FBDEVSINK (bsink);
294 
295   if (!fbdevsink->device) {
296     fbdevsink->device = g_strdup ("/dev/fb0");
297   }
298 
299   fbdevsink->fd = open (fbdevsink->device, O_RDWR);
300 
301   if (fbdevsink->fd == -1)
302     return FALSE;
303 
304   /* get the fixed screen info */
305   if (ioctl (fbdevsink->fd, FBIOGET_FSCREENINFO, &fbdevsink->fixinfo))
306     return FALSE;
307 
308   /* get the variable screen info */
309   if (ioctl (fbdevsink->fd, FBIOGET_VSCREENINFO, &fbdevsink->varinfo))
310     return FALSE;
311 
312   /* map the framebuffer */
313   fbdevsink->framebuffer = mmap (0, fbdevsink->fixinfo.smem_len,
314       PROT_WRITE, MAP_SHARED, fbdevsink->fd, 0);
315   if (fbdevsink->framebuffer == MAP_FAILED)
316     return FALSE;
317 
318   return TRUE;
319 }
320 
321 static gboolean
gst_fbdevsink_stop(GstBaseSink * bsink)322 gst_fbdevsink_stop (GstBaseSink * bsink)
323 {
324   GstFBDEVSink *fbdevsink;
325 
326   fbdevsink = GST_FBDEVSINK (bsink);
327 
328   if (munmap (fbdevsink->framebuffer, fbdevsink->fixinfo.smem_len))
329     return FALSE;
330 
331   if (close (fbdevsink->fd))
332     return FALSE;
333 
334 
335   return TRUE;
336 }
337 
338 static void
gst_fbdevsink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)339 gst_fbdevsink_set_property (GObject * object, guint prop_id,
340     const GValue * value, GParamSpec * pspec)
341 {
342   GstFBDEVSink *fbdevsink;
343 
344   fbdevsink = GST_FBDEVSINK (object);
345 
346   switch (prop_id) {
347     case ARG_DEVICE:{
348       g_free (fbdevsink->device);
349       fbdevsink->device = g_value_dup_string (value);
350       break;
351     }
352     default:
353       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
354       break;
355   }
356 }
357 
358 
359 static void
gst_fbdevsink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)360 gst_fbdevsink_get_property (GObject * object, guint prop_id, GValue * value,
361     GParamSpec * pspec)
362 {
363   GstFBDEVSink *fbdevsink;
364 
365   fbdevsink = GST_FBDEVSINK (object);
366 
367   switch (prop_id) {
368     case ARG_DEVICE:{
369       g_value_set_string (value, fbdevsink->device);
370       break;
371     }
372     default:
373       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
374       break;
375   }
376 }
377 
378 static GstStateChangeReturn
gst_fbdevsink_change_state(GstElement * element,GstStateChange transition)379 gst_fbdevsink_change_state (GstElement * element, GstStateChange transition)
380 {
381   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
382 
383   g_return_val_if_fail (GST_IS_FBDEVSINK (element), GST_STATE_CHANGE_FAILURE);
384 
385   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
386 
387   switch (transition) {
388     default:
389       break;
390   }
391   return ret;
392 }
393 
394 static gboolean
plugin_init(GstPlugin * plugin)395 plugin_init (GstPlugin * plugin)
396 {
397   if (!gst_element_register (plugin, "fbdevsink", GST_RANK_NONE,
398           GST_TYPE_FBDEVSINK))
399     return FALSE;
400 
401   return TRUE;
402 }
403 
404 static void
gst_fbdevsink_class_init(GstFBDEVSinkClass * klass)405 gst_fbdevsink_class_init (GstFBDEVSinkClass * klass)
406 {
407   GObjectClass *gobject_class;
408   GstElementClass *gstelement_class;
409   GstBaseSinkClass *basesink_class;
410   GstVideoSinkClass *videosink_class;
411 
412   gobject_class = (GObjectClass *) klass;
413   gstelement_class = (GstElementClass *) klass;
414   basesink_class = (GstBaseSinkClass *) klass;
415   videosink_class = (GstVideoSinkClass *) klass;
416 
417   gobject_class->set_property = gst_fbdevsink_set_property;
418   gobject_class->get_property = gst_fbdevsink_get_property;
419   gobject_class->finalize = gst_fbdevsink_finalize;
420 
421   gstelement_class->change_state =
422       GST_DEBUG_FUNCPTR (gst_fbdevsink_change_state);
423 
424   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
425       g_param_spec_string ("device", "device",
426           "The framebuffer device eg: /dev/fb0", NULL, G_PARAM_READWRITE));
427 
428   basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_fbdevsink_setcaps);
429   basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_fbdevsink_getcaps);
430 #if 0
431   basesink_class->get_times = GST_DEBUG_FUNCPTR (gst_fbdevsink_get_times);
432 #endif
433   basesink_class->start = GST_DEBUG_FUNCPTR (gst_fbdevsink_start);
434   basesink_class->stop = GST_DEBUG_FUNCPTR (gst_fbdevsink_stop);
435 
436   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_fbdevsink_show_frame);
437 
438   gst_element_class_set_static_metadata (gstelement_class, "fbdev video sink",
439       "Sink/Video", "Linux framebuffer videosink",
440       "Sean D'Epagnier <sean@depagnier.com>");
441 
442   gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
443 }
444 
445 static void
gst_fbdevsink_finalize(GObject * object)446 gst_fbdevsink_finalize (GObject * object)
447 {
448   GstFBDEVSink *fbdevsink = GST_FBDEVSINK (object);
449 
450   g_free (fbdevsink->device);
451 
452   G_OBJECT_CLASS (parent_class)->finalize (object);
453 }
454 
455 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
456     GST_VERSION_MINOR,
457     fbdevsink,
458     "Linux framebuffer video sink",
459     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
460