参考了飞凌公司提供的测试源码,自己稍加修改,可以实现摄像头的在LCD上显示
由于飞凌公司提供的测试源码功能很多所以一下子可能不能理解,所以我就想一步步拆开来做,先实现在LCD屏上的显示
———————————————————————————————————————————————————————————————————————
改动的地方:
1.我经过测试知道了摄像头输出的图像格式是YUV422,所以代码中我就没有再判断图像格式去选择哪一种显示方法,精简了一下代码。代码中存在一个格式转换的函数 show_cam_image(),还有一个转换公式的函数Conv_YCbCr_Rgb()。因为我们的LCD的图像显示格式是RGB565的,而我摄像头获得的数据是YUV422的(摄像头寄存器参数决定),所以想在LCD上显示必须得转换一下格式,这2个函数我还没看懂。
如果你的摄像头获得的图像格式是RGB的,那么你就不用转换,只需要将v4l mmap获得的图像内存地址copy到LCD的framebuffer中即可。
2.增加了一个退出的功能,原来代码中是有的,不过比这个复杂,你只需要在屏幕上按e或者E即可退出摄像头,采用了select,这里我的tv设置了0,因为是while(1)循环,所以并没有大影响,如果你设置了一个时间,那么在屏幕上你会看见图像动得很慢,因为CPU花时间去轮询是否要去看是否要退出。原代码中用了一个方法去解决这个问题,跟根据驱动来的,目前我还没看懂,就是我注销的那一段。
======================================================================================================
还有需要注意的地方就是v4l的一些结构体:(具体可以去百度,还有很多结构体是代码中没用到的,是设置图像的一些其他参数的,这里只是在LCD上显示,所以没用到那么多)
struct video_capability vc;
struct video_window vw;
struct video_capture vcp;
struct video_picture vp;
struct video_mbuf vm;
======================================================================================================
这里我就稍微说一下mmap
mmap()是实现了一个映射过程,将用户空间的一段内存与设备的内存相关联,当用户访问用户空间的这段地址范围实际上就转化为对设备的访问。对于摄像头这样的设备,mmap应该是最好的选择,可以直接将图像数据存放的内存映射到用户空间,然后将这段内存再和frambuffer去重合,就可以直接实现LCD显示,不用再调用read这个函数,很快,因为调用read这个系统调用,还是要先进内核,然后在内核中读取数据,再从内核空间搬运到用户空间,这样比只在用户空间操作慢很多。
上面这个过程和理解我想了很久,就是因为不理解用户空间和内核空间的概念,这里我再说一下我对这2个空间的理解吧
因为32位的linux系统(开启了MMU)每个进程有4G的内存空间,3G是用户空间,1G是内核空间,每个进程的用户空间的完全独立的,而内核空间是由内核负责映射的,它并不会跟着进程改变,而每个进程的用户空间和内核空间也是分开的(为了防止用户空间错误导致系统奔溃),用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址。
所以用户空间是不可能直接访问设备的,但是我现在需要去操作设备呀,比如去点灯,打开摄像头,这里就可以利用系统调用(代表用户进程在内核态执行),来实现这个过程;系统调用在用户空间调用了一个功能函数,其实是调用file_operations里面我们定义的一些功能函数,通过这些功能函数我们再去操作设备。
上面说到了一个4G的空间,其实是我们虚拟出来的,实际上我们的SDRAM只有64M,它操作的大概方法是:假如我有一个1G的程序需要在内存中运行,但是我们的内存只有64M,但是我的虚拟内存有4G,它首先把这1G的程序搬到4G上,然后在这4G的空间里面,取一段,假如8M的程序放到我们64M中运行,当满了64M的时候就把前面的扔掉,再取,就这样一直循环,也有可能是边拿边扔,具体怎样我不知道,需要看算法。这里就出了问题了哪来的4G呢?这里就要说到MMU了,它里面是管理这些实际内存和虚拟内存映射的单元,它会建立一个映射表,哪些虚拟内存地址是对应到实际内存地址,CPU只需要处理这些虚拟地址。
======================================================================================================
这个结构体是用来存放v4l设备mmap相关参数的,因为v4l驱动对于设备的内存访问使用到了mmap
struct video_mbuf
{
int size; 可映射的摄像头内存大小
int frames; 摄像头可同时存储的帧数
int offsets[VIDEO_MAX_FRAME];每一帧图像的偏移量
};
用户空间操作:
ioctl(fd, VIDIOCGMBUF, &vm)
buf = (__u8 *)mmap(0, vm.size, PROT_READ, MAP_SHARED, fd, 0);//这里就调用mmap实际是去内核中调用我们定义的mmap函数
注意:在用户空间进行mmap摄像头数据之间都需要调用VIDIOCGMBUF的ioctl
PROT_READ //页内容可以被读取
MAP_SHARED//对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享
V4L提供了2种读取摄像头数据的方式,一个是直接读取,一个是mmap,我查了一下两者最大的区别在于写的速度mmap会快一点,对于读取数据方面应该差不多的(我改了它的程序都试过一下)。
不过大多都是用后者(应该是会节约一点CPU时间),使用后者的时候它给的测试程序会先调用一个VIDIOCGMBUF的ioctl,内核中主要是将摄像头采集的数据放到一个video_mbuf结构体类型的变量中(用来保存摄像头的buf),然后copy_to_user将数据传到用户空间中去,然后再调用mmap,对于内核具体是如何将这段摄像头buf和mmap的内存空间重叠的还没看懂。(我怀疑是配置了摄像头接口的寄存器,将采集数据的目的地址进行了修改吧)
内核会进行如下处理:(这里就是我前一个文章提到的深水区,在内核中看到了VMA的操作)
1.在进程的虚拟空间查找一块VMA
2.将这块VMA进行映射
3.如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,则调用它
4.将这个VMA插入到进程的VMA链表中
======================================================================================================
OK,我们已经通过了mmap获取了图像数据内存地址,然后再将它和frambuffer重合即可,可以使用mmecpy,不过我上面就提到了由于我们获得的图像格式和frambuffer图像显示格式不一样,所以我们需要进行转换,这就是show_cam_image()这个函数所要做的事情,具体函数的操作我还没看懂~
今天就分析到这里了,代码在下面。
贴一下代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <termios.h> #include <linux/fb.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <linux/videodev.h> #include <errno.h> #define FIXED_SOURCE_WIDTH 640 #define FIXED_SOURCE_HEIGHT 480 #define VIDEO_START 0 #define VIDEO_PALETTE_YUV422P 13 #define YCbCrtoR(Y,Cb,Cr) (1000*Y + 1371*(Cr-128))/1000 #define YCbCrtoG(Y,Cb,Cr) (1000*Y - 336*(Cb-128) - 698*(Cr-128))/1000 #define YCbCrtoB(Y,Cb,Cr) (1000*Y + 1732*(Cb-128))/1000 #define min(x1, x2) (((x1)<(x2))?(x1):(x2)) static unsigned short image_width; static unsigned short image_height; //static unsigned short image_format; static int fb_xres; static int fb_yres; static int fb_bpp; /* static struct { int palette; char *name; } optional_image_format[] = { { VIDEO_PALETTE_YUV422P, "YCbCr422 planar", }, { VIDEO_PALETTE_RGB565, "RGB565", }, { VIDEO_PALETTE_RGB24, "RGB24", }, }; */ __u32 Conv_YCbCr_Rgb(__u8 y0, __u8 y1, __u8 cb0, __u8 cr0) { // bit order is // YCbCr = [Cr0 Y1 Cb0 Y0], RGB=[R1,G1,B1,R0,G0,B0]. int r0, g0, b0, r1, g1, b1; __u16 rgb0, rgb1; __u32 rgb; #if 1 // 4 frames/s @192MHz, 12MHz ; 6 frames/s @450MHz, 12MHz r0 = YCbCrtoR(y0, cb0, cr0); g0 = YCbCrtoG(y0, cb0, cr0); b0 = YCbCrtoB(y0, cb0, cr0); r1 = YCbCrtoR(y1, cb0, cr0); g1 = YCbCrtoG(y1, cb0, cr0); b1 = YCbCrtoB(y1, cb0, cr0); #endif if (r0>255 ) r0 = 255; if (r0<0) r0 = 0; if (g0>255 ) g0 = 255; if (g0<0) g0 = 0; if (b0>255 ) b0 = 255; if (b0<0) b0 = 0; if (r1>255 ) r1 = 255; if (r1<0) r1 = 0; if (g1>255 ) g1 = 255; if (g1<0) g1 = 0; if (b1>255 ) b1 = 255; if (b1<0) b1 = 0; // 5:6:5 16bit format rgb0 = (((__u16)r0>>3)<<11) | (((__u16)g0>>2)<<5) | (((__u16)b0>>3)<<0); //RGB565. rgb1 = (((__u16)r1>>3)<<11) | (((__u16)g1>>2)<<5) | (((__u16)b1>>3)<<0); //RGB565. rgb = (rgb1<<16) | rgb0; return(rgb); } static char yuv_interval[] = {0, 2, 4, 8, 16}; static void show_cam_img(void *scr, __u8 *y_buf, __u8 *cb_buf, __u8 *cr_buf) { __u16 x, y, w, h, i, f; __u16 *fb_buf = (__u16 *)scr; __u32 rgb_data; for(i=0; i<4; i++) { //0,1,2,3 if((image_width>>i)<=fb_xres) { f = 0; w = min(image_width>>i, fb_xres); h = min(image_height>>i, fb_yres); break; } if((image_height>>i)<=fb_yres) { f = 1; w = min(image_width>>i, fb_yres); h = min(image_height>>i, fb_xres); break; } } if(i>=4) return; if(!f) { for(y=0; y<h; y++) { for(x=0; x<w; x+=2) { //calculate 2 times if(i) { fb_buf[x] = Conv_YCbCr_Rgb(y_buf[x<<i],y_buf[(x<<i)+1],cb_buf[(x<<i)>>1],cr_buf[(x<<i)>>1]); fb_buf[x+1] = Conv_YCbCr_Rgb(y_buf[(x<<i)+yuv_interval[i]],y_buf[(x<<i)+1+yuv_interval[i]],cb_buf[((x<<i)+yuv_interval[i])>>1], cr_buf[((x<<i)+yuv_interval[i])>>1]); } else { rgb_data = Conv_YCbCr_Rgb(y_buf[x<<i],y_buf[(x<<i)+1],cb_buf[(x<<i)>>1],cr_buf[(x<<i)>>1]); fb_buf[x] = rgb_data; fb_buf[x+1] = rgb_data>>16; } } fb_buf += fb_xres; y_buf += image_width<<i; cb_buf += (image_width<<i)>>1; cr_buf += (image_width<<i)>>1; } } else { for(y=0; y<h; y++) { for(x=0; x<w; x+=2) { if(i) { fb_buf[(fb_yres-x-1)*fb_xres+y] = Conv_YCbCr_Rgb(y_buf[x<<i],y_buf[(x<<i)+1],cb_buf[(x<<i)>>1],cr_buf[(x<<i)>>1]); fb_buf[(fb_yres-x-2)*fb_xres+y] = Conv_YCbCr_Rgb(y_buf[(x<<i)+yuv_interval[i]],y_buf[(x<<i)+1+yuv_interval[i]],cb_buf[((x<<i)+yuv_interval[i])>>1],cr_buf[((x<<i)+yuv_interval[i])>>1]); } else { rgb_data = Conv_YCbCr_Rgb(y_buf[x<<i],y_buf[(x<<i)+1],cb_buf[(x<<i)>>1],cr_buf[(x<<i)>>1]); fb_buf[(fb_yres-x-1)*fb_xres+y] = rgb_data; fb_buf[(fb_yres-x-2)*fb_xres+y] = fb_buf[x+1] = rgb_data>>16; } } y_buf += image_width<<i; cb_buf += (image_width<<i)>>1; cr_buf += (image_width<<i)>>1; } } } int main(int argc, char *argv[]) { int i, fd, fbfd; struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; __u8 *fb_buf; __u32 screensize; struct video_capability vc; struct video_window vw; struct video_capture vcp; struct video_picture vp; struct video_mbuf vm; __u8 *buf = NULL; fd_set rfds; struct timeval tv; fbfd = open("/dev/fb0", O_RDWR); if (fbfd < 0) { fbfd = open("/dev/fb/0", O_RDWR); if(fbfd<0) { printf("Error: cannot open framebuffer device.\n"); return -1; } } if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) { printf("Error reading fixed information.\n"); close(fbfd); return -1; } if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) { printf("Error reading variable information.\n"); close(fbfd); return -1; } fb_xres = vinfo.xres; fb_yres = vinfo.yres; fb_bpp = vinfo.bits_per_pixel; screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8; fb_buf = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); if ((int)fb_buf == -1) { printf("Error: failed to map framebuffer device to memory.\n"); close(fbfd); return -1; } fd = open("/dev/video0", O_RDONLY); //rd&wr if(fd<0) { fprintf(stderr, "Open camera fail!\n"); close(fbfd); return -1; } else fprintf(stdout, "Open camera success\n"); if(ioctl(fd, VIDIOCGCAP, &vc)<0) printf("VIDIOCGCAP fail\n"); else printf("max width %d, height %d\nmin width %d, height %d\n",vc.maxwidth, vc.maxheight, vc.minwidth, vc.minheight); vw.width = FIXED_SOURCE_WIDTH; vw.height = FIXED_SOURCE_HEIGHT; if(ioctl(fd, VIDIOCSWIN, &vw)<0) printf("VIDIOCSWIN fail\n"); if(ioctl(fd, VIDIOCGWIN, &vw)<0) printf("VIDIOCGWIN fail\n"); else printf("current width %d, height %d\n", vw.width, vw.height); if(!image_width||!image_height) { image_width = fb_xres;//vw.width; image_height = fb_yres;//vw.height; } vcp.width = image_width; vcp.height = image_height; if(ioctl(fd, VIDIOCGCAPTURE, &vcp)<0) printf("VIDIOCGCAPTURE fail\n"); else printf("capture width %d, height %d\n", vcp.width, vcp.height); vp.palette = VIDEO_PALETTE_YUV422P;//modify by lzj //vp.palette = optional_image_format[image_format].palette; if(ioctl(fd, VIDIOCSPICT, &vp)<0) printf("VIDIOCSPICT fail\n"); if(ioctl(fd, VIDIOCGPICT, &vp)<0) printf("VIDIOCGPICT fail\n"); else printf("current palette %d\n", vp.palette); if(ioctl(fd, VIDIOCGMBUF, &vm)<0) printf("VIDIOCGMBUF fail\n"); else { printf("current camera buffer size %d, total frames %d\n", vm.size, vm.frames); buf = (__u8 *)mmap(0, vm.size, PROT_READ, MAP_SHARED, fd, 0); if((int)buf==-1) { printf("mmap camera fail!\n"); } else puts("mmap camera ok.\n"); } printf("buffer at 0x%08x\n", (int)buf); printf("now start capture...\n"); printf("please input 'e'or'E' to exit\n"); if(ioctl(fd, VIDIOCCAPTURE, VIDEO_START)<0) { printf("VIDIOCCAPTURE fail\n"); } while(1) { FD_ZERO(&rfds); FD_SET(0, &rfds); //FD_SET(fd, &rfds); tv.tv_sec = 0; tv.tv_usec = 0; select(1, &rfds, NULL, NULL, &tv); if(FD_ISSET(0, &rfds)) { char cmd; scanf("%c",&cmd); getchar(); if(cmd=='e'||cmd=='E') break; } /* //就是这一段,没看懂,因为驱动里面的read还没看懂 if(FD_ISSET(fd, &rfds)) { i = read(fd, buf, 0); if(i<0) { printf("read fail!%d\n", i); break; } }*/ //modify by lzj show_cam_img(fb_buf, buf,buf+image_width*image_height,buf+(image_width*image_height*3)/2); } close(fd); munmap(buf, vm.size); munmap(fb_buf, screensize); close(fbfd); return 0; }