最近打算做一个H.264的图像传输设备,第一步当然是采集图像,了解过相关知识后得知图像采集需要用到V4L2,于是在网上找关于V4L2的资料,昨天终于把V4L2看的差不多,并且把网上的程序封装成了符合我的习惯的一个C++类。关于V4L2采集图像在网上有很多博客讲的都很详细,而我也水平有限,就不写V4L2的详细采集过程了,现在分享几点学习V4L2采集图像时遇到的一些不容易懂或者易错的地方,相信对于初学者来说这些困惑点也会遇到。
首先放上一张V4L2采集图像的流程图,把这个图理解透了也就差不多了。
struct v4l2_capability里的capabilities的BIT0和BIT26很重要,这两个必须是1,否则是没法采集到图像的。可以使用这样的代码来验证一下:
//judge wherher or not to be a video-get device
if (!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE))
{
printf("The Current device is not a video capture device\n");
exit(EXIT_FAILURE);
}
//judge whether or not to supply the form of video stream
if (!(cap.capabilities & V4L2_CAP_STREAMING))
{
printf("The Current device does not support streaming i/o\n");
exit(EXIT_FAILURE);
}
第二点要说的就是mmap这一步,这一步会建立起内核与用户之间的联系,在打开设备的视频流后,只要第四步里申请到的队列不是满的,设备就会采集图像并送到队列中,而用户是不能访问内核的,所以我们需要mmap来建立起这个联系,我们才能得到图像数据。当然可以用read来读取,但是操作IO效率是很慢的。
for (n_buffer = 0; n_buffer < reqbufs.count; n_buffer++)
{
//stand for a frame
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffer;
//check the information of the kernel cache requested
if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
{
perror("xioctl VIDIOC_QUERYBUF");
exit(EXIT_FAILURE);
}
usr_buf[n_buffer].length = buf.length;
usr_buf[n_buffer].start =
(char *)mmap(
NULL,
buf.length,
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
buf.m.offset
);
if (MAP_FAILED == usr_buf[n_buffer].start)
{
perror("mmap");
exit(EXIT_FAILURE);
}
}
在开始采集图像这一步里,程序所做的工作有两点。
一是把申请到的帧缓存区送到视频采集队列,也就是第四步申请到的帧缓冲区
//place the kernel cache to a queue
for (i = 0; i < n_buffer; i++)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)){
perror("xioctl VIDIOC_QBUF");
exit(EXIT_FAILURE);
}
}
二是打开视频流数据的采集,这个简单
enum v4l2_buf_type type;
//start capture data
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
{
printf("i=%d.\n", i);
perror("VIDIOC_STREAMON");
close(fd);
exit(EXIT_FAILURE);
}
在我们需要得到图像的时候,首先要从队列中取出一帧,得到信息后再把这一帧放回队列中就行。
int CameraClass::GetOneImage(struct buffer_type *Image)
{
struct v4l2_buffer buf;
unsigned int i;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
//put cache from queue
if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf))
{
perror("xioct VIDIOC_DQBUF");
exit(EXIT_FAILURE);
}
assert(buf.index < n_buffer);
//read process space's data to a file
(*Image).start = usr_buf[buf.index].start;
(*Image).length = usr_buf[buf.index].length;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
{
perror("xioctl VIDIOC_QBUF");
exit(EXIT_FAILURE);
}
return 0;
}
最后附上完整的代码,已经测试成功。刚刚入门嵌入式,还有许多地方不太清楚,真诚欢迎批评指正!
以下是cpp文件
#include "camera_v4l2.h"
CameraClass::CameraClass(char *DevicePath, int Width, int Height)
{
memcpy(dev_name, DevicePath, strlen(DevicePath));
width = Width;
height = Height;
}
CameraClass::~CameraClass()
{
}
int CameraClass::xioctl(int fd, int request, void * arg)
{
int r;
do
r = ioctl(fd, request, arg);
while (-1 == r && EINTR == errno); //如果是被信号打断就重新请求io
return r;
}
int CameraClass::InitCamera(void)
{
fd = open(dev_name, O_RDWR); //打开设备文件
if (fd < 0)
{
perror("open dev_name");
exit(EXIT_FAILURE);
}
//decive fuction, such as video input
struct v4l2_capability cap;
//frame format
struct v4l2_format tv_fmt;
//detail control value
struct v4l2_fmtdesc fmt;
int ret;
//get the format of video supply
memset(&fmt, 0, sizeof(fmt));
fmt.index = 0;
//supply to image capture
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// show all format of supply
printf("Support format:\n");
while (-1 != xioctl(fd, VIDIOC_ENUM_FMT, &fmt))
{
fprintf(stdout, "%d.%s\n", ++fmt.index, fmt.description);
}
//check video decive driver capability
ret = xioctl(fd, VIDIOC_QUERYCAP, &cap);
if (ret < 0){
perror("xioctl VIDIOC_QUERYCAP");
exit(EXIT_FAILURE);
}
fprintf(stdout, "driver is %s!\n", cap.driver);
fprintf(stdout, "card is %s!\n", cap.card);
fprintf(stdout, "bus_info is %s!\n", cap.bus_info);
fprintf(stdout, "version is %d!\n", cap.version);
fprintf(stdout, "capabilities is %d!\n", cap.capabilities);
//judge wherher or not to be a video-get device
if (!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE))
{
printf("The Current device is not a video capture device\n");
exit(EXIT_FAILURE);
}
//judge whether or not to supply the form of video stream
if (!(cap.capabilities & V4L2_CAP_STREAMING))
{
printf("The Current device does not support streaming i/o\n");
exit(EXIT_FAILURE);
}
//set the form of camera capture data
tv_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tv_fmt.fmt.pix.width = width;
tv_fmt.fmt.pix.height = height;
tv_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //此处应该与设备所支持的格式相匹配
tv_fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (xioctl(fd, VIDIOC_S_FMT, &tv_fmt)< 0)
{
perror("xioctl VIDIOC_S_FMT");
exit(EXIT_FAILURE);
}
ret = xioctl(fd, VIDIOC_G_FMT, &tv_fmt); //打印出来检测一下
if (ret < 0)
{
perror("xioctl VIDIO_G_FMT");
return ret;
}
// Print Stream Format
printf("Stream Format Informations:\n");
printf(" type: %d\n", tv_fmt.type);
printf(" width: %d\n", tv_fmt.fmt.pix.width);
printf(" height: %d\n", tv_fmt.fmt.pix.height);
char fmtstr[8];
memset(fmtstr, 0, 8);
memcpy(fmtstr, &tv_fmt.fmt.pix.pixelformat, 4);
printf(" pixelformat: %s\n", fmtstr);
printf(" field: %d\n", tv_fmt.fmt.pix.field);
printf(" bytesperline: %d\n", tv_fmt.fmt.pix.bytesperline);
printf(" sizeimage: %d\n", tv_fmt.fmt.pix.sizeimage);
printf(" colorspace: %d\n", tv_fmt.fmt.pix.colorspace);
printf(" priv: %d\n", tv_fmt.fmt.pix.priv);
printf(" raw_date: %s\n", tv_fmt.fmt.raw_data);
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));
reqbufs.count = 3;
reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbufs.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl(fd, VIDIOC_REQBUFS, &reqbufs)){
perror("xioctl VIDIOC_REQBUFS");
exit(EXIT_FAILURE);
}
n_buffer = reqbufs.count;
fprintf(stdout, "n_buffer = %d\n", n_buffer);
usr_buf = (struct buffer_type*)calloc(reqbufs.count, sizeof(usr_buf));
if (usr_buf == NULL){
printf("Out of memory\n");
exit(-1);
}
//map kernel cache to user process
for (n_buffer = 0; n_buffer < reqbufs.count; n_buffer++)
{
//stand for a frame
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffer;
//check the information of the kernel cache requested
if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
{
perror("xioctl VIDIOC_QUERYBUF");
exit(EXIT_FAILURE);
}
usr_buf[n_buffer].length = buf.length;
usr_buf[n_buffer].start =
(char *)mmap(
NULL,
buf.length,
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
buf.m.offset
);
if (MAP_FAILED == usr_buf[n_buffer].start)
{
perror("mmap");
exit(EXIT_FAILURE);
}
}
return 0;
}
int CameraClass::StartCapture(void)
{
unsigned int i;
enum v4l2_buf_type type;
//place the kernel cache to a queue
for (i = 0; i < n_buffer; i++)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)){
perror("xioctl VIDIOC_QBUF");
exit(EXIT_FAILURE);
}
}
//start capture data
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
{
printf("i=%d.\n", i);
perror("VIDIOC_STREAMON");
close(fd);
exit(EXIT_FAILURE);
}
return 0;
}
int CameraClass::GetOneImage(struct buffer_type *Image)
{
struct v4l2_buffer buf;
unsigned int i;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
//put cache from queue
if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf))
{
perror("xioct VIDIOC_DQBUF");
exit(EXIT_FAILURE);
}
assert(buf.index < n_buffer);
//read process space's data to a file
(*Image).start = usr_buf[buf.index].start;
(*Image).length = usr_buf[buf.index].length;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
{
perror("xioctl VIDIOC_QBUF");
exit(EXIT_FAILURE);
}
return 0;
}
int CameraClass::StopCapture(void)
{
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
{
perror("xioctl VIDIOC_STREAMOFF");
exit(EXIT_FAILURE);
}
return 0;
}
int CameraClass::CloseCamera(void)
{
unsigned int i;
for (i = 0; i < n_buffer; i++)
{
if (-1 == munmap(usr_buf[i].start, usr_buf[i].length))
{
exit(EXIT_FAILURE);
}
}
if (-1 == close(fd))
{
perror("close");
exit(EXIT_FAILURE);
}
return 0;
}
以下是头文件
#ifndef __CAMERA_V4L2_H_
#define __CAMERA_V4L2_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct buffer_type
{
void *start; //用户空间与文件映射的地址
size_t length; //由Width和Height决定
};
class CameraClass
{
public:
CameraClass(char *DevicePath, int Width, int Height);
~CameraClass();
public:
int InitCamera(void); //初始化,包括打开文件,查询设备能力,映射至内核空间
int StartCapture(void); //开始采集图像,在内核中建立对应buf
int GetOneImage(struct buffer_type *Image);
int StopCapture(void);
int CloseCamera(void);
private:
char dev_name[50]; //设备路径
int fd;
int width; //图像长
int height; //图像宽
struct buffer_type *usr_buf; //用户空间的缓存
unsigned int n_buffer; //FIFO长度
int xioctl(int fd, int request, void *arg); //避免信号打断导致ioctl失败
};
#endif
以下是测试用文件
#include "camera_v4l2.h"
#include "stdio.h"
int main(int argc, char *argv[])
{
if(argc != 2)
{
fprintf(stderr, "Input picture name:\n");
return -1;
}
CameraClass *MyLogitech = new CameraClass("/dev/video4", 1280, 960);
if(0 == MyLogitech->InitCamera())
fprintf(stdout, "InitCamera succeed!\n");
if(0 == MyLogitech->StartCapture())
fprintf(stdout, "StartCapture succeed!\n");
struct buffer_type Image;
if(0 == MyLogitech->GetOneImage(&Image))
fprintf(stdout, "GetOneImage succeed!\n");
fprintf(stdout, "Image ptr is %p, length is %d\n", Image.start, Image.length);
int fd = creat(argv[1], 0644);
if(-1 == fd)
{
perror("creat");
}
else
{
int ret = write(fd, Image.start, Image.length);
if(ret == -1)
{
perror("write");
}
ret = close(fd);
if(-1 == ret)
{
perror("close");
}
}
if(0 == MyLogitech->StopCapture())
fprintf(stdout, "StopCapture succeed!\n");
if(0 == MyLogitech->CloseCamera())
fprintf(stdout, "CloseCamera succeed!\n");
return 0;
}