Rename all occurrences of "fbdev" into "am7xxx"
[gst-am7xxxsink.git] / src / gstam7xxxsink.c
1 /* GStreamer am7xxx 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 "gstam7xxxsink.h"
41
42 enum
43 {
44   ARG_0,
45   ARG_DEVICE
46 };
47
48 #if 0
49 static void gst_am7xxxsink_get_times (GstBaseSink * basesink,
50     GstBuffer * buffer, GstClockTime * start, GstClockTime * end);
51 #endif
52
53 static GstFlowReturn gst_am7xxxsink_show_frame (GstVideoSink * videosink,
54     GstBuffer * buff);
55
56 static gboolean gst_am7xxxsink_start (GstBaseSink * bsink);
57 static gboolean gst_am7xxxsink_stop (GstBaseSink * bsink);
58
59 static GstCaps *gst_am7xxxsink_getcaps (GstBaseSink * bsink, GstCaps * filter);
60 static gboolean gst_am7xxxsink_setcaps (GstBaseSink * bsink, GstCaps * caps);
61
62 static void gst_am7xxxsink_finalize (GObject * object);
63 static void gst_am7xxxsink_set_property (GObject * object,
64     guint prop_id, const GValue * value, GParamSpec * pspec);
65 static void gst_am7xxxsink_get_property (GObject * object,
66     guint prop_id, GValue * value, GParamSpec * pspec);
67 static GstStateChangeReturn gst_am7xxxsink_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_am7xxxsink_parent_class
79 G_DEFINE_TYPE (GstAM7XXXSink, gst_am7xxxsink, GST_TYPE_VIDEO_SINK);
80
81 static void
82 gst_am7xxxsink_init (GstAM7XXXSink * am7xxxsink)
83 {
84   /* nothing to do here yet */
85 }
86
87 #if 0
88 static void
89 gst_am7xxxsink_get_times (GstBaseSink * basesink, GstBuffer * buffer,
90     GstClockTime * start, GstClockTime * end)
91 {
92   GstAM7XXXSink *am7xxxsink;
93
94   am7xxxsink = GST_AM7XXXSINK (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 (am7xxxsink->fps_n > 0) {
102         *end = *start +
103             gst_util_uint64_scale_int (GST_SECOND, am7xxxsink->fps_d,
104             am7xxxsink->fps_n);
105       }
106     }
107   }
108 }
109 #endif
110
111 static GstCaps *
112 gst_am7xxxsink_getcaps (GstBaseSink * bsink, GstCaps * filter)
113 {
114   GstAM7XXXSink *am7xxxsink;
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   am7xxxsink = GST_AM7XXXSINK (bsink);
124
125   caps = gst_static_pad_template_get_caps (&sink_template);
126
127   /* FIXME: locking */
128   if (!am7xxxsink->framebuffer)
129     goto done;
130
131   bpp = am7xxxsink->varinfo.bits_per_pixel;
132
133   rmask = ((1 << am7xxxsink->varinfo.red.length) - 1)
134       << am7xxxsink->varinfo.red.offset;
135   gmask = ((1 << am7xxxsink->varinfo.green.length) - 1)
136       << am7xxxsink->varinfo.green.offset;
137   bmask = ((1 << am7xxxsink->varinfo.blue.length) - 1)
138       << am7xxxsink->varinfo.blue.offset;
139   tmask = ((1 << am7xxxsink->varinfo.transp.length) - 1)
140       << am7xxxsink->varinfo.transp.offset;
141
142   depth = am7xxxsink->varinfo.red.length + am7xxxsink->varinfo.green.length
143       + am7xxxsink->varinfo.blue.length;
144
145   switch (am7xxxsink->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 += am7xxxsink->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 am7xxx 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_am7xxxsink_setcaps (GstBaseSink * bsink, GstCaps * vscapslist)
213 {
214   GstAM7XXXSink *am7xxxsink;
215   GstStructure *structure;
216   const GValue *fps;
217
218   am7xxxsink = GST_AM7XXXSINK (bsink);
219
220   structure = gst_caps_get_structure (vscapslist, 0);
221
222   fps = gst_structure_get_value (structure, "framerate");
223   am7xxxsink->fps_n = gst_value_get_fraction_numerator (fps);
224   am7xxxsink->fps_d = gst_value_get_fraction_denominator (fps);
225
226   gst_structure_get_int (structure, "width", &am7xxxsink->width);
227   gst_structure_get_int (structure, "height", &am7xxxsink->height);
228
229   /* calculate centering and scanlengths for the video */
230   am7xxxsink->bytespp = am7xxxsink->fixinfo.line_length / am7xxxsink->varinfo.xres;
231
232   am7xxxsink->cx = ((int) am7xxxsink->varinfo.xres - am7xxxsink->width) / 2;
233   if (am7xxxsink->cx < 0)
234     am7xxxsink->cx = 0;
235
236   am7xxxsink->cy = ((int) am7xxxsink->varinfo.yres - am7xxxsink->height) / 2;
237   if (am7xxxsink->cy < 0)
238     am7xxxsink->cy = 0;
239
240   am7xxxsink->linelen = am7xxxsink->width * am7xxxsink->bytespp;
241   if (am7xxxsink->linelen > am7xxxsink->fixinfo.line_length)
242     am7xxxsink->linelen = am7xxxsink->fixinfo.line_length;
243
244   am7xxxsink->lines = am7xxxsink->height;
245   if (am7xxxsink->lines > am7xxxsink->varinfo.yres)
246     am7xxxsink->lines = am7xxxsink->varinfo.yres;
247
248   return TRUE;
249 }
250
251
252 static GstFlowReturn
253 gst_am7xxxsink_show_frame (GstVideoSink * videosink, GstBuffer * buf)
254 {
255
256   GstAM7XXXSink *am7xxxsink;
257   GstMapInfo map;
258   int i;
259
260   am7xxxsink = GST_AM7XXXSINK (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 < am7xxxsink->lines; i++) {
269     memcpy (am7xxxsink->framebuffer
270         + (i + am7xxxsink->cy) * am7xxxsink->fixinfo.line_length
271         + am7xxxsink->cx * am7xxxsink->bytespp,
272         map.data + i * am7xxxsink->width * am7xxxsink->bytespp,
273         am7xxxsink->linelen);
274   }
275
276   gst_buffer_unmap (buf, &map);
277
278   return GST_FLOW_OK;
279 }
280
281 static gboolean
282 gst_am7xxxsink_start (GstBaseSink * bsink)
283 {
284   GstAM7XXXSink *am7xxxsink;
285
286   am7xxxsink = GST_AM7XXXSINK (bsink);
287
288   if (!am7xxxsink->device) {
289     am7xxxsink->device = g_strdup ("/dev/fb0");
290   }
291
292   am7xxxsink->fd = open (am7xxxsink->device, O_RDWR);
293
294   if (am7xxxsink->fd == -1)
295     return FALSE;
296
297   /* get the fixed screen info */
298   if (ioctl (am7xxxsink->fd, FBIOGET_FSCREENINFO, &am7xxxsink->fixinfo))
299     return FALSE;
300
301   /* get the variable screen info */
302   if (ioctl (am7xxxsink->fd, FBIOGET_VSCREENINFO, &am7xxxsink->varinfo))
303     return FALSE;
304
305   /* map the framebuffer */
306   am7xxxsink->framebuffer = mmap (0, am7xxxsink->fixinfo.smem_len,
307       PROT_WRITE, MAP_SHARED, am7xxxsink->fd, 0);
308   if (am7xxxsink->framebuffer == MAP_FAILED)
309     return FALSE;
310
311   return TRUE;
312 }
313
314 static gboolean
315 gst_am7xxxsink_stop (GstBaseSink * bsink)
316 {
317   GstAM7XXXSink *am7xxxsink;
318
319   am7xxxsink = GST_AM7XXXSINK (bsink);
320
321   if (munmap (am7xxxsink->framebuffer, am7xxxsink->fixinfo.smem_len))
322     return FALSE;
323
324   if (close (am7xxxsink->fd))
325     return FALSE;
326
327
328   return TRUE;
329 }
330
331 static void
332 gst_am7xxxsink_set_property (GObject * object, guint prop_id,
333     const GValue * value, GParamSpec * pspec)
334 {
335   GstAM7XXXSink *am7xxxsink;
336
337   am7xxxsink = GST_AM7XXXSINK (object);
338
339   switch (prop_id) {
340     case ARG_DEVICE:{
341       g_free (am7xxxsink->device);
342       am7xxxsink->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_am7xxxsink_get_property (GObject * object, guint prop_id, GValue * value,
354     GParamSpec * pspec)
355 {
356   GstAM7XXXSink *am7xxxsink;
357
358   am7xxxsink = GST_AM7XXXSINK (object);
359
360   switch (prop_id) {
361     case ARG_DEVICE:{
362       g_value_set_string (value, am7xxxsink->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_am7xxxsink_change_state (GstElement * element, GstStateChange transition)
373 {
374   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
375
376   g_return_val_if_fail (GST_IS_AM7XXXSINK (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, "am7xxxsink", GST_RANK_NONE,
391           GST_TYPE_AM7XXXSINK))
392     return FALSE;
393
394   return TRUE;
395 }
396
397 static void
398 gst_am7xxxsink_class_init (GstAM7XXXSinkClass * 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_am7xxxsink_set_property;
411   gobject_class->get_property = gst_am7xxxsink_get_property;
412   gobject_class->finalize = gst_am7xxxsink_finalize;
413
414   gstelement_class->change_state =
415       GST_DEBUG_FUNCPTR (gst_am7xxxsink_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_am7xxxsink_setcaps);
422   basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_am7xxxsink_getcaps);
423 #if 0
424   basesink_class->get_times = GST_DEBUG_FUNCPTR (gst_am7xxxsink_get_times);
425 #endif
426   basesink_class->start = GST_DEBUG_FUNCPTR (gst_am7xxxsink_start);
427   basesink_class->stop = GST_DEBUG_FUNCPTR (gst_am7xxxsink_stop);
428
429   videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_am7xxxsink_show_frame);
430
431   gst_element_class_set_static_metadata (gstelement_class, "am7xxx 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_am7xxxsink_finalize (GObject * object)
441 {
442   GstAM7XXXSink *am7xxxsink = GST_AM7XXXSINK (object);
443
444   g_free (am7xxxsink->device);
445
446   G_OBJECT_CLASS (parent_class)->finalize (object);
447 }
448
449 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
450     GST_VERSION_MINOR,
451     am7xxxsink,
452     "Linux framebuffer video sink",
453     plugin_init, VERSION, "LGPL", "gst-am7xxxsink", "http://ao2.it")