Qt音视频开发38-USB摄像头解码linux方案
一、前言
做嵌入式linux上的开发很多年了,扳手指头算算,也起码9年了,陆陆续续做过很过诸如需要读取外接的USB摄像头或者CMOS摄像机的程序,实时采集视频,将图像传到前端,或者对图像进行人脸分析处理,最开始尝试的就是QCamera来处理,直接歇菜放弃,后面通过搜索发现都说要用v4l2视频框架来进行,于是东搞搞西搞搞尝试了很多次,终于整出来了,前后完善了好几年,无论写什么程序,发现要简简单单的实现基础的功能,都是非常快速而且容易的,但是想要做得好做得精,要花不少的精力时间去完善,适应各种不同的场景,比如就说用v4l2加载摄像头这个,需要指定设备文件来读取,而现场不可能让用户来给你指定,频繁的拔插也会导致设备文件名的改动,所以必须找到一个机制自动寻找你想要的摄像机的设备文件名称,比如开个定时器去调用linux命令来处理,甚至在不同的系统平台上要执行的命令还有些许的区别,如果本地有多个摄像头还需要区分左右之类的时候,那就只能通过断电先后上电顺序次序来区分了。
linux方案处理流程:
- 调用封装的函数findCamera实时查找摄像头设备文件名。
- 调用::open函数打开设备文件。
- 调用封装的函数initCamera初始化摄像头参数(图片格式、分辨率等)。
- 调用::select函数从缓冲区取出一个缓冲帧。
- 缓冲帧数据是yuyv格式的,需要转换rgb24再转成QImage。
- 拿到图片进行绘制、人脸分析等。
- 关闭设备文件。
二、功能特点
- 同时支持windows、linux、嵌入式linux上的USB摄像头实时采集。
- 支持多路USB摄像头多线程实时采集。
- 在嵌入式linux设备上,自动查找USB设备文件并加载。
- 可手动设置设备文件名称,手动设置后按照手动设置的设备文件加载。
- 在嵌入式linux设备上支持人脸识别接口,实时绘制人脸框。
- 具有打开、暂停、继续、关闭、截图等常规功能。
- 可设置两路OSD标签,分别设置文本、颜色、字号、位置等。
- 可作为视频监控系统使用。
三、效果图

四、相关站点
- 国内站点:https://gitee.com/feiyangqingyun/QWidgetDemo
- 国际站点:https://github.com/feiyangqingyun/QWidgetDemo
- 个人主页:https://blog.csdn.net/feiyangqingyun
- 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
- 体验地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652
五、核心代码
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[i].start, buff_img[i].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[i].start, buff_img[i].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)[i].start, (buff_img)[i].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;
}
Qt音视频开发38-USB摄像头解码linux方案的更多相关文章
- Android IOS WebRTC 音视频开发总结(八十五)-- 使用WebRTC广播网络摄像头视频(下)
本文主要介绍WebRTC (我们翻译和整理的,译者:weizhenwei,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:bl ...
- Android IOS WebRTC 音视频开发总结(八十三)-- 使用WebRTC广播网络摄像头视频(上)
本文主要介绍WebRTC (我们翻译和整理的,译者:weizhenwei,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:bl ...
- Android开发 音视频开发需要了解的专业术语知识
前言 在摸索一段时间的音视频开发后,越来越发现这个坑的深度真是特别的深. 除了了解Android自带的音视频处理API以外,还得了解一些视频与音频方面的知识.这篇博客就是主要讲解这方面的专业术语.内容 ...
- Android音视频开发(1):H264 基本原理
前言 H264 视频压缩算法现在无疑是所有视频压缩技术中使用最广泛,最流行的.随着 x264/openh264 以及 ffmpeg 等开源库的推出,大多数使用者无需再对H264的细节做过多的研究,这大 ...
- Android IOS WebRTC 音视频开发总结(六)-- iOS开发之含泪经验
前段时间在搞webrtc iOS开发,所以将标题改为了Android IOS WebRTC 音视频开发总结, 下面都是开发过程中的经验总结,转载请说明出处(博客园RTC.Blacker): 1. IO ...
- Android WebRTC 音视频开发总结(四)-- webrtc传输模块
在介绍WebRTC通讯之前我们先来看一个P2P视频聊天包括的主要过程,转载请说明出处(博客园RTC.Blacker): 音视频数据采集->编码->发送->接收->解码-> ...
- WebRTC 音视频开发
WebRTC 音视频开发 webrtc Android IOS WebRTC 音视频开发总结(七八)-- 为什么WebRTC端到端监控很关键? 摘要: 本文主要介绍WebRTC端到端监控(我们翻译 ...
- Android 音视频开发学习思路
Android 音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的.只能通过一点点的学习和积累把这块的知识串联积累起来. 初级入门篇: Android 音视频开发(一) ...
- 转:Android IOS WebRTC 音视频开发总结 (系列文章集合)
随笔分类 - webrtc Android IOS WebRTC 音视频开发总结(七八)-- 为什么WebRTC端到端监控很关键? 摘要: 本文主要介绍WebRTC端到端监控(我们翻译和整理的,译 ...
- 音视频开发-FFmpeg
音视频开发是个非常复杂的,庞大的开发话题,初涉其中,先看一下结合 OEIP(开源项目) 新增例子. 可以打开flv,mp4类型文件,以及rtmp协议音视频数据,声音的播放使用SDL. 把采集的麦/声卡 ...
随机推荐
- KubeSphere 3.2.0 发布:带来面向 AI 场景的 GPU 调度与更灵活的网关
现如今最热门的服务器端技术是什么?答案大概就是云原生!KubeSphere 作为一个以 Kubernetes 为内核的云原生分布式操作系统,也是这如火如荼的云原生热潮中的一份子.KubeSphere ...
- KubeSphere 社区双周报 | OpenFunction v1.0.0-rc.0 发布
KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...
- 基于 CoreDNS 和 K8s 构建云原生场景下的企业级 DNS
容器作为近些年最火热的后端技术,加快了很多企业的数字化转型进程.目前的企业,不是在使用云原生技术,就是在转向云原生技术的过程中.在容器化进程中,如何保持业务的平稳迁移,如何将现有的一些服务设施一并进行 ...
- Nuxt.js 应用中的 app:templatesGenerated 事件钩子详解
title: Nuxt.js 应用中的 app:templatesGenerated 事件钩子详解 date: 2024/10/19 updated: 2024/10/19 author: cmdra ...
- Oracle 19.3 RAC on Redhat 7.6 安装最佳实践
本文讲述了在Redhat Linux 7.6上安装Oracle 19.3 RAC的详细步骤,是一篇step by step指南: 借鉴资深工程师赵庆辉.赵靖宇等人技术博客或公众号编写. 一.实施前期准 ...
- JavaScript String 对象-常用知识点
JavaScript String 对象-常用知识点 对象用于处理文本(字符串). 对象创建方法: new String(). String 对象属性 属性 描述 constructor 对创建该对象 ...
- 论文发表汇款:SWIFT code跨境汇款 —— 如何向境外账号汇款
如何向境外账号汇款? 有以下几种方式: 对方开通中国金融产品账号或在中国有代理公司:如对方开通中国的银行卡,微信.支付宝,等等,这样其实就不属于跨境汇款了,外国的一些公司已经开设中国的金融产品和银行账 ...
- nsenter 常用操作
nsenter 是一个可以用来进入到目标程序说在 namespace 中运行命令的工具,一般可以用于在容器外 debug 容器中运行的程序.简单记录一下 nsenter 的常用用法. 常用参数 最常用 ...
- php yaconf扩展
在了解到PHP鸟哥还有这个扩展后,我安装尝试了一下 在这里有介绍 https://pecl.php.net/package/yaconf 这里有更详细的代码和说明 https://github.co ...
- 前端截图取色工具Snipaste
在Web前端开发中,在写页面CSS样式时经常要用工具去取色来设置字体颜色.背景颜色.边框颜色等等,以还原设计图的最佳效果.今天给大家推荐的取色工具是Snipaste.Snipaste 是一个简单但强大 ...