一、前言
在上一篇文章將視頻文件存儲好了,需要提供界面方便用戶查詢視頻文件進行回放,其實這個回放就是播放歷史存儲的視頻文件,並不是什么高大上的東西,視頻回放在這個系統中分三種,第一種是本地回放,回放存儲在客戶端本地的視頻文件;第二種是遠程回放,采用NVR廠家提供的SDK開發包或者國標GB28181協議來回放存儲在NVR上的視頻文件;第三種是設備回放(統一放在設備播放模塊),通過rtsp的格式來回放NVR上存儲的視頻文件,這個主要是方便第三方集成廠家開發,畢竟GB28181協議比較復雜,調試麻煩,不如直接指定特定的rtsp格式來回放視頻,當然需要在請求的rtsp的地址中帶上對應的用戶驗證信息,不然沒法保證安全性。
如果存儲采用的是ffmpeg,則回放該存儲的視頻文件,也是直接用ffmpeg打開文件播放即可,可以自由控制播放的速度,由於是裸流,默認很快的,在解碼的時候都是使勁的解碼繪制,所以這個特性剛好提供了速度控制的可能,自己在解碼的時候休息一下下,就相當於慢點播放。如果存儲采用的是vlc,則存儲的是標准的視頻文件,可以直接用其他播放器打開進行播放的,也可以用vlc打開本地文件播放的形式來播放。
本地回放模塊,提供了查詢界面,首先選擇某個通道或者所有通道,再選擇視頻類型,有存儲視頻和報警視頻兩種,一般這兩個都會分開文件夾存儲的,優先級不一樣,最后選擇開始時間和結束時間,默認是按照天來計算的,還可以精確到時分秒,選擇好以后單擊查詢按鈕則會將符合條件的所有視頻文件列出來,雙擊其中的一個文件就能啟用播放,一個視頻播放完畢,會自動跳到下一個文件繼續播放,直到最后一個文件,界面上提供了播放暫停按鈕,可以單擊暫停當前播放的視頻,還能看到播放的進度。
皮膚開源:https://gitee.com/feiyangqingyun/QWidgetDemo https://github.com/feiyangqingyun/QWidgetDemo
文件名稱:styledemo
體驗地址:https://gitee.com/feiyangqingyun/QWidgetExe https://github.com/feiyangqingyun/QWidgetExe
文件名稱:bin_video_system.zip
二、功能特點
- 支持16畫面切換,全屏切換等,包括1+4+6+8+9+13+16畫面切換。
- 支持alt+enter全屏,esc退出全屏。
- 自定義信息框+錯誤框+詢問框+右下角提示框。
- 17套皮膚樣式隨意更換,所有樣式全部統一,包括菜單等。
- 雲台儀表盤鼠標移上去高亮,八個方位精准識別。
- 底部畫面工具欄(畫面分割切換+截圖聲音等設置)移上去高亮。
- 可在配置文件更改左上角logo+中文軟件名稱+英文軟件名稱。
- 封裝了百度地圖,三維切換,設備點位,鼠標按下獲取經緯度等。
- 堆棧窗體,每個窗體都是個單獨的qwidget,方便編寫自己的代碼。
- 頂部鼠標右鍵菜單,可動態控制時間CPU+左上角面板+左下角面板+右上角面板+右下角面板的顯示和隱藏,支持恢復默認布局。
- 工具欄可以放置多個小圖標和關閉圖標。
- 左側右側可拖動拉伸,並自動記憶寬高位置,重啟后恢復。
- 雙擊攝像機節點自動播放視頻,雙擊節點自動依次添加視頻,會自動跳到下一個,雙擊父節點自動添加該節點下的所有視頻。
- 攝像機節點拖曳到對應窗體播放視頻,同時支持拖曳本地文件直接播放。
- 視頻畫面窗體支持拖曳交換,瞬間響應。
- 雙擊節點+拖曳節點+拖曳窗體交換位置,均自動更新url.txt。
- 支持從url.txt中加載16通道視頻播放,自動記憶最后通道對應的視頻,軟件啟動后自動打開播放。
- 右下角音量條控件,失去焦點自動隱藏,音量條帶靜音圖標。
- 集成百度地圖,可以添加設備對應位置,自動生成地圖,支持縮放和三維地圖,提供地圖風格選擇,共12種風格。
- 視頻拖動到通道窗體外自動刪除視頻。
- 鼠標右鍵可刪除當前+所有視頻,截圖當前+所有視頻。
- 錄像機管理、攝像機管理,可添加刪除修改導入導出打印信息,立即應用新的設備信息生成樹狀列表,不需重啟。
- 在pro文件中可以自由開啟是否加載地圖。
- 視頻播放可選四種內核自由切換,vlc+ffmpeg+easyplayer+海康sdk,均可在pro中設置。
- 可設置1+4+9+16畫面輪詢,可設置輪詢間隔以及輪詢碼流類型等,直接在主界面底部工具欄右側單擊啟動輪詢按鈕即可,再次單擊停止輪詢。
- 默認超過10秒鍾未操作自動隱藏鼠標指針。
- 支持onvif搜素設備,支持任意onvif攝像機,包括但不限於海康大華宇視天地偉業華為等,支持onvif雲台控制。
- 高度可定制化,用戶可以很方便的在此基礎上衍生自己的功能,支持linux系統。
三、效果圖
四、核心代碼
void frmVideoPlayLocal::on_btnSelect_clicked()
{
QDate dateStart = ui->dateStart->date();
QDate dateEnd = ui->dateEnd->date();
if (dateStart > dateEnd) {
QUIHelper::showMessageBoxError("開始時間不能大於結束時間!", 3);
return;
}
//將日期轉換為日期時間計算相差的天數,超過60天則提示不用繼續
QDateTime dateTimeStart = ui->dateStart->dateTime();
QDateTime dateTimeEnd = ui->dateEnd->dateTime();
if (dateTimeStart.daysTo(dateTimeEnd) >= 60) {
QUIHelper::showMessageBoxError("每次最大只能查詢60天內!", 3);
return;
}
ui->listWidget->clear();
QString videoPath;
if (ui->cboxType->currentText() == "存儲視頻") {
videoPath = QString("%1/%2").arg(QUIHelper::appPath()).arg(DBData::VideoNormalPath);
} else {
videoPath = QString("%1/%2").arg(QUIHelper::appPath()).arg(DBData::VideoAlarmPath);
}
//獲取所有文件夾名稱,根據時間查詢對應通道對應類型視頻
//如果開始時間小於或者等於結束時間,則將開始時間對應文件夾下的視頻文件添加到列表
//然后將開始時間加一天,知道大於結束時間
while (dateStart <= dateEnd) {
QString savePath = QString("%1/%2").arg(videoPath).arg(dateStart.toString("yyyy-MM-dd"));
QDir saveDir(savePath);
QStringList filter;
filter << "*.mp4" << "*.h264" ;
QStringList files = saveDir.entryList(filter);
foreach (QString file, files) {
//如果是選擇的所有通道,則不過濾視頻文件
if (ui->cboxCh->currentText() == "所有通道") {
QListWidgetItem *item = new QListWidgetItem(ui->listWidget);
item->setText(file);
item->setData(Qt::UserRole, QString(savePath));
} else {
//對應通道的視頻文件添加進來
QString videoCh = file.split("-").at(6).split(".").at(0);
QString chName = QString("Ch%1").arg(ui->cboxCh->currentIndex());
if (videoCh == chName) {
QListWidgetItem *item = new QListWidgetItem(ui->listWidget);
item->setText(file);
item->setData(Qt::UserRole, QString(savePath));
}
}
}
dateStart = dateStart.addDays(1);
}
ui->labTip->setText(QString("共找到 %1 個").arg(ui->listWidget->count()));
}
void frmVideoPlayLocal::on_btnPlayVideo_clicked()
{
if (ui->listWidget->currentRow() < 0) {
return;
}
bool check = ui->btnPlayVideo->isChecked();
if (!check) {
IconHelper::Instance()->setIcon(ui->btnPlayVideo, 0xf28d, btnRadius);
widget->next();
timerPlay->start();
} else {
IconHelper::Instance()->setIcon(ui->btnPlayVideo, 0xf144, btnRadius);
widget->pause();
timerPlay->stop();
}
}
void frmVideoPlayLocal::on_listWidget_doubleClicked()
{
timerPlay->stop();
QListWidgetItem *item = ui->listWidget->currentItem();
QString file = QString("%1/%2").arg(item->data(Qt::UserRole).toString()).arg(item->text());
//將文件路徑轉為操作系統可以識別的路徑,在windows下Qt之外的程序調用路徑必須轉換
file = QDir::toNativeSeparators(file);
//存儲的視頻不能獲取視頻長度,這里另想高招,取文件的創建時間,用修改時間減去創建時間就是長度
QFileInfo mp4File(file);
QDateTime creatTime = mp4File.created();
QDateTime lastTime = mp4File.lastModified();
//如果創建時間大於最后修改時間,說明文件長度為0或者是復制過來的而不是正常的存儲文件.
if (creatTime >= lastTime) {
QUIHelper::showMessageBoxError("非正常存儲視頻文件,請重新選擇!");
return;
}
//得到時間差,單位毫秒
qint64 milliSecondTime = creatTime.msecsTo(lastTime);
int hour = milliSecondTime / (60 * 60 * 1000);
int minute = (milliSecondTime - hour * 60 * 60 * 1000) / (60 * 1000);
int seconds = (milliSecondTime - hour * 60 * 60 * 1000 - minute * 60 * 1000) / 1000;
if (seconds >= 60) {
seconds = seconds % 60;
minute += seconds / 60;
}
if (minute >= 60) {
minute = minute % 60;
hour += minute / 60;
}
//播放時長重新計數
hourPlay = 0;
minutePlay = 0;
secondsPlay = 0;
secondsPlayCount = 0;
timerPlay->start();
//重新設置進度條和總時長
currentVideoLenght = milliSecondTime / 1000;
ui->slider->setMinimum(0);
ui->slider->setMaximum(currentVideoLenght);
ui->slider->setValue(0);
ui->labTimeAll->setText(QString("總時長: %1時%2分%3秒").arg(hour).arg(minute).arg(seconds));
//先釋放上一個播放的視頻,再打開當前播放的視頻
widget->setUrl(file);
widget->restart();
currentVideo = file;
ui->btnPlayVideo->setEnabled(true);
ui->btnPlayVideo->setChecked(false);
IconHelper::Instance()->setIcon(ui->btnPlayVideo, 0xf28d, btnRadius);
}