友善之臂视频监控方案源码学习(1) - 架构分析
【说明】
对友善之臂的视频监控源码进行学习总结。如有错误,敬请指正。
【学习准备】
1 源码:http://download.csdn.net/detail/tandesir/4915905
2 工具:source insight 3
3 Fedora 10 + gcc4.5.1 + gdb
【源码树】
[tandesir@localhost ~]$ tree mjpg-streamer-mini2440-read-only/
mjpg-streamer-mini2440-read-only/|-- CHANGELOG|-- LICENSE|-- Makefile|-- README|-- mjpg-streamer-mini2440-bin.tar.gz|-- mjpg-streamer-mini2440.kdev4|-- mjpg_streamer.c|-- mjpg_streamer.h|-- plugins| |-- input.h| |-- input_control| | |-- Makefile| | |-- dynctrl.c| | |-- dynctrl.h| | |-- dynctrl.lo| | |-- input_control.so| | |-- input_uvc.c| | |-- uvc_compat.h| | `-- uvcvideo.h| |-- input_file| | |-- Makefile| | |-- input_file.c| | `-- input_file.so| |-- input_gspcav1| | |-- Makefile| | |-- encoder.c| | |-- encoder.h| | |-- huffman.c| | |-- huffman.h| | |-- input_gspcav1.c| | |-- jconfig.h| | |-- jdatatype.h| | |-- marker.c| | |-- marker.h| | |-- quant.c| | |-- quant.h| | |-- readme.spcacat| | |-- spcaframe.h| | |-- spcav4l.c| | |-- spcav4l.h| | |-- utils.c| | `-- utils.h| |-- input_s3c2410| | |-- Makefile| | |-- input_s3c2410.c| | |-- readme.s3c2410| | |-- s3c2410.c| | |-- s3c2410.h| | |-- utils.c| | `-- utils.h| |-- input_testpicture| | |-- Makefile| | |-- input_testpicture.c| | |-- pictures| | | |-- 160x120_1.jpg| | | |-- 160x120_2.jpg| | | |-- 320x240_1.jpg| | | |-- 320x240_2.jpg| | | |-- 640x480_1.jpg| | | |-- 640x480_2.jpg| | | |-- 960x720_1.jpg| | | `-- 960x720_2.jpg| | `-- testpictures.h| |-- input_uvc| | |-- Makefile| | |-- dynctrl.c| | |-- dynctrl.h| | |-- huffman.h| | |-- input_uvc.c| | |-- jpeg_utils.c| | |-- jpeg_utils.h| | |-- uvc_compat.h| | |-- uvcvideo.h| | |-- v4l2uvc.c| | `-- v4l2uvc.h| |-- output.h| |-- output_autofocus| | |-- Makefile| | |-- output_autofocus.c| | |-- processJPEG_onlyCenter.c| | `-- processJPEG_onlyCenter.h| |-- output_file| | |-- Makefile| | `-- output_file.c| `-- output_http| |-- Makefile| |-- httpd.c| |-- httpd.h| `-- output_http.c|-- simplified_jpeg_encoder.c|-- simplified_jpeg_encoder.h|-- start_s3c2410.sh|-- start_uvc.sh|-- start_uvc_yuv.sh|-- utils.c|-- utils.h`-- www |-- LICENSE.txt |-- bodybg.gif |-- cambozola.jar |-- control.htm |-- example.jpg |-- favicon.ico |-- favicon.png |-- fix.css |-- functions.js |-- index.html |-- java.html |-- java_control.html |-- java_simple.html |-- javascript.html |-- javascript_motiondetection.html |-- javascript_simple.html |-- sidebarbg.gif |-- static.html |-- static_simple.html |-- stream.html |-- stream_simple.html |-- style.css `-- videolan.html
【源码架构】
1 Makefile分析
顶层的Makefile如下所示:
################################################################# Purpose: Makefile for "M-JPEG Streamer"# Author.: Tom Stoeveken (TST)# Version: 0.3# License: GPL#################################################################CC = arm-linux-gccCC ?= gccCFLAGS += -O3 -DLINUX -D_GNU_SOURCE -Wall #CFLAGS += -O2 -DDEBUG -DLINUX -D_GNU_SOURCE -WallLFLAGS += -lpthread -ldl APP_BINARY=mjpg_streamerOBJECTS=mjpg_streamer.o utils.o# define the names and targets of the pluginsPLUGINS = input_uvc.soPLUGINS += output_file.soPLUGINS += output_http.soPLUGINS += input_testpicture.soPLUGINS += output_autofocus.soPLUGINS += input_gspcav1.soPLUGINS += input_file.soPLUGINS += input_control.so# PLUGINS += input_http.so# PLUGINS += output_viewer.soall: application pluginsclean:make -C plugins/input_uvc $@make -C plugins/input_testpicture $@make -C plugins/output_file $@make -C plugins/output_http $@make -C plugins/output_autofocus $@make -C plugins/input_gspcav1 $@rm -f *.a *.o $(APP_BINARY) core *~ *.so *.lo test_jpegplugins: $(PLUGINS)#input_testpicture.so output_autofocus.so input_gspcav1.soapplication: $(APP_BINARY)output_autofocus.so: mjpg_streamer.h utils.hmake -C plugins/output_autofocus all CC=$(CC)cp plugins/output_autofocus/output_autofocus.so .input_testpicture.so: mjpg_streamer.h utils.hmake -C plugins/input_testpicture all CC=$(CC)cp plugins/input_testpicture/input_testpicture.so .input_uvc.so: mjpg_streamer.h utils.hmake -C plugins/input_uvc all CC=$(CC)cp plugins/input_uvc/input_uvc.so .output_file.so: mjpg_streamer.h utils.hmake -C plugins/output_file all CC=$(CC)cp plugins/output_file/output_file.so .output_http.so: mjpg_streamer.h utils.hmake -C plugins/output_http all CC=$(CC)cp plugins/output_http/output_http.so .input_gspcav1.so: mjpg_streamer.h utils.hmake -C plugins/input_gspcav1 all CC=$(CC)cp plugins/input_gspcav1/input_gspcav1.so .input_file.so: mjpg_streamer.h utils.hmake -C plugins/input_file allcp plugins/input_file/input_file.so .input_control.so: mjpg_streamer.h utils.hmake -C plugins/input_control allcp plugins/input_control/input_control.so .$(APP_BINARY): mjpg_streamer.c mjpg_streamer.h mjpg_streamer.o utils.c utils.h utils.o$(CC) $(CFLAGS) $(LFLAGS) $(OBJECTS) -o $(APP_BINARY)chmod 755 $(APP_BINARY)package: application pluginstar czvf mjpg-streamer-mini2440-bin.tar.gz \--exclude www/.svn \ mjpg_streamer \input_testpicture.so \input_uvc.so \ output_file.so \output_http.so \start_uvc.sh \start_uvc_yuv.sh \ www \ LICENSE test_jpeg: test_jpeg.c simplified_jpeg_encoder.c simplified_jpeg_encoder.hgcc -O0 -g simplified_jpeg_encoder.c test_jpeg.c -o test_jpeg
由Makefile可以看出,经过编译将编译出如下文件:
(1) input plugins(临时文件) : input_uvc.so, input_testpicture.so, input_gspcav1.so, input_file.so, input_control.so, input_http.so
(2) output plugins(临时文件) : output_file.so, output_http.so, output_autofocus.so, output_viewer.so
(3) 目标(临时文件) : mjpg_streamer.o utils.o
(4) 可执行文件 : mjpg_streamer
2 main入口函数
main入口函数在mjpg_streamer.c,其执行过程可概括如下:
(1) 参数解析
1) 参数解析利用了option结构,其定义在getopt.h中:
struct option{#if defined (__STDC__) && __STDC__ const char *name;#else char *name;#endif /* has_arg can't be an enum because some compilers complain about type mismatches in all the code that assumes it is an int. */ int has_arg; int *flag; int val;};(a) 基本概念
myprog -a vv --add -b --file a.txt b.txt -e c.txt
长选项(带--的选项):--add --file
短选项(带-的选项):-a -b -e
(b) 参数解释
name:不带短横线的选项名,前面没有短横线。譬如“help”、“verbose”之类。
ihas_arg: 描述了选项是否有选项参数。如果有,是哪种类型的参数。此时,它的值一定是下表中的一个。符号常量数值含义
no_argument 0 选项没有参数
required_argument 1 选项需要参数
optional_argument 2 选项参数可选
flag:指明长选项如何返回,如果flag为NULL,则getopt_long返回val。否则返回0,flag指向一个值为val的变量。如果该长选项没有发现,flag保持不变;
val:指明返回的值,或者需要加载到被flag所指示的变量中。
2)
int option_index = 0, c=0; static struct option long_options[] = \ { {"h", no_argument, 0, 0}, {"help", no_argument, 0, 0}, {"i", required_argument, 0, 0}, {"input", required_argument, 0, 0}, {"o", required_argument, 0, 0}, {"output", required_argument, 0, 0}, {"v", no_argument, 0, 0}, {"version", no_argument, 0, 0}, {"b", no_argument, 0, 0}, {"background", no_argument, 0, 0}, {0, 0, 0, 0} };struct option最后一项须全部初始化为0。可见,input和output选项需要输入参数,其余选项无需参数。上述初始化,还表明,如果参数解析成功将返回val, 即0。
3) 参数解析采用了getopt_long_only函数:
c = getopt_long_only(argc, argv, "", long_options, &option_index);
函数getopt_long()的工作方式类似于getopt(),不过它还能接收长选项。在接收长选项之前,我们必须定义个一个结构体数组变量longopts,指明我们希望获取的长选项。getopt_long_only类似于getopt_long,但是它把'-'开头的选项当作长选项来处理。如果该选项与长选项不匹配,而与短选项匹配,则可以作为短选项解析。错误情况的处理和getopt一样,只是返回'?'时,还可能是别的情况引起的:选项含糊不明确或者无关参数。
4) 参数解析过程如下所示:
/* no more options to parse */ if (c == -1) break; /* unrecognized option */ if(c=='?'){ help(argv[0]); return 0; } switch (option_index) { /* h, help */ case 0: case 1: help(argv[0]); return 0; break; /* i, input */ case 2: case 3: input = strdup(optarg); break; /* o, output */ case 4: case 5: output[global.outcnt++] = strdup(optarg); break; /* v, version */ case 6: case 7: printf("MJPG Streamer Version: %s\n" \ "Compilation Date.....: %s\n" \ "Compilation Time.....: %s\n", SOURCE_VERSION, __DATE__, __TIME__); return 0; break; /* b, background */ case 8: case 9: daemon=1; break; default: help(argv[0]); return 0; }整个解析的代码在while(1)循环内进行,若返回值为-1,则跳出循环。
(2) 根据参数判定是否创建守护进程
/* fork to the background */ if ( daemon ) { LOG("enabling daemon mode"); daemon_mode(); }
(3) 初始化互斥锁
if( pthread_mutex_init(&global.db, NULL) != 0 ) { LOG("could not initialize mutex variable\n"); closelog(); exit(EXIT_FAILURE); } if( pthread_cond_init(&global.db_update, NULL) != 0 ) { LOG("could not initialize condition variable\n"); closelog(); exit(EXIT_FAILURE); }
(4) 检测终止信号(Ctrl + C)
/* ignore SIGPIPE (send by OS if transmitting to closed TCP sockets) */ signal(SIGPIPE, SIG_IGN); /* register signal handler for <CTRL>+C in order to clean up */ if (signal(SIGINT, signal_handler) == SIG_ERR) { LOG("could not register signal handler\n"); closelog(); exit(EXIT_FAILURE); }回调函数定义如下:
void signal_handler(int sig){ int i; /* signal "stop" to threads */ LOG("setting signal to stop\n"); global.stop = 1; usleep(1000*1000); /* clean up threads */ LOG("force cancelation of threads and cleanup ressources\n"); global.in.stop(); for(i=0; i<global.outcnt; i++) { global.out[i].stop(global.out[i].param.id); } usleep(1000*1000); /* close handles of input plugins */ dlclose(&global.in.handle); for(i=0; i<global.outcnt; i++) { /* skip = 0; DBG("about to decrement usage counter for handle of %s, id #%02d, handle: %p\n", \ global.out[i].plugin, global.out[i].param.id, global.out[i].handle); for(j=i+1; j<global.outcnt; j++) { if ( global.out[i].handle == global.out[j].handle ) { DBG("handles are pointing to the same destination (%p == %p)\n", global.out[i].handle, global.out[j].handle); skip = 1; } } if ( skip ) { continue; } DBG("closing handle %p\n", global.out[i].handle); */ dlclose(global.out[i].handle); } DBG("all plugin handles closed\n"); pthread_cond_destroy(&global.db_update); pthread_mutex_destroy(&global.db); LOG("done\n"); closelog(); exit(0); return;}主要作用是释放资源。
(5) 加载相应的输入、输出动态链接库
/* open input plugin */ tmp = (size_t)(strchr(input, ' ')-input); global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input); global.in.handle = dlopen(global.in.plugin, RTLD_LAZY); if ( !global.in.handle ) { LOG("ERROR: could not find input plugin\n"); LOG(" Perhaps you want to adjust the search path with:\n"); LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n"); LOG(" dlopen: %s\n", dlerror() ); closelog(); exit(EXIT_FAILURE); } global.in.init = dlsym(global.in.handle, "input_init"); if ( global.in.init == NULL ) { LOG("%s\n", dlerror()); exit(EXIT_FAILURE); } global.in.stop = dlsym(global.in.handle, "input_stop"); if ( global.in.stop == NULL ) { LOG("%s\n", dlerror()); exit(EXIT_FAILURE); } global.in.run = dlsym(global.in.handle, "input_run"); if ( global.in.run == NULL ) { LOG("%s\n", dlerror()); exit(EXIT_FAILURE); }上述代码为输入插件的情况,输出与之相似。global的结构如下:
struct _globals { int stop; /* signal fresh frames */ pthread_mutex_t db; pthread_cond_t db_update; /* global JPG frame, this is more or less the "database" */ unsigned char *buf; int size; /* input plugin */ input in; /* output plugin */ output out[MAX_OUTPUT_PLUGINS]; int outcnt; /* pointer to control functions */ int (*control)(int command, char *details);};该结构中,定义了互斥锁,以及输入、输出、控制结构。output和input类似。input的结构如下:
struct _input { char *plugin; void *handle; input_parameter param; int (*init)(input_parameter *); int (*stop)(void); int (*run)(void); int (*cmd)(in_cmd_type, int);};由此可见,加载动态链接库的过程实质上,是对input、output结构的函数指针进行初试化。输入的input定义如下:
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
下述代码实质上,是取出了input_uvc.so
tmp = (size_t)(strchr(input, ' ')-input);global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);
动态链接库的加载利用了dlopen函数:
global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);
调用动态链接库使用了dlsym函数:
global.in.init = dlsym(global.in.handle, "input_init");
"input_init"为函数接口的名称,声明在plugins目录下的input.h文件中,具体实现在plugins子目录中。
(6) 运行程序
/* start to read the input, push pictures into global buffer */ DBG("starting input plugin\n"); syslog(LOG_INFO, "starting input plugin"); if ( global.in.run() ) { LOG("can not run input plugin\n"); closelog(); return 1; }上述代码为输入插件的情况,输出与之相似。运行加载的run函数。
(7) 其他初始化
(8) 参数配置
参数配置在类似start_uvc_yuv.sh的shell脚本中,其形式如下:
./mjpg_streamer -o "output_http.so -w ./www" -i "input_uvc.so -y -d /dev/video2"
【代码评述】
总体来看,代码利用动态链接库实现了功能的动态加载,其加载过程在运行时发生。运行start_uvc_yuv.sh之类的shell脚本时,mjpg_streamer首先解析参数,然后根据参数加载对应的动态链接库,实现相应的功能。
转载请标明出处,仅供学习交流,勿用于商业目的
Copyright @ http://blog.csdn.net/tandesir