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
gst_fbdevsink_init(GstFBDEVSink * fbdevsink)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 *
gst_fbdevsink_getcaps(GstBaseSink * bsink,GstCaps * filter)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
gst_fbdevsink_setcaps(GstBaseSink * bsink,GstCaps * vscapslist)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
gst_fbdevsink_show_frame(GstVideoSink * videosink,GstBuffer * buf)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
gst_fbdevsink_start(GstBaseSink * bsink)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
gst_fbdevsink_stop(GstBaseSink * bsink)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
gst_fbdevsink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)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
gst_fbdevsink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)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
gst_fbdevsink_change_state(GstElement * element,GstStateChange transition)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
plugin_init(GstPlugin * plugin)397 plugin_init (GstPlugin * plugin)
398 {
399 return GST_ELEMENT_REGISTER (fbdevsink, plugin);
400 }
401
402 static void
gst_fbdevsink_class_init(GstFBDEVSinkClass * klass)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
gst_fbdevsink_finalize(GObject * object)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)
458