unity接入海康网络摄像头的多种实现方法

此次项目需求是接入海康威视的网络智能摄像头实现实时监控。网上搜罗一番,也有挺多例子的,但是大多数都是通过官方提供的rstp协议地址实现,为了自己记忆,在下打算在这里记录一下,不需要的可以跳过这里哈(这里采用一个叫UMP的插件,当然还有其他Vlc for unity,openCV等都可以实现的)

首先贴一下海康的rtsp协议地址:

rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream
说明:
username: 用户名。例如admin。
password: 密码。例如12345。
ip: 为设备IP。例如 192.0.0.64。
port: 端口号默认为554,若为默认可不填写。
codec:有h264、MPEG-4、mpeg4这几种。
channel: 通道号,起始为1。例如通道1,则为ch1。
subtype: 码流类型,主码流为main,辅码流为sub。


例如,请求海康摄像机通道1的主码流,Url如下
主码流:
rtsp://admin:12345@192.0.0.64:554/h264/ch1/main/av_stream
rtsp://admin:12345@192.0.0.64:554/MPEG-4/ch1/main/av_stream


子码流:
rtsp://admin:12345@192.0.0.64/mpeg4/ch1/sub/av_stream

rtsp://admin:12345@192.0.0.64/h264/ch1/sub/av_stream

然后插件的名字叫UMP 地址我就不贴了,搜一下会有的哈。导入后是这样的

然后随便选一个场景填入你的rtsp地址

 

然后这是运行的效果

 

但是本人通过测试后发现延迟~额。。。有一丢丢小高,于是继续搜罗,发现海康的SDK里的函数是可以回调获取视频流的数据的,还提供了一个播放库的SDK,同过此SDK的方法可以将标准的视频码流转换为YV12的格式,相信研究过视频流的对这个格式都不陌生吧,没错,小白我准备在unity中将这个格式的数据实时生成一帧一帧的textur2d,然后,上步骤

首先下载海康最新的SDK包

地址:http://www.hikvision.com/cn/download_more_570.html

记住播放库的也一起下来哦、

前一部分的实现代码其实官方已经提供了各种各样的案例 C#和C++的都有,但是看过代码的同学应该都会知道,在这写Demo中想要播放视频都有一个必不可少的参数就是窗口的句柄Handle,就是一个IntPtr类型的参数,但是untiy中。As you konw ,哪里来的句柄啊,UI都是画出来的嘛,整个unity才是一个窗口,但是SDK中也说了可以给这个参数传空然后给一个回调函数来获取这些视频数据,

 

记住这个回调函数里是用来启用Play_M4播放库解码的(有点长,还是不贴图片贴代码号了,不要喷我乱(* ̄︶ ̄))



/// <summary>
/// 获取数据流回调函数
/// </summary>
/// <param name="lRealHandle">L real handle.</param>
/// <param name="dwDataType">Dw data type.</param>
/// <param name="pBuffer">P buffer.</param>
/// <param name="dwBufSize">Dw buffer size.</param>
/// <param name="pUser">P user.</param>
public void RealDataCallBack(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser)
{
//下面数据处理建议使用委托的方式
//MyDebugInfo AlarmInfo = new MyDebugInfo(DebugInfo);
switch (dwDataType)
{
case CHCNetSDK.NET_DVR_SYSHEAD:     // sys head
if (dwBufSize > 0)
{
if (m_lPort >= 0)
{
return; //同一路码流不需要多次调用开流接口
}
//debugInfo +=  ("系统头数据:" + pBuffer + "数据长度:" + dwBufSize);
//获取播放句柄 Get the port to play
if (!PlayCtrl.PlayM4_GetPort(ref m_lPort))
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("PlayM4_GetPort failed, error code= " + error_num);
break;
}


//设置流播放模式 Set the stream mode: real-time stream mode
if (!PlayCtrl.PlayM4_SetStreamOpenMode(m_lPort, PlayCtrl.STREAME_REALTIME))
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("Set STREAME_REALTIME mode failed, error code= " + error_num);
}


//打开码流,送入头数据 Open stream
if (!PlayCtrl.PlayM4_OpenStream(m_lPort, pBuffer, dwBufSize, 1024*1024))
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("PlayM4_OpenStream failed, error code= " + error_num);
break;
}




//设置显示缓冲区个数 Set the display buffer number
if (!PlayCtrl.PlayM4_SetDisplayBuf(m_lPort, 15))
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("PlayM4_SetDisplayBuf failed, error code= " + error_num);
}


//设置显示模式 Set the display mode
if (!PlayCtrl.PlayM4_SetOverlayMode(m_lPort, 0, 0)) //play off screen 
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("PlayM4_SetOverlayMode failed, error code= " + error_num);
}


//设置解码回调函数,获取解码后音视频原始数据 Set callback function of decoded data
m_fDisplayFun = new PlayCtrl.DECCBFUN(DecCallbackFUN);
if (!PlayCtrl.PlayM4_SetDecCallBack(m_lPort, m_fDisplayFun))
{
debugInfo += ("PlayM4_SetDisplayCallBack fail");
}


//开始解码 Start to play                       
if (!PlayCtrl.PlayM4_Play(m_lPort, IntPtr.Zero))
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("PlayM4_Play failed, error code= " + error_num);
break;
}



}
break;
case CHCNetSDK.NET_DVR_STREAMDATA:     // video stream data
if (dwBufSize > 0 && m_lPort != -1)
{
for (int i = 0; i < 999; i++)
{
//送入码流数据进行解码 Input the stream data to decode
if (!PlayCtrl.PlayM4_InputData(m_lPort, pBuffer, dwBufSize))
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("PlayM4_InputData failed, error code= " + error_num);
Thread.Sleep(10);
}
else
{                                
break;
}
}                      
}
break;
default:
if (dwBufSize > 0 && m_lPort != -1)
{
//送入其他数据 Input the other data
for (int i = 0; i < 999; i++)
{
if (!PlayCtrl.PlayM4_InputData(m_lPort, pBuffer, dwBufSize))
{
error_num = PlayCtrl.PlayM4_GetLastError(m_lPort);
debugInfo += ("PlayM4_InputData failed, error code= " + error_num);
Thread.Sleep(10);
}
else
{                                
break;
}

}
break;
}
}

 

然后在播放库代码中在加一个回调来自己处理数据

看图

 

这里的数据都是别人官方的播放库函数处理好的每一帧的数据,连帧号都有,可以说已经非常方便了,我们只需要将每一帧的数据拿来然后转换为我们需要的Texutrue2D数据,然后一帧一帧的显示到我们的UI上就可以了,那么重点来了,之前说过播放库拿出来的数据是YV12的格式,而我们需要的Texture2D是rgb(YUV)格式的,这些都是图片数据的存储格式,我实在太懒了,给你们贴个地址,不懂得有兴趣的可以看看O(∩_∩)O  https://www.cnblogs.com/samaritan/p/YUV.html

这里转换的话,由于数据量有点大 一帧1280*720的YV12数据大概有2764800,如果自己写硬生生的在数据里复制替换啥的,额,本人试了一下,不到10帧,感觉成GIF图了都,所以网上又搜罗一下(有事没事就搜罗搜罗),看到其他博友测试的五六种方法,有直接硬转的(通过查表法优化过的,其实也没优化多少),然后看到效率比较高的两种,用opencv和ffmpeg实现的,opencv和ffmpeg都试了下,最后选择了ffmpeg,因为ffmpeg的算法效率最好(别人说的,别人说测试了的)

这里如果要用ffmpeg的格式转换算法需要两个库avutil-55.dll和swscale-4.dll

然后呢当然就是调用这两个库,写一个转换方法导出可以供untiy调用的Dll了

下面贴出Dll里的转换函数代码,比较粗糙,大家不要介意.



AVPixelFormat SRC_pixfmt;
AVPixelFormat DST_pixfmt;


AVPicture SRC_frameinfo;
AVPicture DST_frameinfo;
struct SwsContext *img_convert_ctx;
TransformResolution *TP;

FFMPEG_FOR_UNITY_API bool StartConvert_Updated(bool start_or_end,int src_width,int src_height,int dst_width,int dst_height,int src_type,int dst_type)
{
if(start_or_end)
{
SRC_pixfmt = (AVPixelFormat)src_type;//0
DST_pixfmt = (AVPixelFormat)dst_type;//2


int ret=0;
TP = new TransformResolution();
TP->SRC_WIDTH = src_width;
TP->SRC_HEIGHT = src_height;
TP->DST_WIDTH = dst_width;
TP->DST_HEIGHT = dst_height;
ret= av_image_alloc(SRC_frameinfo.data, SRC_frameinfo.linesize,src_width, src_height, SRC_pixfmt, 1);
if (ret< 0) {
//printf( "Could not allocate source image\n");
//strcpy(rst,"Could not allocate source image\n");
return false;
}
ret = av_image_alloc(DST_frameinfo.data, DST_frameinfo.linesize,dst_width, dst_height, DST_pixfmt, 1);
if (ret< 0) {
//printf( "Could not allocate destination image\n");
//strcpy(rst,"Could not allocate destination image\n");
return false;
}


//Init Method 1
img_convert_ctx =sws_alloc_context();
//Show AVOption
//av_opt_show2(img_convert_ctx,stdout,AV_OPT_FLAG_VIDEO_PARAM,NULL);
//Set Value
av_opt_set_int(img_convert_ctx,"sws_flags",SWS_BICUBIC|SWS_PRINT_INFO,NULL);
av_opt_set_int(img_convert_ctx,"srcw",src_width,NULL);
av_opt_set_int(img_convert_ctx,"srch",src_height,NULL);
av_opt_set_int(img_convert_ctx,"src_format",SRC_pixfmt,NULL);
//'0' for MPEG (Y:0-235);'1' for JPEG (Y:0-255)
av_opt_set_int(img_convert_ctx,"src_range",1,NULL);
av_opt_set_int(img_convert_ctx,"dstw",dst_width,NULL);
av_opt_set_int(img_convert_ctx,"dsth",dst_height,NULL);
av_opt_set_int(img_convert_ctx,"dst_format",DST_pixfmt,NULL);
av_opt_set_int(img_convert_ctx,"dst_range",1,NULL);
sws_init_context(img_convert_ctx,NULL,NULL);
return true;
}
else
{
sws_freeContext(img_convert_ctx);
av_freep(&SRC_frameinfo);
av_freep(&DST_frameinfo);
delete TP;
return true;
}

}

StartConvert_Update函数主要做一些初始化,c++里面大家知道的申请内存啊什么的 

 

然后是转化函数

///update
FFMPEG_FOR_UNITY_API bool  YV12toRgb_Updated(uint8_t* pDst, uint8_t* pSrc)
{
if(!pDst)
{
//strcpy(rst,"pDst is null");
return false;
}
if(!TP)
{
return false;
}


switch(SRC_pixfmt){
case AV_PIX_FMT_GRAY8:{
memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT);
break;
  }
case AV_PIX_FMT_YUV420P:{
memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT);                    //Y
memcpy(SRC_frameinfo.data[1],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT*5/4,TP->SRC_WIDTH*TP->SRC_HEIGHT/4);  //V
memcpy(SRC_frameinfo.data[2],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT,TP->SRC_WIDTH*TP->SRC_HEIGHT/4);      //U

break;
}
case AV_PIX_FMT_YUV422P:{
memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT);                    //Y
memcpy(SRC_frameinfo.data[1],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT,TP->SRC_WIDTH*TP->SRC_HEIGHT/2);      //U
memcpy(SRC_frameinfo.data[2],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT*3/2,TP->SRC_WIDTH*TP->SRC_HEIGHT/2);  //V
break;
}
case AV_PIX_FMT_YUV444P:{
memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT);                    //Y
memcpy(SRC_frameinfo.data[1],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT,TP->SRC_WIDTH*TP->SRC_HEIGHT);        //U
memcpy(SRC_frameinfo.data[2],pSrc+TP->SRC_WIDTH*TP->SRC_HEIGHT*2,TP->SRC_WIDTH*TP->SRC_HEIGHT);      //V
break;
}
case AV_PIX_FMT_YUYV422:{
memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT*2);                  //Packed
break;
}
case AV_PIX_FMT_RGB24:{
memcpy(SRC_frameinfo.data[0],pSrc,TP->SRC_WIDTH*TP->SRC_HEIGHT*3);                  //Packed
break;
}
default:{
//printf("Not Support Input Pixel Format.\n");
break;
 }
}




sws_scale(img_convert_ctx, SRC_frameinfo.data, SRC_frameinfo.linesize, 0, TP->SRC_HEIGHT, DST_frameinfo.data, DST_frameinfo.linesize);


switch(DST_pixfmt){
case AV_PIX_FMT_GRAY8:{
memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT);
break;
 }
case AV_PIX_FMT_YUV420P:{
memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT); //Y
memcpy(pDst,DST_frameinfo.data[1],TP->DST_WIDTH*TP->DST_HEIGHT/4); //U
memcpy(pDst,DST_frameinfo.data[2],TP->DST_WIDTH*TP->DST_HEIGHT/4);    //V
break;
}
case AV_PIX_FMT_YUV422P:{
memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT);                     //Y
memcpy(pDst,DST_frameinfo.data[1],TP->DST_WIDTH*TP->DST_HEIGHT/2); //U
memcpy(pDst,DST_frameinfo.data[2],TP->DST_WIDTH*TP->DST_HEIGHT/2); //V
break;
}
case AV_PIX_FMT_YUV444P:{
memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT);                      //Y
memcpy(pDst,DST_frameinfo.data[1],TP->DST_WIDTH*TP->DST_HEIGHT);                      //U
memcpy(pDst,DST_frameinfo.data[2],TP->DST_WIDTH*TP->DST_HEIGHT);                      //V
break;
}
case AV_PIX_FMT_YUYV422:{
memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT*2);//Packed
break;
}

case AV_PIX_FMT_RGB24:{
//fwrite(dstFrameInfo.data[0],1,dst_w*dst_h*3,dst_file); 
memcpy(pDst,DST_frameinfo.data[0],TP->DST_WIDTH*TP->DST_HEIGHT*3);//Packed
 // strcpy(rst,"转换成功!");
return true;
break;
 }
default:{
//printf("Not Support Output Pixel Format.\n");
break;
}
}
//strcpy(rst,"转换失败!");
return false;
}

然后unity里的调用 

然后调用此函数将之前回调函数里存起来的YV12数据 转换为rgb数据

 

然后,额,创建Texture2D?,还是贴一下吧,虽然代码很丑。。

 

 

最后贴一下运行效果吧(里面其他的功能都很简单,就不多做介绍)

试一下GIF图o(* ̄︶ ̄*)o

共同学习,欢迎留言评论以及给我更好的建议或者实现方案,谢谢大家!

  • 24
    点赞
  • 116
    收藏
    觉得还不错? 一键收藏
  • 100
    评论
### 回答1: Unity是一款跨平台的游戏引擎,可以用于开发游戏、虚拟现实和增强现实应用程序。而海康是一家专业的视频监控设备制造商。结合Unity海康摄像头,开发者可以实现摄像头实时播放的功能。 要实现unity海康摄像头实时播放demo,首先需要下载并安装海康SDK并引入Unity项目中。然后在Unity中创建一个Camera对象,并在脚本中使用海康SDK提供的接口进行摄像头的连接与实时播放配置。 在Unity中,可以监听海康摄像头视频流数据,并将其渲染到指定的Texture上。通过设置Texture的像素数据,不断更新纹理数据,就能够实现实时播放。同时,可以调整视频的参数、画面的显示大小等等。 在开发过程中,也可以根据需要,添加一些附加功能,比如显示摄像头的画面控制面板、添加音频处理功能等。使用Unity的强大功能,也可以利用物理引擎、光照等特Effect的特性,提升演示效果。 总结来说,通过整合Unity海康摄像头SDK,开发者可以实现一个基于Unity实时摄像头播放demo。这个demo可以用于展示、教学和模拟等各种应用场景。同时,开发者也可以根据需求,添加各种附加功能,进一步丰富和完善demo的功能和体验。 ### 回答2: Unity 是一种跨平台的游戏引擎,可用于开发游戏、虚拟现实和增强现实等应用。 海康摄像头是一种常见的安防监控摄像设备,具备高清晰度、远距离监控和实时播放等特点。 在 Unity 中,我们可以使用海康摄像头实时播放的功能来开发监控应用。首先,我们需要将海康摄像头SDK 导入 Unity 中,并配置摄像头的 IP 地址、端口和登录信息。 接着,在 Unity 中创建一个场景,添加一个摄像机对象作为预览窗口。通过调用海康摄像头的 API,我们可以初始化摄像头实例并将其绑定到预览窗口中。这样,在游戏运行时,我们就可以看到摄像头实时画面了。 为了实现更多功能,我们还可以在 Unity 中添加一些交互元素,比如在画面上添加按钮,用于控制摄像头的旋转、放大和缩小;或者添加一些图形标识,用于标记特定目标或区域。 此外,我们还可以根据需要,对摄像头实时画面进行处理和分析。比如,通过图像识别算法检测画面中的人脸或车辆;或者通过计算机视觉技术,对画面进行计数、跟踪和行为分析等。 总结来说,通过 Unity海康摄像头的结合,我们可以开发出功能强大且具有交互性的实时监控应用。无论是用于安防领域还是其他领域,这种解决方案都能够提供高质量的摄像头播放和实时图像处理功能。
评论 100
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值