【说明】
对友善之臂的视频监控源码进行学习总结。如有错误,敬请指正。
【学习准备】
1 源码:http://download.youkuaiyun.com/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.youkuaiyun.com/tandesir