基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备

一、前言说明

这个功能是之前写的监控设备模拟器的衍生,同时集成在该项目中,就是单独写个竖版的app样式的界面,然后编译成安卓版本即可,默认就模拟一路设备,从设备地址下拉框中选择安卓摄像头即可,所有的底层功能使用的就是设备模拟器的,为何会有这个需求?之前的模拟器已经可以将任意的视频文件视频流,以及本地USB摄像头或者桌面采集,转成监控设备,通过onvif协议或者28181协议,和服务端平台端进行交互,当时设计的时候就已经考虑到了跨平台的场景,所以其实这些底层组件代码是完全支持安卓的,为了使得ffmpeg能够正常采集到安卓摄像头,当时还特意搞了很久,编译带avdevice的ffmpeg安卓库,网上几乎都是不带avdevice的,搞得之前还以为代码有问题,原来是对应的库没有带。

目前在手机上只是采集了摄像头的实时视频画面,音频这块还没有实现,在win上使用dshow采集麦克风,在linux上使用alsa采集麦克风,到安卓上目前没有发现有对应的实现,所以也挺鸡肋的,后期估计先采用Qt的麦克风采集,相当于单独音频交互,如果是ffmpeg推流rtsp的话,还要涉及到混流,也就是之前的视频流需要加上采集的音频流,混合后再一起推流。

二、效果图



三、相关地址

  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_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();
}
相关推荐
用户2018792831676 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子6 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82276 小时前
安卓接入Max广告源
android
齊家治國平天下6 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO6 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel7 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢7 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖7 小时前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉8 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql