Bootstrap

使用海康威视的SDK,利用WPF设计一个网络摄像头的显示控制程序(上位机)

目录

一、下载海康威视的SDK

1.1 关于SDK

1.2 关于API

1.3 关于DLL

1.4 下载海康威视的SDK

1.5 关于网络高清摄像机

二、对下载的SDK进行调试

三、利用Visual Studio打开项目的解决方案

3.1 Visual Studio 2022下载

3.2 关于句柄

四、打开WPF进行ui页面设计

4.1 关于WindowsFormsHost

4.2 准备工作

4.2 xaml设计

4.3 C# 后台逻辑代码

4.4 功能实现

4.4.1 登录

4.4.2 预览

4.4.3 抓图

4.4.4 通道配置

4.4.5 退出

4.5 小改动

五、关于延迟

1. 设置预览数据,设置SDK中播放库显示缓冲区最大帧数

2. 用python的Demo实验。


一、下载海康威视的SDK

1.1 关于SDK

软件开发工具包(Software Development Kit,SDK )一般都是一些软件工程师为特定的软件包软件框架硬件平台、操作系统等建立应用软件时的开发工具的集合。

软件开发工具广义上指辅助开发某一类软件的相关文档、范例和工具的集合

软件开发工具包是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等创建应用软件的开发工具的集合,一般而言SDK即开发Windows平台下的应用程序所使用的SDK。它通过编译器、调试器、软件框架等来促进应用程序的创建。它可以简单的为某个程序设计语言提供应用程序接口API的一些文件,但也可能包括能与某种嵌入式系统通讯的复杂的硬件。一般的工具包括用于调试和其他用途的实用工具。SDK还经常包括示例代码、支持性的技术注解或者其他的为基本参考资料澄清疑点的支持文档。

1.2 关于API

API(Application Programming Interface,应用编程接口)其实就是操作系统留给应用程序的一个调用接口,应用程序通过调用操作系统的API而使操作系统去执行应用程序的命令(动作)。其实早在DOS时代就有API的概念,只不过那个时候的API是以中断调用的形式(INT 21h)提供的,在DOS下跑的应用程序都直接或间接的通过中断调用来使用操作系统功能,比如将AH置为30h后调用INT 21h就可以得到DOS 操作系统的版本号。而在Windows中,系统API是以函数调用的方式提供的。同样是取得操作系统的版本号,在Windows中你所要做的就是调用GetVersionEx()函数

可以这么说,DOSAPI是“Thinking in汇编语言”的,而Windows API则是“Thinking in 高级语言”的。

DOSAPI是系统程序的一部分,他们与系统一同被载入内存并且可以通过中断矢量表找到他们的入口,那么什么是Windows API呢?要说明白这个问题就不得不引入下面要介绍得这个概念——DLL

1.3 关于DLL

DLL,即Dynamic Link Library(动态链接库)。在Windows环境下含有大量.dll格式的文件,这些文件就是动态链接库文件,其实也是一种可执行文件格式。跟.exe文件不同的是,.dll文件不能直接执行,通常由.exe在执行时装入,内含有一些资源以及可执行代码等。其实Windows的三大模块就是以DLL的形式提供的(Kernel32.dllUser32.dllGDI32.dll),里面就含有了API函数的执行代码。为了使用DLL中的API函数,必须要有API函数的声明(.h)和其导入库(.lib),导入库可以先这样理解,导入库是为了在DLL中找到API的入口点而使用的。

为了使用API函数,我们就要有跟API所对应的.h和.lib文件,而SDK正是提供了一整套开发Windows应用程序所需的相关文件、范例和工具的“工具包”。

SDK包含了使用API的必需资料,所以也常把仅使用API来编写Windows应用程序的开发方式叫做“SDK编程”。而API和SDK是开发Windows应用程序所必需的东西,所以其它编程框架和类库都是建立在它们之上的,比如VCL和MFC,虽然比起“SDK编程”来有着更高的抽象度,但这丝毫不妨碍在需要的时候随时直接调用API函数。

1.4 下载海康威视的SDK

海康威视的SDK下载地址:海康威视下载地址

1.5 关于网络高清摄像机

本次实验用到的是安佳威视的网络摄像机

二、对下载的SDK进行调试

下载完成后获得的文件如下图所示,阅读帮助文档,将要求的DLL配置文件,复制到Demo示例下的bin文件夹下。(可以傻瓜复制,把库文件夹中的内容全部复制过去)

打开Demo示例,找到C#语言开发示例的Demo

 打开2-实时预览示例代码二,找到bin文件下的NVRCsharpDemo.exe,执行查看是否能正常使用

运行结果如下

三、利用Visual Studio打开项目的解决方案

3.1 Visual Studio 2022下载

 打开项目的解决方案,看到海康威视用Winform框架进行ui设计

 3.2 关于句柄

句柄(Handle)是一个用来标识对象或者项目的标识符,可以用来描述窗体、文件等,值得注意的是句柄不能是常量 。

Windows之所以要设立句柄,根本上源于内存管理机制的问题,即虚拟地址。简而言之数据的地址需要变动,变动以后就需要有人来记录、管理变动,因此系统用句柄来记载数据地址的变更。在程序设计中,句柄是一种特殊的智能指针,当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄 。

四、打开WPF进行ui页面设计

4.1 关于WindowsFormsHost

WindowFormsHost控件是WPF中与WinForms集成的控件。它允许您将现有的WinForms控件嵌入到WPF应用程序中,以利用它们的功能。WindowFormsHost控件工作原理是将WinForms控件作为其子元素添加到WPF的逻辑树中。在运行时,WinForms控件被呈现在WPF窗口中。

使用WindowFormsHost控件时,首先需要添加对System.Windows.Forms和System.Windows.Forms.Integration命名空间的引用。然后,创建一个WindowFormsHost控件,并将要嵌入的WinForms控件设置为其Child属性。

4.2 准备工作

将下载的SDK中的CHCNetSDK.cs、IPChannelConfig.cs、IPChannelConfig.Designer.cs、IPChannelConfig.resx、PlayCtrl.cs文件复制到你当前项目解决方案的同级目录下。

4.2 xaml设计

<Window x:Class="project2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:project2"
        xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="550"
        Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="200"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        
        <WrapPanel Background="LightYellow">
            <Label Height="30"
                   Width="90"
                   HorizontalAlignment="Center"
                   HorizontalContentAlignment="Center"
                   VerticalContentAlignment="Center"
                   Name="Label1"
                   Content="设备IP:"
                   FontFamily="22"
                   Margin="10"></Label>
            <TextBox Height="30"
                     Width="120"
                     HorizontalAlignment="Center"
                     VerticalAlignment="Center"                     
                     Name="TextBoxIP"
                     Text="192.168.1.123"
                     FontSize="14"
                     Margin="10"></TextBox>                        
            <Label Height=" 30"
                   Width="90"
                   HorizontalContentAlignment="Center"
                   VerticalContentAlignment="Center"
                   Content="端口号:"
                   FontFamily="22"
                   Margin="10"></Label>
            <TextBox Height="30"
                       Width="120"
                       Name="TextBoxPort"
                       Text="8000"
                       FontSize="14"
                       Margin="10"></TextBox>
            <Label Height=" 30"
                   Width="90"
                   HorizontalContentAlignment="Center"
                   VerticalContentAlignment="Center"
                   Content="用户名:"
                   FontFamily="22"
                   Margin="10"></Label>
            <TextBox Height="30"
                       Width="120"
                       Name="TextBoxUserName"
                       Text="admin"
                       FontSize="14"                       
                       Margin="10"></TextBox>
            <Label Height=" 30"
                   Width="90"
                   HorizontalContentAlignment="Center"
                   VerticalContentAlignment="Center"
                   Content="密码:"
                   FontFamily="22"
                   Margin="10"></Label>
            <PasswordBox Height="30"
                         Width="120"
                         Name="TextBoxPassword"
                         FontSize="14"
                         PasswordChar="*"
                         Margin="10"
                         Password="123456"
                         BorderBrush="Black"></PasswordBox>
        </WrapPanel>

        <WrapPanel Grid.Column="0" Grid.Row="1">          
                        
            <CheckBox Width="130"
                      Height="30"
                      Name="CheckBoxHiDDNS"
                      IsChecked="False"
                      Content="HiDDNS域名登录"                      
                      Click="checkBoxHiDDNS_Checked"
                      Margin="10">
            </CheckBox>
            <ComboBox Height="30"
                      Width="90"
                      Name="ComboBoxView">
                <ComboBoxItem Content="直接预览" 
                              HorizontalAlignment="Center"
                              VerticalAlignment="Center"
                              HorizontalContentAlignment="Center"
                              VerticalContentAlignment="Center"
                              IsSelected="True"></ComboBoxItem>
                <ComboBoxItem Content="间接预览"
                              HorizontalAlignment="Center"
                              VerticalAlignment="Center"
                              HorizontalContentAlignment="Center"
                              VerticalContentAlignment="Center"></ComboBoxItem>
            </ComboBox>
            <!--<WindowsFormsHost Width="230"
                              Height="150"
                              IsEnabled="True"                              
                              Margin="10">
                <forms:ListView 
                    x:Name="ListViewIPChannel"
                    
                    MouseClick="ListViewIPChannel_MouseClick"
                                View="List">
                    <forms:ListView.Items>                      

                    </forms:ListView.Items>
                </forms:ListView>
            </WindowsFormsHost>-->

            <ListView Height="150"
                      Width="230"
                      x:Name="ListViewIPChannel"
                      MouseDoubleClick="ListViewIPChannel_MouseDoubleClick"
                      Margin="10"
                      SelectedIndex="0">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="通道"
                                        x:Name="ColumnHeader1"
                                        Width="110"
                                        DisplayMemberBinding="{Binding str1}"></GridViewColumn>
                        <GridViewColumn Header="状态"
                                        x:Name="ColumnHeader2"
                                        Width="110"
                                        DisplayMemberBinding="{Binding str2}"></GridViewColumn>
                    </GridView>
                </ListView.View>
            </ListView>

            <Button Height="40"
                    Width="100"
                    Content="登录"
                    Name="BtnLogin"
                    Margin="10"
                    Click="BtnLogin_Click"></Button>
            <Button Height="40"
                    Width="100"
                    Content="开始预览"
                    Name="BtnPreview"
                    Click="BtnPreview_Click"
                    Margin="10"></Button>            
        </WrapPanel>
        <WrapPanel Grid.Column="1" Grid.RowSpan="2">
            <WindowsFormsHost Width="500"
                              Height="400"
                              IsEnabled="False"
                              Margin="10">
                <forms:PictureBox x:Name="RealPlayWnd"
                                  BorderStyle="None"
                                  Dock="Fill" />
            </WindowsFormsHost>
            <ComboBox Height="30"
                      Width="100"
                      Text="BMP抓图"
                      Name="ScreenshotBoxView"
                      SelectedValuePath="BMP"
                      Margin="10">
                <ComboBoxItem Content="BMP抓取"
                              IsSelected="True"
                              Tag=""></ComboBoxItem>
                <ComboBoxItem Content="JPG抓取"></ComboBoxItem>
            </ComboBox>
            <Button Height="30"
                    Width="100"
                    Content="抓图"
                    Name="ButtonCatch"
                    Click="ButtonCatch_Click"
                    Margin="10"></Button>
            <Button Height="30"
                    Width="100"
                    Content="刷新"
                    Name="BtnRefresh"
                    Click="BtnRefresh_Click"
                    Margin="10"></Button>
            <Button Height="30"
                    Width="100"
                    Content="Exit"
                    Name="ButtonExit"
                    Click="ButtonExit_Click"
                    Margin="10"></Button>
        </WrapPanel>
    </Grid>
</Window>

 4.3 C# 后台逻辑代码

using NVRCsharpDemo;
using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.IO;
using static NVRCsharpDemo.CHCNetSDK;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using System.Reflection.Emit;


namespace project2
{
    public class data
    {
        public data(string str1,string str2)
        {
            this.str1 = str1;
            this.str2 = str2;
        }
        public string str1 { get; set; }
        public string str2 { get; set; }
    }
    

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        private bool m_bInitSDK = false;
        private bool m_bRecord = false;
        private uint iLastErr = 0;
        private Int32 m_lUserID = -1;
        private Int32 m_lRealHandle = -1;
        private string str1;
        private string str2;
        private Int32 i = 0;
        private Int32 m_lTree = 0;
        private string str;
        private long iSelIndex = 0;
        private uint dwAChanTotalNum = 0;
        private uint dwDChanTotalNum = 0;
        private Int32 m_lPort = -1;
        private IntPtr m_ptrRealHandle;
        private int[] iIPDevID = new int[96];
        private int[] iChannelNum = new int[96];

        private CHCNetSDK.REALDATACALLBACK RealData = null;
        public CHCNetSDK.NET_DVR_DEVICEINFO_V30 DeviceInfo;
        public CHCNetSDK.NET_DVR_IPPARACFG_V40 m_struIpParaCfgV40;
        public CHCNetSDK.NET_DVR_STREAM_MODE m_struStreamMode;
        public CHCNetSDK.NET_DVR_IPCHANINFO m_struChanInfo;
        public CHCNetSDK.NET_DVR_PU_STREAM_URL m_struStreamURL;
        public CHCNetSDK.NET_DVR_IPCHANINFO_V40 m_struChanInfoV40;
        private PlayCtrl.DECCBFUN m_fDisplayFun = null;
        public delegate void MyDebugInfo(string str);
        public MainWindow()
        {
            InitializeComponent();
            m_bInitSDK = CHCNetSDK.NET_DVR_Init();
            if (m_bInitSDK == false)
            {
                System.Windows.MessageBox.Show("NET_DVR_Init error!");
                return;
            }
            else
            {
                //保存SDK日志 To save the SDK log
                CHCNetSDK.NET_DVR_SetLogToFile(3, "C:\\SdkLog\\", true);

                ComboBoxView.SelectedIndex = 0;

                for (int i = 0; i < 64; i++)
                {
                    iIPDevID[i] = -1;
                    iChannelNum[i] = -1;
                }
            }
        }

        public void DebugInfo(string str)
        {
            if (str.Length > 0)
            {
                str += "\n";
                // TextBoxInfo.AppendText(str);
                System.Windows.MessageBox.Show(str);
            }
        }

        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnLogin_Click(object sender, EventArgs e)
        {
            if (m_lUserID < 0)
            {
                string DVRIPAddress = TextBoxIP.Text; //设备IP地址或者域名 Device IP
                Int16 DVRPortNumber = Int16.Parse(TextBoxPort.Text);//设备服务端口号 Device Port
                string DVRUserName = TextBoxUserName.Text;//设备登录用户名 User name to login
                string DVRPassword = TextBoxPassword.Password;//设备登录密码 Password to login

                
                if (CheckBoxHiDDNS.IsChecked == true)
                {
                    byte[] HiDDNSName = System.Text.Encoding.Default.GetBytes(TextBoxIP.Text);
                    byte[] GetIPAddress = new byte[16];
                    uint dwPort = 0;
                    if (!CHCNetSDK.NET_DVR_GetDVRIPByResolveSvr_EX("www.hik-online.com", (ushort)80, HiDDNSName, (ushort)HiDDNSName.Length, null, 0, GetIPAddress, ref dwPort))
                    {
                        iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                        str = "NET_DVR_GetDVRIPByResolveSvr_EX failed, error code= " + iLastErr; //域名解析失败,输出错误号 Failed to login and output the error code
                        DebugInfo(str);
                        return;
                    }
                    else
                    {
                        DVRIPAddress = System.Text.Encoding.UTF8.GetString(GetIPAddress).TrimEnd('\0');
                        DVRPortNumber = (Int16)dwPort;
                    }
                }

                //登录设备 Login the device
                m_lUserID = CHCNetSDK.NET_DVR_Login_V30(DVRIPAddress, DVRPortNumber, DVRUserName, DVRPassword, ref DeviceInfo);
                if (m_lUserID < 0)
                {
                    iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                    str = "NET_DVR_Login_V30 failed, error code= " + iLastErr; //登录失败,输出错误号 Failed to login and output the error code
                    DebugInfo(str);
                    return;
                }
                else
                {
                    //登录成功
                    DebugInfo("NET_DVR_Login_V30 succ!");
                    BtnLogin.Content = "退出登录";

                    dwAChanTotalNum = (uint)DeviceInfo.byChanNum;
                    dwDChanTotalNum = (uint)DeviceInfo.byIPChanNum + 256 * (uint)DeviceInfo.byHighDChanNum;
                    if (dwDChanTotalNum > 0)
                    {
                        InfoIPChannel();
                    }
                    else
                    {
                        for (i = 0; i < dwAChanTotalNum; i++)
                        {
                            ListAnalogChannel(i + 1, 1);
                            iChannelNum[i] = i + (int)DeviceInfo.byStartChan;
                        }

                        //ComboBoxView.SelectedItem = 1;
                        //System.Windows.MessageBox.Show("This device has no IP channel!");
                    }
                }

            }
            else
            {
                //注销登录 Logout the device
                if (m_lRealHandle >= 0)
                {
                    DebugInfo("Please stop live view firstly"); //登出前先停止预览 Stop live view before logout
                    return;
                }

                if (!CHCNetSDK.NET_DVR_Logout(m_lUserID))
                {
                    iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                    str = "NET_DVR_Logout failed, error code= " + iLastErr;
                    DebugInfo(str);
                    return;
                }
                DebugInfo("NET_DVR_Logout succ!");
                ListViewIPChannel.Items.Clear();//清空通道列表 Clean up the channel list
                m_lUserID = -1;
                BtnLogin.Content = "登录";
            }
            return;
        }
        public void InfoIPChannel()
        {
            uint dwSize = (uint)Marshal.SizeOf(m_struIpParaCfgV40);

            IntPtr ptrIpParaCfgV40 = Marshal.AllocHGlobal((Int32)dwSize);
            Marshal.StructureToPtr(m_struIpParaCfgV40, ptrIpParaCfgV40, false);

            uint dwReturn = 0;
            int iGroupNo = 0;  //该Demo仅获取第一组64个通道,如果设备IP通道大于64路,需要按组号0~i多次调用NET_DVR_GET_IPPARACFG_V40获取

            if (!CHCNetSDK.NET_DVR_GetDVRConfig(m_lUserID, CHCNetSDK.NET_DVR_GET_IPPARACFG_V40, iGroupNo, ptrIpParaCfgV40, dwSize, ref dwReturn))
            {
                iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                str = "NET_DVR_GET_IPPARACFG_V40 failed, error code= " + iLastErr;
                //获取IP资源配置信息失败,输出错误号 Failed to get configuration of IP channels and output the error code
                DebugInfo(str);
            }
            else
            {
                DebugInfo("NET_DVR_GET_IPPARACFG_V40 succ!");

                m_struIpParaCfgV40 = (CHCNetSDK.NET_DVR_IPPARACFG_V40)Marshal.PtrToStructure(ptrIpParaCfgV40, typeof(CHCNetSDK.NET_DVR_IPPARACFG_V40));

                for (i = 0; i < dwAChanTotalNum; i++)
                {
                    ListAnalogChannel(i + 1, m_struIpParaCfgV40.byAnalogChanEnable[i]);
                    iChannelNum[i] = i + (int)DeviceInfo.byStartChan;
                }

                byte byStreamType = 0;
                uint iDChanNum = 64;

                if (dwDChanTotalNum < 64)
                {
                    iDChanNum = dwDChanTotalNum; //如果设备IP通道小于64路,按实际路数获取
                }

                for (i = 0; i < iDChanNum; i++)
                {
                    iChannelNum[i + dwAChanTotalNum] = i + (int)m_struIpParaCfgV40.dwStartDChan;
                    byStreamType = m_struIpParaCfgV40.struStreamMode[i].byGetStreamType;

                    dwSize = (uint)Marshal.SizeOf(m_struIpParaCfgV40.struStreamMode[i].uGetStream);
                    switch (byStreamType)
                    {
                        //目前NVR仅支持直接从设备取流 NVR supports only the mode: get stream from device directly
                        case 0:
                            IntPtr ptrChanInfo = Marshal.AllocHGlobal((Int32)dwSize);
                            Marshal.StructureToPtr(m_struIpParaCfgV40.struStreamMode[i].uGetStream, ptrChanInfo, false);
                            m_struChanInfo = (CHCNetSDK.NET_DVR_IPCHANINFO)Marshal.PtrToStructure(ptrChanInfo, typeof(CHCNetSDK.NET_DVR_IPCHANINFO));

                            //列出IP通道 List the IP channel
                            ListIPChannel(i + 1, m_struChanInfo.byEnable, m_struChanInfo.byIPID);
                            iIPDevID[i] = m_struChanInfo.byIPID + m_struChanInfo.byIPIDHigh * 256 - iGroupNo * 64 - 1;

                            Marshal.FreeHGlobal(ptrChanInfo);
                            break;
                        case 4:
                            IntPtr ptrStreamURL = Marshal.AllocHGlobal((Int32)dwSize);
                            Marshal.StructureToPtr(m_struIpParaCfgV40.struStreamMode[i].uGetStream, ptrStreamURL, false);
                            m_struStreamURL = (CHCNetSDK.NET_DVR_PU_STREAM_URL)Marshal.PtrToStructure(ptrStreamURL, typeof(CHCNetSDK.NET_DVR_PU_STREAM_URL));

                            //列出IP通道 List the IP channel
                            ListIPChannel(i + 1, m_struStreamURL.byEnable, m_struStreamURL.wIPID);
                            iIPDevID[i] = m_struStreamURL.wIPID - iGroupNo * 64 - 1;

                            Marshal.FreeHGlobal(ptrStreamURL);
                            break;
                        case 6:
                            IntPtr ptrChanInfoV40 = Marshal.AllocHGlobal((Int32)dwSize);
                            Marshal.StructureToPtr(m_struIpParaCfgV40.struStreamMode[i].uGetStream, ptrChanInfoV40, false);
                            m_struChanInfoV40 = (CHCNetSDK.NET_DVR_IPCHANINFO_V40)Marshal.PtrToStructure(ptrChanInfoV40, typeof(CHCNetSDK.NET_DVR_IPCHANINFO_V40));

                            //列出IP通道 List the IP channel
                            ListIPChannel(i + 1, m_struChanInfoV40.byEnable, m_struChanInfoV40.wIPID);
                            iIPDevID[i] = m_struChanInfoV40.wIPID - iGroupNo * 64 - 1;

                            Marshal.FreeHGlobal(ptrChanInfoV40);
                            break;
                        default:
                            break;
                    }
                }
            }
            Marshal.FreeHGlobal(ptrIpParaCfgV40);

        }
        public void ListIPChannel(Int32 iChanNo, byte byOnline, int byIPID)
        {
            str1 = String.Format("IPCamera {0}", iChanNo);
            m_lTree++;

            if (byIPID == 0)
            {
                str2 = "X"; //通道空闲,没有添加前端设备 the channel is idle                  
            }
            else
            {
                if (byOnline == 0)
                {
                    str2 = "offline"; //通道不在线 the channel is off-line
                }
                else
                    str2 = "online"; //通道在线 The channel is on-line
            }
            ListViewIPChannel.Items.Add(new data(str1,str2));
            
            //ListViewIPChannel.Items.Add(new System.Windows.Forms.ListViewItem(new string[] { str1, str2 }));//将通道添加到列表中 add the channel to the list
        }
        public void ListAnalogChannel(Int32 iChanNo, byte byEnable)
        {
            str1 = String.Format("Camera {0}", iChanNo);
            m_lTree++;

            if (byEnable == 0)
            {
                str2 = "Disabled"; //通道已被禁用 This channel has been disabled               
            }
            else
            {
                str2 = "Enabled"; //通道处于启用状态 This channel has been enabled
            }
            ListViewIPChannel.Items.Add(new data(str1,str2));            
            // ListViewIPChannel.Items.Add(new System.Windows.Forms.ListViewItem(new string[] { str1, str2 }));//将通道添加到列表中 add the channel to the list
        }

        /// <summary>
        /// 判断预览通道
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listViewIPChannel_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            if (ListViewIPChannel.SelectedItems.Count > 0)
            {
                iSelIndex = ListViewIPChannel.SelectedIndex;  //当前选中的行
                //iSelIndex = ListViewIPChannel.SelectedItems[0].Index;  //当前选中的行
            }
        }

        //解码回调函数
        private void DecCallbackFUN(int nPort, IntPtr pBuf, int nSize, ref PlayCtrl.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2)
        {
            // 将pBuf解码后视频输入写入文件中(解码后YUV数据量极大,尤其是高清码流,不建议在回调函数中处理)
            if (pFrameInfo.nType == 3) //#define T_YV12	3
            {
                //    FileStream fs = null;
                //    BinaryWriter bw = null;
                //    try
                //    {
                //        fs = new FileStream("DecodedVideo.yuv", FileMode.Append);
                //        bw = new BinaryWriter(fs);
                //        byte[] byteBuf = new byte[nSize];
                //        Marshal.Copy(pBuf, byteBuf, 0, nSize);
                //        bw.Write(byteBuf);
                //        bw.Flush();
                //    }
                //    catch (System.Exception ex)
                //    {
                //        MessageBox.Show(ex.ToString());
                //    }
                //    finally
                //    {
                //        bw.Close();
                //        fs.Close();
                //    }
            }
        }

        /// <summary>
        /// 直接预览
        /// </summary>
        /// <param name="lRealHandle"></param>
        /// <param name="dwDataType"></param>
        /// <param name="pBuffer"></param>
        /// <param name="dwBufSize"></param>
        /// <param name="pUser"></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; //同一路码流不需要多次调用开流接口
                        }

                        //获取播放句柄 Get the port to play
                        if (!PlayCtrl.PlayM4_GetPort(ref m_lPort))
                        {
                            iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                            str = "PlayM4_GetPort failed, error code= " + iLastErr;
                            this.BeginInvoke(AlarmInfo, str);
                            break;
                        }

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

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


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

                        //设置显示模式 Set the display mode
                        if (!PlayCtrl.PlayM4_SetOverlayMode(m_lPort, 0, 0/* COLORREF(0)*/)) //play off screen 
                        {
                            iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                            str = "PlayM4_SetOverlayMode failed, error code= " + iLastErr;
                            this.BeginInvoke(AlarmInfo, str);
                        }

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

                        //开始解码 Start to play                       
                        if (!PlayCtrl.PlayM4_Play(m_lPort, m_ptrRealHandle))
                        {
                            iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                            str = "PlayM4_Play failed, error code= " + iLastErr;
                            this.BeginInvoke(AlarmInfo, str);
                            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))
                            {
                                iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                                str = "PlayM4_InputData failed, error code= " + iLastErr;
                                Thread.Sleep(2);
                            }
                            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))
                            {
                                iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                                str = "PlayM4_InputData failed, error code= " + iLastErr;
                                Thread.Sleep(2);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// 显示错误信息
        /// </summary>
        /// <param name="alarmInfo"></param>
        /// <param name="str"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void BeginInvoke(MyDebugInfo alarmInfo, string str)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 预览
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnPreview_Click(object sender, EventArgs e)
        {
            if (m_lUserID < 0)
            {
                System.Windows.MessageBox.Show("Please login the device firstly!");
                return;
            }

            if (m_bRecord)
            {
                System.Windows.MessageBox.Show("Please stop recording firstly!");
                return;
            }

            if (m_lRealHandle < 0)
            {
                CHCNetSDK.NET_DVR_PREVIEWINFO lpPreviewInfo = new CHCNetSDK.NET_DVR_PREVIEWINFO();
                lpPreviewInfo.hPlayWnd = RealPlayWnd.Handle;//预览窗口 live view window
                lpPreviewInfo.lChannel = iChannelNum[(int)iSelIndex];//预览的设备通道 the device channel number
                lpPreviewInfo.dwStreamType = 0;//码流类型:0-主码流,1-子码流,2-码流3,3-码流4,以此类推
                lpPreviewInfo.dwLinkMode = 0;//连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP 
                lpPreviewInfo.bBlocked = true; //0- 非阻塞取流,1- 阻塞取流
                lpPreviewInfo.dwDisplayBufNum = 15; //播放库显示缓冲区最大帧数

                IntPtr pUser = IntPtr.Zero;//用户数据 user data 

                if (ComboBoxView.SelectedIndex == 0)
                {
                    //打开预览 Start live view 
                    m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V40(m_lUserID, ref lpPreviewInfo, null/*RealData*/, pUser);
                }
                else
                {
                    lpPreviewInfo.hPlayWnd = IntPtr.Zero;//预览窗口 live view window
                    m_ptrRealHandle = RealPlayWnd.Handle;
                    RealData = new CHCNetSDK.REALDATACALLBACK(RealDataCallBack);//预览实时流回调函数 real-time stream callback function 
                    m_lRealHandle = CHCNetSDK.NET_DVR_RealPlay_V40(m_lUserID, ref lpPreviewInfo, RealData, pUser);
                }

                if (m_lRealHandle < 0)
                {
                    iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                    str = "NET_DVR_RealPlay_V40 failed, error code= " + iLastErr; //预览失败,输出错误号 failed to start live view, and output the error code.
                    DebugInfo(str);
                    return;
                }
                else
                {
                    //预览成功
                    DebugInfo("NET_DVR_RealPlay_V40 succ!");
                    BtnPreview.Content = "停止预览";
                }
            }
            else
            {
                //停止预览 Stop live view 
                if (!CHCNetSDK.NET_DVR_StopRealPlay(m_lRealHandle))
                {
                    iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                    str = "NET_DVR_StopRealPlay failed, error code= " + iLastErr;
                    DebugInfo(str);
                    return;
                }

                if ((ComboBoxView.SelectedIndex == 1) && (m_lPort >= 0))
                {
                    if (!PlayCtrl.PlayM4_Stop(m_lPort))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        str = "PlayM4_Stop failed, error code= " + iLastErr;
                        DebugInfo(str);
                    }
                    if (!PlayCtrl.PlayM4_CloseStream(m_lPort))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        str = "PlayM4_CloseStream failed, error code= " + iLastErr;
                        DebugInfo(str);
                    }
                    if (!PlayCtrl.PlayM4_FreePort(m_lPort))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        str = "PlayM4_FreePort failed, error code= " + iLastErr;
                        DebugInfo(str);
                    }
                    m_lPort = -1;
                }

                DebugInfo("NET_DVR_StopRealPlay succ!");
                m_lRealHandle = -1;
                BtnPreview.Content = "开始预览";
                RealPlayWnd.Invalidate();//刷新窗口 refresh the window
            }
            return;
        }

        /// <summary>
        /// 抓图
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ButtonCatch_Click(object sender, RoutedEventArgs e)
        {
            if (ScreenshotBoxView.SelectedIndex == 0)
            {
                if (m_lRealHandle < 0)
                {
                    DebugInfo("Please start live view firstly!"); //BMP抓图需要先打开预览
                    return;
                }

                string sBmpPicFileName;
                //图片保存路径和文件名 the path and file name to save
                sBmpPicFileName = "../image/test.bmp";

                if (ComboBoxView.SelectedIndex == 0)
                {
                    //BMP抓图 Capture a BMP picture
                    if (!CHCNetSDK.NET_DVR_CapturePicture(m_lRealHandle, sBmpPicFileName))
                    {
                        iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                        str = "NET_DVR_CapturePicture failed, error code= " + iLastErr;
                        DebugInfo(str);
                        return;
                    }
                    else
                    {
                        str = "NET_DVR_CapturePicture succ and the saved file is " + sBmpPicFileName;
                        DebugInfo(str);
                    }
                }
                else
                {
                    int iWidth = 0, iHeight = 0;
                    uint iActualSize = 0;

                    if (!PlayCtrl.PlayM4_GetPictureSize(m_lPort, ref iWidth, ref iHeight))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        str = "PlayM4_GetPictureSize failed, error code= " + iLastErr;
                        DebugInfo(str);
                        return;
                    }

                    uint nBufSize = (uint)(iWidth * iHeight) * 5;

                    byte[] pBitmap = new byte[nBufSize];

                    if (!PlayCtrl.PlayM4_GetBMP(m_lPort, pBitmap, nBufSize, ref iActualSize))
                    {
                        iLastErr = PlayCtrl.PlayM4_GetLastError(m_lPort);
                        str = "PlayM4_GetBMP failed, error code= " + iLastErr;
                        DebugInfo(str);
                    }
                    else
                    {
                        FileStream fs = new FileStream(sBmpPicFileName, FileMode.Create);
                        fs.Write(pBitmap, 0, (int)iActualSize);
                        fs.Close();
                        str = "PlayM4_GetBMP succ and the saved file is " + sBmpPicFileName;
                        DebugInfo(str);
                    }
                }
                return;
            }
            else if (ScreenshotBoxView.SelectedIndex == 1)
            {
                int lChannel = iChannelNum[(int)iSelIndex]; //通道号 Channel number

                CHCNetSDK.NET_DVR_JPEGPARA lpJpegPara = new CHCNetSDK.NET_DVR_JPEGPARA();
                lpJpegPara.wPicQuality = 0; //图像质量 Image quality
                lpJpegPara.wPicSize = 0xff; //抓图分辨率 Picture size: 0xff-Auto(使用当前码流分辨率) 
                                            //抓图分辨率需要设备支持,更多取值请参考SDK文档

                //JPEG抓图保存成文件 Capture a JPEG picture
                string sJpegPicFileName;
                sJpegPicFileName = "../image/filetest.jpg";//图片保存路径和文件名 the path and file name to save

                if (!CHCNetSDK.NET_DVR_CaptureJPEGPicture(m_lUserID, lChannel, ref lpJpegPara, sJpegPicFileName))
                {
                    iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                    str = "NET_DVR_CaptureJPEGPicture failed, error code= " + iLastErr;
                    DebugInfo(str);
                    return;
                }
                else
                {
                    str = "NET_DVR_CaptureJPEGPicture succ and the saved file is " + sJpegPicFileName;
                    DebugInfo(str);
                }

                //JEPG抓图,数据保存在缓冲区中 Capture a JPEG picture and save in the buffer
                uint iBuffSize = 400000; //缓冲区大小需要不小于一张图片数据的大小 The buffer size should not be less than the picture size
                byte[] byJpegPicBuffer = new byte[iBuffSize];
                uint dwSizeReturned = 0;

                if (!CHCNetSDK.NET_DVR_CaptureJPEGPicture_NEW(m_lUserID, lChannel, ref lpJpegPara, byJpegPicBuffer, iBuffSize, ref dwSizeReturned))
                {
                    iLastErr = CHCNetSDK.NET_DVR_GetLastError();
                    str = "NET_DVR_CaptureJPEGPicture_NEW failed, error code= " + iLastErr;
                    DebugInfo(str);
                    return;
                }
                else
                {
                    //将缓冲区里的JPEG图片数据写入文件 save the data into a file
                    string str = "../image/buffertest.jpg";
                    FileStream fs = new FileStream(str, FileMode.Create);
                    int iLen = (int)dwSizeReturned;
                    fs.Write(byJpegPicBuffer, 0, iLen);
                    fs.Close();

                    str = "NET_DVR_CaptureJPEGPicture_NEW succ and save the data in buffer to 'buffertest.jpg'.";
                    DebugInfo(str);
                }

                return;
            }
        }

        /// <summary>
        ///  刷新界面
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnRefresh_Click(object sender, EventArgs e)
        {
            //刷新通道列表
            ListViewIPChannel.Items.Clear();
            for (i = 0; i < dwAChanTotalNum; i++)
            {
                ListAnalogChannel(i + 1, 1);
                iChannelNum[i] = i + (int)DeviceInfo.byStartChan;
            }
            InfoIPChannel();
        }

        /// <summary>
        /// 退出界面
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ButtonExit_Click(object sender, RoutedEventArgs e)
        {
            //停止预览
            if (m_lRealHandle >= 0)
            {
                CHCNetSDK.NET_DVR_StopRealPlay(m_lRealHandle);
                m_lRealHandle = -1;
            }

            //注销登录
            if (m_lUserID >= 0)
            {
                CHCNetSDK.NET_DVR_Logout(m_lUserID);
                m_lUserID = -1;
            }

            CHCNetSDK.NET_DVR_Cleanup();

            System.Windows.Application.Current.MainWindow.Close();
            // Application.Exit();
        }

        /// <summary>
        /// 勾选HIDDNS
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void checkBoxHiDDNS_Checked(object sender, RoutedEventArgs e)
        {
            if (CheckBoxHiDDNS.IsChecked == true)
            {
                //HiDDNS域名方式访问设备
                Label1.Content = "HiDDNS域名";
                TextBoxIP.Text = "a1234test";
                TextBoxPort.IsEnabled = false;
            }
            else
            {
                //IP地址或者普通域名方式访问设备
                Label1.Content = "设备IP/域名";
                TextBoxIP.Text = "192.168.1.123";
                TextBoxPort.IsEnabled = true;
            }
        }


        /// <summary>
        /// 通道配置
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListViewIPChannel_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                int iCurChan = iChannelNum[(int)iSelIndex];
                if (iCurChan >= m_struIpParaCfgV40.dwStartDChan)
                {
                    if (MessageBoxResult.OK == System.Windows.MessageBox.Show("是否配置该IP通道!", "配置提示", MessageBoxButton.OKCancel))
                    {
                        IPChannelConfig dlg = new IPChannelConfig();
                        dlg.m_struIPParaCfgV40 = m_struIpParaCfgV40;
                        dlg.m_lUserID = m_lUserID;
                        int iCurChanIndex = iCurChan - (int)m_struIpParaCfgV40.dwStartDChan; //通道索引
                        int iCurIPDevIndex = iIPDevID[iCurChanIndex]; //设备ID索引
                        dlg.iIPDevIndex = iCurIPDevIndex;
                        dlg.iChanIndex = iCurChanIndex;
                        dlg.ShowDialog();
                    }
                }
                else
                {

                }
            }
        }

        private void ListViewIPChannel_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            int iCurChan = iChannelNum[(int)iSelIndex];
            if (iCurChan >= m_struIpParaCfgV40.dwStartDChan)
            {
                if (MessageBoxResult.OK == System.Windows.MessageBox.Show("是否配置该IP通道!", "配置提示", MessageBoxButton.OKCancel))
                {
                    IPChannelConfig dlg = new IPChannelConfig();
                    dlg.m_struIPParaCfgV40 = m_struIpParaCfgV40;
                    dlg.m_lUserID = m_lUserID;
                    int iCurChanIndex = iCurChan - (int)m_struIpParaCfgV40.dwStartDChan; //通道索引
                    int iCurIPDevIndex = iIPDevID[iCurChanIndex]; //设备ID索引
                    dlg.iIPDevIndex = iCurIPDevIndex;
                    dlg.iChanIndex = iCurChanIndex;
                    dlg.ShowDialog();
                }
            }
            else
            {

            }
        }        
    }
}

4.4 功能实现

4.4.1 登录

点击登录按钮,提示登陆成功,登录按钮文字变为退出登录。

4.4.2 预览

点击开始预览按钮,提示预览成功,开始预览按钮文字变为停止预览。

 4.4.3 抓图

点击下拉列表,BMP和JPG两种抓图方式,点击抓图按钮,图片保存到Debug上一级文件夹中。

4.4.4 通道配置

双击通道内容,打开配置提示,点击确定进入通道配置

4.4.5 退出

点击Exit按钮,退出界面 。

4.5 小改动

原本的右键点击对通道的配置修改为双击对通道的配置。

原本的抓图是两个单独的按钮,修改为在ComboBox中进行选择不同的抓图方式。

对于海康威视的SDK只实现了部分功能,并不是全部相同。

五、关于延迟

相信大家自己实验过后就会发现,不论是自己写的程序还是SDK中的程序,与给定的程序相比,视频显示会有延迟。

1. 设置预览数据,设置SDK中播放库显示缓冲区最大帧数

设置为1,时延大概为0.3s。

 与原本的软件对比

2. 用python的Demo实验。

(记得根据帮助文档,将配置文件放入lib文件夹中)

以下是python延迟的实时监测,与原本软件的视频流延迟相差0.3s左右。

;