海康威视SDK视频录制及强制I帧操作

本文详细介绍使用海康SDK进行视频录制的方法,并针对视频长度不足等问题提出解决方案,包括使用NET_DVR_SaveRealData_V30及强制I帧技术。

一、NET_DVR_SaveRealData方法

使用下面方法可以实现视频录像功能。

    private HCNetSDK sdk = HCNetSDK.INSTANCE;
    sdk.NET_DVR_SaveRealData_V30(cameraInfo.getKey(), file.getPath() + "/" + fileName + ".mp4")

该方法有个问题,视频可以录制,但是不能被前端页面直接加载播放,因为视频编码格式问题,虽然可以用FFMPEG工具进行格式转化,但是引入第三方的库,无疑会使代码的维护性降低。

FFMPEG工具 地址 提取码 zc14
使用FFmpeg进行转换的方法如下:

ffmpeg -i  in.mp4  out.mp4

该方法网络上有大量的帖子,我直接放连接过来,可以自行前往学习
大牛博客地址

二、NET_DVR_SaveRealData_V30方法

这个方法是问海康的工程师得到的,跟海康的工程师交流还是挺美好的,工程师很耐心,直到自己的问题得到解答,代码测试通过,所以还是必须给点个赞。

附上该方法的SDK文档

NET_DVR_SaveRealData_V30 SDK

按照SDK文档的说明,第二个参数传2,就可以了

  //预览成功后 调用接口使视频资源保存到文件中
     if (!sdk.NET_DVR_SaveRealData_V30(cameraInfo.getKey(), 2,file.getPath() + "/" + fileName + ".mp4")) {
         log.error("保存视频文件到文件夹失败 错误码为:  " + sdk.NET_DVR_GetLastError());
         logoutHIK(cameraInfo);
         return;
     }

到这里会发现一个问题,官方下载的SDK HCNetSDK.java文件中没有NET_DVR_SaveRealData_V30这个方法,尴尬了。
这就是官方SDK的坑点,SDK中有,但是HCNetSDK.java文件中没有,好多同学可能都会懵一下,这里可以将SDK中的方法直接复制进HCNetSDK.java文件即可。SDK是按照C语言来描述的,JAVA要做响应的适配改变。

 boolean  NET_DVR_SaveRealData_V30(NativeLong lRealHandle,int STREAM_TYPE,String sFileName);

OK,到此视频已经可以存储,播放也正常,但是你又会发现一个新的问题,就是保存的视频,设定1分钟,但是只有51秒 ,52秒,57秒等,就是没有1分钟的,甚至59秒的都很少,如果要求不那么高还好,到这里就可以结束了,如果要求的高,那就得做出处理了。

三、强制I帧

这里面其实是有一个 I帧的定义

录像数据解码播放必须从I帧开始,而客户端录像的时候开始时间点不一定是I帧,比如帧率25fps、I帧间隔100帧的情况下,每4秒才一个I帧,录像播放可能存在1~4秒的延时,所以录像的时候可以强制设备生成一个I帧。
NET_DVR_MakeKeyFrame强制主码流生成一个I帧,NET_DVR_MakeKeyFrameSub强制子码流生成一个I帧,z这两个是老的接口。
后来因为设备不只有主码流和子码流,还有三码流、虚拟码流等,因此增加了NET_DVR_RemoteControl(命令:NET_DVR_MAKE_I_FRAME)接口,兼容前面的两个接口,输入参数NET_DVR_I_FRAME中byStreamType表示码流类型。建议使用该接口。

可以看到,如果I帧没有到,也是不录视频的,但是I帧一般会慢几秒中,所以视频长度不够自己配置的长度

需要用到NET_DVR_RemoteControl 这个方法,会发现 HCNetSDK.java文件中并没有NET_DVR_RemoteControl 这个方法,继续添加上。

附上NET_DVR_RemoteControl 的SDKNET_DVR_RemoteControl SDK
同样,在 HCNetSDK.java文件中增加这个方法(根据java做了语法转换,如果不好转,请参考原HCNetSDK.java文件中的数据类型)

 boolean NET_DVR_RemoteControl(NativeLong lUserID, int dwCommand, Pointer pointer, int dwInBufferSize);

官方SDK简直了,有些方法没有,就连结构体也没有,都需要自己新增,没办法,谁让咱要用人家的产品么
lpInBuffer参数对应结构体 NET_DVR_I_FRAME
看SDK手册 NET_DVR_I_FRAME 结构体的官方文档
NET_DVR_I_FRAME SDK

//强制I帧参数结构体。
public static class NET_DVR_I_FRAME extends Structure{
    public int dwSize;
    public byte[] sStreamID = new byte[32];  //流IDs    SDK 中对  STREAM_ID_LEN进行了定义,赋值32
    public int dwChannel;//通道号
    public byte byStreamType; //码流类型:0-主码流,1-子码流,2-码流3,3-虚拟码流,以此类推
    public byte[] byRes = new byte[63];  //保留,置为0    按照SDK要求进行对应赋值
}
//新建结构体对象
        HCNetSDK.NET_DVR_I_FRAME netDvrIFrame = new HCNetSDK.NET_DVR_I_FRAME();   //新建结构体对象
        netDvrIFrame.read();
        netDvrIFrame.dwChannel = 1;          //因为上文代码中设置了通道号,按照上文中的设置
        netDvrIFrame.byStreamType = 0;
        netDvrIFrame.dwSize = netDvrIFrame.size();
        netDvrIFrame.write();

        if(!sdk.NET_DVR_RemoteControl(cameraInfo.getUserId(),3402,netDvrIFrame.getPointer(),netDvrIFrame.dwSize)){
            log.error("强制I帧 错误码为:  " + sdk.NET_DVR_GetLastError());
        }

在每次录像之前,对I帧进行强制设置,可以及时录像,经过测试基本上按照配置的时间进行录制,比如1分钟就是1分钟,偶尔有59秒的情况,提到的问题已经大大的修复了。经次一轮,对SDK的使用也有了更深的了解。自己踩过的坑,也记录下来,也让别人能很好的使用官方的SDK。

最后附上程序,欢迎下载传阅,另外还有海康工程师给的有更全结构体的HCNetSDK.java文件
百度网盘 链接:https://pan.baidu.com/s/1JEKvEnb5wTDtmNqlNsKvLg
提取码:x4bj

需求为将现有代码进行修改,在前端进行显示的同时,增加录制开关,通过开始录制按钮使能,在后端进行录制视频 录制线程流程为: A[开始录制命令] --> B[图像采集线程: GetImageBuffer] B --> C{采集到新图像?} C -- 是 --> D[复制图像数据] C -- 否 --> B D --> E[存入录制缓存队列] E --> B A --> F((启动录制线程)) F --> G{缓存队列中有数据?} G -- 是 --> H[从队列取出一] G -- 否 --> I[等待短暂时间] I --> G H --> J[调用InputFrame编码] J --> K[已编码计数N+1] K --> L{N >= 计算的总数?} L -- 否 --> G L -- 是 --> M[停止录制线程] M --> N[生成最终视频文件] N --> O[结束] 代码为:#include <stdio.h> #include <Windows.h> #include <process.h> #include <conio.h> #include "MvCameraControl.h" bool g_bExit = false; // ch:等待按键输入 | en:Wait for key press void WaitForKeyPress(void) { while(!_kbhit()) { Sleep(10); } _getch(); } bool PrintDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo) { if (NULL == pstMVDevInfo) { printf("The Pointer of pstMVDevInfo is NULL!\n"); return false; } if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE) { int nIp1 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24); int nIp2 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16); int nIp3 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8); int nIp4 = (pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff); // ch:打印当前相机ip和用户自定义名字 | en:print current ip and user defined name printf("CurrentIp: %d.%d.%d.%d\n" , nIp1, nIp2, nIp3, nIp4); printf("UserDefinedName: %s\n\n" , pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName); } else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE) { printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName); printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber); printf("Device Number: %d\n\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.nDeviceNumber); } else if (pstMVDevInfo->nTLayerType == MV_GENTL_GIGE_DEVICE) { printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName); printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chSerialNumber); printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chModelName); } else if (pstMVDevInfo->nTLayerType == MV_GENTL_CAMERALINK_DEVICE) { printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chUserDefinedName); printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chSerialNumber); printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chModelName); } else if (pstMVDevInfo->nTLayerType == MV_GENTL_CXP_DEVICE) { printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chUserDefinedName); printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chSerialNumber); printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chModelName); } else if (pstMVDevInfo->nTLayerType == MV_GENTL_XOF_DEVICE) { printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chUserDefinedName); printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chSerialNumber); printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chModelName); } else { printf("Not support.\n"); } return true; } static unsigned int __stdcall WorkThread(void* pUser) { int nRet = MV_OK; MV_FRAME_OUT stImageInfo = {0}; MV_CC_INPUT_FRAME_INFO stInputFrameInfo = {0}; while(1) { nRet = MV_CC_GetImageBuffer(pUser, &stImageInfo, 1000); if (nRet == MV_OK) { printf("Get Image Buffer: Width[%d], Height[%d], FrameNum[%d]\n", stImageInfo.stFrameInfo.nExtendWidth, stImageInfo.stFrameInfo.nExtendHeight, stImageInfo.stFrameInfo.nFrameNum); stInputFrameInfo.pData = stImageInfo.pBufAddr; stInputFrameInfo.nDataLen = stImageInfo.stFrameInfo.nFrameLenEx; nRet = MV_CC_InputOneFrame(pUser, &stInputFrameInfo); if (MV_OK != nRet) { printf("Input one frame fail! nRet [0x%x]\n", nRet); } nRet = MV_CC_FreeImageBuffer(pUser, &stImageInfo); if(nRet != MV_OK) { printf("Free Image Buffer fail! nRet [0x%x]\n", nRet); } } else { printf("Get Image fail! nRet [0x%x]\n", nRet); } if(g_bExit) { break; } } return 0; } int main() { int nRet = MV_OK; void* handle = NULL; do { // ch:初始化SDK | en:Initialize SDK nRet = MV_CC_Initialize(); if (MV_OK != nRet) { printf("Initialize SDK fail! nRet [0x%x]\n", nRet); break; } // ch:枚举设备 | en:Enum device MV_CC_DEVICE_INFO_LIST stDeviceList; memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST)); nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE, &stDeviceList); if (MV_OK != nRet) { printf("Enum Devices fail! nRet [0x%x]\n", nRet); break; } if (stDeviceList.nDeviceNum > 0) { for (unsigned int i = 0; i < stDeviceList.nDeviceNum; i++) { printf("[device %d]:\n", i); MV_CC_DEVICE_INFO* pDeviceInfo = stDeviceList.pDeviceInfo[i]; if (NULL == pDeviceInfo) { break; } PrintDeviceInfo(pDeviceInfo); } } else { printf("Find No Devices!\n"); break; } printf("Please Input camera index(0-%d):", stDeviceList.nDeviceNum-1); unsigned int nIndex = 0; scanf_s("%d", &nIndex); if (nIndex >= stDeviceList.nDeviceNum) { printf("Input error!\n"); break; } // ch:选择设备并创建句柄 | en:Select device and create handle nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]); if (MV_OK != nRet) { printf("Create Handle fail! nRet [0x%x]\n", nRet); break; } // ch:打开设备 | en:Open device nRet = MV_CC_OpenDevice(handle); if (MV_OK != nRet) { printf("Open Device fail! nRet [0x%x]\n", nRet); break; } // ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera) if (stDeviceList.pDeviceInfo[nIndex]->nTLayerType == MV_GIGE_DEVICE) { int nPacketSize = MV_CC_GetOptimalPacketSize(handle); if (nPacketSize > 0) { nRet = MV_CC_SetIntValueEx(handle,"GevSCPSPacketSize",nPacketSize); if(nRet != MV_OK) { printf("Warning: Set Packet Size fail nRet [0x%x]!", nRet); } } else { printf("Warning: Get Packet Size fail nRet [0x%x]!", nPacketSize); } } // ch:设置触发模式为off | en:Set trigger mode as off nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0); if (MV_OK != nRet) { printf("Set Trigger Mode fail! nRet [0x%x]\n", nRet); break; } // ch:设置HB模式为off | en:Set HB mode as off nRet = MV_CC_SetEnumValue(handle,"ImageCompressionMode", 0); if (MV_OK != nRet) { printf("Get ImageCompressionMode fail! nRet [0x%x]\n", nRet); } MV_CC_RECORD_PARAM stRecordPar; MVCC_INTVALUE stParam = {0}; nRet = MV_CC_GetIntValue(handle, "Width", &stParam); if (MV_OK != nRet) { printf("Get Width fail! nRet [0x%x]\n", nRet); break; } stRecordPar.nWidth = stParam.nCurValue; nRet = MV_CC_GetIntValue(handle, "Height", &stParam); if (MV_OK != nRet) { printf("Get Height fail! nRet [0x%x]\n", nRet); break; } stRecordPar.nHeight = stParam.nCurValue; MVCC_ENUMVALUE stEnumValue = {0}; nRet = MV_CC_GetEnumValue(handle, "PixelFormat", &stEnumValue); if (MV_OK != nRet) { printf("Get Width fail! nRet [0x%x]\n", nRet); break; } stRecordPar.enPixelType = MvGvspPixelType(stEnumValue.nCurValue); MVCC_FLOATVALUE stFloatValue; nRet = MV_CC_GetFloatValue(handle, "ResultingFrameRate", &stFloatValue); if (MV_OK != nRet) { printf("Get Float value fail! nRet [0x%x]\n", nRet); break; } // ch:率(大于1/16)fps | en:Frame Rate (>1/16)fps stRecordPar.fFrameRate = stFloatValue.fCurValue; // ch:码率kbps(128kbps-16Mbps) | en:Bitrate kbps(128kbps-16Mbps) stRecordPar.nBitRate = 1000; // ch:录像格式(仅支持AVI) | en:Record Format(AVI is only supported) stRecordPar.enRecordFmtType = MV_FormatType_AVI; stRecordPar.strFilePath= "./Recording.avi"; nRet = MV_CC_StartRecord(handle,&stRecordPar); if (MV_OK != nRet) { printf("Start Record fail! nRet [0x%x]\n", nRet); break; } // ch:开始取流 | en:Start grab image nRet = MV_CC_StartGrabbing(handle); if (MV_OK != nRet) { printf("Start Grabbing fail! nRet [0x%x]\n", nRet); break; } unsigned int nThreadID = 0; void* hThreadHandle = (void*) _beginthreadex( NULL , 0 , WorkThread , handle, 0 , &nThreadID ); if (NULL == hThreadHandle) { printf("Start work thread fail! \n"); break; } printf("Press a key to stop grabbing.\n"); WaitForKeyPress(); g_bExit = true; Sleep(1000); // ch:停止取流 | en:Stop grab image nRet = MV_CC_StopGrabbing(handle); if (MV_OK != nRet) { printf("Stop Grabbing fail! nRet [0x%x]\n", nRet); break; } nRet = MV_CC_StopRecord(handle); if (MV_OK != nRet) { printf("Stop record fail! nRet [0x%x]\n", nRet); break; } // ch:关闭设备 | Close device nRet = MV_CC_CloseDevice(handle); if (MV_OK != nRet) { printf("ClosDevice fail! nRet [0x%x]\n", nRet); break; } // ch:销毁句柄 | Destroy handle nRet = MV_CC_DestroyHandle(handle); if (MV_OK != nRet) { printf("Destroy Handle fail! nRet [0x%x]\n", nRet); break; } handle = NULL; } while (0); if (handle != NULL) { MV_CC_DestroyHandle(handle); handle = NULL; } // ch:反初始化SDK | en:Finalize SDK MV_CC_Finalize(); printf("Press a key to exit.\n"); WaitForKeyPress(); return 0; }
最新发布
11-07
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值