• 741阅读
  • 1回复

Qt音视频开发39-海康sdk回调拿到数据GPU绘制的实现 [复制链接]

上一主题 下一主题
离线liudianwu
 

只看楼主 倒序阅读 楼主  发表于: 2023-04-19


## 一、前言
采用海康的sdk做开发,最简单最容易的方式就是传入句柄(windows和linux都支持/很多人以为只有windows才支持)即可,这种方式不用自己处理绘制,全部交给了sdk去处理,所以cpu的占用是最低的;还有一种方式是回调函数拿到视频帧数据转成qimage绘制,这种方式优点很明显,能够拿到一张张图片数据,可以任意用来做人工智能算法处理等,缺点是如果采用qpainter绘制,那走的是cpu运算,在低配置的硬件上很耗费cpu,所以需要第三种方式来折中一下,既能拿到每一张图片,又能采用gpu绘制,这下思路出来了,意味着如果qopenglwidget可以直接绘制qimage就好,当然是支持的,qopenglwidget肯定支持rgb数据的绘制,于是只需要将qimage.data的数据传出来交给qopenglwidget绘制就好。亲测下来cpu占用比纯painter绘制下降不少,但是还是比句柄方式要高一些,毕竟回调拿到数据是yv12格式,需要转换成yuv格式,这个转换运算走的是cpu运算。

## 二、效果图
window.open('http://www.qtcn.org/bbs/attachment/Mon_2304/17_110085_a81abe307e268c3.jpg?89');" style="max-width:700px;max-height:700px;" onload="if(is_ie6&&this.offsetWidth>700)this.width=700;" >



## 三、体验地址
1. 国内站点:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 体验地址:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_video_demo/bin_linux_video。

## 四、相关代码
```cpp
void HaiKangHelper::yv12toYuv(const quint8 *yuv420, quint8 *y, quint8 *u, quint8 *v, int width, int height)
{
    //Y分量的长度
    int yLen = width * height;
    //U和V分量的长度
    int uvLen = width / 2 * height / 2;
    //海康的uv是反的需要反过来否则颜色发蓝
    memcpy((quint8 *)y, yuv420, yLen);
    memcpy((quint8 *)v, yuv420 + yLen, uvLen);
    memcpy((quint8 *)u, yuv420 + yLen + uvLen, uvLen);
}

void HaiKangHelper::yv12toYuv(const quint8 *yv12, quint8 *yuv, int width, int height, int widthStep)
{
    int col, row;
    int tmp, idx;
    uint y, u, v;

    for (row = 0; row < height; row++) {
        idx = row * widthStep;
        for (col = 0; col < width; col++) {
            tmp = (row / 2) * (width / 2) + (col / 2);
            y = (uint)yv12[row * width + col];
            u = (uint)yv12[width * height + width * height / 4 + tmp];
            v = (uint)yv12[width * height + tmp];
            yuv[idx + col * 3] = y;
            yuv[idx + col * 3 + 1] = u;
            yuv[idx + col * 3 + 2] = v;
        }
    }
}

void HaiKangHelper::ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser)
{
    //具体类型含义看头文件或者手册即可
    HaiKangThread *thread = (HaiKangThread *)pUser;
    thread->debug("异常回调", "");
    switch (dwType) {
        case EXCEPTION_RECONNECT:
            thread->debug("超时重连", "");
            break;
        default:
            break;
    }
}

void HaiKangHelper::RealDataCallBack(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void *pUser)
{
    //每个类都对应自己的port
    HaiKangCallbackData *callbackData = (HaiKangCallbackData *)pUser;
    HaiKangThread *thread = callbackData->thread;
    LONG 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;
                }

                //设置解码回调函数,只解码不显示
                bool result = PlayM4_SetDecCallBackMend(nPort, DecCallBack, pUser);
                if (!result) {
                    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);
                    qApp->processEvents();
                    inData = PlayM4_InputData(nPort, pBuffer, dwBufSize);
                }
            }
            break;
    }
}

void HaiKangHelper::FileEndCallback(LONG nPort, void *pUser)
{
    HaiKangThread *thread = (HaiKangThread *)pUser;
    thread->stop2();
}

//void HaiKangHelper::DecCallBack(LONG nPort, char *pBuf, LONG nSize, FRAME_INFO *pFrameInfo, LONG pUser, LONG nReserved2)
#ifdef Q_OS_WIN
void HaiKangHelper::DecCallBack(LONG nPort, char *pBuf, LONG nSize, FRAME_INFO *pFrameInfo, void *pUser, void *nReserved2)
#else
void HaiKangHelper::DecCallBack(LONG nPort, char *pBuf, LONG nSize, FRAME_INFO *pFrameInfo, void *pUser, LONG nReserved2)
#endif
{
    HaiKangCallbackData *callbackData = (HaiKangCallbackData *)pUser;
    HaiKangThread *thread = callbackData->thread;
    if (thread->getIsPause()) {
        return;
    }

    quint8 *data = (quint8 *)pBuf;
    long frameType = pFrameInfo->nType;
    //编码时产生的图像帧率(如果是音频数据则为采样率)
    long frameRate = pFrameInfo->nFrameRate;

    //视频数据是 T_YV12 音频数据是 T_AUDIO16
    if (frameType == T_YV12) {
        long width = pFrameInfo->nWidth;
        long height = pFrameInfo->nHeight;
        //识别尺寸发生变化
        thread->checkVideoSize(width, height);
        //thread->debug("回调视频", QString("宽高: %1x%2").arg(width).arg(height));

        //如果处于截图标志位则立即将图片保存
        if (thread->getIsSnap()) {
            QImage image(width, height, QImage::Format_RGB888);
            yv12ToRgb888(data, image.bits(), width, height);
            image.save(thread->getSnapName(), "jpg");
            QMetaObject::invokeMethod(thread, "snapFinsh");
        }

        //如果是绘制则转成图片否则转成yuv用opengl绘制
        if (thread->getVideoMode() == VideoMode_Opengl) {
            yv12toYuv(data, callbackData->dataY, callbackData->dataU, callbackData->dataV, width, height);
            thread->setYuv(width, height, callbackData->dataY, callbackData->dataU, callbackData->dataV);
        } else {
            QImage image(width, height, QImage::Format_RGB888);
            yv12ToRgb888(data, image.bits(), width, height);
            thread->setImage(image);
        }
    } else if (frameType == T_AUDIO16) {
        //thread->debug("回调音频", QString("采样: %1").arg(frameRate));
    }
}
```

4条评分好评度+1贡献值+1金钱+10威望+1
20091001753 好评度 +1 - 2023-04-19
20091001753 贡献值 +1 - 2023-04-19
20091001753 威望 +1 - 2023-04-19
20091001753 金钱 +10 - 2023-04-19
欢迎关注微信公众号:Qt实战/Qt入门和进阶(各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发) QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线new浪子

只看该作者 1楼 发表于: 2023-04-22
传入句柄绘制的底层方式是什么?
快速回复
限100 字节
 
上一个 下一个