一、前言
一般視頻監控行業都會選擇把視頻存儲在本地NVR或者服務器上,而不是存儲在客戶端電腦,只有當用戶經費預算有限的時候,或者用戶特殊需求要求存儲在本地客戶端電腦的時候才會開啟存儲到本地,正常來說視頻存儲需要專用的硬盤來存儲比較好,一個是安全性,更重要的是容量大,一般NVR可以外接8塊硬盤,每個4T,一起就是32T,這個對於普通的電腦來說肯定是比不了的,尤其是現在高清視頻階段了,需要存儲的視頻清晰度很高,就算用H265編碼存儲,一天24小時的視頻量也不少,很多重要場所重點部位,要求存儲的視頻天數大於60天,這個一般電腦上是不夠存儲的,所以現在還衍生了磁盤陣列來存儲在服務器,這樣能夠存儲的天數更大。
本視頻監控系統默認內核採用的是ffmpeg來解析rtsp視頻流,同時還支持vlc內核,ffmpeg在播放視頻流的時候,可以打開文件進行視頻流信息的存儲,默認存儲的是裸流,可以自行根據編碼規則改成MP4格式的,這樣存儲的視頻文件可以用其他播放器打開,而如果是存儲的裸流的話,一般需要ffmpeg自身去打開播放,目前測試過的支持裸流直接播放的播放器是完美解碼播放器potplayer,如果還有同名的aac聲音文件的話,會同步播放聲音。
採用ffmpeg來存儲視頻流和對應的聲音文件,還是非常方便的,直接打開文件後寫入data數據即可,在拿到視頻流解碼的時候,可以判斷是否還帶了音頻流,如果帶了的話,可以同步存儲音頻文件到aac文件,存儲音頻流的時候需要做個特殊處理,先寫入dts頭,再寫入音頻流數據,不然會出錯。本系統封裝的ffmpeg類,提供了兩種方式存儲視頻文件,一種是存儲成單個視頻文件,還有一種是按照存儲間隔比如30分鐘存儲成多個視頻文件,到了時間間隔重新生成文件存儲,在視頻監控領域第二種用的比較多,這樣方便回放錄像和拷貝錄像,單個文件比較小,很容易查詢和拷貝。
皮膚開源:[
https://gitee.com/feiyangqingyun/QWidgetDemo](https://gitee.com/feiyangqingyun/QWidgetDemo) [https://github.com/feiyangqingyun/QWidgetDemo](https://github.com/feiyangqingyun/QWidgetDemo)
文件名稱:styledemo
體驗地址:[
https://gitee.com/feiyangqingyun/QWidgetExe](https://gitee.com/feiyangqingyun/QWidgetExe) [https://github.com/feiyangqingyun/QWidgetExe](https://github.com/feiyangqingyun/QWidgetExe)
文件名稱:bin_video_system.zip
二、功能特點
1. 支持16畫面切換,全屏切換等,包括1+4+6+8+9+13+16畫面切換。
2. 支持alt+enter全屏,esc退出全屏。
3. 自定義信息框+錯誤框+詢問框+右下角提示框。
4. 17套皮膚樣式隨意更換,所有樣式全部統一,包括菜單等。
5. 雲台儀錶盤鼠標移上去高亮,八個方位精準識別。
6. 底部畫面工具欄(畫面分割切換+截圖聲音等設置)移上去高亮。
7. 可在配置文件更改左上角logo+中文軟件名稱+英文軟件名稱。
8. 封裝了百度地圖,三維切換,設備點位,鼠標按下獲取經緯度等。
9. 堆棧窗體,每個窗體都是個單獨的qwidget,方便編寫自己的代碼。
10. 頂部鼠標右鍵菜單,可動態控制時間CPU+左上角面板+左下角面板+右上角面板+右下角面板的顯示和隱藏,支持恢復默認布局。
11. 工具欄可以放置多個小圖標和關閉圖標。
12. 左側右側可拖動拉伸,並自動記憶寬高位置,重啟後恢復。
13. 雙擊攝像機節點自動播放視頻,雙擊節點自動依次添加視頻,會自動跳到下一個,雙擊父節點自動添加該節點下的所有視頻。
14. 攝像機節點拖曳到對應窗體播放視頻,同時支持拖曳本地文件直接播放。
15. 視頻畫面窗體支持拖曳交換,瞬間響應。
16. 雙擊節點+拖曳節點+拖曳窗體交換位置,均自動更新url.txt。
17. 支持從url.txt中加載16通道視頻播放,自動記憶最後通道對應的視頻,軟件啟動後自動打開播放。
18. 右下角音量條控件,失去焦點自動隱藏,音量條帶靜音圖標。
19. 集成百度地圖,可以添加設備對應位置,自動生成地圖,支持縮放和三維地圖,提供地圖風格選擇,共12種風格。
20. 視頻拖動到通道窗體外自動刪除視頻。
21. 鼠標右鍵可刪除當前+所有視頻,截圖當前+所有視頻。
22. 錄像機管理、攝像機管理,可添加刪除修改導入導出打印信息,立即應用新的設備信息生成樹狀列表,不需重啟。
23. 在pro文件中可以自由開啟是否加載地圖。
24. 視頻播放可選四種內核自由切換,vlc+ffmpeg+easyplayer+海康sdk,均可在pro中設置。
25. 可設置1+4+9+16畫面輪詢,可設置輪詢間隔以及輪詢碼流類型等,直接在主界面底部工具欄右側單擊啟動輪詢按鈕即可,再次單擊停止輪詢。
26. 默認超過10秒鐘未操作自動隱藏鼠標指針。
27. 支持onvif搜素設備,支持任意onvif攝像機,包括但不限于海康大華宇視天地偉業華為等,支持onvif雲台控制。
28. 高度可定製化,用戶可以很方便的在此基礎上衍生自己的功能,支持linux系統。
三、效果圖

四、核心代碼
void FFmpegThread::run()
{
//計時
QTime time;
while (!stopped) {
//根據標誌位執行初始化操作
if (isPlay) {
if (init()) {
//啟用保存文件,先關閉文件
if (saveFile) {
if (fileVideo.isOpen()) {
fileVideo.close();
}
if (fileAudio.isOpen()) {
fileAudio.close();
}
//如果存儲間隔大於0說明需要定時存儲
if (saveInterval > 0) {
fileName = QString("%1/%2.mp4").arg(savePath).arg(STRDATETIME);
emit sig_startSave();
}
if (videoStreamIndex >= 0) {
fileVideo.setFileName(fileName);
fileVideo.open(QFile::WriteOnly);
}
if (audioStreamIndex >= 0) {
fileAudio.setFileName(fileName.replace(QFileInfo(fileName).suffix(), "aac"));
fileAudio.open(QFile::WriteOnly);
}
}
emit receivePlayOk();
} else {
break;
emit receivePlayError();
}
isPlay = false;
continue;
}
if (isPause) {
//這裡需要假設正常,暫停期間繼續更新時間
lastTime = QDateTime::currentDateTime();
msleep(1);
continue;
}
time.restart();
if (av_read_frame(avFormatContext, avPacket) >= 0) {
//判斷當前包是視頻還是音頻
int packetSize = avPacket->size;
int index = avPacket->stream_index;
if (index == videoStreamIndex) {
//解碼視頻流
if (hardware == "none") {
avcodec_decode_video2(videoCodec, avFrame2, &frameFinish, avPacket);
} else {
frameFinish = decode_packet(videoCodec, avPacket);
}
if (frameFinish) {
//計數,只有到了設定的幀率才刷新圖片
frameCount++;
if (frameCount != interval) {
av_packet_unref(avPacket);
av_freep(avPacket);
msleep(1);
continue;
} else {
frameCount = 0;
}
//保存視頻流數據到文件
QMutexLocker lock(&mutex);
if (fileVideo.isOpen()) {
//rtmp視頻流需要添加pps sps
#ifndef gcc45
av_bsf_filter(filter, avPacket, avFormatContext->streams[videoStreamIndex]->codecpar);
#endif
fileVideo.write((const char *)avPacket->data, packetSize);
}
//將數據轉成一張圖片
sws_scale(swsContext, (const uint8_t *const *)avFrame2->data, avFrame2->linesize, 0, videoHeight, avFrame3->data, avFrame3->linesize);
//以下兩種方法都可以
//QImage image(avFrame3->data[0], videoWidth, videoHeight, QImage::Format_RGB32);
QImage image((uchar *)buffer, videoWidth, videoHeight, QImage::Format_RGB32);
if (!image.isNull()) {
lastTime = QDateTime::currentDateTime();
emit receiveImage(image);
//計算本地視頻文件等待時間
int useTime = time.elapsed();
if (!isRtsp && videoFps > 0) {
//一幀解碼用時+固定休眠1毫秒+其他用時1毫秒
int frameTime = useTime + 1 + 1;
//等待時間=1秒鐘即1000毫秒-所有幀解碼完成用的毫秒數/幀數
sleepTime = (1000 - (videoFps * frameTime)) / videoFps;
//有時候如果圖片很大或者解碼很難比如h265造成解碼一張圖片耗時很大可能出現負數
sleepTime = sleepTime < 0 ? 0 : sleepTime;
}
//qDebug() << TIMEMS << image.size() << "useTime" << time.elapsed() << "sleepTime" << sleepTime << "videoFps" << videoFps;
}
msleep(sleepTime);
}
} else if (index == audioStreamIndex) {
//解碼音頻流,這裡暫不處理,以後交給sdl播放
//保存音頻流數據到文件
QMutexLocker lock(&mutex);
if (fileAudio.isOpen()) {
//先寫入dts頭,再寫入音頻流數據
dtsData[3] = (char)(((2 & 3) << 6) + ((7 + packetSize) >> 11));
dtsData[4] = (char)(((7 + packetSize) & 0x7FF) >> 3);
dtsData[5] = (char)((((7 + packetSize) & 7) << 5) + 0x1F);
fileAudio.write((const char *)dtsData, 7);
fileAudio.write((const char *)avPacket->data, packetSize);
}
}
} else if (!isRtsp) {
//如果不是視頻流則說明是視頻文件播放完畢
break;
}
av_packet_unref(avPacket);
av_freep(avPacket);
msleep(1);
}
emit sig_stopSave();
//線程結束後釋放資源
free();
stopped = false;
isPlay = false;
isPause = false;
emit receivePlayFinsh();
qDebug() << TIMEMS << "stop ffmpeg thread";
}原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/221264.html
微信掃一掃
支付寶掃一掃