【原创】IP摄像头技术纵览(三)—图像数据在帧缓存设备(framebuffer)上的显示
本文属于《IP摄像头技术纵览》系列文章之一:
Author: chad
Mail: linczone@163.com
本文可以自由转载,但转载请务必注明出处以及本声明信息。
提起Linux的窗口系统,我们第一个想到的就是X-Window。X-Window是Unix/Linux上的图形系统,它是通过X-Server来控制硬件的。但有一些Linux的发行版在引导的时候就会在屏幕上出现图形,这时的图形是不可能由X来完成的,那是什么机制呢?答案就是FrameBuffer。
FrameBuffer不是一个图形系统,更不是窗口系统。它比X要低级,简单来说FrameBuffer就是一种虚拟显存。这种机制是把屏幕上的每个点映射成一段线性内存空间,程序可以简单的改变这段内存的值来改变屏幕上某一点的颜色。X的高度可移植性就是来自于这种机制。
1、FrameBuffer的原理
FrameBuffer 是出现在 2.2以上内核当中的一种驱动程序接口。帧缓冲(frame buffer)是Linux视频系统的核心概念。因为视频适配器可能基于不同的硬件体系架构,较高内核层和应用程序的实现可能会因视频卡的不同而不同,这会导致在使用不同视频卡的时需要采用不同的方案。随之而来的低可移植性和冗余的代码需要大量的投入和维护开销。帧缓冲的概念解决了这个问题,它进行了一般化的抽象并规定编程接口,从而开发人员可以以与平台无关的方式编写应用层和较高内核层程序。因此,内核的帧缓冲接口允许应用程序与底层图形硬件的变化无关,如果应用和显示器驱动程序遵循帧缓冲接口,应用程序不用改变就可以在不同类型的视频硬件上运行。
在应用程序中,一般通过将 FrameBuffer 设备映射到进程地址空间的方式使用。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。Framebuffer本身不具备任何运算数据的能力,中间不会对数据做处理。FrameBuffer 设备还提供了若干 ioctl 命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息等等。
2、FrameBuffer在Linux中的实现和机制
Framebuffer设备驱动程序架构如下图,用户空间应用程序主要通过read、write、mmap、ioctl这四个接口与Framebuffer设备打交道。但是一般都是采用mmap方式直接操作内存,所以下文也主要介绍mmap方式。
(1)、Framebuffer主要结构体
用户空间可见的FrameBuffer设备的主要结构体几乎都是在fb.h(可以直接点击该链接查看fb.h文件)这个中文件定义的。这些结构包括:
1)fb_var_screeninfo主要是记录用户可以修改的控制器可变参数:
struct fb_var_screeninfo {
/*可见解析度*/
__u32 xres;
__u32 yres;
/*虚拟解析度*/
__u32 xres_virtual;
__u32 yres_virtual;
/*虚拟到可见之间的偏移*/
__u32 xoffset;
__u32 yoffset;
__u32 bits_per_pixel; /*每像素的位数,BPP*/
__u32 grayscale; /*非0时指灰度*/
/*fb缓存的R\G\B位域*/
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp; /*透明度*/
__u32 nonstd; /*!=0非标准像素格式*/
__u32 activate;
__u32 height; /*高度*/
__u32 width; /*宽度*/
__u32 accel_flags;
/*除pixclock本身外,其他都以像素时钟为单位*/
__u32 pixclock; /*像素时钟(皮秒)*/
__u32 left_margin; /*行切换:从同步到绘图之间的延迟*/
__u32 right_margin; /*行切换:从绘图到同步之间的延迟*/
__u32 upper_margin; /*帧切换:从同步到绘图之间的延迟*/
__u32 lower_margin; /*帧切换:从绘图到同步之间的延迟*/
__u32 hsync_len; /*水平同步的长度*/
__u32 vsync_len; /*垂直同步的长度*/
__u32 sync;
__u32 vmode;
__u32 rotate; /*顺时针旋转的角度*/
__u32 reserved[5]; /*保留*/
};
2) fb_fix_screeninfon
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。
struct fb_fix_screeninfo {
char id[16]; /*字符串形式的标识符*/
unsigned long smem_start; /*fb缓冲内存的开始位置(物理地址)*/
__u32 smem_len; /*fb缓冲的长度*/
__u32 type; /*FB_TYPE_* */
__u32 type_aux; /*Interleave*/
__u32 visual; /*FB_VISUAL_* */
__u16 xpanstep; /*如果没有硬件panning,赋0*/
__u16 ypanstep;
__u16 ywrapstep;
__u32 line_length; /*一行的字节数*/
unsigned long mmio_start; /*内存映射I/O的开始位置*/
__u32 mmio_len; /*内存映射I/O的长度*/
__u32 accel;
__u16 reserved[3]; /*保留*/
};
fb_var_screeninfo与fb_fix_screeninfo的关系见下图:
3、FrameBuffer的应用
帧缓冲设备对应的设备文件为 /dev/fb*,如果系统有多个显示卡,Linux下还可支持多个帧缓冲设备,最多可达32 个,分别为/dev/fb0到/dev/fb31,而/dev/fb则为当前缺省的帧缓冲设备,通常指向/dev/fb0。当然在嵌入式系统中支持一个显 示设备就够了。帧缓冲设备为标准字符设备,主设备号为29,次设备号则从0到31。分别对应/dev/fb0-/dev/fb31。通过/dev/fb, 应用程序的操作主要有这几种:
1、读/写(read/write)/dev/fb:相当于读/写屏幕缓冲区。
例如用 cp /dev/fb0 tmp命令可将当前屏幕的内容拷贝到一个文件中,而命令cp tmp > /dev/fb0 则将图形文件tmp显示在屏幕上。
2、映射(map)操作:由于Linux工作在保护模式,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区地址的。为此, Linux在文件操作 file_operations结构中提供了mmap函数,可将文件的内容映射到用户空间。对于帧缓冲设备,则可通过映射操作,可将屏幕缓冲区的物理地址 映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图了。实际上,使用帧缓冲设备的应用程序都是通过映射操 作来显示图形的。由于映射操作都是由内核来完成,下面我们将看到,帧缓冲驱动留给开发人员的工作并不多。
3、 I/O控制:对于帧缓冲设备,对设备文件的ioctl操作可读取/设置显示设备及屏幕的参数,如分辨率,显示颜色数,屏幕大小等等。ioctl的操作是由底层的驱动程序来完成的。
在应用程序中,操作/dev/fb的一般步骤如下:
- 打开/dev/fb设备文件。
- 用ioctrl操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
- 将屏幕缓冲区映射到用户空间。
- 映射后就可以直接读写屏幕缓冲区,进行绘图和图片显示了。
典型实例程序—在屏幕上画一条线:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
int main ()
{
int fp=0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
long screensize=0;
char *fbp = 0;
int x = 0, y = 0;
long location = 0;
fp = open ("/dev/fb0",O_RDWR);
if (fp < 0)
{
printf("Error : Can not open framebuffer device/n");
exit(1);
}
if (ioctl(fp,FBIOGET_FSCREENINFO,&finfo))
{
printf("Error reading fixed information/n");
exit(2);
}
if (ioctl(fp,FBIOGET_VSCREENINFO,&vinfo))
{
printf("Error reading variable information/n");
exit(3);
}
//显示结构体信息
printf("The mem is :%d\n",finfo.smem_len);
printf("The line_length is :%d\n",finfo.line_length);
printf("The xres is :%d\n",vinfo.xres);
printf("The yres is :%d\n",vinfo.yres);
printf("bits_per_pixel is :%d\n",vinfo.bits_per_pixel);
//计算显存大小
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
/*这就是把fp所指的文件中从开始到screensize大小的内容给映射出来,得到一个指向这块空间的指针*/
fbp =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fp,0);
if ((int) fbp == -1)
{
printf ("Error: failed to map framebuffer device to memory./n");
exit (4);
}
/*这是你想画的点的位置坐标,(0,0)点在屏幕左上角*/
int i,j;
x = 10;
y = 10;
for(i=x,j=y;i<100;i++,j++)
{
location = i * (vinfo.bits_per_pixel / 8) + j * finfo.line_length;
*(fbp + location) = 0; /* 蓝色的色深 */ /*直接赋值来改变屏幕上某点的颜色*/
*(fbp + location + 1) = 0xcc; /* 绿色的色深*/
*(fbp + location + 2) = 0xcc; /* 红色的色深*/
*(fbp + location + 3) = 0; /* 是否透明*/
}
munmap (fbp, screensize