Merging gst-plugins-bad
[platform/upstream/gstreamer.git] / sys / fbdev / gstfbdevsink.c
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 GST_ELEMENT_REGISTER_DEFINE (fbdevsink, "fbdevsink", GST_RANK_NONE,
87     GST_TYPE_FBDEVSINK);
88
89 static void
90 gst_fbdevsink_init (GstFBDEVSink * fbdevsink)
91 {
92   /* nothing to do here yet */
93 }
94
95 #if 0
96 static void
97 gst_fbdevsink_get_times (GstBaseSink * basesink, GstBuffer * buffer,
98     GstClockTime * start, GstClockTime * end)
99 {
100   GstFBDEVSink *fbdevsink;
101
102   fbdevsink = GST_FBDEVSINK (basesink);
103
104   if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
105     *start = GST_BUFFER_TIMESTAMP (buffer);
106     if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
107       *end = *start + GST_BUFFER_DURATION (buffer);
108     } else {
109       if (fbdevsink->fps_n > 0) {
110         *end = *start +
111             gst_util_uint64_scale_int (GST_SECOND, fbdevsink->fps_d,
112             fbdevsink->fps_n);
113       }
114     }
115   }
116 }
117 #endif
118
119 static GstCaps *
120 gst_fbdevsink_getcaps (GstBaseSink * bsink, GstCaps * filter)
121 {
122   GstFBDEVSink *fbdevsink;
123   GstVideoFormat format;
124   GstCaps *caps;
125   uint32_t rmask;
126   uint32_t gmask;
127   uint32_t bmask;
128   uint32_t tmask;
129   int endianness, depth, bpp;
130
131   fbdevsink = GST_FBDEVSINK (bsink);
132
133   caps = gst_static_pad_template_get_caps (&sink_template);
134
135   /* FIXME: locking */
136   if (!fbdevsink->framebuffer)
137     goto done;
138
139   bpp = fbdevsink->varinfo.bits_per_pixel;
140
141   rmask = ((1 << fbdevsink->varinfo.red.length) - 1)
142       << fbdevsink->varinfo.red.offset;
143   gmask = ((1 << fbdevsink->varinfo.green.length) - 1)
144       << fbdevsink->varinfo.green.offset;
145   bmask = ((1 << fbdevsink->varinfo.blue.length) - 1)
146       << fbdevsink->varinfo.blue.offset;
147   tmask = ((1 << fbdevsink->varinfo.transp.length) - 1)
148       << fbdevsink->varinfo.transp.offset;
149
150   depth = fbdevsink->varinfo.red.length + fbdevsink->varinfo.green.length
151       + fbdevsink->varinfo.blue.length;
152
153   switch (fbdevsink->varinfo.bits_per_pixel) {
154     case 32:
155       /* swap endianness of masks */
156       rmask = GUINT32_SWAP_LE_BE (rmask);
157       gmask = GUINT32_SWAP_LE_BE (gmask);
158       bmask = GUINT32_SWAP_LE_BE (bmask);
159       tmask = GUINT32_SWAP_LE_BE (tmask);
160       depth += fbdevsink->varinfo.transp.length;
161       endianness = G_BIG_ENDIAN;
162       break;
163     case 24:{
164       /* swap red and blue masks */
165       tmask = rmask;
166       rmask = bmask;
167       bmask = tmask;
168       tmask = 0;
169       endianness = G_BIG_ENDIAN;
170       break;
171     }
172     case 15:
173     case 16:
174       tmask = 0;
175       endianness = G_LITTLE_ENDIAN;
176       break;
177     default:
178       goto unsupported_bpp;
179   }
180
181   format = gst_video_format_from_masks (depth, bpp, endianness, rmask, gmask,
182       bmask, tmask);
183
184   if (format == GST_VIDEO_FORMAT_UNKNOWN)
185     goto unknown_format;
186
187   caps = gst_caps_make_writable (caps);
188   gst_caps_set_simple (caps, "format", G_TYPE_STRING,
189       gst_video_format_to_string (format), NULL);
190
191 done:
192
193   if (filter != NULL) {
194     GstCaps *icaps;
195
196     icaps = gst_caps_intersect (caps, filter);
197     gst_caps_unref (caps);
198     caps = icaps;
199   }
200
201   return caps;
202
203 /* ERRORS */
204 unsupported_bpp:
205   {
206     GST_WARNING_OBJECT (bsink, "unsupported bit depth: %d", bpp);
207     return NULL;
208   }
209 unknown_format:
210   {
211     GST_WARNING_OBJECT (bsink, "could not map fbdev format to GstVideoFormat: "
212         "depth=%u, bpp=%u, endianness=%u, rmask=0x%08x, gmask=0x%08x, "
213         "bmask=0x%08x, tmask=0x%08x", depth, bpp, endianness, rmask, gmask,
214         bmask, tmask);
215     return NULL;
216   }
217 }
218
219 static gboolean
220 gst_fbdevsink_setcaps (GstBaseSink * bsink, GstCaps * vscapslist)
221 {
222   GstFBDEVSink *fbdevsink;
223   GstStructure *structure;
224   const GValue *fps;
225
226   fbdevsink = GST_FBDEVSINK (bsink);
227
228   structure = gst_caps_get_structure (vscapslist, 0);
229
230   fps = gst_structure_get_value (structure, "framerate");
231   fbdevsink->fps_n = gst_value_get_fraction_numerator (fps);
232   fbdevsink->fps_d = gst_value_get_fraction_denominator (fps);
233
234   gst_structure_get_int (structure, "width", &fbdevsink->width);
235   gst_structure_get_int (structure, "height", &fbdevsink->height);
236
237   /* calculate centering and scanlengths for the video */
238   fbdevsink->bytespp =
239       fbdevsink->fixinfo.line_length / fbdevsink->varinfo.xres_virtual;
240
241   fbdevsink->cx = ((int) fbdevsink->varinfo.xres - fbdevsink->width) / 2;
242   if (fbdevsink->cx < 0)
243     fbdevsink->cx = 0;
244
245   fbdevsink->cy = ((int) fbdevsink->varinfo.yres - fbdevsink->height) / 2;
246   if (fbdevsink->cy < 0)
247     fbdevsink->cy = 0;
248
249   fbdevsink->linelen = fbdevsink->width * fbdevsink->bytespp;
250   if (fbdevsink->linelen > fbdevsink->fixinfo.line_length)
251     fbdevsink->linelen = fbdevsink->fixinfo.line_length;
252
253   fbdevsink->lines = fbdevsink->height;
254   if (fbdevsink->lines > fbdevsink->varinfo.yres)
255     fbdevsink->lines = fbdevsink->varinfo.yres;
256
257   return TRUE;
258 }
259
260
261 static GstFlowReturn
262 gst_fbdevsink_show_frame (GstVideoSink * videosink, GstBuffer * buf)
263 {
264
265   GstFBDEVSink *fbdevsink;
266   GstMapInfo map;
267   int i;
268
269   fbdevsink = GST_FBDEVSINK (videosink);
270
271   /* optimization could remove this memcpy by allocating the buffer
272      in framebuffer memory, but would only work when xres matches
273      the video width */
274   if (!gst_buffer_map (buf, &map, GST_MAP_READ))
275     return GST_FLOW_ERROR;
276
277   for (i = 0; i < fbdevsink->lines; i++) {
278     memcpy (fbdevsink->framebuffer
279         + (i + fbdevsink->cy) * fbdevsink->fixinfo.line_length
280         + fbdevsink->cx * fbdevsink->bytespp,
281         map.data + i * fbdevsink->width * fbdevsink->bytespp,
282         fbdevsink->linelen);
283   }
284
285   gst_buffer_unmap (buf, &map);
286
287   return GST_FLOW_OK;
288 }
289
290 static gboolean
291 gst_fbdevsink_start (GstBaseSink * bsink)
292 {
293   GstFBDEVSink *fbdevsink;
294
295   fbdevsink = GST_FBDEVSINK (bsink);
296
297   if (!fbdevsink->device) {
298     fbdevsink->device = g_strdup ("/dev/fb0");
299   }
300
301   fbdevsink->fd = open (fbdevsink->device, O_RDWR);
302
303   if (fbdevsink->fd == -1)
304     return FALSE;
305
306   /* get the fixed screen info */
307   if (ioctl (fbdevsink->fd, FBIOGET_FSCREENINFO, &fbdevsink->fixinfo))
308     return FALSE;
309
310   /* get the variable screen info */
311   if (ioctl (fbdevsink->fd, FBIOGET_VSCREENINFO, &fbdevsink->varinfo))
312     return FALSE;
313
314   /* map the framebuffer */
315   fbdevsink->framebuffer = mmap (0, fbdevsink->fixinfo.smem_len,
316       PROT_WRITE, MAP_SHARED, fbdevsink->fd, 0);
317   if (fbdevsink->framebuffer == MAP_FAILED)
318     return FALSE;
319
320   return TRUE;
321 }
322
323 static gboolean
324 gst_fbdevsink_stop (GstBaseSink * bsink)
325 {
326   GstFBDEVSink *fbdevsink;
327
328   fbdevsink = GST_FBDEVSINK (bsink);
329
330   if (munmap (fbdevsink->framebuffer, fbdevsink->fixinfo.smem_len))
331     return FALSE;
332
333   if (close (fbdevsink->fd))
334     return FALSE;
335
336
337   return TRUE;
338 }
339
340 static void
341 gst_fbdevsink_set_property (GObject * object, guint prop_id,
342     const GValue * value, GParamSpec * pspec)
343 {
344   GstFBDEVSink *fbdevsink;
345
346   fbdevsink = GST_FBDEVSINK (object);
347
348   switch (prop_id) {
349     case ARG_DEVICE:{
350       g_free (fbdevsink->device);
351       fbdevsink->device = g_value_dup_string (value);
352       break;
353     }
354     default:
355       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
356       break;
357   }
358 }
359
360
361 static void
362 gst_fbdevsink_get_property (GObject * object, guint prop_id, GValue * value,
363     GParamSpec * pspec)
364 {
365   GstFBDEVSink *fbdevsink;
366
367   fbdevsink = GST_FBDEVSINK (object);
368
369   switch (prop_id) {
370     case ARG_DEVICE:{
371       g_value_set_string (value, fbdevsink->device);
372       break;
373     }
374     default:
375       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
376       break;
377   }
378 }
379
380 static GstStateChangeReturn
381 gst_fbdevsink_change_state (GstElement * element, GstStateChange transition)
382 {
383   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
384
385   g_return_val_if_fail (GST_IS_FBDEVSINK (element), GST_STATE_CHANGE_FAILURE);
386
387   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
388
389   switch (transition) {
390     default:
391       break;
392   }
393   return ret;
394 }
395
396 static gboolean
397 plugin_init (GstPlugin * plugin)
398 {
399   return GST_ELEMENT_REGISTER (fbdevsink, plugin);
400 }
401
402 static void
403 gst_fbdevsink_class_init (GstFBDEVSinkClass * klass)
404 {
405   GObjectClass *gobject_class;
406   GstElementClass *gstelement_class;
407   GstBaseSinkClass *basesink_class;
408   GstVideoSinkClass *videosink_class;
409
410   gobject_class = (GObjectClass *) klass;
411   gstelement_class = (GstElementClass *) klass;
412   basesink_class = (GstBaseSinkClass *) klass;
413   videosink_class = (GstVideoSinkClass *) klass;
414
415   gobject_class->set_property = gst_fbdevsink_set_property;
416   gobject_class->get_property = gst_fbdevsink_get_property;
417   gobject_class->finalize = gst_fbdevsink_finalize;
418
419   gstelement_class->change_state =
420       GST_DEBUG_FUNCPTR (gst_fbdevsink_change_state);
421
422   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
423       g_param_spec_string ("device", "device",
424           "The framebuffer device eg: /dev/fb0", NULL, G_PARAM_READWRITE));
425
426   basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_fbdevsink_setcaps);
427   basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_fbdevsink_getcaps);
428 #if 0
429   basesink_class->get_times = GST_DEBUG_FUNCPTR (gst_fbdevsink_get_times);
430 #endif
431   basesink_class->start = GST_DEBUG_FUNCPTR (gst_fbdevsink_start);
432   basesink_class->stop = GST_DEBUG_FUNCPTR (gst_fbdevsink_stop);
433
434   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_fbdevsink_show_frame);
435
436   gst_element_class_set_static_metadata (gstelement_class, "fbdev video sink",
437       "Sink/Video", "Linux framebuffer videosink",
438       "Sean D'Epagnier <sean@depagnier.com>");
439
440   gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
441 }
442
443 static void
444 gst_fbdevsink_finalize (GObject * object)
445 {
446   GstFBDEVSink *fbdevsink = GST_FBDEVSINK (object);
447
448   g_free (fbdevsink->device);
449
450   G_OBJECT_CLASS (parent_class)->finalize (object);
451 }
452
453 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
454     GST_VERSION_MINOR,
455     fbdevsink,
456     "Linux framebuffer video sink",
457     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)