一、前言说明
这个功能是之前写的监控设备模拟器的衍生,同时集成在该项目中,就是单独写个竖版的app样式的界面,然后编译成安卓版本即可,默认就模拟一路设备,从设备地址下拉框中选择安卓摄像头即可,所有的底层功能使用的就是设备模拟器的,为何会有这个需求?之前的模拟器已经可以将任意的视频文件视频流,以及本地USB摄像头或者桌面采集,转成监控设备,通过onvif协议或者28181协议,和服务端平台端进行交互,当时设计的时候就已经考虑到了跨平台的场景,所以其实这些底层组件代码是完全支持安卓的,为了使得ffmpeg能够正常采集到安卓摄像头,当时还特意搞了很久,编译带avdevice的ffmpeg安卓库,网上几乎都是不带avdevice的,搞得之前还以为代码有问题,原来是对应的库没有带。
目前在手机上只是采集了摄像头的实时视频画面,音频这块还没有实现,在win上使用dshow采集麦克风,在linux上使用alsa采集麦克风,到安卓上目前没有发现有对应的实现,所以也挺鸡肋的,后期估计先采用Qt的麦克风采集,相当于单独音频交互,如果是ffmpeg推流rtsp的话,还要涉及到混流,也就是之前的视频流需要加上采集的音频流,混合后再一起推流。
二、效果图
三、相关地址
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_simulate。
四、功能特点
- 标准onvif协议,支持设备搜索、获取参数、快照抓图等。
- 支持264/265/aac等标准视音频协议传输。
- 支持多路批量onvif设备模拟,每一路都独立的端口。
- 支持本地摄像头采集转成onvif,可选择不同的设备、分辨率、帧率等参数。
- 支持本地桌面采集转成onvif,可选择不同的屏幕、分辨率、帧率等参数。
- 支持各种视频文件和视频流转成onvif,可重新设置编码转换以及分辨率转换。
- 支持4K、8K等高清分辨率,不限制分辨率,非264/265会自动转码推流。
- 每一路都可以设置统一或者独立的用户验证信息,为空则表示不验证。
- 可以把任意内容接入到NVR以及视频监控系统,方便保存录像文件,以便回放可查。
- 也可作为压力测试工具,比如模拟几千路onvif设备,让集成平台软件做接入压力测试。
- 推出去的流不仅有rtsp格式,还支持rtmp、http、flv、ws-flv、webrtc等方式访问,可以直接网页查看。
- 在管理工具上可以看到每一路的推流状况以及分辨率信息,非常直观。
- 支持自动重连拉流,重连推流,保证7乘以24小时稳定运行。
- 可设置开机自启动运行和后台运行,不显示在任务栏,作为后台服务运行。
- 可批量添加文件、添加目录,自动将目录下的所有文件添加到模拟器。
- 多功能添加地址面板,可以选择本地设备和监控设备,本地设备会自动识别摄像头设备和桌面设备,监控设备可以选择不同厂家,自动填充对应rtsp格式,填入用户信息即可,可以批量递增添加监控设备。
- 可无缝上传到市面上所有的onvif协议设备,包括海康、大华、宇视、华为、天地伟业等,也支持ONVIF Device Manager国际onvif工具。
- 支持gb28181设备模拟,具备设备注册、设备注销、设备心跳、设备信息、设备配置、设备状态应答等。
- 支持模拟报警和位置上报等,方便平台侧显示对应设备的实时位置。
- 支持一键添加批量模拟28181设备,实时显示已注册和已注销状态。
- 支持将本地桌面、本地摄像头、任意视频文件、视频流文件、手机摄像头等转换成28181设备,添加到NVR或者国标软件平台。
- sip协议同时支持udp和tcp两种通信方式,视频点播同时支持udp/tcp主动/tcp被动三种方式,涵盖所有可能的场景需求。
- 无论是onvif设备模拟组件还是28181设备模拟组件,全部原创底层协议解析,纯Qt实现,跨任意平台。
- 代码结构框架非常清晰,注释详细,代码精简不繁琐,非常易于学习和移植,可以很容易拓展其他接口需求。
- 支持Qt4/Qt5/Qt6以及后续所有版本、所有编译器、所有开发环境。
- 支持windows、linux、mac、国产OS、嵌入式linux、RK3588、树莓派、香橙派等系统。
五、相关代码
#include "frmonvifapp.h"
#include "ui_frmonvifapp.h"
#include "qthelper.h"
#include "videoutil.h"
#include "urlutil.h"
#include "onvifdevicepush.h"
#include "onvifdeviceserver.h"frmOnvifApp::frmOnvifApp(QWidget *parent) : QWidget(parent), ui(new Ui::frmOnvifApp)
{ui->setupUi(this);this->initForm();this->initConfig();
}frmOnvifApp::~frmOnvifApp()
{server->stop();delete ui;
}void frmOnvifApp::initForm()
{//设置开启解码/需要解码后在这里显示OnvifDevicePush::disableDecode = false;//实例化设备服务类并关联信号槽server = new OnvifDeviceServer(this);connect(server, SIGNAL(pushImage(QString, QImage)), this, SLOT(pushImage(QString, QImage)));server->start();QStringList ips;ips << QtHelper::getLocalIPs();ui->cboxServerIp->addItems(ips);//不存在则取第一个int index = ui->cboxServerIp->findText(AppConfig::OnvifServerIp);if (index < 0) {QString ip = ips.first();AppConfig::OnvifServerIp = ip;AppConfig::OnvifServerHost = ip;AppConfig::OnvifPushHost = ip;}
}void frmOnvifApp::initConfig()
{VideoUtil::loadMediaUrl(ui->cboxMediaUrl, AppConfig::OnvifMediaUrl, UrlUtil::Simple);connect(ui->cboxMediaUrl->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->cboxServerIp->lineEdit()->setText(AppConfig::OnvifServerIp);connect(ui->cboxServerIp->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));connect(ui->cboxServerIp->lineEdit(), SIGNAL(textChanged(QString)), ui->txtServerHost, SLOT(setText(QString)));connect(ui->cboxServerIp->lineEdit(), SIGNAL(textChanged(QString)), ui->txtPushHost, SLOT(setText(QString)));ui->txtServerHost->setText(AppConfig::OnvifServerHost);connect(ui->txtServerHost, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtUserName->setText(AppConfig::OnvifUserName);connect(ui->txtUserName, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtUserPwd->setText(AppConfig::OnvifUserPwd);connect(ui->txtUserPwd, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtPushFlag->setText(AppConfig::OnvifPushFlag);connect(ui->txtPushFlag, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtPushHost->setText(AppConfig::OnvifPushHost);connect(ui->txtPushHost, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtPushPort->setText(QString::number(AppConfig::OnvifPushPort));connect(ui->txtPushPort, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtListenPort->setText(QString::number(AppConfig::OnvifListenPort));connect(ui->txtListenPort, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));
}void frmOnvifApp::saveConfig()
{AppConfig::OnvifMediaUrl = ui->cboxMediaUrl->lineEdit()->text().trimmed();AppConfig::OnvifServerHost = ui->txtServerHost->text().trimmed();AppConfig::OnvifServerIp = ui->cboxServerIp->lineEdit()->text().trimmed();AppConfig::OnvifUserName = ui->txtUserName->text().trimmed();AppConfig::OnvifUserPwd = ui->txtUserPwd->text().trimmed();AppConfig::OnvifPushFlag = ui->txtPushFlag->text().trimmed();AppConfig::OnvifPushHost = ui->txtPushHost->text().trimmed();AppConfig::OnvifPushPort = ui->txtPushPort->text().toInt();AppConfig::OnvifListenPort = ui->txtListenPort->text().toInt();AppConfig::writeConfig();
}void frmOnvifApp::pushImage(const QString &, const QImage &image)
{if (ui->btnStart->text() == "停止服务") {ui->labImage->setImage(image, true);} else {ui->labImage->clear();}
}void frmOnvifApp::on_btnStart_clicked()
{if (ui->btnStart->text() == "启动服务") { QString rtspUrl = QString("rtsp://%1:%2/stream/%3").arg(AppConfig::OnvifPushHost).arg(AppConfig::OnvifPushPort).arg(AppConfig::OnvifPushFlag);server->setPara(AppConfig::OnvifServerHost, AppConfig::OnvifServerIp, AppConfig::OnvifUserName, AppConfig::OnvifUserPwd);bool ok = server->append(AppConfig::OnvifPushFlag, AppConfig::OnvifListenPort, AppConfig::OnvifMediaUrl, rtspUrl);if (ok) {ui->txtPushUrl->setText(rtspUrl);ui->frame->setEnabled(false);ui->btnStart->setText("停止服务");}} else {server->clear();ui->labImage->clear();ui->txtPushUrl->clear();ui->frame->setEnabled(true);ui->btnStart->setText("启动服务");}
}#include "frmgb28181app.h"
#include "ui_frmgb28181app.h"
#include "qthelper.h"
#include "videoutil.h"
#include "urlutil.h"
#include "gb28181deviceserver.h"
#include "gb28181devicepush.h"frmGB28181App::frmGB28181App(QWidget *parent) : QWidget(parent), ui(new Ui::frmGB28181App)
{ui->setupUi(this);this->initForm();this->initConfig();
}frmGB28181App::~frmGB28181App()
{server->stop();delete ui;
}void frmGB28181App::initForm()
{//设置开启解码/需要解码后在这里显示GB28181DevicePush::disableDecode = false;//实例化设备服务类并关联信号槽server = new GB28181DeviceServer(this);connect(server, SIGNAL(statusChanged(QString, int)), this, SLOT(statusChanged(QString, int)));connect(server, SIGNAL(pushImage(QString, QImage)), this, SLOT(pushImage(QString, QImage)));server->start();QStringList ips;ips << QtHelper::getLocalIPs();ui->cboxDeviceIp->addItems(ips);//不存在则取第一个int index = ui->cboxDeviceIp->findText(AppConfig::GB28181DeviceIp);if (index < 0) {AppConfig::GB28181DeviceIp = ips.first();}
}void frmGB28181App::initConfig()
{VideoUtil::loadMediaUrl(ui->cboxMediaUrl, AppConfig::GB28181MediaUrl, UrlUtil::Simple);connect(ui->cboxMediaUrl->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->cboxSipProtocol->setCurrentIndex(ui->cboxSipProtocol->findText(AppConfig::GB28181SipProtocol));connect(ui->cboxSipProtocol, SIGNAL(currentIndexChanged(int)), this, SLOT(saveConfig()));ui->txtServerId->setText(AppConfig::GB28181ServerId);connect(ui->txtServerId, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtServerRealm->setText(AppConfig::GB28181ServerRealm);connect(ui->txtServerRealm, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtServerIp->setText(AppConfig::GB28181ServerIp);connect(ui->txtServerIp, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtServerPort->setText(QString::number(AppConfig::GB28181ServerPort));connect(ui->txtServerPort, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtServerPwd->setText(AppConfig::GB28181ServerPwd);connect(ui->txtServerPwd, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->cboxDeviceIp->lineEdit()->setText(AppConfig::GB28181DeviceIp);connect(ui->cboxDeviceIp->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtDevicePort->setText(QString::number(AppConfig::GB28181DevicePort));connect(ui->txtDevicePort, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtDeviceId->setText(AppConfig::GB28181DeviceId);connect(ui->txtDeviceId, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtChannelId->setText(AppConfig::GB28181ChannelId);connect(ui->txtChannelId, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtRegistInterval->setText(QString::number(AppConfig::GB28181RegistInterval));connect(ui->txtRegistInterval, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtHeartInterval->setText(QString::number(AppConfig::GB28181HeartInterval));connect(ui->txtHeartInterval, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));ui->txtHeartTimeout->setText(QString::number(AppConfig::GB28181HeartTimeout));connect(ui->txtHeartTimeout, SIGNAL(textChanged(QString)), this, SLOT(saveConfig()));
}void frmGB28181App::saveConfig()
{AppConfig::GB28181MediaUrl = ui->cboxMediaUrl->lineEdit()->text().trimmed();AppConfig::GB28181SipProtocol = ui->cboxSipProtocol->currentText();AppConfig::GB28181ServerId = ui->txtServerId->text().trimmed();AppConfig::GB28181ServerRealm = ui->txtServerRealm->text().trimmed();AppConfig::GB28181ServerIp = ui->txtServerIp->text().trimmed();AppConfig::GB28181ServerPort = ui->txtServerPort->text().toInt();AppConfig::GB28181ServerPwd = ui->txtServerPwd->text().trimmed();AppConfig::GB28181DeviceIp = ui->cboxDeviceIp->lineEdit()->text().trimmed();AppConfig::GB28181DevicePort = ui->txtDevicePort->text().toInt();AppConfig::GB28181DeviceId = ui->txtDeviceId->text().trimmed();AppConfig::GB28181ChannelId = ui->txtChannelId->text().trimmed();AppConfig::GB28181RegistInterval = ui->txtRegistInterval->text().toInt();AppConfig::GB28181HeartInterval = ui->txtHeartInterval->text().toInt();AppConfig::GB28181HeartTimeout = ui->txtHeartTimeout->text().toInt();AppConfig::writeConfig();
}void frmGB28181App::statusChanged(const QString &id, int status)
{QString text = (status == 1 ? "已注册" : "已注销");ui->labImage->setText(text);
}void frmGB28181App::pushImage(const QString &, const QImage &image)
{if (ui->btnStart->text() == "停止服务") {ui->labImage->setImage(image, true);} else {ui->labImage->clear();}
}void frmGB28181App::on_btnStart_clicked()
{if (ui->btnStart->text() == "启动服务") {server->setPara(AppConfig::GB28181ServerId, AppConfig::GB28181ServerRealm, AppConfig::GB28181ServerIp, AppConfig::GB28181ServerPort, AppConfig::GB28181ServerPwd, AppConfig::GB28181RegistInterval, AppConfig::GB28181HeartInterval, AppConfig::GB28181HeartTimeout);bool ok = server->append(AppConfig::GB28181SipProtocol, AppConfig::GB28181DeviceIp, AppConfig::GB28181DevicePort, AppConfig::GB28181DeviceId, AppConfig::GB28181ChannelId, AppConfig::GB28181MediaUrl);if (ok) {ui->frame->setEnabled(false);ui->btnStart->setText("停止服务");}} else {server->clear();ui->labImage->clear();ui->frame->setEnabled(true);ui->btnStart->setText("启动服务");}
}void frmGB28181App::on_btnUpdate_clicked()
{server->update();server->catalog();
}