找实习好难,还是好好沉淀吧!!!
mjpg_streamer是最基础的嵌入式linux视频流推流库,虽然停止更新了但还是想深扒一下它的源码。毕竟之前也没有好好看过库文件的源码,且对多线程理解比较不透彻。
视频流推流应该由两部分程序组成,一部分从摄像头获取拍摄到的数据,另一部分通过各种方式推送拍到的图像数据。mjpg_streamer程序通过动态链接库将各部分函数动态编译。
主函数
部分参数的初始化与申明
//char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
char *input[MAX_INPUT_PLUGINS];
char *output[MAX_OUTPUT_PLUGINS];
int daemon = 0, i, j;
size_t tmp = 0;
output[0] = "output_http.so --port 8080";
global.outcnt = 0;
global.incnt = 0;
global是个结构体,作为全局变量申明。用来存储支持几种输入输出方式并保存各种方式的各项参数。各项参数保存在结构体input和output中,这些结构体包含的内容我们后面再细看。
当没有指定输出方式时以http方式作为默认的输出方式。
解析命令行参数
while(1) {
int c = 0;
static struct option long_options[] = {
{"help", no_argument, NULL, 'h'},
{"input", required_argument, NULL, 'i'},
{"output", required_argument, NULL, 'o'},
{"version", no_argument, NULL, 'v'},
{"background", no_argument, NULL, 'b'},
{NULL, 0, NULL, 0}
};
c = getopt_long(argc, argv, "hi:o:vb", long_options, NULL);
/* no more options to parse */
if(c == -1) break;
switch(c) {
case 'i':
input[global.incnt++] = strdup(optarg);
/*
* strdup() 函数将参数 s 指向的字符串复制到一个字符串指针上去,这个字符串指针事先可以没被初始化。
* 在复制时,strdup() 会给这个指针分配空间,使用 malloc() 函数进行分配,如果不再使用这个指针,
* 相应的用 free() 来释放掉这部分空间。
*/
break;
case 'o':
output[global.outcnt++] = strdup(optarg);
break;
case 'v':
printf("MJPG Streamer Version: %s\n",
#ifdef GIT_HASH
GIT_HASH
#else
SOURCE_VERSION
#endif
);
return 0;
break;
case 'b':
daemon = 1;
break;
case 'h': /* fall through */
default:
help(argv[0]);
exit(EXIT_FAILURE);
}
}
通过getopt_long()函数来解析命令行参数,-i和-o命令代指input和output也就是上面说的两个部分的功能。后面接的参数被保存在两个字符串数组中,里面的内容用来指定以什么方式进行输入输出操作。
获取输入输出方式并存储各项参数
for(i = 0; i < global.incnt; i++) {
/* this mutex and the conditional variable are used to synchronize access to the global picture buffer */
if(pthread_mutex_init(&global.in[i].db, NULL) != 0) {
LOG("could not initialize mutex variable\n");
closelog();
exit(EXIT_FAILURE);
}
if(pthread_cond_init(&global.in[i].db_update, NULL) != 0) {
LOG("could not initialize condition variable\n");
closelog();
exit(EXIT_FAILURE);
}
tmp = (size_t)(strchr(input[i], ' ') - input[i]);
global.in[i].stop = 0;
global.in[i].context = NULL;
global.in[i].buf = NULL;
global.in[i].size = 0;
global.in[i].plugin = (tmp > 0) ? strndup(input[i], tmp) : strdup(input[i]);
global.in[i].handle = dlopen(global.in[i].plugin, RTLD_LAZY);
if(!global.in[i].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[i].init = dlsym(global.in[i].handle, "input_init");
if(global.in[i].init == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/*
* dlsym函数的功能就是可以从共享库(动态库)中获取符号(全局变量与函数符号)地址,
* 通常用于获取函数符号地址,这样可用于对共享库中函数的包装
*/
global.in[i].stop = dlsym(global.in[i].handle, "input_stop");
if(global.in[i].stop == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in[i].run = dlsym(global.in[i].handle, "input_run");
if(global.in[i].run == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/* try to find optional command */
global.in[i].cmd = dlsym(global.in[i].handle, "input_cmd");
global.in[i].param.parameters = strchr(input[i], ' ');
/*strchr()
*
* 表示在字符串 s 中查找字符 c,返回字符 c 第一次在字符串 s 中出现的位置,如果未找到字符 c,
* 则返回 NULL。也就是说,strchr 函数在字符串 s 中从前到后(或者称为从左到右)查找字符 c
*/
for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
global.in[i].param.argv[j] = NULL;
}
split_parameters(global.in[i].param.parameters, &global.in[i].param.argc, global.in[i].param.argv);
global.in[i].param.global = &global;
global.in[i].param.id = i;
if(global.in[i].init(&global.in[i].param, i)) {
LOG("input_init() return value signals to exit\n");
closelog();
exit(0);
}
}
这段代码是用来给命令行指定的输入方式的各项参数进行初始化。
首先先初始化一个互斥锁和一个条件变量。
然后通过dlopen()函数打开对应的input方式链接库,再通过dlsym()函数获取各对应函数的地址赋值给结构体中的函数名。
最后通过split_parameters函数将该种方式调用input函数所需要的参数解析并保存。
整体调用加上参数近似于
"input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0"
同理output的各种方式参数初始化也类似于input。源码如下
for(i = 0; i < global.outcnt; i++) {
tmp = (size_t)(strchr(output[i], ' ') - output[i]);
global.out[i].plugin = (tmp > 0) ? strndup(output[i], tmp) : strdup(output[i]);
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
if(!global.out[i].handle) {
LOG("ERROR: could not find output plugin %s\n", global.out[i].plugin);
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.out[i].init = dlsym(global.out[i].handle, "output_init");
if(global.out[i].init == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
if(global.out[i].stop == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].run = dlsym(global.out[i].handle, "output_run");
if(global.out[i].run == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/* try to find optional command */
global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");
global.out[i].param.parameters = strchr(output[i], ' ');
for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
global.out[i].param.argv[j] = NULL;
}
split_parameters(global.out[i].param.parameters, &global.out[i].param.argc, global.out[i].param.argv);
global.out[i].param.global = &global;
global.out[i].param.id = i;
if(global.out[i].init(&global.out[i].param, i)) {
LOG("output_init() return value signals to exit\n");
closelog();
exit(EXIT_FAILURE);
}
}
调用各种方式的run函数
DBG("starting %d input plugin\n", global.incnt);
for(i = 0; i < global.incnt; i++) {
syslog(LOG_INFO, "starting input plugin %s", global.in[i].plugin);
if(global.in[i].run(i)) {
LOG("can not run input plugin %d: %s\n", i, global.in[i].plugin);
closelog();
return 1;
}
}
DBG("starting %d output plugin(s)\n", global.outcnt);
for(i = 0; i < global.outcnt; i++) {
syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
global.out[i].run(global.out[i].param.id);
}