搞了一段时间的嵌入式网络视频采集与传输,基本上在服务器端采集视频用的是servfox程序包,它可以用arm-linux-gcc编译后移植到开发板上,我现在一直在做这方面的工作,用的是S3C2410的板子,这个开源程序十分不错,觉得有必要深入理解和学习一下它的源程序,网上一般没有对它的详细说明,只是简单介绍了它怎么用的,就如我当初刚开始学习嵌入式时一样看不懂这个程序,现在对其各个代码作个详细的解释,以备后忘,也希望对新手有所帮助。
servfox主要有server.c,spcav4l.c ,spcav4l.h,utils.c ,utils.h,tcputils.c ,tcputils.h,spcaframe.h,Makefile,Makefile.fox这几个文件, arm-linux-gcc编译后可以生成servfox可执行文件,可以移植到ARM板上作为服务器端视频采集程序用。我们从server.c代码开始。
1. server.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <errno.h>
#include<netdb.h>
#include<sys/types.h>
#include<sys/fcntl.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netinet/ether.h>
#include <time.h>
#include <sys/time.h>
#include <pthread.h>
//#include <signal.h>
#include "spcaframe.h"
#include "spcav4l.h"
#include "utils.h"
#include "tcputils.h"
#include "version.h"
static int debug = 0;
void grab (void);
void service (void *ir);
void sigchld_handler(int s);
struct vdIn videoIn;
int main (int argc, char *argv[])
{
char *videodevice = NULL;
int grabmethod = 1;
int format = VIDEO_PALETTE_JPEG;
int width = 352;
int height = 288;
char *separateur;
char *sizestring = NULL;
int i;
int serv_sock,new_sock;
pthread_t w1;
pthread_t server_th;
int sin_size;
unsigned short serverport = 7070;
struct sockaddr_in their_addr;
struct sigaction sa;
int ret;
/************************************************************/
命令行输入参数设定命令行参数应该这个样子的:
./servfox -g -d /dev/video0 -s 640x480 -w 7070
***********************************************************/
for (i = 1; i < argc; i++)
{
/* skip bad arguments */
if (argv[i] == NULL || *argv[i] == 0 || *argv[i] != '-')
{
continue;
}
/************************************************************** /
-d 参数用于设置输入视频采集设备。
****************************************************************/
if (strcmp (argv[i], "-d") == 0)
{
if (i + 1 >= argc)
{
if(debug) printf ("No parameter specified with -d, aborting./n");
exit (1);
}
videodevice = strdup (argv[i + 1]);
}
/********************************************************************************************
-g 参数用于读取采集到的视频的方式,用read方式而非mmap方式。
*********************************************************************************************/
if (strcmp (argv[i], "-g") == 0)
{
/* Ask for read instead default mmap */
grabmethod = 0;
}
/************************************************************
-s 参数用于设置视频图像帧的大小,如 640x480
*********************************************************/
if (strcmp (argv[i], "-s") == 0)
{
if (i + 1 >= argc)
{
if(debug) printf ("No parameter specified with -s, aborting./n");
exit (1);
}
sizestring = strdup (argv[i + 1]);
width = strtoul (sizestring, &separateur, 10);
if (*separateur != 'x')
{
if(debug) printf ("Error in size use -s widthxheight /n");
exit (1);
}
else {
++separateur;
height =strtoul (separateur, &separateur, 10);
if (*separateur != 0)
if(debug) printf ("hmm.. dont like that!! trying this height /n");
if(debug) printf (" size width: %d height: %d /n",
width, height);
}
}
/******************************************************
-w参数用于设置端口。
*********************************************************/
if (strcmp (argv[i], "-w") == 0)
{
if (i + 1 >= argc)
{
if(debug) printf ("No parameter specified with -w, aborting./n");
exit (1);
}
serverport = (unsigned short) atoi (argv[i + 1]);
if (serverport < 1024 ){
if(debug) printf ("Port should be between 1024 to 65536 set default 7070 !./n");
serverport = 7070;
}
}
if (strcmp (argv[i], "-h") == 0)
{
printf ("usage: cdse [-h -d -g ] /n");
printf ("-h print this message /n");
printf ("-d /dev/videoX use videoX device/n");
printf ("-g use read method for grab instead mmap /n");
printf ("-s widthxheight use specified input size /n");
printf ("-w port server port /n");
exit (0);
}
}
/*********************************
main code主程序开始位置
*****************************************/
printf(" %s /n",version);
if (videodevice == NULL || *videodevice == 0)
{
videodevice = "/dev/video0";
}
memset (&videoIn, 0, sizeof (struct vdIn));
if (init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) != 0) // 跟踪进入 init_videoIn()
if(debug) printf (" damned encore rate !!/n");
/*********************************************************
spcav4l.c中:
int init_videoIn (struct vdIn *vd, char *device, int width, int height,int format, int grabmethod)
struct vdIn videoIn; //在spcav4l.h中定义
videodevice = "/dev/video0";
int width = 352;
int height = 288;
int format = VIDEO_PALETTE_JPEG;
int grabmethod = 1;
请进入下面的 跟踪进入 init_videoIn() ,对其进 行详细说明,我们来看看它里面做了些什么事情。
跟踪进入 init_videoIn()
/*****************************************************
/**********************************************************
init_videoIn()函数在spcav4l.c中定义,这个函数主要用来初始化视频采集设备。
它的几个参数是由server.c中传递进来的:
if (init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) != 0)
其中
videoIn;在spcav4l.h中定义的struct vdIn videoIn结构体
videodevice = "/dev/video0"; //前面输入的
int width = 352;(640)
int height = 288;(320)
int format = VIDEO_PALETTE_JPEG;
int grabmethod = 1;
**********************************************************/
int init_videoIn (struct vdIn *vd, char *device, int width, int height,int format, int grabmethod)
{
int err = -1;
int i;
if (vd == NULL || device == NULL)
return -1;
if (width == 0 || height == 0)
return -1;
if(grabmethod < 0 || grabmethod > 1)
grabmethod = 1; //read by default,设置read方式为默认方式
/**************************************************************
// check format检查格式,
我们先来看一下 struct vdIn结构体,在spcav4l.h中定义 ,它是对 Video4Linux视频设备数据
结构的定义。
struct vdIn {
int fd; //设备 描述符, 文件描述符
char *videodevice ; //设备路径指针, 视频捕捉接口文件
struct video_mmap vmmap;
struct video_capability videocap; // 包含设备的基本信息(设备名
int mmapsize; //称、支持的最大最小分辨率,信
//号愿信息
struct video_mbuf videombuf; //映射的帧信息,实际是映射到
//摄像头存储缓
struct video_picture videopict; //冲区的帧信息,包括帧的大小
//(size)最多支持的帧数
//(frames)每帧相对基
//址的偏移(offset)
struct video_window videowin;
struct video_channel videochan;
int cameratype ; // 是否能capture,彩色还是黑白,
//是否 能裁剪等等
//,这将决定视频数据的格式
char *cameraname; //设备名称
char bridge[9];
int sizenative; // available size in jpeg
int sizeothers; // others palette
int palette; // available palette
int norme ; // set spca506 usb video grabber
int channel ; // set spca506 usb video grabber
//信号源个数
int grabMethod ;
unsigned char *pFramebuffer; //指向内存映射的指针,存储图像数据
unsigned char *ptframe[4]; //指向压缩后的帧的指针数组
int framelock[4];
pthread_mutex_t grabmutex; // 视频采集线程和传输线程的互斥信号
int framesizeIn ; // 视频帧的大小
// framesizeIn = (vd->hdrwidth * vd->hdrheight >> 2 )
volatile int frame_cour; // 指向压缩后的帧的指针数组下标
int bppIn; // 采集的视频帧的BPP
-------------------------------------------------------------------------------------------
我们在流媒体文件的编码过程中总是试图得到最佳的图像质量,这可以通过调整编码过程中帧的大小或编码码
率来实现。在此可通过位每像素(BPP,BitPerPixel)来确定帧的大小或编码码率,从而得到理想的图像质量。
位每像素(BPP)是通过调整帧的大小或编码码率得到最佳图像质量的一个重要参考指标.
如果已确认要编码的视频图像帧的大小,但不确定采用何种编码码率可以得到最佳的编码图像质量,
可采用如公式1确定编码码率。
公式1:编码码率=位每像素×帧率×宽度×高度
(Bitrate=BPP×FrameRate×Width×Height )
--------------------------------------------------------------------------------
int hdrwidth; // 采集的视频帧的宽度
int hdrheight; // 采集的视频帧的高度
int formatIn; //采集的视频帧的格式
int signalquit; //停止视频采集的信号
};
*********************************************************/
vd->videodevice = NULL;
vd->cameraname = NULL;
vd->videodevice = NULL;
vd->videodevice = (char *) realloc (vd->videodevice, 16);
vd->cameraname = (char *) realloc (vd->cameraname, 32);
/****************************************************************
把server.c中传递进来的device指向的videodevice = "/dev/video0"复制到 vd->videodevice中
使 vd->videodevice就指向 "/dev/video0"
************************************************************/
snprintf (vd->videodevice, 12, "%s", device);
if(debug) printf("video %s /n",vd->videodevice);
memset (vd->cameraname, 0, sizeof (vd->cameraname));
memset(vd->bridge, 0, sizeof(vd->bridge));
vd->signalquit = 1; //信号设置
vd->hdrwidth = width; //传递宽高
vd->hdrheight = height;
/*compute the max frame size */
vd->formatIn = format; //传进来的 format = VIDEO_PALETTE_JPEG;
vd->bppIn = GetDepth (vd->formatIn);
/*********************************************************
GetDepth()是 前面声明的函数, spcav4l.c 后面有定义
static int GetDepth (int format)
{
int depth;
switch (format)
{
case VIDEO_PALETTE_JPEG:
{
depth = 8;
}
break;
case VIDEO_PALETTE_RAW:
{
depth = 8;
}
break;
case VIDEO_PALETTE_YUV420P:
{
depth = (8 * 3) >> 1;
}
break;
case VIDEO_PALETTE_RGB565:
depth = 16;
break;
case VIDEO_PALETTE_RGB24:
depth = 24;
break;
case VIDEO_PALETTE_RGB32:
{
depth = 32;
}
break;
default:
depth = -1;
break;
}
return depth;
}
**************************************************************/
vd->grabMethod = grabmethod; //mmap or read
vd->pFramebuffer = NULL;
/* init and check all setting */
err = init_v4l (vd); 跟踪进入 init_v4l (vd)
************************************************************/
我们跟踪进入 init_v4l (vd)来看看,看看它里面做了哪些工作
init_v4l (vd)是初始化V4L视频设备的函数,执行videodevice = "/dev/video0"的打开,
在spcav4l.c中定义:
static int init_v4l (struct vdIn *vd)
{
int f;
int erreur = 0;
int err;
if ((vd->fd = open (vd->videodevice, O_RDWR)) == -1) //打开 视频设备
exit_fatal ("ERROR opening V4L interface");
if (ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap)) == -1)
exit_fatal ("Couldn't get videodevice capability");
/********************************************************
ioctl用于向设备发控制和配置命令 ,有些命令也需要读写一些数据,但这些数据是不能用
read/write读写的,称为Out-of-band数据。也就是说,read/write读写的数据是in-band数据,
是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数据。ioctl是设备驱动程序
中对设备的I/O通道进行管理的函数,所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如,
在串口线上收发数据通过read/write操作,而串口的波特率、
校验位、停止位通过ioctl设置,A/D转换的结果通过read读取,而A/D转换的精度和工作频率通过
ioctl设置。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户
就可以在用户
程序中使用ioctl函数控制设备的I/O通道。
如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可以在驱动
程序中实现
write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令
(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混乱,
程序员自己也会头昏眼花的。
所以,我们就使用ioctl来实现控制的功能 。要记住,用户程序所作的只是通过命令码告诉驱动
程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
int ioctl(int fd, ind cmd, …);
其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的
控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
调用函数ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap))成功后可读取vd->capability各分量 ,
读video_capability
中信息包括设备名称,支持最大最小分辨率,信号源信息等。
video_capability是Video4linux支持的数据结构, 在前面定义:
struct vdIn {
int fd; //设备 描述符, 文件描述符
char *videodevice ; //设备, 视频捕捉接口文件
struct video_mmap vmmap;
struct video_capability videocap; // 包含设备的基本信息
//(设备名称、支持的最大最小分辨率、信号源
}
video_capability 包含设备的基本信息(设备名称、支持的最大最小分辨率、
//信号源信息等),包含的分量:
.name[32] //设备名称
.maxwidth ,maxheight,minwidth,minheight
.Channels //信号源个数
.type //是否能capture,彩色还是黑白,是否能裁剪等等。
//值如VID_TYPE_CAPTURE等
struct video_capability
{
char name[32];
int type;
int channels; /* Num channels */
int audios; /* Num audio devices */
int maxwidth; /* Supported width */
int maxheight; /* And height */
int minwidth; /* Supported width */
int minheight; /* And height */
};
name是摄像头的名字,maxwidth maxheight是摄像头所能获取的最大图像大小,
用像素作单位 。
在程序中,通过ioctl函数的VIDIOCGCAP控制命令读写设备通道已获取这个结构
************ ***************************************************************** /
if(debug) printf ("Camera found: %s /n", vd->videocap.name);
snprintf (vd->cameraname, 32, "%s", vd->videocap.name);
/**********************************************************
我们通过在 Video4Linux视频设备数据结构中:
struct vdIn {
int fd; //设备 描述符, 文件描述符
char *videodevice ; //设备, 视频捕捉接口文件
struct video_mmap vmmap;
struct video_capability videocap;
............................................
char *cameraname; //设备名称
}
定义 struct video_capability videocap这个结构体,通过上面的
ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap))获得视频采集设备
的名称后传给 vd->cameraname。
*************************************************************/
erreur = GetVideoPict (vd);
/**********************************************************
GetVideoPict()也在spcav4l.c中定义:
static int GetVideoPict (struct vdIn *vd)
{
if (ioctl (vd->fd, VIDIOCGPICT, &vd->videopict) < 0)
exit_fatal ("Couldnt get videopict params with VIDIOCGPICT");
if(debug)
printf ("VIDIOCGPICT brightnes=%d hue=%d color=%d contrast=%d whiteness=%d"
"depth=%d palette=%d/n", vd->videopict.brightness,
vd->videopict.hue, vd->videopict.colour, vd->videopict.contrast,
vd->videopict.whiteness, vd->videopict.depth,
vd->videopict.palette);
return 0;
}
-------------------------------------------------------------
跟 ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap))类似,
ioctl (vd->fd, VIDIOCGPICT,&vd->videopict) 获取摄象
头缓冲区中video_picture 中各 分量的值。
我们来看看 struct video_picture videopict这个结构体:
struct video_picture
{
__u16 brightness;
__u16 hue;
__u16 colour;
__u16 contrast;
__u16 whiteness; /* Black and white only */
__u16 depth; /* Capture depth */
__u16 palette; /* Palette in use */
}
video_picture 设备采集的图象的各种属性
.brightness 0~65535 亮度
.hue
.colour
.contrast 对比度
.whiteness
.depth // 24 色深
.palette //VIDEO_PALETTE_RGB24 调色板
picture 结构包括了亮度,对比度,色深,调色板等等信息
-------------------------------------------------------------
这个函数是为了获得 摄象头 采集到的图象 缓冲区中video_picture 中各分量的值
如果要对 对采集图象的各种属性进行设置,可分为两步进行,首先获取摄象头缓冲区
中video_picture中信息调用函数ioctl(vd->fd, VIDIOCGPICT, &(vd->picture));
然后改变video_picture中分量的值,
调用 ioctl (vd->fd, VIDIOCSPICT, &vd->videopict) 为vd->videopict分量赋新值。
******************************************************************/
if (ioctl (vd->fd, VIDIOCGCHAN, &vd->videochan) == -1)
{
if(debug) printf ("Hmm did not support Video_channel/n");
vd->cameratype = UNOW;
}
/********************************************************
同样, ioctl (vd->fd, VIDIOCGCHAN, &vd->videochan)可以获得 各个信号源的属性的信息
在上面的 struct vdIn中定义的 struct video_channel videochan
video_channel 关于各个信号源的属性
Channel //信号源的编号
name
tuners
Type VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm制式
struct video_channel在 v4l API 中定义:
struct video_channel
{
int channel;
char name[32];
int tuners;
__u32 flags;
#define VIDEO_VC_TUNER 1 /* Channel has a tuner */
#define VIDEO_VC_AUDIO 2 /* Channel has audio */
__u16 type;
# define VIDEO_TYPE_TV 1
#define VIDEO_TYPE_CAMERA 2
__u16 norm; /* Norm set by channel */
};
成员channel代表输入源,通常,0: television 1:composite1 2:s-video
name 表示该输入源的名称。
norm 表示制式,通常,0:pal 1:ntsc 2:secam 3:auto
在linux下,在 /usr/include/linux /videodev.h中有详细定义。
*******************************************************************/
else
{
if (vd->videochan.name)
{
if(debug) printf ("Bridge found: %s /n", vd->videochan.name);
snprintf (vd->bridge, 9, "%s", vd->videochan.name);
vd->cameratype = GetStreamId (vd->bridge);
}
/******************************************************
进入:GetStreamId ()函数定义在spcav4l.c中:
static int GetStreamId (const char *BridgeName)
{
int i = -1;
int match = -1;
/* return Stream_id otherwhise -1 */
if ((match = isSpcaChip (BridgeName)) < 0)
{
if(debug) printf ("Not an Spca5xx Camera !!/n");
return match;
}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
isSpcaChip也定义在spcav4l.c中:在 GetStreamId()的上面。
static int isSpcaChip (const char *BridgeName)
{
int i = -1;
int find = -1;
int size = 0;
/* Spca506 return more with channel video, cut it */
/* return Bridge otherwhise -1 */
for (i = 0; i < MAX_BRIDGE -1; i++)
{
size = strlen (Blist[i].name) ;
if(debug) printf ("is_spca %s size %d /n",Blist[i].name,size);
if (strncmp (BridgeName, Blist[i].name, size) == 0)
{
find = i;
break;
}
}
return find;
}
===========================================
函数原型:extern int strcmp(char *str1,char * str2,int n)
参数说明:str1为第一个要比较的字符串,str2为第二个要比较的字符串,
n为指定的str1与str2的比较的 字符数。
所在库名:#include <string.h>
函数功能:比较字符串str1和str2的前n个字符。
返回说明:返回整数值:当str1<str2时,返回值<0; 当str1=str2时,
返回值=0; 当str1>str2时,返回值>0。
===========================================
MAX_BRIDGE在spcav4l.h中定义,是一个枚举成员
static struct bridge_list Blist[]={
Blist[]数组在spcav4l.c前面定义:
static struct bridge_list Blist[]={
{BRIDGE_SPCA505,"SPCA505"},
{BRIDGE_SPCA506,"SPCA506"},
{BRIDGE_SPCA501,"SPCA501"},
{BRIDGE_SPCA508,"SPCA508"},
{BRIDGE_SPCA504,"SPCA504"},
{BRIDGE_SPCA500,"SPCA500"},
{BRIDGE_SPCA504B,"SPCA504B"},
{BRIDGE_SPCA533,"SPCA533"},
{BRIDGE_SPCA504C,"SPCA504C"},
{BRIDGE_SPCA561,"SPCA561"},
{BRIDGE_SPCA536,"SPCA536"},
{BRIDGE_SONIX,"SN9C102"},
{BRIDGE_ZR364XX,"ZR364XX"},
{BRIDGE_ZC3XX,"ZC301-2"},
{BRIDGE_CX11646,"CX11646"},
{BRIDGE_TV8532,"TV8532"},
{BRIDGE_ETOMS,"ET61XX51"},
{BRIDGE_SN9CXXX,"SN9CXXX"},
{BRIDGE_MR97311,"MR97311"},
{BRIDGE_UNKNOW,"UNKNOW"},
{-1,NULL}
};
static struct bridge_list结构体
struct bridge_list
{
int num;
const char *name;
};
…………………………………………………………………………………………………
switch (match)
{
case BRIDGE_SPCA505:
case BRIDGE_SPCA506:
i = YYUV;
break;
case BRIDGE_SPCA501:
i = YUYV;
break;
case BRIDGE_SPCA508:
i = YUVY;
break;
case BRIDGE_SPCA536:
case BRIDGE_SPCA504:
case BRIDGE_SPCA500:
case BRIDGE_SPCA504B:
case BRIDGE_SPCA533:
case BRIDGE_SPCA504C:
case BRIDGE_ZR364XX:
case BRIDGE_ZC3XX:
case BRIDGE_CX11646:
case BRIDGE_SN9CXXX:
case BRIDGE_MR97311:
i = JPEG;
break;
case BRIDGE_ETOMS:
case BRIDGE_SONIX:
case BRIDGE_SPCA561:
case BRIDGE_TV8532:
i = GBRG;
break;
default:
i = UNOW; // -1;
if(debug) printf ("Unable to find a StreamId !!/n");
break;
}
return i;
}
返回的是视频图像信号的格式。
现在退出GetStreamId(),回到init_v4l (struct vdIn *vd)中:
********************************************************/
else
{
if(debug) printf ("Bridge not found not a spca5xx Webcam Probing the hardware !!/n");
vd->cameratype = UNOW;
}
}
/* Only jpeg webcam allowed */
if(vd->cameratype != JPEG)
{
exit_fatal ("Not a JPEG webcam sorry Abort !");
}
if(debug) printf ("StreamId: %d Camera/n", vd->cameratype);
vd->videopict.palette = vd->formatIn; //采集的视频帧的格式,调色 VIDEO_PALETTE_RGB24
vd->videopict.depth = GetDepth (vd->formatIn);
vd->bppIn = GetDepth (vd->formatIn);
// here alloc the output ringbuffer(环形缓冲 区) jpeg only,
vd->framesizeIn = (vd->hdrwidth * vd->hdrheight >> 2 );
erreur = SetVideoPict (vd);// 视频帧的大小
/******************************************************
进入:SetVideoPict(),在spcav4l.c中定义:
static int SetVideoPict (struct vdIn *vd)
{
if (ioctl (vd->fd, VIDIOCSPICT, &vd->videopict) < 0)
exit_fatal ("Couldnt set videopict params with VIDIOCSPICT");
if(debug) printf ("VIDIOCSPICT brightnes=%d hue=%d color=%d contrast=%d whiteness=%d"
"depth=%d palette=%d/n", vd->videopict.brightness,
vd->videopict.hue, vd->videopict.colour, vd->videopict.contrast,
vd->videopict.whiteness, vd->videopict.depth,
vd->videopict.palette);
return 0;
}
这里其实没有设置采集图像的亮度,对比度,色深,调色板等等信息,只是用ioctl获取了一下,
采用视频设备本身默认的,我们可以输出这些属性看看。
退出SetVideoPict(),返回到init_v4l
**************************************************************/
erreur = GetVideoPict (vd);
/*****************************************************************
进入:GetVideoPict (vd),在spcav4l.c中定义:
static int GetVideoPict (struct vdIn *vd)
{
if (ioctl (vd->fd, VIDIOCGPICT, &vd->videopict) < 0)
exit_fatal ("Couldnt get videopict params with VIDIOCGPICT");
if(debug) printf ("VIDIOCGPICT brightnes=%d hue=%d color=%d contrast=%d whiteness=%d"
"depth=%d palette=%d/n", vd->videopict.brightness,
vd->videopict.hue, vd->videopict.colour, vd->videopict.contrast,
vd->videopict.whiteness, vd->videopict.depth,
vd->videopict.palette);
return 0;
}
这里其实可以得到采集图像的亮度,色调,色深,对比度,色度,深度,调色板等等信息,
只是用ioctl获取了一下,采用视频设备本身默认的,我们可以输出这些属性看看。
退出GetVideoPict (vd),返回到init_v4l
*****************************************************************/
if (vd->formatIn != vd->videopict.palette || vd->bppIn != vd->videopict.depth)
exit_fatal ("could't set video palette Abort !");
if (erreur < 0)
exit_fatal ("could't set video palette Abort !");
if (vd->grabMethod)
{
if(debug) printf (" grabbing method default MMAP asked /n");
/***************************************************************
在前面的init_videoIn中:
grabmethod = 1; //read by default;
vd->grabMethod = grabmethod; //mmap or read
采用mmap方式截取图象,视频数据的读取,这里我们简单介绍一下获得图像的两种方式
初始化好上面的v4l结构后,摄像头采集的视频数据可以有两种方式来读取:
分别是直接读取设备和使用mmap内存映射 ,而通常大家使用的方法都是后者
1).直接读取设备
直接读设备的方式就是使用read()函数 ,我们先前定义的
extern int v4l_grab_picture(v4l_device *, unsigned int);
函数就是完成这个工作的,它的实现也很简单
int v4l_grab_picture(v4l_device *vd, unsighed int size)
{
if(read(vd-fd,&(vd->map),size)==0)
return -1;
return 0;
}
该函数的使用也很简单,就是给出图像数据的大小,vd->map所指向的数据
就是图像数据。而图像数据的大小
你要根据设备的属性自己计算获得。这就是下面的/* read method */
2).使用mmap内存映射来获取图像
在这部分涉及到下面几个函数,它们配合来完成最终图像采集的功能。
extern int v4l_mmap_init(v4l_device *);该函数把摄像头图像数据映射到进程内存中,
也就是只要使用vd->map指针就可以使用 采集到的图像数据 (下文详细说明)
extern int v4l_grab_init(v4l_device *, int, int);该函数完成图像采集前的初始化工作。
extern int v4l_grab_frame(v4l_device *, int);该函数是真正完成图像采集的一步,在本文使用
了一个通常都会使用的一个小技巧,可以在处理一帧数据时同时采集下一帧的数据,因为通常我们
使用的摄像头都可以至少存储两帧的数据。
extern int v4l_grab_sync(v4l_device *);该函数用来完成截取图像的同步工作,在截取一帧图
像后调用,返回表明一帧截取结束
mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到
进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必在调用read(),write()等
操作。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址
空间。进程A可以即时访问进程B对共享内存中数据的更新,反之亦然。
采用共享内存通信的一个显而易见的好处是减少I/O操作提高读取效率,
因为使用mmap后进程可以直接读取内存而不需要任何数据的拷贝。
mmap的函数原型如下
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
addr:共享内存的起始地址,一般设为0,表示由系统分配。
len:指定映射内存的大小。在我们这里,该值为摄像头mbuf结构体的size值,即图像数据的总大小。
port:指定共享内存的访问权限 PROT_READ(可读),PROT_WRITE(可写)
flags:一般设置为MAP_SHARED
fd:同享文件的文件描述符。
用内存映射法一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。
与 read()方式相比, mmap()方式通过把设备文件映射到内存,绕过了内核缓冲区,加速了 I/O访问。
完成内存映射之后,就可以用 mmap()方式实现对内存映射区域视频数据的单帧采集。此方式下真正做视频
截取的为 VIDIOCMCAPTURE,调用函数 ioctl(_fd, VIDIOCMCAPTURE,&mmap),激活设备并真正开始一帧
图像的截取,是非阻塞的,接着调用 ioctl(_fd,VIDIOCSYNC,&frame)函数等待一帧图像截取结束,
成功返回表示一帧截取已完成,接着可以做下一次的 VIDIOCMCAPTURE操作。
***************************************************/
// MMAP VIDEO acquisition
memset (&(vd->videombuf), 0, sizeof (vd->videombuf));
if (ioctl (vd->fd, VIDIOCGMBUF, &(vd->videombuf)) < 0)
{
perror (" init VIDIOCGMBUF FAILED/n");
}
/********************************************************
vd->videopict在下面定义:
struct vdIn {
int fd; //设备 描述符, 文件描述符
char *videodevice ; //设备, 视频捕捉接口文件
struct video_mmap vmmap;
struct video_capability videocap; // 包含设备的基本信息(设备名称、
//支持的最大最小分辨率、信号源信息等)
int mmapsize;
struct video_mbuf videombuf ; //映射的帧信息,实际是映射到摄像头存储
缓冲区的帧信息,包括帧
的大小(size),最多支持的帧数(frames) 每帧相对基址的偏移 (offset)
struct video_picture videopict; //采集图像的各种属性
struct video_window videowin;
struct video_channel videochan;
...................................................................................
}
我们来看看struct video_mbuf videombuf这个结构体:
struct video_mbuf
{
int size; /* Total memory to map */帧的大小
int frames; /* Frames */最多支持的帧数
int offsets[VIDEO_MAX_FRAME];每帧相对基址的偏移
};
这里用ioctl来获取摄象头存储缓冲区的帧信息。
***************************************************************/
if(debug) printf ("VIDIOCGMBUF size %d frames %d offets[0]=%d offsets[1]=%d/n",
vd->videombuf.size, vd->videombuf.frames,
vd->videombuf.offsets[0], vd->videombuf.offsets[1]);
vd->pFramebuffer =
(unsigned char *) mmap (0, vd->videombuf.size, PROT_READ | PROT_WRITE,
MAP_SHARED, vd->fd, 0);
vd->mmapsize = vd->videombuf.size;
vd->vmmap.height = vd->hdrheight;
vd->vmmap.width = vd->hdrwidth;
vd->vmmap.format = vd->formatIn;
/*********************************************************
将mmap与video_mbuf绑定,把摄象头对应的设备文件映射到内存区,成功调用后设备文
件内容映射到内存区,返回的映象内存区指针给vd->pFramebuffer,失败时返回-1。
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
addr:共享内存的起始地址,一般设为0,表示由系统分配。
len:映射到调用进程地址空间的字节数,即指定映射内存的大小。在我们这里,该值为摄像
头video_mbuf结构体的size值,即图像数据帧的总大小。它从被映射文件开头offset个
字节开始算起。
prot:指定共享内存的访问权限 PROT_READ(可读), PROT_WRITE (可写),
PROT_EXEC (可执行)
flags :由MAP_SHARED和MAP_PRIVATE中必选一个,MAP_ FIXED不推荐使用addr
fd:共享文件的文件描述符
offset:一般设为0
mmap( ) 返回值是系统实际分配的起始地址
vd->mmapsize = vd->videombuf.size;
vd->vmmap.height = vd->hdrheight;
vd->vmmap.width = vd->hdrwidth;
vd->vmmap.format = vd->formatIn;
上面几行是修改vd->vmmap中的设置,例如设置图象帧的大小,垂直水平分辨率,
彩色显示 格式。
******************************************************************/
for (f = 0; f < vd->videombuf.frames; f++)
{
vd->vmmap.frame = f;//当前帧
if (ioctl (vd->fd, VIDIOCMCAPTURE, &(vd->vmmap)))
{
perror ("cmcapture");
}
}
vd->vmmap.frame = 0;
}
/******************************************************************
mmap方式下真正做视频截取的 VIDIOCMCAPTURE,上面是循环采集 。
ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
?若调用成功,则激活设备真正开始一帧的截取,是非阻塞的,
?是否截取完毕留给VIDIOCSYNC来判断
可以调用VIDIOCSYNC等待一帧截取结束
if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
{
perror("v4l_sync:VIDIOCSYNC");
return -1;
}
若成功,表明一帧截取已完成。可以开始做下一次 VIDIOCMCAPTURE
frame是当前截取的帧的序号。
这里没采用VIDIOCSYNC。
****关于双缓冲:
?video_bmuf bmuf.frames = 2;
?一帧被处理时可以采集另一帧
****************************************************************/
else
{
/* read method 直接读取方式*/
/* allocate the read buffer */
vd->pFramebuffer = (unsigned char *) realloc (vd->pFramebuffer, (size_t) vd->framesizeIn);
if(debug) printf (" grabbing method READ asked /n");
if (ioctl (vd->fd, VIDIOCGWIN, &(vd->videowin)) < 0)
perror ("VIDIOCGWIN failed /n");
vd->videowin.height = vd->hdrheight;
vd->videowin.width = vd->hdrwidth;
if (ioctl (vd->fd, VIDIOCSWIN , &(vd->videowin)) < 0)
perror ("VIDIOCSWIN failed /n");
if(debug) printf ("VIDIOCSWIN height %d width %d /n",
vd->videowin.height, vd->videowin.width);
}
vd->frame_cour = 0;
return erreur;
}
到此,V4L视频设备的初始化工作完成,现在我们退出init_v4l()函数,
进入init_videoIn()函数,我们看看它后面又是怎么工作的。
*******************************************************************/
/* allocate the 4 frames output buffer */
for (i = 0; i < OUTFRMNUMB; i++) //OUTFRMNUMB = 4
{
vd->ptframe[i] = NULL;
vd->ptframe[i] (unsigned char *) realloc (vd->ptframe[i],
sizeof (struct frame_t) + (size_t) vd->framesizeIn );
vd->framelock[i] = 0;
}
vd->frame_cour = 0; //将作为ptframe的下标
pthread_mutex_init (&vd->grabmutex, NULL); //初始化互斥锁,
//主要为了确保对
//ptframe的互斥访问
/*********************************************************************************
(1)ptframe中将存储格式转换后将要通过网络发送的视频数据 ,这里主要对其进行初始化并分配
内存空间,再每段视频数据前附加了一个frame_t格式的信息头,主要传递和视频相关的参数信息。
(2)struct frame_t定义在spcaframe.h中
struct frame_t
{
char header[5]; //
int nbframe;
double seqtimes;
int deltatimes;
int w;
int h;
int size;
int format;
unsigned short bright;
unsigned short contrast;
unsigned short colors;
unsigned short exposure;
unsigned char wakeup;
int acknowledge;
} __attribute__ ((packed));
__attrubte__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
在v4lGrab 函数中他们将被设置如下:
struct frame_t *headerframe;
.............................
jpegsize= convertframe(vd->ptframe[vd->frame_cour]
+ sizeof(struct frame_t),
vd->pFramebuffer
+ vd->videombuf.offsets[vd->vmmap.frame],
vd->hdrwidth,vd->hdrheight,vd->formatIn,
vd>framesizeIn);
headerframe=(struct frame_t*)vd->ptframe[vd->frame_cour];
snprintf(headerframe->header,5,"%s","SPCA");
headerframe->seqtimes = ms_time();
headerframe->deltatimes=(int)(headerframe->seqtimes-timecourant);
headerframe->w = vd->hdrwidth;
headerframe->h = vd->hdrheight;
headerframe->size = (( jpegsize < 0)?0:jpegsize);
headerframe->format = vd->formatIn;
headerframe->nbframe = frame++;
*********************************************************************************/
init_videoIn完成后回到sever.c中,将启动抓图线程
pthread_create (&w1, NULL, (void *) grab, NULL); //start grab pthread
进入grab函数
void grab (void)
{
int err = 0;
for (;;)
{
//if(debug) printf("I am the GRABBER !!!!! /n");
err = v4lGrab(&videoIn);
if (!videoIn.signalquit || (err < 0))
{
if(debug) printf("GRABBER going out !!!!! /n");
break;
}
}
}
可以看到这里面主要是调用v4lGrab函数,v4lGrab定义在spcav4l.c中,跟踪进入
此函数主要根据vd->grabMethod的值决定抓图方法,grabMethod = 1采用mmap方法
grabMethod = 0 采用read方法,由于在启动servfox中使用了 -g 参数,所以使用
直接read方法。关键代码如下:
/* read method */
size = vd->framesizeIn;
len = read (vd->fd, vd->pFramebuffer, size); //采集的视频数据放在pFramebuffer中
if (len < 0 )
{
if(debug) printf ("v4l read error/n");
if(debug) printf ("len %d asked %d /n", len, size);
return 0;
}
/* Is there someone using the frame */
while((vd->framelock[vd->frame_cour] != 0)&& vd->signalquit)
//如果有人在用ptframe缓冲区,
usleep(1000); //等待
pthread_mutex_lock (&vd->grabmutex);
temps = ms_time();
jpegsize= convertframe(vd->ptframe[vd->frame_cour]
+ sizeof(struct frame_t),
vd->pFramebuffer ,vd->hdrwidth,
vd->hdrheight,vd->formatIn,vd->framesizeIn);
headerframe=(struct frame_t*)vd->ptframe[vd->frame_cour];
headerframe->seqtimes = ms_time();
headerframe->deltatimes=(int)(headerframe->seqtimes-timecourant);
headerframe->w = vd->hdrwidth;
headerframe->h = vd->hdrheight;
headerframe->size = (( jpegsize < 0)?0:jpegsize);
headerframe->format = vd->formatIn;
headerframe->nbframe = frame++;
// if(debug) printf("compress frame %d times %f/n",frame, headerframe->seqtimes-temps);
vd->frame_cour = (vd->frame_cour +1) % OUTFRMNUMB;
pthread_mutex_unlock (&vd->grabmutex);
/*********************************************************************************
len = read (vd->fd, vd->pFramebuffer, size);
可以看到,采集到的原始视频数据放在pFramebuffer缓冲区中,然后锁定ptframe缓冲区
对采集到的视频数据进行转换并加入相关的附加信息头,存储到ptframe缓冲区中,等待网络
发送。
/*********************************************************************************
再回到server.c中
serv_sock = open_sock(serverport); //打开网络
signal(SIGPIPE, SIG_IGN); /* Ignore sigpipe */
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
syslog(LOG_ERR,"Spcaserv Listening on Port %d/n",serverport);
printf("Waiting .... for connection. CTrl_c to stop !!!! /n");
while (videoIn.signalquit)
{
sin_size = sizeof(struct sockaddr_in);
if ((new_sock = accept(serv_sock, (struct sockaddr *)&their_addr, &sin_size)) == -1)
{
continue;
}
syslog(LOG_ERR,"Got connection from %s/n",inet_ntoa(their_addr.sin_addr));
printf("Got connection from %s/n",inet_ntoa(their_addr.sin_addr));
pthread_create(&server_th, NULL, (void *)service, &new_sock);
}
pthread_join (w1, NULL);
/*********************************************************************************
此时将打开网络,并对绑定的7070端口进行侦听,然后调用accept从套接口队列中检索出等待
的请求信号,并启动service发送线程。
进入void service (void *ir)函数,它将读取请信息,进行相应的处理。然后发送存储于
ptframe中的视频数据,关键代码如下
if (videoIn.signalquit)
{
videoIn.framelock[frameout]++;
headerframe = (struct frame_t *) videoIn.ptframe[frameout];
headerframe->acknowledge = ack;
headerframe->bright = bright;
headerframe->contrast = contrast;
headerframe->wakeup = wakeup;
ret = write_sock(sock, (unsigned char *)headerframe,
sizeof(struct frame_t)) ;
if(!wakeup)
ret = write_sock(sock,(unsigned char*)(videoIn.ptframe [frameout]+sizeof(struct frame_t)),headerframe->size);
videoIn.framelock[frameout]--;
frameout = (frameout+1)%4;
}