一、前言
海康sdk顯示實時視頻流除了支持句柄方式以外,也支持回調的方式拿到每一張圖片自己繪制處理,當然回調除了拿到視頻數據,其實音頻數據也一塊拿到了,自行調用音頻設備播放就行,關於海康sdk回調這塊,還着實折騰了一陣子才搞定,可能最開始沒有參照提供的demo以及沒有徹底的搜索吧,只是單單看sdk的文檔折騰來折騰去的,搞了一星期居然沒搞定,后面找到了正確的辦法才發現,原來就差一點點一丟丟呢,這又讓我聯想到很多事情,包括生活中的事情,不都是如此么?當你鉚足了勁,試驗搞了各種辦法,快要精疲力盡放棄的時候,其實此時離成功就差一步了,真的就差那么一丟丟,處理生活中的很多事情也是如此,所以很多時候如果方向對了,堅持過努力過,還不行的話,再努力一把估計就ok了。
折騰了很久總結失敗在哪里,調用NET_DVR_RealPlay_V40設置回調函數也是對的,回調函數里面也進去了,調用PlayM4_SetDecCallBackMend設置解碼回調函數也是對的(這地方也着實折騰了一陣子,沒想到還要用播放MP4的形式來處理),最后發現問題出在解碼后的數據,數據也都是拿到了,默認是yv12的數據,如果需要轉成image的話就需要做個轉換,這個轉換網上找了一堆的函數來測試,都失敗了,后面找到一個yv12轉rgb888格式的,終於可以了,我勒個去。
海康sdk回調流程:
- 調用NET_DVR_RealPlay_V40設置回調處理函數。
- 在回調處理函數RealDataCallBack中依次處理打開、播放、解碼。
- 調用PlayM4_GetPort獲取播放庫未使用的通道號。
- 調用PlayM4_OpenStream打開視頻流。
- 調用PlayM4_SetDecCallBackMend設置解碼回調函數,只解碼不顯示。
- 調用PlayM4_Play播放視頻流。
- 調用PlayM4_InputData循環解碼數據。
- 在解碼回調函數DecCallBack中分別處理音視頻數據。
- 調用自己封裝的yv12ToRGB888函數將數據轉成QImage。
關於回調函數請注意以下幾點:
- 回調函數必須有關鍵詞 CALLBACK。
- 回調函數本身必須是全局函數或者靜態函數,不可定義為某個特定的類的成員函數。
- 回調函數並不由開發者直接調用執行,只是使用系統接口API函數作為起點。
- 回調函數通常作為參數傳遞給系統API,由該API來調用。
- 回調函數可能被系統API調用一次,也可能被循環調用多次。
二、功能特點
- 支持播放視頻流和本地MP4文件。
- 支持句柄和回調兩種模式。
- 多線程顯示圖像,不卡主界面。
- 自動重連網絡攝像頭。
- 可設置邊框大小即偏移量和邊框顏色。
- 可設置是否繪制OSD標簽即標簽文本或圖片和標簽位置。
- 可設置兩種OSD位置和風格。
- 可設置是否保存到文件以及文件名。
- 可直接拖曳文件到haikangwidget控件播放。
- 支持h264/h265視頻流。
- 可暫停播放和繼續播放。
- 支持存儲單個視頻文件和定時存儲視頻文件。
- 自定義頂部懸浮條,發送單擊信號通知,可設置是否啟用。
- 可設置畫面拉伸填充或者等比例填充。
- 可設置解碼是速度優先、質量優先、均衡處理。
- 可對視頻進行截圖(原始圖片)和截屏(視頻窗體)。
- 錄像文件存儲為MP4文件。
- 支持焦距控制、雲台控制。
- 可定制功能。
三、效果圖

四、相關站點
- 國內站點: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
五、核心代碼
//yv12轉RGB888
static bool yv12ToRGB888(const unsigned char *yv12, unsigned char *rgb888, int width, int height)
{
if ((width < 1) || (height < 1) || (yv12 == NULL) || (rgb888 == NULL)) {
return false;
}
int len = width * height;
unsigned char const *yData = yv12;
unsigned char const *vData = &yData[len];
unsigned char const *uData = &vData[len >> 2];
int rgb[3];
int yIdx, uIdx, vIdx, idx;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
yIdx = i * width + j;
vIdx = (i / 2) * (width / 2) + (j / 2);
uIdx = vIdx;
rgb[0] = static_cast<int>(yData[yIdx] + 1.370705 * (vData[uIdx] - 128));
rgb[1] = static_cast<int>(yData[yIdx] - 0.698001 * (uData[uIdx] - 128) - 0.703125 * (vData[vIdx] - 128));
rgb[2] = static_cast<int>(yData[yIdx] + 1.732446 * (uData[vIdx] - 128));
for (int k = 0; k < 3; ++k) {
idx = (i * width + j) * 3 + k;
if ((rgb[k] >= 0) && (rgb[k] <= 255)) {
rgb888[idx] = static_cast<unsigned char>(rgb[k]);
} else {
rgb888[idx] = (rgb[k] < 0) ? (0) : (255);
}
}
}
}
return true;
}
//解碼回調 視頻為YUV420P數據(YV12),音頻為PCM數據
static void CALLBACK DecCallBack(qport nPort, char *pBuf, qport nSize, FRAME_INFO *pFrameInfo, quser luser, quser nReserved2)
{
HaiKangThread *thread = (HaiKangThread *)luser;
long frameType = pFrameInfo->nType;
//視頻數據是 T_YV12 音頻數據是 T_AUDIO16
if (frameType == T_YV12) {
//qDebug() << TIMEMS << width << height << thread;
int width = pFrameInfo->nWidth;
int height = pFrameInfo->nHeight;
QImage image(width, height, QImage::Format_RGB888);
if (yv12ToRGB888((unsigned char *)pBuf, image.bits(), width, height)) {
thread->setImage(image);
}
} else if (frameType == T_AUDIO16) {
//qDebug() << TIMEMS << "T_AUDIO16" << thread;
}
}
static void CALLBACK RealDataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void *dwUser)
{
//每個類都對應自己的 port
HaiKangThread *thread = (HaiKangThread *)dwUser;
qport nPort = thread->port;
DWORD dRet;
switch (dwDataType) {
case NET_DVR_SYSHEAD:
//獲取播放庫未使用的通道號
if (!PlayM4_GetPort(&nPort)) {
break;
}
if (dwBufSize > 0) {
thread->port = nPort;
if (!PlayM4_OpenStream(nPort, pBuffer, dwBufSize, 1024 * 1024)) {
dRet = PlayM4_GetLastError(nPort);
break;
}
//設置解碼回調函數 只解碼不顯示
if (!PlayM4_SetDecCallBackMend(nPort, DecCallBack, (quser)dwUser)) {
dRet = PlayM4_GetLastError(nPort);
break;
}
//打開視頻解碼
if (!PlayM4_Play(nPort, NULL)) {
dRet = PlayM4_GetLastError(nPort);
break;
}
//打開音頻解碼, 需要碼流是復合流
if (!PlayM4_PlaySound(nPort)) {
dRet = PlayM4_GetLastError(nPort);
break;
}
}
break;
case NET_DVR_STREAMDATA:
//解碼數據
if (dwBufSize > 0 && nPort != -1) {
BOOL inData = PlayM4_InputData(nPort, pBuffer, dwBufSize);
while (!inData) {
sleep(10);
inData = PlayM4_InputData(nPort, pBuffer, dwBufSize);
}
}
break;
}
}
