c# nvr开发

2015-04-30 23:24:00 weixin_30834019 阅读数 130

 

  最近继续在家休息,在完成上一个Python抓取某音乐网站爬虫后,琢磨着实现一个基于HTTP推送的 IP视频监控,比如外出的时候,在家里

开启一个监控端(摄像头+服务端),可以看到实时画面,如果再加上自动告警,就更好了。公网访问需要在 路由器上设置 花生壳+端口转发。

计划在退休的安卓手机上实现这IP视频监控软件,虽然应用市场一大堆别人写好的软件,不过我觉得吧,既然是程序员,自己敲代码实现的软件会

更有成就感。考虑到需要先验证下方案的可行性,我用比较熟悉的C# 控制台实现了一个DEMO。

 

设想的方案:

  1.实现一个简单HTTP服务器,用来接受请求并启动一个线程处理图片流的推送功能

  2.开发一个实时抓取图片的线程,并将图片交给HTTP推送线程

  3.HTTP的请求URL参数中 附带推送频率、图片高度和宽度

  4.使用一个IP摄像头监控端(或者Firefox浏览器),实时查看视频画面

  5.循环录制视频(未实现)

  6.对画面进行监控告警(未实现)

 

核心技术点:

  1.HttpListener (HTTP.SYS)

  2.HTTP :multipart/x-mixed-replace;

  3.线程同步、委托、事件

  4.摄像头驱动、图片抓取(Andrew Kirillov 写的)

  5.图片流解析,显示(Andrew Kirillov 写的,也可以直接在Firefox浏览器打开直接显示)

 

运行截图:

  1.视频监控端 (Andrew Kirillov 写的 视频源支持N种,当前配置推送频率50毫秒 w=240&h=120)

  

 

  2.视频服务端(我写的 简陋的DEMO 不过实现了功能 嘎嘎)

 

 

下面开始贴核心源码(最近右胳膊有石膏,左手写代码  凑合看吧!):

 

1.建立HTTP服务:

 1 using (HttpListener listerner = new HttpListener())
 2                 {
 3                     listerner.AuthenticationSchemes = AuthenticationSchemes.Anonymous;//指定身份验证 Anonymous匿名访问
 4                     listerner.Prefixes.Add("http://+:6666/");
 5                     
 6                     //listerner.Prefixes.Add("http://+/");
 7                     //listerner.Prefixes.Add("http://+:8080/");
 8                     //listerner.Prefixes.Add("http://+:6666/");
 9                     //listerner.Prefixes.Add("http://+/video.cgi/");
10                     //listerner.Prefixes.Add("http://+:8080/video.cgi/");
11 
12                     listerner.Start();
13                     Console.WriteLine("WebServer Start Successed.......");
14                     while (true)
15                     {
16                         try
17                         {
18                             //等待请求连接
19                             //没有请求则GetContext处于阻塞状态
20                             HttpListenerContext ctx = listerner.GetContext();
21 
22                             SendImgService oService = new SendImgService();
23                             oService.Ctx = ctx;
24                             localsev.NewFrame += new CameraEventHandler(oService.camera_NewFrame);
25                             ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), oService);
26 
27                             //Thread osThread = new Thread(new ThreadStart(oService.ServiceRun));
28                             //osThread.Start();
29                         }
30                         catch (Exception ex)
31                         {
32                             Console.WriteLine(ex);
33                         }
34                     }
35                     listerner.Stop();
36                     listerner.Close();
37                 }
View Code

2.启动本地视频头,并抓取图片

 1 public void ServiceRun()
 2         {
 3 
 4             FilterCollection filters = new FilterCollection(FilterCategory.VideoInputDevice);
 5 
 6             if (filters.Count == 0)
 7                 throw new ApplicationException();
 8 
 9             // add all devices to combo
10             foreach (Filter filter in filters)
11             {
12                 Console.WriteLine(filter.Name + "" + filter.MonikerString);
13             }
14             CaptureDevice localSource = new CaptureDevice();
15             localSource.VideoSource = filters[0].MonikerString;
16 
17             // create camera
18             camera = new Camera(localSource);
19             // start camera
20             camera.Start();
21 
22 
23             // set event handlers
24             camera.NewFrame += new CameraEventHandler(camera_NewFrame);
25 
26         }
27 
28         // On new frame ready
29         private void camera_NewFrame(object sender, CameraEventArgs e)
30         {
31             if (seq == 999)
32             {
33                 seq = 0;
34             }
35          //   Console.WriteLine("LocalCamService get camera_NewFrame ==> {0}", ++seq);
36 
37             // lock
38             Monitor.Enter(this);
39 
40             if (camera != null)
41             {
42                 camera.Lock();
43 
44                 // dispose old frame
45                 if (lastFrame != null)
46                 {
47                     lastFrame.Dispose();
48                 }
49                 // draw frame
50                 if (camera.LastFrame != null)
51                 {
52                     lastFrame = (Bitmap)camera.LastFrame.Clone();
53                     // notify client
54                     if (NewFrame != null)
55                         NewFrame(this, new CameraEventArgs(lastFrame));
56                 }
57 
58 
59                 camera.Unlock();
60             }
61 
62             // unlock
63             Monitor.Exit(this);
64         }
65     }
View Code

3.图片推送

 1 public void ServiceRun()
 2         {
 3             remoteInfo = ctx.Request.RemoteEndPoint.ToString();
 4             string intervalstr = ctx.Request.QueryString["i"];
 5             string widthstr = ctx.Request.QueryString["w"];
 6             string heightstr = ctx.Request.QueryString["h"];
 7 
 8             if (!string.IsNullOrWhiteSpace(intervalstr))
 9             {
10                 interval = int.Parse(intervalstr);
11             }
12             if (!string.IsNullOrWhiteSpace(widthstr))
13             {
14                 width = int.Parse(widthstr);
15             }
16             if (!string.IsNullOrWhiteSpace(heightstr))
17             {
18                 height = int.Parse(heightstr);
19             }
20             Console.WriteLine("Accept one new request:{0},interval:[{1}]", remoteInfo, interval);
21 
22 
23             ctx.Response.StatusCode = 200;//设置返回给客服端http状态代码
24             ctx.Response.ContentType = "multipart/x-mixed-replace; boundary=--BoundaryString";
25 
26             string rspheard = "--BoundaryString\r\nContent-type: image/jpg\r\nContent-Length: {0}\r\n\r\n";
27             string strrn = "\r\n";
28 
29             using (Stream stream = ctx.Response.OutputStream)
30             {
31                 while (true)
32                 {
33                     Thread.Sleep(interval);
34 
35                     try
36                     {
37                         // lock
38                         Monitor.Enter(this);
39 
40                         if (newFrame == null)
41                         {
42                             continue;
43                         }
44                         //得到一个ms对象
45                         byte[] imageBuffer;
46                         using (MemoryStream ms = new MemoryStream())
47                         {
48 
49                             //newFrame = (Bitmap)GetThumbnail(newFrame, width, height);
50                             //将图片保存至内存流
51                             newFrame.Save(ms, ImageFormat.Jpeg);
52 
53                             rspheard = string.Format(rspheard, ms.Length);
54 
55                             byte[] heardbuff = Encoding.ASCII.GetBytes(rspheard);
56                             stream.Write(heardbuff, 0, heardbuff.Length);
57 
58                             imageBuffer = new byte[512];
59                             int c;
60                             ms.Position = 0;
61                             //通过内存流读取到imageBytes
62                             while ((c = ms.Read(imageBuffer, 0, 512)) > 0)
63                             {
64                                 stream.Write(imageBuffer, 0, c);
65                             }
66                             byte[] rnbuff = Encoding.ASCII.GetBytes(strrn);
67                             stream.Write(rnbuff, 0, rnbuff.Length);
68 
69                             Console.WriteLine("[{0}] : SendImgService send NewFrame", remoteInfo);
70 
71                         }
72 
73                         // stream.Flush();
74                     }
75                     catch (Exception ex)
76                     {
77                         Console.WriteLine(ex);
78 
79                         break;
80                     }
81                     finally
82                     {
83                         // unlock
84                         Monitor.Exit(this);
85                     }
86                 }
87             }
88             Console.WriteLine("[{0}] : 线程结束...", remoteInfo);
89         }
View Code

 

附件:(刚会传文件,还不知道怎么插入链接,谁教我下?)

可运行程序:

http://files.cnblogs.com/files/ryhan/%E7%9B%91%E6%8E%A7%E5%8F%8A%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%8F%AF%E8%BF%90%E8%A1%8C%E7%A8%8B%E5%BA%8F.zip

 

监控端源码:

http://files.cnblogs.com/files/ryhan/%E7%9B%91%E6%8E%A7%E7%AB%AF%E6%BA%90%E7%A0%81.zip

 

服务端源码:

http://files.cnblogs.com/files/ryhan/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%BA%90%E7%A0%81%28%E5%8D%9A%E5%AE%A2%E4%B8%AD%E5%AE%9E%E7%8E%B0%29.zip

 

PS:

  1.建议用VS2010打开

  2.监控端cv_src目录下cv3.sln为监控客户端程序,用来看画面,cameras.config配置视频源

  3.HttpImageStream是本次实现的图片推送Demo 效率上估计有点问题。

  4.运行HttpImageStream时,建议电脑上有摄像头,不然估计会无法启动。

 

posted on 2015-04-30 23:24 ryhan 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/ryhan/p/4470107.html

2020-01-08 11:47:28 xd19890922 阅读数 324

主要功能:

海康SDK开发,通过连接NVR,实现连接NVR的2个相机同时采集(多线程),并进行opencv图像格式转换。

关键技术点:

1、回调函数

2、YV12->oepncv图像格式转换

3、多线程连接多IPcamera,同时采集

---------------------------分割线-------------------------------------------------------

1、回调函数

定义:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

回调函数是继承自C语言的。

在C++中,应只在与C代码建立接口或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或仿函数(functor),而不是回调函数。

理解:即回调函数的目的是解耦,

 

Callback
下面以一段不完整的C语言代码来呈现上图的意思:
#include<stdio.h>
#include<softwareLib.h> // 包含Library Function所在读得Software library库的头文件
int Callback() // Callback Function
{
// TODO
return 0;
}
int main() // Main program
{
// TODO
Library(Callback);
// TODO
return 0;
}

      乍一看,回调似乎只是函数间的调用,和普通函数调用没啥区别,但仔细一看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且丝毫不需要修改库函数的实现,这就是解耦。

      再仔细看看,主函数和回调函数是在同一层的,而库函数在另外一层,想一想,如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。

更详细的讲解请参考:

 https://www.cnblogs.com/jiangzhaowei/p/9129105.html

https://baike.baidu.com/item/%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0/7545973?fr=aladdin

实例:

//回调函数实例
void __stdcall show(void(*CallLBackFun)(const string&, InputArray), const string& winname, InputArray m)
{
	namedWindow(winname,0);
	CallLBackFun(winname, m);
	waitKey(1);
}

void  CALLBACK DecCBFun(long nPort,char *pBuf,long nSize,FRAME_INFO *pFrameInfo,void *nUser,void *nReserved2)
{
	char buff[10];
	sprintf_s(buff, "%d", nPort+11);
	if (pFrameInfo->nType == T_YV12)
	{
		//  YUV--> Mat格式转换
		Mat g_BGRImage;
		g_BGRImage.create(pFrameInfo->nHeight, pFrameInfo->nWidth, CV_8UC3);
		Mat YUVImage(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, (unsigned char*)pBuf);
		cvtColor(YUVImage, g_BGRImage, COLOR_YUV2BGR_YV12);
		//把opencv的imshow作为回调函数使用
           //imshow("dst", g_BGRImage);
		show(imshow,buff, g_BGRImage);	
	}
}

2、YV12->oepncv图像格式转换

void  CALLBACK DecCBFun(long nPort,char *pBuf,long nSize,FRAME_INFO *pFrameInfo,void *nUser,void *nReserved2)
{
	if (pFrameInfo->nType == T_YV12)
	{
		//  YUV--> Mat格式转换
		Mat g_BGRImage;
		g_BGRImage.create(pFrameInfo->nHeight, pFrameInfo->nWidth, CV_8UC3);
		Mat YUVImage(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, (unsigned char*)pBuf);
		cvtColor(YUVImage, g_BGRImage, COLOR_YUV2BGR_YV12);
           imshow("dst", g_BGRImage);
           cvWaitKey(1);	
	}
}

3、多线程连接多IPcamera,同时采集

C语言多线程介绍:

c语言库 process.h 中的函数, 用来创建一个线程 :_beginthreadex

unsigned long _beginthreadex(
    void *security,    // 安全属性, 为NULL时表示默认安全性
    unsigned stack_size,    // 线程的堆栈大小, 一般默认为0
    unsigned(_stdcall *start_address)(void *),    // 所要启动的线程函数
    void *argilist, // 线程函数的参数, 是一个void*类型, 传递多个参数时用结构体
    unsigned initflag, //新线程的初始状态,0表示立即执行,CREATE_SUSPENDED
表示创建之后挂起
    unsigned *threaddr    // 用来接收线程ID
);
返回值 : // 成功返回新线程句柄, 失败返回0
-------------分割线----------------------
#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned int __stdcall threadDemo(LPVOID) // void *
{
    printf("我被执行啦!\n");
    return 0;
}
int main()
{
    HANDLE handle;    
    handle = (HANDLE)_beginthreadex(NULL, 0, ThreadDemo, NULL, 0, NULL);   
    return 0;
}

参考:https://blog.csdn.net/p312011150/article/details/81538247

实例:

#include <process.h>
typedef struct MyStruct
{
	LONG lUserId;
	LONG channel;
}MyStruct, *LPMyStruct;
----------------------------
unsigned int __stdcall play(void* user)
{
	LPMyStruct stru = (LPMyStruct)user;
	//启动预览并设置回调数据流
	LONG lRealPlayHandle;
	//HWND hWnd = GetConsoleWindowAPI();     //获取窗口句柄
	NET_DVR_PREVIEWINFO struPlayInfo = { 0 };
	struPlayInfo.hPlayWnd = NULL;         //需要SDK解码时句柄设为有效值,仅取流不解码时可设为空
	struPlayInfo.lChannel = stru->channel + 32;       //预览通道号
	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(stru->lUserId, &struPlayInfo, fRealDataCallBack, NULL);
	if (lRealPlayHandle < 0)
	{
		printf("NET_DVR_RealPlay_V40 error\n");
		printf("error :%d", NET_DVR_GetLastError());
		NET_DVR_Logout(stru->lUserId);
		NET_DVR_Cleanup();
		return 0;
	}
	Sleep(10000);
	//关闭预览
	NET_DVR_StopRealPlay(lRealPlayHandle);
	return 0;
}
-------------------------
//多线程
	MyStruct structdata, structdata2;
	structdata.lUserId = lUserID;
	structdata.channel = 7;
	HANDLE handle, handle2;
	handle = (HANDLE)_beginthreadex(NULL, 0, play, (void*)(&structdata), 0, NULL);
	structdata2.lUserId = lUserID;
	structdata2.channel = 3;
	handle2 = (HANDLE)_beginthreadex(NULL, 0, play, (void*)(&structdata2), 0, NULL);

	Sleep(20000);

-----------------------分割线----------------------------------------

源程序:

不能添加附件,大家去下载链接下载吧,资源分1分,没有积分的留言联系我吧

 

 

2016-02-24 20:54:23 bigpudding24 阅读数 29587

转载于整理自:心澄欲遣

目前使用的海康SDK包括IPC_SDK(硬件设备),Plat_SDK(平台),其中两套SDK都需单独调用海康播放库PlayCtrl.dll来解码视频流,返回视频信息和角度信息。本文仅对视频监控常用功能的使用进行说明,其它未实现功能请参看设备网络SDK使用手册播放库编程指南V7.2

IPC_SDK编程指南

(一)    SDK的引用

由于IPC_SDK没有SDK安装程序,所以需手工把下面图表中的DLL放入Debug或者Release文件夹的根目录下供程序调用,或者加入系统环境变量Path下。

 

名称

版本号

说明

AudioIntercom.dll

1.1.0.5

 

AudioRender.dll

1.0.0.2

 

DsSdk.dll

6.0.10.922

 

gdiplus.dll

 

微软库

HCNetSDK.dll

4.3.0.6

网络功能调用,大量功能调用此库

OpenAL32.dll

 

 

PlayCtrl.dll

7.2.0.0

播放库,定制版本,增加返角回调及数据结构

QosControl.dll

1.0.0.1

 

StreamTransClient.dll

1.1.2.12

 

SuperRender.dll

1.0.1.0

 

SystemTransform.dll

2.4.0.3

设备信息转发,根据播放库修改过

(二)    C#程序调用DLL中的非托管函数方法

1.        调用外部声明方法

首先在C#语言源程序中声明外部方法,其基本形式是:

[DLLImport(“DLL文件”)]

修饰符 extern 返回变量类型 方法名称 (参数列表)

例如:

using System.Runtime.InteropServices;
 
[DllImport("HCNetSDK.dll")]
public static extern bool NET_DVR_Init();

注意:

1)        需要在程序声明中使用System.Runtime.InteropServices命名空间。     DllImport只能放置在方法声明上。

2)        DLL文件必须位于程序当前目录或系统定义的查询路径中(即:系统环境变量中Path所设置的路径)。

3)        返回变量类型、方法名称、参数列表一定要与DLL文件中的定义相一致。

4)        若要使用其它函数名,可以使用EntryPoint属性设置,如:[DllImport("user32.dll", EntryPoint="MessageBoxA")]

static extern int MsgBox(int hWnd, string msg, string caption, int type);

5)        其它可选的 DllImportAttribute 属性:

CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;

SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;

ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,

如:ExactSpelling=false;

PreserveSig指示方法的签名应当被保留还是被转换, 如:PreserveSig=true;

CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;

2.        参数数据类型转换(详细参考这里

Win32 Types

CLR Type

char,INT8,SBYTE,CHAR

System.SByte

short,short int,INT16,SHORT

System.Int16

int,long,long int,INT32,LONG32,BOOL,INT

System.Int32

_int64,INT64,LONGLONG

System.Int64

unsigned char,UINT8,UCHAR,BYTE

System.Byte

unsigned short,UINT16,USHORT,WORD,ATOM,WCHAR,__wchar_t

System.UInt16

unsigned,unsigned int,UINT32,ULONG32,DWORD32,ULONG,DWORD,UINT

System.UInt32

unsigned __int64,UINT64,DWORDLONG,ULONGLONG

System.UInt64

float,FLOAT

System.Single

double,long double,DOUBLE

System.Double

BSTR

StringBuilder

LPCTSTR

StringBuilder

LPCWSTR

IntPtr

handle

IntPtr

hwnd

IntPtr

char * 

string

int *

ref int

int &

ref int

void *

IntPtrs

unsigned char * 

ref byte

BOOL ——

bool

DWORD

uint或int

注意:

  1. 指针做参数时在C#中一定要使用ref 或out关键字,尤其是结构体指针,要不会报内存读取错误
  2. 首先声明结构体 [StructLayoutAttribute(LayoutKind.Sequential)]
  3. 重写结构体的时候,之前有指明类型长度或数组长度的地方,也要进行相应的标注,要不也会导致内存错误

3.        重写结构体

例如:[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)],具体长度需参看SDK中改结构体的说明文档

或者  [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 64, ArraySubType = UnmanagedType.I1)]

4.        结构体与指针之间的转换

1)        结构体转换为指针

Hik.NET_DVR_IPPARACFG_V40 ipParaCfgV40 = new Hik.NET_DVR_IPPARACFG_V40();//初始化结构体
Int32 size = Marshal.SizeOf(ipParaCfgV40);//返回结构体字节数
IntPtr ptrIpParaCfgV40 = Marshal.AllocHGlobal(size);//定义指针字节数
Marshal.StructureToPtr(ipParaCfgV40, ptrIpParaCfgV40, false);//将结构体封装到内存指针中
//调用需要指针的方法
Marshal.FreeHGlobal(ptrIpParaCfgV40);//释放指针

2)        指针转换为结构体

Hik.NET_DVR_CAMERAPARAMCFG_EX cameraParamCfg = new Hik.NET_DVR_CAMERAPARAMCFG_EX();//实例化结构体
Int32 size = Marshal.SizeOf(cameraParamCfg );//获取结构体字节数
IntPtr ptrCfg = Marshal.AllocHGlobal(size);//为指针分配空间
//调用获取指针的方法
cameraParamCfg = (Hik.NET_DVR_CAMERAPARAMCFG_EX)Marshal.PtrToStructure(ptrCfg, typeof(Hik.NET_DVR_CAMERAPARAMCFG_EX));//把指针转换为结构体
Marshal.FreeHGlobal(ptrCfg);//释放指针

3)        指针转换为结构体精简写法

Int32 size = Marshal.SizeOf(typeof(Hik.NET_DVR_PTZPOS));
IntPtr ptrPTZ = Marshal.AllocHGlobal(size);
//调用获取指针的方法
Hik.NET_DVR_PTZPOS PTZPos = (Hik.NET_DVR_PTZPOS)Marshal.PtrToStructure(ptrPTZ, typeof(Hik.NET_DVR_PTZPOS));//指针转换为结构体
Marshal.FreeHGlobal(ptrPTZ);//释放指针

对第2点和第3点的说明:当一个方法的参数为一个结构体的指针时,并且执行方法后此指针会返回结构体信息时,可以有两种方式来初始化这个指针,第二种更为简洁些。

(三)    SDK的调用

对IPC_SDK的C#封装类见附件Hik.cs(类似我们CHCNetSDK.cs),可参考此基础类文件进行程序功能编写。

(类似我们的HKIPCamera.cs类来调用CHCNetSDK类的win32函数)

1.        获取错误码

Hik.NET_DVR_GetLastError();//获取错误码

使用方式:如果有个非托管函数方法返回结果为false,则调用此方法获取错误码。

例如:

ret = Hik.NET_DVR_Init();
if (ret != true)
    throw new HikException(Hik.NET_DVR_GetLastError());//HikException为自定义异常调用类,用来解析错误码抛出异常

2.        登录

1)        用户注册

Hik.NET_DVR_Init();//初始化SDK,多次初始化会抛出错误
Hik.NET_DVR_SetConnectTime(2000, 1);//设置超时时间
Hik.NET_DVR_SetReconnect(10000, 1);//设置重连时间
Hik.NET_DVR_DEVICEINFO_V30 _deviceInfo = new Hik.NET_DVR_DEVICEINFO_V30();//设备参数结构体,可以返回设备信息
String userId = Hik.NET_DVR_Login_V30(IP,Port,UserName,Password,ref _deviceInfo);//登录后,获取用户ID和设备信息供后续方法调用

2)        用户注销

Hik.NET_DVR_StopRealPlay(_realHandle);//如果有预览视频,则根据其播放句柄关闭视频
Hik.NET_DVR_Logout(_userId);//根据用户ID注销用户登录
Hik.NET_DVR_Cleanup();//必须执行的释放IPC_SDK

3.        获取通道号

由于IPC_SDK同时支持IPC与NVR,所以两者获取通道号的方式有所不同。

1)        获取IPC设备通道号

如果Hik.NET_DVR_DEVICEINFO_V30结构体中byChanNum模拟通道数量属性值大于0,则代表登录设备为IPC设备,需要获取其模拟通道号byStartChan属性的值。每组模拟通道号范围为0-32。

for (Int32 i = 0; i < _deviceInfo.byChanNum; i++) //一个IPC设备可以设置多个模拟通道号,默认为一个模拟通道号
{
    Int channelId = _deviceInfo.byStartChan
}

2)        获取NVR设备通道号

如果Hik.NET_DVR_DEVICEINFO_V30结构体中byIPChanNum数字通道数量属性值大于0,则代表登录设备为NVR设备,需要获取其数字通道号,其获取方法要比获取IPC设备通道号复杂。每组数字通道号范围为33-64。

public void InfoIPChannel()
        {
 
            Hik.NET_DVR_IPPARACFG_V40 ipParaCfgV40 = new Hik.NET_DVR_IPPARACFG_V40();
            Int32 size = Marshal.SizeOf(ipParaCfgV40);
 
            IntPtr ptrIpParaCfgV40 = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(ipParaCfgV40, ptrIpParaCfgV40, false);
 
            UInt32 result = 0;
            Int32 groupNo = 0;
            Boolean ret = Hik.NET_DVR_GetDVRConfig(_userId, Hik.NET_DVR_GET_IPPARACFG_V40, groupNo, ptrIpParaCfgV40, (UInt32)size, ref result);//获取配置信息
            if (ret)
            {
                ipParaCfgV40 = (Hik.NET_DVR_IPPARACFG_V40)Marshal.PtrToStructure(ptrIpParaCfgV40, typeof(Hik.NET_DVR_IPPARACFG_V40));
 
                byte byStreamType;
                for (Int32 i = 0; i < ipParaCfgV40.dwDChanNum; i++)
                {
 
                    byStreamType = ipParaCfgV40.struStreamMode[i].byGetStreamType;
                    size = Marshal.SizeOf(ipParaCfgV40.struStreamMode[i].uGetStream);
 
                    switch (byStreamType)
                    {
                        //目前NVR仅支持直接从设备取流 NVR supports only the mode: get stream from device directly
                        case 0:
                            IntPtr ptrChanInfo = Marshal.AllocHGlobal(size);
                            Marshal.StructureToPtr(ipParaCfgV40.struStreamMode[i].uGetStream, ptrChanInfo, false);
                            Hik.NET_DVR_IPCHANINFO _struChanInfo = (Hik.NET_DVR_IPCHANINFO)Marshal.PtrToStructure(ptrChanInfo, typeof(Hik.NET_DVR_IPCHANINFO));
                            Int32 deviceId = _struChanInfo.byIPID + _struChanInfo.byIPIDHigh * 256 - groupNo * 64 - 1;
                            if (deviceId == -1)
                                continue;
                            _channelInfoList.Add(new ChannelInfo() { DeviceID = deviceId, ChannelID = i + (Int32)ipParaCfgV40.dwStartDChan, State = (Int32)_struChanInfo.byEnable, IP = ipParaCfgV40.struIPDevInfo[i].struIP.sIpV4 });//获取数字通道信息封装到自定义通道类中
                            Marshal.FreeHGlobal(ptrChanInfo);
                            break;
                        default:
                            break;
                    }
                }
            }
            Marshal.FreeHGlobal(ptrIpParaCfgV40);
        }

4.        预览

1)        初始化预览结构体

Hik.NET_DVR_PREVIEWINFO previewInfo = new Hik.NET_DVR_PREVIEWINFO();
previewInfo.lChannel = channelId;//预览的设备通道 the device channel number
previewInfo.dwStreamType = 0;//码流类型:0-主码流,1-子码流,2-码流3,3-码流4,以此类推
previewInfo.dwLinkMode = 0;//连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP 
previewInfo.hPlayWnd = IntPtr.Zero;//预览窗口,如果不使用回调显示视频流,则此处可以设置显示窗口句柄
previewInfo.bBlocked = true; //0- 非阻塞取流,1- 阻塞取流

2)        获取实时视频流和视频句柄

_realHandle = Hik.NET_DVR_RealPlay_V40(UserId, ref previewInfo, realData, IntPtr.Zero);//返回实时流句柄供后续方法调用

参数说明:

UserID为登录后返回的用户句柄,

previewInfo为已实例化的预览结构体

realData为预览实时流回调函数,如果直接在窗口显示实时流而不通过回调方式,则此参数可以设置为null

3)        实时流回调函数的使用

在封装类(CHCNetSDK)中定义回调函数:

/// <summary>
/// 预览实时流回调函数
/// </summary>
/// <param name="lRealHandle">当前的预览句柄</param>
/// <param name="dwDataType">数据类型:1系统头数据,2流数据(包括复合流或音视频分开的视频流数据),3音频数据</param>
/// <param name="pBuffer">存放数据的缓冲区指针</param>
/// <param name="dwBufSize">缓冲区大小</param>
/// <param name="pUser">用户数据</param>
public delegate void REALDATACALLBACK(Int32 lRealHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser);

使用时注意:实例化实时流回调函数时,不能在方法内声明并实例化,否则会被回收机制提前释放实例化对象,导致程序错误。需要在最外部调用类中作为字段预先声明。

例如:(HKIPCamera类中)

private Hik.REALDATACALLBACK _callback;//实时流回调,作为字段声明
public override int IncreaseClient()
{
    if (_callback == null)
         _callback = new Hik.REALDATACALLBACK(RealDataCallBack);//RealDataCallBack是一个方法
         Hik.StartPlay(Hik.ChannelInfoList[0].ChannelID, _callback);//播放实时流视频
}

简易流程描述为:实时流回调函数返回实时视频流,然后把实时视频流传入到播放库解码(PlayCtrl.dll),通过播放库的两个回调函数来返回解析后的视频流和角度。角度回调信息的格式为我公司定制协议,具体内容参看角度信息私有数据格式,59个字节长度的16进制字符串。

private Hik.DECCBFUN _displayCallback;//解码回调
private Hik.ADDITIONDATACBFUN _additionDataDisplayCallback;//角度回调

获取播放库错误码

Int port;
PlayM4_GetPort(ref port);//首先需获取播放句柄
PlayM4_GetLastError(port);//获取播放库错误码

具体回调如下:

int port;播放库端口号 
switch (dwDataType)
{
        case 1:     // sys head 系统头数据
        if (dwBufSize > 0)
        {
           //获取播放句柄 Get the port to play
           PlayM4_GetPort(ref _port);
           //设置流播放模式
           PlayM4_SetStreamOpenMode(_port, 0);
           //打开码流,送入头数据 Open stream
          PlayM4_OpenStream(_port, pBuffer, dwBufSize, 2 * 1024 * 1024);
           //设置显示缓冲区个数 
           PlayM4_SetDisplayBuf(_port, 15);
           //设置解码回调函数,获取解码后音视频原始数据 Set callback function of decoded data
           PlayM4_SetDecCallBackEx(_port, displayFun, IntPtr.Zero, 0);
           //设置角度回调函数,获取解码前角度信息
          PlayM4_SetAdditionDataCallBack(_port, 0x1004, additionDataDisplayFun, IntPtr.Zero);
          //开始解码 Start to play  
          PlayM4_Play(_port, IntPtr.Zero);//最后一个参数表示播放窗体的指针
          }
          break;
          case 2:     // video stream data 视频流数据(包括复合流和音视频分开的视频流数据)
          if (dwBufSize > 0 && _port != -1)
          {
            for (Int32 i = 0; i < 999; i++)
            {
              //送入码流数据进行解码 Input the stream data to decode
             if (!PlayM4_InputData(_port, pBuffer, dwBufSize))
                Thread.Sleep(2);
            else
                 break;  
                      }
            }
           break;
          default:
           if (dwBufSize > 0 && _port != -1)
           {
              //送入其他数据 Input the other data
             for (Int32 i = 0; i < 999; i++)
             {
              if (!PlayM4_InputData(_port, pBuffer, dwBufSize))
                  Thread.Sleep(2);
              else
                  break;
              }
            }
              break;
            }

4)        停止预览

Hik.NET_DVR_StopRealPlay(_realHandle);//_realHandle为播放句柄

5.        云台控制

1)        云台操作命令

            SET_PRESET = 8,// 设置预置点 
            CLE_PRESET = 9,// 清除预置点 
            Up = 21,/* 云台以SS的速度上仰 */
            Down = 22,/* 云台以SS的速度下俯 */
            Left = 23,/* 云台以SS的速度左转 */
            Right = 24,/* 云台以SS的速度右转 */
            UpLeft = 25,/* 云台以SS的速度上仰和左转 */
            UpRight = 26,/* 云台以SS的速度上仰和右转 */
            DownLeft = 27,/* 云台以SS的速度下俯和左转 */
            DownRight = 28,/* 云台以SS的速度下俯和右转 */
            Auto = 29,/* 云台以SS的速度左右自动扫描 */
            ZOOM_IN = 11,/* 焦距以速度SS变大(倍率变大) */
            ZOOM_OUT = 12,/* 焦距以速度SS变小(倍率变小) */
            FOCUS_NEAR = 13,  /* 焦点以速度SS前调 */
            FOCUS_FAR = 14,  /* 焦点以速度SS后调 */
            IRIS_OPEN = 15,  /* 光圈以速度SS扩大 */
            IRIS_CLOSE = 16,  /* 光圈以速度SS缩小 */
            GOTO_PRESET = 39/* 快球转到预置点 */

2)        云台操作方法

根据定制协议,云台控制速度为1-160。但是需保证球机的升级包版本为IPD_R3_STD_5.2.0_141111。

/// <summary>
/// 带速度的云台控制操作(需先启动图像预览)
/// </summary>
/// <param name="lRealHandle">NET_DVR_RealPlay_V40的返回值</param>
/// <param name="dwPTZCommand">云台控制命令</param>
/// <param name="dwStop">云台停止动作或开始动作:0- 开始;1- 停止</param>
/// <param name="dwSpeed">云台控制的速度,用户按不同解码器的速度控制值设置。取值范围[1,160]</param>
/// <returns>TRUE表示成功,FALSE表示失败</returns>
bool NET_DVR_PTZControlWithSpeed(int lRealHandle, uint dwPTZCommand, uint dwStop, uint dwSpeed);

6.        云台预置位

1)        预置位命令

     SET_PRESET = 8,// 设置预置点 
            CLE_PRESET = 9,// 清除预置点 
            GOTO_PRESET = 39// 转到预置点 

2)        预置位操作

/// <summary>
/// 设置预置位
/// </summary>
/// <param name="lRealHandle">NET_DVR_RealPlay_V40的返回值</param>
/// <param name="dwPTZPresetCmd">预置位控制命令</param>
/// <param name="dwPresetIndex">预置位编号最多支持255个预置点</param>
/// <returns>TRUE表示成功,FALSE表示失败</returns>
bool NET_DVR_PTZPreset(int lRealHandle, uint dwPTZPresetCmd, uint dwPresetIndex);

3)        反向操作

Hik.NET_DVR_PTZPreset(_realHandle, (uint)8, 33);

 

7.        获取设备的配置信息

在设定某些配置时,需预先执行此方法来获取相关配置信息的结构体。

/// <summary>
/// 获取设备的配置信息
/// </summary>
/// <param name="lUserID">NET_DVR_Login_V30的返回值</param>
/// <param name="dwCommand">设备配置命令</param>
/// <param name="lChannel">通道号或者组号,不同的命令对应不同的取值</param>
/// <param name="lpOutBuffer">接收数据的缓冲指针</param>
/// <param name="dwOutBufferSize">接收数据的缓冲长度(以字节为单位),不能为0</param>
/// <param name="lpBytesReturned">实际收到的数据长度指针,不能为NULL</param>
/// <returns></returns>
bool NET_DVR_GetDVRConfig(int lUserID, uint dwCommand, int lChannel, IntPtr lpOutBuffer, uint dwOutBufferSize, ref uint lpBytesReturned);

具体调用见下面操作示例

8.        设置设备的配置信息

/// <summary>
/// 设置设备的配置信息
/// </summary>
/// <param name="lUserID">NET_DVR_Login_V30的返回值</param>
/// <param name="dwCommand">设备配置命令</param>
/// <param name="lChannel">通道号或者组号,不同的命令对应不同的取值
<param>
/// <param name="lpInBuffer">输入数据的缓冲指针</param>
/// <param name="dwInBufferSize">输入数据的缓冲长度(以字节为单位)</param>
/// <returns></returns>
bool NET_DVR_SetDVRConfig(int lUserID, uint dwCommand, int lChannel, IntPtr lpInBuffer, uint dwInBufferSize);

具体调用见下面操作示例

9.        日夜切换 (前端参数配置结构体中

1)        设备配置命令

3369; //IPC设置CCD参数配置
3368; //IPC获取CCD参数配置

2)        通道号或者组号

ChannelId//登录IPC设备时获取的通道号

3)        获取配置信息

Hik.NET_DVR_CAMERAPARAMCFG_EX cameraParamCfg = new Hik.NET_DVR_CAMERAPARAMCFG_EX();//实例化前端参数结构体
Int32 nSize = Marshal.SizeOf(cameraParamCfg );//获取结构体空间大小
IntPtr ptrCfg = Marshal.AllocHGlobal(nSize);//设置指针大小
Hik.NET_DVR_GetDVRConfig(_userId,3368, _channelId, ptrCfg, (UInt32)nSize, ref dwReturn);//获取配置信息的指针
cameraParamCfg = (Hik.NET_DVR_CAMERAPARAMCFG_EX)Marshal.PtrToStructure(ptrCfg, typeof(Hik.NET_DVR_CAMERAPARAMCFG_EX));//指针转换为数据结构
Marshal.FreeHGlobal(ptrCfg);//释放指针

4)        日夜切换命令

0白天
1夜晚
2自动

5)        日夜切换结构体

NET_DVR_DAYNIGHT struDayNight;/*日夜转换*/

6)        设置配置信息

Hik.NET_DVR_CAMERAPARAMCFG_EX cameraParamCfg=获取配置信息();
Int32 size = Marshal.SizeOf(cameraParamCfg);//获取结构体大小
IntPtr ptrCfg = Marshal.AllocHGlobal(size);//设置指针空间大小
cameraParamCfg.struDayNight.byDayNightFilterType = (byte)日夜切换命令;
Marshal.StructureToPtr(cameraParamCfg, ptrCfg, false);//结构体转换为指针
Hik.NET_DVR_SetDVRConfig(_userId, 3369, _channelId, ptrCfg, (uint)size);//设置参数
Marshal.FreeHGlobal(ptrCfg);//释放指针

10.    透雾切换(前端参数配置结构体中

1)        设备配置命令

3369; //IPC设置CCD参数配置
3368; //IPC获取CCD参数配置

2)        通道号或者组号

        ChannelId//登录IPC设备时获取的通道号

3)        获取配置信息

Hik.NET_DVR_CAMERAPARAMCFG_EX cameraParamCfg = new Hik.NET_DVR_CAMERAPARAMCFG_EX();//实例化前端参数结构体
Int32 nSize = Marshal.SizeOf(cameraParamCfg );//获取结构体空间大小
IntPtr ptrCfg = Marshal.AllocHGlobal(nSize);//设置指针大小
Hik.NET_DVR_GetDVRConfig(_userId,3368, _channelId, ptrCfg, (UInt32)nSize, ref dwReturn);//获取配置信息的指针
cameraParamCfg = (Hik.NET_DVR_CAMERAPARAMCFG_EX)Marshal.PtrToStructure(ptrCfg, typeof(Hik.NET_DVR_CAMERAPARAMCFG_EX));//指针转换为数据结构
Marshal.FreeHGlobal(ptrCfg);//释放指针

4)        透雾切换命令

0关闭
2开启

5)        透雾切换结构体

NET_DVR_DEFOGCFG struDefogCfg;//透雾参数

6)        设置配置信息

Hik.NET_DVR_CAMERAPARAMCFG_EX cameraParamCfg=获取配置信息();
Int32 size = Marshal.SizeOf(cameraParamCfg);//获取结构体大小
IntPtr ptrCfg = Marshal.AllocHGlobal(size);//设置指针空间大小
cameraParamCfg.struDefogCfg.byMode = (byte)透雾切换命令;
cameraParamCfg.struDefogCfg.byLevel = (byte)100;//透雾级别
Marshal.StructureToPtr(cameraParamCfg, ptrCfg, false);//结构体转换为指针
Hik.NET_DVR_SetDVRConfig(_userId, 3369, _channelId, ptrCfg, (uint)size);//设置参数
Marshal.FreeHGlobal(ptrCfg);//释放指针

11.    聚焦模式切换

1)        设备配置命令

        3306; //设置快球聚焦模式参数
        3305; //获取快球聚焦模式参数

2)        通道号或者组号

        ChannelId//登录IPC设备时获取的通道号

3)        获取配置信息

Hik.NET_DVR_FOCUSMODE_CFG focusModeCfg = new Hik.NET_DVR_FOCUSMODE_CFG();//实例化聚焦模式结构体
Int32 size = Marshal.SizeOf(focusModeCfg);//获取结构体大小
IntPtr ptrCfg = Marshal.AllocHGlobal(size);//设置指针空间大小
Hik.NET_DVR_GetDVRConfig(_userId, 3305, _channelId, ptrCfg, (UInt32)size, ref dwReturn);//获取聚焦模式信息的指针
focusModeCfg = (Hik.NET_DVR_FOCUSMODE_CFG)Marshal.PtrToStructure(ptrCfg, typeof(Hik.NET_DVR_FOCUSMODE_CFG));//指针转换为聚焦模式结构体
Marshal.FreeHGlobal(ptrCfg);//释放指针

4)        聚焦模式切换命令

0自动
1手动
2半自动

5)        聚焦模式切换结构体

NET_DVR_FOCUSMODE_CFG

6)        设置配置信息

Hik.NET_DVR_FOCUSMODE_CFG focusModeCfg = 获取配置信息();
Int32 size = Marshal.SizeOf(focusModeCfg);//获取结构体大小
IntPtr ptrCfg = Marshal.AllocHGlobal(size);//设置指针空间大小
focusModeCfg.byFocusMode = (byte)聚焦模式命令;
Marshal.StructureToPtr(focusModeCfg, ptrCfg, false);//结构体转换为指针
Hik.NET_DVR_SetDVRConfig(_userId, Hik.NET_DVR_SET_FOCUSMODECFG, _channelId, ptrCfg, (uint)size);//设置聚焦模式
Marshal.FreeHGlobal(ptrCfg);//释放指针

12.    OSD字符设置

1)        设备配置命令

1030;//获取叠加字符操作命令
1031;//设置叠加字符操作命令

2)        通道号或者组号

ChannelId//登录IPC设备时获取的通道号

3)        获取配置信息

Hik.NET_DVR_SHOWSTRING_V30 struShowStrCfg = new Hik.NET_DVR_SHOWSTRING_V30();//初始化叠加字符结构体
Int32 size = Marshal.SizeOf(struShowStrCfg);//获取结构体空间大小
IntPtr ptr = Marshal.AllocHGlobal(size);//设置指针大小
Hik.NET_DVR_GetDVRConfig(_userId, 1030, _channelId, ptr, (UInt32)size, ref dwReturn);//获取配置信息
struShowStrCfg = (Hik.NET_DVR_SHOWSTRING_V30)Marshal.PtrToStructure(ptr, typeof(Hik.NET_DVR_SHOWSTRING_V30));//指针转换为结构体
Marshal.FreeHGlobal(ptr);//释放指针

4)        叠加字符结构体

NET_DVR_SHOWSTRINGINFO struStringInfo;/* 要显示的字符内容 */

说明:设置叠加字符需要为其属性赋值。

5)        设置配置信息

Hik.NET_DVR_SHOWSTRING_V30 struShowStrCfg =获取叠加字符配置信息();
Int32 size = Marshal.SizeOf(struShowStrCfg);//获取结构体空间大小
IntPtr ptr = Marshal.AllocHGlobal(size);//设置指针空间大小
String osd = "海康智能监控视频一";
struShowStrCfg.struStringInfo[0].wShowString = 1;//1为显示,0为不显示
struShowStrCfg.struStringInfo[0].sString = osd;//叠加的字符串
struShowStrCfg.struStringInfo[0].wStringSize = (ushort)(osd.Length * 2);//字符串大小
struShowStrCfg.struStringInfo[0].wShowStringTopLeftX = 0;//坐标
struShowStrCfg.struStringInfo[0].wShowStringTopLeftY = 0;//坐标
Marshal.StructureToPtr(struShowStrCfg, ptr, false);//街头体转换为指针
Hik.NET_DVR_SetDVRConfig(_userId, 1031, _channelId, ptr, (UInt32)size);//设置配置信息
Marshal.FreeHGlobal(ptr);//释放指针

说明:可以叠加显示多个字符串。

13.    球机定位速度设置

1)        设备配置命令

3270;//获取PTZ基本参数信息
3271;//设置PTZ基本参数信息

2)        通道号或者组号

ChannelId//登录IPC设备时获取的通道号

3)        获取配置信息

Hik.NET_DVR_PTZ_BASICPARAMCFG basicParamCfg = new Hik.NET_DVR_PTZ_BASICPARAMCFG();//初始化PTZ基本参数结构体
Int32 size = Marshal.SizeOf(basicParamCfg);//获取结构体空间大小
IntPtr ptr = Marshal.AllocHGlobal(size);//设置指针大小
Hik.NET_DVR_GetDVRConfig(userId, 3270, _channelId, ptr, (UInt32)size, ref bytesReturned);//获取配置参数指针
basicParamCfg = (Hik.NET_DVR_PTZ_BASICPARAMCFG)Marshal.PtrToStructure(ptr, typeof(Hik.NET_DVR_PTZ_BASICPARAMCFG));//指针转换为结构体
Marshal.FreeHGlobal(ptr);//释放指针

4)        球机定位速度命令

1-8

5)        球机定位速度结构体

Hik.NET_DVR_PTZ_BASICPARAMCFG basicParamCfg

6)        设置配置信息

Hik.NET_DVR_PTZ_BASICPARAMCFG basicParamCfg = 获取配置信息();
Int32 size = Marshal.SizeOf(basicParamCfg);//获取结构体空间大小
IntPtr ptr = Marshal.AllocHGlobal(size);//设置指针空间大小
basicParamCfg.byPresetSpeed = (byte)速度值;
Marshal.StructureToPtr(basicParamCfg, ptr, false);//结构体转换为指针
Hik.NET_DVR_SetDVRConfig(userId, 3271, _channelId, ptr, size);//设置配置信息
Marshal.FreeHGlobal(ptr);//释放指针

14.    获取球机转动范围

1)        设备配置命令

294;//云台获取PTZ范围

2)        通道号或者组号

ChannelId//登录IPC设备时获取的通道号

3)        获取配置信息

Hik.NET_DVR_PTZSCOPE PTZScope = new Hik.NET_DVR_PTZSCOPE();//初始化球机范围信息结构体
Int32 size = Marshal.SizeOf(PTZScope);//获取结构体空间大小
IntPtr ptrScope = Marshal.AllocHGlobal(size);//设置指针空间大小
Hik.NET_DVR_GetDVRConfig(_userId, 294, _channelId, ptrScope, (UInt32)size, ref result);//获取配置信息
PTZScope = (Hik.NET_DVR_PTZSCOPE)Marshal.PtrToStructure(ptrScope, typeof(Hik.NET_DVR_PTZSCOPE));//指针转换为结构体
Marshal.FreeHGlobal(ptrScope);//释放指针

4)        球机转动范围结构体

Hik.NET_DVR_PTZSCOPE PTZScope

15.    球机定位

使用球机定位功能前,需先设置球机定位速度和获取球机转动范围,再根据球机转动范围信息来操作球机定位功能。

1)        设备配置命令

292;//云台设置PTZ位置
293;//云台获取PTZ位置

2)        通道号或者组号

ChannelId//登录IPC设备时获取的通道号

3)        获取配置信息

//用精简方式实现
Int32 size = Marshal.SizeOf(typeof(Hik.NET_DVR_PTZPOS));//获取球机位置信息结构体大小
IntPtr ptrPTZ = Marshal.AllocHGlobal(size);//设置指针空间大小
Hik.NET_DVR_GetDVRConfig(_userId, 293, _channelId, ptrPTZ, (UInt32)size, ref result);//获取球机位置配置信息
Hik.NET_DVR_PTZPOS PTZPos = (Hik.NET_DVR_PTZPOS)Marshal.PtrToStructure(ptrPTZ, typeof(Hik.NET_DVR_PTZPOS));//指针转换为结构体
Marshal.FreeHGlobal(ptrPTZ);//释放指针

4)        透雾切换结构体

Hik.NET_DVR_PTZPOS PTZPos

5)        设置配置信息

Hik.NET_DVR_PTZPOS PTZPos = 获取球机位置信息();
Int32 size = Marshal.SizeOf(PTZPos);//获取结构体空间大小
IntPtr ptr = Marshal.AllocHGlobal(size);//设置指针空间大小
PTZPos.wAction = 1;//-表示定位PTZ参数
//本结构体中的wAction参数是设置时的操作类型,因此获取时该参数无效。实际显示的PTZ值是获取到的十六进制值的十分之一,如获取的水平参数P的值是0x1750,实际显示的P值为175度;获取到的垂直参数T的值是0x0789,实际显示的T值为78.9度;获取到的变倍参数Z的值是0x1100,实际显示的Z值为110度。
String temp = "0x" + Convert.ToString(horAngle * 10);//实际显示的PTZ值是获取到的十六进制值的十分之一,所以需要把输入的数值乘以10再拼成十六进制字符串
PTZPos.wPanPos = Convert.ToUInt16(temp, 16);//转换为16进制水平角度
if (pitAngle >= 0)//判断俯仰角度的正负。由于零方位角的设置不同,会导致出现负的俯仰角度,所以处理方式不同
   PTZPos.wTiltPos = Convert.ToUInt16("0x" + Convert.ToString(pitAngle * 10), 16);
else
   PTZPos.wTiltPos = Convert.ToUInt16("0x" + Convert.ToString((pitAngle + 360) * 10), 16);
PTZPos.wZoomPos = Convert.ToUInt16("0x" + Convert.ToString(viewAngle * 10), 16);
Marshal.StructureToPtr(PTZPos, ptr, false);//结构体转换为指针
Hik.NET_DVR_SetDVRConfig(_userId, 292, _channelId, ptr, (uint)size);//设置配置
Marshal.FreeHGlobal(ptr);//释放指针

16.    零方位角控制

1)        设备配置命令

3283;// 零方位角控制

2)        通道号或者组号

ChannelId//登录IPC设备时获取的通道号

3)        零方位角控制命令

SET = 0,//设置
GOTO = 1,//调用
CLE = 2//清除

4)        零方位角控制结构体

Hik.NET_DVR_INITIALPOSITIONCTRL initialPositionCtrl

5)        设置控制信息

Hik.NET_DVR_INITIALPOSITIONCTRL initialPositionCtrl = new Hik.NET_DVR_INITIALPOSITIONCTRL();//初始化零方位角控制结构体
Int32 size = Marshal.SizeOf(initialPositionCtrl);//获取结构体空间大小
IntPtr ptr = Marshal.AllocHGlobal(size);//设置指针大小
initialPositionCtrl.dwSize = (uint)size;//结构体大小
initialPositionCtrl.dwChan = (uint)_channelId;//播放通道号
initialPositionCtrl.byWorkMode = (byte)command;//零方位角控制命令
Marshal.StructureToPtr(initialPositionCtrl, ptr, false);//结构体转换为指针
Hik.NET_DVR_RemoteControl(_userId, (uint)3283, ptr, (uint)size);//零方位角控制
Marshal.FreeHGlobal(ptr);//释放指针

17.    录像回放

此功能需要硬盘录像机支持。需要先登录NVR设备。在登录和获取数字通道号后,根据设备所对应的通道号操作相关IPC设备的一些基础功能。

1)        设备配置命令

1;//开始播放
2;//停止播放
3;//暂停播放
4;//恢复播放
5;//快放
6;//慢放
7;//正常速度
8;//单帧放
9;//打开声音
10;//关闭声音
11;//调节音量
12;//改变文件回放的进度
13;//获取文件回放的进度
14;//获取当前已经播放的时间(按文件回放的时候有效)
15;//获取当前已经播放的帧数(按文件回放的时候有效)
16;//获取当前播放文件总的帧数(按文件回放的时候有效)
17;//获取当前播放文件总的时间(按文件回放的时候有效)
20;//丢B帧
24;//设置码流速度
25;//保持与设备的心跳(如果回调阻塞,建议2秒发送一次)
26;//按绝对时间定位
27;//获取按时间回放对应时间段内的所有文件的总长度
29;//倒放切换为正放
30;//正放切换为倒放
32;//设置转封装类型
33;//正放切换为倒放

2)        初始化录像回放结构体

Hik.NET_DVR_VOD_PARA struVodPara = new Hik.NET_DVR_VOD_PARA();//初始化录像回放结构体
struVodPara.dwSize = (uint)Marshal.SizeOf(struVodPara);//获取结构体空间大小
struVodPara.struIDInfo.dwChannel = (uint)_ipChannelId; //数字通道号 Channel number,海康设备数字通道号从33开始  
//struVodPara.hWnd = this.pictureBoxVideo.Handle;//不使用回调获取回放视频时,使用此属性设置回放窗口句柄
 struVodPara.hWnd = IntPtr.Zero;//使用回调获取回放视频时,需如此设置回放窗口句柄
//设置回放的开始时间 Set the starting time to search video files
struVodPara.struBeginTime.dwYear = (uint)dateTimeStart.Value.Year;
struVodPara.struBeginTime.dwMonth = (uint)dateTimeStart.Value.Month;
struVodPara.struBeginTime.dwDay = (uint)dateTimeStart.Value.Day;
struVodPara.struBeginTime.dwHour = (uint)dateTimeStart.Value.Hour;
struVodPara.struBeginTime.dwMinute = (uint)dateTimeStart.Value.Minute;
struVodPara.struBeginTime.dwSecond = (uint)dateTimeStart.Value.Second;
//设置回放的结束时间 Set the stopping time to search video files
struVodPara.struEndTime.dwYear = (uint)dateTimeEnd.Value.Year;
struVodPara.struEndTime.dwMonth = (uint)dateTimeEnd.Value.Month;
struVodPara.struEndTime.dwDay = (uint)dateTimeEnd.Value.Day;
struVodPara.struEndTime.dwHour = (uint)dateTimeEnd.Value.Hour;
struVodPara.struEndTime.dwMinute = (uint)dateTimeEnd.Value.Minute;
struVodPara.struEndTime.dwSecond = (uint)dateTimeEnd.Value.Second;

3)        获取录像回放播放句柄

目前根据需求只实现根据录像起止时间范围播放录像视频,其他功能请参考SDK功能说明文档。

//按时间回放 Playback by time
_realHandle = Hik.NET_DVR_PlayBackByTime_V40(_NVRUserId, ref struVodPara);// struVodPara为录像回放结构体

4)        录像数据回调函数的使用

/// <summary>
/// 录像数据回调函数 
/// </summary>
/// <param name="lPlayHandle">当前的录像播放句柄</param>
/// <param name="dwDataType">数据类型</param>
/// <param name="pBuffer">存放数据的缓冲区指针</param>
/// <param name="dwBufSize">缓冲区大小</param>
/// <param name="pUser">用户数据</param>
public delegate void PlayDataCallBack_V40(Int32 lPlayHandle, UInt32 dwDataType, IntPtr pBuffer, UInt32 dwBufSize, IntPtr pUser);

此回调函数的使用方式与实时流回调函数的操作方式类似,都是获取视频流后,用播放库解码,返回角度信息和解码后的视频流。

Hik.NET_DVR_PlayBackControl_V40(_realHandle, 1, IntPtr.Zero, 0, IntPtr.Zero, ref iOutValue);//播放录像回放视频

5)        停止录像回放

Hik.NET_DVR_StopPlayBack(_realHandle)

18.    录像下载

1)        初始化录像下载结构体

Hik.NET_DVR_PLAYCOND struDownPara = new Hik.NET_DVR_PLAYCOND();//初始化录像下载结构体
struDownPara.dwChannel = (uint)_ipChannelId; //数字通道号 Channel number  
//设置下载的开始时间 Set the starting time
struDownPara.struStartTime.dwYear = (uint)dateTimeStart.Value.Year;
struDownPara.struStartTime.dwMonth = (uint)dateTimeStart.Value.Month;
struDownPara.struStartTime.dwDay = (uint)dateTimeStart.Value.Day;
struDownPara.struStartTime.dwHour = (uint)dateTimeStart.Value.Hour;
struDownPara.struStartTime.dwMinute = (uint)dateTimeStart.Value.Minute;
struDownPara.struStartTime.dwSecond = (uint)dateTimeStart.Value.Second;
//设置下载的结束时间 Set the stopping time
struDownPara.struStopTime.dwYear = (uint)dateTimeEnd.Value.Year;
struDownPara.struStopTime.dwMonth = (uint)dateTimeEnd.Value.Month;
struDownPara.struStopTime.dwDay = (uint)dateTimeEnd.Value.Day;
struDownPara.struStopTime.dwHour = (uint)dateTimeEnd.Value.Hour;
struDownPara.struStopTime.dwMinute = (uint)dateTimeEnd.Value.Minute;
struDownPara.struStopTime.dwSecond = (uint)dateTimeEnd.Value.Second;

2)        录像下载结构体

Hik.NET_DVR_PLAYCOND  struDownPara

3)        录像下载

录像下载后默认保存格式为mp4。

_realHandle = Hik.NET_DVR_GetFileByTime_V40(_NVRUserId, sVideoFileName, ref struDownPara);

Plat_SDK编程指南

海康平台可以挂载多个NVR设备,调用NVR下挂载的IPC设备时,只需要在海康平台的设备设置中找到其摄像头ID即可。

(一)    SDK的引用

具体引用方式,参见IPC_SDK的引用。由于海康平台技术人员提供的DLL文件过于繁杂,就不一一列出DLL的说明,详情参见IVMS_SDK文件夹目录。

(二)    C#程序调用DLL中的非托管函数方法

详情参见C#程序调用DLL中的非托管函数方法

(三)    SDK的调用

根据海康视频设备及平台使用的方案设计,只应用海康平台SDK的视频回调显示功能,其它功能暂不使用。对Plat_SDK的C#封装类见附件HikIVMS.cs,可参考此基础类文件进行程序功能编写。

1.        获取错误码

Plat_GetLastError(_userId);//获取错误码,userId为登录后获得的用户ID

使用方式:如果每个非托管函数方法返回结果不为0(正确),则调用此方法获取错误码。

例如:

Int32 ret = Plat_Init();
if (ret != 0)
   throw new HikIVMSException(Plat_GetLastError(_userId));// HikIVMSException为自定义异常调用类,用来解析错误码抛出异常

2.        登录

1)        用户注册

Int32 ret;
ret = Plat_Init();//platformSDK初始化
if (ret != 0)
   throw new HikIVMSException(Plat_GetLastError(_userId));
IntPtr ptrIP = Marshal.StringToHGlobalAnsi(uri.Host);//IP
IntPtr ptrUserName = Marshal.StringToHGlobalAnsi(a[0]);//用户名
IntPtr ptrPassword = Marshal.StringToHGlobalAnsi(a[1]);//密码
IntPtr ptrPort = Marshal.StringToHGlobalAnsi(uri.Port.ToString());//端口号
_userId = Plat_LoginCMS(ptrIP, ptrUserName, ptrPassword, ptrPort, 0);//登录
//释放指针
Marshal.FreeHGlobal(ptrIP);
Marshal.FreeHGlobal(ptrUserName);
Marshal.FreeHGlobal(ptrPassword);
Marshal.FreeHGlobal(ptrPort);

2)        用户注销

//如果存在播放的视频,也就是_streamHandle播放句柄>0,则先停止播放视频
Plat_StopVideo(_streamHandle);
Plat_LogoutCMS(_userId);//注销用户
Plat_Free();//释放SDK,必须执行

3.        预览

1)        播放预览

/// <summary>
/// 播放实时视频(开始预览/回放)
/// </summary>
/// <param name="url">预览或回放的播放路径,由Plat_QueryRealStreamURL或Plat_QueryRecordFile</param>
/// <param name="hWnd">播放窗口句柄,如果为空,则不播放</param>
/// <param name="userHandle">用户ID</param>
/// <param name="streamCallback">视频码流接收回调函数指针,如果回调函数为NULL则不给码流</param>
/// <returns>>=0成功,返回实时视频句柄,错误时返回-1</returns>
Int32 Plat_PlayVideo(String url, IntPtr hWnd, Int32 userHandle, fStreamCallback streamCallback, IntPtr user);

例如:

_streamHandle = Plat_PlayVideo(cameraId, IntPtr.Zero, _userId, callback, IntPtr.Zero);

注意:此函数的第一个参数,在应用中只需要传入摄像机ID即可。

2)        实时流回调函数的使用

/// <summary>
/// 实时流回调
/// </summary>
/// <param name="handle">Plat_PlayVideo返回的句柄</param>
/// <param name="data">接收视频码流数据缓冲区指针</param>
/// <param name="size">接收视频码流数据字节数</param>
/// <param name="user">用户数据</param>
public delegate void fStreamCallback(Int32 handle, IntPtr data, UInt32 size, IntPtr user, UInt32 dataType);

其调用方式与IPC_SDK中回调实时视频流的方式相同,都是调用播放库解码,然后返回角度信息和解码后的视频。详情参见IPC_SDK预览功能说明

示例如下:

private HikIVMS.fStreamCallback _callback;
private HikIVMS.DECCBFUN _displayCallback;//解码回调
private HikIVMS.ADDITIONDATACBFUN _additionDataDisplayCallback;//角度回调
 
if (_callback == null)
_callback = new HikIVMS.fStreamCallback(StreamCallback);
if (_displayCallback == null)
 _displayCallback = new HikIVMS.DECCBFUN(DecCallbackFUN);
if (_additionDataDisplayCallback == null)
 _additionDataDisplayCallback = new HikIVMS.ADDITIONDATACBFUN(AdditionDataCallBackFUN);
Hik.Play(cameraCode, _callback);

注意:Plat的回调函数与IPC回调函数中唯一的区别在于数据类型的定义不同,IPC的系统头数据类型为1,视频流数据类型为2;Plat系统头数据类型为0,视频流数据类型为1,仅此区别,其它相同。

2017-11-14 02:44:00 weixin_34368949 阅读数 144

正文

  一、截图

 

 

二、代码

        #region Member Variable

        
//登录标识
        private int lUserID = -1;
        
//预览标识
        private int lRealHandle = -1;

        
#endregion

        
#region Form

        
public frmMain()
        {
            InitializeComponent();
        }

        
private void frmMain_Load(object sender, EventArgs e)
        {
            HCNetSDK.NET_DVR_Init();
            HCNetSDK.NET_DVR_SetConnectTime(
50001);
        }

        
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            HCNetSDK.NET_DVR_Cleanup();
        }

        
#endregion

        
#region 功能

        
/// <summary>
        
/// 登录DVS并预览
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>
        private void btnOpen_Click(object sender, EventArgs e)
        {
            
#region New V30
            NET_DVR_DEVICEINFO_V30 dev 
= new NET_DVR_DEVICEINFO_V30();
            lUserID 
= HCNetSDK.NET_DVR_Login_V30("192.168.1.1"8000"admin""12345"out dev);
            
if (lUserID == -1)
            {
                MessageBox.Show(
string.Format("登录DVS失败,错误码: {0}", HCNetSDK.NET_DVR_GetLastError()), "登录DVS失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
                
return;
            }

            NET_DVR_CLIENTINFO clientinfo 
= new NET_DVR_CLIENTINFO();
            clientinfo.hPlayWnd 
= this.panel1.Handle;//视频窗口
            clientinfo.lChannel = 2// 通道号
            clientinfo.lLinkMode = 0;
            clientinfo.sMultiCastIP 
= "234.5.6.7";

            lRealHandle 
= HCNetSDK.NET_DVR_RealPlay_V30(lUserID, ref clientinfo, null1false);
            
if (lRealHandle == -1)
            {
                MessageBox.Show(
string.Format("播放失败,错误码: {0}", HCNetSDK.NET_DVR_GetLastError()));
                
return;
            }
            
#endregion
            
#region Old
            
//NET_DVR_DEVICEINFO dev = new NET_DVR_DEVICEINFO();
            
//lUserID = HCNetSDK.NET_DVR_Login("125.119.30.175", 8000, "admin", "12345", out dev);
            
//if (lUserID == -1)
            
//{
            
//    MessageBox.Show(string.Format("登录DVS失败,错误码: {0}", HCNetSDK.NET_DVR_GetLastError()), "登录DVS失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
            
//    return;
            
//}

            
//NET_DVR_CLIENTINFO clientinfo = new NET_DVR_CLIENTINFO();
            
//clientinfo.hPlayWnd = this.panel1.Handle;//视频窗口
            
//clientinfo.lChannel = 2; // 通道号
            
//clientinfo.lLinkMode = 0;
            
//clientinfo.sMultiCastIP = "234.5.6.7";

            
//lRealHandle = HCNetSDK.NET_DVR_RealPlay(lUserID, ref clientinfo);
            
//if (lRealHandle == -1)
            
//{
            
//    MessageBox.Show(string.Format("播放失败,错误码: {0}", HCNetSDK.NET_DVR_GetLastError()));
            
//    return;
            
//}
            #endregion
        }

        
/// <summary>
        
/// 停止预览并退出登录
        
/// </summary>
        
/// <param name="sender"></param>
        
/// <param name="e"></param>
        private void btnClose_Click(object sender, EventArgs e)
        {
            HCNetSDK.NET_DVR_StopRealPlay(lRealHandle);

            
#region New V30
            HCNetSDK.NET_DVR_Logout_V30(lUserID);
            
#endregion
            
#region Old
            
//如果不退出登录,下次开启拾音将失效
            
//HCNetSDK.NET_DVR_Logout(lUserID);
            #endregion

            
this.panel1.Invalidate(false);
        }

        
#endregion

    代码说明:

      1.  [#region New]与[#region Old]代码块分别是新旧版本的API,这里均测试通过。

      2.  需要把HCNetSDK.dll(这里事例使用版本为3.0.3.3)直接拷贝到bin\Debug下去,或者拷贝到项目工程根目录下,然后选中右键属性->复制到输出目录 选中始终复活或者如果较新则复制,重新编译即可。

 

  三、提醒

    提醒大家API有变动,请立即更新!

 

  五、后期维护 
 

    1.  2009-11-2

      也需要把PlayCtrl.dll加到的项目中来,否则可能出现登录成功,播放报错,显示错误代码64。


本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/586616,如需转载请自行联系原作者

2017-05-16 14:24:19 GG_SiMiDa 阅读数 2422

这是使用md格式写成,为了方便阅读我就直接放到博客上了

一.工作内容

  • 外面客户购买了我们的NVR产品,需要提供SDK包做二次开发
  • 解答客户对接SDK过程中遇到的问题
  • 解决SDK本身存在的bug
  • 根据新的需求增加接口

总结起来就是:提供SDK安装包、解答对接、解决bug、新增需求接口;

二.准备工作

为了快速顺利的完成上述工作,需要的准备工作如下:

  • 了解NVRSDK发展历程
  • 熟悉NSIS打包
  • 熟悉接口使用
  • 熟悉代码框架
  • 钻研代码细节
  • 新增需求
  • 发现代码异味(来自《重构》),不断优化

以上每一步我都会下文中给出详细的讲解。

三.NVRSDK发展历程

NVRSDK是使用我公司NVR的通信库,NVRSDK第一版接口以NVR_前缀,同时包含了通信库和解码库的功能,现在只有日本方还在使用,而且已经由VC6迁移到了VC9,方便使用现有的底层库。工程所在目录为20161115_NVR_V5R2_JP/NVR_VOB/32-nvrclient,除此外,第一版SDK不再维护,统一提供第二版的NVRSDK,以NET_NVR_前缀。NVRSDK2.x分离了解码功能,另外封装了解码库DecodeSDK。

DecodeSDK因为刚开始提供的是cpp接口,即类接口,外面已经有些项目在使用,所以至今仍然保留了其打包脚本(打包下节会说到)。后来因为外面项目有使用各种语言(e.g. JAVA、delphi、C#),所以封装成c接口,工程为decodesdk_C.vcproj。如果是新客户,现在一律提供C接口版本。

NVRSDK2.x和DecodeSDK最新的工程目录为:
20160914_NVR_V5R2_Develop/NVR_VOB/32-nvrclient/nvrsdk2.6。nvrsdk2.6意为2016年创建。

四.NSIS打包

NVRSDK工程的编译脚本在nvrsdk2.6目录下的nvrsdk_setup.bat,打包脚本在NSIS目录下,打包须知如下:

  • 版本号控制:版本号以日期为号,如2016年12月5号提供的版本号为2.6.12.05,
    需要修改nsi打包脚本的!define PRODUCT_VERSION “2.6.12.05”
  • 由于历史原因当前打包有三个:
    I. nvrsdk.nsi打包ch文件夹,里面放的是C接口的解码库;
    II. nvrsdkcpp.nsi打包ch(c++)文件夹,里面放的是C++接口的解码库;
    III. nvrsdkcpp_en.nsi打包en文件夹,里面放的头文件和demo使用英文改写,
    解码库是C++接口的;
  • dll目录下请将pdb文件打包进去,方便外面崩溃后dump诊断;
  • 每次改动版本变更后,请在history文件夹下做好记录和备份;

五.熟悉接口使用

这部分内容请参考头文件、帮助文档、demo。大致接口调用流程图:

Created with Raphaël 2.2.0初始化NET_NVR_SDKInitDS_Init登录NET_NVR_LoginSync获取设备列表NET_NVR_GetAllDevice实时浏览、录像回放、etc.登出NET_NVR_Logout反初始化NET_NVR_SDKCleanupDS_Quit

1. 初始化

一般库都有的初始化、反初始化、获取版本号、获取构建时间

- NET_NVR_SDKInit
- NET_NVR_SDKCleanup
- NET_NVR_GetSdkVersion
- NET_NVR_GetSdkBuildTime  

需要注意的是NET_NVR_SDKInit需要提供一个处理通知事件的回调函数,
客户比较感兴趣的几个通知事件是:

- NVR断链通知:SDK_NET_MSG_DISCONNECT_NVR
- 告警通知:SDK_NET_MSG_ALL_ALARM_NTY
- 前端设备状态变更通知:SDK_NET_MSG_DEV_STATUS_NTY
- 放像进度通知:SDK_NET_MSG_PLAYBACK_PROGRESS

2. 登录登出NVR

- NET_NVR_LoginSync
- NET_NVR_Logout

登录常见的错误码是#define SDK_NET_ERR_USER_OR_PSWD SKD_NET_ERR_BASE + 24
// 用户名或者密码错误

3. 实时浏览

Created with Raphaël 2.2.0开始获取连接NVR的本地IPNET_NVR_GetLocalIp获取本地空闲端口NET_NVR_GetLocalIdleStreamPort获取视频重传端口NET_NVR_GetEncoderVideoRetransPort创建解码器DS_CreateDecoder设置解码器数据源网络参数DS_SetNetParam开始解码播放DS_StartPlayStream申请实时码流NET_NVR_ApplyVideoStream停止实时码流NET_NVR_StopVideoStream销毁解码器 DS_FreeDecoder结束

4. 录像回放

录像回放的步骤与实时浏览类似,注意的是录像回放重传端口导致顺序有点区别:

Created with Raphaël 2.2.0开始获取连接NVR的本地IPNET_NVR_GetLocalIp获取本地空闲端口NET_NVR_GetLocalIdleStreamPort创建解码器DS_CreateDecoder申请录像码流NET_NVR_SingleRecordPlaybackSync获取录像重传端口NET_NVR_GetRecordRetransPort设置解码器数据源网络参数DS_SetNetParam开始解码播放DS_StartPlayStream停止录像码流NET_NVR_StopRecordPlayback销毁解码器 DS_FreeDecoder结束

录像查询(包括正常录像、告警录像、标签录像)相关接口:

- NET_NVR_QueryRecordSync
- NET_NVR_GetRecordItemCount
- NET_NVR_GetRecordItem
- NET_NVR_GetRecordAlarmCount
- NET_NVR_GetRecordAlarm
- NET_NVR_GetRecordTagCount
- NET_NVR_GetRecordTag

放像控制、录像下载、录像备份相关接口:

- NET_NVR_RecordPlaybackControl
- NET_NVR_DownloadRecord
- NET_NVR_StopDownloadRecord
- NET_NVR_RecordBackUp

5. PTZ云台控制

需要注意的是发送上下左右等移动命令后,只有当发送了停止命令(宏定义是eSDK_NET_PtzCommand_MOVESTOP)才会停止移动,界面开发时可以响应鼠标按下发送移动命令,鼠标弹起发送停止命令。

- NET_NVR_DevicePtzControl

6. NVR参数配置

包括基本参数、网络参数、NAT、串口、平台参数、系统时间、录像抓拍设置,etc.

- 获取NVR基本参数:NET_NVR_GetBasicSetting
- 设置NVR基本参数:NET_NVR_SetBasicSetting
- 获取NVR网络参数:NET_NVR_GetNetSetting
- 设置NVR网络参数:NET_NVR_SetNetSetting
- 获取NVR-NAT参数:NET_NVR_GetNatSetting
- 设置NVR-NAT参数:NET_NVR_SetNatSetting
- 获取NVR串口信息:NET_NVR_GetSerialPort
- 设置NVR串口信息:NET_NVR_SetSerialPort
- 获取NVR平台参数:NET_NVR_GetPlatfromSetting
- 设置NVR平台参数:NET_NVR_SetPlatfromSetting
- 获取NVR系统时间:NET_NVR_GetSystemTime
- 设置NVR系统时间:NET_NVR_SetSystemTime
- 获取录像抓拍参数:NET_NVR_GetRecordGrabSetting
- 设置录像抓拍参数:NET_NVR_SetRecordGrabSetting
- 获取邮箱服务:NET_NVR_GetEmailServer
- 设置邮箱服务:NET_NVR_SetEmailServer
- 测试邮箱服务:NET_NVR_CheckEmailServer

7. NVR系统管理

- 获取NVR版本信息:NET_NVR_GetVersion
- 获取NVR运行状态:NET_NVR_GetStatus
- 关机:NET_NVR_ShutDown
- 重启:NET_NVR_Reboot
- 恢复出厂:NET_NVR_RestoreFactory

8. 用户管理

- 增:NET_NVR_AddUser
- 删:NET_NVR_DeleteUser
- 改:NET_NVR_ModifyUser
- 查:NET_NVR_GetAllUserSync  

9. 日志管理

- NET_NVR_GetLog

10. 前端管理

- 增加前端设备:NET_NVR_AddDevice
- 删除前端设备:NET_NVR_DeleteDevice
- 获取前端设备列表:NET_NVR_GetAllDevice
- 获取所有前端设备基本信息:NET_NVR_GetAllDeviceBasicInfo
- 获取单个前端设备一般信息:NET_NVR_GetSingleDeviceInfo
- 获取设备能力集:NET_NVR_GetDeviceCapability
- 获取设备参数配置:NET_NVR_GetDeviceSetting
- 修改设备参数配置:NET_NVR_SetDeviceSetting
- 获取设备录像模式:NET_NVR_GetDeviceRecordMode
- 修改设备录像模式:NET_NVR_SetEncoderRecordMode

11. 告警管理

这部分涉及到告警联动较为复杂,告警分为系统告警(或者称为设备告警,e.g.设备上下线)、
业务告警(通过图像算法分析出的告警,e.g.移动侦测、图像遮蔽)

- 查询告警:NET_NVR_QueryAlarmData
- 获取系统告警处理:NET_NVR_GetServiceAlarmSetting
- 设置系统告警处理:NET_NVR_SetServiceAlarmSetting
- 获取告警输入开关状态:NET_NVR_GetAlarmInputSetting
- 设置告警输入开关状态:NET_NVR_SetAlarmInputSetting
- 获取业务告警联动: NET_NVR_GetServiceAlarmSetting
- 设置业务告警联动:NET_NVR_SetServiceAlarmSetting

12. 磁盘管理

包括盘组、磁盘、分区、配额信息

- 获取盘组表: NET_NVR_GetDiskGroupTable
- 设置盘组表: NET_NVR_SetDiskGroupTable
- 获取磁盘表: NET_NVR_GetDiskTable
- 获取磁盘运行时间: NET_NVR_GetDiskRunningTime
- 获取磁盘SMART信息: NET_NVR_GetDiskSmartInfo
- 获取分区表: NET_NVR_GetPartitionTable
- 修改分区设置: NET_NVR_ModifyPartitionSetting
- 格式化分区: NET_NVR_FormatPatition
- 获取配额表: NET_NVR_GetDiskQuotaTable
- 设置配额表: NET_NVR_SetDiskQuotaTable

13. 广播

- NET_NVR_StartCallorBroadcast
- NET_NVR_StopCallorBroadcast
- NET_NVR_SetCallorBroadcastVolumn
- NET_NVR_GetCallorBroadcastVolumn

14. 电视墙

- NET_NVR_LoadTvwall
- NET_NVR_StopTvwall

15. 智能模块

这一块还有待开发,目前已有的是查询智能分析图片接口、人流统计接口

- NET_NVR_QueryTracke
- NET_NVR_GetTrackeItemCount
- NET_NVR_GetTrackeItemInfo
- NET_NVR_StartStatistics
- NET_NVR_StopStatistics
- NET_NVR_GetStatisticsCount

16. 图片管理

- NET_NVR_CreatePictureDir
- NET_NVR_DeletePicture
- NET_NVR_UploadPicture
- NET_NVR_DownloadPicture
- NET_NVR_QueryPictureByDir

六.熟悉代码框架

nclib架构

NVRSDK建立在nclib的基础之上,nclib底层网络模块是用到了osp库,osp、nclib的学习案例可以参考王导写的,我这里只是简单的概述下。下面先贴出nclib的UML图:
nclib

响应流程图:

Created with Raphaël 2.2.0开始nvr消息入口点CNCIns::InstanceEntry消息派发 CNCInsCfg::DispatchEvent查找消息处理类CManagerDisp::FindEventDisp查找消息处理函数CXXXDisp::DispEvent消息处理函数CXXXDisp::OnYYY发送nclib消息IDispEvent::SendEvent/PostEventnclib消息处理函数CNCLib::m_pNclibMsgProc

osp将每条TCP连接当做一个节点来处理,每个连接使用一个节点号进行通信。在InstanceEntry这个入口点会收到对应节点来的消息,消息号在evnvr.h中,消息携带的结构体信息在nvrstruct.h中由业务组定义。通过InstanceEntry中通过GetInsID获取到节点号,进而获取到对应的CNCInsCfg指针,这个类中保存了两个重要的指针,分别是消息派遣类CManagerDisp和数据保存类CNCLibData。CManagerDisp中根据业务对消息进行了一个分类,以防止类过于臃肿庞大。BuildEventMap通过宏REG_EVENT2DISP将消息号和具体处理类映射起来,通过FindEventDisp函数在map表中根据消息号寻找到处理类,DispEvent中再通过宏REG_ON_MESSAGE将消息号由对应函数来处理。

请求流程图:

Created with Raphaël 2.2.0开始获取派发管理类CNCLib::GetManagerDisp获取具体派发接口类 CManagerDisp::GetXXXDispInterface具体业务IXXXDisp::DoSomething准备请求参数IXXXDisp::PostReqToNvr发出请求CNCInsCfg::PostReqToNvrosp发送消息::OspPost

请求流程相对来说比较清晰简单,需要注意的是在PostReqToNvr可以设置等待的响应消息,以此判定是同步消息请求,在CNCInsCfg::PostReqToNvr中调用BeginWaitMsg对请求进行加锁,直到接收到对应的响应CheckIsWaitMsg才会调用StopWaitMsg解锁,这时才可以进行下一个请求。

NvrSDK架构

NvrSDK2.x版本因为需要用作服务器端,接受多线程的考验,而nclib层不是多线程安全的,这就要求NvrSDK多线程安全机制,而NvrSDK正是通过加了一层任务队列实现的。关键的两个类正是CTaskManager和CTaskInfo。

CTaskManager

1、主任务队列m_pNvrTaskDeque

所有任务请求都将通过PushTask到主任务队列中

2、处理ready任务线程HandleTask

从主任务队列中取出ready状态任务,调用StartTask开始做任务

3、处理done任务线程HandleDoneTask

从主任务队列中取出done或者error状态任务,主要处理异步任务做完后回调、同步任务超时后释放

4、nty任务队列m_pNvrNtyTaskDeque

因为考虑到主动上报消息(告警通知、放像进度etc.)过多可能会影响主任务队列的处理效率,所以额外创建了一个nty任务队列

5、处理nty任务线程HandleNtyTask

一般需要调用外部回调

6、CNCLib::m_pNclibMsgProc入口点:

s32 CTaskManager::NclibMsgProc(KEDA_NVRSDK::TMsgItem &tMsgItem)
  	{
		CTaskManager* pTaskManager = CTaskManager::Instance();

		bool bDeal = FALSE;

		// 先处理放像进度、告警通知、ping通知(主动上报nty)
		if (tMsgItem.message == WM_NCLIB30_PLAY_PROGGRESS_NTY || 
			tMsgItem.message == WM_NCLIB_ALL_ALARM_NTY ||
			tMsgItem.message ==	WM_NCLIB_NVRTONC_PING_NTY ||
			tMsgItem.message == WM_NCLIB_STATISTICS_NTY ||
			tMsgItem.message == WM_NCLIB_REF_ALL_DEV_STA_NTY)
		{
			bDeal = pTaskManager->GetNtyTaskDeque()->DealNtyTask(tMsgItem);
			return bDeal;
		}

		// 轮询主任务队列中的nclibMsgProc
		bDeal = pTaskManager->GetTaskDeque()->DealTask(tMsgItem);
		if (bDeal)
			return bDeal;

		// 处理剩下的nty
		if (tMsgItem.wSerialNO == 0)
		{
			bDeal = pTaskManager->GetNtyTaskDeque()->DealNtyTask(tMsgItem);
			return bDeal;
		}

		SdkTraceWarning("nclibmsg not deal:message=%d\n", tMsgItem.message);
		return bDeal;
	}

7、PushTask

	BOOL CTaskManager::PushTask(ITaskInfo* pTaskInfo)
	{
		if (!m_pNvrTaskDeque->PushBackTask(pTaskInfo))
		{
			// 任务太多无法放入队列则丢掉
			CNvrSdkGlobal::Instance()->SetErrorCode(pTaskInfo->GetSerialNO(), SDK_NET_ERR_TASK_OVER);
			SdkTraceError("PushTask error:SDK_NET_ERR_TASK_OVER\n");
			delete pTaskInfo;
			return FALSE;
		}

		if (pTaskInfo->GetTaskExecuteMode() == ITaskInfo::EMTaskExecuteMode_Syn)
		{
			pTaskInfo->WaitSyn();
			SdkTraceDebug("DeleteSynTask %d\n", (int)(pTaskInfo->GetTaskFlag()));
			delete pTaskInfo; // 同步任务在等待结束后在此处删除任务
		}

		return TRUE;
	}

客户线程调用该接口将请求任务放入主任务队列,如果是同步任务会加锁,直到任务完成或错误或超时才会解锁。但是该锁只会阻塞住该客户线程,不会影响其它线程继续请求任务,所以NvrSDK实现了多线程处理任务并且安全。

CTaskInfo

OOD最重要的就是抽象类的设计,可以说一个框架搭的是否优秀,就是看它的抽象类设计的是否合理。抽象类就是定义了一种契约,约定了子类必须实现的接口,所以抽象类的接口是所有子类的共有特性和行为,不要把飞行的接口给到一个动物的抽象类,因为不是所有的动物都会飞,你可能会采取在爬行动物接口中覆盖飞行接口返回false,但这是一种愚蠢的设计。所以抽象类的每一个接口都应该深思熟虑是否适用于所有的子类。并且一定要遵守类的单一职责原则。

以这些标准来看CTaskInfo类还是过于臃肿了,身兼多职。即包含了多任务接口InsertTask、DelTask、DoNextTask等,又包含了同步任务和异步任务的划分,这点在后面的代码中可以发现极其繁琐,同时只有异步任务才需要用到回调接口来告诉客户任务处理的进度和结果,这些糅合在一起给实际的扩展和维护,可读性,都带来了不少的麻烦。因为不是所有的子类都是由多任务组合而成,有的已经可以确定是原子任务(不能再拆分的任务)了,而且大多数都是原子任务。原子任务和组合任务的关系有点类似控件与容器,容器可以容纳其它控件。在现有的需求下,我认为的任务抽象类应该如此纯洁:

class CTaskInfo{
public:
	CTaskInfo(TaskData tData) ;
	virtual ~CTaskInfo() ;

	// 具体任务子类需实现此方法调用IXXXDisp::DoSomething发出请求
	virtual bool doTask() = 0;
	// 实现此方法处理nclib响应消息
	virtual bool handleNclibMessage(TMsgItem tMsg) = 0;
	
	// 调用doTask,启动计时
	virtual bool startTask() ;
	
	// 任务状态变化后需要做的操作,我分为五种状态:准备态Ready、执行态Exec、完成态Done、错误态Error、子任务完成态SubDone(仅用于异步,可用来回调通知任务执行进度)
	virtual bool OnReady() ;
	virtual bool OnExec() ;
	virtual bool OnDone() ;
	virtual bool OnError() ;
	virtual bool OnSubDone() ;

	// 变化状态时调用对应OnStatus
	virtual bool setStatus(ETaskStatus eStatus) ;

	// 判断同步任务是否超时,返回对应状态
	virtual ETaskStatus getStatus() ;

	// 一些任务属性的get/set
	virtual DWORD getSerialNO() ;
	virtual ETaskFlag getTaskFlag() ;
	virtual WPARAM getWParam() ;
	virtual LPARAM getLParam() ;
	virtual void setParam(WPARAM wParam, LPARAM lParam) ;
	virtual int setErrCode() ;
	virtual void getErrCode() ;

protected:
	TaskData m_tData; // 任务创建时需要的数据
	DWORD m_dwSerialNO; // 任务流水号
	ETaskFlag m_eTaskFlag; // 任务标识
	WPARAM m_dwWParam;// 参数1
	LPARAM m_dwLParam;// 参数2
	ETaskStatus m_eTaskStatus; // 任务状态
	int m_iErrCode;// 错误码
	bool m_bAsync; // 是否是异步任务
};

接口我认为小写动词+大写名词的驼峰法是最能清晰表示的。
除了doTask(调用IXXXDisp中的接口发送任务请求)、handleNclibMessage(处理nclib消息),其它接口都可以在CTaskInfo中给出一般实现。所有继承自CTaskInfo的子类只需要实现doTask和handleNclibMessage即可。

而组合任务扩展自简单任务类:

class CTaskContainer : public CTaskInfo{
public:
	virtual ~CTaskContainer();
	
	// 重写startTask方法,来循环做子任务
	virtual bool startTask() ;
	
	bool addTask(TaskData tData);
	bool removeTask(CTaskInfo* pTask);
	CTaskInfo* getNextTask();
	
protected:
	vector<CTaskInfo*> m_vecTask;// 子任务组合
}

通过这样解释后,你应该能将NvrSdk的任务机制流程画出来了:

Created with Raphaël 2.2.0开始创建任务CTaskFactory::CreateTaskByTaskFlag将任务放入任务队列CTaskManager::PushTask处理任务CTaskManager::HandleTask开始任务(任务请求)ITaskInfo::StartTasknclib消息处理入口点CTaskManager::NclibMsgProc轮询任务队列nclib消息CNvrTaskDeque::DealTask由具体子类处理nclib消息(任务响应)ITaskInfo::nclibMsgProc处理并移除完成任务CTaskManager::HandleDoneTask结束

注意的是这个流程不是在一条线程中完成的,客户线程调用PushTask放入任务,如果是同步任务则阻塞等待,由HandleTask线程去取任务做任务进行驱动,最后由HandleDoneTask线程取出并移除完成的任务。

七. 钻研代码细节

这部分内容要求你对某个具体任务子类请求和响应消息有个大致了解,下面以登录任务为例:
登录任务相当其它get/set参数任务来说,相当比较复杂,它由多任务组成,包括TCP连接、版本验证、用户名密码验证、同步数据到本地。

/*====================================================================
函 数 名:NET_NVR_LoginSync
功    能:NVR登录(同步方式)
参    数:pSerialTSDK_NVRLogin - [in] TSDK_NVRLogin 结构的串行化字符串
线程安全:是
返 回 值:成功,返回NVR ID (大于等于1)
		  失败,返回SDK_NET_ERR_FAILED,通过NET_NVR_SDKGetLastError获取
		  错误码
说    明:
====================================================================*/
NET_NVR_API int _STDCALL NET_NVR_LoginSync(const char *pSerialTSDK_NVRLogin)
{
	SdkTraceDebug("NET_NVR_LoginSync被调用\n");
	CSDKSerial cSerial(pSerialTSDK_NVRLogin);

	if (pSerialTSDK_NVRLogin == NULL)
	{
		CNvrSdkGlobal::Instance()->SetErrorCode(CNvrSdkGlobal::Instance()->GetCurThreadId(), SDK_NET_ERR_INVALID_PARAMS);
		return SDK_NET_ERR_FAILED;
	}

	TSDK_NVRLogin tLoginInfo;
	int nRet = DeserializationParam((char*)pSerialTSDK_NVRLogin, tLoginInfo);
	if (nRet != 0)
	{
		CNvrSdkGlobal::Instance()->SetErrorCode(GetCurrentThreadId(), SDK_NET_ERR_PARMETER);
		return SDK_NET_ERR_FAILED;
	}

	if (tLoginInfo.m_byAddrType == 1)
	{
		string strIP;
		u16 wPort;
		if (!GetIpAndPortByUrl(tLoginInfo.m_abyNvrUrl, strIP, wPort))
		{
			CNvrSdkGlobal::Instance()->SetErrorCode(GetCurrentThreadId(), SDK_NET_ERR_PARMETER);
			return SDK_NET_ERR_FAILED;
		}
		else
		{
			tLoginInfo.m_dwNVRIp = inet_addr(strIP.c_str());
			tLoginInfo.m_wLogInPort = wPort;
		}
	}

	CNvrData* pNvrData = CNvrSdkGlobal::Instance()->NewNvrData(tLoginInfo);
	if (pNvrData == NULL)
	{
		CNvrSdkGlobal::Instance()->SetErrorCode(GetCurrentThreadId(), SDK_NET_ERR_LOGIN_NVR_OVER);
		return SDK_NET_ERR_FAILED;	
	}

	int nNvrID = pNvrData->GetInsId();
	CNVR* pNvr = CNvrSdkGlobal::Instance()->CreateNvr(nNvrID);
	if (pNvr == NULL)
	{
		CNvrSdkGlobal::Instance()->SetErrorCode(CNvrSdkGlobal::Instance()->GetCurThreadId(), SDK_NET_ERR_NVR_NOT_EXIST);
		return SDK_NET_ERR_FAILED;
	}

	TUserInfo tUser;
	strcpy(tUser.m_achUserName, tLoginInfo.m_abyNvrUserName);
	strcpy(tUser.m_achLoginPwd, tLoginInfo.m_abyNvrPwd);
	TCREATETASKDATA tCreateTaskData;
	tCreateTaskData.emExecuteMode = ITaskInfo::EMTaskExecuteMode_Syn;
	tCreateTaskData.emTaskFlag = EMTaskFlag_LogIn;
	tCreateTaskData.nNvrID = nNvrID;
	tCreateTaskData.pSynParam1 = &tUser;
	tCreateTaskData.dwThreadId = GetCurrentThreadId();
	nRet = pNvr->Login(&tCreateTaskData);
	if (nRet != 0)
	{
		DWORD dwErr = CNvrSdkGlobal::Instance()->GetErrorCode(CNvrSdkGlobal::Instance()->GetCurThreadId());
		SdkTraceError("NET_NVR_LoginSync failed!ErrCode:%d\n", dwErr);
		CNvrSdkGlobal::Instance()->SetErrorCode(CNvrSdkGlobal::Instance()->GetCurThreadId(), dwErr);
		return SDK_NET_ERR_FAILED;
	}

	return nNvrID;
}
  1. 可以看到先是构造CSDKSerial对象,通过析构的方式保证序列化字符串被释放。判断输入参数是否有效合理,这是一个良好的习惯;
  2. CNvrSdkGlobal::Instance()->NewNvrData(tLoginInfo)实际是调用底层nclib去分配一个可用NVRID号;
  3. 然后调用CNvrSdkGlobal::Instance()->CreateNvr(nNvrID)创建CNVR对象;
  4. 填充TCREATETASKDATA任务数据,最终调用CNVR::Login,实际是将CTaskLogin放入任务队列中;
  5. 在CTaskLogin的SetParam中组合子任务CTaskConnect;
  6. CTaskConnect::DoTask中调用CNCLib::ConnectNVR进行TCP连接,在CTaskConnect::nclibMsgProc中处理WM_NCLIB_CONNECTNVR_NTY连接结果通知;
  7. 在CTaskLogin::DoTask中调用了CNCInsCfg::Login进行版本和用户名密码验证,在CTaskLogin::nclibMsgProc中处理WM_NCLIB_LOGIN_RES验证结果响应;
  8. 如果验证成功继而调用CNCLib::SyncLibData同步数据到本地,不断处理WM_NCLIB_SYNCDATA_STATE_NTY同步数据通知,直到SYNCDATA_SUCCEED同步数据全部完成。

这是登录任务比较繁琐,实际大多get/set参数任务都是很简单,以NET_NVR_GetEmailServer获取邮箱服务为例:

NET_NVR_API int _STDCALL NET_NVR_GetEmailServer(int nNvrID, void *pTSDK_EMailServer){
	if (!CNvrSdkGlobal::Instance()->IsValidInsId(nNvrID) || pTSDK_EMailServer == NULL)
	{
		return SDK_NET_ERR_INVALID_PARAMS;
	}

	TCREATETASKDATA tCreateTaskData;
	tCreateTaskData.emTaskFlag = EMTaskFlag_GetEmailServer;
	tCreateTaskData.emExecuteMode = ITaskInfo::EMTaskExecuteMode_Syn;
	tCreateTaskData.nNvrID = nNvrID;
	tCreateTaskData.pSynParam1 = pTSDK_EMailServer;
	CNVR* pNvr = CNvrSdkGlobal::Instance()->GetNVR(nNvrID);
	return pNvr->GetEmailServer(&tCreateTaskData);	
}
  1. 验证nNvrID、输入参数的有效性;
  2. 通过nNvrID获取到CNVR对象指针,调用对应接口GetEmailServer;
  3. 在CTaskGetEmailServer::DoTask中获取到INVRDisp指针,调用GetSystemParam,因为是直接从nclib本地获取的数据,所以都没有响应,CTaskGetEmailServer::nclibMsgProc中直接返回FALSE即可,即不处理任何响应。

八. 新增需求

  1. 业务组会在evnvr.h中添加请求和响应消息号,在nvrstruct.h中添加请求和响应消息结构体。
  2. 我们组(应用组)需要在msgdefine.h中添加nclib消息号
  3. 在对应分类IXXXDisp::BuildEventMap中注册派发类,在IXXXDisp::DispEvent中调用OnYYY的处理函数。
  4. 添加具体任务子类,实现doTask和handleNclibMessage
  5. 在CTaskFactory::CreateTaskByTaskFlag中添加任务产品的创建
  6. 在nvrsdk头文件中给出对外函数、结构体
  7. 在nvrsdk源文件中给出实现,即填充创建任务需要的数据

九. 优化代码

不要害怕去修改原代码,里面肯定是有很多烂代码的(包括我也可能埋下了很多坑,如果你发现了尽管吐槽好了,反正我也听不见),即使是以前良好的设计也有可能在需求变更下变得不合理了。只有通过不断的优化才能保证现在框架的可扩展性和稳定性。

我说下我发现的几处代码异味,有的我已经做出了优化,但有的还未来得及,如果你有时间可以尝试去优化,在这个过程中,你设计类和处理业务逻辑的能力都会得到很大的提升的。

  • CTaskInfo类可优化,但是现在具体任务子类已经快有上百个,改动抽象类修改量太大。
  • CTaskManager类还有优化余地,但也是受限于CTaskInfo类给出的接口,之前有过多线程多任务只使用一把同步任务锁导致多线程登录超时问题;
  • CNvrSdkGlobal严重违反单一职责原则,应该具体细分,之前就有过一把锁滥用导致死锁的问题;
  • CNVR类可以说是这个SDK中最大的败笔,本应先有对外的C++类接口,再封装成C接口,使用NVRID去映射对应的CNVR类,这个可以参考DecodeSDK的C接口实现,就是使用nDecoderID映射IDecoderSdk类。这个相当来说可以去优化改动,重新设计CNVR接口和实现,只是nvrsdk源文件实现需要调用CNVR接口,而现在C接口过多,所以修改量也不小;
  • 结构体序列化SerializeParam,这个是为了确保客户给的参数是对应的结构体,但我认为是多此一举,建议可以去掉。首先调用繁琐,还有之前就因为用于多线程中,有两处释放序列化字符串导致bug,还有如果接口实现中忘记添加CSDKSerial释放序列化字符串,会导致无序列化字符串可用;
  • 很多代码冗余,命名不规范,建议看看《代码简洁之道》

十.解码库简单介绍

解码库DecodeSDK现在统一提供C接口,解码数据源可以通过DS_SetNetParam设定为本地网络端口,也可以调用DS_PushRtpToDecode接口推送Rtp包。DS_CreateDecoder创建解码器时可以指定窗口句柄进行播放,如果仅用作回调不解码请将bOnlyFrmCB参数设为TRUE,这样不会创建解码线程和解码数据缓存,能大大减少CPU和内存占用。

解码库接口的一般调用流程图:

Created with Raphaël 2.2.0初始化解码库DS_Init创建解码器DS_CreateDecoder设置网络参数DS_SetNetParam开始解码播放DS_StartPlayStream停止解码播放DS_StopPlayStream释放解码器DS_FreeDecoder释放解码库DS_Quit

此外还有一些好用的接口:

  • 视频帧回调:DS_VideoFrameCB
  • 音频帧回调:DS_AudioFrameCB
  • YUV数据回调:DS_YuvDataCB(DS_CreateDecoder时需要解码
  • 抓拍:DS_PicSnapshot
  • 开始本地录像:DS_StartLocalRecord
  • 停止本地录像:DS_StopLocalRecord

解码库如果客户使用有问题,处理起来比较棘手,涉及到媒体组、网传组、解码驱动库。我们可以使用telnet日志进行一个初始的判断:

telnet 127.0.0.1 2600		打开telnet2600端口
pdinfo 8		打印解码日志
pdinfo 255 	    打印网传日志
decodershow		解码统计信息

十一. 常见客户问题记录

1、登录60024:用户名或密码错误,亦或版本认证失败;
2、登录60014:网络ping不通导致;
3、60178:任务超时 ;
4、60001:入参有误;
5、NET_NVR_GetLastError()获取当前线程最后一次错误,目前仅登录、申请实时码流、申请录像码流接口用到;
6、由于解码库使用的默认四字节对齐(底层解码库是四字节对齐),网络库是一字节 对齐,所以使用其它开发语言定义头文件的请注意;(delphi的demo在交接的文件夹中可以找到
7、不安装打包,没拷贝解码驱动库,导致有码流无画面现象;
8、dxdiag诊断音频服务未启用,导致音频播放失败;