mjpg-streamer详解2


之前已经分析了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

    三、接着就是分配一个空的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函数)




 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
 

你可能感兴趣的:(项目经验积累)