当前位置:   article > 正文

C++ 使用海康威视SDK将视频推流到rtmp服务器_相机sdk回调函数yuv推流rtmp

相机sdk回调函数yuv推流rtmp

研究FFmpeg有两三年了,一直没写过这方面的文章,今天记一下。

由于工作关系,需要将化工企业内部的视频发布到一个部署在公网的视频服务器,然后由相关人员浏览。由于是化工企业,企业严禁外部的机器直接访问视频网络,最多提供一个跳板机。因此,两年多前,针对这种情况,基于FFmpeg研发了一个推流系统。

随着接入视频数量的增加,发现不能单纯的使用RTSP协议获取硬盘录像机视频数据了,海康威视的硬盘录像机,最多允许5个用户同时访问,如果使用RTSP的话,每路RTSP都相当于一个访问用户,因此,需要使用海康卫视的SDK将视频流转为FFmpeg帧。

其实海康威视在开发文档中给出了相应示例,这里我贴一下我写的这部分代码:

 

初始化程序,连接硬盘录像机:

  1. void HKVideo::initInput() {
  2.     inputSuccess = false;
  3.     NET_DVR_USER_LOGIN_INFO struLoginInfo = { 0 };
  4.     NET_DVR_DEVICEINFO_V40 struDeviceInfoV40 = { 0 };
  5.     while (true)
  6.     {
  7.         //---------------------------------------
  8.     // 初始化
  9.         NET_DVR_Init();
  10.         //设置连接时间与重连时间
  11.         NET_DVR_SetConnectTime(2000, 1);
  12.         NET_DVR_SetReconnect(2000, true);
  13.         //---------------------------------------
  14.         //设置异常消息回调函数
  15.         NET_DVR_SetExceptionCallBack_V30(0, NULL, g_ExceptionCallBack, NULL);
  16.         //---------------------------------------
  17.         // 注册设备
  18.         //登录参数,包括设备地址、登录用户、密码等
  19.         
  20.         struLoginInfo.bUseAsynLogin = 0; //同步登录方式
  21.         strcpy(struLoginInfo.sDeviceAddress, ip.c_str()); //设备IP地址
  22.         struLoginInfo.wPort = port; //设备服务端口
  23.         strcpy(struLoginInfo.sUserName, user.c_str()); //设备登录用户名
  24.         strcpy(struLoginInfo.sPassword, psd.c_str()); //设备登录密码
  25.         //设备信息, 输出参数
  26.         
  27.         lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfoV40);
  28.         printf("Login  code: %d\n", NET_DVR_GetLastError());
  29.         if (lUserID < 0)
  30.         {
  31.             
  32.             NET_DVR_Cleanup();
  33.             Sleep(3000);
  34.             continue;
  35.         }
  36.         else {
  37.             printf("lUserID:%d\n", lUserID);
  38.             break;
  39.         }
  40.         
  41.     }
  42.     int size = rtsp_json["channel"].size();
  43.     for (size_t i = 0; i < size; i++)
  44.     {
  45.         int lChannel = rtsp_json["channel"][i].asInt();
  46.         HKVideoPush* push = new HKVideoPush;
  47.         stringstream stream;
  48.         stream << rtmp;
  49.         stream << "_";
  50.         stream << lChannel;
  51.         string rtmp0;
  52.         stream >> rtmp0;
  53.         push->init(rtmp0);
  54.         printf("Channel:%s\n",push->rtmp);
  55.         //---------------------------------------
  56.         //启动预览并设置回调数据流
  57.         //HWND  h = (HWND)cvGetWindowHandle("Mywindow");
  58.         push->struPlayInfo.hPlayWnd = NULL;         //需要SDK解码时句柄设为有效值,仅取流不解码时可设为空
  59.         push->struPlayInfo.lChannel = lChannel;       //预览通道号
  60.         push->struPlayInfo.dwStreamType = 0;       //0-主码流,1-子码流,2-码流3,3-码流4,以此类推
  61.         push->struPlayInfo.dwLinkMode = 0;       //0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP
  62.         push->struPlayInfo.bBlocked = 1;       //0- 非阻塞取流,1- 阻塞取流
  63.         push->lRealPlayHandle = NET_DVR_RealPlay_V40(lUserID, &push->struPlayInfo, g_RealDataCallBack_V30, push);
  64.         if (push->lRealPlayHandle < 0)
  65.         {
  66.             printf("NET_DVR_RealPlay_V40 error, %d\n", NET_DVR_GetLastError());
  67.             NET_DVR_Logout(lUserID);
  68.             NET_DVR_Cleanup();
  69.         }
  70.         HKVideos[lChannel] = push;
  71.         HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartHKPushThread, (PVOID)push, 1, 0); //创建子线程
  72.         ResumeThread(h);  //启动子线程
  73.     }
  74.     inputSuccess = true;
  75.     
  76. };

 

 回调函数:

  1. void HKVideoPush::AVFrame2AVPacket(unsigned char* pYUV, int srcWidth, int srcHeight) {
  2. //关键函数,将海康威视视频帧转为FFmpeg帧
  3.     AVFrame* avframe_tmp = av_frame_alloc();//申请一个新的帧
  4.     AVPacket* pkt = av_packet_alloc();//申请一个新的视频包
  5.     avframe_tmp->format = AV_PIX_FMT_YUV420P;
  6.     avframe_tmp->width = srcWidth;
  7.     avframe_tmp->height = srcHeight;
  8.     
  9.     avpicture_fill((AVPicture*)avframe_tmp, pYUV, AV_PIX_FMT_YUV420P, srcWidth, srcHeight);//将海康威视帧数据填充到FFmpeg视频帧中
  10.     //下面这一步很重要,如果没有下面三行代码,rtmp看到的视频颜色会失常
  11.     uint8_t* ptmp = avframe_tmp->data[1];
  12.     avframe_tmp->data[1] = avframe_tmp->data[2];
  13.     avframe_tmp->data[2] = ptmp;
  14.     this->vpts += 1;
  15.     this->lock();//锁定,
  16.     avframe_tmp->pts = this->vpts;
  17.     int ret = avcodec_send_frame(this->vc, avframe_tmp);
  18.     
  19.     if (ret < 0) {
  20.         printf("Error avcodec_send_frame:%d\n", ret);
  21.         av_frame_free(&avframe_tmp);
  22.         this->unlock();
  23.         return;
  24.     }
  25.     av_frame_free(&avframe_tmp);
  26.     ret = avcodec_receive_packet(this->vc, pkt);
  27.     if (ret < 0)
  28.     {
  29.         printf("Error avcodec_receive_packet:%d\n", ret);
  30.         this->unlock();
  31.         return;
  32.     }
  33.     //this->lock();
  34.     this->Pkts.push_back(pkt);//将最终的到的视频包,推入一个vector中,其他线程会从vector中取出包,推流给rtmp
  35.     this->unlock();//解锁
  36. };
  37. void CALLBACK DecCBFun(long nPort, char* pBuf, long nSize, FRAME_INFO* pFrameInfo, long nReserved1, long nReserved2)
  38. {
  39.     long lFrameType = pFrameInfo->nType;
  40.     if (lFrameType == T_YV12)
  41.     {
  42.         //将海康威视包转为FFmpeg帧
  43.         AVFrame2AVPacket((unsigned char*)pBuf, pFrameInfo->nWidth, pFrameInfo->nHeight);
  44.     }
  45. }
  46. void CALLBACK g_RealDataCallBack_V30(LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, void* dwUser)
  47. {
  48.     HKVideoPush* push = (HKVideoPush*)dwUser;
  49.     int dRet = 0;
  50.     switch (dwDataType)
  51.     {
  52.     case NET_DVR_SYSHEAD: //系统头
  53.         //if (push->lPort >= 0)
  54.         //{
  55.         //    break;  //该通道取流之前已经获取到句柄,后续接口不需要再调用
  56.         //}
  57.         if (push->lPort >= 0)
  58.         {
  59.             break;  //该通道取流之前已经获取到句柄,后续接口不需要再调用
  60.         }
  61.         if (!PlayM4_GetPort(&push->lPort))  //获取播放库未使用的通道号
  62.         {
  63.             break;
  64.         }
  65.         //m_iPort = lPort; //第一次回调的是系统头,将获取的播放库port号赋值给全局port,下次回调数据时即使用此port号播放
  66.         if (dwBufSize > 0)
  67.         {
  68.             if (!PlayM4_OpenStream(push->lPort, pBuffer, dwBufSize, 1600 * 1600))
  69.             {
  70.                 dRet = PlayM4_GetLastError(push->lPort);
  71.                 break;
  72.             }
  73.             //设置解码回调函数 仅仅解码不显示
  74.             if (!PlayM4_SetDecCallBack(push->lPort, DecCBFun))
  75.             {
  76.                 dRet = PlayM4_GetLastError(push->lPort);
  77.                 break;
  78.             }
  79.             //设置解码回调函数 解码且显示
  80.             //if (!PlayM4_SetDecCallBackEx(nPort,DecCBFun,NULL,NULL))
  81.             //{
  82.             //  dRet=PlayM4_GetLastError(nPort);
  83.             //  break;
  84.             //}
  85.             //打开视频解码
  86.             if (!PlayM4_Play(push->lPort, push->hWnd))
  87.             {
  88.                 dRet = PlayM4_GetLastError(push->lPort);
  89.                 break;
  90.             }
  91.         }
  92.         break;
  93.     case NET_DVR_STREAMDATA:   //码流数据
  94.         if (dwBufSize > 0 && push->lPort != -1)
  95.         {
  96.             BOOL inData = PlayM4_InputData(push->lPort, pBuffer, dwBufSize);
  97.             while (!inData)
  98.             {
  99.                 inData = PlayM4_InputData(push->lPort, pBuffer, dwBufSize);
  100.                 OutputDebugString("PlayM4_InputData failed \n");
  101.             }
  102.         }
  103.         break;
  104.     default: //其他数据
  105.         if (dwBufSize > 0)
  106.         {
  107.         }
  108.         break;
  109.     }
  110. }
  111. void CALLBACK g_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void* pUser)
  112. {
  113.     printf("异常回调%X\n", dwType);
  114.     switch (dwType)
  115.     {
  116.     case EXCEPTION_RECONNECT:    //预览时重连
  117.         break;
  118.     default:
  119.         break;
  120.     }
  121. }

 

 总结:

缺点:这种方式,会造成大量计算,每一帧都会经过计算,我在实际项目使用的时候,使用了一个CPU为J1900的工控机,读取一个硬盘录像机的5路摄像头,CPU占用率达到90% 以上。主要耗费CPU的就是AVFrame2AVPacket函数,它需要大量计算

优点:节省带宽。。。因为海康卫视的SDK解出来的视频包很小,子码流时每路最多200Kb/s(要知道我用RTSP时,子码流每路800Kb)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/233744
推荐阅读
相关标签
  

闽ICP备14008679号