Mjpeg-streamer源码学习笔记-Main-动态库插件(三)

来源:互联网 发布:java项目源代码下载 编辑:程序博客网 时间:2024/06/05 04:22

目标文件: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

    //运行完以上函数,该进程进入休眠状态,等待用户按下<CTRL>+C结束所有的进程
    pause();

 

    return 0;

------------------------------------------------------------------------------

 

至此,main函数的代码已经贴完。但具体还有很多细节没分析,下面逐个分析。

 

三:动态链接库

在上面加粗的很多函数中,见到了dlopen,dlerror等函数。下面就动态链接库内容分析

 

3.1 动态链接库的操作函数

 #include<dlfcn.h>

~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<string.h>

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功能把进程挂起一段时间, 单位是微秒(千分之一毫秒);
头文件: <unistd.h>
语法: 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<unistd.h>
定义函数: int pause(void);
函数说明: pause()会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。
返回值: 只返回-1。

--------------------------------------------------------

 

至此,mjpg-streamer.c + mjpg-streamer.h + input.h + output.h + utils.c + utils.h 全部内容已经分析完了。

下一篇,我们将会抛开源码,着重讲线程,线程控制,多线程编程的应用以解释源码中互斥锁和条件变量的运用。

原创粉丝点击