资源描述
34IPTV机顶盒设计说明IPTV机顶盒终端技术说明1 引言机顶盒终端可以实现“网络电视”的功能,具有良好的人机交互界面,方便用户操作。具体为:终端接收来自IPTV平台服务器的音频视频流,将音视频流解码并将码流输出到电视上显示。用户采用遥控器来实现所有的操作功能。终端功能如下: 实现网络电视功能,即通过建立多个频道接收不同类型节目的音频/视频流,支持的媒体解码格式包括:MP3、WMA/WMV、MPEG-2、MPEG-4、AVI。 实现VOD(视频点播),用户可以根据喜好自行点播喜爱的电影。支持的媒体解码格式同上。 实现硬盘数字播放器功能,通过外接SATA硬盘,终端可以直接读取硬盘媒体内容并播放。支持的媒体解码格式同上。 实现图片浏览功能,用户可随意浏览移动存储介质中的图片,支持的图片格式包括:PNG,JPG,GIF,BMP。 实现家庭视频监控功能,通过外接摄像头可以记录被监控区域的图像变化并保存在外接存储设备上。 实现用户对系统参数的自由调节。 实现业务分级的节目管理控制。2 终端系统终端由流媒体编解码芯片(DSP)、流媒体接入模块与流媒体接入软件平台构成。研发工作分为两部分:研制支持多种编解码格式的流媒体编解码芯片;编写支持硬件平台运行,可实现良好人机交互的流媒体接入软件平台。2.1 终端的层次结构图2.2.1.1 终端系统的层次结构整个终端的层次结构如图2.2.1.1所示,分为三个层次:内核层:主要包括流媒体接入模块的硬件与流媒体解码芯片(机顶盒硬件),以及一个控制硬件的基本Linux内核(版本2.6.xx)。中间层:包括一个用于音视频直播/点播使用的视频传输协议栈;用于驱动流媒体解码芯片的驱动程序和外接存储设备以及摄像头的驱动程序;用于绘制界面、将解码后的数据显示并输出到电视上的DirectFB库。应用层:包括基于中间层开发的一个核心主程序以及模块化的各个模块。2.2 流媒体接入软件的系统结构图2.2.2.1 终端的系统结构由于嵌入式平台下面没有集成类似MFC的基础类库和完善的消息响应机制,故我们借助于DirectFB实现了一套自己的事件响应、处理机制。下面详细述之。用户事件缓冲区:基于DirectFB库的一个事件缓冲区,内建了遥控器、键盘、鼠标等事件的二进制码,在程序中会根据事先定义好的命令码将用户事件的二进制码转化为预定义的命令码,机顶盒终端只解析这些命令码,具体码表2.2.2.2如下:命令码功能命令码功能commandMenu,显示菜单commandDigit0数字 0 commandUp向上方向键commandDigit1数字 1commandDown向下方向键commandDigit2数字 2commandLeft向左方向键commandDigit3数字 3commandRight向右方向键commandDigit4数字 4commandPrevious选择前一级菜单commandDigit5数字 5commandChannelUp选择上一频道commandDigit6数字 6commandChannelDown选择下一频道commandDigit7数字 7commandVolume显示音量进度条commandDigit8数字 8commandVolumeUp增大音量commandDigit9数字 9commandVolumeDown减小音量commandShutdown关机commandMute静音commandPause播放中暂停commandOK确定commandFullscreen全屏commandInfo显示所选节目/频道的信息commandNone空命令表2.2.2.2 机顶盒接收的命令码表应用程序全局环境配置表:将一些全局需要的环境变量集中起来管理,在程序中具体为一个名为appControlInfo的数据结构。其包含的各个数据成员(全局环境变量参数)如表2.2.2.3:环境变量说明audioStatus描述音量状态(是否为静音)ipInfoscreenOutputs包含2个成员的数组,分别描述直播/点播播放的状态及网络信息soundInfo描述音量与音频解码选项的信息mediaInfo描述本地媒体文件的信息digitInfo描述使用遥控器时数字按键状态的信息timeInfo描述时间信息pictureInfo描述设置图像的信息outputInfo描述输出图像选项的信息imageInfo描述本体图片文件的信息scanActive目前未使用timeout描述流媒体解码芯片内部连接超时的标志displayCount描述显示图层的个数enableWatchdog是否启用watchdog线程表2.2.2.3 全局环境变量表应用程序管理器:负责整个程序的总体管理,如各个模块的初始化以及退出的销毁工作,其核心为一个消息命令循环。通过判断一个消息命令开关是否为真来决定是否继续执行消息命令的循环。其消息循环机制如图2.2.2.4所示:图2.2.2.4 应用程序管理器的消息循环机制菜单结构与控制模块:构建菜单(如菜单的大小,颜色,边框等参数),菜单的跳转控制等操作。机顶盒的流媒体接入软件平台的菜单结构是一个状态机,如图2.2.2.5所示:图2.2.2.5 菜单结构状态转移图每一个菜单界面是一个结构体menuContainer,记录了菜单的一些基本参数。其成员如下表2.2.2.6所示:成员名称说明_menuContainer *prev一个指向自己前一个菜单的指针,通过记录自己的前一个菜单来实现菜单的跳转int width定义了菜单的宽度int height定义了菜单的高度int posX定义菜单左上角的x坐标int posY定义菜单左上角的y坐标int highlight菜单目前是否高亮int selected菜单目前是否被选中int numEntries菜单包含几个菜单项int firstShownIndex菜单中的第一个显示的菜单项索引int lastShownIndex菜单中的最后一个显示的菜单项索引menuEntry menuEntryNUM_MENU_ENTRIES菜单中菜单项构成的数组menuHighlightFunction *pHighlight高亮菜单项的执行动作函数menuDrawFunction *pDraw菜单的自绘制函数表2.2.2.6 menuContainer结构的成员描述表其中menuEntry表示一个菜单项,同样它记录了一个菜单项所拥有的一些基本参数。其成员如下表2.2.2.7所示:成员名称说明char infoMENU_ENTRY_INFO_LENGTH菜单项的描述信息char iconMENU_ENTRY_ICONNAME_LENGTH菜单项的图标的描述信息menuActionFunction *pAction菜单项的执行动作函数void *pArg传递给执行动作函数的参数表2.2.2.7 menuEntry结构的成员描述表网络传输模块:接收并处理来自网络的数据流。媒体播放模块:包含了直播/点播/本地播放的解码播放处理与控制。移动检测模块:包含了启动监控设备、监控区域的图像采集、图像码流转码、图片比较分析、图片显示、整个模块的控制等功能。详细技术说明见3.2。图片浏览模块:分幻灯片与缩略图两种模式浏览图片。系统设置模块:分别实现对系统视频的亮度、对比度、饱和度、音量的设置。图形图像显示模块:使用封装好的DirectFB API接口直接将图形图像数据解码并显示。2.3 终端初始化2.3.1 应用程序管理器应用程序管理器为整个终端软件平台的核心,负责终端软件应用平台与操作系统的交互;接收用户的响应并分发命令给相应的模块执行;初始化并销毁各个模块。其初始化过程和销毁流程并非一一对应,原因主要是很多在堆栈中分配的空间会随着程序退出自动销毁,只有在堆上分配的空间才需要手工销毁。如涉及DirectFB的各个菜单与模块,随着DirectFB的销毁会自动销毁。其处理流程为图2.3.1.1所示:图2.3.1.1 应用程序管理器流程以下几个小结分别就重要的操作进行详细叙述。2.3.2 远程登录与EPG光纤到户流媒体接入系统支持用户认证与业务分级管理机制。通过在机顶盒终端内嵌用户名与授权密码,可以对用户进行身份授权认证,通过查询用户在服务器端的用户等级,可以实现不同用户有不同业务级别的分级管理机制,即不同级别的用户获取的EPG节目信息并不相同,可以收看的节目、使用的功能也不相同,从而实现了业务分级管理。终端机顶盒首先登录服务器端系统获取EPG节目信息数据(点播节目信息与直播节目信息),然后通过人机界面交互模块将节目信息显示到界面供用户选择。终端远程登录服务器端并获取EPG的流程如图2.3.2.1所示:图2.3.2.1 终端远程登录服务器并获取EPG流程(0.1&1)客户端网络连接初始化&客户端登录验证:由于传送的EPG节目信息为文本信息,故客户端与服务器端的连接采用可靠的TCP传送,服务器端通过固化的验证字符串对客户端进行验证。目前暂定的格式为:LOG#usernamepassword。如:LOG#whu12345。其初始化流程主要如图2.3.2.2所示:图2.3.2.2 客户端网络连接初始化流程(2)验证用户信息: 验证过程主要由服务器端通过数据库的查询来对客户端进行验证。其验证过程如图2.3.2.3所示:图2.3.2.3 服务器端对客户端的验证过程(5)客户端存储EPG数据到本地:EPG文件格式存储为文本格式,一共有2个,LIVE.TXT存储直播节目信息。VOD.TXT存储点播节目信息。其格式分别如图2.3.2.4和2.3.2.5格式:ChannelEntrychannelnameurlpidvpidapidpvformataformatchannel_descriptionprograms_contains_by_channelprogramname1programname2示例:ChannelEntryTiYuurl=rtsp:/192.168.101.188:554/musicpidv=1000pida=1002pidp=0fffvformat=0003aformat=0001entertainment channelprogNum=3dangerous_gameFor_My_Dearzc2图2.3.2.4 直播节目信息格式格式:VoDEntryprogramnameurlpidvpidapidpvformataformatprogram,_description示例:VoDEntryMr.Smith.by.20th.FOXurl=rtsp:/192.168.101.188:8554/0122pidv=0044pida=0045pidp=0044vformat=0003aformat=0002Mr.Smith is a movie which describes a killer couple.图2.3.2.4 点播节目信息格式对于任何一个直播节目和点播节目,都是采用ts流格式对原有的音视频流进行封装,而对ts码流进行解码,需要提供5个参数。它们分别是video_pid, audio_pid, pcr_pid, video_type, audio_type。分别对应上述的pidv,pida,pidp,vformat,aformat。(6)客户端解析EPG数据:将EPG文件解析为系统所要求的数据结构。 2.3.3 初始化DirectFBDirectFB是流媒体接入软件中图像显示的核心技术,同时为上层应用提供了音视频解码的接口调用,使得音视频编解码器的开发和上层应用开发完全隔离开来,方便了系统的扩展并缩短了应用开发的周期。下面就DirectFB的初始化工作做一个详细的介绍。其初始化流程如图2.3.3.1所示:图2.3.3.1 DirectFB的初始化流程 创建IDirectFB对象:任何一个使用DirectFB进行显示操作的应用都需要首先创建一个IDirectFB对象,它为整个DirectFB操作提供接口。 设置DirectFB的操作级别:将操作级别设置为全屏,因为在电视上显示的图像为全屏显示。 创建IDirectFBSurface对象:surface对象的接口,为渲染,状态控制,缓冲操作和调色板等提供操作接口。 创建字体对象:创建字体并设置字体的大小、内容和编码格式。 获取IDirectScreen对象:IDirectScreen是不同显示输出、编码器等的操作接口。 获取显示层的个数:终端的DirectFB共有5层。编号为04,其中:0层为主层(fb_layer),2层为图像层(image_layer),3层为画中画层(pip_layer),4层为视频显示主层(main_layer)。(详细解释见3.1) 1层暂时未用。上述4层按照0层在最上层,4层在最下层的顺序叠加。故当设置上面一层为透明时可以看到它下面一层的内容。 获取输出配置的参数:将获取的输出配置参数直接写入应用程序全局环境配置表。 清屏准备显示:实际上是在主层上绘制一个标准的全屏黑屏直接将其他层内容全部覆盖。 执行双缓冲互换操作并显示:DirectFB显示操作的一个特点支持双缓冲(甚至三缓冲)直接操作硬件的特点,将准备好的缓冲区内容通过IDirectFBSurface接口直接输出显示。2.3.4 建立信号处理器终端中直接调用Linux内核的signal函数将3个信号绑定到同一个信号处理器上,这个信号处理器用于终止主程序的命令循环,实际上就是退出程序。signal函数原型如下:sighandler_t signal (int signum, sighandler_t action)signum为绑定的信号,action为被绑定的信号处理器。sighandler_t为信号处理器函数的类型。绑定的3个信号分别为:SIGINT:用户按下Ctrl-c产生的中断信号SIGTERM:引起程序中断的通用中断信号SIGSEGV:内存非法读写产生的信号当系统捕获到上述三种信号时,通过信号处理器signal_handler将keepCommandLoopAlive标识置为0来退出程序的循环。2.3.5 初始化图片浏览图片浏览模块有一个属于自身的一个控制环境配置表,如表2.3.5.1所示:成员名称说明int state图片浏览状态int keepAlive是否持续浏览pthread_t thread浏览图片的线程标识displayWidth浏览图片的显示区域宽度displayHight浏览图片的显示区域高度char directory1024图片所处目录int imageFile当前图片的文件句柄int filter过滤选项int thumb方阵的个数int immediate立即切换图片选项int userFlickerFiltering是否防止显示图片抖动IDirectFBSurface* pImageBuffer动态创建的存储图片数据surface对象指针IDirectFBSurface* pFrameBuffer硬件图片缓冲区的surface对象指针表2.3.5.1 图片浏览模块的控制环境配置表在初始化图片浏览模块的过程中,首先对上述的控制环境配置表中的参数进行初始化;然后获取图像层的层ID;然后将图像层的透明度设置为255(即完全不透明);进而获取图像层的配置信息并设置显示区域矩形大小;然后通过图像层的surface对象获取图像层的缓冲区大小,设置缓冲区的背景色,填充缓冲区;最后创建一个单独用于对图片进行解码的线程。整个流程如图2.3.5.2所示:图2.3.5.2 图片浏览初始化模块2.3.6 初始化所有菜单菜单的初始化和菜单的绘制是两个不同的概念,菜单的初始化是指构建menuContainer和menuEntry中的各项参数内容,而绘制则是通过使用DirectFB技术将构建好的各个菜单显示出来。菜单初始化按照从上而下的顺序进行初始化(构建)。首先构建顶级菜单,然后构建各级菜单。各个菜单的构建过程大同小异,基本上都有以下步骤,如图2.3.6.1所示: 菜单menuContainer中的参数初始化:主要是指定菜单中的菜单项个数;指定本菜单的上一级菜单是哪一个(用于菜单回退跳转);指定菜单的起始菜单项索引和结束菜单项索引。 设置菜单位置:设置菜单的左上角坐标。 设置菜单的绘制函数:指定菜单进行绘制、重新绘制的函数。格式一般为XXX_drawXXX。 设置高亮选项:高亮选项指当前光标停留的菜单项,初始值为0。 设置被选中选项:被选中选项有别于高亮选项,指通过OK键(enter键)选中的菜单项。 显示菜单:将初始化的菜单结构显示出来,一般显示菜单的函数名称的格式为XXX_displayXXX(具体的关于终端菜单绘制、显示的细节见3.1)。图2.3.6.1 菜单初始化流程2.4 命令循环设置一个循环标识keepCommandLoopAlive。初始化为1。然后使用一个while循环控制整个循环。while ( keepCommandLoopAlive )/循环体整个循环流程如图2.4.1所示:整个循环其中很重要的一个if判断在于判断是否存在滑动条(pCurrentSilder是否为真),因为滑动条本身是一个菜单(menuContainer),它作为音量、视频文件播放进度的控制单元和其他菜单一样位于主层之上。不过它一般浮现于各级菜单之上。因此当有滑动条存在时,它会临时的建立与主层菜单之间的关系,所有存在与不存在滑动条,对于某些命令而言其执行的操作是不同的。另一个特殊情况就是数字命令的执行操作。当使用遥控器直接按下数字键操作系统时,可能存在连续按下2个键的情况(比如将音量调为78需要连续按下7和8,这时不是把音量调为7后再调为8)。在应用程序配置环境表中有digitInfo这个参数结构专门负责管理,这样当数字键状态(digitInfo.active)为真时,需要采取额外的操作。图2.4.1 命令循环流程图2.5 媒体播放媒体播放实际上涵盖了本地影片播放、视频直播、视频点播三个模块中关于媒体播放功能的总和。其主要是调用DirectFB中的VideoProvider接口类来完成播放功能。本小节主要描述终端中的媒体播放模块如何将缓存中的媒体数据渲染到指定图层上显示出来(播放操作)。凡直播和点播模块中涉及网络相关的操作(见3.3)一概略去不述。启动播放过程的核心为gfx_startVideoProvider函数,其函数原型为:void gfx_startVideoProvider(char* videoSource, int videoLayer, int force, char* options): 该函数实现播放功能参数名:videoSource 视频源的名称 videoLayer 播放视频所在图层 force 是否强制从头播放 options 附加选项返回值:无该函数主要调用更底层的DirectFB库接口实现DSP解码并反馈数据到帧缓冲中实现显示。具体的启动播放的流程如图2.5.1所示:图2.5.1 启动播放模块的流程图 判断文件或者URL的合法性:判断播放的文件与流媒体播放中URL是否为正确的参数,这样可以防止系统播放非法的文件或其他错误的URL,防止系统崩溃。 关闭其他播放线程:由于可能存在多个播放线程,考虑实际应用场景(用户不可能同时观看两个节目),为保证系统稳定性预先设计各线程同步的互斥量,这样保证了系统中只能存在一个播放线程。所以播放启动前,事先强行关闭其他所有播放线程。 获取图层ID:视频播放有两个层,4层和5层。视频点播在4层;而本地播放和视频直播在5层。 设置播放区域大小:由于播放存在全屏和预览两种模式,故在播放之前需要预先设定播放区域大小,预览模式的目标播放区域为一个指定大小的非全屏矩形;而全屏模式的目标播放区域为全屏矩形。 设置应用程序全局环境配置表:主要设置appInfo结构中的ipInfo和mediaInfo两个成员的某些参数,如状态信息。 开始播放:调用底层的接口播放缓存中的数据。启动和停止播放都是通过OK键来操作,通过判断mediaInfo或ipInfo中的active参数来判断是否处于正在播放的过程之中,从而判断是启动还是停止播放线程。2.6 图片浏览图片浏览是将存储介质上的图片通过各种显示模式显示出来。显示模式支持类似幻灯片式的上开、下开、左开、右开等多种。核心操作控制函数为一个线程函数slideShow_Thread,通过预先设定的宏定义模式,其流程如图2.6.1所示。图2.6.1 图片浏览线程slideShow_Thread流程图keepAlive为是否持续slideShow_Thread线程的标识。线程内部共定义了几种状态标识,分别为IAMGE_STOP、IMAGE_START、IMAGE_DECODED、IMAGE_DISPLAY、IMAGE_EXIT。通过switch开关语句来依次实现状态跳转。对图片实现解码渲染的核心函数为decodeImage。函数内通过DirectFB提供的关于图片解码的接口IDirectFBImageProvider来实现图片的解码、渲染。最终将解码后的图片数据显示出来是通过displayImage函数实现。displayImage实现了前述的多种图片的浏览方式,如上开,下开,左开,右开等。流程如图2.6.2。图2.6.2 displayImage函数流程下面简单说明左开方式的实现。在slideShowInfo控制结构中有两个参数pFrameBuffer和pImageBuffer。pImageBuffer存储了解码后的图片数据。而pFrameBuffer则是系统设备上图片层的surface对象指针。进入不接循环缓冲处理中,逐步按照x方向坐标拷贝pImageBuffer中的数据至pFrameBuffer之中,如图2.5.3所示,每次拷贝一个竖形条状矩形块,顺序为先拷贝深色,后拷贝浅色,这样,配以适当的延时就形成了向左开的效果。最终pImageBuffer中的数据会全部拷贝到pFrameBuffer之中。实现了整幅图片的显示。其它模式的显示原理与此类似。图2.6.3 左开模式实现示意图3 终端系统关键技术说明IPTV机顶盒终端是一个智能多媒体处理平台,这个平台可以看作是一台专门针对流媒体应用的计算机。但是它明显有别于普通的一般PC。主要区别在于它采用嵌入式芯片,针对特定的流媒体应用。它包括以下三项关键技术:嵌入式平台的图像显示技术;移动检测技术;环形视频缓冲区技术。3.1 嵌入式平台的图像显示技术3.1.1 概述嵌入式环境下的图像显示技术不同于普通PC,普通PC有专门的显卡来承担相关的计算工作,但是嵌入式平台限于体积、成本等特点不能配备独立的显卡。故我们采用DirectFB(Direct FrameBuffer)技术,即直接操作显示帧缓冲。DirectFB作为一项开源的技术,专门应用于Linux上具有FrameBuffer的设备之上。它是一个等同于QT、GTK+等开源技术的重量级库,具有硬件图形加速、输入设备管理、多层显示等功能。本平台使用了DirectFB的架构,如图3.1.1所示:图3.1.1 DirectFB的架构DirectFB使用的分层双缓冲技术。即同时使用两个帧缓冲区来加快显示过程,一个用于准备显示,另一个置入下一帧将要显示的内容。当需要显示下一帧时,直接将缓冲指针切换到另一个帧缓冲。好像两个缓冲不断的交替装载数据来实现显示。另一方面,DirectFB具有多层显示的功能,一般最多支持5层。DirectFB实现终端上的显示功能通过两种方式,一种是直接的绘制方式,这种一般直接通过IDirectFBSurface接口提供的各种接口操作函数如FillRectangle来实现,主层(即0层)上可以实现任意图像的绘制;另外的方式为渲染方式,是将视频、图片内容渲染到图层上显示,其余几层都为这种方式,使用的接口为DirectFB中的ImageProvider、VideoProvider等。渲染方式不允许在渲染的区域上直接绘制图形。但是不论采用直接绘制方式还是渲染方式,都可以设定目标显示区域,即是否全屏或者非全屏。显示的时候各层有一个叠加顺序,0层在最上,4层在最下。如果想显示下层的内容,必须将上一层的alpha通道置为0。这样下层的内容自然浮现上来。下面着重阐述几个关键的部分。3.1.2 预览窗口在直播、点播、本地影片都需要都视频预览,如果内容满足用户需要才有必要全屏浏览。首先在主层上绘制顶级菜单后绘制一个小矩形用于作为预览窗口,然后将矩形的透明度设为0,这样视频层的内容就可以浮现上来,实现预览效果,但是必须将预览时的视频目标显示区域设为上述的小矩形那般大小。3.1.3 全屏播放全屏播放需要将视频层整个屏幕大小的内容全部浮现上来,这时先要在主层上绘制一个全屏大小的透明菜单,并将这个菜单的前驱菜单设为当前菜单。以便可以从全屏播放退回到预览状态。当从预览窗口切换到全屏播放时,需要重新设置视频播放层的目标显示区域大小从预览窗口大小到全屏大小。3.1.4 界面显示过程每一个菜单界面都不相同,但是显示过程的原理大同小异,绘制的差别在于每个menuContainer中的pDraw成员,这是一个专门绘制菜单自身的函数,一般格式为XXX_drawXXX。当所有菜单全部构建完毕后(即完成所有的XXX_buildXXX函数之后)。调用menuApp_displayMain函数,这个函数继而调用menuInfra_display函数,传给这个函数的参数的参数就是指向topLevelMenu(顶级菜单)的指针。而menuInfra_display函数则直接调用topLevelMenu自身的draw函数topLevel_drawMenu函数绘制自身。每次当主界面发生改变时,都会引起调用topLevel_drawMenu函数来重新绘制主界面。其他界面的绘制过程都以此类推。其关系如图3.1.2所示:图3.1.2 显示函数的关系3.1.5 菜单的切换菜单的切换通过定义每个menuContainer的prev成员变量实现。每当切换到另一个菜单时,首先在主层上绘制将要显示的菜单,同时通过prev成员记录下上一级菜单的指针,这样就可以回退到上一级菜单了。而prev的赋值一般在进入某个菜单项的时候赋值。比如从顶级菜单进入直播菜单。首先要对直播菜单项的pAction成员变量赋值。pAction就是选中菜单项时执行的动作。这里执行的动作就是执行menuInfra_display(void*(&IpMenu)。3.2 移动检测技术3.2.1 概述移动检测主要是用来监控并记录被监控区域发生的场景变化,当被监控场景中发生了场景变化,终端可以发出报警信息并记录场景变化中的关键场景快照图片。终端的这项功能可用于简单的家庭视频监控,当开启终端切换到移动检测菜单,通过USB摄像头来扫描室内的情况,将读取的图像转换成RGB格式的图像信息,同时不断运用移动检测技术来比较相邻时间间隔中摄入图像的亮度和色彩,如有明显差别,则就表示有人或物体进入或移动,便记录这些存在差异的图片到终端存储设备上,同步显示给终端用户观看。同时根据需要在检测到场景变化的时候可以发出警报。3.2.2 整体过程首先在系统的启动脚本中我们直接启用了摄像头的驱动程序驱动摄像头工作,这实际上是初始化移动检测设备。然后用户通过主界面进入移动检测界面。整个移动检测模块的核心是一个移动检测线程motion_start线程函数,先给出其整个运行流程图3.2.2.1:图3.2.2.1 motion_start移动检测线程流程图3.2.3 设备初始化驱动摄像头通过一个类quickCam实现。quickCam类包含以下成员变量和成员函数:成员名称说明quickCam(string devname=/dev/video)与摄像头建立连接quickCam()释放开辟的缓存空间,与摄像头断开连接unsigned int set_fps (unsigned int fps)设置摄像头的采样频率unsigned int get_fps (void)获得摄像头的采用频率bool set_resolution (int width, int height)设置摄像头的分辨率void get_resolution (int * width, int * height)获得所采集图片的宽和高unsigned char * get_buffer (void)获得缓存原生图片的地址size_t get_buffer_size (void)获得图片的大小unsigned char * get_rgbbuffer(void)获取缓存RGB图片的地址bool take_photo ()采集图片bool yuv2rgb()将原生图片转换成RGB格式的图像信息enum ERRCODE 列举状态信息:正常,出错,警告等enum ERRCODE error返回 _error_codevoid _set_error设置_error_sring和_error_codevoid _unset_error(void)清除错误状态信息string _error_string错误描述enum ERRCODE _error_code错误码unsigned char * _buffer用以存储原生图片unsigned char * _rgb_buffer用以存储RGB图片int _fps摄像头采样频率int _image_width采集图片的宽度int _image_height采集图片的高度static const float _bytes_per_pixel = 1.5每像素占字节数bool _sync_settings (void)同步设置摄像头参数bool _setup_camera (void)启动摄像头void _usleep (unsigned long usecs)休眠usecs微秒int _devfd摄像头设备描述符表3.2.3.1 quickCam类摄像头的具体初始化过程如图3.2.3.2所示:图3.2.3.2 摄像头的初始化过程3.2.4 图像采集移动检测模块采用的是快照式的检测方式,故图像采集就是摄像头每隔一定的时间间隔便对被监测场景取景一次,记录在缓存中。其具体流程如图3.2.4.1所示:图3.2.4.1 图像采集流程图摄像头连接成功后,调用函数take_photo ()和yuv2rgb()分别来实现对场景的快照图像采集和图像格式的转换。take_photo ():完成对图片的采集,具体过程为首先调用mmap()将磁盘文件映像到内存中;通过访问被映射的内存结构体vmap就可以读出摄像头采集的图片的宽、高和类型;在未出现任何异常的情况下,将采集到的图片存储到缓存_buffer中。最后,调用munmap解除内存映像并把内存释放返回给操作系统。yuv2rgb():将原生图像转换成RGB格式的图像信息,并将转换后的图片存储到缓存_rgb_buffer中。继而,按照预定的象素值和图片格式来存储图像。其过程实际是一个拷贝过程。将缓冲区中的图片信息拷贝到应用程序中,然后再控制和处理图片数据。该过程就是通过拷贝函数memcpy(imagesbuffer_pos, cam.get_rgbbuffer(), IMAGE_BUFFER)来完成的。将转换后的RGB图像存储在*images指针数组中,方便后期的图像比较。当*image中存满五幅图片时,转入detect_motion()函数,比较五幅图片的差异。3.2.5 启动和停止移动检测移动检测菜单为motionMenu,启动移动检测菜单项的动作函数为motion_threadstart,停止移动检测菜单项的动作函数为motion_threadstop。移动检测模块的启动/结束调用过程如图3.2.5.1所示:图3.2.5.1 移动检测的启动/结束调用过程启动移动检测的动作函数motion_threadstart的具体过程如图3.2.5.2所示:图3.2.5.2 启动移动检测的具体过程其中值得注意的是在创建移动检测线程之前一般关闭其他所有的视频播放线程,这样既防止了资源竞争,也保证了系统的稳定性。这里主要主要创建了两个线程:motion_start和motionShow_start。下属小节有详细叙述。停止移动检测模块由motion_threadstart函数完成。其具体流程如图3.2.5.3所示:图3.2.5.3 停止移动检测的具体过程3.2.6 移动检测通过比较当前帧与平均帧的亮度和RGB来判断是否有移动发生,当其变化超过一定值时,我们便认为移动发生,返回相应值。平均帧的定义是将所有比较过的变化不大的帧信息总和取均值。几个重要函数如下:int detect_motion(const int width, const int height, const int no_images, const int current_image_pos, unsigned char *images): 该函数实现移动检测功能参数说明:width, height 待比较的图片的宽和高 no_images 缓存中图片的数目 current_image_pos 当前指针 *images 用于存储图片的指针数组返回值 0:表示未检测到移动; 1:表示检测到了移动;intmotion_index(struct PixelpixelblockMOTION_DETECTION_BLOCKMOTION_DETECTION_BLOCK, unsigned short int mode)mode:用来表示获取信息的类型,如:MOTION_DETECTION_INDEX_BRIGHTNESS、MOTION_DETECTION_INDEX_RED、MOTION_DETECTION_INDEX_BLUE等;返回值为 avg;通过调用int motion_index()来获取当前象素块的亮度(brightness)和RGB颜色值的3个分量red、blue、green的信息,如下:并运用到了int diff(int a, int b)函数,用来比较图片间的亮度和RGB,a: 用来表示平均值信息,如avg_brightness、avg_red、avg_blue等;b: 用来表示当前信息,如brightness、red、blue等;返回值为 a与b差异的绝对值。 其中MOTION_DETECTION_BRIGHTNESS,MOTION_DETECTION_RED等表示各信息分量的预定值。当差异大于相应的预定值时,便返回1并存储检测到移动时当前images数组中的图片。调用函数viod compress_jpeg()将imags数组中的RGB图像转换成JPG格式存储在相应的文件里。同时,执行函数insertMotionNode()将存储的图片名加到全局链表中。最后当线程结束时,释放所有的缓存空间。流程如图3.2.6.1所示:图3.2.6.1 移动检测的流程3.2.7 图像显示线程在检测到移动后,为便于终端用户观看到实时监测到的移动,在启动移动检测模块时我们创建了一个image_motionShowThread显示线程来实时反馈检测到的移动,实际上就是将检测到的移动场景按照幻灯片的方式依次放映给终端用户观看。image_motionShowThread线程的运行过程如图3.2.7.1所示:图3.2.7.1 image_motionShowThread线程流程图3.2.8 线程同步同步示意图如图3.2.8.1所示:图3.2.8.1 移动检测线程同步示意图motion_start线程在调用compress_jpeg()函数保存图片后,将图片的文件名插入到该链表中,更新pMotionInsertTail;如此同时,image_motionShowThread线程不断检测pMotionShowTail下面是否有新的结点,若有则调用displayImage()函数在移动检测界面的显示框里显示下一幅图片,同时,更新pMotionShowTail指针;若没有新图片加入,则继续检测。3.3 视频直播/点播与环形缓冲区3.3.1 概述视频点播、视频直播都涉及到网络相关的操作,2.4小节只说明了使用DirectFB实现具体播放的原理,而从网络上接收数据,缓存,流同步等操作都与缓冲区有关,本小节阐明了如何利用环形缓冲区实现网络流数据的接收,以及写缓存线程、读缓存线程的同步操作。终端接收来自网络的数据,不可能直接送入DSP解码芯片用于解码显示,必须首先存入缓冲区当中,当积累到一定的量时再将数据读入DSP中。缓冲区一般为在RAM中开辟的一段空间。而RAM是一种随机存储设备,不具有数据读写循环的特性,故我们需要人工制定一种读写规范,使得数据的读写好像是使用一段环形存储器,可以不断循环读写以实现数据的正确缓冲。我们将这种技术称之为环形缓冲区技术。由于视频点播的原理与视频直播一样,区别仅仅在于EPG信息不一样(点播较直播简单)。故3.3小节仅阐述视频直播。3.3.2 视频直播启动播放:直播首先通过函数ip_startVideo来初始化指定播放节目的解码参数以及网络参数(如IP地址和端口号),然后通过函数ip_startIP来初始化DSP设备文件,并为设备文件设置适当的参数准备接收数据流。最后创建一个从网络上读取数据的线程(即上图中的线程1),线程函数为ip_main。整个播放启动过程完毕。如图3.3.2.1所示:图3.3.2.1 直播播放启动流程网络实例:ip_instance。播放实例是一个全局的数据结构。终端共有两个播放实例,一个用于直播,一个用于点播。结构如表3.3.2.2所示:成员名称说明int fdaDSP设备文件描述符int fdvDSP设备文件描述符int fdpDSP设备文件描述符int selected是否选中struct dmx_pes_filter_params pesfiltervDSP视频解码参数选项struct dmx_pes_filter_params pesfilteraDSP音频解码参数选项struct dmx_pes_filter_params pesfilterpDSP pcr解码参数选项int streamnumber节目流编号ip_stream_info stream_info节目流信息结构pthread_t thread读取网络数据流ip_main线程的线程IDpthread_attr_t tattrip_main线程的线程属性表3.3.2.2 ip_instance结构其中ip_stream_info是一个记录单个节目(直播/点播)信息的结构,如表3.3.2.3所示:成员名称说明char streamname256节目流名称int pidaTS流视频解码参数int pidvTS流音频解码参数int pidpTS流PCR解码参数int vformat视频格式参数int aformat音频格式参数int device设备编号char videoProviderName256DSP中demux设备文件名称char inputDeviceName256DSP中dvr设备文件名称unsigned int port节目的端口号char ip256节目流的ip地址表3.3.2.3 ip_stream_info结构函数ip_startVideo和ip_startIP的初始化工作主要就是将上述两个结构的各个成员初始化。其中ip_stream_info中关于节目解码的参数streamname、pida、pidv、pidp、vformat、aformat、port、ip都来自于从网络上获取的EPG节目单。直播EPG格式见图2.3.2.4。停止播放:通过函数ip_stopVideo实现,流程如图3.3.2.4所示:图3.3.2.4 直播停止流程3.3.3 环形缓冲区启动视频直播时我们创建了ip_main线程用于接收网络流数据,ip_main线程接收的流数据实际上全部送入环形缓冲区。同时ip_main线程创建了读取环形缓冲区的线程ip_feed。线程ip_feed读取缓冲区中的数据并写入DSP设备文件。线程ip_main与ip_feed通过POSIX线程标准进行同步。环形缓冲区的示意图如图3.3.3.1所示:图3.3.3.1 环形缓冲区示意图RINGBUFFER_SIZE:环形缓冲区长度。dataBuffer:环形缓冲区的首指针。writePtr:ip_main线程进行写操作的指针。readPtr:ip_feed线程进行读操作的指针。dataLeft:缓冲区中writePtr指针与(dataBuffer+RINGBUFFER_SIZE)指针的距离差值。ip_main线程的写操作由函数afterReading完成。afterReading每次读取一帧(大小为framesize)的缓冲数据写入dataBuffer环形缓冲区。由于写入是循环的,所以需要先判断dataLeft和framesize的大小然后做出合适的写操作。过程如图3.3.3.2:图3.3.3.2 afterReading函数中写dataBuffer缓冲过程ip_feed线程的读操作要视writePtr与readPtr的位置而定。因为缓冲区为环形,如果dataLeft部分足够装下framesize大小的数据,则ip_feed可读的数据相当于(writePrt-readPtr);但当dataLeft小于framesize时,writePtr会从dataBuffer缓冲的头部开始重写,这样导致writePtr转到readPtr前部。故此时ip_feed可读的数据不是(writePtr-readPtr)。ip_feed函数都环形缓冲区数据如图3.3.3.3:图3.3.3.3 ip_feed线程中读取dataBuffer缓冲过程3.3.4 线程同步在ip_main中设置一个blockingFlag标识来配合Live555库中的接口实现线程的退出。在ip_feed中设置一个keepFeeding标识,当keepFeeding标识为真,则执行图3.3.3.3中的操作,否则将关闭DSP设备文件退出。ip_main线程与ip_feed线程通过POSIX标准中的条件等待方式实现互斥。ip_main线程中的afterReading在满足(dataAvailable BUFFER_PLAY_THRESHOLD)& buffer_prefill)后,查看标识processingData(即是否正在有线程对缓冲区进行操作)是否为假。若为假,则调用POSIX标准pthread_cond_signal()函数唤醒ip_feed线程。ip_feed线程则在进入keepFeeding的循环中调用POSIX标准pthread_cond_wait()函数来等待信号。从而实现ip_main和ip_feed的线程自动同步,如图3.3.4.1。图3.3.4.1 ip_main与ip_feed线程同步示意图3.4 消息驱动机制3.4.1 概述消息驱动机制负责 GUI 与操作系统、输入设备以及其它系统等进行信息交换,也用于处理 GUI 系统内部的各种事件和交互,它是整个 GUI 系统的动力源。GUI 系统的根本目的在于与用户交互,而交互的实现则通过一些称为“事件”的动作来触发,例如来自外部的输入设备事件、GUI 内部对象间传递信息的内部事件等。如何处理这些不可预期的突发性的事件,消息驱动机制可以很好的解决这种基于事件驱动的多应用设计问题,并且可以形成一种处理多个系统之间、系统内部对象和对象之间关系的简洁办法,因此消息驱动机制目前广泛应用于 GUI 系统、分布式系统设计中。消息驱动机制包括消息的产生、分发和处理等。消息驱动机制的根本在于GUI 系统中事件产生和事件响应的异步本质。发送消息的对象只需要发送消息,并不关心目的对象如何接收消息,也不用关心具体的消息响应过程;同样,消息接收者也不需要考虑消息的来源,而只需要对接收到的消息进行响应即可。因此消息产生后,究竟如何传递,会引起什么样的过程来响应,是由管理消息的部件来决定的,对消息的发送者和接收者来说都是透明的。消息的发送方和响应方形成了隐式的调用关系,非常有利于具有松散
展开阅读全文