1 /*****************************************************************************
2 * fb.c : framebuffer plugin for vlc
3 *****************************************************************************
4 * Copyright (C) 2000-2009 VLC authors and VideoLAN
7 * Authors: Samuel Hocevar <sam@zoy.org>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
33 #include <signal.h> /* SIGUSR1, SIGUSR2 */
34 #include <fcntl.h> /* open() */
35 #include <unistd.h> /* close() */
37 #include <termios.h> /* struct termios */
38 #include <sys/ioctl.h>
39 #include <sys/mman.h> /* mmap() */
42 #include <linux/vt.h> /* VT_* */
43 #include <linux/kd.h> /* KD* */
45 #include <vlc_common.h>
46 #include <vlc_plugin.h>
47 #include <vlc_vout_display.h>
48 #include <vlc_picture_pool.h>
51 /*****************************************************************************
53 *****************************************************************************/
54 #define FB_DEV_VAR "fbdev"
56 #define DEVICE_TEXT N_("Framebuffer device")
57 #define DEVICE_LONGTEXT N_(\
58 "Framebuffer device to use for rendering (usually /dev/fb0).")
60 #define TTY_TEXT N_("Run fb on current tty")
61 #define TTY_LONGTEXT N_(\
62 "Run framebuffer on current TTY device (default enabled). " \
63 "(disable tty handling with caution)")
65 #define FB_MODE_TEXT N_("Framebuffer resolution to use")
66 #define FB_MODE_LONGTEXT N_(\
67 "Select the resolution for the framebuffer. Currently it supports " \
68 "the values 0=QCIF 1=CIF 2=NTSC 3=PAL, 4=auto (default 4=auto)")
70 #define HW_ACCEL_TEXT N_("Framebuffer uses hw acceleration")
71 #define HW_ACCEL_LONGTEXT N_("Disable for double buffering in software.")
73 #define CHROMA_TEXT N_("Image format (default RGB)")
74 #define CHROMA_LONGTEXT N_("Chroma fourcc used by the framebuffer. Default is RGB since the fb device has no way to report its chroma.")
76 static int Open (vlc_object_t
*);
77 static void Close(vlc_object_t
*);
80 set_shortname("Framebuffer")
81 set_category(CAT_VIDEO
)
82 set_subcategory(SUBCAT_VIDEO_VOUT
)
83 add_loadfile(FB_DEV_VAR
, "/dev/fb0", DEVICE_TEXT
, DEVICE_LONGTEXT
,
85 add_bool("fb-tty", true, TTY_TEXT
, TTY_LONGTEXT
, true)
86 add_string( "fb-chroma", NULL
, CHROMA_TEXT
, CHROMA_LONGTEXT
, true )
87 add_obsolete_string("fb-aspect-ratio")
88 add_integer("fb-mode", 4, FB_MODE_TEXT
, FB_MODE_LONGTEXT
,
90 add_bool("fb-hw-accel", true, HW_ACCEL_TEXT
, HW_ACCEL_LONGTEXT
,
92 set_description(N_("GNU/Linux framebuffer video output"))
93 set_capability("vout display", 30)
94 set_callbacks(Open
, Close
)
97 /*****************************************************************************
99 *****************************************************************************/
100 static picture_pool_t
*Pool (vout_display_t
*, unsigned);
101 static void Display(vout_display_t
*, picture_t
*, subpicture_t
*);
102 static int Control(vout_display_t
*, int, va_list);
105 static int OpenDisplay (vout_display_t
*, bool force_resolution
);
106 static void CloseDisplay (vout_display_t
*);
108 static void SwitchDisplay(int i_signal
);
110 static void TextMode (int tty
);
111 static void GfxMode (int tty
);
113 static int TtyInit(vout_display_t
*);
114 static void TtyExit(vout_display_t
*);
117 struct vout_display_sys_t
{
118 /* System information */
119 int tty
; /* tty device handle */
121 struct termios old_termios
;
123 /* Original configuration information */
125 struct sigaction sig_usr1
; /* USR1 previous handler */
126 struct sigaction sig_usr2
; /* USR2 previous handler */
128 struct vt_mode vt_mode
; /* previous VT mode */
130 /* Framebuffer information */
131 int fd
; /* device handle */
132 struct fb_var_screeninfo old_info
; /* original mode information */
133 struct fb_var_screeninfo var_info
; /* current mode information */
134 bool has_pan
; /* does device supports panning ? */
135 struct fb_cmap fb_cmap
; /* original colormap */
136 uint16_t *palette
; /* original palette */
137 bool is_hw_accel
; /* has hardware support */
139 /* Video information */
142 uint32_t line_length
;
147 uint8_t *video_ptr
; /* base address */
148 size_t video_size
; /* page size */
151 picture_pool_t
*pool
;
155 static void ClearScreen(vout_display_sys_t
*sys
)
157 switch (sys
->chroma
) {
158 /* XXX: add other chromas */
159 case VLC_CODEC_UYVY
: {
160 unsigned int j
, size
= sys
->video_size
/ 4;
161 uint32_t *ptr
= (uint32_t*)((uintptr_t)(sys
->video_ptr
+ 3) & ~3);
162 for(j
=0; j
< size
; j
++)
163 ptr
[j
] = 0x10801080; /* U = V = 16, Y = 128 */
167 memset(sys
->video_ptr
, 0, sys
->video_size
);
172 * This function allocates and initializes a FB vout method.
174 static int Open(vlc_object_t
*object
)
176 vout_display_t
*vd
= (vout_display_t
*)object
;
177 vout_display_sys_t
*sys
;
179 if (vout_display_IsWindowed(vd
))
182 /* Allocate instance and initialize some members */
183 vd
->sys
= sys
= calloc(1, sizeof(*sys
));
187 /* Does the framebuffer uses hw acceleration? */
188 sys
->is_hw_accel
= var_InheritBool(vd
, "fb-hw-accel");
190 /* Set tty and fb devices */
191 sys
->tty
= 0; /* 0 == /dev/tty0 == current console */
192 sys
->is_tty
= var_InheritBool(vd
, "fb-tty");
193 #if !defined(_WIN32) && defined(HAVE_ISATTY)
194 /* Check that stdin is a TTY */
195 if (sys
->is_tty
&& !isatty(0)) {
196 msg_Warn(vd
, "standard input is not a TTY");
200 msg_Warn(vd
, "disabling TTY handling, use with caution because "
201 "there is no way to return to the TTY");
204 const int mode
= var_InheritInteger(vd
, "fb-mode");
205 bool force_resolution
= true;
225 force_resolution
= false;
229 char *chroma
= var_InheritString(vd
, "fb-chroma");
231 sys
->chroma
= vlc_fourcc_GetCodecFromString(VIDEO_ES
, chroma
);
234 msg_Dbg(vd
, "forcing chroma '%s'", chroma
);
236 msg_Warn(vd
, "chroma %s invalid, using default", chroma
);
243 if (sys
->is_tty
&& TtyInit(vd
)) {
249 sys
->video_ptr
= MAP_FAILED
;
253 if (OpenDisplay(vd
, force_resolution
)) {
254 Close(VLC_OBJECT(vd
));
260 video_format_ApplyRotation(&fmt
, &vd
->fmt
);
263 fmt
.i_chroma
= sys
->chroma
;
267 msg_Dbg(vd
, "%d bppd", sys
->var_info
.bits_per_pixel
);
268 switch (sys
->var_info
.bits_per_pixel
) {
269 case 8: /* FIXME: set the palette */
270 fmt
.i_chroma
= VLC_CODEC_RGB8
;
273 fmt
.i_chroma
= VLC_CODEC_RGB15
;
276 fmt
.i_chroma
= VLC_CODEC_RGB16
;
279 fmt
.i_chroma
= VLC_CODEC_RGB24
;
282 fmt
.i_chroma
= VLC_CODEC_RGB32
;
285 msg_Err(vd
, "unknown screendepth %i", sys
->var_info
.bits_per_pixel
);
286 Close(VLC_OBJECT(vd
));
289 if (sys
->var_info
.bits_per_pixel
!= 8) {
290 fmt
.i_rmask
= ((1 << sys
->var_info
.red
.length
) - 1)
291 << sys
->var_info
.red
.offset
;
292 fmt
.i_gmask
= ((1 << sys
->var_info
.green
.length
) - 1)
293 << sys
->var_info
.green
.offset
;
294 fmt
.i_bmask
= ((1 << sys
->var_info
.blue
.length
) - 1)
295 << sys
->var_info
.blue
.offset
;
299 fmt
.i_visible_width
= sys
->width
;
300 fmt
.i_visible_height
= sys
->height
;
303 vout_display_info_t info
= vd
->info
;
304 info
.has_hide_mouse
= true;
311 vd
->display
= Display
;
312 vd
->control
= Control
;
316 vout_display_SendEventFullscreen(vd
, true);
317 vout_display_SendEventDisplaySize(vd
, fmt
.i_visible_width
, fmt
.i_visible_height
);
322 * Terminate an output method created by Open
324 static void Close(vlc_object_t
*object
)
326 vout_display_t
*vd
= (vout_display_t
*)object
;
327 vout_display_sys_t
*sys
= vd
->sys
;
330 picture_pool_Release(sys
->pool
);
331 if (!sys
->is_hw_accel
&& sys
->picture
)
332 picture_Release(sys
->picture
);
343 static picture_pool_t
*Pool(vout_display_t
*vd
, unsigned count
)
345 vout_display_sys_t
*sys
= vd
->sys
;
349 picture_resource_t rsc
;
351 memset(&rsc
, 0, sizeof(rsc
));
352 rsc
.p
[0].p_pixels
= sys
->video_ptr
;
353 rsc
.p
[0].i_lines
= sys
->var_info
.yres
;
354 rsc
.p
[0].i_pitch
= sys
->line_length
;
356 sys
->picture
= picture_NewFromResource(&vd
->fmt
, &rsc
);
361 if (sys
->is_hw_accel
)
362 sys
->pool
= picture_pool_New(1, &sys
->picture
);
364 sys
->pool
= picture_pool_NewFromFormat(&vd
->fmt
, count
);
368 static void Display(vout_display_t
*vd
, picture_t
*picture
, subpicture_t
*subpicture
)
370 vout_display_sys_t
*sys
= vd
->sys
;
372 /* swap the two Y offsets if the drivers supports panning */
374 sys
->var_info
.yoffset
= 0;
375 /*vd->sys->var_info.yoffset = vd->sys->var_info.yres; */
377 /* the X offset should be 0, but who knows ...
378 * some other app might have played with the framebuffer */
379 sys
->var_info
.xoffset
= 0;
381 /* FIXME 'static' is damn wrong and it's dead code ... */
382 static int panned
= 0;
384 ioctl(sys
->fd
, FBIOPAN_DISPLAY
, &sys
->var_info
);
389 if (!sys
->is_hw_accel
)
390 picture_Copy(sys
->picture
, picture
);
392 picture_Release(picture
);
393 VLC_UNUSED(subpicture
);
395 static int Control(vout_display_t
*vd
, int query
, va_list args
)
397 (void) vd
; (void) query
; (void) args
;
401 /* following functions are local */
402 static int TtyInit(vout_display_t
*vd
)
404 vout_display_sys_t
*sys
= vd
->sys
;
406 struct termios new_termios
;
410 /* Set keyboard settings */
411 if (tcgetattr(0, &sys
->old_termios
) == -1) {
412 msg_Err(vd
, "tcgetattr failed");
415 if (tcgetattr(0, &new_termios
) == -1) {
416 msg_Err(vd
, "tcgetattr failed");
419 /* new_termios.c_lflag &= ~ (ICANON | ISIG);
420 new_termios.c_lflag |= (ECHO | ECHOCTL); */
421 new_termios
.c_lflag
&= ~ (ICANON
);
422 new_termios
.c_lflag
&= ~(ECHO
| ECHOCTL
);
423 new_termios
.c_iflag
= 0;
424 new_termios
.c_cc
[VMIN
] = 1;
425 new_termios
.c_cc
[VTIME
] = 0;
427 if (tcsetattr(0, TCSAFLUSH
, &new_termios
) == -1) {
428 msg_Err(vd
, "tcsetattr failed");
431 ioctl(sys
->tty
, VT_RELDISP
, VT_ACKACQ
);
434 /* Set-up tty signal handler to be aware of tty changes */
435 struct sigaction sig_tty
;
436 memset(&sig_tty
, 0, sizeof(sig_tty
));
437 sig_tty
.sa_handler
= SwitchDisplay
;
438 sigemptyset(&sig_tty
.sa_mask
);
439 if (sigaction(SIGUSR1
, &sig_tty
, &sys
->sig_usr1
) ||
440 sigaction(SIGUSR2
, &sig_tty
, &sys
->sig_usr2
)) {
441 msg_Err(vd
, "cannot set signal handler (%s)", vlc_strerror_c(errno
));
442 /* FIXME SIGUSR1 could have succeed */
447 /* Set-up tty according to new signal handler */
448 if (-1 == ioctl(sys
->tty
, VT_GETMODE
, &sys
->vt_mode
)) {
449 msg_Err(vd
, "cannot get terminal mode (%s)", vlc_strerror_c(errno
));
452 struct vt_mode vt_mode
= sys
->vt_mode
;
453 vt_mode
.mode
= VT_PROCESS
;
455 vt_mode
.relsig
= SIGUSR1
;
456 vt_mode
.acqsig
= SIGUSR2
;
458 if (-1 == ioctl(sys
->tty
, VT_SETMODE
, &vt_mode
)) {
459 msg_Err(vd
, "cannot set terminal mode (%s)", vlc_strerror_c(errno
));
466 sigaction(SIGUSR1
, &sys
->sig_usr1
, NULL
);
467 sigaction(SIGUSR2
, &sys
->sig_usr2
, NULL
);
470 tcsetattr(0, 0, &sys
->old_termios
);
474 static void TtyExit(vout_display_t
*vd
)
476 vout_display_sys_t
*sys
= vd
->sys
;
478 /* Reset the terminal */
479 ioctl(sys
->tty
, VT_SETMODE
, &sys
->vt_mode
);
482 /* Remove signal handlers */
483 sigaction(SIGUSR1
, &sys
->sig_usr1
, NULL
);
484 sigaction(SIGUSR2
, &sys
->sig_usr2
, NULL
);
487 /* Reset the keyboard state */
488 tcsetattr(0, 0, &sys
->old_termios
);
490 /* Return to text mode */
494 /*****************************************************************************
495 * OpenDisplay: initialize framebuffer
496 *****************************************************************************/
497 static int OpenDisplay(vout_display_t
*vd
, bool force_resolution
)
499 vout_display_sys_t
*sys
= vd
->sys
;
500 char *psz_device
; /* framebuffer device path */
502 /* Open framebuffer device */
503 if (!(psz_device
= var_InheritString(vd
, FB_DEV_VAR
))) {
504 msg_Err(vd
, "don't know which fb device to open");
508 sys
->fd
= vlc_open(psz_device
, O_RDWR
);
510 msg_Err(vd
, "cannot open %s (%s)", psz_device
, vlc_strerror_c(errno
));
516 /* Get framebuffer device information */
517 if (ioctl(sys
->fd
, FBIOGET_VSCREENINFO
, &sys
->var_info
)) {
518 msg_Err(vd
, "cannot get fb info (%s)", vlc_strerror_c(errno
));
522 sys
->old_info
= sys
->var_info
;
524 /* Get some info on the framebuffer itself */
525 if (force_resolution
) {
526 sys
->var_info
.xres
= sys
->var_info
.xres_virtual
= sys
->width
;
527 sys
->var_info
.yres
= sys
->var_info
.yres_virtual
= sys
->height
;
530 /* Set some attributes */
531 sys
->var_info
.activate
= sys
->is_tty
? FB_ACTIVATE_NXTOPEN
:
533 sys
->var_info
.xoffset
= 0;
534 sys
->var_info
.yoffset
= 0;
536 if (ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->var_info
)) {
537 msg_Err(vd
, "cannot set fb info (%s)", vlc_strerror_c(errno
));
542 struct fb_fix_screeninfo fix_info
;
543 /* Get some information again, in the definitive configuration */
544 if (ioctl(sys
->fd
, FBIOGET_FSCREENINFO
, &fix_info
) ||
545 ioctl(sys
->fd
, FBIOGET_VSCREENINFO
, &sys
->var_info
)) {
546 msg_Err(vd
, "cannot get additional fb info (%s)",
547 vlc_strerror_c(errno
));
549 /* Restore fb config */
550 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
556 /* If the fb has limitations on mode change,
557 * then keep the resolution of the fb */
558 if ((sys
->height
!= sys
->var_info
.yres
) ||
559 (sys
->width
!= sys
->var_info
.xres
)) {
561 "using framebuffer native resolution instead of requested (%ix%i)",
562 sys
->width
, sys
->height
);
564 sys
->height
= sys
->var_info
.yres
;
565 sys
->width
= sys
->var_info
.xres_virtual
? sys
->var_info
.xres_virtual
:
567 sys
->line_length
= fix_info
.line_length
;
569 /* FIXME: if the image is full-size, it gets cropped on the left
570 * because of the xres / xres_virtual slight difference */
571 msg_Dbg(vd
, "%ix%i (virtual %ix%i) (request %ix%i)",
572 sys
->var_info
.xres
, sys
->var_info
.yres
,
573 sys
->var_info
.xres_virtual
,
574 sys
->var_info
.yres_virtual
,
575 sys
->width
, sys
->height
);
578 sys
->has_pan
= (fix_info
.ypanstep
|| fix_info
.ywrapstep
);
580 switch (sys
->var_info
.bits_per_pixel
) {
582 sys
->palette
= malloc(4 * 256 * sizeof(uint16_t));
584 /* Restore fb config */
585 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
590 sys
->fb_cmap
.start
= 0;
591 sys
->fb_cmap
.len
= 256;
592 sys
->fb_cmap
.red
= sys
->palette
;
593 sys
->fb_cmap
.green
= sys
->palette
+ 256;
594 sys
->fb_cmap
.blue
= sys
->palette
+ 2 * 256;
595 sys
->fb_cmap
.transp
= sys
->palette
+ 3 * 256;
597 /* Save the colormap */
598 ioctl(sys
->fd
, FBIOGETCMAP
, &sys
->fb_cmap
);
600 sys
->bytes_per_pixel
= 1;
605 sys
->bytes_per_pixel
= 2;
609 sys
->bytes_per_pixel
= 3;
613 sys
->bytes_per_pixel
= 4;
617 msg_Err(vd
, "screen depth %d is not supported",
618 sys
->var_info
.bits_per_pixel
);
620 /* Restore fb config */
621 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
627 sys
->video_size
= sys
->line_length
* sys
->var_info
.yres_virtual
;
629 /* Map a framebuffer at the beginning */
630 sys
->video_ptr
= mmap(NULL
, sys
->video_size
,
631 PROT_READ
| PROT_WRITE
, MAP_SHARED
, sys
->fd
, 0);
633 if (sys
->video_ptr
== MAP_FAILED
) {
634 msg_Err(vd
, "cannot map video memory (%s)", vlc_strerror_c(errno
));
636 if (sys
->var_info
.bits_per_pixel
== 8) {
641 /* Restore fb config */
642 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
651 "framebuffer type=%d, visual=%d, ypanstep=%d, ywrap=%d, accel=%d",
652 fix_info
.type
, fix_info
.visual
,
653 fix_info
.ypanstep
, fix_info
.ywrapstep
, fix_info
.accel
);
657 /*****************************************************************************
658 * CloseDisplay: terminate FB video thread output method
659 *****************************************************************************/
660 static void CloseDisplay(vout_display_t
*vd
)
662 vout_display_sys_t
*sys
= vd
->sys
;
664 if (sys
->video_ptr
!= MAP_FAILED
) {
666 munmap(sys
->video_ptr
, sys
->video_size
);
670 /* Restore palette */
671 if (sys
->var_info
.bits_per_pixel
== 8) {
672 ioctl(sys
->fd
, FBIOPUTCMAP
, &sys
->fb_cmap
);
677 /* Restore fb config */
678 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
686 /*****************************************************************************
687 * SwitchDisplay: VT change signal handler
688 *****************************************************************************
689 * This function activates or deactivates the output of the thread. It is
690 * called by the VT driver, on terminal change.
691 *****************************************************************************/
692 static void SwitchDisplay(int i_signal
)
696 vlc_mutex_lock(&p_vout_bank
->lock
);
698 /* XXX: only test the first video output */
699 if (p_vout_bank
->i_count
)
701 vd
= p_vout_bank
->pp_vout
[0];
705 case SIGUSR1
: /* vt has been released */
707 ioctl(sys
->tty
, VT_RELDISP
, 1);
709 case SIGUSR2
: /* vt has been acquired */
711 ioctl(sys
->tty
, VT_RELDISP
, VT_ACTIVATE
);
712 /* handle blanking */
713 vlc_mutex_lock(&vd
->change_lock
);
714 vd
->i_changes
|= VOUT_SIZE_CHANGE
;
715 vlc_mutex_unlock(&vd
->change_lock
);
720 vlc_mutex_unlock(&p_vout_bank
->lock
);
724 /*****************************************************************************
725 * TextMode and GfxMode : switch tty to text/graphic mode
726 *****************************************************************************
727 * These functions toggle the tty mode.
728 *****************************************************************************/
729 static void TextMode(int tty
)
731 /* return to text mode */
732 if (-1 == ioctl(tty
, KDSETMODE
, KD_TEXT
)) {
733 /*msg_Err(vd, "failed ioctl KDSETMODE KD_TEXT");*/
737 static void GfxMode(int tty
)
739 /* switch to graphic mode */
740 if (-1 == ioctl(tty
, KDSETMODE
, KD_GRAPHICS
)) {
741 /*msg_Err(vd, "failed ioctl KDSETMODE KD_GRAPHICS");*/