在应用层通过v4l2 api将采集的摄像头数据yuv转为rgb后写到帧缓冲区达到预览摄像头的目的,程序运行后切换到tty下就可以看到。我的屏幕是bgra格式的,这点要注意,不同屏幕格式不同,不同r,g,b的偏移通过修改(0x00<<24) | (R0<<16) | (G0<<8) | (B0<<0);中的顺序即可
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <string.h>
#include <malloc.h>
#include <linux/fb.h>
/**
** v4l2在帧缓存区预览摄像头: gcc -std=c99 -o main main.c
**/
static int width;//屏幕宽度
static int height;//屏幕高度
static __u32 *pfb;//屏幕缓冲区指针
static int fb;
static struct fb_fix_screeninfo finfo;
static struct fb_var_screeninfo vinfo;
void fb_fillimg(const __u32* img);
void initFb()
{
int ret = -1;
//打开帧缓冲区设备
fb = open("/dev/fb0", O_RDWR);
if (fb < 0) { perror("open fb0"); return; }
printf("open /dev/fb0 success \n");
//得到固定屏幕信息
ret = ioctl(fb,FBIOGET_FSCREENINFO,&finfo);
//得到可变屏幕信息
ret = ioctl(fb, FBIOGET_VSCREENINFO, &vinfo);
if (ret < 0) { perror("get var info"); return; }
width = vinfo.xres_virtual;
height = vinfo.yres_virtual;
printf("width=%d,height=%d\n",width,height);
//映射帧缓冲区基址
pfb = (__u32*)mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
if (NULL == pfb) { perror("fb0 mmap"); return; }
}
void closeFb()
{
munmap(pfb,finfo.smem_len);
close(fb);
}
/**
** yuv422转bgra
**/
int max(int a,int b){ return a<b? b:a; }
int min(int a,int b){ return a<b? a:b; }
__u32 yuv2rgb(int Y0,int U0,int Y1,int V1,int index)
{
int Y = index? Y1:Y0;
int R0 = max(0,min(255,1.164*(Y-16) + 1.596*(V1-128)));
int G0 = max(0,min(255,1.164*(Y-16) - 0.391*(U0-128) - 0.813*(V1-128)));
int B0 = max(0,min(255,1.164*(Y-16) + 2.018*(U0-128)));
return (0x00<<24) | (R0<<16) | (G0<<8) | (B0<<0);//转为bgra
}
int main()
{
int fd;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
struct v4l2_buffer buf;
__u8* bufStart;//图形缓冲区基址
enum v4l2_buf_type type;
//打开摄像头设备
fd = open("/dev/video0", O_RDWR);
if (fd<0) { perror("open video0"); return -1;}
/**
** 设置摄像头格式,我的UVC摄像头有两个格式:YUV422(YUYV)和MJPEG
** 我的摄像头宽高是640x480
**/
memset( &fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { perror("set format failed\n");return -1; }
//得到实际的格式
if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) { perror("get format failed\n");return -1; }
printf("Picture:Width = %d Height = %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
//申请缓冲区
memset(&req, 0, sizeof (req));
req.count = 1; //缓冲区数量为1
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;//mmap方式
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { perror("request buffer error \n"); return 0-1; }
//缓冲区类型设置
memset( &buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) { perror("VIDIOC_QUERYBUF error\n"); return -1;}
printf("buffer len=%u\n",buf.length);//长度614400=640x480x2
/** 映射得到缓冲区的地址 **/
bufStart = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,MAP_SHARED, fd, buf.m.offset);
if (bufStart == MAP_FAILED)
{
perror("buffers mmap\n");
return -1;
}
/** 将缓冲区放入到循环队列**/
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF error\n"); return -1; }
/** 打开流:stream on,打开流后摄像头驱动就开始往缓冲区缓冲区队列中的缓冲区填入数据了 **/
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) { perror("VIDIOC_STREAMON error\n"); return -1;}
int i=0;
//定义转换后的bgra区域
#define OUT_SIZE 640*480*4
__u32 *out = (__u32*)malloc(OUT_SIZE);//bgra
__u32 *saveout;
if(out==NULL) { perror("out"); return -1;}
__u8 *saveBufStart;
initFb();//初始化帧缓冲区
while(i++<500){ //循环读取500帧
//从循环队列中取出缓冲区
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) { perror("VIDIOC_DQBUF failed.\n"); return -1; }
//将yuv422转为bgra
saveBufStart = bufStart;
saveout=out;
for(int t = 0;t<OUT_SIZE/2;t+=4){
*out++ = yuv2rgb(bufStart[0],bufStart[1],bufStart[2],bufStart[3],0);
*out++ = yuv2rgb(bufStart[0],bufStart[1],bufStart[2],bufStart[3],1);
bufStart += 4;
}
out = saveout;
bufStart = saveBufStart;
//将bgra数据显示到帧缓冲区
fb_fillimg(out);
/** 将缓冲区重新放入循环队列**/
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF error\n"); return -1;}
}
closeFb();
munmap(bufStart,buf.length);
close(fd);
return 0;
}
/**
** 在帧缓冲区居中显示图片
**/
#define imgW 640
#define imgH 480
void fb_fillimg(const __u32* img)
{
unsigned int x, y,cX,cY;
cX = (width-imgW)/2;
cY = (height-imgH)/2;
for (y = cY; y < cY+imgH; y++)
{
for (x = cX; x < cX+imgW; x++)
{
*(pfb + y * width + x) = *(img + (y-cY)*imgW+(x-cX));
}
}
}
预览效果: