liudianwu |
2020-10-21 12:45 |
Qt音视频开发38-USB摄像头解码linux方案
## 一、前言 做嵌入式linux上的开发很多年了,扳手指头算算,也起码9年了,陆陆续续做过很过诸如需要读取外接的USB摄像头或者CMOS摄像机的程序,实时采集视频,将图像传到前端,或者对图像进行人脸分析处理,最开始尝试的就是QCamera来处理,直接歇菜放弃,后面通过搜索发现都说要用v4l2视频框架来进行,于是东搞搞西搞搞尝试了很多次,终于整出来了,前后完善了好几年,无论写什么程序,发现要简简单单的实现基础的功能,都是非常快速而且容易的,但是想要做得好做得精,要花不少的精力时间去完善,适应各种不同的场景,比如就说用v4l2加载摄像头这个,需要指定设备文件来读取,而现场不可能让用户来给你指定,频繁的拔插也会导致设备文件名的改动,所以必须找到一个机制自动寻找你想要的摄像机的设备文件名称,比如开个定时器去调用linux命令来处理,甚至在不同的系统平台上要执行的命令还有些许的区别,如果本地有多个摄像头还需要区分左右之类的时候,那就只能通过断电先后上电顺序次序来区分了。
linux方案处理流程: 1. 调用封装的函数findCamera实时查找摄像头设备文件名。 2. 调用::open函数打开设备文件。 3. 调用封装的函数initCamera初始化摄像头参数(图片格式、分辨率等)。 4. 调用::select函数从缓冲区取出一个缓冲帧。 5. 缓冲帧数据是yuyv格式的,需要转换rgb24再转成QImage。 6. 拿到图片进行绘制、人脸分析等。 7. 关闭设备文件。
## 二、功能特点 1. 同时支持windows、linux、嵌入式linux上的USB摄像头实时采集。 2. 支持多路USB摄像头多线程实时采集。 3. 在嵌入式linux设备上,自动查找USB设备文件并加载。 4. 可手动设置设备文件名称,手动设置后按照手动设置的设备文件加载。 5. 在嵌入式linux设备上支持人脸识别接口,实时绘制人脸框。 6. 具有打开、暂停、继续、关闭、截图等常规功能。 7. 可设置两路OSD标签,分别设置文本、颜色、字号、位置等。 8. 可作为视频监控系统使用。
## 三、效果图 [attachment=21980]
## 四、相关站点 1. 国内站点:[https://gitee.com/feiyangqingyun/QWidgetDemo](https://gitee.com/feiyangqingyun/QWidgetDemo) 2. 国际站点:[https://github.com/feiyangqingyun/QWidgetDemo](https://github.com/feiyangqingyun/QWidgetDemo) 3. 个人主页:[https://blog.csdn.net/feiyangqingyun](https://blog.csdn.net/feiyangqingyun) 4. 知乎主页:[https://www.zhihu.com/people/feiyangqingyun/](https://www.zhihu.com/people/feiyangqingyun/) 5. 体验地址:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
## 五、核心代码 ```cpp void CameraLinux::run() { while (!stopped) { if (!cameraOk) { msleep(10); continue; }
if (isPause) { //这里需要假设正常,暂停期间继续更新时间 lastTime = QDateTime::currentDateTime(); msleep(10); continue; }
QImage image = readImage(); if (!image.isNull()) { if (isSnap) { emit snapImage(image); isSnap = false; }
if (findFaceOne) { findFace(image); }
if (findFaceRect) { image = drawFace(image); }
lastTime = QDateTime::currentDateTime(); emit receiveImage(image); }
msleep(interval); }
this->closeCamera(); this->initData(); }
QDateTime CameraLinux::getLastTime() const { return this->lastTime; }
QString CameraLinux::getCameraName() const { return this->cameraName; }
int CameraLinux::getCameraWidth() const { return this->cameraWidth; }
int CameraLinux::getCameraHeight() const { return this->cameraHeight; }
void CameraLinux::sleep(int msec) { if (msec > 0) { QTime endTime = QTime::currentTime().addMSecs(msec); while (QTime::currentTime() < endTime) { QCoreApplication::processEvents(QEventLoop::AllEvents, 100); } } }
void CameraLinux::initData() { stopped = false; isPause = false; isSnap = false; cameraOk = false; cameraHwnd = -1; errorCount = 0; }
void CameraLinux::readData() { QStringList cameraNames; while (!process->atEnd()) { //逐行读取返回的结果 过滤video开头的是摄像头设备文件 QString line = process->readLine(); if (line.startsWith("video")) { line = line.replace("\n", ""); cameraNames << QString("/dev/%1").arg(line); } }
if (cameraNames.count() > 0) { cameraName = cameraNames.first(); emit receiveCamera(cameraNames); qDebug() << TIMEMS << cameraNames; } }
bool CameraLinux::initCamera() { //如果没有指定设备文件名称(默认auto)则查找 if (cameraName == "auto") { findCamera(); }
//延时判断是否获取到了设备文件 sleep(300); return openCamera(); }
void CameraLinux::findCamera() { if (process->state() == QProcess::NotRunning) { process->start("ls /dev/"); } }
bool CameraLinux::openCamera() { #ifdef Q_OS_LINUX if (cameraName.length() > 5) { cameraHwnd = ::open(cameraName.toUtf8().data(), O_RDWR | O_NONBLOCK, 0); }
if (cameraHwnd < 0) { qDebug() << TIMEMS << "open camera error"; return false; }
//查询设备属性 struct v4l2_capability capability; if (::ioctl(cameraHwnd, VIDIOC_QUERYCAP, &capability) < 0) { qDebug() << TIMEMS << "error in VIDIOC_QUERYCAP"; ::close(cameraHwnd); return false; }
if (!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { qDebug() << TIMEMS << "it is not a video capture device"; ::close(cameraHwnd); return false; }
if (!(capability.capabilities & V4L2_CAP_STREAMING)) { qDebug() << TIMEMS << "it can not streaming"; ::close(cameraHwnd); return false; }
if (capability.capabilities == 0x4000001) { qDebug() << TIMEMS << "capabilities" << "V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING"; }
//设置视频输入源 int input = 0; if (::ioctl(cameraHwnd, VIDIOC_S_INPUT, &input) < 0) { qDebug() << TIMEMS << "error in VIDIOC_S_INPUT"; ::close(cameraHwnd); return false; }
//设置图片格式和分辨率 struct v4l2_format format; format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //多种格式 V4L2_PIX_FMT_YUV420 V4L2_PIX_FMT_YUYV(422) V4L2_PIX_FMT_RGB565 format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //部分硬件花屏要设置成 V4L2_FIELD_NONE format.fmt.pix.field = V4L2_FIELD_INTERLACED; format.fmt.pix.width = cameraWidth; format.fmt.pix.height = cameraHeight;
int bpp = 16; //format.fmt.pix.bytesperline = width * bpp / 8; //format.fmt.pix.sizeimage = cameraWidth * cameraHeight * bpp / 8;
if (::ioctl(cameraHwnd, VIDIOC_S_FMT, &format) < 0) { ::close(cameraHwnd); return false; }
//查看图片格式和分辨率,判断是否设置成功 if (::ioctl(cameraHwnd, VIDIOC_G_FMT, &format) < 0) { qDebug() << TIMEMS << "error in VIDIOC_G_FMT"; ::close(cameraHwnd); return false; }
//重新打印下宽高看下是否真正设置成功 struct v4l2_pix_format pix = format.fmt.pix; quint32 pixelformat = pix.pixelformat; qDebug() << TIMEMS << "cameraWidth" << cameraWidth << "cameraHeight" << cameraHeight << "width" << pix.width << "height" << pix.height; qDebug() << TIMEMS << "pixelformat" << QString("%1%2%3%4").arg(QChar(pixelformat & 0xFF)).arg(QChar((pixelformat >> 8) & 0xFF)).arg(QChar((pixelformat >> 16) & 0xFF)).arg(QChar((pixelformat >> 24) & 0xFF));
//重新设置宽高为真实的宽高 cameraWidth = pix.width; cameraHeight = pix.height;
//设置帧格式 struct v4l2_streamparm streamparm; streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; streamparm.parm.capture.timeperframe.numerator = 1; streamparm.parm.capture.timeperframe.denominator = 25; streamparm.parm.capture.capturemode = 0;
if (::ioctl(cameraHwnd, VIDIOC_S_PARM, &streamparm) < 0) { qDebug() << TIMEMS << "error in VIDIOC_S_PARM"; ::close(cameraHwnd); return false; }
if (::ioctl(cameraHwnd, VIDIOC_G_PARM, &streamparm) < 0) { qDebug() << TIMEMS << "error in VIDIOC_G_PARM"; ::close(cameraHwnd); return false; }
//申请和管理缓冲区 struct v4l2_requestbuffers requestbuffers; requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; requestbuffers.memory = V4L2_MEMORY_MMAP; requestbuffers.count = 1;
if (::ioctl(cameraHwnd, VIDIOC_REQBUFS, &requestbuffers) < 0) { qDebug() << TIMEMS << "error in VIDIOC_REQBUFS"; ::close(cameraHwnd); return false; }
buff_yuv422 = (uchar *)malloc(cameraWidth * cameraHeight * bpp / 8); buff_yuv420 = (uchar *)malloc(cameraWidth * cameraHeight * bpp / 8); buff_rgb24 = (uchar *)malloc(cameraWidth * cameraHeight * 24 / 8);
buff_img = (ImgBuffer *)calloc(1, sizeof(ImgBuffer)); if (buff_img == NULL) { qDebug() << TIMEMS << "error in calloc"; ::close(cameraHwnd); return false; }
struct v4l2_buffer buffer; for (int index = 0; index < 1; index++) { buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buffer.memory = V4L2_MEMORY_MMAP; buffer.index = index;
if (::ioctl(cameraHwnd, VIDIOC_QUERYBUF, &buffer) < 0) { qDebug() << TIMEMS << "error in VIDIOC_QUERYBUF"; ::free(buff_img); ::close(cameraHwnd); return false; }
buff_img[index].length = buffer.length; buff_img[index].start = (quint8 *)mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, cameraHwnd, buffer.m.offset); if (MAP_FAILED == buff_img[index].start) { qDebug() << TIMEMS << "error in mmap"; ::free(buff_img); ::close(cameraHwnd); return false; }
//把缓冲帧放入队列 if (::ioctl(cameraHwnd, VIDIOC_QBUF, &buffer) < 0) { qDebug() << TIMEMS << "error in VIDIOC_QBUF"; for (int i = 0; i <= index; i++) { munmap(buff_img.start, buff_img.length); }
::free(buff_img); ::close(cameraHwnd); return false; } }
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (::ioctl(cameraHwnd, VIDIOC_STREAMON, &type) < 0) { qDebug() << TIMEMS << "error in VIDIOC_STREAMON"; for (int i = 0; i < 1; i++) { munmap(buff_img.start, buff_img.length); }
::free(buff_img); ::close(cameraHwnd); return false; }
cameraOk = true; #endif qDebug() << TIMEMS << "open camera ok"; return cameraOk; }
void CameraLinux::closeCamera() { #ifdef Q_OS_LINUX if (cameraOk && buff_img != NULL) { //停止摄像头采集 enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (::ioctl(cameraHwnd, VIDIOC_STREAMOFF, &type) < 0) { qDebug() << TIMEMS << "error in VIDIOC_STREAMOFF"; }
//解除内存映射 for (int i = 0; i < 1; i++) { munmap((buff_img).start, (buff_img).length); }
//关闭设备文件 ::close(cameraHwnd); qDebug() << TIMEMS << "close camera ok"; }
//释放资源 ::free(buff_img); buff_img = NULL; ::free(buff_yuv422); buff_yuv422 = NULL; ::free(buff_yuv420); buff_yuv420 = NULL; ::free(buff_rgb24); buff_rgb24 = NULL;
cameraOk = false; cameraHwnd = -1; #endif }
int CameraLinux::readFrame() { int index = -1; #ifdef Q_OS_LINUX //等待摄像头采集到一桢数据 for (;;) { fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(cameraHwnd, &fds); tv.tv_sec = 2; tv.tv_usec = 0;
int r = ::select(cameraHwnd + 1, &fds, NULL, NULL, &tv); if (-1 == r) { if (EINTR == errno) { continue; } return -1; } else if (0 == r) { return -1; } else { //采集到一张图片 跳出循环 break; } }
//从缓冲区取出一个缓冲帧 struct v4l2_buffer buffer; buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buffer.memory = V4L2_MEMORY_MMAP; if (::ioctl(cameraHwnd, VIDIOC_DQBUF, &buffer) < 0) { qDebug() << TIMEMS << "error in VIDIOC_DQBUF"; return -1; }
memcpy(buff_yuv422, (uchar *)buff_img[buffer.index].start, buff_img[buffer.index].length);
//将取出的缓冲帧放回缓冲区 if (::ioctl(cameraHwnd, VIDIOC_QBUF, &buffer) < 0) { qDebug() << TIMEMS << "error in VIDIOC_QBUF"; return -1; }
index = buffer.index; #endif return index; } ``` |
|