## 一、前言
采用海康的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));
}
}
```