Qt支持RKMPP硬解的视频监控系统/性能卓越界面精美/实时性好延迟低/录像存储和回放/云台控制

一、前言

之前做的监控系统,已经实现了在windows上硬解码比如dxva2和d3d11va,后续又增加了linux上的硬解vdpau的支持,这几种方式都是跨系统的硬解实现方案,也是就是如果都是windows系统,无论X86还是ARM都通用,在系统层面屏蔽了实现细节,按照规范来写就行,这种方式叫跨硬件但是不跨系统,无论是英特尔显卡还是英伟达显卡都一样,无需区分具体用的什么显卡。后面还有跨系统但是不跨硬件的策略,典型的就是针对英伟达显示,代码可以跨系统,但是不支持其他显卡。现在随着越来越多的嵌入式板子,尤其是RK3588,默认有个RKMPP的硬解,用户在这个场景下的需求也越来越多,于是也要增加支持。

最早监控系统开发就已经考虑好了模块化的方式,采用一个个core组件模块来实现,这样一旦某个组件模块更新了代码,只需要替换这个组件的代码就行,一般就是一个pri带一个目录,同理监控内核ffmpeg模块也是如此,于是抽空将对RKMPP硬解的支持也加入了其中,这其中的关键并不是使用硬解解码部分,后面捣鼓下来发现关键是要编译一个支持RKMPP硬解的ffmpeg库就行(https://github.com/nyanmisaka/ffmpeg-rockchip),把这个库替换掉,然后硬解名称选择RKMPP即可,整个硬解流程和之前的dxva2/d3d11va一模一样,然后解码后的数据发给opengl绘制,数据格式一般是NV12。打完收工非常完美。

二、效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、相关地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_demo/bin_video_system。

四、功能特点

  1. 支持各种本地音视频文件和网络音视频文件,格式包括mp3、aac、wav、wma、mp4、mkv、rmvb、wmv、mpg、flv、asf等。
  2. 支持各种网络音视频流,网络摄像头,协议包括rtsp、rtmp、http等。
  3. 支持本地摄像头设备推流,可指定分辨率、帧率、格式等。
  4. 支持本地桌面采集推流,可指定屏幕索引、采集区域、起始坐标、帧率等,也支持指定窗口标题进行采集。
  5. 可实时切换预览视频文件,可切换音视频文件播放进度,切换到哪里就推流到哪里。预览过程中可以切换静音状态和暂停推流。
  6. 可指定重新编码推流,任意源头格式可选强转264或265格式。
  7. 可转换分辨率推流,设置等比例缩放或者指定分辨率进行转换。
  8. 推流的清晰度、质量、码率都可调,可以节约网络带宽和拉流端的压力。
  9. 音视频文件自动循环不间断推流。
  10. 音视频流有自动掉线重连机制,重连成功自动继续推流。
  11. 支持各种流媒体服务程序,包括但不限于mediamtx、ZLMediaKit、srs、LiveQing、nginx-rtmp、EasyDarwin、ABLMediaServer。
  12. 通过配置文件自动加载对应流媒体程序的协议和端口,自动生成推流地址和各种协议的拉流地址。可以通过配置文件自己增加流媒体程序。
  13. 可选rtmp、rtmp格式推流,推流成功后,支持多种格式拉流,包括但不限于rtsp、rtmp、hls、flv、ws-flv、webrtc等。
  14. 在软件上推流成功后,可以直接单击网页预览,实时预览推流后拉流的画面,多画面网页展示。
  15. 软件界面上可单击对应按钮,动态添加文件和目录,可手动输入地址。
  16. 推拉流实时性极高,延迟极低,延迟时间大概在100ms左右。
  17. 极低CPU资源占用,4路主码流推流只需要占用0.2%CPU。理论上常规普通PC机器推100路毫无压力,主要性能瓶颈在网络。
  18. 可以推流到外网服务器,然后通过手机、电脑、平板等设备播放对应的视频流。
  19. 每路推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照策略随机生成hash值。也支持自动按照指定标识后面加数字的方式递增命名。比如设置标识为字母v,策略为标识递增,则每添加一个对应的推流码命名依次是v1、v2、v3等。
  20. 根据推流协议自动转码格式,默认策略按照选择的推流协议,比如rtsp支持265而rtmp不支持,如果是265的文件而选择rtmp推流,则自动转码成264格式再推流。
  21. 音视频同步推流,在拉流和采集的时候就会自动处理好同步,同步后的数据再推流。
  22. 表格中实时显示每一路推流的分辨率和音视频数据状态,灰色表示没有输入流,黑色表示没有输出流,绿色表示原数据推流,红色表示转码后的数据推流。
  23. 自动重连视频源,自动重连流媒体服务器,保证启动后,推流地址和打开地址都实时重连,只要恢复后立即连上继续采集和推流。
  24. 根据不同的流媒体服务器类型,自动生成对应的rtsp、rtmp、hls、flv、ws-flv、webrtc拉流地址,用户可以直接复制该地址到播放器或者网页中预览查看。
  25. 添加的推流地址等信息自动存储到文件,可以手动打开进行修改,默认启动后自动加载历史记录。
  26. 可以指定生成的网页文件保存位置,方便作为网站网页发布,可以直接在浏览器中输入网址进行访问,发布后可以直接在局域网其他设备比如手机或者电脑打开对应网址访问。
  27. 可选是否开机启动、后台运行等。网络推流添加的rtsp地址可勾选是否隐藏地址中的用户信息。
  28. 自带设备推流模块,自动识别本地设备,包括本地的摄像头和桌面,可以手动选择不同的是视频和音频采集设备进行推流。
  29. 自带文件点播模块,添加文件后用户可以拉取地址点播,用户端可以任意切换播放进度。支持各种浏览器(谷歌chromium、微软edge、火狐firefox等)、各种播放器(vlc、mpv、ffplay、potplayer、mpchc等)打开请求。
  30. 文件点播模块实时统计显示每个文件对应的访问数量、总访问数量、不同IP地址访问数量。
  31. 文件点播模块采用纯QTcpSocket通信,不依赖流媒体服务程序,核心源码不到500行,注释详细,功能完整。
  32. 支持任意Qt版本(Qt4、Qt5、Qt6),支持任意系统(windows、linux、macos、android、嵌入式linux等)。

五、相关代码

bool FFmpegThread::initVideo()
{
    //找到视频流索引
    videoIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoIndex < 0) {
        //有些没有视频流所以这里不用返回
        videoIndex = -1;
        debug(0, "无视频流", "");
    } else {
        //如果手动指定了轨道则取指定的(节目流有多个轨道可以指定某个)
        if (videoTrack >= 0 && videoTracks.contains(videoTrack)) {
            videoIndex = videoTrack;
        }

        //取出流获取对应的信息创建解码器
        int result = -1;
        AVStream *videoStream = formatCtx->streams[videoIndex];

        //如果主动设置过旋转角度则将旋转信息设置到流信息中以便保存那边也应用(不需要保存也旋转可以注释)
        if (rotate != -1) {
            FFmpegHelper::setRotate(videoStream, rotate);
        }

        //先获取旋转角度(如果有旋转角度则不能用硬件加速)
        this->getRotate();
        if (rotate != 0) {
            hardware = "none";
        }

        //查找视频解码器(如果上面av_find_best_stream第五个参数传了则这里不需要)
        AVCodecID codecId = FFmpegHelper::getCodecId(videoStream);
        if (codecId == AV_CODEC_ID_NONE) {
            debug(result, "无视解码", "");
            return false;
        }

        //初始化解码器
        FFmpegThreadHelper::initVideoCodec(&videoCodec, codecId, videoCodecName, hardware);

        //创建视频解码器上下文
        videoCodecCtx = avcodec_alloc_context3(NULL);
        if (!videoCodecCtx) {
            debug(result, "创建视解", "");
            return false;
        }

        //将视频流的参数拷贝给视频解码器上下文/以便能够按照对应流参数进行解码
        result = FFmpegHelper::copyContext(videoCodecCtx, videoStream, false);
        if (result < 0) {
            debug(result, "视频参数", "");
            return false;
        }

        //初始化硬件加速(也可以叫硬解码/如果当前格式不支持硬解则立即切换到软解码)
        if (hardware != "none" && !FFmpegThreadHelper::initHardware(this, videoCodec, videoCodecCtx, hardware)) {
            hardware = "none";
            videoCodec = avcodec_find_decoder(codecId);
        }

        if (!videoCodec) {
            return false;
        }

        //设置低延迟和加速解码等参数(设置max_lowres的话很可能画面采用最小的分辨率)
        if (!getIsFile()) {
            //videoCodecCtx->lowres = videoCodec->max_lowres;
            videoCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
            videoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            videoCodecCtx->flags2 |= AV_CODEC_FLAG2_FAST;
        }

        //打开视频解码器
        result = avcodec_open2(videoCodecCtx, videoCodec, NULL);
        if (result < 0) {
            debug(result, "打开视解", "");
            return false;
        }

        if (videoCodecCtx->pix_fmt == AV_PIX_FMT_NONE) {
            debug(0, "格式为空", "");
            return false;
        }

        //获取分辨率大小
        FFmpegHelper::getResolution(videoStream, videoWidth, videoHeight);
        //如果没有获取到宽高则返回
        if (videoWidth == 0 || videoHeight == 0) {
            debug(0, "无分辨率", "");
            return false;
        }

        //记录首帧开始时间和解码器名称
        videoFirstPts = videoStream->start_time;
        videoCodecName = videoCodec->name;
        frameRate = FFmpegHelper::getFrameRate(videoStream, formatName);
        qint64 frames = videoStream->nb_frames;
        this->setProperty("frames", frames);
        QString msg = QString("索引: %1 解码: %2 帧率: %3 宽高: %4x%5 角度: %6 帧数: %7").arg(videoIndex).arg(videoCodecName).arg(frameRate).arg(videoWidth).arg(videoHeight).arg(rotate).arg(frames);
        debug(0, "视频信息", msg);
        //FFmpegUtil::getExtraData(videoCodecCtx);
    }

    return true;
}

void FFmpegThreadHelper::initVideoCodec(AVCodecx **videoCodec, AVCodecID codecId, QString &videoCodecName, QString &hardware)
{
    //获取默认的解码器
    (*videoCodec) = avcodec_find_decoder(codecId);
    videoCodecName = (*videoCodec)->name;

    bool otherHardware = (hardware != "none" && hardware != "dxva2" && hardware != "d3d11va");
    if (!otherHardware) {
        return;
    }

    //264/265才能去启用系统层以外的硬解码
    if (videoCodecName != "h264" && videoCodecName != "hevc") {
        hardware = "none";
        return;
    }

    //指定硬解码器名称 h264_qsv/h264_cuvid/h264_vaapi hevc_qsv/hevc_cuvid/hevc_vaapi/h264_mediacodec
    QString name = QString("%1_%2").arg(videoCodecName).arg(hardware);
    (*videoCodec) = avcodec_find_decoder_by_name(name.toUtf8().constData());
    //如果硬解码器分配失败则立即切换到软解码
    if (!(*videoCodec)) {
        (*videoCodec) = avcodec_find_decoder(codecId);
    }
}

bool FFmpegThreadHelper::initHardware(FFmpegThread *thread, AVCodecx *videoCodec, AVCodecContext *videoCodecCtx, const QString &hardware)
{
#if (FFMPEG_VERSION_MAJOR > 2)
    //根据名称自动寻找硬解码
    enum AVHWDeviceType type;
    QByteArray hwData = hardware.toUtf8();
    const char *hwName = hwData.constData();
#ifdef __arm__
    //发现嵌入式上低版本的库没有av_hwdevice_find_type_by_name函数
#if (FFMPEG_VERSION_MAJOR < 4)
    return false;
#else
    type = av_hwdevice_find_type_by_name(hwName);
#endif
#else
    type = av_hwdevice_find_type_by_name(hwName);
#endif

    //找到对应的硬解码格式
    thread->debug(0, "硬件加速", QString("名称: %1 数值: %2").arg(hardware).arg(type));
    FFmpegThreadHelper::hw_pix_fmt = FFmpegThreadHelper::find_fmt_by_hw_type(type, videoCodec);
    if (FFmpegThreadHelper::hw_pix_fmt == -1) {
        thread->debug(0, "加速失败", "");
        return false;
    }

    int result = -1;
    //创建硬解码设备
    AVBufferRef *hw_device_ref;
    result = av_hwdevice_ctx_create(&hw_device_ref, type, NULL, NULL, 0);
    if (result < 0) {
        thread->debug(result, "创建硬解", "av_hwdevice_ctx_create");
        return false;
    }

    //解码器格式赋值为硬解码
    videoCodecCtx->get_format = FFmpegThreadHelper::get_hw_format;
    videoCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ref);
    av_buffer_unref(&hw_device_ref);
    thread->debug(result, "初始硬解", QString("成功: %1").arg(hardware));
    return true;
#else
    return false;
#endif
}

bool FFmpegThreadHelper::initVideoData(FFmpegThread *thread, AVFrame *yuvFrame, AVFrame *imageFrame, SwsContext **yuvSwsCtx, SwsContext **imageSwsCtx, quint8 **imageData,
                                       AVPixelFormat srcFormat, AVPixelFormat dstFormat, int videoWidth, int videoHeight, const QString &hardware, int flags)
{
    //设置属性以便该帧对象正常
    yuvFrame->format = AV_PIX_FMT_YUV420P;
    yuvFrame->width = videoWidth;
    yuvFrame->height = videoHeight;

    //如果发现画面斜了或者条纹状可以考虑修改这里的对齐值
    int align = (hardware == "none" ? 4 : 16);

    //分配视频数据(转yuv420)
    int result = av_frame_get_buffer(yuvFrame, align);
    if (result < 0) {
        thread->debug(result, "视频转换", "av_frame_get_buffer");
        return false;
    }

    //视频图像转换(转yuv420)
    (*yuvSwsCtx) = sws_getContext(videoWidth, videoHeight, srcFormat, videoWidth, videoHeight, AV_PIX_FMT_YUV420P, flags, NULL, NULL, NULL);
    if (!yuvSwsCtx) {
        thread->debug(result, "视频转换", "sws_getContext");
        return false;
    }

    //视频图像转换(转rgb)
    AVPixelFormat imageFormat = AV_PIX_FMT_RGB24;
    (*imageSwsCtx) = sws_getContext(videoWidth, videoHeight, srcFormat, videoWidth, videoHeight, imageFormat, flags, NULL, NULL, NULL);
    if (!imageSwsCtx) {
        thread->debug(result, "视频转换", "sws_getContext");
        return false;
    }

    //分配视频帧数据(转rgb)
    align = 4;
    int imageSize = av_image_get_buffer_size(imageFormat, videoWidth, videoHeight, align);
    (*imageData) = (quint8 *)av_malloc(imageSize * sizeof(quint8));
    result = av_image_fill_arrays(imageFrame->data, imageFrame->linesize, (*imageData), imageFormat, videoWidth, videoHeight, align);
    if (result < 0) {
        thread->debug(result, "视频转换", "av_image_fill_arrays");
        return false;
    }

    QString srcFormatString = FFmpegHelper::getPixFormatString(srcFormat);
    QString dstFormatString = FFmpegHelper::getPixFormatString(dstFormat);
    QString msg = QString("源头: %1 目标: %2 硬解: %3").arg(srcFormatString).arg(dstFormatString).arg(hardware);
    thread->debug(0, "视频转换", msg);
    return true;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/927119.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web API基本认知

作用和分类 作用&#xff1a;就是使用JS去操作html和浏览器 分类&#xff1a;DOM&#xff08;文档对象模型&#xff09;、BOM&#xff08;浏览器对象模型&#xff09; 什么是DOM DOM&#xff08;Document Object Model ——文档对象模型&#xff09;是用来呈现以及与任意 HTM…

Linux——自定义简单shell

shell 自定义shell目标普通命令和内建命令&#xff08;补充&#xff09; shell实现实现原理实现代码 自定义shell 目标 能处理普通命令能处理内建命令要能帮助我们理解内建命令/本地变量/环境变量这些概念理解shell的运行 普通命令和内建命令&#xff08;补充&#xff09; …

智能桥梁安全运行监测系统守护桥梁安全卫士

一、方案背景 桥梁作为交通基础设施中不可或缺的重要组成部分&#xff0c;其安全稳定的运行直接关联到广大人民群众的生命财产安全以及整个社会的稳定与和谐。桥梁不仅是连接两地的通道&#xff0c;更是经济发展和社会进步的重要纽带。为了确保桥梁的安全运行&#xff0c;桥梁安…

【Python网络爬虫笔记】5-(Request 带参数的get请求) 爬取豆瓣电影排行信息

目录 1.抓包工具查看网站信息2.代码实现3.运行结果 1.抓包工具查看网站信息 请求路径 url:https://movie.douban.com/typerank请求参数 页面往下拉&#xff0c;出现新的请求结果&#xff0c;参数start更新&#xff0c;每次刷新出20条新的电影数据 2.代码实现 # 使用网络爬…

新质驱动·科东软件受邀出席2024智能网联+低空经济暨第二届湾区汽车T9+N闭门会议

为推进广东省加快发展新质生产力&#xff0c;贯彻落实“百县千镇万村高质量发展工程”&#xff0c;推动韶关市新丰县智能网联新能源汽车、低空经济与数字技术的创新与发展&#xff0c;充分发挥湾区汽车产业链头部企业的带动作用。韶关市指导、珠三角湾区智能网联新能源汽车产业…

C#使用ExcelDataReader读取Xlsx文件为DataTable对象

创建控制台项目 在NuGet中安装ExcelDataReader.DataSet 3.7.0 创建一个xlsx文件 测试代码 读取xlsx文件内容&#xff0c;为一个DataTable对象。 读取xlsx时&#xff0c;xlsx文件不能被其他软件打开&#xff0c;否则会报“进程无法访问此文件”的错。 using ExcelDataRead…

“harmony”整合不同平台的单细胞数据之旅

其实在Seurat v3官方网站的Vignettes中就曾见过该算法&#xff0c;但并没有太多关注&#xff0c;直到看了北大张泽民团队在2019年10月31日发表于Cell的《Landscap and Dynamics of Single Immune Cells in Hepatocellular Carcinoma》&#xff0c;为了同时整合两类数据&#xf…

智慧银行反欺诈大数据管控平台方案(一)

智慧银行反欺诈大数据管控平台建设方案的核心在于通过整合先进的大数据技术和深度学习算法&#xff0c;打造一个全面、智能且实时的反欺诈系统&#xff0c;以有效识别、预防和应对各类金融欺诈行为。该方案涵盖数据采集、存储、处理和分析的全流程&#xff0c;利用多元化的数据…

基于 JNI + Rust 实现一种高性能 Excel 导出方案(上篇)

每个不曾起舞的日子,都是对生命的辜负。 ——尼采 一、背景:Web 导出 Excel 的场景 Web 导出 Excel 功能在数据处理、分析和共享方面提供了极大的便利,是许多 Web 应用程序中的重要功能。以下是一些典型的场景: 数据报表导出:在企业管理系统(如ERP、CRM)中,用户经常需…

使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件

使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件 Python 是一门强大的编程语言&#xff0c;它不仅可以用于数据处理、自动化脚本&#xff0c;还可以用于创建图形用户界面 (GUI) 应用程序。在本教程中&#xff0c;我们将使用 Python 的标准库模块 tkinter 创建一…

「Mac畅玩鸿蒙与硬件35」UI互动应用篇12 - 简易日历

本篇将带你实现一个简易日历应用&#xff0c;显示当前月份的日期&#xff0c;并支持选择特定日期的功能。用户可以通过点击日期高亮选中&#xff0c;还可以切换上下月份&#xff0c;体验动态界面的交互效果。 关键词 UI互动应用简易日历动态界面状态管理用户交互 一、功能说明…

江协科技最新OLED保姆级移植hal库

江协科技最新OLED移植到hal库保姆级步骤 源码工程存档 工程和源码下载(密码 1i8y) 原因 江协科技的开源OLED封装的非常完美, 可以满足我们日常的大部分开发, 如果可以用在hal库 ,将是如虎添翼, 为我们开发调试又增加一个新的瑞士军刀, 所以我们接下来手把手的去官网移植源码…

NLTK工具包

NLTK工具包 NLTK工具包安装 非常实用的文本处理工具&#xff0c;主要用于英文数据&#xff0c;历史悠久~ 安装命令&#xff1a; pip install nltk import nltk # nltk.download() # nltk.download(punkt) # nltk.download(stopwords) # nltk.download(maxent_ne_chunker) nl…

HarmonyOS:使用Emitter进行线程间通信

Emitter主要提供线程间发送和处理事件的能力&#xff0c;包括对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等。 一、Emitter的开发步骤如下&#xff1a; 订阅事件 import { emitter } from kit.BasicServicesKit; import { promptAction } from kit.…

Unity之一键创建自定义Package包

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之一键创建自定义Package包 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; …

【html网页页面007】html+css制作旅游主题内蒙古网页制作含注册表单(4页面附效果及源码)

旅游家乡主题网页制作 &#x1f964;1、写在前面&#x1f367;2、涉及知识&#x1f333;3、网页效果&#x1f308;4、网页源码4.1 html4.2 CSS4.3 源码获取 &#x1f40b;5、作者寄语 &#x1f964;1、写在前面 家乡网站主题内蒙古的网页 一共4个页面 网页使用htmlcss制作页面…

Ardupilot开源无人机之Geek SDK讨论

Ardupilot开源无人机之Geek SDK讨论 1. 源由2. 假设3. 思考3.1 结构构型3.2 有限资源3.3 软硬件构架 4.Ardupilot构架 - 2024kaga Update5. 讨论5.1 话题1&#xff1a;工作模式5.2 话题2&#xff1a;关键要点5.3 话题3&#xff1a;产品设计 6. Geek SDK - OpenFire6.1 开源技术…

JavaWeb——Maven高级

11.1. 分模块设计与开发 将项目按照功能拆分成若干个子模块&#xff0c;方便项目的管理维护、扩展&#xff0c;也方便模块之间的互相调用&#xff0c;资源共享。 11.2. 继承与聚合 11.2.1. 继承 父工程的的打包方式必须为pom 实现步骤 11.2.2. 版本锁定 dependencyManagemen…

Python中的实用工具JSON解析

对于Python不熟悉的同学&#xff0c;建议从本专栏第一篇开始观看 https://blog.csdn.net/qq_20330595/category_12844705.html 先上效果图 代码 import threading import tkinter as tk import json from tkinter import scrolledtext import tkinter.filedialog as filedialo…

医学临床机器学习中算法公平性与偏差控制简析

摘要 随着医疗领域中数据的不断积累和计算能力的提升&#xff0c;临床机器学习技术发展迅速&#xff0c;但算法不公平性和偏差问题凸显。本文深入探讨了临床机器学习算法公平性的重要性、概念与定义、在临床应用中的影响、偏差来源、降低偏差方法及提升公平性策略。通过对不同…