一、前言说明
瑞芯微RK3588芯片作为一款高性能的处理器解决方案,能够在安防监控领域发挥重要作用。它支持同时进行16路1080P视频的硬件解码和GPU加速渲染,这为构建高效能的嵌入式监控系统提供了可能。相比于其他RK系列的板子,RK3588不仅在处理能力上有了显著提升,还在多视频流处理方面展现了出色的性能。此外,该芯片对于国产操作系统及硬件的支持也使得其成为支持国家信息技术自主可控战略的重要组成部分。无论是用于家庭安全监控还是商业场所的安全防护,基于RK3588的设备都能提供稳定而高效的监控体验。
在基于瑞芯微RK3588平台的嵌入式系统中,利用Qt框架实现硬件解码方案可以显著提升多路高清视频监控应用的性能与用户体验。由于RK3588内置了强大的视频处理单元(VPU),支持H.264、H.265等主流编码格式的硬解,开发者可以通过调用其提供的MPP(Media Process Platform)或Rockchip VPU API,在Qt应用程序中创建专用的解码线程,将解码后的YUV数据转换为OpenGL或EGL可识别的纹理格式,并通过QOpenGLWidget或QQuickItem进行高效渲染。这种方式不仅能实现32路1080P视频的流畅播放,还能有效降低CPU占用率,充分发挥RK3588的异构计算优势。
二、效果图
三、功能特点
- 主界面采用停靠窗体模式,各种组件以小模块的形式加入,可自定义任意模块加入。
- 停靠模块可拖动任意位置嵌入和悬浮,支持最大化全屏,支持多屏幕。
- 双重布局文件存储机制,正常模式、全屏模式都对应不同的布局方案,自动切换和保存,比如全屏模式可以突出几个模块透明显示在指定位置,更具科幻感现代化。
- 原创onvif协议机制,采用底层协议解析(udp广播搜索+http请求执行命令)更轻量易懂易学习拓展,不依赖任何第三方组件比如gsoap。
- 原创数据导入、导出、打印机制,跨平台不依赖任何组件,瞬间导出数据。
- 内置多个原创组件,宇宙超值超级牛逼,包括数据导入导出组件(导出到xls、pdf、打印)、数据库组件(数据库管理线程、自动清理数据线程、万能分页、数据请求等)、地图组件、视频监控组件、文件多线程收发组件、onvif通信组件、通用浏览器内核组件等。
- 自定义信息框、错误框、询问框、右下角提示框(包含多种格式)等。
- 精美换肤,高达20套皮肤样式随意更换,所有样式全部统一,包括菜单等。
- 选中通道对应设备树节点高亮,选中通道节点对应视频控件高亮,方便查看当前通道信息。
- 视频控件悬浮条可以自行增加多个按钮,监控界面底部小工具栏也可自行增加按钮。
- 双击摄像机节点自动播放视频,双击节点自动依次添加视频,会自动跳到下一个,双击父节点自动添加该节点下的所有视频。可选主码流、子码流。
- 录像机管理、摄像机管理,可添加删除修改导入导出打印信息,立即应用新的设备信息生成树状列表,不需重启。
- 摄像机搜索支持一键搜索和批量添加,支持onvif的NVR一键添加子设备,可以手动设置开始地址和数量一键生成摄像机信息。
- 可选多种内核自由切换,ffmpeg、vlc、mpv等,均可在pro中设置。推荐用ffmpeg,跨平台最多,默认提供好了linux和mac平台上编译好的库。
- 支持windows、linux、macos等系统硬解码,还支持嵌入式linux RKMPP硬解码,可设置硬解码类型(dxva2、d3d11va、vaapi、vdpau等)。
- 各种模块可以勾选是否激活,方便根据实际需求搭配各种组合,比如隐藏电子地图模块,隐藏远程回放模块只保留本地回放等。
- 尽最大化可能,将常用的功能封装接口,全局静态函数调用,极其容易使用,提供各种使用示例,方便用户二开。
- 默认采用opengl绘制视频,超低的CPU资源占用,支持yuyv和nv12两种格式绘制,性能爆表。
- 标签和图形信息支持三种绘制方式,绘制到遮罩层、绘制到图片、源头绘制(对应信息可以存储到文件)。
- 包括但不限于视频监控内核组件的所有功能,可参阅说明书中功能介绍 [视频监控内核](###8.1 视频监控内核)。
- 高度可定制化,用户可以很方便的在此基础上衍生自己的功能,比如增加自定义模块,增加运行模式、机器人监控、无人机监控、挖掘机监控、广播监控等。
- 支持xp、win7、win10、win11、linux、mac、各种国产系统(UOS、中标麒麟、银河麒麟等)、嵌入式linux等系统。
- 注释完整,项目结构清晰,超级详细完整的使用开发手册,精确到每个代码文件的功能说明,不断持续迭代版本。
四、相关地址
- 国内站点:gitee.com/feiyangqing…
- 国际站点:github.com/feiyangqing…
- 个人作品:blog.csdn.net/feiyangqing…
- 文件地址:pan.baidu.com/s/1d7TH_GEY… 提取码:01jf 名称:bin_video_system
五、相关代码
#include "onvifdevicemanage.h"
#include "qthelper.h"
#include "dbquery.h"
#include "deviceutil.h"
#include "devicehelper.h"
#include "onvifthread.h"
#include "videomanage.h"
#include "urlhelper.h"
bool OnvifDeviceManage::checkUrl(const QString &url, OnvifDeviceData &deviceData)
{
//不是rtsp开头也不是摄像机,为空也包含在这个判断中
if (!url.startsWith("rtsp")) {
return false;
}
//找到对应的索引/没有码流地址不用继续
int index = DbData::getIpcIndex(url);
if (index < 0) {
return false;
}
//只有onvif地址存在才是onvif设备
QString onvifAddr = DbData::IpcInfo_OnvifAddr.at(index);
if (onvifAddr.isEmpty()) {
return false;
}
//onvif地址中的IP和rtsp地址中的IP必须一致
//为什么会出现这个现象(因为用户很可能直接在原来的正确的带有onvif地址的信息中修改了rtsp地址)
if (UrlHelper::getUrlIP(onvifAddr) != UrlHelper::getUrlIP(url)) {
return false;
}
//设置唯一标识
QList<VideoWidget *> videoWidgets = VideoManage::Instance()->getVideoWidgets();
foreach (VideoWidget *videoWidget, videoWidgets) {
if (videoWidget->getVideoPara().mediaUrl == url) {
deviceData.flag = videoWidget->getWidgetPara().videoFlag;
break;
}
}
//对应结构体数据赋值
deviceData.userName = DbData::IpcInfo_UserName.at(index);
deviceData.userPwd = DbData::IpcInfo_UserPwd.at(index);
deviceData.onvifAddr = DbData::IpcInfo_OnvifAddr.at(index);
deviceData.profileToken = DbData::IpcInfo_ProfileToken.at(index);
deviceData.videoSource = DbData::IpcInfo_VideoSource.at(index);
return true;
}
QList<OnvifPresetInfo> OnvifDeviceManage::getPresets(const QString &url)
{
QList<OnvifPresetInfo> presets;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
presets = device->getPresets(deviceData.profileToken);
device->debug("onvif组件", QString("获取预置位置: %1").arg(presets.count()));
}
return presets;
}
bool OnvifDeviceManage::ptzControl(quint8 type, const QString &url, qreal x, qreal y, qreal z)
{
bool result = false;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
result = device->ptzControl(type, deviceData.profileToken, x, y, z);
device->debug("onvif组件", QString("执行云台控制: %1").arg(result));
}
return result;
}
bool OnvifDeviceManage::ptzFocus(const QString &url, int speed)
{
bool result = false;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
result = device->ptzFocus(deviceData.videoSource, speed);
device->debug("onvif组件", QString("执行动态调焦: %1").arg(result));
}
return result;
}
bool OnvifDeviceManage::ptzPreset(quint8 type, const QString &url, const QString &presetToken, const QString &presetName)
{
bool result = false;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
result = device->ptzPreset(type, deviceData.profileToken, presetToken, presetName);
device->debug("onvif组件", QString("预置位置处理: %1").arg(result));
}
return result;
}
QList<OnvifOsdInfo> OnvifDeviceManage::getOSDs(const QString &url)
{
QList<OnvifOsdInfo> osds;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
VideoWidget *widget = VideoManage::Instance()->getVideoWidget(url, url);
if (widget) {
osds = device->getOsds(deviceData.videoSource, QSize(widget->getVideoWidth(), widget->getVideoHeight()));
device->debug("onvif组件", QString("获取OSD集合: %1").arg(osds.count()));
}
}
return osds;
}
bool OnvifDeviceManage::osdControl(quint8 type, const QString &url, const OnvifOsdInfo &osd)
{
bool result = false;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
VideoWidget *widget = VideoManage::Instance()->getVideoWidget(url, url);
if (widget) {
QString flag;
QSize size(widget->getVideoWidth(), widget->getVideoHeight());
if (type == 0) {
flag = "创建OSD信息";
result = device->createOsd(size, osd);
} else if (type == 1) {
flag = "设置OSD信息";
result = device->setOsd(size, osd);
} else if (type == 2) {
flag = "删除OSD信息";
result = device->deleteOsd(osd.token);
}
device->debug("onvif组件", QString("%1: %2").arg(flag).arg(result));
}
}
return result;
}
OnvifNetConfig OnvifDeviceManage::getNetConfig(const QString &url)
{
OnvifNetConfig netConfig;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
netConfig = device->getNetConfig();
device->debug("onvif组件", QString("获取网络配置: %1").arg(netConfig.ipAddress == "255.255.255.255" ? "false" : "true"));
}
return netConfig;
}
bool OnvifDeviceManage::setNetConfig(const QString &url, const OnvifNetConfig &netConfig)
{
bool result = false;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
result = device->setNetConfig(netConfig);
}
return result;
}
SINGLETON_IMPL(OnvifDeviceManage)
OnvifDeviceManage::OnvifDeviceManage(QObject *parent) : QThread(parent)
{
//注册数据类型
qRegisterMetaType<QVector<int> >("QVector<int>");
//显示截图的标签
labImage.setWindowFlags(Qt::WindowStaysOnTopHint);
labImage.setFixedSize(QSize(800, 600));
labImage.setWindowTitle("抓拍图片预览");
QtHelper::setFormInCenter(&labImage);
//关联信号
connect(OnvifThread::Instance(), SIGNAL(receiveImage(QString, QImage)), this, SLOT(receiveImage(QString, QImage)));
connect(OnvifThread::Instance(), SIGNAL(receiveEvent(QString, OnvifEventInfo)), this, SLOT(receiveEvent(QString, OnvifEventInfo)));
connect(OnvifThread::Instance(), SIGNAL(receiveResult(QString, QString, QVariant)), this, SIGNAL(receiveResult(QString, QString, QVariant)));
//启动onvif线程
OnvifThread::Instance()->start();
//启动本线程
stopped = false;
this->start();
//重新保存了摄像头信息需要复位
connect(AppEvent::Instance(), SIGNAL(saveIpcInfo()), this, SLOT(reset()));
}
OnvifDeviceManage::~OnvifDeviceManage()
{
stopped = true;
this->wait();
}
void OnvifDeviceManage::run()
{
this->reset();
while (!stopped) {
//优先处理图文警情信息
if (listMsg.count() > 0) {
//先延时一下以便抓图的文件确保生成
msleep(100);
mutex.lock();
QString msg = listMsg.takeFirst();
QString file = listFile.takeFirst();
mutex.unlock();
QMetaObject::invokeMethod(this, "addMsgList", Q_ARG(QString, msg), Q_ARG(QString, "待处理!"), Q_ARG(QImage, QImage(file)));
continue;
}
//没有启用离线检测则不用继续
if (!OtherConfig::CheckOffline || DbData::IpcInfo_Count == 0) {
msleep(100);
continue;
}
//到了最后一个说明一轮完成了需要多休息一下
if (finsh) {
finsh = false;
//下面的写法在保证延时很长一个时间的中途/还能立即跳出而不是等延时完成
//msleep(5000);
int count = 0;
while (!stopped && listMsg.count() == 0) {
msleep(10);
count++;
if (count == 500) {
break;
}
}
} else {
msleep(10);
}
//判断设备是否在线/为什么这里放在下面执行/是为了保证首次处理/对应主界面设备树状控件/全部加载完成
this->checkOnline();
}
stopped = false;
}
void OnvifDeviceManage::reset()
{
finsh = true;
currentIndex = 0;
}
void OnvifDeviceManage::checkOnline()
{
QString url = DbData::getRtspAddr(currentIndex);
bool online = DeviceUtil::checkOnline(url);
//过滤下只有当状态变化了才需要
if (online) {
if (!DbData::IpcInfo_IpcOnline.at(currentIndex)) {
DeviceHelper::setDeviceIcon(url, true);
}
} else {
if (DbData::IpcInfo_IpcOnline.at(currentIndex)) {
DeviceHelper::setDeviceIcon(url, false);
}
}
//qDebug() << TIMEMS << currentIndex << online << url;
DbData::IpcInfo_IpcOnline[currentIndex] = online;
//每次递增直到末尾则重新开始
currentIndex++;
if (currentIndex == DbData::IpcInfo_Count) {
this->reset();
}
}
void OnvifDeviceManage::receivePlayStart(int time)
{
//拿到触发信号的控件
VideoWidget *videoWidget = (VideoWidget *)sender();
QString mediaUrl = videoWidget->getVideoPara().mediaUrl;
//设置设备名称用于悬浮条显示
int index = DbData::getIpcIndex(mediaUrl);
if (index >= 0) {
QString mediaName = DbData::IpcInfo_IpcName.at(index);
videoWidget->setProperty("mediaName", mediaName);
}
//轮询阶段不处理
if (AppConfig::Polling) {
//return;
}
//如果没有启用任何服务则不用继续
if (!AppConfig::OnvifNtp && !AppConfig::OnvifEvent) {
//return;
}
//先校验当前视频对应的信息是否符合
OnvifDeviceData deviceData;
if (checkUrl(mediaUrl, deviceData)) {
//交给线程执行指令
OnvifThread::Instance()->append(deviceData, "getServices");
if (AppConfig::OnvifEvent) {
OnvifThread::Instance()->append(deviceData, "getEvent");
}
if (AppConfig::OnvifNtp) {
OnvifThread::Instance()->append(deviceData, "setDateTime");
}
}
}
void OnvifDeviceManage::receivePlayFinsh()
{
//先校验当前视频对应的信息是否符合
VideoWidget *videoWidget = (VideoWidget *)sender();
OnvifDeviceData deviceData;
if (checkUrl(videoWidget->getVideoPara().mediaUrl, deviceData)) {
//交给线程执行指令
OnvifThread::Instance()->append(deviceData, "remove");
}
}
void OnvifDeviceManage::receiveImage(const QString &url, const QImage &image)
{
QImage img = image;
if (!img.isNull()) {
//等比例缩放一下
img = img.scaled(labImage.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
labImage.setPixmap(QPixmap::fromImage(img));
labImage.show();
}
}
void OnvifDeviceManage::receiveEvent(const QString &url, const OnvifEventInfo &event)
{
//可能临时关闭了事件订阅
if (!AppConfig::OnvifEvent) {
return;
}
//事件内容存放在结构体数据中
//qDebug() << TIMEMS << event;
QString name = event.dataName;
QVariant value = event.dataValue;
//true/1 false/0 字符转成bool类型方便统一判断
bool alarm = value.toBool();
//这里添加到消息栏方便查看是何种内容/方便分析判断
DeviceHelper::addMsg(QString("%1|%2").arg(name).arg(value.toString()), alarm ? 2 : 0);
//有多种关键字可以自行过滤需要的
if (!OtherConfig::EventName.contains(name)) {
qDebug() << TIMEMS << "未知报警事件" << QtHelper::getIP(url) << name << value;
return;
} else {
qDebug() << TIMEMS << "收到报警事件" << QtHelper::getIP(url) << name << value;
}
//过滤不在本系统中的设备发过来的报警
int index = DbData::IpcInfo_OnvifAddr.indexOf(url);
if (index < 0) {
return;
}
QString ipcName = DbData::IpcInfo_IpcName.at(index);
int i = OtherConfig::EventName.indexOf(name);
QString info = (alarm ? OtherConfig::EventAlarm.at(i) : OtherConfig::EventNormal.at(i));
//添加到信息栏
QString msg = QString("%1%2").arg(ipcName).arg(info);
DeviceHelper::addMsg(msg, alarm ? 2 : 0);
//抓拍对应通道图像
QString flag, fileName;
if (alarm) {
QString rtspMain = DbData::IpcInfo_RtspMain.at(index).split("|").first();
QString rtspSub = DbData::IpcInfo_RtspSub.at(index).split("|").first();
VideoWidget *videoWidget = VideoManage::Instance()->getVideoWidget(rtspMain, rtspSub);
if (videoWidget) {
flag = videoWidget->objectName();
fileName = QString("%1/%2/%3_%4.jpg").arg(OtherConfig::ImageAlarmPath).arg(QDATE).arg(flag).arg(STRDATETIMEMS);
videoWidget->snap(fileName);
//放入队列线程处理(保证抓图完成)
mutex.lock();
listMsg << info;
listFile << fileName;
mutex.unlock();
}
}
//右下角弹出提示
if (AppConfig::TipInterval != 10000) {
QtHelper::showTipBox("提示", msg, AppConfig::FullScreen, true, AppConfig::TipInterval);
}
//暂定开关量报警弹出报警视频并录像
if (alarm && (info == "开关量报警" || info == "开关量联动")) {
QString url = DbData::getRtspAddr(index);
fileName = QString("%1/%2/%3_%4.mp4").arg(OtherConfig::VideoAlarmPath).arg(QDATE).arg(flag).arg(STRDATETIMEMS);
DeviceUtil::previewVideo(url, flag, AppConfig::AlarmSaveTime, fileName);
}
//播放报警声音并插入警情到数据库/报警则带上文件路径/有报警视频则优先取视频路径
if (alarm) {
DeviceUtil::playAlarm("8.wav");
DbQuery::addUserLog(flag.right(2), "报警日志", msg, fileName);
} else {
DeviceUtil::stopSound();
DbQuery::addUserLog("报警日志", msg);
}
}
void OnvifDeviceManage::addMsgList(const QString &msg, const QString &result, const QImage &image)
{
DeviceHelper::addMsgList(msg, result, image);
}