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