简介
linux环境使用V4l2实现摄像头捕捉,界面流畅播放并可以保存图片到本地。
代码
void VideoCapture::run()
{
qDebug() << "VideoCapture start";
// 打开设备
int fd = open("/dev/video0", O_RDWR);
if(fd < 0)
{
qDebug("video设备打开失败\n");
return;
}
else
{
qDebug("video设备打开成功\n");
}
//查看设备是否为视频采集设备
struct v4l2_capability vcap;
ioctl(fd, VIDIOC_QUERYCAP, &vcap);
if (!(V4L2_CAP_VIDEO_CAPTURE & vcap.capabilities))
{
qDebug("No capture video device!\n");
return;
}
// 枚举帧格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
qDebug("Video支持所有格式如下:");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0)
{
qDebug("v4l2_format %d:%s",fmtdesc.index, fmtdesc.description);
fmtdesc.index++;
}
// 枚举分辨率
struct v4l2_frmsizeenum frmsize;
frmsize.index = 0;
frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
qDebug("Video支持分辨率如下:");
while(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0)
{
qDebug("%d frame_size<%d*%d %d>", frmsize.index, frmsize.discrete.width, frmsize.discrete.height, frmsize.pixel_format);
frmsize.index++;
}
// 枚举某分辨率下的帧速率
int pixel_width = 1280;
int pixel_height = 720;
struct v4l2_frmivalenum frmival;
frmival.index = 0;
frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
frmival.pixel_format = V4L2_PIX_FMT_MJPEG;
frmival.width = pixel_width;
frmival.height = pixel_height;
while(ioctl(fd,VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0)
{
qDebug("<%d*%d> support frame_size %dfps", frmival.width, frmival.height, frmival.discrete.denominator / frmival.discrete.numerator);
frmival.index++;
}
// 设置采集格式
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vfmt.fmt.pix.width = pixel_width;
vfmt.fmt.pix.height = pixel_height;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
if(ioctl(fd, VIDIOC_S_FMT, &vfmt) < 0)
{
qDebug("video设置格式失败\n");
return;
}
// 检查设置参数是否生效
if(ioctl(fd, VIDIOC_G_FMT, &vfmt) < 0)
{
qDebug("video获取格式失败\n");
return;
}
// 获取帧信息
struct v4l2_streamparm streamparm;
streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_G_PARM, &streamparm);
qDebug("current frameRate <%d * %d>\n", streamparm.parm.capture.timeperframe.numerator, streamparm.parm.capture.timeperframe.denominator);
// 设置帧信息
if(V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability)
{
streamparm.parm.capture.timeperframe.numerator = 1;
streamparm.parm.capture.timeperframe.denominator = 30;
// if(ioctl(fd, VIDIOC_S_PARM, &streamparm) < 0)
// {
// qDebug("video设置帧率失败 <%d * %d>", streamparm.parm.capture.timeperframe.numerator, streamparm.parm.capture.timeperframe.denominator);
// }
}
// 申请缓冲区空间
struct v4l2_requestbuffers reqbuf;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.count = m_frameCount;
reqbuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0)
{
qDebug("video申请缓冲区失败\n");
return;
}
// 将帧缓冲映射到进程地址空间
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
// 将每一帧对应的缓冲区的起始地址保存在m_userVideoBuf数组中,读取采集数据时,只需直接读取映射区即可
for(buf.index=0; buf.index<m_frameCount; buf.index++)
{
ioctl(fd, VIDIOC_QUERYBUF, &buf);
m_userVideoBuf[buf.index] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
m_userVideoBufSize[buf.index] = buf.length;
if(m_userVideoBuf[buf.index] == MAP_FAILED)
{
qDebug("video mmap failed\n");
return;
}
// 入队操作
if(ioctl(fd, VIDIOC_QBUF, &buf) < 0)
{
qDebug("入队失败\n");
return;
}
}
// 开始采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
qDebug("video开始采集失败");
m_isRun = false;
}
//持续读取图像数据 使用select监听数据
fd_set fds;
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
//图片文件缓存
char *fileBuf = new char[3*1024*1024];
while(m_isRun)
{
FD_ZERO(&fds);
FD_SET(fd, &fds);
int ret = select(fd+1, &fds, NULL, NULL, &tv);
if(ret < 0)
{
qDebug("select io error\n");
break;
}
// 读取帧
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
readbuffer.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd, VIDIOC_DQBUF, &readbuffer) < 0)
{
qDebug("读取帧失败\n");
}
// qDebug() << QDateTime::currentDateTime().toString("hh:mm:ss:zzz");
//重点:帧数据处理
const uchar *data = (const uchar *)m_userVideoBuf[readbuffer.index];
int length = readbuffer.length;
//构造并显示
QImage image = QImage::fromData(data, length);
emit sigUpdateImage(image.copy());
// 再次入队
if(ioctl(fd, VIDIOC_QBUF, &readbuffer) < 0)
{
qDebug("再次入队失败\n");
}
}
// 停止采集
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
{
qDebug("停止采集失败\n");
}
// 释放映射
for(uint i=0; i<m_frameCount; i++)
{
munmap(m_userVideoBuf[i], m_userVideoBufSize[i]);
}
//关闭文件
emit sigUpdateImage(QImage());
close(fd);
delete[] fileBuf;
qDebug() << "VideoCapture stop";
}