之前已经分析了main函数,了解了整个程序的主体,最根本的就是那四个函数,接下来就开始继续分析这四个函数:
本篇先讲解一下input_init函数和input_run函数
1、input_init(...)函数
一、首先是一些变量的初始化(后面会用到这些变量)
int input_init(input_parameter *param) {/* 函数的参数 param 是在main函数中传过来的,它是一个结构体(内部有两个成员:parameter_string、global)
parameter_string = "-f 10 -r 320*240" 、 global = &global(即那个全局global的地址)
char *argv[MAX_ARGUMENTS]={NULL}, *dev = "/dev/video0", *s;
int argc=1, width=640, height=480, fps=5, format=V4L2_PIX_FMT_MJPEG, i;
in_cmd_type led = IN_CMD_LED_AUTO;
/* 初始化 controls_mutex 这个互斥锁全局变量*/
if( pthread_mutex_init(&controls_mutex, NULL) != 0 ) {
IPRINT("could not initialize mutex variable\n");
exit(EXIT_FAILURE);
}
/* 为后面的解析参数而做的准备工作 */
argv[0] = INPUT_PLUGIN_NAME;
if ( param->parameter_string != NULL && strlen(param->parameter_string) != 0 ) {
char *arg=NULL, *saveptr=NULL, *token=NULL;
arg=(char *)strdup(param->parameter_string);
if ( strchr(arg, ' ') != NULL ) {
token=strtok_r(arg, " ", &saveptr);
if ( token != NULL ) {
argv[argc] = strdup(token);
argc++;
while ( (token=strtok_r(NULL, " ", &saveptr)) != NULL ) {
argv[argc] = strdup(token);
argc++;
if (argc >= MAX_ARGUMENTS) {
IPRINT("ERROR: too many arguments to input plugin\n");
return 1;
}
}
}
}
}
for (i=0; i
三、接着就是分配一个空的vdIn结构体,它是摄像头相关的(里面涵盖了摄像头所有的参数和数据),后面会来填
充这个结构体的!
/* 从param中取出global 给本文件中使用*/
pglobal = param->global;
/* 分配一个结构体:涵盖了摄像头所有信息 */
videoIn = malloc(sizeof(struct vdIn));
if ( videoIn == NULL ) {
IPRINT("not enough memory for videoIn\n");
exit(EXIT_FAILURE);
}
memset(videoIn, 0, sizeof(struct vdIn));
/*打印一些参数(这些参数都是之前解析所得到的结果) */
IPRINT("Using V4L2 device.: %s\n", dev);//打印设备节点
IPRINT("Desired Resolution: %i x %i\n", width, height);//打印分辨率
IPRINT("Frames Per Second.: %i\n", fps);//打印帧率
IPRINT("Format............: %s\n", (format==V4L2_PIX_FMT_YUYV)?"YUV":"MJPEG");//打印格式
if ( format == V4L2_PIX_FMT_YUYV )
IPRINT("JPEG Quality......: %d\n", gquality);//如果是YUYV格式的,就打印质量
四、最后就是 : 1、填充那个vdIn结构体了 2、调用v4l2相关函数 3、分配缓存
if (init_videoIn(videoIn, dev, width, height, fps, format, 1) < 0) {//init_videoIn函数是核心,所以贴其代码继续分析,见下面的同色代码
IPRINT("init_VideoIn failed\n");
closelog();
exit(EXIT_FAILURE);
}
//动态控制摄像头,比如调节亮度、白平衡、焦距、饱和度等等(这边不讲了)
if (dynctrls)
initDynCtrls(videoIn->fd);//动态调节摄像头所需要的一些初始化
input_cmd(led, 0);//通过命令行来正式开始调节
return 0;
}
此处是上面步骤四中的子函数的讲解部分
int init_videoIn(struct vdIn *vd, char *device, int width, int height, int fps, int format, int grabmethod)
{
//先是做一些判断
if (vd == NULL || device == NULL)
return -1;
if (width == 0 || height == 0)
return -1;
if (grabmethod < 0 || grabmethod > 1)
grabmethod = 1;
//填充刚才第三步骤我们所分配的那个vdIn结构体(填充的内容就是解析参数所获得的那些东西)
vd->videodevice = NULL;
vd->status = NULL;
vd->pictName = NULL;
vd->videodevice = (char *) calloc (1, 16 * sizeof (char));
vd->status = (char *) calloc (1, 100 * sizeof (char));
vd->pictName = (char *) calloc (1, 80 * sizeof (char));
snprintf (vd->videodevice, 12, "%s", device);
vd->toggleAvi = 0;
vd->getPict = 0;
vd->signalquit = 1;
vd->width = width;
vd->height = height;
vd->fps = fps;
vd->formatIn = format;
vd->grabmethod = grabmethod;
if (init_v4l2 (vd) < 0) {//init_v4l2函数必要重要,放在下面讲,见同色代码部分!
fprintf (stderr, " Init v4L2 failed !! exit fatal \n");
goto error;;
}
//分配一个临时缓冲区用来接收视频数据
vd->framesizeIn = (vd->width * vd->height << 1);//要分配的缓冲区的大小
//不同的输出格式有不同的分配方式
switch (vd->formatIn) {
case V4L2_PIX_FMT_MJPEG: //我用的是MJPEG格式的,所以走这个分支(分配了两个buf,一个tmpbuffer,一个framebuffer)
vd->tmpbuffer = (unsigned char *) calloc(1, (size_t) vd->framesizeIn);//注意,tmp翻译就是临时的意思,相当于一个中转缓存区
if (!vd->tmpbuffer)
goto error;
vd->framebuffer =
(unsigned char *) calloc(1, (size_t) vd->width * (vd->height + 8) * 2);
break;
case V4L2_PIX_FMT_YUYV:
vd->framebuffer =
(unsigned char *) calloc(1, (size_t) vd->framesizeIn);
break;
default:
fprintf(stderr, " should never arrive exit fatal !!\n");
goto error;
break;
}
if (!vd->framebuffer)
goto error;
return 0;
error:
free(vd->videodevice);
free(vd->status);
free(vd->pictName);
close(vd->fd);
return -1;
}
static int init_v4l2(struct vdIn *vd)//这个函数就是调用UVC摄像头驱动给上层应用提供的那些接口函数
{
int i;
int ret = 0;
//首先是打开设备节点,例如/dev/video0
if ((vd->fd = open(vd->videodevice, O_RDWR)) == -1) {
perror("ERROR opening V4L interface");
return -1;
}
//查看所打开的设备是不是视频捕获设备
memset(&vd->cap, 0, sizeof(struct v4l2_capability));
ret = ioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap);//调用了之后第三个参数就会获得摄像头的信息(例如是不是视频捕获设备)
if (ret < 0) {
fprintf(stderr, "Error opening device %s: unable to query device.\n", vd->videodevice);
goto fatal;
}
//判断是否是视频捕获设备(根据刚才得到的vd->cap)
if ((vd->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
fprintf(stderr, "Error opening device %s: video capture not supported.\n",
vd->videodevice);
goto fatal;;
}
//判断是否支持该种数据传输方式
if (vd->grabmethod) {
if (!(vd->cap.capabilities & V4L2_CAP_STREAMING)) { //流传输
fprintf(stderr, "%s does not support streaming i/o\n", vd->videodevice);
goto fatal;
}
} else {
if (!(vd->cap.capabilities & V4L2_CAP_READWRITE)) { //读写传输
fprintf(stderr, "%s does not support read i/o\n", vd->videodevice);
goto fatal;
}
}
//设置摄像头的输出格式(这些格式也是命令行参数传来的,也可用默认的)
ret = ioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);
if (ret < 0) {
perror("Unable to set format");
goto fatal;
}
//设置摄像头的输出格式(命令行解析的参数赋值给它们的,也可用默认)
memset(&vd->fmt, 0, sizeof(struct v4l2_format));
vd->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd->fmt.fmt.pix.width = vd->width; //分辨率——宽
vd->fmt.fmt.pix.height = vd->height;//分辨率——高
vd->fmt.fmt.pix.pixelformat = vd->formatIn;//输出格式(MJPEG或者YUYV)
vd->fmt.fmt.pix.field = V4L2_FIELD_ANY;
ret = ioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);//执行了此函数后才真的设置好了
if (ret < 0) {
perror("Unable to set format");
goto fatal;
}
if ((vd->fmt.fmt.pix.width != vd->width) ||
(vd->fmt.fmt.pix.height != vd->height)) {
fprintf(stderr, " format asked unavailable get width %d height %d \n", vd->fmt.fmt.pix.width, vd->fmt.fmt.pix.height);
vd->width = vd->fmt.fmt.pix.width;
vd->height = vd->fmt.fmt.pix.height;
/*
* look the format is not part of the deal ???
*/
// vd->formatIn = vd->fmt.fmt.pix.pixelformat;
}
//设置摄像头输出的帧率
struct v4l2_streamparm *setfps;
setfps = (struct v4l2_streamparm *) calloc(1, sizeof(struct v4l2_streamparm));
memset(setfps, 0, sizeof(struct v4l2_streamparm));
setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
setfps->parm.capture.timeperframe.numerator = 1;
setfps->parm.capture.timeperframe.denominator = vd->fps;
ret = ioctl(vd->fd, VIDIOC_S_PARM, setfps);
//申请缓存
memset(&vd->rb, 0, sizeof(struct v4l2_requestbuffers));//v4l2_requestbuffers中是需要我们自己设定,让其知道需要分配几个buffer等等
vd->rb.count = NB_BUFFER;//NB_BUFFER是个宏,等于4,表示要申请4个缓存
vd->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd->rb.memory = V4L2_MEMORY_MMAP;//此标志表示:申请驱动分配连续的一段物理内存空间
ret = ioctl(vd->fd, VIDIOC_REQBUFS, &vd->rb);
if (ret < 0) {
perror("Unable to allocate buffers");
goto fatal;
}
//这个for循环是将刚才申请的缓存映射到用户空间
for (i = 0; i < NB_BUFFER; i++) {
//获取内核空间视频缓冲区的信息(刚才申请的缓存)
memset(&vd->buf, 0, sizeof(struct v4l2_buffer));//vd->buf 是 struct v4l2_buffer结构
vd->buf.index = i;
vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd->buf.memory = V4L2_MEMORY_MMAP;
/*
* VIDIOC_QUERYBUF表示获取缓冲区的状态(例如): 缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等
* 上层获得了这些信息之后,就可以用mmap函数将缓冲区的内核空间地址映射到用户空间,以后就可以在用户空间访问到内核空间的缓冲区了
* 获得的这些信息会填充此函数的参数3:vd->buf(即v4l2_buffer这个结构体)
* 总结:VIDIOC_QUERYBUF就是为了得到信息来为后面的mmap函数所服务的!
*/
ret = ioctl(vd->fd, VIDIOC_QUERYBUF, &vd->buf);
if (ret < 0) {
perror("Unable to query buffer");
goto fatal;
}
if (debug)
fprintf(stderr, "length: %u offset: %u\n", vd->buf.length, vd->buf.m.offset);
//根据刚才获得的缓存信息做映射操作,用的是mmap函数
vd->mem[i] = mmap(0,vd->buf.length, PROT_READ, MAP_SHARED, vd->fd,vd->buf.m.offset);
if (vd->mem[i] == MAP_FAILED) {
perror("Unable to map buffer");
goto fatal;
}
if (debug)
fprintf(stderr, "Buffer mapped at address %p.\n", vd->mem[i]);
}
//将申请的缓冲区入队列
for (i = 0; i < NB_BUFFER; ++i) {
memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
vd->buf.index = i;
vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd->buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(vd->fd, VIDIOC_QBUF, &vd->buf);
if (ret < 0) {
perror("Unable to queue buffer");
goto fatal;;
}
}
return 0;
fatal:
return -1;
}
到此处为止input_init函数的框架就讲解完毕了,就是上面标注的四个步骤一、二、三、四代码量比较少,直接贴代码
int input_run(void) {
//先为global的buf分配大小为一帧图像的缓存
pglobal->buf = malloc(videoIn->framesizeIn);
if (pglobal->buf == NULL) {
fprintf(stderr, "could not allocate memory\n");
exit(EXIT_FAILURE);
}
//创建一个线程(线程的概念可以参照我博客中的《线程基础篇》)
pthread_create(&cam, 0, cam_thread, NULL);//这行执行后参数3中的函数cam_thread就跑起来了
pthread_detach(cam);//等待线程执行完,然后回收它的资源
return 0;
}
到此为止input_run函数的框架就讲解完毕了,详细的子函数cam_thread分析如
下(见同色代码):
void *cam_thread( void *arg ) {
//当线程执行完后会调用cam_cleanup函数,来做些清理工作
pthread_cleanup_push(cam_cleanup, NULL);
//如果pglobal->stop这个标志一直为0的话就一直循环(但是当我们按下ctrl+c的时候其会被置为1,即跳出循环(做法详见main函数中的那个信号绑定))
while( !pglobal->stop )
{
//获得一帧数据
if( uvcGrab(videoIn) < 0 ) {//函数uvcGrab的讲解见下面同色代码
IPRINT("Error grabbing frames\n");
exit(EXIT_FAILURE);
}
DBG("received frame of size: %d\n", videoIn->buf.bytesused);
if ( videoIn->buf.bytesused < minimum_size )//根据数据大小,判断该帧是否有效
{
DBG("dropping too small frame, assuming it as broken\n");
continue;//如果无效,就不执行下面,直接跳到上面的while循环去接着执行下一个循环
}
//设置一个临界区:即从下面开始直到调用pthread_mutex_unlock函数,在这中间的部分只能让一个线程执行(防止竞态)
pthread_mutex_lock( &pglobal->db );
if (videoIn->formatIn == V4L2_PIX_FMT_YUYV) {
DBG("compressing frame\n");
pglobal->size = compress_yuyv_to_jpeg(videoIn, pglobal->buf, videoIn->framesizeIn, gquality);//压缩YUYV到jpeg格式
}
else {//此时因为是MJPEG格式,所以走的是这一条分支
DBG("copying frame\n");
//将数据拷贝到global的buf中去:memcpy_picture函数做了两件事:1、判断图像是否损坏(并修复) 2、直接用mencpy拷贝图像
pglobal->size = memcpy_picture(pglobal->buf, videoIn->tmpbuffer, videoIn->buf.bytesused);//将数据拷贝到global的buf中去 }
//发出一个数据更新的信号,通知发送渠道可以来取数据了!(非常重要!因为此函数让输出渠道知道global中的buf被更新了)
pthread_cond_broadcast(&pglobal->db_update); pthread_mutex_unlock( &pglobal->db );//为上面的这部分临界区解锁了 DBG("waiting for next frame\n");//打印,告诉我们一帧数据已经采集完成了 if ( videoIn->fps < 5 ) { usleep(1000*1000/videoIn->fps);//如果帧率小于5就做一个小的延时操作 } } DBG("leaving input thread, calling cleanup function now\n"); pthread_cleanup_pop(1); return NULL;}
int uvcGrab(struct vdIn *vd)
{
#define HEADERFRAME1 0xaf
int ret;
if (!vd->isstreaming)
if (video_enable(vd))//使能该设备:内部其实就是调用的ioctl(vd->fd, VIDIOC_STREAMON, &type);
goto err;
//从视频缓冲队列取出一个已经存有一帧数据的缓存
memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vd->buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(vd->fd, VIDIOC_DQBUF, &vd->buf);
if (ret < 0) {
perror("Unable to dequeue buffer");
goto err;
}
switch (vd->formatIn)
{
case V4L2_PIX_FMT_MJPEG://视频格式为MJPEG格式,所以现在执行此分支
if (vd->buf.bytesused <= HEADERFRAME1) //根据数据的大小,判断该帧数据是否有效
{
fprintf(stderr, "Ignoring empty buffer ...\n");
return 0;
}
//将视频数据拷贝到vd->tmpbuffer中
memcpy(vd->tmpbuffer, vd->mem[vd->buf.index], vd->buf.bytesused);
if (debug)//判断是否使能了调试功能
fprintf(stderr, "bytes in used %d \n", vd->buf.bytesused);
break;
case V4L2_PIX_FMT_YUYV:
if (vd->buf.bytesused > vd->framesizeIn)
memcpy (vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->framesizeIn);
else
memcpy (vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->buf.bytesused);
break;
default:
goto err;
break;
}
//投放一个空的视频缓冲到视频缓冲队列中
ret = ioctl(vd->fd, VIDIOC_QBUF, &vd->buf);
if (ret < 0) {
perror("Unable to requeue buffer");
goto err;
}
return 0;
err:
vd->signalquit = 0;
return -1;
}