1 /*****************************************************************************
2 * fb.c : framebuffer plugin for vlc
3 *****************************************************************************
4 * Copyright (C) 2000-2009 the VideoLAN team
7 * Authors: Samuel Hocevar <sam@zoy.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 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 General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, 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_(\
72 "If your framebuffer supports hardware acceleration or does double buffering " \
73 "in hardware then you must disable this option. It then does double buffering " \
76 #define CHROMA_TEXT N_("Image format (default RGB)")
77 #define CHROMA_LONGTEXT N_("Chroma fourcc used by the framebuffer. Default is RGB since the fb device has no way to report its chroma.")
79 static int Open (vlc_object_t
*);
80 static void Close(vlc_object_t
*);
83 set_shortname("Framebuffer")
84 set_category(CAT_VIDEO
)
85 set_subcategory(SUBCAT_VIDEO_VOUT
)
86 add_file(FB_DEV_VAR
, "/dev/fb0", NULL
, DEVICE_TEXT
, DEVICE_LONGTEXT
,
88 add_bool("fb-tty", true, NULL
, TTY_TEXT
, TTY_LONGTEXT
, true)
89 add_string( "fb-chroma", NULL
, NULL
, CHROMA_TEXT
, CHROMA_LONGTEXT
, true )
90 add_obsolete_string("fb-aspect-ratio")
91 add_integer("fb-mode", 4, NULL
, FB_MODE_TEXT
, FB_MODE_LONGTEXT
,
93 add_bool("fb-hw-accel", true, NULL
, HW_ACCEL_TEXT
, HW_ACCEL_LONGTEXT
,
95 set_description(N_("GNU/Linux framebuffer video output"))
96 set_capability("vout display", 30)
97 set_callbacks(Open
, Close
)
100 /*****************************************************************************
102 *****************************************************************************/
103 static picture_pool_t
*Pool (vout_display_t
*, unsigned);
104 static void Display(vout_display_t
*, picture_t
*);
105 static int Control(vout_display_t
*, int, va_list);
106 static void Manage (vout_display_t
*);
109 static int OpenDisplay (vout_display_t
*, bool force_resolution
);
110 static void CloseDisplay (vout_display_t
*);
112 static void SwitchDisplay(int i_signal
);
114 static void TextMode (int tty
);
115 static void GfxMode (int tty
);
117 static int TtyInit(vout_display_t
*);
118 static void TtyExit(vout_display_t
*);
121 struct vout_display_sys_t
{
122 /* System information */
123 int tty
; /* tty device handle */
125 struct termios old_termios
;
127 /* Original configuration information */
129 struct sigaction sig_usr1
; /* USR1 previous handler */
130 struct sigaction sig_usr2
; /* USR2 previous handler */
132 struct vt_mode vt_mode
; /* previous VT mode */
134 /* Framebuffer information */
135 int fd
; /* device handle */
136 struct fb_var_screeninfo old_info
; /* original mode information */
137 struct fb_var_screeninfo var_info
; /* current mode information */
138 bool has_pan
; /* does device supports panning ? */
139 struct fb_cmap fb_cmap
; /* original colormap */
140 uint16_t *palette
; /* original palette */
141 bool is_hw_accel
; /* has hardware support */
143 /* Video information */
146 uint32_t line_length
;
151 uint8_t *video_ptr
; /* base address */
152 size_t video_size
; /* page size */
155 picture_pool_t
*pool
;
159 static void ClearScreen(vout_display_sys_t
*sys
)
161 switch (sys
->chroma
) {
162 /* XXX: add other chromas */
163 case VLC_CODEC_UYVY
: {
164 unsigned int j
, size
= sys
->video_size
/ 4;
165 uint32_t *ptr
= (uint32_t*)((uintptr_t)(sys
->video_ptr
+ 3) & ~3);
166 for(j
=0; j
< size
; j
++)
167 ptr
[j
] = 0x10801080; /* U = V = 16, Y = 128 */
171 memset(sys
->video_ptr
, 0, sys
->video_size
);
176 * This function allocates and initializes a FB vout method.
178 static int Open(vlc_object_t
*object
)
180 vout_display_t
*vd
= (vout_display_t
*)object
;
181 vout_display_sys_t
*sys
;
183 /* Allocate instance and initialize some members */
184 vd
->sys
= sys
= calloc(1, sizeof(*sys
));
188 /* Does the framebuffer uses hw acceleration? */
189 sys
->is_hw_accel
= var_CreateGetBool(vd
, "fb-hw-accel");
191 /* Set tty and fb devices */
192 sys
->tty
= 0; /* 0 == /dev/tty0 == current console */
193 sys
->is_tty
= var_CreateGetBool(vd
, "fb-tty");
194 #if !defined(WIN32) && defined(HAVE_ISATTY)
195 /* Check that stdin is a TTY */
196 if (sys
->is_tty
&& !isatty(0)) {
197 msg_Warn(vd
, "standard input is not a TTY");
201 msg_Warn(vd
, "disabling TTY handling, use with caution because "
202 "there is no way to return to the TTY");
205 const int mode
= var_CreateGetInteger(vd
, "fb-mode");
206 bool force_resolution
= true;
226 force_resolution
= false;
230 char *chroma
= var_CreateGetNonEmptyString(vd
, "fb-chroma");
232 sys
->chroma
= vlc_fourcc_GetCodecFromString(VIDEO_ES
, chroma
);
235 msg_Dbg(vd
, "forcing chroma '%s'", chroma
);
237 msg_Warn(vd
, "chroma %s invalid, using default", chroma
);
244 if (sys
->is_tty
&& TtyInit(vd
)) {
250 sys
->video_ptr
= MAP_FAILED
;
254 if (OpenDisplay(vd
, force_resolution
)) {
255 Close(VLC_OBJECT(vd
));
258 vout_display_DeleteWindow(vd
, NULL
);
261 video_format_t fmt
= vd
->fmt
;
264 fmt
.i_chroma
= sys
->chroma
;
268 msg_Dbg(vd
, "%d bppd", sys
->var_info
.bits_per_pixel
);
269 switch (sys
->var_info
.bits_per_pixel
) {
270 case 8: /* FIXME: set the palette */
271 fmt
.i_chroma
= VLC_CODEC_RGB8
;
274 fmt
.i_chroma
= VLC_CODEC_RGB15
;
277 fmt
.i_chroma
= VLC_CODEC_RGB16
;
280 fmt
.i_chroma
= VLC_CODEC_RGB24
;
283 fmt
.i_chroma
= VLC_CODEC_RGB32
;
286 msg_Err(vd
, "unknown screendepth %i", sys
->var_info
.bits_per_pixel
);
287 Close(VLC_OBJECT(vd
));
290 if (sys
->var_info
.bits_per_pixel
!= 8) {
291 fmt
.i_rmask
= ((1 << sys
->var_info
.red
.length
) - 1)
292 << sys
->var_info
.red
.offset
;
293 fmt
.i_gmask
= ((1 << sys
->var_info
.green
.length
) - 1)
294 << sys
->var_info
.green
.offset
;
295 fmt
.i_bmask
= ((1 << sys
->var_info
.blue
.length
) - 1)
296 << sys
->var_info
.blue
.offset
;
300 fmt
.i_width
= sys
->width
;
301 fmt
.i_height
= sys
->height
;
304 vout_display_info_t info
= vd
->info
;
305 info
.has_hide_mouse
= true;
312 vd
->display
= Display
;
313 vd
->control
= Control
;
317 vout_display_SendEventFullscreen(vd
, true);
318 vout_display_SendEventDisplaySize(vd
, fmt
.i_width
, fmt
.i_height
, true);
323 * Terminate an output method created by Open
325 static void Close(vlc_object_t
*object
)
327 vout_display_t
*vd
= (vout_display_t
*)object
;
328 vout_display_sys_t
*sys
= vd
->sys
;
331 picture_pool_Delete(sys
->pool
);
332 if (!sys
->is_hw_accel
&& sys
->picture
)
333 picture_Release(sys
->picture
);
344 static picture_pool_t
*Pool(vout_display_t
*vd
, unsigned count
)
346 vout_display_sys_t
*sys
= vd
->sys
;
350 picture_resource_t rsc
;
352 memset(&rsc
, 0, sizeof(rsc
));
353 rsc
.p
[0].p_pixels
= sys
->video_ptr
;
354 rsc
.p
[0].i_lines
= sys
->var_info
.yres
;
355 rsc
.p
[0].i_pitch
= sys
->line_length
;
357 sys
->picture
= picture_NewFromResource(&vd
->fmt
, &rsc
);
362 if (sys
->is_hw_accel
)
363 sys
->pool
= picture_pool_New(1, &sys
->picture
);
365 sys
->pool
= picture_pool_NewFromFormat(&vd
->fmt
, count
);
369 static void Display(vout_display_t
*vd
, picture_t
*picture
)
371 vout_display_sys_t
*sys
= vd
->sys
;
373 /* swap the two Y offsets if the drivers supports panning */
375 sys
->var_info
.yoffset
= 0;
376 /*vd->sys->var_info.yoffset = vd->sys->var_info.yres; */
378 /* the X offset should be 0, but who knows ...
379 * some other app might have played with the framebuffer */
380 sys
->var_info
.xoffset
= 0;
382 /* FIXME 'static' is damn wrong and it's dead code ... */
383 static int panned
= 0;
385 ioctl(sys
->fd
, FBIOPAN_DISPLAY
, &sys
->var_info
);
390 if (!sys
->is_hw_accel
)
391 picture_Copy(sys
->picture
, picture
);
393 picture_Release(picture
);
395 static int Control(vout_display_t
*vd
, int query
, va_list args
)
397 vout_display_sys_t
*sys
= vd
->sys
;
400 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE
: {
401 const vout_display_cfg_t
*cfg
= va_arg(args
, const vout_display_cfg_t
*);
402 if (cfg
->display
.width
!= sys
->width
||
403 cfg
->display
.height
!= sys
->height
)
408 msg_Err(vd
, "Unsupported query in vout display fb");
412 static void Manage (vout_display_t
*vd
)
419 if (vd
->i_changes
& VOUT_SIZE_CHANGE
)
421 msg_Dbg(vd
, "reinitializing framebuffer screen");
422 vd
->i_changes
&= ~VOUT_SIZE_CHANGE
;
424 vout_display_SendEventDisplaySize();
426 ClearScreen(vd
->sys
);
431 /* following functions are local */
432 static int TtyInit(vout_display_t
*vd
)
434 vout_display_sys_t
*sys
= vd
->sys
;
436 struct termios new_termios
;
440 /* Set keyboard settings */
441 if (tcgetattr(0, &sys
->old_termios
) == -1) {
442 msg_Err(vd
, "tcgetattr failed");
445 if (tcgetattr(0, &new_termios
) == -1) {
446 msg_Err(vd
, "tcgetattr failed");
449 /* new_termios.c_lflag &= ~ (ICANON | ISIG);
450 new_termios.c_lflag |= (ECHO | ECHOCTL); */
451 new_termios
.c_lflag
&= ~ (ICANON
);
452 new_termios
.c_lflag
&= ~(ECHO
| ECHOCTL
);
453 new_termios
.c_iflag
= 0;
454 new_termios
.c_cc
[VMIN
] = 1;
455 new_termios
.c_cc
[VTIME
] = 0;
457 if (tcsetattr(0, TCSAFLUSH
, &new_termios
) == -1) {
458 msg_Err(vd
, "tcsetattr failed");
461 ioctl(sys
->tty
, VT_RELDISP
, VT_ACKACQ
);
464 /* Set-up tty signal handler to be aware of tty changes */
465 struct sigaction sig_tty
;
466 memset(&sig_tty
, 0, sizeof(sig_tty
));
467 sig_tty
.sa_handler
= SwitchDisplay
;
468 sigemptyset(&sig_tty
.sa_mask
);
469 if (sigaction(SIGUSR1
, &sig_tty
, &sys
->sig_usr1
) ||
470 sigaction(SIGUSR2
, &sig_tty
, &sys
->sig_usr2
)) {
471 msg_Err(vd
, "cannot set signal handler (%m)");
472 /* FIXME SIGUSR1 could have succeed */
477 /* Set-up tty according to new signal handler */
478 if (-1 == ioctl(sys
->tty
, VT_GETMODE
, &sys
->vt_mode
)) {
479 msg_Err(vd
, "cannot get terminal mode (%m)");
482 struct vt_mode vt_mode
= sys
->vt_mode
;
483 vt_mode
.mode
= VT_PROCESS
;
485 vt_mode
.relsig
= SIGUSR1
;
486 vt_mode
.acqsig
= SIGUSR2
;
488 if (-1 == ioctl(sys
->tty
, VT_SETMODE
, &vt_mode
)) {
489 msg_Err(vd
, "cannot set terminal mode (%m)");
496 sigaction(SIGUSR1
, &sys
->sig_usr1
, NULL
);
497 sigaction(SIGUSR2
, &sys
->sig_usr2
, NULL
);
500 tcsetattr(0, 0, &sys
->old_termios
);
504 static void TtyExit(vout_display_t
*vd
)
506 vout_display_sys_t
*sys
= vd
->sys
;
508 /* Reset the terminal */
509 ioctl(sys
->tty
, VT_SETMODE
, &sys
->vt_mode
);
512 /* Remove signal handlers */
513 sigaction(SIGUSR1
, &sys
->sig_usr1
, NULL
);
514 sigaction(SIGUSR2
, &sys
->sig_usr2
, NULL
);
517 /* Reset the keyboard state */
518 tcsetattr(0, 0, &sys
->old_termios
);
520 /* Return to text mode */
524 /*****************************************************************************
525 * OpenDisplay: initialize framebuffer
526 *****************************************************************************/
527 static int OpenDisplay(vout_display_t
*vd
, bool force_resolution
)
529 vout_display_sys_t
*sys
= vd
->sys
;
530 char *psz_device
; /* framebuffer device path */
532 /* Open framebuffer device */
533 if (!(psz_device
= var_InheritString(vd
, FB_DEV_VAR
))) {
534 msg_Err(vd
, "don't know which fb device to open");
538 sys
->fd
= vlc_open(psz_device
, O_RDWR
);
540 msg_Err(vd
, "cannot open %s (%m)", psz_device
);
546 /* Get framebuffer device information */
547 if (ioctl(sys
->fd
, FBIOGET_VSCREENINFO
, &sys
->var_info
)) {
548 msg_Err(vd
, "cannot get fb info (%m)");
552 sys
->old_info
= sys
->var_info
;
554 /* Get some info on the framebuffer itself */
555 if (force_resolution
) {
556 sys
->var_info
.xres
= sys
->var_info
.xres_virtual
= sys
->width
;
557 sys
->var_info
.yres
= sys
->var_info
.yres_virtual
= sys
->height
;
560 /* Set some attributes */
561 sys
->var_info
.activate
= sys
->is_tty
? FB_ACTIVATE_NXTOPEN
:
563 sys
->var_info
.xoffset
= 0;
564 sys
->var_info
.yoffset
= 0;
566 if (ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->var_info
)) {
567 msg_Err(vd
, "cannot set fb info (%m)");
572 struct fb_fix_screeninfo fix_info
;
573 /* Get some information again, in the definitive configuration */
574 if (ioctl(sys
->fd
, FBIOGET_FSCREENINFO
, &fix_info
) ||
575 ioctl(sys
->fd
, FBIOGET_VSCREENINFO
, &sys
->var_info
)) {
576 msg_Err(vd
, "cannot get additional fb info (%m)");
578 /* Restore fb config */
579 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
585 /* If the fb has limitations on mode change,
586 * then keep the resolution of the fb */
587 if ((sys
->height
!= sys
->var_info
.yres
) ||
588 (sys
->width
!= sys
->var_info
.xres
)) {
590 "using framebuffer native resolution instead of requested (%ix%i)",
591 sys
->width
, sys
->height
);
593 sys
->height
= sys
->var_info
.yres
;
594 sys
->width
= sys
->var_info
.xres_virtual
? sys
->var_info
.xres_virtual
:
596 sys
->line_length
= fix_info
.line_length
;
598 /* FIXME: if the image is full-size, it gets cropped on the left
599 * because of the xres / xres_virtual slight difference */
600 msg_Dbg(vd
, "%ix%i (virtual %ix%i) (request %ix%i)",
601 sys
->var_info
.xres
, sys
->var_info
.yres
,
602 sys
->var_info
.xres_virtual
,
603 sys
->var_info
.yres_virtual
,
604 sys
->width
, sys
->height
);
607 sys
->has_pan
= (fix_info
.ypanstep
|| fix_info
.ywrapstep
);
609 switch (sys
->var_info
.bits_per_pixel
) {
611 sys
->palette
= malloc(8 * 256 * sizeof(uint16_t));
613 /* Restore fb config */
614 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
619 sys
->fb_cmap
.start
= 0;
620 sys
->fb_cmap
.len
= 256;
621 sys
->fb_cmap
.red
= sys
->palette
;
622 sys
->fb_cmap
.green
= sys
->palette
+ 256 * sizeof(uint16_t);
623 sys
->fb_cmap
.blue
= sys
->palette
+ 2 * 256 * sizeof(uint16_t);
624 sys
->fb_cmap
.transp
= sys
->palette
+ 3 * 256 * sizeof(uint16_t);
626 /* Save the colormap */
627 ioctl(sys
->fd
, FBIOGETCMAP
, &sys
->fb_cmap
);
629 sys
->bytes_per_pixel
= 1;
634 sys
->bytes_per_pixel
= 2;
638 sys
->bytes_per_pixel
= 3;
642 sys
->bytes_per_pixel
= 4;
646 msg_Err(vd
, "screen depth %d is not supported",
647 sys
->var_info
.bits_per_pixel
);
649 /* Restore fb config */
650 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
656 sys
->video_size
= sys
->line_length
* sys
->var_info
.yres_virtual
;
658 /* Map a framebuffer at the beginning */
659 sys
->video_ptr
= mmap(NULL
, sys
->video_size
,
660 PROT_READ
| PROT_WRITE
, MAP_SHARED
, sys
->fd
, 0);
662 if (sys
->video_ptr
== MAP_FAILED
) {
663 msg_Err(vd
, "cannot map video memory (%m)");
665 if (sys
->var_info
.bits_per_pixel
== 8) {
670 /* Restore fb config */
671 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
680 "framebuffer type=%d, visual=%d, ypanstep=%d, ywrap=%d, accel=%d",
681 fix_info
.type
, fix_info
.visual
,
682 fix_info
.ypanstep
, fix_info
.ywrapstep
, fix_info
.accel
);
686 /*****************************************************************************
687 * CloseDisplay: terminate FB video thread output method
688 *****************************************************************************/
689 static void CloseDisplay(vout_display_t
*vd
)
691 vout_display_sys_t
*sys
= vd
->sys
;
693 if (sys
->video_ptr
!= MAP_FAILED
) {
695 munmap(sys
->video_ptr
, sys
->video_size
);
699 /* Restore palette */
700 if (sys
->var_info
.bits_per_pixel
== 8) {
701 ioctl(sys
->fd
, FBIOPUTCMAP
, &sys
->fb_cmap
);
706 /* Restore fb config */
707 ioctl(sys
->fd
, FBIOPUT_VSCREENINFO
, &sys
->old_info
);
715 /*****************************************************************************
716 * SwitchDisplay: VT change signal handler
717 *****************************************************************************
718 * This function activates or deactivates the output of the thread. It is
719 * called by the VT driver, on terminal change.
720 *****************************************************************************/
721 static void SwitchDisplay(int i_signal
)
725 vlc_mutex_lock(&p_vout_bank
->lock
);
727 /* XXX: only test the first video output */
728 if (p_vout_bank
->i_count
)
730 vd
= p_vout_bank
->pp_vout
[0];
734 case SIGUSR1
: /* vt has been released */
736 ioctl(sys
->tty
, VT_RELDISP
, 1);
738 case SIGUSR2
: /* vt has been acquired */
740 ioctl(sys
->tty
, VT_RELDISP
, VT_ACTIVATE
);
741 /* handle blanking */
742 vlc_mutex_lock(&vd
->change_lock
);
743 vd
->i_changes
|= VOUT_SIZE_CHANGE
;
744 vlc_mutex_unlock(&vd
->change_lock
);
749 vlc_mutex_unlock(&p_vout_bank
->lock
);
753 /*****************************************************************************
754 * TextMode and GfxMode : switch tty to text/graphic mode
755 *****************************************************************************
756 * These functions toggle the tty mode.
757 *****************************************************************************/
758 static void TextMode(int tty
)
760 /* return to text mode */
761 if (-1 == ioctl(tty
, KDSETMODE
, KD_TEXT
)) {
762 /*msg_Err(vd, "failed ioctl KDSETMODE KD_TEXT");*/
766 static void GfxMode(int tty
)
768 /* switch to graphic mode */
769 if (-1 == ioctl(tty
, KDSETMODE
, KD_GRAPHICS
)) {
770 /*msg_Err(vd, "failed ioctl KDSETMODE KD_GRAPHICS");*/