之前已经分析了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);
}
二、接着又是命令行参数的解析,解析的是传过来的param参数(参数解析大同小异不再分析)
/* 为后面的解析参数而做的准备工作 */
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<argc; i++) {
DBG("argv[%d]=%s\n", i, argv[i]);
}
/* 正式开始解析参数了,原理跟之前分析main中的参数解析一样 */
reset_getopt();
while(1) {
int option_index = 0, c=0;
static struct option long_options[] = \
{
{"h", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{"d", required_argument, 0, 0},
{"device", required_argument, 0, 0},
{"r", required_argument, 0, 0},
{"resolution", required_argument, 0, 0},
{"f", required_argument, 0, 0},
{"fps", required_argument, 0, 0},
{"y", no_argument, 0, 0},
{"yuv", no_argument, 0, 0},
{"q", required_argument, 0, 0},
{"quality", required_argument, 0, 0},
{"m", required_argument, 0, 0},
{"minimum_size", required_argument, 0, 0},
{"n", no_argument, 0, 0},
{"no_dynctrl", no_argument, 0, 0},
{"l", required_argument, 0, 0},
{"led", required_argument, 0, 0},
{0, 0, 0, 0}
};
/* 这就是那个专门用来解析参数的函数,刚才上面做的参数解析的准备工作就是为了它 */
c = getopt_long_only(argc, argv, "", long_options, &option_index);
if (c == -1) break;
if (c == '?'){
help();
return 1;
}
switch (option_index) {
case 0:
case 1:
DBG("case 0,1\n");
help();
return 1;
break;
case 2:
case 3:
DBG("case 2,3\n");
dev = strdup(optarg);
break;
case 4:
case 5:
DBG("case 4,5\n");
width = -1;
height = -1;
for ( i=0; i < LENGTH_OF(resolutions); i++ ) {
if ( strcmp(resolutions[i].string, optarg) == 0 ) {
width = resolutions[i].width;
height = resolutions[i].height;
}
}
if(width != -1 && height != -1)
break;
width = strtol(optarg, &s, 10);
height = strtol(s+1, NULL, 10);
break;
case 6:
case 7:
DBG("case 6,7\n");
fps=atoi(optarg);
break;
case 8:
case 9:
DBG("case 8,9\n");
format = V4L2_PIX_FMT_YUYV;
break;
case 10:
case 11:
DBG("case 10,11\n");
format = V4L2_PIX_FMT_YUYV;
gquality = MIN(MAX(atoi(optarg), 0), 100);
break;
case 12:
case 13:
DBG("case 12,13\n");
minimum_size = MAX(atoi(optarg), 0);
break;
case 14:
case 15:
DBG("case 14,15\n");
dynctrls = 0;
break;
case 16:
case 17:
DBG("case 16,17\n");
if ( strcmp("on", optarg) == 0 ) {
led = IN_CMD_LED_ON;
} else if ( strcmp("off", optarg) == 0 ) {
led = IN_CMD_LED_OFF;
} else if ( strcmp("auto", optarg) == 0 ) {
led = IN_CMD_LED_AUTO;
} else if ( strcmp("blink", optarg) == 0 ) {
led = IN_CMD_LED_BLINK;
}
break;
default:
DBG("default case\n");
help();
return 1;
}
}
三、接着就是分配一个空的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; } }
到此处为止input_init函数的框架就讲解完毕了,就是上面标注的四个步骤一、二、三、四//设置摄像头的输出格式(这些格式也是命令行参数传来的,也可用默认的) 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; }
--------------------------------------------------------------------------------------------------------------------------
2、input_run(...)函数代码量比较少,直接贴代码
到此为止input_run函数的框架就讲解完毕了,详细的子函数cam_thread分析如下(见同色代码):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; }
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");
pglobal->size = memcpy_picture(pglobal->buf, videoIn->tmpbuffer, videoIn->buf.bytesused);//将数据拷贝到global的buf中去 }//将数据拷贝到global的buf中去:memcpy_picture函数做了两件事:1、判断图像是否损坏(并修复) 2、直接用mencpy拷贝图像
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;}//发出一个数据更新的信号,通知发送渠道可以来取数据了!(非常重要!因为此函数让输出渠道知道global中的buf被更新了)
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; }
到此为止,输入渠道已经全部讲解完毕(即input_init函数和input_run函数)