一、前言說明
這個工具前前后后也算是廢了不少功夫,最開始是因為28181服務端的組件已經完美實現,對照國標文檔看了很多遍,逐個實現需要的交互協議,整體上比onvif協議要難不少,主要是涉及到的東西比較多,有sip協議,又有xml數據封裝,云臺控制用的又是模擬設備時代的16進制數據來控制,音視頻傳輸用的又是單獨的rtp,而播放控制用的又是rtsp中的控制指令,哎呀我去全部雜交啊,一般人沒個幾個月搞不定的,發量越來越少是肯定的。
能夠把28181的服務端搞定,那設備端的指令就簡單多了,底層其實就是udp和tcp通信,根據收到的數據進行解析和交互即可,按照國標文檔來就行,肯定錯不了,如果錯了那肯定是對應平臺或者設備廠家有問題沒寫好。設備端最大難點困在如何發送視頻rtp數據這里,一直在想要不要用第三方的輪子比如jrtp,好在之前就對ffmpeg推流很熟悉了,嘗試了直接推流rtp,一開始死活不行,后面發現原來格式不對,國標要求的是rtp攜帶ts格式的數據包,對應不應該是rtp格式而應該是rtp_mpegts,相當于rtp over mpegts,其實udp推流這種就是mpegts格式,這個細節網上很少人提到,搞得這里困了很多天,以為ffmpeg實現不了,原來用純ffmpeg就可以直接實現的。
二、效果圖
三、相關地址
- 國內站點: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 "gb28181devicepush.h"
#include "ffmpegthread.h"
#include "ffmpegsave.h"
#include "videohelper.h"
#include "osdgraph.h"bool GB28181DevicePush::disableDecode = true;
GB28181DevicePush::GB28181DevicePush(QObject *parent) : QObject(parent)
{ffmpegThread = NULL;
}GB28181DevicePush::~GB28181DevicePush()
{this->stop();
}void GB28181DevicePush::setPara(const QString &flag, const QString &mediaUrl, const QString &pushUrl)
{this->flag = flag;this->mediaUrl = mediaUrl;this->pushUrl = pushUrl;
}bool GB28181DevicePush::isOk()
{return (ffmpegThread != NULL);
}void GB28181DevicePush::start()
{if (ffmpegThread || mediaUrl.isEmpty() || pushUrl.isEmpty()) {return;}//實例化視頻采集線程ffmpegThread = new FFmpegThread;//關聯播放開始信號/用來啟動推流connect(ffmpegThread, SIGNAL(receivePlayStart(int)), this, SLOT(receivePlayStart(int)));//關聯錄制信號變化/用來判斷是否推流成功connect(ffmpegThread, SIGNAL(recorderStateChanged(RecorderState, QString)), this, SLOT(recorderStateChanged(RecorderState, QString)));//設置保存視頻類將數據包信號發出來用于保存文件FFmpegSave *saveFile = ffmpegThread->getSaveFile();saveFile->setProperty("ssrc", flag);connect(saveFile, SIGNAL(receiveSaveStart()), this, SLOT(receiveSaveStart()));connect(saveFile, SIGNAL(receiveSaveFinsh()), this, SLOT(receiveSaveFinsh()));connect(saveFile, SIGNAL(receiveSaveError(int)), this, SLOT(receiveSaveError(int))); //設置播放地址ffmpegThread->setMediaUrl(mediaUrl);//設置視頻模式ffmpegThread->setVideoMode(VideoMode_Painter);//設置讀取超時時間超時后會自動重連ffmpegThread->setReadTimeout(10 * 1000);//設置連接超時時間ffmpegThread->setConnectTimeout(0);//設置重復播放相當于循環推流ffmpegThread->setPlayRepeat(true);//設置不解碼音頻ffmpegThread->setDecodeAudio(false);//設置不解碼數據ffmpegThread->setDisableDecode(disableDecode);//如果是本地設備或者桌面錄屏要取出其他參數VideoHelper::initVideoPara(ffmpegThread, mediaUrl);//啟動播放ffmpegThread->play();
}void GB28181DevicePush::stop()
{//停止推流和采集并徹底釋放對象if (ffmpegThread) {ffmpegThread->recordStop();ffmpegThread->stop();ffmpegThread->deleteLater();ffmpegThread = NULL;}
}void GB28181DevicePush::receivePlayStart(int time)
{//演示添加OSD后推流
#ifdef betaversionint height = ffmpegThread->getVideoHeight();QList<OsdInfo> osds = OsdGraph::getTestOsd(height);ffmpegThread->setOsdInfo(osds);
#endif//打開后才能啟動錄像ffmpegThread->recordStart(pushUrl);
}void GB28181DevicePush::recorderStateChanged(const RecorderState &state, const QString &)
{int width = 0;int height = 0;if (ffmpegThread) {width = ffmpegThread->getVideoWidth();height = ffmpegThread->getVideoHeight();}bool start = (state == RecorderState_Recording);emit pushStart(flag, width, height, start);
}void GB28181DevicePush::receiveSaveStart()
{emit pushChanged(flag, 0);
}void GB28181DevicePush::receiveSaveFinsh()
{emit pushChanged(flag, 1);
}void GB28181DevicePush::receiveSaveError(int)
{emit pushChanged(flag, 2);
}