基于海康SDK的C++实时视频流逐帧抓取存图小工具

C++实时视频流抓取工具开发

效果

项目

使用

Usage: PlayDemo.exe <IP> <Port> <Username> <Password>

代码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string>
#include <iostream>
#include <Windows.h>
#include <thread>
#include <time.h>
#include <conio.h>
#include <filesystem>
#include <iomanip>
#include <chrono>
#include <sstream>
#include <direct.h>

using namespace std;
#include "PlayM4.h"
#include "HCNetSDK.h"

int times = 0;
LONG lRealPlayHandle;
LONG m_lPort[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };//全局的播放库port号

std::string imgFolder = "img"; // 图片保存文件夹

//播放库硬解码回调
void CALLBACK DisplayCBFun(DISPLAY_INFO_YUV* pstDisplayInfo) {

 //每100次保存一次yuv数据
if (times % 100 == 0) {
  FILE* fp = NULL;
  string ansiString = "example" + to_string(pstDisplayInfo->nPort) + "___" + to_string(times / 100) + ".yuv";
  fp = fopen(ansiString.c_str(), "wb");
  // 将字符数组写入文件
  fwrite(pstDisplayInfo->pBuf, sizeof(char), pstDisplayInfo->nBufLen, fp); // 不包括末尾的空字符
  // 关闭文件
  fclose(fp);
 }
times++;
printf("Buf长度:%d\n画面宽:%d\n画面高:%d\n数据类型:%d\nn播放库句柄:%d\n", pstDisplayInfo->nBufLen, pstDisplayInfo->nWidth, pstDisplayInfo->nHeight, pstDisplayInfo->nType, pstDisplayInfo->nPort);
}

//播放库解码回调
void CALLBACK DecCBFunIm(long nPort, char* pBuf, long nSize, FRAME_INFO* pFrameInfo, void* nUser, void* nReserved2) {

 // 获取当前时间(精确到毫秒)
 auto now = std::chrono::system_clock::now();
 auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
 auto epoch = now_ms.time_since_epoch();
 auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
 long long milliseconds = value.count();

 // 转换为时间结构
 std::time_t time = std::chrono::system_clock::to_time_t(now);
 std::tm tm = *std::localtime(&time);

 // 格式化时间字符串
 std::ostringstream oss;
 oss << std::put_time(&tm, "%Y%m%d_%H%M%S_") << std::setfill('0') << std::setw(3) << (milliseconds % 1000);
 std::string baseName = imgFolder + "/" + oss.str();

 // 保存JPEG图片并计算耗时
 auto jpgStart = std::chrono::high_resolution_clock::now();
 std::string jpgPath = baseName + ".jpg";
 BOOL jpgResult = PlayM4_ConvertToJpegFile(pBuf, nSize, pFrameInfo->nWidth, pFrameInfo->nHeight, pFrameInfo->nType, const_cast<char*>(jpgPath.c_str()));
 auto jpgEnd = std::chrono::high_resolution_clock::now();
 auto jpgDuration = std::chrono::duration_cast<std::chrono::milliseconds>(jpgEnd - jpgStart);

 // 保存BMP图片并计算耗时
 //auto bmpStart = std::chrono::high_resolution_clock::now();
 //std::string bmpPath = baseName + ".bmp";
 //BOOL bmpResult = PlayM4_ConvertToBmpFile(pBuf, nSize, pFrameInfo->nWidth, pFrameInfo->nHeight, pFrameInfo->nType, const_cast<char*>(bmpPath.c_str()));
 //auto bmpEnd = std::chrono::high_resolution_clock::now();
 //auto bmpDuration = std::chrono::duration_cast<std::chrono::milliseconds>(bmpEnd - bmpStart);

 // 输出保存结果和时间
if (jpgResult) {
  std::cout << "JPEG saved: " << jpgPath << " (Size: " << nSize << " bytes, " << pFrameInfo->nWidth << "x" << pFrameInfo->nHeight << ")" << " 耗时: " << jpgDuration.count() << " ms" << std::endl;
 }
else {
  std::cout << "Failed to save JPEG. Error code: " << GetLastError() << std::endl;
 }

 //if (bmpResult) {
 // std::cout << "BMP saved: " << bmpPath << " in " << bmpDuration.count() << " ms" << std::endl;
 //}
 //else {
 // std::cout << "Failed to save BMP. Error code: " << GetLastError() << std::endl;
 //}

 //// 输出总耗时
 //auto totalDuration = std::chrono::duration_cast<std::chrono::milliseconds>(bmpEnd - jpgStart);
 //std::cout << "Total time for both images: " << totalDuration.count() << " ms" << std::endl;

}

//sdk码流回调
void CALLBACK g_RealDataCallBack_V30(LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, void* dwUser)
{
 DWORD dRet = 0;
 BOOL inData = FALSE;
 LONG lPort = -1;
 switch (dwDataType)
 {
case NET_DVR_SYSHEAD: //系统头

if (!PlayM4_GetPort(&lPort))  //获取播放库未使用的通道号
  {
   printf("申请播放库资源失败");
   break;
  }
printf("播放库句柄:%d\n", lPort);
  m_lPort[lRealPlayHandle] = lPort; //第一次回调的是系统头,将获取的播放库port号赋值给全局port,下次回调数据时即使用此port号播放

if (dwBufSize > 0) {

   //设置实时流播放模式
   if (!PlayM4_SetStreamOpenMode(m_lPort[lRealPlayHandle], STREAME_REALTIME))
   {
    printf("PlayM4_SetStreamOpenMode Error\n");
    printf("GetLastError错误码 :%d\n", PlayM4_GetLastError(m_lPort[lRealPlayHandle]));
    break;
   }
   else
   {
    printf("PlayM4_SetStreamOpenMode Sus!\n");
   }
   //打开流接口
   if (!PlayM4_OpenStream(m_lPort[lRealPlayHandle], pBuffer, dwBufSize, 5 * 1024 * 1024))
   {
    printf("PlayM4_OpenStream Error\n");
    printf("GetLastError错误码 :%d\n", PlayM4_GetLastError(m_lPort[lRealPlayHandle]));
    break;
   }
   else
   {
    printf("PlayM4_OpenStream Sus!\n");
   }


   //设置解码模式,第二个参数0为软解码,1为硬解码(硬解码需要硬件支持)
   //if (!PlayM4_SetDecodeEngine(m_lPort[lRealPlayHandle], 1))
   //{
   // printf("PlayM4_SetDecodeEngine Error\n");
   // printf("GetLastError错误码 :%d\n", PlayM4_GetLastError(m_lPort[lRealPlayHandle]));
   // break;
   //}
   //else
   //{
   // printf("PlayM4_SetDecodeEngine Sus!\n");
   //}
   ////设置硬解码回调,若设置为硬解码模式,需要使用该接口设置硬解码回调
   //if (!PlayM4_SetDisplayCallBackYUV(m_lPort[lRealPlayHandle], DisplayCBFun, FALSE, NULL))
   //{
   // printf("PlayM4_SetDisplayCallBackYUV Error\n");
   // printf("GetLastError错误码 :%d\n", PlayM4_GetLastError(m_lPort[lRealPlayHandle]));
   // break;
   //}
   //else
   //{
   // printf("PlayM4_SetDecodeEngine Sus!\n");
   //}

   //设置解码回调函数 解码显示 回调yuv数据,软解模式下,使用该回调   
   if (!PlayM4_SetDecCallBackExMend(m_lPort[lRealPlayHandle], DecCBFunIm, NULL, 0, NULL))
   {
    printf("PlayM4_SetDecCallBackExMend Error\n");
    printf("GetLastError错误码 :%d\n", PlayM4_GetLastError(m_lPort[lRealPlayHandle]));
    break;
   }
   else
   {
    printf("PlayM4_SetDecodeEngine Sus!\n");
   }

   if (!PlayM4_Play(m_lPort[lRealPlayHandle], NULL)) //播放开始hWnd[lRealHandle]
   {
    printf("PlayM4_Play Error\n");
    printf("GetLastError错误码 :%d\n", PlayM4_GetLastError(m_lPort[lRealPlayHandle]));
    break;
   }
   else
   {
    printf("PlayM4_SetDecodeEngine Sus!\n");
   }
  }
break;
case NET_DVR_STREAMDATA:   //码流数据
if (dwBufSize > 0 && m_lPort[lRealPlayHandle] != -1)
  {
   //送数据入播放库
   while (!PlayM4_InputData(m_lPort[lRealPlayHandle], pBuffer, dwBufSize))
   {
    int dwError = PlayM4_GetLastError(m_lPort[lRealPlayHandle]);
    printf("播放库句柄ID:%d,错误码:%d\n", m_lPort[lRealPlayHandle], dwError);
    if (dwError == 11)  //缓冲区满,需要重复送入数据
    {
     continue;
    }
   }
  }
break;
 default: //其他数据
if (dwBufSize > 0 && m_lPort[lRealPlayHandle] != -1)
  {
   if (!PlayM4_InputData(m_lPort[lRealPlayHandle], pBuffer, dwBufSize))
   {
    break;
   }
  }
break;
 }

}
//播放库抓图
void getPic() {
 int i = 0;
 BOOL   bFlag = FALSE;
 DWORD  dwErr = 0;
 LONG dwWidth = 0;
 LONG dwHeight = 0;
 DWORD dwSize = 0;
 DWORD dwCapSize = 0;

 //抓10张图
while (i++ < 10) {

  //获取当前视频文件的分辨率
  int bFlag = PlayM4_GetPictureSize(m_lPort[lRealPlayHandle], &dwWidth, &dwHeight);
if (bFlag == FALSE)
  {
   dwErr = PlayM4_GetLastError(m_lPort[lRealPlayHandle]);
   printf("PlayM4_GetPictureSize, error code: %d\n", dwErr);
   break;
  }
  dwSize = dwWidth * dwHeight * 5;
  //申请抓图内存
  BYTE* m_pCapBuf = NULL;
if (m_pCapBuf == NULL)
  {
   m_pCapBuf = new BYTE[dwSize];
   if (m_pCapBuf == NULL)
   {
    return;
   }
  }

  //抓图BMP图片
  bFlag = PlayM4_GetJPEG(m_lPort[lRealPlayHandle], m_pCapBuf, dwSize, &dwCapSize);
if (bFlag == FALSE)
  {
   dwErr = PlayM4_GetLastError(m_lPort[lRealPlayHandle]);
   printf("PlayM4_GetLastError, error code: %d\n", dwErr);
   break;
  }
if (bFlag) {
   FILE* fp = NULL;
   time_t timep;
   time(&timep); //获取从1970至今过了多少秒,存入time_t类型的timep
   std::string temp_str = std::to_string(timep) + ".jpg";
   fp = fopen(temp_str.c_str(), "wb");
   // 将字符数组写入文件,文件即为图片文件
   fwrite(m_pCapBuf, sizeof(char), dwCapSize, fp); // 不包括末尾的空字符
   // 关闭文件
   fclose(fp);
  }

if (m_pCapBuf != NULL)
  {
   delete[] m_pCapBuf;
   m_pCapBuf = NULL;
  }
printf("完成第%d张抓图\n", i);
  //等待1秒后进下下一次抓图
  Sleep(1000);
 }
}

// 检查文件夹是否存在 (C++14兼容方法)
bool folderExists(const std::string& folderPath) {
 struct stat info;
returnstat(folderPath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
}

// 创建文件夹 (C++14兼容方法)
bool createFolder(const std::string& folderPath) {
return _mkdir(folderPath.c_str()) == 0;
}

// 检查并创建图片保存文件夹
bool ensureImageFolderExists() {
 // 检查文件夹是否存在
if (folderExists(imgFolder)) {
  std::cout << "使用图片保存文件夹: " << imgFolder << std::endl;
returntrue;
 }

 // 如果不存在,尝试创建文件夹
if (createFolder(imgFolder)) {
  std::cout << "创建图片保存文件夹: " << imgFolder << std::endl;
returntrue;
 }

 std::cerr << "错误: 无法创建图片保存文件夹" << std::endl;
returnfalse;
}

//*********************************
// 函数入口
//*********************************
int main(int argc, char* argv[])
{
 std::string folderPath = "Capture";
if (!CreateDirectoryA(folderPath.c_str(), NULL)) {
if (GetLastError() != ERROR_ALREADY_EXISTS) {
   std::cout << "Failed to create directory: " << folderPath << std::endl;
   return 1;
  }
 }

 // 检查参数数量
if (argc != 5) {
printf("Usage: %s <IP> <Port> <Username> <Password>\n", argv[0]);
return 1;
 }

 // 从参数获取连接信息
 const char* deviceAddress = argv[1];
 WORD wPort = static_cast<WORD>(atoi(argv[2]));
 const char* userName = argv[3];
 const char* password = argv[4];


 // 检查并创建图片保存文件夹
if (!ensureImageFolderExists()) {
  std::cerr << "错误: 无法创建或访问图片保存文件夹,程序将退出" << std::endl;
return -1;
 }

 //---------------------------------------
 // 初始化
 NET_DVR_Init();
 char ansiStringss[] = "./SdkLog";
 NET_DVR_SetLogToFile(3, ansiStringss, TRUE);
 //设置连接时间与重连时间
 NET_DVR_SetConnectTime(2000, 1);
 NET_DVR_SetReconnect(10000, true);

 // 注册设备
 LONG lUserID;

 //登录参数,包括设备地址、登录用户、密码等
 NET_DVR_USER_LOGIN_INFO struLoginInfo = { 0 };
 struLoginInfo.bUseAsynLogin = 0; //同步登录方式
 strncpy(struLoginInfo.sDeviceAddress, deviceAddress, NET_DVR_DEV_ADDRESS_MAX_LEN - 1);//设备IP地址
 struLoginInfo.wPort = wPort;//设备服务端口
 strncpy(struLoginInfo.sUserName, userName, NAME_LEN - 1); //设备登录用户名
 strncpy(struLoginInfo.sPassword, password, NAME_LEN - 1);//设备登录密码

 //设备信息, 输出参数
 NET_DVR_DEVICEINFO_V40 struDeviceInfoV40 = { 0 };
 //登录
 lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfoV40);
if (lUserID < 0)
 {
printf("Login failed, error code: %d\n", NET_DVR_GetLastError());
  NET_DVR_Cleanup();
return 1;
 }
 //预览相关参数设置
 NET_DVR_PREVIEWINFO struPlayInfo = { 0 };
 struPlayInfo.hPlayWnd = NULL;         //需要SDK解码时句柄设为有效值,仅取流不解码时可设为空
 struPlayInfo.lChannel = 1;       //预览通道号
 struPlayInfo.dwStreamType = 0;       //0-主码流,1-子码流,2-码流3,3-码流4,以此类推
 struPlayInfo.dwLinkMode = 0;       //0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP
 struPlayInfo.bBlocked = 1;       //0- 非阻塞取流,1- 阻塞取流

 //启动预览并设置回调数据流
 lRealPlayHandle = NET_DVR_RealPlay_V40(lUserID, &struPlayInfo, g_RealDataCallBack_V30, NULL);//

if (lRealPlayHandle < 0)
 {
printf("NET_DVR_RealPlay_V40 error %d\n", NET_DVR_GetLastError());
  NET_DVR_Logout(lUserID);
  NET_DVR_Cleanup();
return 1;
 }

 //等待播放库有数据,否则后面无法使用播放库抓图
 //Sleep(3000);
 // 创建并启动抓图线程
 //std::thread t1(getPic);
 // 播放库抓图分离线程
 //t1.detach();

 // 等待按键退出
while (true) {
if (_kbhit()) {  // 检测键盘输入
   int key = _getch();  // 获取按键
   if (key == 27) {  // ESC键
    printf("ESC pressed, exiting...\n");
    break;
   }
  }
  Sleep(100);  // 减少CPU占用
 }

 //关闭预览
 NET_DVR_StopRealPlay(lRealPlayHandle);
 //释放播放库资源
 PlayM4_Stop(m_lPort[lRealPlayHandle]);
 //关闭流
 PlayM4_CloseStream(m_lPort[lRealPlayHandle]);
 //释放播放端口
 PlayM4_FreePort(m_lPort[lRealPlayHandle]);
 //退出登录
 NET_DVR_Logout(lUserID);
 //释放sdk资源
 NET_DVR_Cleanup();
return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值