1 ioctl
调用v4l2库需要大量的使用ioctl这个函数,那么什么是ioctl?
一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。例:当你用 read,write不能完成某一功能时,就用 ioctl来操作。配合一些头文件(v4l2-controls.h / videodev2.h),根据命令,实现对摄像头的操作,如:白平衡、聚焦、曝光、饱和度、亮度
2 V4L2采图具体流程
图片转载来源于:https://blog.csdn.net/Windgs_YF/article/details/124382204

总体框架如上图所示
- 打开相机并进行初始化
- 打开设备文件/dev/videoX;
- 查询相机基本信息;
- 设置图像采集格式;
- 申请内存与mmap;
- 循环进行视频流采集工作;
- 开始取流
- 从队列中抓取buffer并读取图像,并对图像进行所需要的操作,把buffer再次入队
3 各阶段代码
打开相机并初始化
1 打开设备文件/dev/videoX
/* 打开设备文件 */
void openCamera(QString url)
{
camHandle = open(url.toStdString().c_str(), O_RDWR);
}
2 查询相机基本信息
/* 查询相机基本信息 */
int query()
{
struct v4l2_capability cap;
if(ioctl(camHandle, VIDIOC_QUERYCAP, &cap) ! = 0)
{
qDebug()<<"ioctl(VIDIOC_QUERYCAP) Failed";
return -1;
}
//检查驱动类型
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) &&
!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE))
{
qDebug()<<"Device is not a video capture.";
return -1;
}
if(!(cap.capabilities & V4L2_CAP_STREAMING))
{
qDebug()<<"Device does not support streaming.";
return -1;
}
if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)
bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
return 0;
}
3 设置图像采集格式
/* 设置采集图像格式 */
int setFormat()
{
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = bufType;
format.fmt.pix.pixelformat = format;
format.fmt.pix.width = WIDTH;
format.fmt.pix.height = HEIGHT;
format.fmt.pix.field = V4L2_FIELD_INTERLACED;
if(ioctl(m_camHandle, VIDIOC_S_FMT, &format) ! = 0)
{
qDebug()<<"ioctl(VIDIOC_S_FMT) Failed";
return -1;
}
return 0;
}
4 申请内存与mmap
/* 申请内存与mmap */
int setMmap()
{
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4; // 选定一个合适的即可
req.type = bufType;
req.memory = V4L2_MEMORY_MMAP;
//申请req.count个缓冲区,并且这些缓冲区的buf.index为0到req.count - 1
if(ioctl(camHandle, VIDIOC_REQBUFS, &req) != 0)
{
qDebug()<<"ioctl(VIDIOC_REQBUFS) Failed";
return -1;
}
buffers = (struct buffer*)calloc(req.count, sizeof(*buffers));
// 进行具体的内存映射,把内核空间的内存映射到我们自己定义的用户空间的内存,这样我们就可以直接操作这块内存
for(int i = 0; i < req.count; i++)
{
struct v4l2_buffer buf;
struct v4l2_plane planes[1];
memset(&buf, 0, sizeof(buf));
memset(&planes, 0, sizeof(planes));
buf.type = m_bufType;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = m_nBufs; // 指定要查询和mmap的缓冲区
if (bufType == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
{
buf.m.planes = planes;
buf.length = 1;
}
// 查询每个缓冲区的元信息,查询得到才可以进行mmap
if(ioctl(camHandle, VIDIOC_QUERYBUF, &buf) != 0)
{
qDebug()<<"ioctl(VIDIOC_QUERYBUF) Failed :"<<errno<<strerror(errno);
return -1;
}
// 根据获取到的内存信息进行映射,如buf.m.offset
if (m_bufType == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
{
buffers[i].length = buf.m.planes[0].length;
buffers[i].point =
mmap(NULL, buf.m.planes[0].length, PROT_READ | PROT_WRITE, MAP_SHARED ,
camHandle, buf.m.planes[0].m.mem_offset);
}
else
{
m_buffers[i].length = buf.length;
m_buffers[i].point =
mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, m_camHandle, buf.m.offset);
}
}
return 0;
}
循环进行视频流采集工作
1 开始取流
/* 开始抓图 */
bool startGrab()
{
enum v4l2_buf_type type;
for(int i = 0; i < 4; ++i)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = bufType;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if(bufType == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
{
struct v4l2_plane planes[1];
buf.m.planes = planes;
buf.length = 1;
}
// 将帧缓冲放入队列中
if(ioctl(m_camHandle, VIDIOC_QBUF, &buf) != 0) // 告诉驱动:“把 i 号缓冲区加入队列”
{
qDebug()<<"ioctl(VIDIOC_QBUF) Failed";
return false;
}
}
type = bufType;
//开始取流
if(ioctl(m_camHandle, VIDIOC_STREAMON, &type) != 0)
{
qDebug()<<"ioctl(VIDIOC_STREAMON) Failed";
return false;
}
return true;
}
由于之前申请buf的时候,确定了申请几块buf,所以这里新建buf的时候,可以指定其index,然后直接把这个新建的buf入队,实际上就是把之前已经分配好的编号为index的buf入队,之后的出队也是同理。
2 从队列中抓取buffer并读取图像,并对图像进行所需要的操作,把buffer再次入队
/* 逐帧读取原始图像数据 */
void grabImg()
{
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(m_camHandle, &fds); // 监听摄像头设备文件描述符
// 设置超时
tv.tv_sec = 2; // 2秒超时
tv.tv_usec = 0;
int ret;
while(1)
{
// 等待数据就绪
ret = select(m_camHandle + 1, &fds, NULL, NULL, &tv);
if (ret == -1) {
qDebug() << "select() error:" << errno << strerror(errno);
break; // 发生错误时退出循环
} else if (ret == 0) {
qDebug() << "select() timeout, no data available.";
continue; // 超时后重新等待
}
// 数据就绪开始抓图
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = bufType;
buf.memory = V4L2_MEMORY_MMAP;
if(bufType == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
{
struct v4l2_plane planes[1];
buf.m.planes = planes;
buf.length = 1;
}
//从队列中抓取图像
int ret = ioctl(m_camHandle, VIDIOC_DQBUF, &buf);
if(ret != 0)
{
qDebug()<<"ioctl(VIDIOC_DQBUF) Failed :"<<errno<<strerror(errno);
QThread::usleep(200);
continue;
}
int idx = buf.index; // 获取缓冲区的index
//process(buffers[idx]); 一些需要的操作
//将当前帧数据放入队列
if(ioctl(m_camHandle, VIDIOC_QBUF, &buf) != 0)
{
qDebug()<<"ioctl(VIDIOC_QBUF) Failed"<<errno<<strerror(errno);
}
}
}
常见问题
Q:为什么要进行mmap,而不能直接操作struct v4l2_buffer buf?
A:buf 是一个控制结构体,它的核心作用是:传递元信息(如缓冲区索引 index、数据类型 type、状态 flags 等)。不包含实际视频数据!它只是告诉驱动“我要操作哪个缓冲区”mmap 不是映射 buf 本身的数据,而是将 摄像头硬件的物理内存 映射到用户空间。buf 只是描述这块内存的元信息(如位置、长度),而真正的视频数据由驱动直接写入映射后的内存
Q:buf入队的时候,为什么可以新建一个buf,然后操作这个buf入队即可?
A:buf 是一个控制结构体,它的核心作用是:传递元信息(如缓冲区索引 index),当指定了index时,驱动就知道要操纵哪个对应的mmap的缓冲区,就会把这个缓冲区入队。出队同理



5126

被折叠的 条评论
为什么被折叠?



