参考: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文件中存放的是在浏览器中查看视频所需要的文件。