mjpg-streamer详解1

来源:互联网 发布:一览php英才网 编辑:程序博客网 时间:2024/05/08 15:16

下载到源码包,解压缩后创建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:
case 5:
  output[global.outcnt++] = strdup(optarg);
break;
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;
  case 8:
  case 9:
daemon=1;//命令行参数传入了-b或者-background的话就将daemon设置为1,标志将此程序设置为后台形式
  break;
  default:
  help(argv[0]);
  return 0;
 }
}
/* 系统标志,不用理会 */
 openlog("MJPG-streamer ", LOG_PID|LOG_CONS, LOG_USER);
 syslog(LOG_INFO, "starting application");
/* 是否后台运行的判断:刚上面讲过了 */
if ( daemon )
{
 LOG("enabling daemon mode");
 daemon_mode();
}
到此为止,命令行参数的解析基本完成了!接下来做一些初始化工作:
/* 初始化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<global.outcnt; i++) {   /* 之前output[0] = "output_http.so -w www"  */    tmp = (size_t)(strchr(output[i], ' ')-output[i]);    /* 使得global.out[0].plugin =   "input_uvc.so" */    global.out[i].plugin = (tmp > 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<global.outcnt; i++) {    syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);    /* 相当于调用了inut_http.so中的run函数了  */    global.out[i].run(global.out[i].param.id);  }  pause();  return 0;}

到此为止main函数分析完毕了,总结了整个程序的主体最终就是为了执行四个函数,其执行的顺序如下所示:
1、input_init();
2、output_init();
3、input_run();
4、output_run();
下一篇就是讲解这四个函数的内部实现

                                             
0 0
原创粉丝点击