本章使用C# 3.5+视频采集卡制作一个简单的家庭视频监控系统,并将系统划分为几个模块进行详细讲解。通过对这些模块的学习,读者可以完全掌握视频监控系统的开发技术及原理。笔者通过简洁的程序代码和通俗的技术讲解,将家庭视频监控系统的奥秘完全展示在读者面前,使读者可以更轻松地理解其中的内容。 通过阅读本章,读者可以学习到: ? 单元测试及常用单元测试工具 ? Access 2003数据库和数据表的创建 ? 如何在WinForms程序中操作Access 2003数据库 ? 视频采集卡的使用 ? Pelco-D协议的使用 ? 如何使用Pelco-D协议进行云台控制 ? 如何读取和写入注册表信息 1.1 开 发 背 景 XXX家庭为了紧随逐渐加快的生活节奏,并适应不断增加的工作压力,现委托相关单位开发一个视频监控系统。该系统的主要作用是:当用户外出时,可以使用该系统监控家里的各种情况,并可以将家里的变化情况录制成视频文件,以供后期查看。 1.2 系 统 分 析 1.2.1 需求分析 随着生活节奏的加快,工作压力的增加,人们用于照顾家庭的时间越来越少。年幼孩子的看护、年迈父母的照管、家庭财产的防窃等一系列问题经常缠绕着人们,成为人们忙碌中挥之不去的牵挂。本章使用C#语言结合视频采集卡制作了一个简单的家庭视频监控系统,以解决上面出现的各种问题。 1.2.2 可行性分析 根据《GB8567-88计算机软件产品开发文件编制指南》中可行性分析的要求,制定可行性研究报告如下。 1.引言 ? 编写目的 为了给企业的决策层提供是否进行项目实施的参考依据,现以文件的形式分析项目的风险、项目需要的投资与效益。 ? 背景 XXX家庭为了在外出时,可以监控家里的各种情况,现委托相关公司开发一个视频监控系统,项目名称为家庭视频监控系统。 2.可行性研究的前提 ? 要求 家庭视频监控系统要求能够提供视频监控、快照、录像和自动监控等功能。 ? 目标 家庭视频监控系统的主要目标是保证家里的安全。 ? 条件、假定和限制 项目需要在1个月内交付用户使用,系统分析人员需要两天内到位,用户需要3天时间确认需求分析文档,去除其中可能出现的问题,例如用户临时有事,需占用5天时间确认需求分析,那么程序开发人员需要在23天内进行系统设计、程序编码、系统测试、程序调试和系统打包部署工作,其间,还包括了员工每周的休息时间。 3.投资及效益分析 ? 支出 根据系统的规模及项目的开发周期(1个月),公司决定投入3个人。此外,公司将直接支付3万元的工资及各种福利待遇。在项目安装及调试阶段,用户培训、员工出差等费用支出需要1万元。在项目维护阶段,预计需要投入1.5万元资金。项目累计投入需要5.5万元资金。 ? 收益 用户提供项目资金10万元。对于项目运行后进行的改动,采取协商的原则,根据改动规模额外提供资金。因此从投资与收益的效益比上,公司可以获得4.5万元的利润。 项目完成后,会给公司提供资源储备,包括技术、经验的积累,其后再开发类似的项目时,可以极大地缩短项目开发周期。 4.结论 根据前面的分析,在技术上不会存在问题,因此项目延期的可能性很小。在效益上公司投入3个人、1个月的时间获利4.5万元,比较可观;在公司今后发展上可以储备软件开发的经验和资源,因此认为该项目可以开发。 1.2.3 编写项目计划书 根据《GB8567-88计算机软件产品开发文件编制指南》中的项目开发计划要求,结合单位实际情况,设计项目计划书如下。 1.引言 ? 编写目的 为了保证项目开发人员按时保质地完成预订目标,更好地了解项目实际情况,按照合理的顺序开展工作,现以书面的形式将项目开发生命周期中的项目任务范围、项目团队组织结构、团队成员的工作责任、团队内外沟通协作方式、开发进度、检查项目工作等内容描述出来,作为项目相关人员之间的统一约定和项目生命周期内的所有项目活动的行动基础。 ? 背景 家庭视频监控系统是由XXX家庭委托我公司开发的小型视频监控系统,系统主要用于监控家里的人员活动情况,项目周期为1个月。项目背景规划如表1.1所示。 表1.1 项目背景规划 项 目 名 称 项目委托单位 任务提出者 项目承担部门 家庭视频监控系统 XXX单位 王经理 研发部门 测试部门 2.概述 ? 项目目标 项目目标应当符合SMART原则,把项目要完成的工作用清晰的语言描述出来。家庭视频监控系统的项目目标如下。 家庭视频监控系统的主要目的是随时对家里的情况进行监控,并可以由用户灵活控制监控方向,另外,用户还可以设置自动监控、对监控画面进行快照和录像等。 ? 应交付成果 项目开发完成后,交付的内容如下: ? 以光盘的形式提供家庭视频监控的源程序、系统数据库文件和系统使用说明书。 ? 系统发布后,进行无偿维护和服务6个月,超过6个月进行系统有偿维护与服务。 ? 项目开发环境 开发本项目所用的操作系统可以是Windows 2000 Server、Windows XP、Windows Server 2003或Windows 7,开发工具为Visual Studio 2008+视频采集卡,数据库采用Microsoft Access 2003。 ? 项目验收方式与依据 项目验收分为内部验收和外部验收两种方式。在项目开发完成后,首先进行内部验收,由测试人员根据用户需求和项目目标进行验收。通过内部验收后,交给用户进行外部验收,验收的主要依据为需求规格说明书。 3.项目团队组织 ? 组织结构 为了完成家庭视频监控系统的开发,公司组建了一个临时的项目团队,由项目经理、软件工程师和测试人员构成,如图1.1所示。 ? 人员分工 为了明确项目团队中每个人的任务分工,现制定人员分工表如表1.2所示。 表1.2 人员分工表 姓 名 技 术 水 平 所 属 部 门 角 色 工 作 描 述 王某 MBA 项目开发部 项目经理 负责项目的前期分析、策划,项目开发进度的跟踪,项目质量的检查 刘某 高级软件工程师 项目开发部 软件工程师 负责系统功能分析、软件设计与编码 张某 中级系统测试工程师 项目开发部 测试人员 对软件进行测试、编写软件测试文档 1.3 系 统 设 计 1.3.1 系统目标 本系统属于小型的家庭视频监控系统,可以对指定的区域进行适时监控。本系统主要实现以下目标: ? 系统采用人机交互的方式,界面美观友好,视频监控灵活、方便。 ? 灵活控制云台,以监控某一区域的各个角落。 ? 适时对监控画面进行快照和录像操作。 ? 选择观看已经录制的视频文件。 ? 完善的系统注册功能。 ? 系统最大限度地实现易维护性和易操作性。 1.3.2 系统功能结构 家庭视频监控系统的功能结构如图1.2所示。 图1.2 家庭视频监控系统功能结构图 1.3.3 业务流程图 家庭视频监控系统的业务流程图如图1.3所示。 图1.3 家庭视频监控系统业务流程图 1.3.4 系统预览 家庭视频监控系统由多个程序窗体组成,下面仅列出几个典型窗体,其他窗体参见光盘中的源 程序。 系统登录窗体如图1.4所示,该窗体用于实现用户登录功能。主窗体如图1.5所示,该窗体用于实现视频监控及云台控制等功能。 图1.4 系统登录窗体(光盘\TM\01\…\frmLogin.cs) 图1.5 主窗体(光盘\TM\01\…\frmMain.cs) 监控管理窗体如图1.6所示,该窗体用于实现添加、修改和删除用户功能。软件注册窗体如图1.7所示,该窗体用于实现软件注册功能。 图1.6 监控管理窗体(光盘\TM\01\…\frmSetMonitor.cs) 图1.7 软件注册窗体(光盘\TM\01\…\frmRegister.cs) 1.3.5 程序运行环境 本系统对其运行环境有一定的要求,具体如下。 ? 系统开发平台:Microsoft Visual Studio 2008。 ? 系统开发语言:C# 3.5。 ? 数据库管理系统软件:Microsoft Access 2003。 ? 运行平台:Windows XP(SP2)/Windows 2000(SP4)/Windows Server 2003(SP1)/Windows 7。 ? 运行环境:Microsoft.NET Framework SDK v3.5。 ? 分辨率:最佳效果1024×768像素。 1.3.6 编码规范 1.数据库命名规范 ? 数据库 数据库命名以字母“db”开头(小写),后面加数据库相关英文单词或缩写。下面将举例进行说明,如表1.3所示。 表1.3 数据库命名 数据库名称 描 述 db_VWMS 视频监控管理系统数据库 ? 数据表 数据表以字母“tb”开头(小写),后面加数据表相关英文单词或缩写。下面将举例进行说明,如表1.4所示。 表1.4 数据表命名 数据表名称 描 述 tb_admin 管理员信息表 ? 字段 字段一律采用英文单词或词组(可利用翻译软件)命名,如找不到专业的英文单词或词组,可以用相同意义的英文单词或词组代替。下面将举例进行说明,如表1.5所示。 表1.5 字段命名 数据表名称 描 述 name 名字 id 密码 ? 视图 视图命名以字母“view”开头(小写),后面加表示该视图作用的相关英文单词或缩写。下面将举例进行说明,如表1.6所示。 表1.6 视图命名 view_AdminInfo 视 图 全 名 view 视图 AdminInfo 查看管理员信息 ? 存储过程 存储过程命名以字母“proc”开头(小写),后面加表示该存储过程作用的相关英文单词或缩写。下面将举例进行说明,如表1.7所示。 表1.7 存储过程命名 proc_Login 存储过程全名 proc 存储过程 Login 实现登录功能 ? 触发器 触发器命名以字母“trig”开头(小写),后面加表示该触发器作用的相关英文单词或缩写。下面将举例进行说明,如表1.8所示。 表1.8 触发器命名 trig_inAdmin 触发器全名 trig 触发器 inAdmin 插入管理员信息 2.程序代码命名规范 (1)变量及对象名称定义规则 根据不同的程序需要,编写代码时都需要定义一定的变量或常量。下面介绍一种常见的变量及常量命名规则,如表1.9所示。 表1.9 变量及常量命名规则 变量及常量级别 命 名 规 则 举 例 模块级变量 M_+数据类型简写+变量名称 M_int_xx 全局变量 G_+数据类型简写+变量名称 G_int_xx 局部变量 P_+数据类型简写+变量名称 P_dbl_sl 模块级常量 Mc_+数据类型简写+常量名称 Mc_str_xx 全局常量 Gc_+数据类型简写+常量名称 Gc_str_xx 过程级常量 Pc_+数据类型简写+常量名称 Pc_str_xx (2)数据类型简写规则 程序中定义常量、变量或方法等内容时,常常需要指定类型。下面介绍一种常见的数据类型简写规则,如表1.10所示。 表1.10 数据类型简写规则 数 据 类 型 简 写 整型 int 字符串 str 布尔型 bl 短整型 sint 长整型 lint 单精度浮点型 flt 双精度浮点型 dbl 字节型 bt (3)控件命名规则 所有的对象名称都为自然名称的拼音简写,出现冲突可采用不同的简写规则。另外,在编码过程中涉及不到编码的控件,其名称可以取默认名称。控件命名规则如表1.11所示。 表1.11 控件命名规则 控 件 缩 写 形 式 Form frm TextBox txt Button btn ComboBox cbox Label lab DataGridView dgv ListBox lb Timer tmr CheckBox chb LinkLabel llbl RichTextBox rtbox CheckedListBox clbox RadioButton rbtn NumericUpDown nudown Panel pl GroupBox gbox TabControl tcl ErrorProvider epro ImageList ilist HelpProvider hpro ListView lv TreeView tv PictrueBox pbox NotifyIcon nicon DateTimePicker dtpicker MonthCalendar mcalen ToolTip ttip ProgressBar pbar 1.3.7 数据库设计 一套完善的系统离不开数据库的设计,数据库设计的好与坏直接影响系统运行的效率,所以在制作某个系统之前,首先要根据项目的成本以及整个系统的信息量去选择数据库,然后根据系统的具体要求以及功能去设计数据库。 1.数据库分析 在家庭视频监控系统中,因为系统的信息量不是很大,并且系统的项目成本不高,数据库主要用来存储用户登录系统的名字和密码,因此对数据库的要求并不是很高,所以本系统采用Microsoft Access 2003作为后台数据库,将数据库命名为db_VWMS,其中包含了一张数据表,用于存储用户登录信息。详细信息如图1.8所示。 2.数据库概念设计 系统开发过程中,数据库设计占有重要的地位,数据库设计的原则是根据系统的整体需求而定的。例如,在本系统中,为了增加系统的安全性,每个用户首先都要通过系统登录模块的验证才能进入主窗体。这时,就要在数据库中创建一个存储登录名和登录密码的管理员基本信息表。管理员基本信息实体E-R图如图1.9所示。 图1.8 数据库db_ VWMS中的所有表 图1.9 管理员信息实体E-R图 3.数据库逻辑结构设计 根据设计好的E-R图在数据库中创建数据表。本系统中只有一个tb_admin表,该表用于保存管理员登录的基本信息,其结构如表1.12所示。 表1.12 管理员登录信息表 字 段 名 数 据 类 型 主 键 描 述 name 文本 否 登录用户名 pwd 文本 否 登录密码 1.3.8 文件夹组织结构 每个项目都会有相应的文件夹组织结构,如果项目中窗体数量很多,可以将所有的窗体及资源放在不同的文件夹中。如果项目中窗体不是很多,可以将图片、公共类或者程序资源文件放在相应的文件夹中,而窗体可以直接放在项目根目录下。家庭视频监控系统就是按照后者的文件夹组织结构排列的,如图1.10所示。 图1.10 项目文件夹组织结构 1.4 公共类设计 在开发项目中以类的形式来组织、封装一些常用的方法和事件,不仅可以提高代码的重用率,也大大方便了代码的管理。本系统中创建了5个公共类,分别为DataCon类、DataOperate类、SoftReg类、VideoOperate类和PelcoD类。其中,DataCon类用来访问Microsoft Access 2003数据库,DataOperate类用来操作Microsoft Access 2003数据库,SoftReg类用来实现生成机器码和系统注册功能,VideoOperate用来封装视频采集卡中的各种枚举和API函数,PelcoD类用来实现Pelco-D协议。在程序开发时,窗体只需调用相应的方法即可。下面分别对这5个类中的方法进行详细介绍。 1.4.1 DataCon类 DataCon类中,因为本系统使用的是Microsoft Access 2003数据库,所以在命名空间区域内引用using System.Data.OleDb来连接数据库。该类中定义了一个getCon()方法,该方法用来使用OleDbConnection对象连接Access数据库。GetCon()方法的主要代码如下: 例程01 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\DataCon.cs public OleDbConnection getCon() { string strDPath = Application.StartupPath; string strDataSource = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + strDPath.Substring(0, strDPath.LastIndexOf("\\")).Substring(0, strDPath.Substring(0, strDPath. LastIndexOf("\\")).LastIndexOf("\\")) + "\\DataBase\\db_VWMS.mdb"; ? OleDbConnection oledbCon = new OleDbConnection(strDataSource); return oledbCon; } ? 代码贴士 ? OleDbConnection:用来连接OLEDB数据源。 1.4.2 DataOperate类 DataOperate类中,首先实例化datacon、oledbcon、oledbcom、oledbda和ds 5个对象。其中,datacon对象用来调用自定义类DataCon类中的方法,oledbcon对象用来连接Access数据库,oledbcom对象用来执行Command命令语句,oledbda对象表示用于填充DataSet数据集和更新Access数据库的一组数据命令和一个数据库连接,ds对象为数据集。实例化datacon、oledbcon、oledbcom、oledbda和ds这5个对象的关键代码如下: 例程02 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\DataOperate.cs DataCon datacon = new DataCon(); //实例化DataCon类对象 OleDbConnection oledbcon; //实例化OleDbConnection类对象,用来连接Access数据库 OleDbCommand oledbcom; //实例化OleDbCommand类对象,用来执行SQL语句 OleDbDataAdapter oledbda; //实例化OleDbDataAdapter类对象,用来执行SQL语句,并记录结果集 DataSet ds; //DataSet数据集 DataOperate类中自定义了getCom()和getDs()两个方法,下面分别对它们进行介绍。 1.getCom()方法 getCom()方法为无返回值类型的自定义方法,主要用来执行SQL语句,关键代码如下: 例程03 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\DataOperate.cs public void getCom(string strCon) { oledbcon = datacon.getCon(); oledbcom = new OleDbCommand(strCon, oledbcon); ? oledbcon.Open(); ? oledbcom.ExecuteNonQuery(); ? oledbcon.Close(); } ? 代码贴士 ? Open:该方法用来打开数据库连接。 ? ExecuteNonQuery:执行Command命令。 ? Close:该方法用来关闭数据库连接。 2.getDs()方法 getDs()方法用来执行SQL语句,并返回一个DataSet类型的数据集对象。此方法中,首先调用DataCon类中的getCon ()方法实现Access数据库连接,然后使用OleDbDataAdapter类对象填充DataSet数据集。关键代码如下: 例程04 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\DataOperate.cs public DataSet getDs(string strCon,string tbname) { oledbcon = datacon.getCon(); //获得数据库连接 oledbda = new OleDbDataAdapter(strCon, oledbcon); //实例化OleDbDataAdapter对象 ds = new DataSet(); //实例化DataSet对象 oledbda.Fill(ds, tbname); //填充DataSet数据集 return ds; } 1.4.3 SoftReg类 SoftReg类中自定义了GetDiskVolumeSerialNumber()、getCpu()、getMNum()和getRNum() 4个方法,下面分别对它们进行介绍。 1.GetDiskVolumeSerialNumber()方法 GetDiskVolumeSerialNumber()方法用来使用ManagementObject对象的GetPropertyValue()方法获得本机的硬盘标识号,其实现代码如下: 例程05 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\SoftReg.cs //取得设备硬盘的卷标号 public string GetDiskVolumeSerialNumber() { ? ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration"); ? ManagementObject disk = new ManagementObject("win32_logicaldisk.deviceid=\"d:\""); disk.Get(); ? return disk.GetPropertyValue("VolumeSerialNumber").ToString(); } U 代码贴士 ? ManagementClass:公共信息模型(CIM)管理类,它是一个WMI类,通过该类的成员,可以使用特定的WMI类路径访问WMI数据。 ? ManagementObject:表示WMI实例。 ? GetPropertyValue():该方法用来获取某属性值的等效访问器。 2.getCpu()方法 getCpu()方法用来获得本机的CPU序列号,其实现代码如下: 例程06 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\SoftReg.cs //获得CPU的序列号 public string getCpu() { string strCpu = null; ManagementClass myCpu = new ManagementClass("win32_Processor"); //指定win32_Processor管理类 ManagementObjectCollection myCpuConnection = myCpu.GetInstances(); foreach (ManagementObject myObject in myCpuConnection) { strCpu = myObject.Properties["Processorid"].Value.ToString(); //获得CPU序列号 break; } return strCpu; } 3.getMNum()方法 getMNum()方法用来从得到的硬盘标识号和CPU序列号中取出一定的位数作为机器码,其实现代码如下: 例程07 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\SoftReg.cs //生成机器码 public string getMNum() { string strNum = getCpu() + GetDiskVolumeSerialNumber(); //获得CPU和硬盘序列号 string strMNum = strNum.Substring(0,24); //从生成的字符串中取出前个字符作为机器码 return strMNum; } public int[] intCode = new int[127]; //存储密钥 public int[] intNumber = new int[25]; //存机器码的ASCII值 public char[] Charcode = new char[25]; //存储机器码字 public void setIntCode() //给数组赋值小于10的数 { for (int i = 1; i < intCode.Length; i++) { intCode[i] = i % 9; } } 4.getRNum()方法 getRNum()方法用来根据得到的机器码生成注册码,其实现代码如下: 例程08 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\SoftReg.cs //生成注册码 public string getRNum() { setIntCode(); //初始化为数组 for (int i = 1; i < Charcode.Length; i++) //把机器码存入数组中 { Charcode[i] = Convert.ToChar(this.getMNum().Substring(i - 1, 1)); } for (int j = 1; j < intNumber.Length; j++) //把字符的ASCII值存入一个整数组中 { intNumber[j] = intCode[Convert.ToInt32(Charcode[j])] + Convert.ToInt32(Charcode[j]); } string strAsciiName = ""; //用于存储注册码 for (int j = 1; j < intNumber.Length; j++) { if (intNumber[j] >= 48 && intNumber[j] <= 57) //判断字符ASCII值是否在48~57之间 { strAsciiName += Convert.ToChar(intNumber[j]).ToString(); } else if (intNumber[j] >= 65 && intNumber[j] <= 90) //判断字符ASCII值是否在A~Z之间 { strAsciiName += Convert.ToChar(intNumber[j]).ToString(); } else if (intNumber[j] >= 97 && intNumber[j] <= 122) //判断字符ASCII值是否在a~z之间 { strAsciiName += Convert.ToChar(intNumber[j]).ToString(); } else //判断字符ASCII值不在以上范围内 { if (intNumber[j] > 122) //判断字符ASCII值是否大于z { strAsciiName += Convert.ToChar(intNumber[j] - 10).ToString(); } else { strAsciiName += Convert.ToChar(intNumber[j] - 9).ToString(); } } } return strAsciiName; } ? 注意:由于本实例中用到了ManagementClass、ManagementObject和ManagementObjectCollection类,所以需要在命名空间区域添加System.Management命名空间。 1.4.4 VideoOperate类 VideoOperate类主要封装了操作视频采集卡的各种枚举及方法,由于要调用Sa7134Capture.dll动态链接库,所以首先要引用命名空间using System.Runtime.InteropServices。封装Sa7134Capture.dll动态链接库中各种枚举及方法的关键代码如下: 例程09 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\VideoOperate.cs #region 视频采集卡中的枚举 public enum DISPLAYTRANSTYPE { NOT_DISPLAY = 0, PCI_VIEDOMEMORY = 1, PCI_MEMORY_VIDEOMEMORY = 2 } //视频预览和视频捕捉数据流格式,目前版本只支持UUY2格式 public enum COLORFORMAT { RGB32 = 0x0, RGB24 = 0x1, RGB16 = 0x2, RGB15 = 0x3, YUY2 = 0x4, TYUV = 0x5, 8 = 0x6, GB8 = 0x7, L422 = 0x8, L411 = 0x9, UV12 = 0xA, UV9 = 0xB, AW = 0xE } /*视频预览及视频捕获的显示属性,其中: BRIGHTNESS为亮度,value范围:0~255,最佳:80 CONTRAST为对比度,value范围:-128~127,最佳:x44 SATURATION为饱和度,value范围:-128~127,最佳:x40 HUE为色度,value范围:-128~127,最佳:x0 只有当COLORDEVICETYPE等于COLOR_DECODER时才有效 SHARPNESS为锐度,value范围:-8~7,最佳:x0 只有当COLORDEVICETYPE等于COLOR_DECODER时才有效 */ public enum COLORCONTROL { BRIGHTNESS = 0, CONTRAST = 1, SATURATION = 2, HUE = 3, SHARPNESS = 4 } /*显示设备的显示属性,其中: COLOR_DECODER为解码器的显示属性,它会影响视频预览和视频捕获的显示属性 COLOR_PREVIEW为视频预览的显示属性 COLOR_CAPTURE为视频捕获的显示属性 */ pblic enum COLORDEVICETYPE { COLOR_DECODER = 0, COLOR_PREVIEW = 1, COLOR_CAPTURE = 2, } /*视频捕获方式,其中: CAP_NULL_STREAM 捕获无效 CAP_ORIGIN_STREAM 捕获为原始流回调 CAP_MPEG4_STREAM 捕获为MPEG4 */ public enum CAPMODEL { CAP_NULL_STREAM = 0, CAP_ORIGIN_STREAM = 1, CAP_MPEG4_STREAM = 2, } /*音视频MPEG4捕获方式,只有CAPMODEL等于CAP_MPEG4_STREAM时有效,其中: MPEG4_AVIFILE_ONLY 存为MPEG4文件 MPEG4_CALLBACK_ONLY MPEG数据回调 MPEG4_AVIFILE_CALLBACK 存为MPEG文件并回调 */ public enum MP4MODEL { MPEG4_AVIFILE_ONLY = 0, MPEG4_CALLBACK_ONLY = 1, MPEG4_AVIFILE_CALLBACK = 2, } /*MPEG4_XVID压缩模式,其中: XVID_CBR_MODE (固定码率模式) XVID_VBR_MODE (动态码率模式) */ public enum COMPRESSMODE { XVID_CBR_MODE = 0, XVID_VBR_MODE = 1, } /*视频源的输入频率,其中: FIELD_FREQ_50HZ 50HZ,绝对多数为PAL制式 FIELD_FREQ_60HZ 60HZ,绝对多数为NTSC制式 FIELD_FREQ_0HZ,无信号 */ public enum eFieldFrequency { FIELD_FREQ_50HZ = 0, FIELD_FREQ_60HZ = 1, FIELD_FREQ_0HZ = 2, } /*电平状态,其中: HIGH_VOLTAGE为高电平 LOW_VOLTAGE为低电平 */ public enum eVOLTAGELEVEL { HIGH_VOLTAGE = 0, LOW_VOLTAGE = 1, } #endregion #region 视频采集卡中的API函数 //初始化系统资源 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAInitSdk")] public extern static bool VCAInitSdk(IntPtr hWndMain, DISPLAYTRANSTYPE eDispTransType, bool bLnitAuDev); //释放系统资源 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAUnInitSdk")] public extern static void VCAUnInitSdk(); //打开指定卡号的设备,分配相应系统资源 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAOpenDevice")] public extern static bool VCAOpenDevice(Int32 dwCard, IntPtr hPreviewWnd); //关闭指定卡号的设备,释放相应系统资源 [DllImport("Sa7134Capture.dll", EntryPoint = "VCACloseDevice")] public extern static bool VCACloseDevice(Int32 dwCard); //返回系统当中卡号数量,即为SAA7134硬件数目,为0时表示没有设备存在 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAGetDevNum")] public extern static int VCAGetDevNum(); //开始视频预览 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAStartVideoPreview")] public extern static bool VCAStartVideoPreview(Int32 dwCard); //停止视频预览 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAStopVideoPreview")] public extern static bool VCAStopVideoPreview(Int32 dwCard); //更新视频预览 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAUpdateVideoPreview")] public extern static bool VCAUpdateVideoPreview(Int32 dwCard, IntPtr hPreviewWnd); //更新overlay窗口,当overlay窗口句柄改变或尺寸、位置改变时调用,overlay窗口就是包含 //多路显示小窗口的大窗口,overlay窗口必须有一个,多路显示小窗口必须包含在其内部 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAUpdateOverlayWnd")] public extern static bool VCAUpdateOverlayWnd(IntPtr hOverlayWnd); //保存快照为JPEG文件 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASaveAsJpegFile")] public extern static bool VCASaveAsJpegFile(Int32 dwCard, string lpFileName, Int32 dwQuality); //保存快照为BMP文件 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASaveAsBmpFile")] public extern static bool VCASaveAsBmpFile(Int32 dwCard, string lpFileName); //开始视频捕获 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAStartVideoCapture")] public extern static bool VCAStartVideoCapture(Int32 dwCard, CAPMODEL enCapMode, MP4MODEL enMp4Mode, string lpFileName); //停止视频捕获 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAStopVideoCapture")] public extern static bool VCAStopVideoCapture(Int32 dwCard); //设置视频捕获尺寸,dwWidth和dwHeight最好为16的倍数,否则,动态检测为16×16的一个检测小块,检测将会不准确 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASetVidCapSize")] public extern static bool VCASetVidCapSize(Int32 dwCard, Int32 dwWidth, Int32 dwHeight); //得到视频捕获尺寸 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAGetVidCapSize")] public extern static bool VCAGetVidCapSize(Int32 dwCard, Int32 dwWidth, Int32 dwHeight); //设置视频捕获频率 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASetVidCapFrameRate")] public extern static bool VCASetVidCapFrameRate(Int32 dwCard, Int32 dwFrameRate, bool bFrameRateReduction); //设置MPEG压缩的位率 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASetBitRate")] public extern static bool VCASetBitRate(Int32 dwCard, Int32 dwBitRate); //设置MPEG压缩的关键帧间隔,必须大于等于帧率 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASetKeyFrmInterval")] public extern static bool VCASetKeyFrmInterval(Int32 dwCard, Int32 dwKeyFrmInterval); //设置MPEG4_XVID压缩的质量 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASetXVIDQuality")] public extern static bool VCASetXVIDQuality(Int32 dwCard, Int32 dwQuantizer, Int32 dwMotionPrecision); //设置MPEG4_XVID压缩的模式 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASetXVIDCompressMode")] public extern static bool VCASetXVIDCompressMode(Int32 dwCard, COMPRESSMODE enCompessMode); //设置视频颜色属性,它将影响视频预览和视频捕获的显示属性 [DllImport("Sa7134Capture.dll", EntryPoint = "VCASetVidDeviceColor")] public extern static bool VCASetVidDeviceColor(Int32 dwCard, COLORCONTROL enCtlType, Int32 dwValue); //得到视频源输入频率,即可得到视频源输入制式 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAGetVidFieldFrq")] public extern static bool VCAGetVidFieldFrq(Int32 dwCard, eFieldFrequency eVidSourceFieldRate); //初始化视频设备,当视频不显示,只需视频录像获得音频处理时,或通过VCAInitSdk()函数已经初始化完成, //可以不初始化 [DllImport("Sa7134Capture.dll", EntryPoint = "VCAInitVidDev")] public extern static bool VCAInitVidDev(); #endregion 1.4.5 PelcoD类 PelcoD类主要封装了控制云台的Pelco-D协议,该协议由7个字节构成。第1个字节为同步字节,始终为FFH;第2个字节为地址码,也就是摄像头的逻辑地址号;第3、4个字节表示指令码,即执行哪项操作;第5、6个字节表示数据码,用于指定摄像头的水平、垂直方向移动速度;第7个字节为校验码。PelcoD类中通过将Pelco-D协议中的7个字节返回为串口消息值来对云台进行控制,其关键代码如下: 例程10 代码位置:光盘\TM\01\VWMS\VWMS\CommonClass\PelcoD.cs private string watchdir = ""; //监控方向 private static readonly byte STX = 0xFF; //同步字节 #region 监控方向和定时监控实体 public string WatchDir { get { return watchdir; } set { watchdir = value; } } #endregion #region 基本指令定义 #region 指令码 private const byte FocusNear = 0x01; //增加聚焦 private const byte IrisOpen = 0x02; //减小光圈 private const byte IrisClose = 0x04; //增加光圈 private const byte CameraOnOff = 0x08; //摄像机打开和关闭 private const byte AutoManualScan = 0x10; //自动和手动扫描 private const byte Sense = 0x80; //Sense码 #endregion #region 指令码 private const byte PanRight = 0x02; //右 private const byte PanLeft = 0x04; //左 private const byte TiltUp = 0x08; //上 private const byte TiltDown = 0x10; //下 private const byte ZoomTele = 0x20; //增加对焦 private const byte ZoomWide = 0x40; //减小对焦 private const byte FocusFar = 0x80; //减小聚焦 #endregion #region 镜头左右平移的速度 private const byte PanSpeedMin = 0x00; //停止 private const byte PanSpeedMax = 0xFF; //最高速 #endregion #region 镜头上下移动的速度 private const byte TiltSpeedMin = 0x00; //停止 private const byte TiltSpeedMax = 0x3F; //最高速 #endregion #endregion #region 云台控制枚举 public enum Switch { On = 0x01, Off = 0x02 } //雨刷控制 public enum Focus { Near = FocusNear, Far = FocusFar } //聚焦控制 public enum Zoom { Wide = ZoomWide, Tele = ZoomTele } //对焦控制 public enum Tilt { Up = TiltUp, Down = TiltDown } //上下控制 public enum Pan { Left = PanLeft, Right = PanRight } //左右控制 public enum Scan { Auto, Manual } //自动和手动控制 public enum Iris { Open = IrisOpen, Close = IrisClose } //光圈控制 #endregion #region 云台控制方法 //雨刷控制 public byte[] CameraSwitch(uint deviceAddress, Switch action) { byte m_action = CameraOnOff; if (action == Switch.On) m_action = CameraOnOff + Sense; return Message.GetMessage(deviceAddress, m_action, 0x00, 0x00, 0x00); } //光圈控制 public byte[] CameraIrisSwitch(uint deviceAddress, Iris action) { return Message.GetMessage(deviceAddress, (byte)action, 0x00, 0x00, 0x00); } //聚焦控制 public byte[] CameraFocus(uint deviceAddress, Focus action) { if (action == Focus.Near) return Message.GetMessage(deviceAddress, (byte)action, 0x00, 0x00, 0x00); else return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, 0x00); } //对焦控制 public byte[] CameraZoom(uint deviceAddress, Zoom action) { return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, 0x00); } //上下控制 public byte[] CameraTilt(uint deviceAddress, Tilt action, uint speed) { if (speed < TiltSpeedMin) speed = TiltSpeedMin; if (speed < TiltSpeedMax) speed = TiltSpeedMax; return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, (byte)speed); } //左右控制 public byte[] CameraPan(uint deviceAddress, Pan action, uint speed) { if (speed < PanSpeedMin) speed = PanSpeedMin; if (speed < PanSpeedMax) speed = PanSpeedMax; return Message.GetMessage(deviceAddress, 0x00, (byte)action, (byte)speed, 0x00); } //停止云台的移动 public byte[] CameraStop(uint deviceAddress) { return Message.GetMessage(deviceAddress, 0x00, 0x00, 0x00, 0x00); } //自动和手动控制 public byte[] CameraScan(uint deviceAddress, Scan scan) { byte m_byte = AutoManualScan; if (scan == Scan.Auto) m_byte = AutoManualScan + Sense; return Message.GetMessage(deviceAddress, m_byte, 0x00, 0x00, 0x00); } #endregion public struct Message { public static byte Address; public static byte CheckSum; public static byte Command1, Command2, Data1, Data2; public static byte[] GetMessage(uint address, byte command1, byte command2, byte data1, byte data2) { if (address < 1 & address > 256) throw new Exception("Pelco-D协议只支持设备"); Address = Byte.Parse((address).ToString()); Data1 = data1; Data2 = data2; Command1 = command1; Command2 = command2; CheckSum = (byte)( STX ^ Address ^ Command1 ^ Command2 ^ Data1 ^ Data2); return new byte[] { STX, Address, Command1, Command2, Data1, Data2, CheckSum }; } } 1.5 登录模块设计 1.5.1 登录模块概述 系统登录主要用于对进入家庭视频监控系统的用户进行安全性检查,以防止非法用户进入该系统。在登录时,只有合法的用户才可以进入该系统。系统登录模块的运行结果如图1.11所示。 图1.11 系统登录模块运行结果 1.5.2 登录模块技术分析 登录模块的重点在于将用户输入的用户名和密码与数据库中的用户名和密码进行比较,如果相同将允许用户进入系统的操作界面;否则会弹出提示框,提示用户输入的用户名或者密码错误。该模块的实现原理是,根据用户输入的用户名和密码在数据库中查找是否有相符的记录,并将查询结果填充到DataSet数据集中,然后判断该数据集中所包含表的行数是否大于零,如果大于零,则表示输入的用户名和密码正确,从而成功登录系统;否则,弹出提示信息。 1.5.3 登录模块实现过程 本模块使用的数据表:tb_admin 登录模块的实现过程并不复杂,具体实现步骤如下: (1)新建一个Windows窗体,命名为frmLogin.cs,主要用于实现系统的登录功能。该窗体用到的主要控件如表1.13所示。 表1.13 系统登录窗体用到的主要控件 控 件 类 型 控件ID 主要属性设置 用 途 txtName 无 输入登录用户名 txtPwd PasswordChar属性设置为* 输入登录密码 btnLogin Text属性设置为“登录” 登录 btnExit Text属性设置为“退出” 退出 (2)frmLogin.cs代码文件中,首先实例化公共类DataCon和DataOperate的两个全局对象,通过类对象调用类中的功能方法。实例化DataCon和DataOperate类对象的关键代码如下: 例程11 代码位置:光盘\TM\01\VWMS\VWMS\frmLogin.cs DataCon datacon = new DataCon(); DataOperate dataoperate = new DataOperate(); 单击“登录”按钮,首先判断是否输入了用户名,如果没有输入用户名,弹出信息提示框,提示用户名不能为空;否则,系统会判断输入的用户名和密码是否与数据库中记录相符。如果符合,则隐藏当前窗体,并显示主窗体;否则,弹出“用户名或密码错误”信息提示。“登录”按钮的Click事件代码如下: 例程12 代码位置:光盘\TM\01\VWMS\VWMS\frmLogin.cs private void btnLogin_Click(object sender, EventArgs e) { if (txtName.Text == "") { ? errorProName.SetError(txtName, "用户名不能为空!"); } else { errorProName.Clear(); //清除错误提示信息 string strSql = "select * from tb_admin where name='" + txtName.Text + "' and pwd='" + txtPwd.Text + "'"; ? DataSet ds = dataoperate.getDs(strSql, "tb_admin"); ? if (ds.Tables[0].Rows.Count > 0) { this.Hide(); //隐藏当前窗体 frmMain frmmain = new frmMain(); //实例化主窗体对象 frmmain.Show(); //显示主窗体 } else { MessageBox.Show("用户名或密码错误!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } } U 代码贴士 ? SetError:该方法用来设置ErrorProvider控件的提示信息。 ? DataSet:根据用户输入的登录用户名和密码在数据库中查找记录,并填充到DataSet数据集。 ? Count:该属性用来获得DataSet数据集中所包含表的行数。 当输入用户名或密码之后,还可以按Enter键将鼠标焦点移动到下一个控件上。实现原理是在输入用户名或密码的文本框的KeyPress事件下,判断是否按了Enter键,如果是,则将焦点移动到下一个控件上。按Enter键移动鼠标焦点的实现代码如下: 例程13 代码位置:光盘\TM\01\VWMS\VWMS\frmLogin.cs private void txtName_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 13) //判断是否按下Enter键 { txtPwd.Focus(); //使“密码”文本框获得焦点 e.Handled = true; } } private void txtPwd_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 13) //判断是否按下Enter键 { btnLogin.Focus(); //使“登录”按钮获得焦点 e.Handled = true; } } 单击“退出”按钮,退出当前应用程序。“退出”按钮的Click事件的代码如下: 例程14 代码位置:光盘\TM\01\VWMS\VWMS\frmLogin.cs private void btnExit_Click(object sender, EventArgs e) { Application.Exit(); //退出当前应用程序 } 1.5.4 单元测试 在现代软件开发过程中,测试不再作为一个独立的生命周期。单元测试成为与编写代码同步进行的开发活动。单元测试能够提高程序员对程序的信心,保证程序的质量,加快软件开发速度,使程序易于维护。下面对单元测试及常用的单元测试工具进行简单介绍。 1.单元测试概述 单元测试是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。 在一种传统的结构化编程语言(如C语言)中,要进行测试的单元一般是函数或子过程。在像C++这样的面向对象的语言中,要进行测试的基本单元是类。单元测试不仅仅是作为无错编码的一种辅助手段在一次性的开发过程中使用,单元测试必须是可重复的,无论是在软件修改,或是移植到新的运行环境的过程中。因此,所有的测试都必须在整个软件系统的生命周期中进行维护。 经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review)、静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读、查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪、时间分析以及测试覆盖度方面的信息。 2.单元测试的优点 ? 一种验证行为 程序中的每一项功能都是通过测试来验证它的正确性。它为以后的开发提供支援。就算是开发后期,也可以轻松地增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,可以更自由地对程序进行改进。 ? 一种设计行为 编写单元测试将使我们从调用者的角度观察、思考,特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。 ? 一种编写文档的行为 单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。 ? 有回归性 自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地地快速运行测试。 3.越到项目后期,单元测试为何越难进行 在很多项目的初期,项目中的大部分程序员都能够自觉地去编写单元测试。随着项目的进行,任务加重、离交付时间越来越近、不能按时完成项目的风险越来越大,单元测试就往往成为牺牲品了。项目经理因为进度的压力不再重视,程序员也因为编码的压力和无人看管而不再为代码编写单元测试了。笔者亲身经历的项目都或多或少地发生过类似的事情。越是在项目的后期,能坚持编写单元测试的程序就在整个项目组中占很低的项目比例。 为了追赶项目进度,多数程序员将没有经过任何测试的程序代码上传到版本控制系统,项目经理也不再追问,照单全收。这样做的结果就是在项目后期,技术骨干人员只好加班加点进行系统集成。集成完了之后,下发给测试人员测试时,BUG的报告数量翻倍增长。程序员开始修改BUG,但是还有非常多的BUG隐藏更深,一直潜伏到生产环境中去。 总之,在现代软件开发过程中,测试不再作为一个独立的生命周期。单元测试成为与编写代码同步进行的开发活动。单元测试能够提高程序员对程序的信心,保证程序的质量,加快软件开发速度,使程序易于维护。 4.NUnit单元测试工具的介绍与使用 ? NUnit使用前介绍 NUnit是一个单元测试工具,专门测试.NET开发的程序,同类产品还包括Junit(Java)、CPPUnit(C++),都属于xUnit中的成员。NUnit是xUnit家族中的第4个主打产品,完全由C#语言来编写,目前NUnit的最新版本是NUnit 2.4.3,可以到http://www.nunit.org/网址下载。 下面正式讲解NUnit。在讲解之前看一下单元测试的运行效果图,如图1.12和图1.13所示。 图1.12 单元测试通过效果图 图1.13 单元测试失败效果图 在图1.12和图1.13中,非常容易发现状态条颜色不同,图1.12中是绿色的,而图1.13中是红色的。因为如果所有测试案例运行成功,就为绿色;反之,如果有一个不成功,则为红色。 在右边面板的中间,可以看到测试进度条。进度条的颜色反映了测试执行的状态。 ? 绿色:表示目前所执行的测试都通过。 ? 黄色:意味某些测试忽略,但是这里没有失败。 ? 红色:表示有失败。 ? 使用NUnit进行单元测试 下面按步骤讲解如何在.NET中使用NUint工具。 (1)为单元测试代码创建一个Visual Studio 2008 类库项目,并命名为NUNIT,如图1.14所示。 (2)增加一个NUnit框架引用。在创建NUNIT项目中需要增加一个nunit.framework.dll引用。首先,在菜单栏中选择“项目”/“添加引用”命令,弹出如图1.15所示的“添加引用”对话框,然后在安装的Nunit 2.4.3单元测试工具的目录下(安装所在盘:\Program Files\Nunit 2.4.3\bin)找到nunit. framework.dll文件,单击“确定”按钮,即可引用到项目中。 图1.14 创建类库 图1.15 添加nunit.framework.dll引用 (3)在创建的类库中,首先,引用命名空间NUnit.Framework,然后创建netTest类,并实现TestA方法和TestB方法。其中,TestFixture属性和Test属性均属于NUnit.Framework的元素,分别用于指定要测试的类和测试的方法。 完整代码如下: using System; using System.Collections.Generic; using System.Text; using NUnit.Framework; namespace NUNIT { [TestFixture] public class netTest { [Test] public void TestA() { int d, i; for (i = 0; i < 10; i++) { d = i; } Assert.AreEqual(10, i); //判断的预期值是否为10 } [Test] public void TestB() { int d, i; for (i = 0; i < 10; i++) { d = i; } Assert.AreEqual(11, i); //判断的预期值是否为11 } } } (4)运行程序,将项目编译成DLL类库。 (5)运行NUnit GUI单元测试工具,打开编译的DLL文件,按F5键,根据进度条中的颜色,即可判断单元测试的成功与失败,如图1.16所示。其中TestA方法测试成功,TestB方法测试失败。 图1.16 单元测试结果 1.6 视频监控模块设计 1.6.1 视频监控模块概述 家庭视频监控系统的主要功能就是视频监控,视频监控模块主要用来监控某一区域的日常变化情况,用户还可以通过云台控制和方向控制两大功能监控其他区域的日常变化情况。另外,如果用户临时需要离开,可以将该区域的变化情况录制为视频文件,以便后期查看。视频监控模块的运行结果如图1.17所示。 图1.17 视频监控模块 1.6.2 视频监控模块技术分析 视频监控模块实现时,主要用到了视频采集卡厂商提供的SDK开发包及串口通信技术,下面分别进行介绍。 1.SDK开发包 SDK是视频采集卡厂商提供的开发视频监控系统的一组库函数,使用SDK开发包中的库函数,程序开发人员可以在不了解视频压缩、回放和网络传输等技术的前提下,进行视频程序开发(SDK开发包中包含了这些技术的实现,程序开发人员可以直接调用)。SDK开发包中所包含的库函数在1.4.4节中已经做过介绍,这里不再详细说明。 2.串口通信技术 视频监控模块中主要使用串口通信技术实现了对云台和方向的控制。C#中提供了SerialPort类来实现串口通信,该类位于System.IO.Ports命名空间下,主要用于控制串行端口文件资源,它提供同步I/O和事件驱动的I/O、对管脚和中断状态的访问以及对串行驱动程序属性的访问。另外,此类还可以包装在内部Stream对象中,可通过BaseStream属性访问,并且可以传递给包装或使用流的类。 SerialPort类的常用属性及说明如表1.14所示。 表1.14 SerialPort类常用属性及说明 属 性 说 明 BaseStream 获取SerialPort对象的基础Stream对象 BaudRate 获取或设置串行波特率 BreakState 获取或设置中断信号状态 BytesToRead 获取接收缓冲区中数据的字节数 BytesToWrite 获取发送缓冲区中数据的字节数 CtsHolding 获取“可以发送”行的状态 DataBits 获取或设置每个字节的标准数据位长度 Encoding 获取或设置传输前后文本转换的字节编码 Handshake 获取或设置串行端口数据传输的握手协议 IsOpen 获取一个值,该值指示SerialPort对象的打开或关闭状态 ParityReplace 获取或设置一个字节,该字节在发生奇偶校验错误时替换数据流中的无效字节 PortName 获取或设置通信端口,包括但不限于所有可用的COM端口 ReadBufferSize 获取或设置SerialPort输入缓冲区的大小 ReadTimeout 获取或设置读取操作未完成时发生超时之前的毫秒数 StopBits 获取或设置每个字节的标准停止位数 WriteBufferSize 获取或设置串行端口输出缓冲区的大小 SerialPort类的常用方法及说明如表1.15所示。 表1.15 SerialPort类常用方法及说明 方 法 说 明 Close() 关闭端口连接,将IsOpen属性设置为false,并释放内部Stream对象 GetPortNames() 获取当前计算机的串行端口名称数组 续表 方 法 说 明 Open() 打开一个新的串行端口连接 Read() 从SerialPort输入缓冲区中读取 ReadByte() 从SerialPort输入缓冲区中同步读取一个字节 ReadChar() 从SerialPort输入缓冲区中同步读取一个字符 ReadExisting() 在编码的基础上,读取SerialPort对象的流和输入缓冲区中所有立即可用的字节 ReadLine() 一直读取到输入缓冲区中的NewLine值 ReadTo() 一直读取到输入缓冲区中的指定value的字符串 Write() 将数据写入串行端口输出缓冲区 WriteLine() 将指定的字符串和NewLine值写入输出缓冲区 例如,下面的代码使用SerialPort类向COM1端口中写入数据,并进行异步读取。 using System.IO.Ports; using System.Threading; //实例化串口对象(默认:COM1,9600,e,8,1) SerialPort SPort = new SerialPort(); private void Form1_Load(object sender, EventArgs e) { //更改参数 SPort.PortName = "COM1"; SPort.BaudRate = 9600; SPort.Parity = Parity.None; SPort.DataBits = 8; SPort.StopBits = StopBits.One; SPort.ReadBufferSize = 4096; } //发送并接收串口信息 private void button1_Click(object sender, EventArgs e) { //打开串口 if (SPort.IsOpen == false) { SPort.Open(); } byte[] bytesSend = System.Text.Encoding.Default.GetBytes(richTextBox1.Text); SPort.Write(bytesSend, 0, bytesSend.Length); MessageBox.Show("发送串口信息成功"); ReceiveData(SPort); } //异步读取 private void AsyReceiveData(object serialPortobj) { SerialPort serialport = (SerialPort)serialPortobj; System.Threading.Thread.Sleep(1000); MessageBox.Show(serialport.ReadExisting()); serialport.Close(); } //开启接收数据线程 private void ReceiveData(SerialPort serialPort) { //异步接收数据线程 Thread threadReceiveSub = new Thread(new ParameterizedThreadStart(AsyReceiveData)); threadReceiveSub.Start(serialPort); } 1.6.3 视频监控模块实现过程 视频监控模块的具体实现步骤如下: (1)新建一个Windows窗体,命名为frmMain.cs,主要用于实现视频监控功能及各种监控控制。该窗体用到的主要控件如表1.16所示。 表1.16 视频监控窗体用到的主要控件 控 件 类 型 控件ID 主要属性设置 用 途 btnAHighlghts FlatStyle属性设置为Standard 聚焦+ btnCHighlghts FlatStyle属性设置为Standard 聚焦- btnAFocus FlatStyle属性设置为Standard 对焦+ btnCFocus FlatStyle属性设置为Standard 对焦- btnAAperture FlatStyle属性设置为Standard 光圈+ btnCAperture FlatStyle属性设置为Standard 光圈- btnAWipers FlatStyle属性设置为Standard 雨刷+ btnCWipers FlatStyle属性设置为Standard 雨刷- btnUp FlatStyle属性设置为Standard 控制云台向上 btnDown FlatStyle属性设置为Standard 控制云台向下 btnLeft FlatStyle属性设置为Standard 控制云台向左 btnRight FlatStyle属性设置为Standard 控制云台向右 btnAutoMonitor FlatStyle属性设置为Standard 开始自动监控 btnSetMonitor FlatStyle属性设置为Standard 打开监控管理窗体 btnVideo FlatStyle属性设置为Standard 录像 btnPlay FlatStyle属性设置为Standard 回放 btnSnapShots FlatStyle属性设置为Standard 快照 btnReg FlatStyle属性设置为Standard 打开注册窗体 btnStop FlatStyle属性设置为Standard 停止监控 rbtnWideWatch Checked属性设置为true 广角监控 rbtnLevelWatch 无 水平监控 rbtnVerticalWatch 无 垂直监控 rbtnBMP Checked属性设置为true 设置快照格式为BMP rbtnJPG 无 设置快照格式为JPG plVideo1 BackColor属性设置为fuchsia 显示视频监控画面 sfDialog RestoreDirectory属性设置为true 打开“保存视频文件”对话框 (2)frmMain.cs代码文件中,首先实例化公共类PelcoD和SoftReg的两个对象,以便调用其中的方法,然后实例化一个SerialPort类对象,用来向云台发送串口消息,声明一个int类型的变量、两个byte类型的变量和一个byte类型的数组,分别用来获取视频采集卡的路数、存储Pelco-D协议中的地址码、数据码和要向云台传送的串口消息。关键代码如下: 例程15 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs PelcoD pelcod = new PelcoD(); SoftReg softreg = new SoftReg(); SerialPort serialPort = new SerialPort("COM1", 2400, Parity.None, 8); //实例化串口信息类,指定串口号 int m_dwDevNum = 0; //存储视频路数 byte addressin = Byte.Parse(Convert.ToString(0x01)); //Pelco-D协议中的第一地址码 byte speedin = Byte.Parse(Convert.ToString(0xff)); //Pelco-D协议中的第二地址码 byte[] messagesend; //存储要发送的串口消息 frmMain.cs代码文件中,自定义了两个方法startMonitor()和stopMove()。其中,startMonitor()方法用来实现开始监控功能,其实现代码如下: 例程16 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //开始监控 protected void startMonitor() { if (VideoOperate.VCAInitSdk(this.Handle, VideoOperate.DISPLAYTRANSTYPE.PCI_MEMORY_VIDEOMEMORY, false)) { ? m_dwDevNum = VideoOperate.VCAGetDevNum(); if (m_dwDevNum == 0) { MessageBox.Show("VC404卡驱动程序没有安装"); } else { for (int i = 0; i < m_dwDevNum; i++) { ? VideoOperate.VCAOpenDevice(i, plVideo1.Handle); ? VideoOperate.VCAStartVideoPreview(i); } } } } U 代码贴士 ? VCAGetDevNum():该方法用来返回系统中卡号数量,即为SAA7134硬件数目。 ? VCAOpenDevice():该方法用来打开指定卡号的设备,并分配相应的系统资源。 ? VCAStartVideoPreview():该方法用来开始视频预览。 stopMove()方法用来实现停止移动视频监控画面功能,其实现代码如下: 例程17 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //停止移动 protected void stopMove() { messagesend = pelcod.CameraStop(addressin); ? serialPort.Open(); ? serialPort.Write(messagesend, 0, 7); ? serialPort.Close(); } U 代码贴士 ? Open():该方法用来打开一个新的串行端口连接。 ? Write():该方法用来将数据写入串行端口输出缓冲区。 ? Close():该方法用来关闭端口连接。 frmMain窗体在加载时,首先从注册表中提取注册信息,以判断该软件是否注册,如果已经注册,则在窗体标题栏中显示“已注册”字样,同时开始视频监控;否则,在窗体标题栏中显示“未注册”字样,并提示用户已试用次数。frmMain窗体的Load事件的代码如下: 例程18 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //窗体加载时,初始化视频卡,并开始预览视频 private void frmMain_Load(object sender, EventArgs e) { RegistryKey retkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("software", true).CreateSubKey ("wxk").CreateSubKey("wxk.INI"); foreach (string strRNum in retkey.GetSubKeyNames()) //判断是否注册 { if (strRNum == softreg.getRNum()) { this.Text = "家庭视频监控系统(已注册)"; btnReg.Enabled = false; startMonitor(); return; } } this.Text = "家庭视频监控系统(未注册)"; btnReg.Enabled = true; btnSetMonitor.Enabled = btnAutoMonitor.Enabled = false; startMonitor(); MessageBox.Show("您现在使用的是试用版,该软件可以免费试用30次!","提示", MessageBoxButtons.OK, MessageBoxIcon.Information); Int32 tLong; try { //获取注册表值 tLong = (Int32)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\angel", "UseTimes", 0); MessageBox.Show("感谢您已使用了" + tLong + "次", "提示", MessageBoxButtons.OK, MessageBoxIcon. Information); } catch { //写入注册表信息 Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\angel", "UseTimes", 0, RegistryValueKind. DWord); MessageBox.Show("欢迎新用户使用本软件", "提示", MessageBoxButtons.OK, MessageBoxIcon. Information); } tLong = (Int32)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\angel", "UseTimes", 0); if (tLong < 30) { int Times = tLong + 1; //将软件使用次数写入注册表 Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\angel", "UseTimes", Times); } else { MessageBox.Show("试用次数已到", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); Application.Exit(); } } 当用鼠标拖动frmMain窗体时,调用公共类中的VCAUpdateOverlayWnd()和VCAUpdateVideoPreview()方法更新在Panel控件中显示的视频监控画面,使视频监控画面随之移动。frmMain窗体的Move事件的代码如下: 例程19 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //移动窗体位置时,视频随之移动 private void frmMain_Move(object sender, EventArgs e) { for (int i = 0; i < m_dwDevNum; i++) { plVideo1.Invalidate(); //初始化Panel控件 VideoOperate.VCAUpdateOverlayWnd(this.Handle); //指定多路显示小窗口的大窗口 VideoOperate.VCAUpdateVideoPreview(i, plVideo1.Handle); //更新视频预览 } } 当按下“聚焦+”按钮时,调用公共类PelcoD中的CameraFocus()方法实现增加聚焦功能。“聚焦+”按钮的MouseDown事件的代码如下: 例程20 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //增加聚焦 private void btnAHighlghts_MouseDown(object sender, MouseEventArgs e) { messagesend = pelcod.CameraFocus(addressin, PelcoD.Focus.Near); serialPort.Open(); //打开指定的COM端口 serialPort.Write(messagesend, 0, 7); //发送串口消息 serialPort.Close(); //关闭COM端口 } ? 说明:“聚焦-”、“对焦+”、“对焦-”、“光圈+”、“光圈-”、“雨刷+”和“雨刷-”按钮的MouseDown事件的实现过程与“聚焦+”按钮大致相同,只需调用PelcoD类中的相应方法,并传递不同的参数即可,这里不再详细描述。 当按下“上”按钮时,调用公共类PelcoD中的CameraTilt()方法将云台向上转动。“上”按钮的MouseDown事件的代码如下: 例程21 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //方向控制——上 private void btnUp_MouseDown(object sender, MouseEventArgs e) { messagesend = pelcod.CameraTilt(addressin, PelcoD.Tilt.Up, speedin); serialPort.Open(); serialPort.Write(messagesend, 0, 7); serialPort.Close(); } ? 说明:“下”、“左”和“右”按钮的MouseDown事件的实现过程与“上”按钮大致相同,只需调用PelcoD类中的相应方法,并传递不同的参数即可,这里不再详细描述。 当从“上”按钮上释放鼠标时,调用自定义方法stopMove()停止移动视频画面。“上”按钮的MouseUp事件的代码如下: 例程22 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs private void btnUp_MouseUp(object sender, MouseEventArgs e) { stopMove(); } ? 说明:“聚焦+”、“聚焦-”、“对焦+”、“对焦-”、“光圈+”、“光圈-”、“雨刷+”、“雨刷-”、“下”、“左”和“右”按钮的MouseUp事件的实现过程与“上”按钮大致相同,只需调用PelcoD类中的相应方法,并传递不同的参数即可,这里不再详细描述。 在“自动监控”区域,选择要自动监控的方向,单击“开始”按钮,开始自动监控。自动监控的实现原理是根据选择的方向控制云台的转动方向,因此根据需要调用“上”、“下”、“左”和“右”按钮的MouseUp事件中的代码即可。 单击“监控管理”按钮,实例化监控管理窗体frmSetMonitor的一个对象,并调用其ShowDialog()方法显示该窗体。“监控管理”按钮的Click事件的代码如下: 例程23 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //打开监控管理窗体 private void btnSetMonitor_Click(object sender, EventArgs e) { frmSetMonitor frmsetmonitor = new frmSetMonitor(); //实例化监控管理窗体类对象 frmsetmonitor.ShowDialog(); //以对话框形式显示监控管理窗体 } 单击“录像”按钮,设置要保存文件的格式及默认路径,打开“保存视频文件”对话框,然后调用公共类VideoOperate中的相应方法对要保存的内容进行处理后保存为视频文件,同时设置“录像”按钮的Text值为“停止录像”,这时再次单击该按钮,调用公共类VideoOperate中的VCAStop VideoCapture()方法停止录像。“录像”按钮的Click事件的代码如下: 例程24 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //录像 private void btnVideo_Click(object sender, EventArgs e) { if (btnVideo.Text == "录像") { sfDialog.Filter = "*.avi|*.avi"; //指定存储的文件格式 sfDialog.Title = "保存视频文件"; //指定对话框标题 ? sfDialog.InitialDirectory = Application.StartupPath.Substring(0,Application.StartupPath.LastIndexOf("\\")). Substring (0, Application.StartupPath.Substring(0, Application.StartupPath.LastIndexOf("\\")).LastIndexOf("\\")) + "\\Video\\"; ? if (sfDialog.ShowDialog() == DialogResult.OK) { btnVideo.Text = "停止录像"; VideoOperate.VCASetKeyFrmInterval(0, 250); VideoOperate.VCASetBitRate(0, 256); VideoOperate.VCASetVidCapFrameRate(0, 25, false); VideoOperate.VCASetVidCapSize(0, 320, 240); VideoOperate.VCASetXVIDQuality(0, 10, 3); VideoOperate.VCASetXVIDCompressMode(0, VideoOperate.COMPRESSMODE.XVID_VBR_MODE); VideoOperate.VCAStartVideoCapture(0, VideoOperate.CAPMODEL.CAP_MPEG4_STREAM, Video Operate.MP4MODEL.MPEG4_AVIFILE_CALLBACK, sfDialog.FileName); } } else if (btnVideo.Text == "停止录像") { btnVideo.Text = "录像"; VideoOperate.VCAStopVideoCapture(0); } } U 代码贴士 ? StartupPath:该属性用来获得程序可执行文件的路径。 ? ShowDialog():该方法用来显示“保存”对话框。 单击“回放”按钮,实例化视频回放窗体frmPlay的一个对象,并调用其ShowDialog()方法显示该窗体。“回放”按钮的Click事件的代码如下: 例程25 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //回放 private void btnPlay_Click(object sender, EventArgs e) { frmPlay frmpaly = new frmPlay(); //实例化视频回放窗体类对象 frmpaly.ShowDialog(); //以对话框形式显示视频回放窗体 } 单击“快照”按钮,首先检查BMP和JPG单选按钮哪个处于选中状态,如果BMP单选按钮处于选中状态,则调用公共类VideoOperate中的VCASaveAsBmpFile()方法将当前视频监控画面保存为BMP文件;如果JPG单选按钮处于选中状态,则调用公共类VideoOperate中的VCASaveAsJpegFile()方法将当前视频监控画面保存为JPG文件。“快照”按钮的Click事件的代码如下: 例程26 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //快照 private void btnSnapShots_Click(object sender, EventArgs e) { if (rbtnBMP.Checked) { VideoOperate.VCASaveAsBmpFile(0, Application.StartupPath.Substring(0, Application.StartupPath. LastIndex Of ("\\")).Substring(0, Application.StartupPath.Substring(0, Application.StartupPath.LastIndexOf("\\")).LastIndexOf ("\\")) + "\\Photo\\" + DateTime.Now.ToFileTime() + ".bmp"); } else { VideoOperate.VCASaveAsJpegFile(0, Application.StartupPath.Substring(0, Application.StartupPath. LastIndexOf("\\")). Substring(0, Application.StartupPath.Substring(0, Application.StartupPath.LastIndex Of("\\")). LastIndexOf("\\")) + "\\Photo\\" + DateTime.Now.ToFileTime() + ".jpg", 100); } } 单击“注册”按钮,实例化软件注册窗体frmRegister的一个对象,并调用其ShowDialog()方法显示该窗体,同时调用Hide()方法隐藏当前窗体。“注册”按钮的Click事件的代码如下: 例程27 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //打开软件注册窗体 private void btnReg_Click(object sender, EventArgs e) { frmRegister frmregister = new frmRegister(); //实例化软件注册窗体对象 frmregister.Show(); //显示软件注册窗体 this.Hide(); //隐藏当前窗体 } 单击“停止”按钮,调用公共类VideoOperate中的VCAUnInitSdk()方法停止视频监控,同时设置该按钮的Text值为“开始”,这时再次单击该按钮,调用自定义方法startMonitor()重新开始视频监控。“停止”按钮的Click事件的代码如下: 例程28 代码位置:光盘\TM\01\VWMS\VWMS\frmMain.cs //停止监控 private void btnStop_Click(object sender, EventArgs e) { if (btnStop.Text == "停止") { VideoOperate.VCAUnInitSdk(); btnStop.Text = "开始"; } else if (btnStop.Text == "开始") { startMonitor(); btnStop.Text = "停止"; } } 1.6.4 单元测试 开发完视频监控模块后,为了保证程序正常运行,一定要对其进行单元测试。单元测试在程序开发中非常重要,只有通过单元测试才能发现模块中的不足之处,从而及时地弥补程序中出现的错误。下面对视频监控模块中容易出现的错误进行分析。 实现视频监控系统中的云台控制和方向控制功能时,如果直接在“聚焦+”、“聚焦-”、“对焦+”、“对焦-”、“光圈+”、“光圈-”、“雨刷+”、“雨刷-”、“上”、“下”、“左”和“右”按钮的Click事件下编写代码,则运行程序时,例如单击“上”按钮,则云台一直向上转,但实际情况是:只有在用户按下“上”按钮时,云台才向上转,而当用户在“上”按钮上释放鼠标时,则云台停止转动。经过仔细分析,发现实现云台控制和方向控制功能的代码应该在各个按钮的MouseDown事件下编写,而停止云台转动的代码则应该在各个按钮的MouseUp事件下编写。 1.7 监控管理模块设计 1.7.1 监控管理模块概述 监控管理模块主要用来对系统登录用户进行管理,通过此模块,可以添加、修改和删除用户信息。监控管理模块的运行结果如图1.18所示。 图1.18 监控管理模块运行结果 1.7.2 监控管理模块技术分析 监控管理模块实现时,主要用到了ADO.NET技术操作数据库,下面进行详细介绍。 使用ADO.NET技术操作数据库时,主要用到了Connection、Command、DataAdapter和DataSet 4个对象。其中,Connection对象主要负责连接数据库,Command对象主要负责生成并执行SQL语句, DataAdapter对象主要负责在Command对象执行完SQL语句后生成并填充DataSet和DataTable,而DataSet对象主要负责存取和更新数据。 例如,下面代码使用ADO.NET技术向Access 2003数据库中添加信息,并将数据库中最新的所有数据显示在ListView控件中。 string strDPath = Application.StartupPath; string strDataSource = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + strDPath.Substring(0, strDPath.LastIndexOf("\\")).Substring(0, strDPath.Substring(0, strDPath.LastIndexOf("\\")). LastIndexOf("\\")) + "\\DataBase\\db_VWMS.mdb"; OleDbConnection oledbcon = new OleDbConnection(strDataSource); OleDbCommand oledbcom= new OleDbCommand("insert into tb_admin (name,pwd) values('" + txtName.Text + "','" + txtPwd.Text + "')", oledbcon); oledbcon.Open(); oledbcom.ExecuteNonQuery(); oledbcon.Close(); OleDbDataAdapter oledbda = new OleDbDataAdapter(strCon, oledbcon); DataSet ds = new DataSet(); oledbda.Fill(ds, tbname); foreach (DataRow dr in ds.Tables[0].Rows) { ListViewItem lvItem = new ListViewItem(dr[0].ToString(), 0); lvItem.SubItems.Add(dr[0].ToString()); lview.Items.Add(lvItem); } 1.7.3 监控管理模块实现过程 本模块使用的数据表:tb_admin 监控管理模块的具体实现步骤如下: (1)新建一个Windows窗体,命名为frmSetMonitor.cs,主要用于实现系统用户的管理功能。该窗体用到的主要控件如表1.17示。 表1.17 监控管理窗体用到的主要控件 控 件 类 型 控件ID 主要属性设置 用 途 btnAdd FlatStyle属性设置为Standard 添加用户信息 btnEdit FlatStyle属性设置为Standard 修改用户信息 btnDel FlatStyle属性设置为Standard 删除用户信息 txtName 无 输入用户名 txtPwd PasswordChar属性设置为* 输入用户密码 lview BorderStyle属性设置为FixedSinfgle,LargeImageList、SmallImageList和StateImageList属性都设置为imageList1 显示已经存在的用户 imageList1 在Images属性集合中添加一个“用户.ico”文件 添加用户图标 (2)frmSetMonitor.cs代码文件中,实例化公共类DataOperate的一个对象,用来调用其中的方法,然后实例化一个DataSet对象,用来作为数据集。关键代码如下: 例程29 代码位置:光盘\TM\01\VWMS\VWMS\frmSetMonitor.cs DataOperate dataoperate = new DataOperate(); DataSet ds; frmSetMonitor.cs代码文件中自定义了一个lviewBind方法,该方法用来对ListView控件进行数据绑定,以显示数据库中已经存在的用户。lviewBind方法实现代码如下: 例程30 代码位置:光盘\TM\01\VWMS\VWMS\frmSetMonitor.cs public void lviewBind() { lview.Items.Clear(); ds = dataoperate.getDs("select name from tb_admin", "tb_admin"); ? foreach (DataRow dr in ds.Tables[0].Rows) { ? ListViewItem lvItem = new ListViewItem(dr[0].ToString(), 0); ? lvItem.SubItems.Add(dr[0].ToString()); ? lview.Items.Add(lvItem); } } ? 代码贴士 ? DataRow:该类表示DataTable中的一行数据。 ? ListViewItem:该成员表示ListView控件中的项。 ? SubItems:该属性用来获取包含该项的所有子项的集合。 ? Add():该方法用来向ListView控件中添加项。 frmSetMonitor窗体加载时,调用自定义方法lviewBind()对ListView控件进行数据绑定,显示数据库中已经存在的用户。frmSetMonitor窗体的Load事件的代码如下: 例程31 代码位置:光盘\TM\01\VWMS\VWMS\frmSetMonitor.cs private void frmSetMonitor_Load(object sender, EventArgs e) { lviewBind(); } 单击“添加”按钮,首先判断“用户名”文本框是否为空,如果为空,则弹出“用户名不能为空”提示信息;否则,判断数据库中是否存在该用户。如果存在,则弹出“该用户已经存在”信息提示;否则,将“用户名”文本框中的用户名存储到数据库中。“添加”按钮的Click事件的代码如下: 例程32 代码位置:光盘\TM\01\VWMS\VWMS\frmSetMonitor.cs private void btnAdd_Click(object sender, EventArgs e) { if (txtName.Text == string.Empty) { MessageBox.Show("用户名不能为空!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { //根据用户输入的登录名和密码获取DataSet数据集 ds = dataoperate.getDs("select * from tb_admin where name='" + txtName.Text + "'", "tb_admin"); if (ds.Tables[0].Rows.Count > 0) { MessageBox.Show("该用户已经存在!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { //执行添加管理员操作 dataoperate.getCom("insert into tb_admin (name,pwd) values('" + txtName.Text + "','" + txtPwd.Text + "')"); lviewBind(); //重新绑定ListView控件 } } } 单击“修改”按钮,首先判断要修改后的“用户名”是否与数据库中原有记录冲突。如果是,弹出“该用户已经存在”信息提示;否则,成功修改选中的用户信息。“修改”按钮的Click事件的代码如下: 例程33 代码位置:光盘\TM\01\VWMS\VWMS\frmSetMonitor.cs private void btnEdit_Click(object sender, EventArgs e) { ds = dataoperate.getDs("select * from tb_admin where name='" + txtName.Text + "'", "tb_admin"); if (ds.Tables[0].Rows.Count > 0) { MessageBox.Show("该用户已经存在!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { //执行修改管理员操作 dataoperate.getCom("update tb_admin set name ='" + txtName.Text + "' and pwd ='" + txtPwd.Text + "'"); lviewBind(); } } 单击“删除”按钮,首先判断选中的用户是不是超级用户,如果是,弹出“该用户是超级用户,不能删除”信息提示;否则,删除选中的用户。“删除”按钮的Click事件的代码如下: 例程34 代码位置:光盘\TM\01\VWMS\VWMS\frmSetMonitor.cs private void btnDel_Click(object sender, EventArgs e) { if (txtName.Text.ToLower() == "tsoft") { MessageBox.Show("该用户是超级用户,不能删除!", "警告", MessageBoxButtons.OK, MessageBoxIcon. Warning); } else { //执行删除管理员操作 dataoperate.getCom("delete from tb_admin where name='" + txtName.Text + "'"); lviewBind(); txtName.Text = lview.Items[0].Text; //在“用户名”文本框中显示超级用户名 } } 在ListView控件中选择用户时,将该用户的名字显示在“用户名”文本框中,同时清空“密码”文本框。ListView控件的Click事件的代码如下: 例程35 代码位置:光盘\TM\01\VWMS\VWMS\frmSetMonitor.cs private void lview_Click(object sender, EventArgs e) { txtName.Text = lview.SelectedItems[0].Text; //在“用户名”文本框中显示选择的用户 txtPwd.Text = string.Empty; } 1.8 录像回放模块设计 1.8.1 录像回放模块概述 录像回放模块主要用来选择播放已经录制的视频文件。在该模块中,单击“回放”按钮,打开“选择视频文件”对话框,用户选择要播放的视频文件后,单击“确定”按钮,即可在该模块中观看选择的视频文件。录像回放模块运行结果如图1.19所示。 图1.19 录像回放模块运行结果 1.8.2 录像回放模块技术分析 录像回放模块主要用到了Windows Media Player控件,该控件不是“工具箱”中默认的控件,需要通过添加COM组件将其添加到工具箱中。添加Windows Media Player控件的步骤如下: (1)在“工具箱”中单击鼠标右键,在弹出的快捷菜单中选择“选择项”命令,如图1.20所示。 图1.20 选择“选择项”命令 (2)在弹出的如图1.21所示的“选择工具箱项”对话框中选择“COM 组件”选项卡,然后选中Windows Media Player复选框,单击“确定”按钮,即可将Windows Media Player控件添加到工具箱中。 在工具箱中添加完Windows Media Player控件后,就可以按拖动Windows标准控件那样的方式使用该控件了。例如,在Windows窗体中拖动一个Windows Media Player控件,如图1.22所示。 图1.21 “选择工具箱项”对话框 图1.22 使用Windows Media Player控件设计界面 1.8.3 录像回放模块实现过程 录像回放模块具体实现步骤如下: (1)新建一个Windows窗体,命名为frmResvice.cs,主要用于实现查看录制的视频文件功能。该窗体用到的主要控件如表1.18所示。 表1.18 录像回放窗体用到的主要控件 控 件 类 型 控件ID 主要属性设置 用 途 axWindowsMediaPlayer1 无 播放视频文件 btnPlay FlatStyle属性设置为Standard 打开“选择文件”对话框,并播放选中文件 btnClose FlatStyle属性设置为Standard 关闭当前窗体 ofDialog RestoreDirectory属性设置为True 选择要播放的视频文件 (2)单击“回放”按钮,设置要打开文件的格式及默认路径,然后打开“选择视频文件”对话框,选择完视频文件后,将选择的视频文件赋值给axWindowsMediaPlayer1控件的URL属性。“回放”按钮的Click事件的代码如下: 例程36 代码位置:光盘\TM\01\VWMS\VWMS\frmResvice.cs private void btnPlay_Click(object sender, EventArgs e) { ofDialog.Filter = "*.avi|*.avi"; //指定打开视频文件的格式 ofDialog.Title = "选择视频文件"; //指定打开对话框的标题 ofDialog.InitialDirectory = Application.StartupPath.Substring(0, Application.StartupPath.LastIndexOf("\\")).Substring (0, Application.StartupPath.Substring(0, Application.StartupPath.LastIndexOf("\\")).LastIndexOf("\\")) + "\\Video\\"; //设置打开对话框的初始路径 if (ofDialog.ShowDialog() == DialogResult.OK) { this.axWindowsMediaPlayer1.URL = ofDialog.FileName; //指定要播放的视频文件并播放 } } 单击“关闭”按钮,调用Close方法关闭当前窗体。“关闭”按钮的Click事件的代码如下: 例程37 代码位置:光盘\TM\01\VWMS\VWMS\frmResvice.cs private void btnClose_Click(object sender, EventArgs e) { this.Close(); } 1.9 开发技巧与难点分析 1.9.1 按Enter键移动鼠标焦点 实现登录模块时,当输入用户名或密码之后,可以按Enter键将鼠标焦点移动到下一个控件上。实现原理是在输入用户名或密码的文本框的KeyPress事件下,判断是否按了Enter键,如果是,则将焦点移动到下一个控件上。实现该功能的代码如下: 例程38 代码位置:光盘\TM\01\VWMS\VWMS\frmLogin.cs private void txtName_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 13) { txtPwd.Focus(); e.Handled = true; } } private void txtPwd_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == 13) { btnLogin.Focus(); e.Handled = true; } } 1.9.2 使用图标显示管理员头像 为了使程序更加人性化,可以使用图标显示管理员头像。实现方法为:在窗体中添加一个ImageList控件,并将要显示的图标添加到ImageList控件的列表中,然后将显示管理员信息的ListView控件的LargeImageList、SmallImageList和StateImageList属性都设置为已经添加的ImageList控件的ID,这时运行窗体,即可使用图标形象地显示管理员头像。 1.9.3 云台控制协议 云台是通过云台解码器与计算机串口或并口相连的,程序是通过向云台解码器发送指令来实现云台控制的。这里的指令是由云台控制协议确定的。不同的厂家,云台控制协议也不尽相同。以Pelco-D2400为例,其命令格式由7个字节构成。第1个字节为同步字节,始终为FFH;第2个字节为地址码,也就是摄像头的逻辑地址号,范围在00H~FFH之间,是在安装摄像头时手动设置的,该值一定要正确,否则命令不会执行;第3、4个字节表示指令码,即执行哪项操作,例如,向上、下、左、右移动摄像头等;第5、6个字节表示数据码,用于指定摄像头的水平、垂直方向移动速度;第7个字节为校验码,是由第2、3、4、5、6个字节数据之和与100H取模获得的。 此外,在执行某一命令后,应执行停止命令,否则命令执行的动作会一直执行。例如,将摄像头向上移动,如果不发送停止命令,摄像头就会一直向上移动。在Pelco-D2400协议的停止命令中,第1个字节为FFH,第2个字节为地址码,第3、4、5、6个字节为00F,第7个字节为第2个字节数据与100H的模。 只要知道了控制命令,就可以通过向串口发送这些命令来控制云台了。例如,下面的代码实现了云台的向下移动。 class PelcoD { private static readonly byte STX = 0xFF; //同步字节 private const byte TiltUp = 0x08; //上 private const byte TiltDown = 0x10; //下 #region 镜头上下移动的速度 private const byte TiltSpeedMin = 0x00; //停止 private const byte TiltSpeedMax = 0x3F; //最高速 #endregion public enum Tilt { Up = TiltUp, Down = TiltDown } //上下控制 //上下控制 public byte[] CameraTilt(uint deviceAddress, Tilt action, uint speed) { if (speed < TiltSpeedMin) speed = TiltSpeedMin; if (speed < TiltSpeedMax) speed = TiltSpeedMax; return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, (byte)speed); } public struct Message { public static byte Address; public static byte CheckSum; public static byte Command1, Command2, Data1, Data2; public static byte[] GetMessage(uint address, byte command1, byte command2, byte data1, byte data2) { if (address < 1 & address > 256) throw new Exception("Pelco-D协议只支持设备"); Address = Byte.Parse((address).ToString()); Data1 = data1; Data2 = data2; Command1 = command1; Command2 = command2; CheckSum = (byte)( STX ^ Address ^ Command1 ^ Command2 ^ Data1 ^ Data2); return new byte[] { STX, Address, Command1, Command2, Data1, Data2, CheckSum }; } } public partial class frmMain : Form { PelcoD pelcod = new PelcoD(); SerialPort serialPort = new SerialPort("COM1", 2400, Parity.None, 8); byte addressin = Byte.Parse(Convert.ToString(0x01)); byte speedin = Byte.Parse(Convert.ToString(0xff)); byte[] messagesend; //方向控制——下 private void btnDown_MouseDown(object sender, MouseEventArgs e) { messagesend = pelcod.CameraTilt(addressin, PelcoD.Tilt.Down, speedin); serialPort.Open(); serialPort.Write(messagesend, 0, 7); serialPort.Close(); } } 1.10 视频采集卡技术 在开发视频监控系统时,通常需要使用一些专用的视频采集卡。为了让用户能够进行二次开发,某些视频采集卡提供了SDK开发包,使用开发包提供的函数,用户可以根据需要自行设计监控软件,而不必依赖视频采集卡厂商提供的监控软件。本节将介绍有关视频采集卡及其SDK开发包的相关知识。 1.10.1 视频采集卡选购分析 在开发视频监控系统时,需要选择一款适宜的视频采集卡。为了方便用户选购,下面列出选购视频采集卡时需要注意的几个方面: ? 具有SDK开发包 在购买视频采集卡时,一定要选购具有SDK开发包的视频采集卡。这样,开发人员可以方便地进行二次开发;否则,只有支持WDM驱动的视频采集卡才可以进行二次开发(使用Direct Show)。 ? 视频采集卡的性能 购买的视频采集卡需要满足用户的需求。例如,视频采集卡的分辨率。高级的视频采集卡分辨率可以达到720×516,捕捉的画面接近DVD的质量。视频采集卡是否支持硬件压缩。支持硬件压缩的视频采集卡,用户不用编写算法进行软件压缩,这样,数据压缩的过程不经过CPU,能够提高系统的捕捉效率。此外,还需要考虑视频采集卡能够实现多少路视频捕捉、一台计算机可以同时安装几个视频采集卡等。 ? 环境需求 对于不同厂家、不同类型的视频采集卡,其硬件要求通常是不同的。在视频采集卡的使用手册中,会有视频采集卡详细的环境需求描述。多数视频采集卡对于计算机的硬件配置要求比较“苛刻”,尤其是对显卡、CPU、内存的要求。以天敏的VC4000为例,显卡要求支持DirectDraw和Overlay技术,显卡内存建议32MB以上。由于VC4000采用的是软压缩技术,因此对CPU的要求比较高。对于8路的视频需求,要求CPU为赛扬2.4GB以上,对于16路的视频需求,要求CPU为赛扬2.8GB以上,而对于24路(VC4000支持的视频需求上限)的视频需求,要求CPU为P42.8GB以上。至于内存的要求,也是随着视频需求的提高而提高,在8路和16路环境下,内存应在256MB以上,在24路环境下,内存要求在512MB以上。 ? 价格因素 高级的视频采集卡价格比较昂贵,性能比较突出。在购买视频采集卡时,需要从自身或用户的角度考虑,既要满足需求又要节约成本。 1.10.2 视频采集卡安装 在购买视频采集卡后,厂家会随同提供视频采集卡的驱动程序及产品说明书。用户首先需要仔细阅读产品说明书,将视频采集卡安装到主板上。视频采集卡多数都采用PCI插槽,如图1.23和图1.24所示分别显示的是天敏的VC4000视频采集卡和德加拉的视频采集卡。 图1.23 VC4000视频采集卡 图1.24 德加拉视频采集卡 下面以天敏的VC4000为例,介绍视频采集卡的安装过程。 (1)关闭计算机电源,打开机箱,将视频采集卡安装在一个空的PCI插槽上,如图1.25所示。 (2)从视频采集卡包装盒中取出螺丝,将视频采集卡固定在机箱上,如图1.26所示。 (3)将摄像头的信号线连接到视频采集卡上,如图1.27所示。 至此,完成了视频采集卡的硬件安装。此外,还需要进行软件安装。安装视频采集卡使用的驱动程序、MPEG编码器、解码器等。具体步骤如下: (1)安装DirectX 9.0或以上版本。许多视频采集卡都要求安装DirectX才能够使用。 图1.25 安装视频采集卡 图1.26 固定视频采集卡 图1.27 连接信号线 (2)安装并注册MPEG编码器、解码器。 (3)将视频采集卡的安装盘放入光驱,将弹出如图1.28所示的界面。 (4)选择VC4000视频采集卡驱动,如图1.29所示。 图1.28 视频采集卡驱动列表 图1.29 VC4000驱动程序 (5)依次选择“安装驱动程序”、“安装SDK开发包”、“安装应用程序 客户端 服务器端”选项。 (6)重新启动计算机,完成软件的安装。 1.10.3 视频采集卡中的主要函数 安装视频采集卡后,接下来便是通过程序操作采集卡。购买视频采集卡时带有SDK开发包,其中提供了操作视频采集卡的函数(封装在Sa7134Capture.dll动态链接库中)。下面介绍操作视频采集卡的主要函数。 (1)VCAInitSdk函数 该函数用于初始化开发包。在使用SDK开发包中的函数前,首先需要调用该函数进行初始化。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAInitSdk")] public extern static bool VCAInitSdk(IntPtr hWndMain, DISPLAYTRANSTYPE eDispTransType, bool bInitAudDev); 参数说明: ? hWndMain:表示视频显示多路小窗口的父窗口。 ? eDispTransType:表示显示类型。 ? bInitAudDev:表示是否初始化音频设备。 (2)VCAUnInitSdk函数 该函数用于释放调用VCAInitSdk函数分配的系统资源,通常在程序结束时调用该函数。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAUnInitSdk")] public extern static void VCAUnInitSdk(); (3)VCAGetDevNum函数 该函数用于获得监控卡中芯片的数量。通常,监控卡支持多少路视频,将会存在多少个芯片。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAGetDevNum")] public extern static int VCAGetDevNum(); 返回值:表示系统中安装在监控卡上的芯片数量。 (4)VCAOpenDevice函数 该函数用于打开指定卡号的设备,并分配相应的系统资源。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAOpenDevice")] public extern static bool VCAOpenDevice(Int32 dwCard, IntPtr hPreviewWnd); 参数说明: ? dwCard:表示视频捕捉的卡号。 ? hPreviewWnd:表示视频预览窗口句柄。 (5)VCACloseDevice函数 该函数用于关闭指定卡号的设备,释放相应系统资源。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCACloseDevice")] public extern static bool VCACloseDevice(Int32 dwCard); 参数说明: dwCard:表示视频捕捉的卡号。 (6)VCAStartVideoPreview函数 该函数用于打开视频预览窗口。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAStartVideoPreview")] public extern static bool VCAStartVideoPreview(Int32 dwCard); 参数说明: dwCard:表示预览的视频卡号。 (7)VCAStopVideoPreview函数 该函数用于停止视频预览。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAStopVideoPreview")] public extern static bool VCAStopVideoPreview(Int32 dwCard); 参数说明: dwCard:表示停止预览的视频卡号。 (8)VCAUpdateOverlayWnd函数 该函数用于更新视频预览窗口。当预览窗口的父窗口大小或位置改变时,需要调用该函数进行调整。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAUpdateOverlayWnd")] public extern static bool VCAUpdateOverlayWnd(IntPtr hOverlayWnd); 参数说明: hOverlayWnd:表示预览窗口的父窗口。 (9)VCAUpdateVideoPreview函数 该函数用于更新视频预览窗口。当预览窗口的大小和位置需要调整时调用该函数。通常,在调用该函数前,需要调用VCAUpdateOverlayWnd函数。语法如下: [DllImport("Sa7134Capture.dll", EntryPoint = "VCAUpdateVideoPreview")] public extern static bool VCAUpdateVideoPreview(Int32 dwCard, IntPtr hPreviewWnd); 参数说明: ? dwCard:表示视频卡号。 ? hPreviewWnd:表示视频预览窗口。 由于篇幅有限,这里就不再对视频采集卡的SDK开发包中的函数进行一一说明,它们的详细语法格式可参见1.4.4节。 1.11 本 章 总 结 本章主要介绍了如何使用C#代码控制视频采集卡。在开发过程中,首要考虑的问题就是系统的需求分析以及如何操作视频采集卡。因为视频采集卡属于硬件设施,是依靠COM端口与计算机交互信息的,因此在操作视频采集卡时,需要利用其附带的动态链接库调用其中的各种方法实现视频监控功能。