Qt音視頻開發18-海康sdk回調


一、前言

海康sdk顯示實時視頻流除了支持句柄方式以外,也支持回調的方式拿到每一張圖片自己繪制處理,當然回調除了拿到視頻數據,其實音頻數據也一塊拿到了,自行調用音頻設備播放就行,關於海康sdk回調這塊,還着實折騰了一陣子才搞定,可能最開始沒有參照提供的demo以及沒有徹底的搜索吧,只是單單看sdk的文檔折騰來折騰去的,搞了一星期居然沒搞定,后面找到了正確的辦法才發現,原來就差一點點一丟丟呢,這又讓我聯想到很多事情,包括生活中的事情,不都是如此么?當你鉚足了勁,試驗搞了各種辦法,快要精疲力盡放棄的時候,其實此時離成功就差一步了,真的就差那么一丟丟,處理生活中的很多事情也是如此,所以很多時候如果方向對了,堅持過努力過,還不行的話,再努力一把估計就ok了。

折騰了很久總結失敗在哪里,調用NET_DVR_RealPlay_V40設置回調函數也是對的,回調函數里面也進去了,調用PlayM4_SetDecCallBackMend設置解碼回調函數也是對的(這地方也着實折騰了一陣子,沒想到還要用播放MP4的形式來處理),最后發現問題出在解碼后的數據,數據也都是拿到了,默認是yv12的數據,如果需要轉成image的話就需要做個轉換,這個轉換網上找了一堆的函數來測試,都失敗了,后面找到一個yv12轉rgb888格式的,終於可以了,我勒個去。

海康sdk回調流程:

  1. 調用NET_DVR_RealPlay_V40設置回調處理函數。
  2. 在回調處理函數RealDataCallBack中依次處理打開、播放、解碼。
  3. 調用PlayM4_GetPort獲取播放庫未使用的通道號。
  4. 調用PlayM4_OpenStream打開視頻流。
  5. 調用PlayM4_SetDecCallBackMend設置解碼回調函數,只解碼不顯示。
  6. 調用PlayM4_Play播放視頻流。
  7. 調用PlayM4_InputData循環解碼數據。
  8. 在解碼回調函數DecCallBack中分別處理音視頻數據。
  9. 調用自己封裝的yv12ToRGB888函數將數據轉成QImage。

關於回調函數請注意以下幾點:

  1. 回調函數必須有關鍵詞 CALLBACK。
  2. 回調函數本身必須是全局函數或者靜態函數,不可定義為某個特定的類的成員函數。
  3. 回調函數並不由開發者直接調用執行,只是使用系統接口API函數作為起點。
  4. 回調函數通常作為參數傳遞給系統API,由該API來調用。
  5. 回調函數可能被系統API調用一次,也可能被循環調用多次。

二、功能特點

  1. 支持播放視頻流和本地MP4文件。
  2. 支持句柄和回調兩種模式。
  3. 多線程顯示圖像,不卡主界面。
  4. 自動重連網絡攝像頭。
  5. 可設置邊框大小即偏移量和邊框顏色。
  6. 可設置是否繪制OSD標簽即標簽文本或圖片和標簽位置。
  7. 可設置兩種OSD位置和風格。
  8. 可設置是否保存到文件以及文件名。
  9. 可直接拖曳文件到haikangwidget控件播放。
  10. 支持h264/h265視頻流。
  11. 可暫停播放和繼續播放。
  12. 支持存儲單個視頻文件和定時存儲視頻文件。
  13. 自定義頂部懸浮條,發送單擊信號通知,可設置是否啟用。
  14. 可設置畫面拉伸填充或者等比例填充。
  15. 可設置解碼是速度優先、質量優先、均衡處理。
  16. 可對視頻進行截圖(原始圖片)和截屏(視頻窗體)。
  17. 錄像文件存儲為MP4文件。
  18. 支持焦距控制、雲台控制。
  19. 可定制功能。

三、效果圖

四、相關站點

  1. 國內站點:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 國際站點:https://github.com/feiyangqingyun/QWidgetDemo
  3. 個人主頁:https://blog.csdn.net/feiyangqingyun
  4. 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/
  5. 體驗地址: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;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM