MJPG-streamer源码简析

参考:http://blog.csdn.net/tandesir/article/details/8437442 系列

     http://blog.csdn.net/guishong/article/details/8660725

     http://blog.csdn.net/dongshutong/article/details/7184776 系列

  MJPG-streamer主体上是由main函数和输入插件、输出插件组成。

  软件运行的流程是先对摄像头进行初始化然后设置基本的输入输出参数,接着从摄像头中获取数据放到全局内存中,然后通知输出函数来取出,接着输出。

  摄像头的初始化由结构体vdIn来进行。

 1 struct vdIn {
 2     int fd;
 3     char *videodevice;
 4     char *status;
 5     char *pictName;
 6     struct v4l2_capability cap;
 7     struct v4l2_format fmt;
 8     struct v4l2_buffer buf;
 9     struct v4l2_requestbuffers rb;
10     void *mem[NB_BUFFER];
11     unsigned char *tmpbuffer;
12     unsigned char *framebuffer;
13     int isstreaming;
14     int grabmethod;
15     int width;
16     int height;
17     int fps;
18     int formatIn;
19     int formatOut;
20     int framesizeIn;
21     int signalquit;
22     int toggleAvi;
23     int getPict;
24     int rawFrameCapture;
25     /* raw frame capture */
26     unsigned int fileCounter;
27     /* raw frame stream capture */
28     unsigned int rfsFramesWritten;
29     unsigned int rfsBytesWritten;
30     /* raw stream capture */
31     FILE *captureFile;
32     unsigned int framesWritten;
33     unsigned int bytesWritten;
34     int framecount;
35     int recordstart;
36     int recordtime;
37 };

  数据传输的开始、停止、结束等函数在输入插件中,它们有一个结构体output统领着。而数据输出的开始、停止、结束等函数则是在输出插件中,由结构体output指挥。

 1 struct _input {
 2   char *plugin;
 3   void *handle;
 4   input_parameter param;
 5 
 6   int (*init)(input_parameter *);
 7   int (*stop)(void);
 8   int (*run)(void);
 9   int (*cmd)(in_cmd_type, int);
10 };
 1 struct _output {
 2   char *plugin;
 3   void *handle;
 4   output_parameter param;
 5 
 6   int (*init)(output_parameter *);
 7   int (*stop)(int);
 8   int (*run)(int);
 9   int (*cmd)(int, out_cmd_type, int);
10 };

  这些函数编译成了两个动态链接库,分别是input_uvc.so和output_http.so

  这些函数以动态链接库的方式在main函数中打开并调用。

 1   /* open input plugin */
 2   tmp = (size_t)(strchr(input, ' ')-input);
 3   global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);
 4   global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);
 5   if ( !global.in.handle ) {
 6     LOG("ERROR: could not find input plugin
");
 7     LOG("       Perhaps you want to adjust the search path with:
");
 8     LOG("       # export LD_LIBRARY_PATH=/path/to/plugin/folder
");
 9     LOG("       dlopen: %s
", dlerror() );
10     closelog();
11     exit(EXIT_FAILURE);
12   }
13   global.in.init = dlsym(global.in.handle, "input_init");
14   if ( global.in.init == NULL ) {
15     LOG("%s
", dlerror());
16     exit(EXIT_FAILURE);
17   }
18   global.in.stop = dlsym(global.in.handle, "input_stop");
19   if ( global.in.stop == NULL ) {
20     LOG("%s
", dlerror());
21     exit(EXIT_FAILURE);
22   }
23   global.in.run = dlsym(global.in.handle, "input_run");
24   if ( global.in.run == NULL ) {
25     LOG("%s
", dlerror());
26     exit(EXIT_FAILURE);
27   }
28   /* try to find optional command */
29   global.in.cmd = dlsym(global.in.handle, "input_cmd");
30 
31   global.in.param.parameter_string = strchr(input, ' ');
32   global.in.param.global = &global;
33 
34   if ( global.in.init(&global.in.param) ) {
35     LOG("input_init() return value signals to exit");
36     closelog();
37     exit(0);
38   }
 1   /* open output plugin */
 2   for (i=0; i<global.outcnt; i++) {
 3     tmp = (size_t)(strchr(output[i], ' ')-output[i]);
 4     global.out[i].plugin = (tmp > 0)?strndup(output[i], tmp):strdup(output[i]);
 5     global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
 6     if ( !global.out[i].handle ) {
 7       LOG("ERROR: could not find output plugin %s
", global.out[i].plugin);
 8       LOG("       Perhaps you want to adjust the search path with:
");
 9       LOG("       # export LD_LIBRARY_PATH=/path/to/plugin/folder
");
10       LOG("       dlopen: %s
", dlerror() );
11       closelog();
12       exit(EXIT_FAILURE);
13     }
14     global.out[i].init = dlsym(global.out[i].handle, "output_init");
15     if ( global.out[i].init == NULL ) {
16       LOG("%s
", dlerror());
17       exit(EXIT_FAILURE);
18     }
19     global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
20     if ( global.out[i].stop == NULL ) {
21       LOG("%s
", dlerror());
22       exit(EXIT_FAILURE);
23     }
24     global.out[i].run = dlsym(global.out[i].handle, "output_run");
25     if ( global.out[i].run == NULL ) {
26       LOG("%s
", dlerror());
27       exit(EXIT_FAILURE);
28     }
29     /* try to find optional command */
30     global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");
31 
32     global.out[i].param.parameter_string = strchr(output[i], ' ');
33     global.out[i].param.global = &global;
34     global.out[i].param.id = i;
35     if ( global.out[i].init(&global.out[i].param) ) {
36       LOG("output_init() return value signals to exit");
37       closelog();
38       exit(0);
39     }
40   }

  开始运行输入数据的处理以及放到全局内存中。

  /* start to read the input, push pictures into global buffer */
  DBG("starting input plugin
");
  syslog(LOG_INFO, "starting input plugin");
  global.in.run();

  开始运行从全局内存中取出数据并输出。

  DBG("starting %d output plugin(s)
", global.outcnt);
  for(i=0; i<global.outcnt; i++) {
    syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
    global.out[i].run(global.out[i].param.id);
  }

  

一:main函数

  首先设定默认的输入输出动态链接库,接着是一个while(1)的循环,它是对输入参数的判断并执行,然后判断是否是守护进程和对全局变量global的一些基本参数设定。接着创建并初始化线程(用来运行数据采集及输出的函数)。然后打开输入、输出的动态链接库,把里面的初始化、运行、停止、结束等函数进行一个导出(相当于从一个库里面取出一个已经写好了的函数然后调用,没别的什么复杂的)。最后就运行从库中得到的运行函数,然后等待线程结束。

二:输入

  Input_uvc.c函数中:

  

  1.线程的创建和清理,用来采集数据

  2.输入数据的初始化和停止、运行、命令等

  而这些函数具体的行动则是在V4l2uvc.c中实现的:

  

 1 int init_videoIn(struct vdIn *vd, char *device, int width, int height, int fps, int format, int grabmethod)
 2 {
 3   if (vd == NULL || device == NULL)
 4     return -1;
 5   if (width == 0 || height == 0)
 6     return -1;
 7   if (grabmethod < 0 || grabmethod > 1)
 8     grabmethod = 1;        //mmap by default;
 9   vd->videodevice = NULL;
10   vd->status = NULL;
11   vd->pictName = NULL;
12   vd->videodevice = (char *) calloc (1, 16 * sizeof (char));
13   vd->status = (char *) calloc (1, 100 * sizeof (char));
14   vd->pictName = (char *) calloc (1, 80 * sizeof (char));
15   snprintf (vd->videodevice, 12, "%s", device);
16   vd->toggleAvi = 0;
17   vd->getPict = 0;
18   vd->signalquit = 1;
19   vd->width = width;
20   vd->height = height;
21   vd->fps = fps;
22   vd->formatIn = format;
23   vd->grabmethod = grabmethod;
24   if (init_v4l2 (vd) < 0) {
25     fprintf (stderr, " Init v4L2 failed !! exit fatal 
");
26     goto error;;
27   }
28   /* alloc a temp buffer to reconstruct the pict */
29   vd->framesizeIn = (vd->width * vd->height << 1);
30   switch (vd->formatIn) {
31   case V4L2_PIX_FMT_MJPEG:
32     vd->tmpbuffer = (unsigned char *) calloc(1, (size_t) vd->framesizeIn);
33     if (!vd->tmpbuffer)
34       goto error;
35     vd->framebuffer =
36         (unsigned char *) calloc(1, (size_t) vd->width * (vd->height + 8) * 2);
37     break;
38   case V4L2_PIX_FMT_YUYV:
39     vd->framebuffer =
40         (unsigned char *) calloc(1, (size_t) vd->framesizeIn);
41     break;
42   default:
43     fprintf(stderr, " should never arrive exit fatal !!
");
44     goto error;
45     break;
46   }
47   if (!vd->framebuffer)
48     goto error;
49   return 0;
50 error:
51   free(vd->videodevice);
52   free(vd->status);
53   free(vd->pictName);
54   close(vd->fd);
55   return -1;
56 }
  1 static int init_v4l2(struct vdIn *vd)
  2 {
  3   int i;
  4   int ret = 0;
  5 
  6   if ((vd->fd = open(vd->videodevice, O_RDWR)) == -1) {
  7     perror("ERROR opening V4L interface");
  8     return -1;
  9   }
 10 
 11   memset(&vd->cap, 0, sizeof(struct v4l2_capability));
 12   ret = ioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap);
 13   if (ret < 0) {
 14     fprintf(stderr, "Error opening device %s: unable to query device.
", vd->videodevice);
 15     goto fatal;
 16   }
 17 
 18   if ((vd->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
 19     fprintf(stderr, "Error opening device %s: video capture not supported.
",
 20            vd->videodevice);
 21     goto fatal;;
 22   }
 23 
 24   if (vd->grabmethod) {
 25     if (!(vd->cap.capabilities & V4L2_CAP_STREAMING)) {
 26       fprintf(stderr, "%s does not support streaming i/o
", vd->videodevice);
 27       goto fatal;
 28     }
 29   } else {
 30     if (!(vd->cap.capabilities & V4L2_CAP_READWRITE)) {
 31       fprintf(stderr, "%s does not support read i/o
", vd->videodevice);
 32       goto fatal;
 33     }
 34   }
 35 
 36   /*
 37    * set format in
 38    */
 39   memset(&vd->fmt, 0, sizeof(struct v4l2_format));
 40   vd->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 41   vd->fmt.fmt.pix.width = vd->width;
 42   vd->fmt.fmt.pix.height = vd->height;
 43   vd->fmt.fmt.pix.pixelformat = vd->formatIn;
 44   vd->fmt.fmt.pix.field = V4L2_FIELD_ANY;
 45   ret = ioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);
 46   if (ret < 0) {
 47     perror("Unable to set format");
 48     goto fatal;
 49   }
 50 
 51   if ((vd->fmt.fmt.pix.width != vd->width) ||
 52       (vd->fmt.fmt.pix.height != vd->height)) {
 53     fprintf(stderr, " format asked unavailable get width %d height %d 
", vd->fmt.fmt.pix.width, vd->fmt.fmt.pix.height);
 54     vd->width = vd->fmt.fmt.pix.width;
 55     vd->height = vd->fmt.fmt.pix.height;
 56     /*
 57      * look the format is not part of the deal ???
 58      */
 59      //vd->formatIn = vd->fmt.fmt.pix.pixelformat;
 60   }
 61 
 62   /*
 63    * set framerate
 64    */
 65   struct v4l2_streamparm *setfps;
 66   setfps = (struct v4l2_streamparm *) calloc(1, sizeof(struct v4l2_streamparm));
 67   memset(setfps, 0, sizeof(struct v4l2_streamparm));
 68   setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 69   setfps->parm.capture.timeperframe.numerator = 1;
 70   setfps->parm.capture.timeperframe.denominator = vd->fps;
 71   ret = ioctl(vd->fd, VIDIOC_S_PARM, setfps);
 72 
 73   /*
 74    * request buffers
 75    */
 76   memset(&vd->rb, 0, sizeof(struct v4l2_requestbuffers));
 77   vd->rb.count = NB_BUFFER;
 78   vd->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 79   vd->rb.memory = V4L2_MEMORY_MMAP;
 80 
 81   ret = ioctl(vd->fd, VIDIOC_REQBUFS, &vd->rb);
 82   if (ret < 0) {
 83     perror("Unable to allocate buffers");
 84     goto fatal;
 85   }
 86 
 87   /*
 88    * map the buffers
 89    */
 90   for (i = 0; i < NB_BUFFER; i++) {
 91     memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
 92     vd->buf.index = i;
 93     vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 94     vd->buf.memory = V4L2_MEMORY_MMAP;
 95     ret = ioctl(vd->fd, VIDIOC_QUERYBUF, &vd->buf);
 96     if (ret < 0) {
 97       perror("Unable to query buffer");
 98       goto fatal;
 99     }
100 
101     if (debug)
102       fprintf(stderr, "length: %u offset: %u
", vd->buf.length, vd->buf.m.offset);
103 
104     vd->mem[i] = mmap(0 /* start anywhere */ ,
105                       vd->buf.length, PROT_READ, MAP_SHARED, vd->fd,
106                       vd->buf.m.offset);
107     if (vd->mem[i] == MAP_FAILED) {
108       perror("Unable to map buffer");
109       goto fatal;
110     }
111     if (debug)
112       fprintf(stderr, "Buffer mapped at address %p.
", vd->mem[i]);
113   }
114 
115   /*
116    * Queue the buffers.
117    */
118   for (i = 0; i < NB_BUFFER; ++i) {
119     memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
120     vd->buf.index = i;
121     vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
122     vd->buf.memory = V4L2_MEMORY_MMAP;
123     ret = ioctl(vd->fd, VIDIOC_QBUF, &vd->buf);
124     if (ret < 0) {
125       perror("Unable to queue buffer");
126       goto fatal;;
127     }
128   }
129   return 0;
130 fatal:
131   return -1;
132 
133 }

摄像头的使能和禁止:

 1 static int video_enable(struct vdIn *vd)
 2 {
 3   int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 4   int ret;
 5 
 6   ret = ioctl(vd->fd, VIDIOC_STREAMON, &type);
 7   if (ret < 0) {
 8     perror("Unable to start capture");
 9     return ret;
10   }
11   vd->isstreaming = 1;
12   return 0;
13 }
 1 static int video_disable(struct vdIn *vd)
 2 {
 3   int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 4   int ret;
 5 
 6   ret = ioctl(vd->fd, VIDIOC_STREAMOFF, &type);
 7   if (ret < 0) {
 8     perror("Unable to stop capture");
 9     return ret;
10   }
11   vd->isstreaming = 0;
12   return 0;
13 }

将摄像头中的数据拷贝到全局内存中:

 1 int memcpy_picture(unsigned char *out, unsigned char *buf, int size)
 2 {
 3   unsigned char *ptdeb, *ptlimit, *ptcur = buf;
 4   int sizein, pos=0;
 5 
 6   if (!is_huffman(buf)) {
 7     ptdeb = ptcur = buf;
 8     ptlimit = buf + size;
 9     while ((((ptcur[0] << 8) | ptcur[1]) != 0xffc0) && (ptcur < ptlimit))
10       ptcur++;
11     if (ptcur >= ptlimit)
12         return pos;
13     sizein = ptcur - ptdeb;
14 
15     memcpy(out+pos, buf, sizein); pos += sizein;
16     memcpy(out+pos, dht_data, sizeof(dht_data)); pos += sizeof(dht_data);
17     memcpy(out+pos, ptcur, size - sizein); pos += size-sizein;
18   } else {
19     memcpy(out+pos, ptcur, size); pos += size;
20   }
21   return pos;
22 }

摄像头数据的采集:

 1 int uvcGrab(struct vdIn *vd)
 2 {
 3 #define HEADERFRAME1 0xaf
 4   int ret;
 5 
 6   if (!vd->isstreaming)
 7     if (video_enable(vd))
 8       goto err;
 9 
10   memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
11   vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
12   vd->buf.memory = V4L2_MEMORY_MMAP;
13 
14   ret = ioctl(vd->fd, VIDIOC_DQBUF, &vd->buf);
15   if (ret < 0) {
16     perror("Unable to dequeue buffer ...");
17     goto err;
18   }
19 
20   switch (vd->formatIn) {
21     case V4L2_PIX_FMT_MJPEG:
22       if (vd->buf.bytesused <= HEADERFRAME1) {    /* Prevent crash
23                                                   * on empty image */
24         fprintf(stderr, "Ignoring empty buffer ...
");
25         return 0;
26       }
27 
28       /* memcpy(vd->tmpbuffer, vd->mem[vd->buf.index], vd->buf.bytesused);
29 
30       memcpy (vd->tmpbuffer, vd->mem[vd->buf.index], HEADERFRAME1);
31       memcpy (vd->tmpbuffer + HEADERFRAME1, dht_data, sizeof(dht_data));
32       memcpy (vd->tmpbuffer + HEADERFRAME1 + sizeof(dht_data), vd->mem[vd->buf.index] + HEADERFRAME1, (vd->buf.bytesused - HEADERFRAME1));
33       */
34 
35       memcpy(vd->tmpbuffer, vd->mem[vd->buf.index], vd->buf.bytesused);
36 
37       if (debug)
38         fprintf(stderr, "bytes in used %d 
", vd->buf.bytesused);
39       break;
40 
41     case V4L2_PIX_FMT_YUYV:
42       if (vd->buf.bytesused > vd->framesizeIn)
43         memcpy (vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->framesizeIn);
44       else
45         memcpy (vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->buf.bytesused);
46       break;
47 
48     default:
49       goto err;
50     break;
51   }
52 
53   ret = ioctl(vd->fd, VIDIOC_QBUF, &vd->buf);
54   if (ret < 0) {
55     perror("Unable to requeue buffer");
56     goto err;
57   }
58 
59   return 0;
60 
61 err:
62   vd->signalquit = 0;
63   return -1;
64 }

关闭设备:

 1 int close_v4l2(struct vdIn *vd)
 2 {
 3   if (vd->isstreaming)
 4     video_disable(vd);
 5   if (vd->tmpbuffer)
 6     free(vd->tmpbuffer);
 7   vd->tmpbuffer = NULL;
 8   free(vd->framebuffer);
 9   vd->framebuffer = NULL;
10   free(vd->videodevice);
11   free(vd->status);
12   free(vd->pictName);
13   vd->videodevice = NULL;
14   vd->status = NULL;
15   vd->pictName = NULL;
16 
17   return 0;
18 }

三:输出

  

  输出数据的初始化、停止、开始和命令。

  WWW文件中存放的是在浏览器中查看视频所需要的文件。

原文地址:https://www.cnblogs.com/yinsua/p/3205241.html