一、前言
最開始接觸視頻監控這塊的時候,用的就是vlc作為解碼的內核,主要是因為vlc使用簡單方便,直接傳入一個句柄即可,簡單幾行代碼就可以實現一個視頻流播放,很適合初學者使用,也推薦初學者用qt+vlc來做播放器,提供的接口還是非常友好的,而且門類特別多,想要獲取媒體文件的各種信息比如寬高,設置寬高比等,直接調用接口函數傳入參數就能設置。
所有用vlc做視頻監控解碼的人都會遇到一個問題,那就是鼠標事件被接管攔截了,不能識別鼠標事件,比如雙擊最大化等,這就很憋屈了,明明很好用的一個東西,怎么突然之間鼠標事件也識別不到了呢,網上一搜一大把,主要有三個解決辦法。
- 修改vlc源碼,重新編譯,替換動態庫文件。
- 全局鼠標鈎子攔截鼠標消息進行處理。
- 設置句柄以后直接將控件/接受視頻渲染的控件禁用掉。
最終采用方法3,實現起來簡單快速,修改vlc源碼的編譯工作量太大了,畢竟vlc依賴一大堆的插件,用vlc的人一般都是初學者半吊子,哪里有能力去編譯一遍vlc哦。
二、功能特點
- 多線程實時播放視頻流和本地視頻。
- 支持windows+linux+mac,支持vlc2和vlc3。
- 多線程顯示圖像,不卡主界面。
- 自動重連網絡攝像頭。
- 可設置邊框大小即偏移量和邊框顏色。
- 可設置是否繪制OSD標簽即標簽文本或圖片和標簽位置。
- 可設置兩種OSD位置和風格。
- 可設置是否保存到文件以及文件名。
- 可直接拖曳文件到vlcwidget控件播放。
- 支持h265視頻流+rtmp等常見視頻流。
- 可暫停播放和繼續播放。
- 支持回調模式和句柄兩種模式。
- 支持線程讀取進度等信息和事件回調兩種處理模式。
- 自動將當前播放位置和音量大小是否靜音以信號發出去。
- 提供接口設置播放位置和音量及設置靜音。
- 支持存儲單個視頻文件和定時存儲視頻文件。
- 自定義頂部懸浮條,發送單擊信號通知,可設置是否啟用。
三、效果圖
四、相關站點
- 國內站點:https://gitee.com/feiyangqingyun/QWidgetDemo
- 國際站點:https://github.com/feiyangqingyun/QWidgetDemo
- 個人主頁:https://blog.csdn.net/feiyangqingyun
- 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/
- 體驗地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652
五、核心代碼
bool VlcThread::init()
{
//判斷該攝像機是否能聯通
if (checkConn && isRtsp) {
if (!checkUrl(url, checkTime)) {
return false;
}
}
QFileInfo info(url);
suffix = info.suffix();
//設置拓展名
if (url.startsWith("dshow://")) {
suffix = "dshow";
} else if (url.startsWith("rtsp")) {
suffix = "rtsp";
} else if (url.startsWith("rtmp")) {
suffix = "rtmp";
} else if (url.startsWith("http")) {
suffix = "http";
}
const char *tempArg = "";
if (suffix == "h264") {
tempArg = "--demux=h264";
} else if (suffix == "h265") {
tempArg = "--demux=h265";
}
const char *vlc_args[9] = {"-I", "dummy", "--no-osd", "--no-stats", "--ignore-config", "--no-video-on-top", "--no-video-title-show", "--no-snapshot-preview", tempArg};
vlcInst = libvlc_new(sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args);
if (vlcInst == NULL) {
return false;
}
if (isRtsp || suffix == "dshow") {
vlcMedia = libvlc_media_new_location(vlcInst, url.toUtf8().constData());
} else {
//windows上需要替換文件路徑
QString url = this->url;
url = QDir::toNativeSeparators(url);
vlcMedia = libvlc_media_new_path(vlcInst, url.toUtf8().constData());
}
if (vlcMedia == NULL) {
return false;
}
//媒體播放對象
vlcPlayer = libvlc_media_player_new_from_media(vlcMedia);
if (vlcPlayer == NULL) {
return false;
}
//創建事件管理器
if (callbackevent) {
vlcEvent = libvlc_media_player_event_manager(vlcPlayer);
libvlc_event_new(vlcEvent, this);
}
//回調方式和句柄方式兩種分別處理
if (callback) {
callbackData = new CallbackData;
callbackData->thread = this;
callbackData->pixels = new uchar[bufferWidth * bufferHeight * 4];
memset(callbackData->pixels, 0, bufferWidth * bufferHeight * 4);
int width = callbackData->thread->getBufferWidth();
int height = callbackData->thread->getBufferHeight();
//設置回調拿到每幀數據
libvlc_video_set_callbacks(vlcPlayer, lock, unlock, display, callbackData);
//設置每幀格式 RV32-Format_RGB32 RGBA-Format_RGBA8888 YUYV I420
libvlc_video_set_format(vlcPlayer, "RV32", width, height, width * 4);
} else {
//設置播放句柄
if (playWidget == NULL) {
return false;
}
#if defined(Q_OS_WIN)
libvlc_media_player_set_hwnd(vlcPlayer, (void *)playWidget->winId());
#elif defined(Q_OS_LINUX)
libvlc_media_player_set_xwindow(vlcPlayer, playWidget->winId());
#elif defined(Q_OS_MAC)
libvlc_media_player_set_nsobject(vlcPlayer, (void *)playWidget->winId());
#endif
//禁用句柄鼠標消息
QTimer::singleShot(1000, this, SLOT(disableHwnd()));
}
//設置硬件加速 none auto any d3d11va dxva2
setOption(QString(":avcodec-hw=%1").arg(hardware));
//設置通信協議 tcp udp
setOption(QString(":rtsp-%1").arg(transport));
//設置緩存時間 默認500毫秒
setOption(QString(":network-caching=%1").arg(caching));
//:rtsp-frame-buffer-size=1000000
//設置寬度高度,本地USB攝像頭需要單獨設置
if (suffix == "dshow") {
setOption(QString(":dshow-size=%1*%2").arg(videoWidth).arg(videoHeight));
} else {
setSize(videoWidth, videoHeight);
}
//設置保存文件
this->initSave();
//打開播放
libvlc_media_player_play(vlcPlayer);
//qDebug() << TIMEMS << "init vlc finsh";
return true;
}