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