servfox分析.docx

上传人:b****7 文档编号:11298061 上传时间:2023-02-26 格式:DOCX 页数:13 大小:34.16KB
下载 相关 举报
servfox分析.docx_第1页
第1页 / 共13页
servfox分析.docx_第2页
第2页 / 共13页
servfox分析.docx_第3页
第3页 / 共13页
servfox分析.docx_第4页
第4页 / 共13页
servfox分析.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

servfox分析.docx

《servfox分析.docx》由会员分享,可在线阅读,更多相关《servfox分析.docx(13页珍藏版)》请在冰豆网上搜索。

servfox分析.docx

servfox分析

servfox分析

同样是写的比较好的文章,转载了。

感谢原文作者,原文地址:

构建嵌入式Linux网络视频监控系统中,我们采用servfox来做服务器采集程序.servfox涉及到的内容主要有:

V4L1接口、套接字和多线程编程.这里简单分析一下servfox-R1_1_3.

1.servfox做了什么?

servfox在采集图像的过程中主要做什么事情?

它初始化摄像头设备后创建了线程1采集视频图像.然后主程序创建一个套接字监听,阻塞等待客户端的请求连接.连接成功后再创建线程2发送采集到的图像数据给客户端.

∙线程1:

采集视频图像.

∙线程2:

发送图像数据给客户端.

在采集线程和发送线程同时运行的情况下,会存在对存储压缩过的图像数据的缓冲区这个临界区竞争的情况.为了能把采集到的一帧图像数据完整地发送出去,需要采用一些同步机制.servfox只是个应用程序,它初始化设备,获取设备属性和图像属性,设置图像参数,捕捉图像数据,都是通过Video4Linux接口标准调用驱动的相关函数完成的.本文末尾将会列举部分摄像头设备驱动要实现的file_oerations结构体里面的函数.

2.servfox运行步骤

servfox运行流程图如下:

2.1从命令行传递参数给变量

main()函数内,首先执行的是一个for循环体.看一下里面的几个语句:

...if(strcmp(argv[i],"-d")==0){if(i+1>=argc){if(debug)printf("Noparameterspecifiedwith-d,aborting.\n");exit

(1);}videodevice=strdup(argv[i+1]);}...

videodevice保存了摄像头设备节点名称.用户不指定的话,后面会将它设置为"/dev/video0".

...if(strcmp(argv[i],"-g")==0){/*Askforreadinsteaddefaultmmap*/grabmethod=0;}...

通过grabmethod的设置就指定了采集图像时使用mmap()内存映射的方法还是read()读取的方法.采用read系统调用来读取图像数据的话在连续抓取的情况下会发生频繁的用户态和内核态的切换,效率低.通过mmap内存映射的话,把摄像头对应的设备文件映射到进程内存中,减少I/O操作,提高了效率.因此启动servfox时不加"-g"选项的话默认采用grabmethod=1为mmap方式.

在for循环体里面还根据用户输入的选项分配了存储分辨率大小width/height,创建套接字时用的端口号serverport(默认为7070).

2.2初始化视频采集设备

接下来主要要执行的语句有:

memset(&videoIn,0,sizeof(structvdIn));//将结构体videoIn初始化为0

先来看看videoIn这个结构体:

∙vdIn结构体(在spcav4l.h中定义,它里面的成员都是依据Video4Linux接口标准而定义的):

structvdIn{intfd;//设备文件描述符char*videodevice;//设备,视频捕捉接口文件structvideo_mmapvmmap;/*用于内存映射方法时进行图像数据的获取,*里面的成员.frame表示当前将获取的帧号,*成员.height和.width表示图像高度和宽度,*成员.format表示图像格式.*/structvideo_capabilityvideocap;/*包含设备的基本信息(设备名称,支持的最大最小分辨率,信号源信息等)*/intmmapsize;structvideo_mbufvideombuf;/*利用mmap映射到摄像头存储缓冲区的帧信息,*包括帧的大小(size),最多支持的帧数(frames),*每帧相对基址的偏移(offset)*/structvideo_picturevideopict;//采集到的图像的各种属性structvideo_windowvideowin;//包含capturearea的信息structvideo_channelvideochan;//各个信号源的属性structvideo_paramvideoparam;intcameratype;//是否能capture,彩色还是黑白,是否能裁剪等等char*cameraname;//设备名称charbridge[9];intsizenative;//availablesizeinjpeg.intsizeothers;//otherspalette.intpalette;//availablepalette.intnorme;//setspca506usbvideograbber.intchannel;//setspca506usbvideograbber信号源个数intgrabMethod;unsignedchar*pFramebuffer;//指向内存映射的指针unsignedchar*ptframe[4];//指向压缩后的帧的指针数组intframelock[4];pthread_mutex_tgrabmutex;//视频采集线程和传输线程的互斥信号intframesizeIn;//视频帧的大小volatileintframe_cour;//指向压缩后的帧的指针数组下标intbppIn;//采集的视频帧的BPPinthdrwidth;//采集的视频帧的宽度inthdrheight;//采集的视频帧的高度intformatIn;//采集的视频帧的格式intsignalquit;//停止视频采集的信号};

接下来执行:

if(init_videoIn(&videoIn,videodevice,width,height,format,grabmethod)!

=0)

这个函数主要是设置了grabmethod:

用mmap方式还是read方式;

设置videodevice成员设备文件名称,默认是"/dev/video0";

设置信号vd->signalquit=1,图像宽高:

vd->hdrwidth=width;vd->hdrheight=height;

设置图像格式为VIDEO_PALETTE_JPEG:

vd->formatIn=format;

获得色深:

vd->bppIn=GetDepth(vd->formatIn);

调用init_v4l():

=================进入init_v4l()================================================

init_v4l()是初始化v4l视频设备的函数,它首先通过系统调用open()打开视频设备,成功打开后主要执行下面几个步骤:

∙1.通过系统调用ioctl(vd->fd,VIDIOCGCAP,&(vd->videocap))取得设备信息。

读取structvideo_capability中有关摄像头的信息,保存到vd->videocap中.

∙2.初始化图像.

ioctl(vd->fd,VIDIOCGPICT,&vd->videopict);

带VIDIOCGPICT参数的ioctl调用会获取图像的属性,并保存在vd->videopict指向的结构体中.

∙3.读取ructvideochanel中有关设备通道的信息,保存到vd->videochan指向的结构体中。

ioctl(vd->fd,VIDIOCGCHAN,&vd->videochan);

∙4.设置摄像头参数.

读取摄像头数据前,需要对摄像头进行设置,主要包括图像参数和分辨率.

ioctl(vd->fd,VIDIOCSPICT,&vd->videopict)

设置分辨率主要是对vd->videowin各分量进行修改,若为read方式,具体实现为:

if(ioctl(vd->fd,VIDIOCGWIN,&(vd->videowin))<0)//获得捕获源的大小perror("VIDIOCGWINfailed\n");vd->videowin.height=vd->hdrheight;vd->videowin.width=vd->hdrwidth;if(ioctl(vd->fd,VIDIOCSWIN,&(vd->videowin))<0)perror("VIDIOCSWINfailed\n");

∙5.摄像头设备文件映射初始化或read方式初始化

完成上述初始化设备工作后,就可以对访问到摄像头设备文件的内容了.如果选用mmap()内存映射方式的话,下面的步骤将摄像头设备文件映射到进程内存,这样就可以直接读取映射了的这片内存,而不必read设备文件了:

a.获取摄像头缓冲区帧信息:

ioctl(vd->fd,VIDIOCGMBUF,&(vd->videombuf));

该操作获取摄像头存储缓冲区的帧信息:

包括帧的大小(size),最多支持的帧数(frames),每帧相对基址的偏移(offset).这些参数都是由摄像头设备硬件决定的.这些信息将被保存在videombuf结构体里面,下面的映射摄像头设备文件到内存操作马上就要用到了:

b.映射摄像头设备文件到内存:

vd->pFramebuffer=(unsignedchar*)mmap(0,vd->videombuf.size,PROT_READ|PROT_WRITE,MAP_SHARED,vd->fd,0);

该操作把摄像头对应的设备文件映射到内存区.该映射内容区可读可写并且不同进程间可共享.帧的大小(vd->videombuf.size)是a步骤获取的.该函数成功返回映像内存区的指针,该指针赋值给vd->pFramebuffer,失败时返回-1.

c.视频图像捕捉测试:

/*Grabframes抓取一帧*/if(ioctl(vd->fd,VIDIOCMCAPTURE,&(vd->vmmap))){perror("cmcapture");}

该操作捕捉一帧图像,获取图像信息到vmmap里.它会根据vmmap中设置的属性参数(frame,height,width和format)通知驱动程序启动摄像头抓拍图像.该操作是非阻塞的,是否截取完毕留给VDIOCSYNC来判断.在init_v4l()这里只是为了测试是否可以成功捕获一帧图像,真正采集图像是在采集线程时执行v4lGrab()这个函数的时候.

以上是用mmap内存映射方式,如果采用直接读取摄像头设备文件的方式获取图像的话,将执行:

els{/*readmethod*//*allocatethereadbuffer*/vd->pFramebuffer=(unsignedchar*)realloc(vd->pFramebuffer,\(size_t)vd->framesizeIn);/*为pFrameffer分配内存*/if(ioctl(vd->fd,VIDIOCGWIN,&(vd->videowin))<0)//获得捕获源的大小perror("VIDIOCGWINfailed\n");vd->videowin.height=vd->hdrheight;vd->videowin.width=vd->hdrwidth;if(ioctl(vd->fd,VIDIOCSWIN,&(vd->videowin))<0)perror("VIDIOCSWINfailed\n");}

摄像头设备文件映射初始化或read方式初始化完成后,返回init_videoIn().

=============从init_v4l()返回==========================================================

从init_v4l()返回到init_videoIn()后,分配vd->ptframe[i]空间.

for(i=0;iptframe[i]=NULL;vd->ptframe[i]=(unsignedchar*)realloc(vd->ptframe[i],\sizeof(structframe_t)+(size_t)vd->framesizeIn);vd->framelock[i]=0;}

unsignedchar*ptframe[4]:

指向四个buffer缓冲数组,用来存放已压缩完成的图像数据.

2.3采集图像数据线程

init_videoIn()执行完后返回main(),接下来创建采集视频图像的线程:

pthread_create(&w1,NULL,(void*)grab,NULL);

进入grab()函数:

可以看到在死循环体里面调用v4lGrab()函数.

进入v4lGrab()函数,先判断一下是用mmap方法还是用read方法.下面仅就mmap方法分析:

ioctl(vd->fd,VIDIOCSYNC,&vd->vmmap.frame);

这条语句是等待捕捉完这一帧图像,调用成功后表明一帧图像捕捉完毕,可以开始进行下一次图像捕捉.vd->vmmap.frame是当前捕捉到帧的序号.

接下来的是个循环睡眠等待:

while((vd->framelock[vd->frame_cour]!

=0)&&vd->signalquit)usleep(1000);

它是等待之后执行的另一个用来的发送采集到的图像数据给客户端的线程,直到它把这一帧图像完整地发送出去.每隔1毫秒就检查一次是否发完.如果不等待就执行下面的操作的话,那么还没发送完就把本来要发送的图像数据重写掉,采集到的数据没用上.可以采用更好的同步机制--信号量来实现.

等到上一帧图像数据发送出去之后,这个线程等待直到获得一把线程互斥锁:

pthread_mutex_lock(&vd->grabmutex);

它把临界区资源vd->ptframe锁住,防止下面获取时间和拷贝数据到ptframe及设置一帧图像的头部时被别的线程抢占.虽然在发送线程里并没有找到相关互斥锁的操作(这个应该是要加的),但为了扩展,有可能以后我们添加一些访问临界区vd->ptframe的线程时可以用它这把锁.

然后执行:

tems=ms_time();

tems获得的是距离UNIX的Epoch时间即:

1970年1月1日0时0分0秒算起的毫秒数.它可以用在视频图像的时间戳.

然后执行:

jpegsize=convertframe(vd->ptframe[vd->frame_cour]+sizeof(structframe_t),vd->pFramebuffer+vd->videombuf.offsets[vd->vmmap.frame],vd->hdrwidth,vd->hdrheight,vd->formatIn,vd->framesizeIn);

跟踪进去可以看出要是视频图像格式是VIDEO_PALETTE_JPEG的话,直接将pFramebuffer中的数据拷贝到ptframe缓存中去,而不压缩处理,因为获得的就是已经压缩过的jpeg格式了(是硬件或底层驱动做了,一般USB摄像头对采集到的图像都作了jpeg格式压缩(内置JPEG硬件压缩)).获得jpeg格式文件的大小是通过调用get_jpegsize()实现的.进入get_jpegsize()可以发现,它利用了jpeg文件格式中是以0xFF0xD9结尾的这个特性.ptframe里面的经压缩过的图像数据就是发送线程要发送出去的内容了.

pFramebuffer中的数据拷贝进ptframe完成后,就截取下一帧图像数据了:

/*Grabframes*/if((ioctl(vd->fd,VIDIOCMCAPTURE,&(vd->vmmap)))<0){perror("cmcapture");if(debug)printf(">>cmcaptureerr\n");erreur=-1;}vd->vmmap.frame=(vd->vmmap.frame+1)%vd->videombuf.frames;vd->frame_cour=(vd->frame_cour+1)%OUTFRMNUMB;

执行完后,跳出v4lGrab()函数体,返回到grab()去.正常运行状态下,将不断循环调用v4lGrab()采集图像数据.采集线程分析完毕.

2.4建立TCP套接字服务端,为图像数据发送线程做好准备

回到main(),继续往下执行:

serv_sock=open_sock(serverport);

跟踪进入open_sock()里面可以看到通过执行socket(),bind(),listen()建立了一个TCP套接字服务端并在指定端口上监听,等待客户端连接.紧跟在socket()后面有一句:

setsockopt(server_handle,SOL_SOCKET,SO_REUSEADDR,&O_on,sizeof(int));

这个语句应该是为了允许启动多个服务端或多个servfox.参见:

(关于SO_REUSEADDR的使用说明)

执行完serv_sock=open_sock(serverport)这个语句之后,下一条语句是:

signal(SIGPIPE,SIG_IGN);/*Ignoresigpipe*/

这是为了忽略SIGPIPE信号:

若客户端关闭了和服务端的连接,但服务端依然试图发送图像数据给客户端(writetopipewithnoreaders),系统就会发出一个SIGPIPE信号,默认对SIGPIPE的处理是terminate(终止),那么负责发送图像数据的服务端就挂掉了,即使还有别的客户端连接.这当然不是我们想要的,因此把我们要执行这句语句把SIGPIPE信号忽略掉.

2.5发送图像数据到客户端的线程

接下来,是一个while(videoIn.signalquit)循环体,如果没有接收到退出信号,它就一直循环运行里面的语句:

while(videoIn.signalquit){sin_size=sizeof(structsockaddr_in);/*等待客户端的连接,如果没有连接就一直阻塞下去,*如果有客户连接就创建一个线程,*在新的套接口上与客户端进行数据交互*/if((new_sock=accept(serv_sock,(structsockaddr*)&their_addr,&sin_size))==-1){continue;}syslog(LOG_ERR,"Gotconnectionfrom%s\n",inet_ntoa(their_addr.sin_addr));printf("Gotconnectionfrom%s\n",inet_ntoa(their_addr.sin_addr));pthread_create(&server_th,NULL,(void*)service,&new_sock);}

之前建立的服务端一直监听等待客户端来连接,一旦有客户端connect()过来,服务端执行accept()建立连接后,就创建了发送图像数据到客户端的线程了:

pthread_create(&server_th,NULL,(void*)service,&new_sock);

我们再进入这个线程执行的service()函数去分析:

=============进入service()==============================

/*initializevideosetting*/bright=upbright(&videoIn);contrast=upcontrast(&videoIn);bright=downbright(&videoIn);contrast=downcontrast(&videoIn);

上面所谓的初始话视频设置,是先增大一下亮度和对比度,在减小亮度和对比度恢复到原来的状态,顺便将亮度值保存在bright变量,将对比度值保存在contrast变量.

然后是一个死循环体:

for(;;){memset(&message,0,sizeof(structclient_t));ret=read(sock,(unsignedchar*)&message,sizeof(structclient_t));......if(message.updobright){switch(message.updobright){case1:

bright=upbright(&videoIn);break;case2:

bright=downbright(&videoIn);break;}ack=1;}elseif(message.updocontrast){switch(message.updocontrast){case1:

contrast=upcontrast(&videoIn);break;case2:

contrast=downcontrast(&videoIn);break;}ack=1;}elseif(message.updoexposure){switch(message.updoexposure){case1:

spcaSetAutoExpo(&videoIn);break;case2:

;break;}ack=1;}elseif(message.updosize){//compatibilityFIXchgqualityfactorATMswitch(message.updosize){case1:

qualityUp(&videoIn);break;case2:

qualityDown(&videoIn);break;}ack=1;}elseif(message.fps){switch(message.fps){case1:

timeDown(&videoIn);break;ca

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 历史学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1