mjpg-streamer详解2

来源:互联网 发布:橡树岛宝藏 知乎 编辑:程序博客网 时间:2024/05/01 00:30

之前已经分析了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;    }  }
  //设置摄像头的输出格式(这些格式也是命令行参数传来的,也可用默认的) 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函数的框架就讲解完毕了,就是上面标注的四个步骤一、二、三、四

--------------------------------------------------------------------------------------------------------------------------
2、input_run(...)函数

  代码量比较少,直接贴代码

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;}

到此为止,输入渠道已经全部讲解完毕(即input_init函数和input_run函数)




                                             
0 0