## 一、前言说明
这个功能是之前写的监控设备模拟器的衍生,同时集成在该项目中,就是单独写个竖版的app样式的
界面,然后
编译成安卓版本即可,默认就模拟一路设备,从设备地址下拉框中选择安卓摄像头即可,所有的底层功能使用的就是设备模拟器的,为何会有这个需求?之前的模拟器已经可以将任意的视频
文件视频流,以及本地USB摄像头或者桌面采集,转成监控设备,通过onvif协议或者28181协议,和服务端平台端进行交互,当时设计的时候就已经考虑到了跨平台的场景,所以其实这些底层组件代码是完全支持安卓的,为了使得ffmpeg能够正常采集到安卓摄像头,当时还特意搞了很久,编译带avdevice的ffmpeg安卓库,网上几乎都是不带avdevice的,搞得之前还以为代码有
问题,原来是对应的库
没有带。
目前在手机上只是采集了摄像头的实时视频画面,音频这块还没有实现,在win上使用dshow采集麦克风,在linux上使用alsa采集麦克风,到安卓上目前没有发现有对应的实现,所以也挺鸡肋的,后期估计先采用Qt的麦克风采集,相当于单独音频交互,如果是ffmpeg推流rtsp的话,还要涉及到混流,也就是之前的视频流需要加上采集的音频流,混合后再一起推流。
## 二、效果图



## 三、相关地址
1. 国内站点:[
https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[
https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[
https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 文件地址:[
https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_video_simulate。
## 四、功能特点
1. 标准onvif协议,支持设备搜索、获取参数、快照抓图等。
2. 支持264/265/aac等标准视音频协议传输。
3. 支持多路批量onvif设备模拟,每一路都独立的端口。
4. 支持本地摄像头采集转成onvif,可选择不同的设备、分辨率、帧率等参数。
5. 支持本地桌面采集转成onvif,可选择不同的屏幕、分辨率、帧率等参数。
6. 支持各种视频文件和视频流转成onvif,可重新设置编码转换以及分辨率转换。
7. 支持4K、8K等高清分辨率,不限制分辨率,非264/265会自动转码推流。
8. 每一路都可以设置统一或者独立的用户验证信息,为空则表示不验证。
9. 可以把任意内容接入到NVR以及视频监控系统,方便保存录像文件,以便回放可查。
10. 也可作为压力测试工具,比如模拟几千路onvif设备,让集成平台软件做接入压力测试。
11. 推出去的流不仅有rtsp格式,还支持rtmp、http、flv、ws-flv、webrtc等方式访问,可以直接网页查看。
12. 在管理工具上可以看到每一路的推流状况以及分辨率信息,非常直观。
13. 支持自动重连拉流,重连推流,保证7乘以24小时稳定运行。
14. 可设置开机自启动运行和后台运行,不
显示在任务栏,作为后台服务运行。
15. 可批量添加文件、添加目录,自动将目录下的所有文件添加到模拟器。
16. 多功能添加地址面板,可以选择本地设备和监控设备,本地设备会自动识别摄像头设备和桌面设备,监控设备可以选择不同厂家,自动填充对应rtsp格式,填入用户信息即可,可以批量递增添加监控设备。
17. 可无缝上传到市面上所有的onvif协议设备,包括海康、大华、宇视、华为、天地伟业等,也支持ONVIF Device Manager国际onvif工具。
18. 支持gb28181设备模拟,具备设备注册、设备注销、设备心跳、设备信息、设备配置、设备状态应答等。
19. 支持模拟报警和位置上报等,方便平台侧显示对应设备的实时位置。
20. 支持一键添加批量模拟28181设备,实时显示已注册和已注销状态。
21. 支持将本地桌面、本地摄像头、任意视频文件、视频流文件、手机摄像头等转换成28181设备,添加到NVR或者国标软件平台。
22. sip协议同时支持udp和tcp两种通信方式,视频点播同时支持udp/tcp主动/tcp被动三种方式,涵盖所有可能的场景需求。
23. 无论是onvif设备模拟组件还是28181设备模拟组件,全部原创底层协议解析,纯Qt实现,跨任意平台。
24. 代码结构框架非常清晰,注释详细,代码精简不繁琐,非常易于学习和移植,可以很容易拓展
其他接口需求。
25. 支持Qt4/Qt5/Qt6以及后续所有版本、所有编译器、所有
开发环境。
26. 支持windows、linux、mac、国产OS、嵌入式linux、RK3588、树莓派、香橙派等系统。
## 五、相关代码
```cpp
#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();
}
```