下载到源码包,解压缩后创建Source Insight工程方便分析:
在分析源代码前先将其中重要的结构体列出来,有了这些分析源码起来更方便:
1、_global结构体(相当于输入渠道和输出渠道的中介)
struct _globals {
int stop;
pthread_mutex_t db;
pthread_cond_t db_update;
unsigned char *buf; /* 数据缓存 ---注意:从输入渠道得到数据会放入这个buf,输出也会从buf中取出数据去使用 */
int size; /* 数据缓存的大小 */
input in; /* 输入通道 */
output out[MAX_OUTPUT_PLUGINS]; /*输出通道 */
int outcnt; /* 当前输出通道有几种方式 */
};
上面结构体中的成员:input 和 output 是通过命令行参数-i选项和-o选项来指明输入渠道和输出渠道的
(例如:#mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www")
问:那怎么用这些动态库xxxx.so呢?(补充:此讲解中所用的输入渠道和输出渠道为input_uvc.so和output_http.so)
答:在main函数中会用dlopen()函数来打开xxxx.so这些动态库,使得_global内的input和output和这些动态库里的函数建立一个映射关系(后面再讲)
2、_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_uvc.c
input_uvc.c
input_init(*param)
init_videoIn(宽度,高度,帧率,格式,...)
init_v4l2(vd) //此函数里肯定是调用一系列uvc驱动的接口(那些ioctl)来设置摄像头的分辨率、帧率、输出格式
vd->frambuffer = ...; //分配一个临时缓存
为了解结构体中的运行函数,同样也是打开input_uvc.c
input_uvc.c
input_run(void)
pthread_create(&cam, 0, cam_thread, NULL);//创建一个线程,让cam_thread()函数跑起来(博客中有篇线程的讲解)
v4lGrab(videoIn);//获得一帧数据 (具体的内部实现后面讲解)
if (videoIn->formatIn == V4L2_PIX_FMT_YUYV).//判断获得的那一帧数据的格式(我用的摄像头可输出YUYV格式的或者MJPG格式的)
.......//说明是YUYV格式的,就要先压为jpg格式,然后再将数据放入_global结构体中的成员buf中去
else
..... //说明是MJPG格式的,就直接将数据放入_global结构体中的成员buf中去
3、_output 结构体(相当于输出渠道,从中介中获得数据并发送出去)
struct _output { //这里使用的是output_http.so(内部使用socket来模拟http协议)
char *plugin;
void *handle;
output_parameter param;
int (*init)(output_parameter *);//socket的初始化工作,ip+端口号之类
int (*stop)(int);//做清理工作
int (*run)(int);//从_global结构体中的buf中取出数据存到一个缓存中,接着将数据发送出去来供客户端来接收
int (*cmd)(int, out_cmd_type, int);
};
三个重要结构体介绍完毕,可以开启main函数来分析程序主体了(在Mjpg_streamer.c 中)
1、首先就是一些初始化(比如如果执行时未传入参数,那么这些初始化的就当做默认值了)
2、接着是一个while(1)循环,循环中会解析参数(这个参数是命令行中敲入的,例如:#mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www")
在循环中最重要的是getopt_long_only(...)函数,它是专门用来解析命令行参数的一个API函数(此函数的介绍详见 http://blog.csdn.net/pengrui18/article/details/8078813)
下面就简单讲解一下具体到本程序中的getopt_long_only(...)函数的用法(见注释):
int main(int argc, char *argv[])
{
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";//赋个初始值,解析参数的时候会使结果将其覆盖(若没有参数就用这个做默认值)
char *output[MAX_OUTPUT_PLUGINS];
int daemon=0,/* 是否让程序在后台运行:也是可以根据参数来决定它的 */
size_t tmp=0;
int i = 0;
output[0] = "output_http.so --port 8080";//同样是赋个默认值
global.outcnt = 0;//同样是赋个默认值
/* 在while循环中解析命令行传过来的参数
* 例如:#mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
*/
while(1) {
int option_index = 0, c=0;
static struct option long_options[] = \ //这个数组就是getopt_long_only(...)函数中的参数4
{
{"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}
};
/*
*getopt_long_only函数简介:
* 参数1 : 传入的参数个数
* 参数2 :传入的参数:例如此时 argv 为 -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
* 参数3是短选项
* 参数4是长选项
* 参数5 :返回参数4数组的索引值
* 返回值:此函数解析参数完毕后返回-1
* 所以在此函数后面要有个判断,以此来推出死循环
* 注意:出现未定义的参数时候返回值是? 号
*/
/* 例如:#mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
比如命令行传来的-i ,它肯定在参数2的argv中,然后与参数4的long_options数组中去比较,
若能找到-i对应的数组项,就返回此数组的下标,例如此时所对应的数组项的下标是2,
所以说参数5的option_index 此时就等于2了,即执行到下面时会使得switch(2),跳到case 2去看*/
c = getopt_long_only(argc, argv, "", long_options, &option_index);
if (c == -1) break;/* 说明参数解析完成了,跳出while循环 */
/* 有未识别的参数:即getopt_long_only()函数会返回 ? 号,然后打印出mjpg-streamer的使用帮助 */
if(c=='?'){ help(argv[0]); return 0; }
switch (option_index) {
/* h, help */
case 0:
case 1:
help(argv[0]);
return 0;
break;
case 2:
case 3:
input = strdup(optarg);/*这行就会变成: input = "input_uvc.so -f 10 -r 320*240" */
break;
case 4:
/* 初始化global中的成员*/
global.stop = 0;
global.buf = NULL;
global.size = 0;
global.in.plugin = NULL;
/* 初始化global 中的成员pthread_mutex_t:互斥锁 */
if( pthread_mutex_init(&global.db, NULL) != 0 ) {
LOG("could not initialize mutex variable\n");
closelog();
exit(EXIT_FAILURE);
}
/* 初始化global 中的成员pthread_cond_t:线程间通信的条件变量 */
if( pthread_cond_init(&global.db_update, NULL) != 0 ) {
LOG("could not initialize condition variable\n");
closelog();
exit(EXIT_FAILURE);
}
/* 忽略SIGPIPE 信号 */
signal(SIGPIPE, SIG_IGN);
/* 进行信号绑定:当我们按下ctrl+c 的时候就会调用 signal_handler 函数(清理工作)*/
if (signal(SIGINT, signal_handler) == SIG_ERR) {//信号处理函数signal_handler里面会使得global.stop = 1(以后会有地方来判断这个标志的)
LOG("could not register signal handler\n");
closelog();
exit(EXIT_FAILURE);
}
LOG("MJPG Streamer Version.: %s\n", SOURCE_VERSION);
/*因为前面传入了输出渠道了(在解析参数的时候讲outcnt++了,所以不执行此处) */
if ( global.outcnt == 0 ) {
/* no? Then use the default plugin instead */
global.outcnt = 1;
}
/*
* 前面得到的input = "input_uvc.so -f 10 -r 320*240"
* 下面这行是让tmp 等于"input_uvc.so"字符串的长度
*/
tmp = (size_t)(strchr(input, ' ')-input);
global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);/* 使得global.in.plugin = "input_uvc.so" */
/* dlopen(...)是打开input_uvc.so 这个动态链接库*/
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);
}
/*
* dlsym函数的作用是从一个文件中取出某个变量或者函数的地址
* 此行的目的:
* 将刚打开的input_uvc.so 动态库文件的input_init 函数复制给global中的init指针
* 这样以后操作global.in.init 就相当于操作的动态库input_uvc.so 中的input_init函数了
*/
global.in.init = dlsym(global.in.handle, "input_init");
if ( global.in.init == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/* 下面的这些global.in.stop 、global.in.stop 、global.in.cmd 就同理了 */
global.in.stop = dlsym(global.in.handle, "input_stop");
global.in.run = dlsym(global.in.handle, "input_run");
global.in.cmd = dlsym(global.in.handle, "input_cmd");
/* 下面这行表示获得"input_uvc.so -f 10 -r 320*240"中第一个空格后面的部分,即"-f 10 -r 320*240"*/
global.in.param.parameter_string = strchr(input, ' ');
global.in.param.global = &global;/* 将全局的global地址给过去,后面也许有用 */
/* 相当于调用的动态库input_uvc.so 中的input_init函数 */
if ( global.in.init(&global.in.param) ) {
LOG("input_init() return value signals to exit");
closelog();
exit(0);
}
/* 开始到输出渠道的操作了:因为outcnt = 1,所以只执行一次 */
for (i=0; i 0)?strndup(output[i], tmp):strdup(output[i]);
/* 同理打开了输出的动态链接库output_http.so */
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
if ( !global.out[i].handle ) {/* 是否打开成功的判断语句 */
......
......
}
/* 很眼熟吧,跟分析上面输入渠道的时候一模一样 */
global.out[i].init = dlsym(global.out[i].handle, "output_init");
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
global.out[i].run = dlsym(global.out[i].handle, "output_run");
global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");
global.out[i].param.parameter_string = strchr(output[i], ' ');
global.out[i].param.global = &global;
global.out[i].param.id = i;
/* 相当于调用了output_http.so中的output_init 函数 */
if(global.out[i].init(&global.out[i].param))
{
......
......
}
}
DBG("starting input plugin\n");
syslog(LOG_INFO, "starting input plugin");
/* 相当于调用了input_uvc.so中的input_run函数了 */
global.in.run();
DBG("starting %d output plugin(s)\n", global.outcnt);
for(i=0; i
2、output_init();
3、input_run();
4、output_run();
下一篇就是讲解这四个函数的内部实现