目标文件:mjpg-stream/mjpg-stream.c + mjpg-stream.h + input.h + output.h
这一篇的主要难点是main()中的结构体globals引出的动态链接库,插件,条件变量,互斥锁等问题。
新手写,有不对的请大神指正,鼓励。
本人参考文章:
http://www.360doc.com/content/13/0913/13/13876325_314174121.shtml
http://www.cnblogs.com/ardar/articles/357321.html
一:结构体介绍
globals
------------------------------------------------------------------------------
// global variables that are accessed by all plugins
typedef struct _globals globals;
struct _globals {
int stop; // 一个全局标志位
input in[MAX_INPUT_PLUGINS]; //输入插件,一个输入插件可对应多个输出插件 宏定义为10
int incnt;//输入插件数量
output out[MAX_OUTPUT_PLUGINS]; //输出插件,以数组形式表示 宏定义为10
int outcnt;//输出插件数量
//int (*control)(int command, char *details);
};
------------------------------------------------------------------------------
input
------------------------------------------------------------------------------
//structure to store variables/functions for input plugin
typedef struct _input input;
struct _input {
char *plugin; //动态链接库的名字,或者是动态链接库的地址
void *handle; //动态链接库的句柄,通过该句柄可以调用动态库中的函数
input_parameter param; //输入插件的参数
// input plugin parameters
struct _control *in_parameters;// control结构
int parametercount;//参数计数
struct v4l2_jpegcompression jpegcomp;//关于v4l2的所有结构体在后面详述
pthread_mutex_t db; //互斥锁,数据锁
pthread_cond_t db_update; //条件变量,数据更新的标志
unsigned char *buf; //全局JPG帧的缓冲区的指针
int size; //缓冲区的大小
struct timeval timestamp;//定时结构
input_format *in_formats;//输入格式
int formatCount;//格式计数
int currentFormat; // holds the current format number 保存当前格式数量
int (*init)(input_parameter *, int id); //四个函数指针
int (*stop)(int);
int (*run)(int);
int (*cmd)(int plugin, unsigned int control_id, unsigned int group, int value);
};
------------------------------------------------------------------------------
output
------------------------------------------------------------------------------
//structure to store variables/functions for output plugin
typedef struct _output output;
struct _output {
char *plugin; //动态链接库的名字,或者是动态链接库的地址
void *handle; //动态链接库的句柄,通过该句柄可以调用动态库中的函数
output_parameter param;//输出插件的参数
// input plugin parameters
struct _control *out_parameters; //control结构
int parametercount; //参数计数
int (*init)(output_parameter *param, int id); //四个函数指针
int (*stop)(int);
int (*run)(int);
int (*cmd)(int plugin, unsigned int control_id, unsigned int group, int value);
};
------------------------------------------------------------------------------
input_parameter
------------------------------------------------------------------------------
//parameters for input plugin
typedef struct _input_parameter input_parameter;
struct _input_parameter {
int id; //用于标记是哪一个输入插件的参数
char *parameters; //输入参数指针
int argc;
char *argv[MAX_PLUGIN_ARGUMENTS];//插件参数数组
struct _globals *global;//全局结构指针
};
------------------------------------------------------------------------------
output_parameter
------------------------------------------------------------------------------
//parameters for output plugin
typedef struct _output_parameter output_parameter;
struct _output_parameter {
int id; //用于标记是哪一个输出插件的参数
char *parameters;//输出参数指针
int argc;
char *argv[MAX_PLUGIN_ARGUMENTS];//插件参数数组
struct _globals *global;//全局结构指针
};
------------------------------------------------------------------------------
input_format
------------------------------------------------------------------------------
typedef struct _input_format input_format;
struct _input_format {
struct v4l2_fmtdesc format;//V4L2 ioctl 中的重要结构体
input_resolution *supportedResolutions; //分辨率指针
int resolutionCount;//分辨率计数
char currentResolution;//当前分辨率
};
------------------------------------------------------------------------------
input_resolution
------------------------------------------------------------------------------
typedef struct _input_resolution input_resolution;
struct _input_resolution {
unsigned int width;//分辨率宽度
unsigned int height;//分辨率高度
};
------------------------------------------------------------------------------
control
------------------------------------------------------------------------------
typedef struct _control control;
struct _control {
struct v4l2_queryctrl ctrl;//v4l2 结构
int value;
struct v4l2_querymenu *menuitems;// v4l2结构
int class_id;
int group;
};
------------------------------------------------------------------------------
二:源码分析
------------------------------------------------------------------------------
铺垫代码
------------------------------------------------------------------------------
if(global.outcnt == 0) {
global.outcnt = 1;
}
这段代码确保了至少有一个输出插件选中
------------------------------------------------------------------------------
打开输入插件代码
------------------------------------------------------------------------------
for(i = 0; i < global.incnt; i++) {
//同步全局图像缓冲区:
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);
}
//查找字符串s中首次出现字符‘ ’的位置
//保存输入插件字节数
tmp = (size_t)(strchr(input[i], ' ') - input[i]);
//初始化global全局变量
global.in[i].stop = 0;
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);
}
//获得动态库内的input_init()函数
global.in[i].init = dlsym(global.in[i].handle, "input_init");
if(global.in[i].init == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
//获得动态库内的input_stop()函数
global.in[i].stop = dlsym(global.in[i].handle, "input_stop");
if(global.in[i].stop == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
//获得动态库内的input_run()函数
global.in[i].run = dlsym(global.in[i].handle, "input_run");
if(global.in[i].run == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
//试图找到可选的命令
//获得动态库内的input_cmd()函数
global.in[i].cmd = dlsym(global.in[i].handle, "input_cmd");
//将命令参数的起始地址赋给para.parameter
global.in[i].param.parameters = strchr(input[i], ' ');
split_parameters(global.in[i].param.parameters, &global.in[i].param.argc, global.in[i].param.argv);
//将global结构体的地址赋给param.global
global.in[i].param.global = &global;
global.in[i].param.id = i;
//传递global.in.param给init,进行初始化
if(global.in[i].init(&global.in[i].param, i)) {
LOG("input_init() return value signals to exit\n");
closelog();
exit(0);
}
}
------------------------------------------------------------------------------
打开输出插件代码
------------------------------------------------------------------------------
for(i = 0; i < global.outcnt; i++) { //因为是一个输入对应多个输出,所以输出采用了for循环
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);
}
//获得动态库内的output_init()函数
global.out[i].init = dlsym(global.out[i].handle, "output_init");
if(global.out[i].init == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
//获得动态库内的output_stop()函数
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
if(global.out[i].stop == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
//获得动态库内的output_run()函数
global.out[i].run = dlsym(global.out[i].handle, "output_run");
if(global.out[i].run == NULL) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
//获得动态库内的output_cmd()函数
global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");
////将命令参数的起始地址赋给para.parameter
global.out[i].param.parameters = strchr(output[i], ' ');
split_parameters(global.out[i].param.parameters, &global.out[i].param.argc, global.out[i].param.argv);
//将global结构体的地址赋给param.global
global.out[i].param.global = &global;
global.out[i].param.id = i;
//传递global.out.param给init,进行初始化
if(global.out[i].init(&global.out[i].param, i)) {
LOG("output_init() return value signals to exit\n");
closelog();
exit(0);
}
}
------------------------------------------------------------------------------
运行插件代码直到Main()结束
------------------------------------------------------------------------------
//start to read the input, push pictures into global buffer
//开始读取输入,把照片放到全局缓冲区
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);
//开始运行输入插件的run()函数
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);
//开始运行输出插件的run()函数
for(i = 0; i < global.outcnt; i++) {
syslog(LOG_INFO, "starting output plugin: %s (ID: d)", global.out[i].plugin, global.out[i].param.id);
global.out[i].run(global.out[i].param.id);
}
//wait for signals
//运行完以上函数,该进程进入休眠状态,等待用户按下
pause();
return 0;
------------------------------------------------------------------------------
至此,main函数的代码已经贴完。但具体还有很多细节没分析,下面逐个分析。
三:动态链接库
在上面加粗的很多函数中,见到了dlopen,dlerror等函数。下面就动态链接库内容分析
3.1 动态链接库的操作函数
#include
~1 void *dlopen(const char *filename, int flag);
flag表示在什么时候解决未定义的符号(调用)。取值有两个:
1) RTLD_LAZY:表明在动态链接库的函数代码执行时解决。
2) RTLD_NOW : 表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
dlopen调用失败时,将返回NULL值,否则返回的是操作句柄。
~2 const char *dlerror(void);
当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。
~3 void *dlsym(void *handle, const char *symbol);
~4 int dlclose(void *handle);
dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。
注意:Link with -ldl.
3.2 Linux动态链接库简介
大家都知道,在WINDOWS系统中有很多的动态链接库(以.DLL为后缀的文件,DLL即Dynamic Link Library)。这种动态链接库,和静态函数库不同,它里面的函数并不是执行程序本身的一部分,而是根据执行程序需要按需装入,同时其执行代码可在多个执行程序间共享,节省了空间,提高了效率,具备很高的灵活性,得到越来越多程序员和用户的青睐
在Linux下动态链接库,在/lib目录下,就有许多以.so作后缀的文件,,这就是LINUX系统应用的动态链接库,只不过与WINDOWS叫法不同,它叫so,即Shared Object,共享对象。
在LINUX下,静态函数库是以.a作后缀的。
3.3动态链接库程序示例
#include"stdio.h"
#include"dlfcn.h"
#define SOFILE "./my.so"
#define SHARED
#include"datetime.h"
main()
{
DATETYPE d;
TIMETYPE t;
void *dp;
char *error;
puts("动态链接库应用示范");
dp=dlopen(SOFILE,RTLD_LAZY);
if (dp==NULL)
{
fputs(dlerror(),stderr);
exit(1);
}
getdate=dlsym(dp,"getdate");
error=dlerror();
if (error)
{
fputs(error,stderr);
exit(1);
}
getdate(&d);
printf("当前日期: d-d-d\n",d.year,d.mon,d.day);
gettime=dlsym(dp,"gettime");
error=dlerror();
if (error)
{
fputs(error,stderr);
exit(1);
}
gettime(&t);
printf("当前时间: d:d:d\n",t.hour,t.min,t.sec);
dlclose(dp);
exit(0);
}
3.4 小结
LINUX创建与使用动态链接库并不是一件难事。
编译函数源程序时选用-shared选项即可创建动态链接库,注意应以.so后缀命名,最好放到公用库目录(如/lib,/usr/lib等)下面,并要写好用户接口文件,以便其它用户共享。
使用动态链接库,源程序中要包含dlfcn.h头文件,写程序时注意dlopen等函数的正确调用,编译时要采用-rdynamic选项与-ldl选项,以产生可调用动态链接库的执行代码。
四:字符串操作
在上面加粗的很多函数中,见到了strchr,strdup,strndup等函数。下面就字符串操作内容进行简单介绍
----------------------------------------------------------------------------------------------
#include
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
char *strdup(const char *s);
char *strndup(const char *s, size_t n);
char *strdupa(const char *s);
char *strndupa(const char *s, size_t n);
----------------------------------------------------------------------------------------------
五:条件变量和互斥锁
在上面加粗的很多函数中,见到了pthread_mutex_init,pthread_cond_init等函数。
这里涉及到很重要的线程问题。
将在下一篇(四)详细讲述线程和线程控制,并举出大量实例来分析。
所以这里一笔带过
六:源码中的两个函数
6.1 signal_handler
--------------------------------------------------------
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 cancellation of threads and cleanup resources\n");
for(i = 0; i < global.incnt; i++) {
//运行输入插件的stop函数
global.in[i].stop(i);
}
for(i = 0; i < global.outcnt; i++) {
//运行输出插件的stop函数
global.out[i].stop(global.out[i].param.id);
//销毁条件变量
pthread_cond_destroy(&global.in[i].db_update);
//销毁互斥锁
pthread_mutex_destroy(&global.in[i].db);
}
usleep(1000 * 1000);
// close handles of input plugins
for(i = 0; i < global.incnt; i++) {
dlclose(global.in[i].handle);
}
for(i = 0; i < global.outcnt; i++) {
dlclose(global.out[i].handle);
}
DBG("all plugin handles closed\n");
LOG("done\n");
closelog();
exit(0);
return;
}
--------------------------------------------------------
6.1.1 usleep函数
--------------------------------------------------------
usleep功能把进程挂起一段时间, 单位是微秒(千分之一毫秒);
头文件:
语法: void usleep(int micro_seconds);
返回值: 无
内容说明:本函数可暂时使程序停止执行。参数 micro_seconds 为要暂停的微秒数(us)。
--------------------------------------------------------
6.1.2 线程函数pthread_cond_destroy,pthread_mutex_destroy
--------------------------------------------------------
和线程有关的,一笔带过,后面详细讲。
--------------------------------------------------------
6.1.3 函数功能总结
--------------------------------------------------------
我们来看看这个函数到底干了什么
最重要的几步:
~1 设置global.stop=1
~2 挂起进程 usleep
~3 逐个分别运行输入插件和输出插件的函数stop
~4 逐个分别销毁互斥锁和条件变量pthread_cond_destroy,pthread_mutex_destroy
~5 挂起进程 usleep
~6 逐个分别关闭输入插件和输出插件的动态链接库
~7 关闭系统日志 closelog
--------------------------------------------------------
6.2 split_parameters
代码中:
split_parameters(global.in[i].param.parameters, &global.in[i].param.argc, global.in[i].param.argv);
split_parameters(global.out[i].param.parameters, &global.out[i].param.argc, global.out[i].param.argv);
--------------------------------------------------------
int split_parameters(char *parameter_string, int *argc, char **argv)
{
int count = 1;
argv[0] = NULL; // the plugin may set it to 'INPUT_PLUGIN_NAME'
//参数名非0,参数长度非0
if(parameter_string != NULL && strlen(parameter_string) != 0) {
//新建3个变量
char *arg = NULL, *saveptr = NULL, *token = NULL;
//arg用来存储传入的地址
arg = strdup(parameter_string);
if(strchr(arg, ' ') != NULL) {
//分割空格 并存入指针saveptr中,再传给token
token = strtok_r(arg, " ", &saveptr);
if(token != NULL) {
argv[count] = strdup(token);
count++;
while((token = strtok_r(NULL, " ", &saveptr)) != NULL) {
argv[count] = strdup(token);
count++;
if(count >= MAX_PLUGIN_ARGUMENTS) { //最大为32
IPRINT("ERROR: too many arguments to input plugin\n");
return 0;
}
}
}
}
}
*argc = count;
return 1;
}
--------------------------------------------------------
6.2.1 strtok函数
--------------------------------------------------------
char *strtok(char s[], const char *delim);
分解字符串为一组字符串。s为要分解的字符串,delim为分隔符字符串。
从s开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。
所有delim中包含的字符都会被滤掉,并将被滤掉的地方设为一处分割的节点。
strtok是一个线程不安全的函数,因为它使用了静态分配的空间来存储被分割的字符串位置
线程安全的函数叫strtok_r
例如:strtok("abc,def,ghi",","),最后可以分割成为abc def ghi.尤其在点分十进制的IP中提取应用较多。
--------------------------------------------------------
6.2.2 函数小结
名字就是分割参数,利用strtok函数做到这一点。
到最后,是通过while,把所有的传入参数:
1,先给parameter_string
2,通过strdup复制,传给arg
3,通过strtok分割,传给argv[x]数组
4,直到count到32,代表所有的输入输出插件的参数全部分割存储完毕。
6.3 pause函数
--------------------------------------------------------
功能:让进程暂停直到信号出现
相关函数:kill,signal,sleep
表头文件: #include
定义函数: int pause(void);
函数说明: pause()会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。
返回值: 只返回-1。
--------------------------------------------------------
至此,mjpg-streamer.c + mjpg-streamer.h + input.h + output.h + utils.c + utils.h 全部内容已经分析完了。
下一篇,我们将会抛开源码,着重讲线程,线程控制,多线程编程的应用以解释源码中互斥锁和条件变量的运用。