ov9650学习(3)

参考了飞凌公司提供的测试源码,自己稍加修改,可以实现摄像头的在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;
}

你可能感兴趣的:(ov9650学习(3))