转载于http://blog.csdn.net/tandesir/article/details/8373013
【说明】
对友善之臂的视频监控源码进行学习总结。如有错误,敬请指正。
【学习准备】
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