Mjpg-Streamer之框架分析

1、框架
mjpg-streamer相当于一个码头,有仓库、输入与输出

2.分析源码

input_init();
output_init();
input_run();
output_run();

3.自己写客服端

(1).发送一个请求字符串
“GET /?action=snapshot\n”
“GET /?action=stream\n”
“GET /?action=command\n”

(2).再发送一次字符串
如果我们不使用密码功能!则只需发送任意长度为小于2字节的字符串,比如:
“f\n”

如果发送的请求是:”GET /?action=snapshot\n”

(3).需要接收一次字符串(是服务器发过来的报文)

(4).接收一帧图片

如果发送的请求是:”GET /?action=stream\n”

(3).需要接收一次字符串(是服务器发过来的报文)

while(1)
{
(4).再接收一次报文,解析它,得到一帧图片的大小(size)

(5).接收size个字节的数据
}

Mjpg_Steamer.c:
1、
采用dlopen、dlsym、dlclose加载动态链接库【总结】
具体参考:http://www.cnblogs.com/Anker/p/3746802.html
dlopen以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程,
dlerror返回出现的错误,
dlsym通过句柄和连接符名称获取函数名或者变量名,
dlclose来卸载打开的库。 dlopen打开模式如下:
2、源码分析:

/*******************************************************************************
# #
# MJPG-streamer allows to stream JPG frames from an input-plugin #
# to several output plugins #
# #
# Copyright (C) 2007 Tom St枚veken #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
*******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/videodev.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <syslog.h>

#include "utils.h"
#include "mjpg_streamer.h"

/* globals */
static globals global;

/******************************************************************************
Description.: Display a help message
Input Value.: argv[0] is the program name and the parameter progname
Return Value: -
******************************************************************************/
void help(char *progname)
{
  fprintf(stderr, "-----------------------------------------------------------------------\n");
  fprintf(stderr, "Usage: %s\n" \
                  " -i | --input \"<input-plugin.so> [parameters]\"\n" \
                  " -o | --output \"<output-plugin.so> [parameters]\"\n" \
                  " [-h | --help ]........: display this help\n" \
                  " [-v | --version ].....: display version information\n" \
                  " [-b | --background]...: fork to the background, daemon mode\n", progname);
  fprintf(stderr, "-----------------------------------------------------------------------\n");
  fprintf(stderr, "Example #1:\n" \
                  " To open an UVC webcam \"/dev/video1\" and stream it via HTTP:\n" \
                  " %s -i \"input_uvc.so -d /dev/video1\" -o \"output_http.so\"\n", progname);
  fprintf(stderr, "-----------------------------------------------------------------------\n");
  fprintf(stderr, "Example #2:\n" \
                  " To open an UVC webcam and stream via HTTP port 8090:\n" \
                  " %s -i \"input_uvc.so\" -o \"output_http.so -p 8090\"\n", progname);
  fprintf(stderr, "-----------------------------------------------------------------------\n");
  fprintf(stderr, "Example #3:\n" \
                  " To get help for a certain input plugin:\n" \
                  " %s -i \"input_uvc.so --help\"\n", progname);
  fprintf(stderr, "-----------------------------------------------------------------------\n");
  fprintf(stderr, "In case the modules (=plugins) can not be found:\n" \
                  " * Set the default search path for the modules with:\n" \
                  " export LD_LIBRARY_PATH=/path/to/plugins,\n" \
                  " * or put the plugins into the \"/lib/\" or \"/usr/lib\" folder,\n" \
                  " * or instead of just providing the plugin file name, use a complete\n" \
                  " path and filename:\n" \
                  " %s -i \"/path/to/modules/input_uvc.so\"\n", progname);
  fprintf(stderr, "-----------------------------------------------------------------------\n");
}

/******************************************************************************
Description.: pressing CTRL+C sends signals to this process instead of just
              killing it plugins can tidily shutdown and free allocated
              ressources. The function prototype is defined by the system,
              because it is a callback function.
Input Value.: sig tells us which signal was received
Return Value: -
******************************************************************************/
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 cancelation of threads and cleanup ressources\n");
  global.in.stop();
  for(i=0; i<global.outcnt; i++) {
    global.out[i].stop(global.out[i].param.id);
  }
  usleep(1000*1000);

  /* close handles of input plugins */
  dlclose(&global.in.handle);
  for(i=0; i<global.outcnt; i++) {
    /* skip = 0;
    DBG("about to decrement usage counter for handle of %s, id #%02d, handle: %p\n", \
        global.out[i].plugin, global.out[i].param.id, global.out[i].handle);
    for(j=i+1; j<global.outcnt; j++) {
      if ( global.out[i].handle == global.out[j].handle ) {
        DBG("handles are pointing to the same destination (%p == %p)\n", global.out[i].handle, global.out[j].handle);
        skip = 1;
      }
    }
    if ( skip ) {
      continue;
    }

    DBG("closing handle %p\n", global.out[i].handle);
    */
    dlclose(global.out[i].handle);
  }
  DBG("all plugin handles closed\n");

  pthread_cond_destroy(&global.db_update);
  pthread_mutex_destroy(&global.db);

  LOG("done\n");

  closelog();
  exit(0);
  return;
}

/******************************************************************************
Description.:
Input Value.:
Return Value:
******************************************************************************/
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, i;                        /* 是否让程序在后台运行的标志 */
    size_t tmp=0;

    output[0] = "output_http.so --port 8080";
    global.outcnt = 0;                            /* 此时输出通道有几种方式, 0 */

    /* parameter parsing */
    /* 解析参数(关键是看懂 getopt_long_only 函数) */
    /*
    getopt_long_only():
        用于解析命令行选项
    参数解析:
        第二个参数:直接从main函数传递而来,表示我们传入的参数
        第三个参数:短选项字符串
        第四个参数:是 struct option 数组,用于存放长选项参数
        第五个参数:用于返回长选项在longopts结构体数组中的索引值
    返回值:
        解析完毕,返回-1
        出现未定义的长选项或者短选项,getopt_long返回?

    注意:
        长选项必须加'-'
    */
    while(1)
    {
        int option_index = 0, c=0;
        static struct option long_options[] = \
        {
            {"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}
        };

        /* argv = " -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www" " */
        c = getopt_long_only(argc, argv, "", long_options, &option_index);

        /* no more options to parse */
        /* 参数解析完成 */
        if (c == -1)
            break;

        /* unrecognized option */
        /* 如果传入的参数不正确,则打印帮助信息 */
        if(c=='?')
        {
            help(argv[0]);
            return 0;
        }

        switch (option_index)
        {
            /* h, help */
            case 0:
            case 1:
            help(argv[0]);
            return 0;
            break;

            /* i, input */
            case 2:
            case 3:
                input = strdup(optarg);    // input = "input_uvc.so -f 10 -r 320*240"
                break;

            /* o, output */
            case 4:
            case 5:
                output[global.outcnt++] = strdup(optarg);    // output[0] = "output_http.so -w www"
                break;

            /* v, version */
            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;

            /* b, background */
            case 8:
            case 9:
            daemon=1;
            break;

            default:
            help(argv[0]);
            return 0;
        }
    }

    /* 打开一个程序的系统记录器的链接 */
    openlog("MJPG-streamer ", LOG_PID|LOG_CONS, LOG_USER);
    //openlog("MJPG-streamer ", LOG_PID|LOG_CONS|LOG_PERROR, LOG_USER);
    syslog(LOG_INFO, "starting application");        //"starting application" 字符串写到系统记录中

    /* fork to the background */
    /* 如果daemon = 1,则让程序在后台运行 */
    if ( daemon )
    {
        LOG("enabling daemon mode");
        daemon_mode();
    }

    /* initialise the global variables */
    /* 初始化 global 中的成员 */
    global.stop      = 0;
    global.buf       = NULL;
    global.size      = 0;
    global.in.plugin = NULL;

    /* this mutex and the conditional variable are used to synchronize access to the global picture buffer */
    if( pthread_mutex_init(&global.db, NULL) != 0 )        /* 初始化 global.db 成员 */
    {
        LOG("could not initialize mutex variable\n");
        closelog();
        exit(EXIT_FAILURE);
    }
    if( pthread_cond_init(&global.db_update, NULL) != 0 )    /* 初始化 global.db_update(条件变量) 成员 */
    {
        LOG("could not initialize condition variable\n");
        closelog();
        exit(EXIT_FAILURE);
    }

    /* ignore SIGPIPE (send by OS if transmitting to closed TCP sockets) */
    signal(SIGPIPE, SIG_IGN);        /* 忽略 SIGPIPE 信号 */

    /* register signal handler for <CTRL>+C in order to clean up */
    /*
        当我们按下 <CTRL>+C 时,则调用signal_handler()函数,做一些清理工作
    */
    if (signal(SIGINT, signal_handler) == SIG_ERR)
    {
        LOG("could not register signal handler\n");
        closelog();
        exit(EXIT_FAILURE);
    }

    /*
    * messages like the following will only be visible on your terminal
    * if not running in daemon mode
    */
    LOG("MJPG Streamer Version.: %s\n", SOURCE_VERSION);        // 打印出 MJPG Streamer 版本号

    /* check if at least one output plugin was selected */
    if ( global.outcnt == 0 )    // 如果输出方式的种类为0,则让他为1
    {
        /* no? Then use the default plugin instead */
        global.outcnt = 1;
    }

    /* open input plugin */
    /* "input_uvc.so -f 10 -r 320*240" */
    tmp = (size_t)(strchr(input, ' ')-input);        // 让tmp 等于 "input_uvc.so"字符串的长度
    global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);    // global.in.plugin = "input_uvc.so"
    global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);            // 打开 "input_uvc.so" 这个动态链接库
    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);
    }
    global.in.init = dlsym(global.in.handle, "input_init");    //global.in.init = input_init
    if ( global.in.init == NULL )
    {
        LOG("%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.in.stop = dlsym(global.in.handle, "input_stop");    //global.in.stop = input_stop
    if ( global.in.stop == NULL )
    {
        LOG("%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.in.run = dlsym(global.in.handle, "input_run");    //global.in.run = input_run
    if ( global.in.run == NULL )
    {
        LOG("%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    /* try to find optional command */
    global.in.cmd = dlsym(global.in.handle, "input_cmd");    // global.in.cmd = input_cmd

    /*Garmen:strchr() 将会找出 str 字符串中第一次出现的字符 c 的地址,然后将该地址返回。*/
    global.in.param.parameter_string = strchr(input, ' ');        // global.in.param.parameter_string = "-f 10 -r 320*240" //原来字符串:input = "input_uvc.so -f 10 -r 320*240"
    global.in.param.global = &global;

    if ( global.in.init(&global.in.param) )        // 调用input_uvc.c中的input_init(&global.in.param)函数
    {
        LOG("input_init() return value signals to exit");
        closelog();
        exit(0);
    }

    /* open output plugin */
    for (i=0; i<global.outcnt; i++)        // outcnt = 1
    {
        /* "output_http.so -w www" */
        tmp = (size_t)(strchr(output[i], ' ')-output[i]);        // 让tmp 等于 "output_http.so"字符串的长度
        global.out[i].plugin = (tmp > 0)?strndup(output[i], tmp):strdup(output[i]);    //global.out[i].plugin = "output_http.so"
        global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);    // 打开 "output_http.so" 动态链接库
        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");        //global.out[i].init = 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");    //global.out[i].stop = 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");        //global.out[i].run = 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].cmd = output_cmd

        global.out[i].param.parameter_string = strchr(output[i], ' ');    //global.out[i].param.parameter_string = "-w www"
        global.out[i].param.global = &global;
        global.out[i].param.id = i;
        if ( global.out[i].init(&global.out[i].param) )    // 调用 output_http.c中的output_init(&global.out[i].param)函数
        {
            LOG("output_init() return value signals to exit");
            closelog();
            exit(0);
        }
    }

    /* start to read the input, push pictures into global buffer */
    DBG("starting input plugin\n");            // 打印调试信息 "starting input plugin\n"
    syslog(LOG_INFO, "starting input plugin");    //"starting input plugin" 字符串写到记录本中
    global.in.run();        // 调用 input_uvc.c中的input_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);
        global.out[i].run(global.out[i].param.id);        // 调用 output_http.c 中的 output_run 函数
    }

    /* wait for signals */
    pause();            // 程序等待信号的发生

    return 0;
}

你可能感兴趣的:(Mjpg-Streamer之框架分析)