【说明】
对友善之臂的视频监控源码进行学习总结。如有错误,敬请指正。
【学习准备】
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-gcc CC ?= gcc CFLAGS += -O3 -DLINUX -D_GNU_SOURCE -Wall #CFLAGS += -O2 -DDEBUG -DLINUX -D_GNU_SOURCE -Wall LFLAGS += -lpthread -ldl APP_BINARY=mjpg_streamer OBJECTS=mjpg_streamer.o utils.o # define the names and targets of the plugins PLUGINS = input_uvc.so PLUGINS += output_file.so PLUGINS += output_http.so PLUGINS += input_testpicture.so PLUGINS += output_autofocus.so PLUGINS += input_gspcav1.so PLUGINS += input_file.so PLUGINS += input_control.so # PLUGINS += input_http.so # PLUGINS += output_viewer.so all: application plugins clean: 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_jpeg plugins: $(PLUGINS) #input_testpicture.so output_autofocus.so input_gspcav1.so application: $(APP_BINARY) output_autofocus.so: mjpg_streamer.h utils.h make -C plugins/output_autofocus all CC=$(CC) cp plugins/output_autofocus/output_autofocus.so . input_testpicture.so: mjpg_streamer.h utils.h make -C plugins/input_testpicture all CC=$(CC) cp plugins/input_testpicture/input_testpicture.so . input_uvc.so: mjpg_streamer.h utils.h make -C plugins/input_uvc all CC=$(CC) cp plugins/input_uvc/input_uvc.so . output_file.so: mjpg_streamer.h utils.h make -C plugins/output_file all CC=$(CC) cp plugins/output_file/output_file.so . output_http.so: mjpg_streamer.h utils.h make -C plugins/output_http all CC=$(CC) cp plugins/output_http/output_http.so . input_gspcav1.so: mjpg_streamer.h utils.h make -C plugins/input_gspcav1 all CC=$(CC) cp plugins/input_gspcav1/input_gspcav1.so . input_file.so: mjpg_streamer.h utils.h make -C plugins/input_file all cp plugins/input_file/input_file.so . input_control.so: mjpg_streamer.h utils.h make -C plugins/input_control all cp 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 plugins tar 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.h gcc -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