引言
现有的视频监控系统主要有模拟视频监控系统、数字视频监控系统和网络视频监控系统。模拟视频监控系统因为扩展能力差、传输距离短,只适用于较小范围区域的监控;数字视频监控系统采用光端机将采集的模拟视频信号传输到客户端,然后转换成数字视频进行保存,其弊端是不仅两端都要布置光端机,而且远距离的传输需要铺设专门的电缆,这样就使得成本较高,维护也比较困难;网络视频监控系统采用IP Camera将采集到的模拟视频在本地转换并压缩为视频流,并采用当前价格低廉、应用广泛的IP网络作为传输媒介,客户端只需要开发相应的流媒体播放器即可实现远程视频监控。
网络视频监控系统目前正朝着高清化、集成化和智能化的方向发展,这就对IP Camera提出了更高的要求。华为海思SoC芯片Hi3507集成了H.264编解码协处理器,独特的MPP系统为上层应用开发提供了MPI接口,降低了开发者的难度和开发成本。本文提出了一种基于Hi3507的视频监控系统的设计方案,可广泛用于银行、超市、交通道路等场所。
1 系统总体设计
本系统分为3大部分:IP Camera模块,流媒体服务器模块和客户端模块。IP Camera模块用来完成视频图像的采集、编码任务,由SoC芯片Hi3507、图像传感器OV2643、存储器等外围器件组成,是整个系统中的核心模块。系统工作流程如下:主芯片 Hi3507控制图像传感器OV2643采集模拟图像并完成模数转换,转换后的视频图像经digital camera接口送入主芯片内处理,Hi3507 MPP系统的VI模块捕获视频图像后首先交由VPP模块进行编码前的图像处理,再通过AHB总线将其写入外存(DDR2 SDRAM)中,VENC模块从外存中读取原始图像进行H.264编码处理,编码后的视频流重新写入外存中,然后流媒体服务器从外存中读取视频流并将其封装成RTP数据包转发到客户端,最后客户端开发相应的客户端播放器,对视频编码流解码后就可以看到高清的实时监控画面。图1是视频监控系统的总体设计图。
图1 系统总体设计图
2 系统硬件设计
Hi3507是一款基于ARM926EJS处理器内核和视频硬件加速引擎的高性能通信媒体处理器,具有高集成、可编程、支持H.264和MJPEG等多协议的优点,广泛应用于实时视频通信、网络摄像机等领域。视频处理单元支持高清720P@30fps,支持H.264和MJPEG双码流同时编码,H.264编码算法极大地提高了视频质量,同时提供场编码或者帧编码,灵活支持不同的显示终端,图形处理单元提供运动检测功能,并支持视频、图形缩放和OSD[1]。
视频采集设备采用Omni Visio公司生产的CMOS图像传感器OV2643,该芯片采用2.2 μmOmniPixel3HS架构,允许1250 mV/luxsec光照敏感度,支持自动曝光、自动白平衡、透镜校正、图像边缘增强等功能。具备200万像素,并支持最高300万像素下的抓拍模式,可以在30 fps下完成720P高清视频的拍摄,同时支持UXGA和SVGA两种模式。该芯片支持两路输入,输出格式支持YUV422/YCbCr422、GBR422、RGB565/555和RAW RGB [2]。
OV2643图像传感器与Hi3507主芯片的通信是通过I2C总线协议完成的。OV2643的SIO_C和SIO_D引脚分别与Hi3507的SCL和SDA引脚连接,Hi3507通过I2C总线对OV2643的寄存器进行配置来实现OV2643芯片的初始化和过程控制,将OV2643的HREF和VSYNC分别作为行同步信号和帧同步信号输入,OV2643通过DATA[0:7]端口将8位图像数据从VI0DAT[2:9]输入,同时输出像素时钟(PCLK)。视频采集模块硬件接口如图2所示。
图2 视频采集模块硬件接口
在网络视频监控系统中,存储模块分为DDR2 SDRAM和NOR Flash两类,存储模块是视频压缩过程中非常重要的部分,DDR2 SDRAM为软件系统运行提供物理内存,NOR Flash则用来存储系统软件的映像文件和上层应用程序。NOR Flash存储模块采用的是S29GL256N,Hi3507的SMI控制器对外提供异步静态存储器接口来连接NOR Flash,用以实现系统的启动和数据的存储等功能。DDR2 SDRAM采用HY5PS1G1631CFPY5,Hi3507的DDRC控制器对外提供DDR2接口,用于完成DDR2 SDRAM的访问。鉴于H.264算法数据量大的特点,此系统采用两片16位的DDR2 SDRAM与其连接,最大支持512 MB存储空间。以太网模块采用Realtek公司的网卡芯片RTL8201CP,Hi3507的以太网接口ETH提供MII接口与RTL8201CP连接,可以工作在10 Mb/s或者100 Mb/s,支持全双工或半双工工作模式,同时支持对网口的帧进行选择性过滤接收和流量限制功能。
3 嵌入式软件设计
嵌入式软件开发平台主要包括上位机Linux环境的搭建、交叉编译环境的搭建和操作系统的移植,开发和调试采用“宿主机+目标机”的模式进行,宿主机和目标机分别是PC机和IP Camera。宿主机采用Linux操作系统作为开发环境,解压并安装Hi3507的SDK,安装交叉编译环境、TFTP服务器和超级终端完成宿主机开发环境的搭建。操作系统的移植包括Bootloader、Linux内核和文件系统的移植。Bootloader是基于uboot1.4.4开发,根据Hi3507的相关硬件特性进行裁减,在宿主机上使用AXD调试软件并通过JTAG仿真器将Uboot烧写到目标板中,并设置从Flash中启动操作系统。同时根据应用需求配置并裁剪Linux内核和文件系统,Linux内核和文件系统分别是基于linux2.6.14和busybox1.1.2来开发的,可以通过网口下载到目标板中,目标板的开发环境搭建完毕。
图3 软件总体设计流程
目标板上电后,启动进入内核,加载Hi3507软件开发包提供的MPP系统中的各个接口和外围芯片驱动,只需调用其提供的API函数就可以完成上层应用软件的设计。本文重点阐述了IP Camera模块和流媒体服务器的软件开发,软件总体设计流程如图3所示。
3.1 IP Camera软件设计
IP Camera软件设计分为驱动程序设计和视频编码软件设计。驱动程序为应用程序提供访问接口,应用程序用来验证驱动程序的有效性,二者密不可分。
3.1.1 驱动程序设计
OV2643图像传感器负责视频图像的采集,首先要为其编写相应的驱动程序。驱动程序包括OV2643驱动程序和I2C总线驱动程序,这里用GPIO来模拟I2C总线。OV2643设备文件操作接口的数据结构如下所示:
static struct file_operations OV2643_fops = {
.owner = THIS_MODULE,
.read = OV2643_read,//读设备
.write = OV2643_write,//写设备
.ioctl = OV2643_ioctl,//控制命令
.open = OV2643_open,//打开设备
.release = OV2643_close,//关闭设备
};
驱动程序的主要任务就是填充文件操作接口数据结构中定义的子函数,OV2643_open和OV2643_close在设备打开或关闭时被调用,OV2643_read函数通过系统调用copy_to_user(buf, ®_data, count)把缓存数据从内核空间拷贝到用户空间处理。在驱动程序中,ov2643_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)是用户进程向OV2643设备进行I/O控制的方法。其中cmd是控制字,本系统涉及到的控制字有OV2643_CONFIG_720P、OV2643_CONFIG_VGA、OV2643_CONFIG_QVGA,分别操控3种不同分辨率视频的拍摄。
相关子函数编写完成后,需要编写module_init()和module_exit( )函数。module_init()是驱动程序的入口,它包括加载GPIO_I2C模块、注册OV2643设备、初始化OV2643设备。
3.1.2 视频编码软件设计
视频编码软件的设计包括系统初始化、视频采集和编码程序的设计,主函数中自定义了一个视频编码函数,同时创建一个线程来获取并存储视频流。视频编码软件设计流程图略——编者注。
Hi3507为软件开发者提供了专门的媒体软件处理平台MPP,MPP层基于驱动层,屏蔽了Hi3507芯片媒体处理硬件的操作细节,提供面向媒体业务的基本功能,为上层应用软件提供MPI接口。应用程序启动MPP业务前,必须完成MPP系统初始化工作,主要是视频缓存池和系统控制两大部分的初始化。视频缓存池主要为视频编码提供大块物理内存,负责内存的分配和回收,调用HI_MPI_VB_SetConf和HI_MPI_VB_Init完成视频缓存池的配置和初始化工作;系统控制则根据Hi3507芯片特性,完成硬件各个部件的复位、基本初始化工作,同时负责完成MPP系统各个业务模块的初始化、去初始化以及管理MPP系统各个业务模块的工作状态,调用HI_MPI_SYS_SetConf和HI_MPI_SYS_Init完成MPP系统配置和初始化。整个系统的初始化流程略——编者注。当应用程序退出MPP业务后,还需要调用HI_MPI_SYS_Exit和HI_MPI_VB_Exit函数完成MPP系统去初始化和释放资源[3]。
主函数中调用自定义函数SAMPLE_720pH264_Enable(video_setting)完成视频编码,采集参数定义在如下的video profile结构体中:
typedef struct video_profile{
unsigned int bit_rate;//比特率
unsigned int width;//图像宽度
unsigned int height;//图像高度
unsigned int framerate;//帧率
unsigned int gop_size;//图像组长度
}video_setting;
采集程序调用open("/dev/misc/ov2643",O_RDWR)函数打开设备文件,第一个参数代表设备文件打开路径,第二个参数表示以读、写的方式打开文件。成功打开设备后需要对OV2643进行配置,配置函数调用ioctl(fd,cmd)函数来配置OV2643,fd代表成功打开OV2643后返回的文件描述符,cmd代表控制字,配置函数根据主函数传递的采集参数来选择控制字,驱动函数为配置函数提供了OV2643_CONFIG_720P、OV2643_CONFIG_VGA、OV2643_CONFIG_QVGA三个控制字,用来支持拍摄3种不同分辨率的视频图像,在驱动程序中需要对3种不同采集方式的寄存器分别进行配置。
视频采集后进入Hi3507中进行处理和编码,Hi3507有一个VI设备接口,可支持一路视频输入,视频的编码是由VI和VENC两个模块完成的。VI模块用于视频捕获,其公共属性中设置视频输入接口模式为数字输入模式,VI捕获子流程略——编者注。VENC完成视频的编码,首先要配置H.264编码属性和编码通道属性,设置图像分辨率为1280×720,VI输入帧率和目标帧率为30 fps,采用帧编码模式和帧模式获取码流,配置完成后创建一个通道组,用于容纳通道,如果有多路编码,首先需要创建多个通道组,创建的通道组需与指定的VI通道完成绑定,然后创建所需要的编码通道并将其注册到指定的编码通道组,最后启动通道进行编码,H.264编码子流程略——编者注。编码完成后强制停止并销毁视频编码,其流程图略——编者注。
3.1.3 获取并存储视频编码流
创建线程获取和存储编码流,首先获取编码通道fd,选择该编码通道,如果出错或超时则退出,否则根据查询到当前帧中的码流包个数来分配结构体缓存大小,可以通过分配一个非常大的缓存来容纳任何情况下的一帧所有码流包。获取码流后对码流进行存储,还要进行释放码流,按照先取出先释放的顺序释放,尽早释放,防止码流阻塞,当释放完分配的码流包结构体缓存后开始下一个循环,具体流程图略——编者注。
3.2 流媒体服务器软件设计
本文采用支持RTP/RTCP、RTSP协议的live555开源项目来完成视频流的传输,首先要熟悉H.264的码流格式以及RTP封包发送过程。H.264基本流是由一系列的NALU组成,H.264压缩标准采用前缀码0x00 0001作为NALU的分隔符,
可以通过搜索前缀码0x00 0001来识别一个NALU,每个NALU单元由一个NALU头和若干个字节的载荷数据组成。NALU单元头格式如图4所示,其中TYPE是NALU中最重要的语法元素,它指出了NALU的类型。
图4 NALU单元头格式
每一个RTP数据包都由固定包头和载荷两部分组成,其头部格式是固定的,载荷是经压缩编码后的视频数据,RTP固定包头格式如图5所示。
图5 RTP固定包头
RTP数据包格式包含了传输媒体的类型、格式、序列号、时间戳和是否有附加数据等信息,为实时流媒体传输提供了相应的基础。使用RTP协议传输流媒体数据时,RTP协议首先从上层链路获取流媒体数据,加入RTP头后向下发送到UDP层,在UDP层加入UDP头后发送到IP层,最后在IP层加入IP头后通过网络传输到客户端。在RTP会话期间,参与者周期性地传送RTCP包,动态调整数据传输速率,实现流量控制和阻塞控制,以适应网络环境的变化。按照规范,RTP包和RTCP包会通过两个连续的UDP端口进行分流,RTP使用偶数端口,RTCP使用相邻的奇数端口[4],RTP包发送过程如图6所示。
图6 RTP包发送过程
RTSP协议主要用于控制流的网络传输,服务器端主要任务是从视频流中分离出每个NALU单元并封装成RTP包进行发送。本文采用live555开源项目来实现基于RTP/RTCP、RTSP协议的视频数据的流化、传输及处理,live555主要包括UsageEnvironment、BasicUsageEnvrionment、GroupSock、LiveMedia四个基本库和各种测试代码。流媒体服务器端接收到客户端发送的Play报文时开始视频流的传输,具体的视频流传输是通过子会话对象ServerMediasubsession中的Source和Sink来完成的,发送的实质是Sink从Source读取视频流的过程。RTP包的发送是从MediaSink::startplaying函数调用开始的,从Sink上调用startplaying,从Source读取一个帧的数据并返回给Sink,通过调用回调函数afterGettingFrame的方式,由发送数据函数SendPacketifnecessary()发送数据。一个包发送完成之后,若数据发送完,则停止;若没有发送完,则继续调用SendNext()函数来发送下一个包,下一个包又会调用buildAndSendPacket函数来组建新的RTP头。RTP打包发送函数调用流程图略——编者注。
客户端与服务器端的交互是通过Option、Describe、Setup、Play和Teardown五个报文来实现的,客户端通过向服务端发送建立RTSP会话请求并通过服务器认证后,就可以接收来自服务器端的RTP包,客户端从接收的RTP包中解析出NALU单元并进行解码播放,用户就可以看到实时高清的视频。
结语
系统测试证明,客户端视频播放流畅,系统运行稳定。该方案对于实时高清网络视频监控领域具有一定的借鉴意义。