操作摄像头是基于V4L2提供的系统调用,步骤大体如下:
1、打开设备 open
2、查询设备信息和能力,用到的结构体为struct v4l2_capability
用到的ioctl命令为 VIDIOC_QUERYCAP
struct v4l2_capability {
__u8 driver[16]; /* i.e. "bttv" */
__u8 card[32]; /* i.e. "Hauppauge WinTV" */
__u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
__u32 version; /* should use KERNEL_VERSION() */
__u32 capabilities; /* Device capabilities */
__u32 reserved[4];
};
void capabilityCamera()
{
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
printf("--------------capability------------------\n");
printf("driver:%s \ncard:%s \ncapabilities:%x\n",cap.driver,cap.card,cap.capabilities);
}
保证设备使用的driver为uvcvideo。
3、查看设备支持的数据格式
用到的结构体为 struct v4l2_fmtdesc
ioctl命令为:VIDIOC_ENUM_FMT
struct v4l2_fmtdesc {
__u32 index; /* Format number */
enum v4l2_buf_type type; /* buffer type */
__u32 flags;
__u8 description[32]; /* Description string */
__u32 pixelformat; /* Format fourcc */
__u32 reserved[4];
};
void enumfmtCamera()
{
int ret;
int i;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* type全部使用V4L2_BUF_TYPE_VIDEO_CAPTURE */
printf("-------------VIDIOC_ENUM_FMT--------------\n");
/* 循环输出所有支持的格式 */
while((ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) != -1)
{
printf("index:%d \npixelformat:%c%c%c%c \ndescription:%s\n",fmtdesc.index, fmtdesc.pixelformat&0xff,(fmtdesc.pixelformat>>8)&0xff,(fmtdesc.pixelformat>>16)&0xff,
(fmtdesc.pixelformat>>24)&0xff,fmtdesc.description);
fmtdesc.index++;
}
}
执行这个函数,输出摄像头支持的格式。这是我的摄像头格式,支持MJPEG和YUV4:2:2两个数据格式输出。我们将使用第二种格式。
4、在使用摄像头前,要设置好视频格式
用到结构体为struct v4l2_format
用到的ioctl命令为:VIDIOC_S_FMT
struct v4l2_format {
enum v4l2_buf_type type; /* 类型设置为V4L2_BUF_TYPE_VIDEO_CAPTURE */
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
int setfmtCamera()
{
int ret;
struct v4l2_format format;
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = width;
format.fmt.pix.height = height; /* 设置摄像头数据的宽和高 */
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; /* 设置为yuyv格式数据 */
format.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd, VIDIOC_S_FMT, &format);
if(ret < 0){
printf("VIDIOC_S_FMT fail\n");
return -1;
}
/* 设置好格式只有,get一下格式,看看是否设置成功 */
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &format);
if(ret < 0)
{
printf("VIDIOC_G_FMT fail\n");
return -1;
}
printf("-----------------VIDIOC_G_FMT----------------------\n");
printf("width:%d \nheight:%d \ntype:%x pixelformat:%c%c%c%c\n",format.fmt.pix.width,format.fmt.pix.height,
format.type,format.fmt.pix.pixelformat&0xff,(format.fmt.pix.pixelformat>>8)&0xff,(format.fmt.pix.pixelformat>>16)&0xff,
(format.fmt.pix.pixelformat>>24)&0xff);
return 0;
}
代码中的宽度和高度可以通过lsusb -v -d 摄像头产商id:设备id 查考到具体信息。此处使用640*480,大多摄像头都支持。
5、申请内存缓冲区并进行内存映射到用户空间
用到的结构体为:struct v4l2_requestbuffers struct v4l2_buffer
ioctl命令为:VIDIOC_REQBUFS VIDIOC_QUERYBUF
struct v4l2_requestbuffers {
__u32 count;
enum v4l2_buf_type type;
enum v4l2_memory memory;
__u32 reserved[2];
};
申请缓冲区的数量一般不止一个,但也通常不超过5个。申请三或四个合适。
int initmmap()
{
struct v4l2_requestbuffers reqbuf;
int i, ret;
reqbuf.count = videocount;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
/* 申请缓冲区 */
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf);
if(0 != ret){
printf("VIDIOC_REQBUFS fail\n");
return -1;
}
/* 查询缓冲区分配的地址并进行映射 */
printf("----------------mmap----------------\n");
for(i =0; i < reqbuf.count; i++){
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
framebuf[i].length = buf.length;
framebuf[i].start = mmap(NULL, buf.length, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, buf.m.offset);
if(framebuf[i].start == MAP_FAILED){
perror("mmap fail.\n");
return -1;
}
printf("start:%x length:%d\n",(unsigned int)framebuf[i].start,framebuf[i].length);
}
return 0;
}
6、开始视频流采集
使用到的结构体为:struct v4l2_buffer
使用的ioctl命令为:VIDIOC_QBUF VIDIOC_STREAMON
static int startcap()
{
int ret = -1, i = 0;
/* 将缓冲区放入队列,以排队获取视频数据 */
for(i=0;i < videocount; i++){
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ret = ioctl(fd, VIDIOC_QBUF, &buf);
if(0 != ret){
perror("VIDIOC_QBUF fail.\n");
return -1;
}
}
/* 开始视频流传输 */
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
return 0;
}
7、使用poll查询缓冲区时候有数据了,有就取出缓冲区,读出其中数据。然后将YUV422数据格式转成RGB格式,进行保存。读完数据后将缓冲区再次放入队列。
8、关闭流传输,释放申请的内存,关闭设备。
操作结束。
可以使用下面命令拷贝源码:
git clone https://github.com/zhangdalei/v4l2-.git
整体代码的功能是从摄像头读取数据并保存在连续的10张bmp图片中。代码在我的Ubuntu14.04使用时,读取数据有问题,图片不完整。但在我的开发板上,读取并保存为正常的图片。初步判断是我的摄像头和我的Unbuntu中的驱动不太适配,驱动上报的数据就有问题。因为我用ubuntu自带的摄像头软件调摄像头图像也不完整。
/**
操作步骤:
1. 打开设备文件。 int fd=open(”/dev/video0″,O_RDWR);
2. 取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability
3. 选择视频输入,一个视频设备可以有多个视频输入。VIDIOC_S_INPUT,struct v4l2_input
4. 设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。
VIDIOC_S_STD,VIDIOC_S_FMT,struct v4l2_std_id,struct v4l2_format
5. 向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers
6. 将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。mmap
7. 将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer
8. 开始视频的采集。VIDIOC_STREAMON
9. 出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF
10. 将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF
11. 停止视频的采集。VIDIOC_STREAMOFF
12. 关闭视频设备。close(fd);
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma pack(1)
typedef struct BITMAPFILEHEADER
{
unsigned short bfType;//位图文件的类型,
unsigned long bfSize;//位图文件的大小,以字节为单位
unsigned short bfReserved1;//位图文件保留字,必须为0
unsigned short bfReserved2;//同上
unsigned long bfOffBits;//位图阵列的起始位置,以相对于位图文件 或者说是头的偏移量表示,以字节为单位
} BITMAPFILEHEADER;
#pragma pack()
typedef struct BITMAPINFOHEADER//位图信息头类型的数据结构,用于说明位图的尺寸
{
unsigned long biSize;//位图信息头的长度,以字节为单位
unsigned long biWidth;//位图的宽度,以像素为单位
unsigned long biHeight;//位图的高度,以像素为单位
unsigned short biPlanes;//目标设备的级别,必须为1
unsigned short biBitCount;//每个像素所需的位数,必须是1(单色),4(16色),8(256色)或24(2^24色)之一
unsigned long biCompression;//位图的压缩类型,必须是0-不压缩,1-BI_RLE8压缩类型或2-BI_RLE4压缩类型之一
unsigned long biSizeImage;//位图大小,以字节为单位
unsigned long biXPelsPerMeter;//位图目标设备水平分辨率,以每米像素数为单位
unsigned long biYPelsPerMeter;//位图目标设备垂直分辨率,以每米像素数为单位
unsigned long biClrUsed;//位图实际使用的颜色表中的颜色变址数
unsigned long biClrImportant;//位图显示过程中被认为重要颜色的变址数
} BITMAPINFOHEADER;
void yuv422_2_rgb();
static void yuyv422toBGRY(unsigned char *src);
#define videocount 3
#define JEPG_FILE "yuyv.jpg"
#define RGB_FILE "rgb.bmp"
static int fd = 0;
static int width = 640;
static int height = 480;
static struct v4l2_fmtdesc fmtdesc;
struct videobuffer{
unsigned int length;
void* start;
};
static struct videobuffer framebuf[videocount];
static struct v4l2_buffer buf;
unsigned char* starter;
unsigned char* newBuf;
struct BITMAPFILEHEADER bfh;
struct BITMAPINFOHEADER bih;
void create_bmp_header()
{
bfh.bfType = (unsigned short)0x4D42;
bfh.bfSize = (unsigned long)(14 + 40 + width * height*3);
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits= (unsigned long)(14 + 40);
bih.biBitCount = 24;
bih.biWidth = width;
bih.biHeight = height;
bih.biSizeImage = width * height * 3;
bih.biClrImportant = 0;
bih.biClrUsed = 0;
bih.biCompression = 0;
bih.biPlanes = 1;
bih.biSize = 40;//sizeof(bih);
bih.biXPelsPerMeter = 0x00000ec4;
bih.biYPelsPerMeter=0x00000ec4;
}
/* 1\打开设备 */
int openCamera(int id)
{
char devicename[12];;
sprintf(devicename,"/dev/video%d",id);
//fd = open("/dev/video1", O_RDWR | O_NONBLOCK, 0);
fd = open(devicename, O_RDWR | O_NONBLOCK, 0);
if(fd <0 ){
printf("open video0 fail.\n");
return -1;
}
return 0;
}
/* 2、查看设备能力 */
void capabilityCamera()
{
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
printf("--------------capability------------------\n");
printf("driver:%s \ncard:%s \ncapabilities:%x\n",cap.driver,cap.card,cap.capabilities);
}
/* 3、查看支持的数据格式 */
void enumfmtCamera()
{
int ret;
int i;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("-------------VIDIOC_ENUM_FMT--------------\n");
while((ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) != -1)
{
printf("index:%d \npixelformat:%c%c%c%c \ndescription:%s\n",fmtdesc.index, fmtdesc.pixelformat&0xff,(fmtdesc.pixelformat>>8)&0xff,(fmtdesc.pixelformat>>16)&0xff,
(fmtdesc.pixelformat>>24)&0xff,fmtdesc.description);
fmtdesc.index++;
}
}
/* 4、设置视频格式 VIDIOC_S_FMT struct v4l2_format */
int setfmtCamera()
{
int ret;
struct v4l2_format format;
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = width;
format.fmt.pix.height = height;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 设置为yuyv格式数据
format.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd, VIDIOC_S_FMT, &format);
if(ret < 0){
printf("VIDIOC_S_FMT fail\n");
return -1;
}
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &format);
if(ret < 0)
{
printf("VIDIOC_G_FMT fail\n");
return -1;
}
printf("-----------------VIDIOC_G_FMT----------------------\n");
printf("width:%d \nheight:%d \ntype:%x pixelformat:%c%c%c%c\n",format.fmt.pix.width,format.fmt.pix.height,
format.type,format.fmt.pix.pixelformat&0xff,(format.fmt.pix.pixelformat>>8)&0xff,(format.fmt.pix.pixelformat>>16)&0xff,
(format.fmt.pix.pixelformat>>24)&0xff);
return 0;
}
/* 5、申请内存作为缓冲区VIDIOC_REQBUFS struct v4l2_requestbuffers,
* 查询缓冲区状态后映射到用于空间 VIDIOC_QUERYBUF struct v4l2_buffer mmap,然后将缓冲区放入队列 VIDIOC_QBUF
*/
int initmmap()
{
struct v4l2_requestbuffers reqbuf;
int i, ret;
reqbuf.count = videocount;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuf);
if(0 != ret){
printf("VIDIOC_REQBUFS fail\n");
return -1;
}
//v4l2_buffer
printf("----------------mmap----------------\n");
for(i =0; i < reqbuf.count; i++){
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
framebuf[i].length = buf.length;
framebuf[i].start = mmap(NULL, buf.length, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, buf.m.offset);
if(framebuf[i].start == MAP_FAILED){
perror("mmap fail.\n");
return -1;
}
printf("start:%x length:%d\n",(unsigned int)framebuf[i].start,framebuf[i].length);
}
return 0;
}
/* 6、开始采集视频数据
* 将缓冲区如队列 VIDIOC_QBUF struct v4l2_buffer
* 开始流传输 VIDIOC_STREAMON
*/
static int startcap()
{
int ret = -1, i = 0;
for(i=0;i < videocount; i++){
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ret = ioctl(fd, VIDIOC_QBUF, &buf);
if(0 != ret){
perror("VIDIOC_QBUF fail.\n");
return -1;
}
}
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
return 0;
}
/* 6、判断缓冲区是否有数据 使用poll函数
* 缓冲区有数据后取出队列 VIDIOC_DQBUF
*/
static int readfram()
{
struct pollfd pollfd;
int ret,i;
char filename[50];
for(i =0; i<10 ;i++){
memset(&pollfd, 0, sizeof(pollfd));
pollfd.fd = fd;
pollfd.events = POLLIN;
ret = poll(&pollfd, 1, 800);
if(-1 == ret){
perror("VIDIOC_QBUF fail.\n");
return -1;
}else if(0 == ret){
printf("poll time out\n");
continue;
}
printf("-------------poll success---------------\n");
// static struct v4l2_buffer buf;
if(pollfd.revents & POLLIN){
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_DQBUF, &buf);
if(0 != ret){
perror("VIDIOC_QBUF fail.\n");
return -1;
}
//v4l2_buffer
// 直接保存的yuyv数据
//FILE *file = fopen(YUYV_FILE, "wb");
//ret = fwrite((char*)framebuf[buf.index].start, 1, buf.length, file);
//fclose(file);
// RGB格式数据
starter = (unsigned char*)framebuf[buf.index].start;
newBuf = (unsigned char*)calloc((unsigned int)(framebuf[buf.index].length*3/2),sizeof(unsigned char));
yuv422_2_rgb();
create_bmp_header();
//yuyv422toBGRY(starter);
sprintf(filename,"rgb%d.bmp",i);
FILE *file1 = fopen(filename, "wb");
fwrite(&bfh,sizeof(bfh),1,file1);
fwrite(&bih,sizeof(bih),1,file1);
fwrite(newBuf, 1, buf.length*3/2, file1);
//fwrite(rgb, 1, width*height*3, file1);
fclose(file1);
ret = ioctl(fd, VIDIOC_QBUF, &buf);
}
}
return ret;
}
/* 最后关闭摄像头数据流和设备 */
static void closeCamera()
{
int ret=-1, i;
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_STREAMOFF, &type);
if(0 != ret){
perror("VIDIOC_QBUF fail.\n");
return ;
}
for(i = 0; i < videocount; i++){
munmap(framebuf[i].start, framebuf[i].length);
}
close(fd);
}
void yuv422_2_rgb()
{
unsigned char YUV[4],RGB[6];
int i,j,k=0;
unsigned int location = 0;
for(i = 0;i < framebuf[buf.index].length; i+=4)
{
YUV[0] = starter[i]; // y
YUV[1] = starter[i+1]; // u
YUV[2] = starter[i+2]; // y
YUV[3] = starter[i+3]; // v
if(YUV[0] < 0){
RGB[0]=0;
RGB[1]=0;
RGB[2]=0;
}else{
RGB[0] = YUV[0] + 1.772*(YUV[1]-128); // b
RGB[1] = YUV[0] - 0.34414*(YUV[1]-128) - 0.71414*(YUV[3]-128); // g
RGB[2] = YUV[0 ]+ 1.402*(YUV[3]-128); // r
}
if(YUV[2] < 0)
{
RGB[3]=0;
RGB[4]=0;
RGB[5]=0;
}else{
RGB[3] = YUV[2] + 1.772*(YUV[1]-128); // b
RGB[4] = YUV[2] - 0.34414*(YUV[1]-128) - 0.71414*(YUV[3]-128); // g
RGB[5] = YUV[2] + 1.402*(YUV[3]-128) ; // r
}
for(j = 0; j < 6; j++){
if(RGB[j] < 0)
RGB[j] = 0;
if(RGB[j] > 255)
RGB[j] = 255;
}
//请记住:扫描行在位图文件中是反向存储的!
if(k%(width*3)==0)//定位存储位置
{
location=(height-k/(width*3))*(width*3);
}
bcopy(RGB,newBuf+location+(k%(width*3)),sizeof(RGB));
k+=6;
}
return ;
}
int main(int argc, char* argv[])
{
if(argc != 2){
printf("usage:%s [0|1] \n",argv[0]);
return -1;
}
printf("use video %s\n",argv[1]);
if(!strcmp(argv[1], "0")){
printf("video 0");
openCamera(0);
}
else {
printf("video 1\n");
openCamera(4);
}
capabilityCamera();
enumfmtCamera();
setfmtCamera();
initmmap();
startcap();
readfram();
closeCamera();
return 0;
}