Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Qt音视频开发38-USB摄像头解码linux方案

Qt音视频开发38-USB摄像头解码linux方案

原创
作者头像
feiyangqingyun
修改于 2020-10-21 06:33:21
修改于 2020-10-21 06:33:21
2.9K0
举报
文章被收录于专栏:Qt项目实战Qt项目实战

一、前言

做嵌入式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. 可作为视频监控系统使用。

三、效果图

QQ图片20201016145740.jpg
QQ图片20201016145740.jpg

四、相关站点

  1. 国内站点:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 国际站点:https://github.com/feiyangqingyun/QWidgetDemo
  3. 个人主页:https://blog.csdn.net/feiyangqingyun
  4. 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
  5. 体验地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

五、核心代码

代码语言:txt
AI代码解释
复制
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;
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
数据结构于JS也可以成为CP(六)字典
Hello小伙伴们大家好,今天我们继续下一个数据结构,前面的数据结构所存储的数据都是单元素,但是如果我们想对一对数据进行存储该用什么呢?这时候就要请出字典了,字典是一种键-值对形式的数据结构,有没有想起什么,没错object就是以字典为基础的呢。
萌兔IT
2019/07/26
6770
JavaScript入门总结——第三弹 数组大放送
Hello大家好~~首先在这里祝大家元宵节快乐呢,大家吃汤圆了吗,兔妞反正还没吃呢,等着晚上放开肚子吃呢,糯叽叽糯叽叽,嘿嘿
萌兔IT
2019/07/25
3840
JavaScript入门总结——第三弹 数组大放送
JavaScript第七弹——深入理解浅拷贝与深拷贝
Hello小伙伴们,抱歉这两天没有更文,今天我来将功补过啦,今天的主题是“拷贝”!大家还记得之前说过的数据类型吗,那可是我们今天的基础呢!
萌兔IT
2019/07/25
4270
JavaScript第七弹——深入理解浅拷贝与深拷贝
重读《学习JavaScript数据结构与算法-第三版》- 第3章 数组(一)​
读《学习JavaScript数据结构与算法》- 第3章 数组,本节将为各位小伙伴分享数组的相关知识:概念、创建方式、常见方法以及ES6数组的新功能。
胡哥有话说
2019/08/19
5350
JavaScript入门总结——第二弹学习对象,分清__proto__、prototype
Hello小伙伴们,我又来啦,今天我们要继续我们JavaScript的入门总结第二弹!!!
萌兔IT
2019/07/26
4900
JavaScript入门总结——第二弹学习对象,分清__proto__、prototype
用js来实现那些数据结构02(数组篇02-数组方法)
    上一篇文章简单的介绍了一下js的类型,以及数组的增删方法。这一篇文章,我们一起来看看数组还有哪些用法,以及在实际工作中我们可以用这些方法来做些什么。由于其中有部分内容并不常用,所以我尽量缩小篇幅。在这篇文章内介绍完大部分的数组方法,加快我们实现其它数据结构的脚步。 1、concat()     合并数组,可以合并一个或多个数组。会按照参数顺序依次合并进想要合并的数组。 //concat的参数并不是只能传入数组,字符串,数字,布尔值,对象等都可以传入。 var arr = [0,1,2,3,4,5,
zaking
2018/05/02
1.2K0
用js来实现那些数据结构02(数组篇02-数组方法)
    上一篇文章简单的介绍了一下js的类型,以及数组的增删方法。这一篇文章,我们一起来看看数组还有哪些用法,以及在实际工作中我们可以用这些方法来做些什么。由于其中有部分内容并不常用,所以我尽量缩小篇幅。在这篇文章内介绍完大部分的数组方法,加快我们实现其它数据结构的脚步。
全栈程序员站长
2022/07/20
5100
比较JavaScript中的数据结构(数组与对象)
大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】。 在编程中,如果你想继续深入,数据结构是我们必须要懂的一块, 学习/理解数据结构的动机可能会有所不同,一方面可能是为了面试,一方面可能
前端小智@大迁世界
2020/10/10
6.3K0
比较JavaScript中的数据结构(数组与对象)
用js来实现那些数据结构01(数组篇01-数组的增删)
   在开始正式的内容之前,不得不说说js中的数据类型和数据结构,以及一些比较容易让人混淆的概念。那么为什么要从数组说起?数组在js中是最常见的内存数据结构,数组数据结构在js中拥有很多的方法,很多初学者记不清数组的大多数用法,只知道push,pop,shift等最基本的几个。所以,本系列(数组篇)会尽可能的让大家对数组有一个透彻的了解。也方便后面其他数据结构的学习和使用。    可能很多web前端开发者都会有一个疑问,那就是,数组和对象究竟是数据类型?还是数据结构?那么我们就带着这样的疑问,开始下面的学习
zaking
2018/05/02
1.5K0
JavaScript数据结构01 - 数组
PS:原始值是指固定而简单的值,存放在栈中的简单数据段,它们的值直接存储在变量访问的位置。
leocoder
2018/10/31
1.2K0
js 数组详细操作方法及解析
目的:Array.of() 出现的目的是为了解决上述构造器因参数个数不同,导致的行为有差异的问题。
kif
2023/02/27
1.4K0
[第 3 期]JavaScript数据结构之数组栈队列
1. 数组 数组是平时使用最常用的数据结构,在JavaScript中数组是动态的分配大小,在这里我不会介绍JavaScript里面数组的所有的方法,而是针对数据结构这个方向谈谈所用到的方法。 1.1 创建和初始化数组 //创建空数组 var array = new Array(); //[] //初始化数组 var array = new Array(1,2,3); var array = Array.of(1,2,3);//ES6的方法 //[1,2,3] //创建大小为5的数组 var array
桃翁
2018/06/27
6000
js数组常用方法总结
最近工作中经常用到数组操作,每次都傻傻不知道怎么用,今天有时间整理了一下,希望对大家有帮助!这些基础的知识,要熟记于心。
半指温柔乐
2018/09/11
5.2K0
JS高级-数据结构的封装
最近在看了《数据结构与算法JavaScript描述》这本书,对大学里学的数据结构做了一次复习(其实差不多忘干净了,哈哈)。如果能将这些知识捡起来,融入到实际工作当中,估计编码水平将是一次质的飞跃。带着这个美好的愿望,开始学习吧O(∩_∩)O~~ 我们知道在JS中,常常用来组织数据的无非是数组和对象(这些基础就不介绍了)。但在数据结构中,还有一些抽象的数据类型:列表、栈、队列、链表、字典、散列、集合、二叉树、图等,可以用来更好的对实际场景建模。当然这些数据类型,原生JS不支持,那么就需要通过封装来模拟,其底层
小古哥
2018/03/08
8.1K0
【Node.js算法题】数组去重、数组删除元素、数组排序、字符串排序、字符串反向、字符串改大写 、数组改大写、字符替换
本期文章是js的一些算法题,包括数组去重、数组删除元素、数组排序、字符串排序、字符串反向、字符串改大写 、数组改大写、字符替换。
颜颜yan_
2023/03/06
1.9K0
【Node.js算法题】数组去重、数组删除元素、数组排序、字符串排序、字符串反向、字符串改大写 、数组改大写、字符替换
力扣 (LeetCode)-最大子序和,JavaScript数据结构与算法(数组)
每天学习编程,让你离梦想更新一步,感谢不负每一份热爱编程的程序员,不论知识点多么奇葩,和我一起,让那一颗四处流荡的心定下来,一直走下去,加油,2021加油!欢迎关注加我vx:xiaoda0423,欢迎点赞、收藏和评论
达达前端
2021/03/22
4890
力扣 (LeetCode)-最大子序和,JavaScript数据结构与算法(数组)
数据结构系列 -- 手撸数组
数组在开发中是必不可少、不可或缺的重要组成元素。在 Java 数据结构中,数组也被赋予神圣的地位。但是你真的会数组吗?那今天换个姿势,我们来怼一怼数据结构中的数组。 一、数组定义 数组的定义比较基础,在这就不展开了。(需要重温 Java 数组的可以参照菜鸟教程的 Java 数组模块) 二、数组基础用法 数组可以直接使用的方法不多,遍历便是最简单的一种使用。 1. 数组遍历 数组遍历比较简单,简单粗暴的使用 for 循环遍历是最简单的事情,当然也可以使用 foreach 遍历。如下: public stat
程序员小跃
2020/01/13
3880
数据结构于JS也可以成为CP(五)链表
Hello大家好~兔妞今天给大家带来的是链表哦!为什么有链表呢,因为数组并不总是组织数据的最佳数据结构。由于在JavaScript中数组是一个对象,所以js的数组相比其他语言的数组效率较低。那么我们就可以考虑使用链表啦。
萌兔IT
2019/07/26
6600
数据结构于JS也可以成为CP(五)链表
数据结构
数组内存地址是连续的,但是js中的内存地址是不连续,原因是数据类型可以是任意类型导致的
花落花相惜
2021/11/25
1.1K0
JS 数组中 reduce 方法详解
reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值。 也就是说,这个累加器会从第一个累加值开始,不断对累加值和数组中的后续元素调用该累加器,直到数组中的最后一个元素,最后返回得到的累加值。
Leophen
2021/06/10
7K0
推荐阅读
相关推荐
数据结构于JS也可以成为CP(六)字典
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档