您的位置:首页 > 运维架构 > 网站架构

友善之臂视频监控方案源码学习(1) - 架构分析

2012-12-22 14:38 615 查看
【说明】

对友善之臂的视频监控源码进行学习总结。如有错误,敬请指正。



【学习准备】

1 源码:http://download.csdn.net/detail/tandesir/4915905

2 工具:source insight 3

3 Fedora 10 + gcc4.5.1 + gdb



【源码树】

[tandesir@localhost ~]$ tree mjpg-streamer-mini2440-read-only/

mjpg-streamer-mini2440-read-only/
|-- CHANGELOG
|-- LICENSE
|-- Makefile
|-- README
|-- mjpg-streamer-mini2440-bin.tar.gz
|-- mjpg-streamer-mini2440.kdev4
|-- mjpg_streamer.c
|-- mjpg_streamer.h
|-- plugins
|   |-- input.h
|   |-- input_control
|   |   |-- Makefile
|   |   |-- dynctrl.c
|   |   |-- dynctrl.h
|   |   |-- dynctrl.lo
|   |   |-- input_control.so
|   |   |-- input_uvc.c
|   |   |-- uvc_compat.h
|   |   `-- uvcvideo.h
|   |-- input_file
|   |   |-- Makefile
|   |   |-- input_file.c
|   |   `-- input_file.so
|   |-- input_gspcav1
|   |   |-- Makefile
|   |   |-- encoder.c
|   |   |-- encoder.h
|   |   |-- huffman.c
|   |   |-- huffman.h
|   |   |-- input_gspcav1.c
|   |   |-- jconfig.h
|   |   |-- jdatatype.h
|   |   |-- marker.c
|   |   |-- marker.h
|   |   |-- quant.c
|   |   |-- quant.h
|   |   |-- readme.spcacat
|   |   |-- spcaframe.h
|   |   |-- spcav4l.c
|   |   |-- spcav4l.h
|   |   |-- utils.c
|   |   `-- utils.h
|   |-- input_s3c2410
|   |   |-- Makefile
|   |   |-- input_s3c2410.c
|   |   |-- readme.s3c2410
|   |   |-- s3c2410.c
|   |   |-- s3c2410.h
|   |   |-- utils.c
|   |   `-- utils.h
|   |-- input_testpicture
|   |   |-- Makefile
|   |   |-- input_testpicture.c
|   |   |-- pictures
|   |   |   |-- 160x120_1.jpg
|   |   |   |-- 160x120_2.jpg
|   |   |   |-- 320x240_1.jpg
|   |   |   |-- 320x240_2.jpg
|   |   |   |-- 640x480_1.jpg
|   |   |   |-- 640x480_2.jpg
|   |   |   |-- 960x720_1.jpg
|   |   |   `-- 960x720_2.jpg
|   |   `-- testpictures.h
|   |-- input_uvc
|   |   |-- Makefile
|   |   |-- dynctrl.c
|   |   |-- dynctrl.h
|   |   |-- huffman.h
|   |   |-- input_uvc.c
|   |   |-- jpeg_utils.c
|   |   |-- jpeg_utils.h
|   |   |-- uvc_compat.h
|   |   |-- uvcvideo.h
|   |   |-- v4l2uvc.c
|   |   `-- v4l2uvc.h
|   |-- output.h
|   |-- output_autofocus
|   |   |-- Makefile
|   |   |-- output_autofocus.c
|   |   |-- processJPEG_onlyCenter.c
|   |   `-- processJPEG_onlyCenter.h
|   |-- output_file
|   |   |-- Makefile
|   |   `-- output_file.c
|   `-- output_http
|       |-- Makefile
|       |-- httpd.c
|       |-- httpd.h
|       `-- output_http.c
|-- simplified_jpeg_encoder.c
|-- simplified_jpeg_encoder.h
|-- start_s3c2410.sh
|-- start_uvc.sh
|-- start_uvc_yuv.sh
|-- utils.c
|-- utils.h
`-- www
    |-- LICENSE.txt
    |-- bodybg.gif
    |-- cambozola.jar
    |-- control.htm
    |-- example.jpg
    |-- favicon.ico
    |-- favicon.png
    |-- fix.css
    |-- functions.js
    |-- index.html
    |-- java.html
    |-- java_control.html
    |-- java_simple.html
    |-- javascript.html
    |-- javascript_motiondetection.html
    |-- javascript_simple.html
    |-- sidebarbg.gif
    |-- static.html
    |-- static_simple.html
    |-- stream.html
    |-- stream_simple.html
    |-- style.css
    `-- videolan.html


【源码架构】

1 Makefile分析

顶层的Makefile如下所示:

###############################################################
#
# Purpose: Makefile for "M-JPEG Streamer"
# Author.: Tom Stoeveken (TST)
# Version: 0.3
# License: GPL
#
###############################################################

#CC = arm-linux-gcc
CC ?= gcc

CFLAGS += -O3 -DLINUX -D_GNU_SOURCE -Wall 
#CFLAGS += -O2 -DDEBUG -DLINUX -D_GNU_SOURCE -Wall
LFLAGS +=  -lpthread -ldl 

APP_BINARY=mjpg_streamer
OBJECTS=mjpg_streamer.o utils.o

# define the names and targets of the plugins
PLUGINS = input_uvc.so
PLUGINS += output_file.so
PLUGINS += output_http.so
PLUGINS += input_testpicture.so
PLUGINS += output_autofocus.so
PLUGINS += input_gspcav1.so
PLUGINS += input_file.so
PLUGINS += input_control.so
# PLUGINS += input_http.so
# PLUGINS += output_viewer.so

all: application plugins

clean:
	make -C plugins/input_uvc $@
	make -C plugins/input_testpicture $@
	make -C plugins/output_file $@
	make -C plugins/output_http $@
	make -C plugins/output_autofocus $@
	make -C plugins/input_gspcav1 $@
	rm -f *.a *.o $(APP_BINARY) core *~ *.so *.lo test_jpeg

plugins: $(PLUGINS)

#input_testpicture.so output_autofocus.so input_gspcav1.so

application: $(APP_BINARY)

output_autofocus.so: mjpg_streamer.h utils.h
	make -C plugins/output_autofocus all CC=$(CC)
	cp plugins/output_autofocus/output_autofocus.so .

input_testpicture.so: mjpg_streamer.h utils.h
	make -C plugins/input_testpicture all CC=$(CC)
	cp plugins/input_testpicture/input_testpicture.so .

input_uvc.so: mjpg_streamer.h utils.h
	make -C plugins/input_uvc all CC=$(CC)
	cp plugins/input_uvc/input_uvc.so .

output_file.so: mjpg_streamer.h utils.h
	make -C plugins/output_file all CC=$(CC)
	cp plugins/output_file/output_file.so .

output_http.so: mjpg_streamer.h utils.h
	make -C plugins/output_http all CC=$(CC)
	cp plugins/output_http/output_http.so .

input_gspcav1.so: mjpg_streamer.h utils.h
	make -C plugins/input_gspcav1 all CC=$(CC)
	cp plugins/input_gspcav1/input_gspcav1.so .

input_file.so: mjpg_streamer.h utils.h
	make -C plugins/input_file all
	cp plugins/input_file/input_file.so .	

input_control.so: mjpg_streamer.h utils.h
	make -C plugins/input_control all
	cp plugins/input_control/input_control.so .

$(APP_BINARY): mjpg_streamer.c mjpg_streamer.h mjpg_streamer.o utils.c utils.h utils.o
	$(CC) $(CFLAGS) $(LFLAGS) $(OBJECTS) -o $(APP_BINARY)
	chmod 755 $(APP_BINARY)

package: application plugins
	tar czvf mjpg-streamer-mini2440-bin.tar.gz \
	--exclude www/.svn \
  mjpg_streamer \
	input_testpicture.so \
	input_uvc.so \
  output_file.so \
	output_http.so \
	start_uvc.sh \
	start_uvc_yuv.sh \
  www \
  LICENSE
  
test_jpeg: test_jpeg.c simplified_jpeg_encoder.c simplified_jpeg_encoder.h
	gcc -O0 -g simplified_jpeg_encoder.c test_jpeg.c  -o test_jpeg


由Makefile可以看出,经过编译将编译出如下文件:

(1) input plugins(临时文件) : input_uvc.so, input_testpicture.so, input_gspcav1.so, input_file.so, input_control.so, input_http.so

(2) output plugins(临时文件) : output_file.so, output_http.so, output_autofocus.so, output_viewer.so

(3) 目标(临时文件) : mjpg_streamer.o utils.o

(4) 可执行文件 : mjpg_streamer



2 main入口函数

main入口函数在mjpg_streamer.c,其执行过程可概括如下:

(1) 参数解析

1) 参数解析利用了option结构,其定义在getopt.h中:

struct option
{
#if defined (__STDC__) && __STDC__
  const char *name;
#else
  char *name;
#endif
  /* has_arg can't be an enum because some compilers complain about
     type mismatches in all the code that assumes it is an int.  */
  int has_arg;
  int *flag;
  int val;
};

(a) 基本概念

myprog -a vv --add -b --file a.txt b.txt -e c.txt

长选项(带--的选项):--add --file

短选项(带-的选项):-a -b -e

(b) 参数解释

name:不带短横线的选项名,前面没有短横线。譬如“help”、“verbose”之类。

ihas_arg: 描述了选项是否有选项参数。如果有,是哪种类型的参数。此时,它的值一定是下表中的一个。符号常量数值含义

no_argument 0 选项没有参数

required_argument 1 选项需要参数

optional_argument 2 选项参数可选

flag:指明长选项如何返回,如果flag为NULL,则getopt_long返回val。否则返回0,flag指向一个值为val的变量。如果该长选项没有发现,flag保持不变;

val:指明返回的值,或者需要加载到被flag所指示的变量中。

2)

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}
    };

struct option最后一项须全部初始化为0。可见,input和output选项需要输入参数,其余选项无需参数。上述初始化,还表明,如果参数解析成功将返回val, 即0。



3) 参数解析采用了getopt_long_only函数:

c = getopt_long_only(argc, argv, "", long_options, &option_index);

函数getopt_long()的工作方式类似于getopt(),不过它还能接收长选项。在接收长选项之前,我们必须定义个一个结构体数组变量longopts,指明我们希望获取的长选项。getopt_long_only类似于getopt_long,但是它把'-'开头的选项当作长选项来处理。如果该选项与长选项不匹配,而与短选项匹配,则可以作为短选项解析。错误情况的处理和getopt一样,只是返回'?'时,还可能是别的情况引起的:选项含糊不明确或者无关参数。



4) 参数解析过程如下所示:

/* 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);
        break;

      /* o, output */
      case 4:
      case 5:
        output[global.outcnt++] = strdup(optarg);
        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;
    }

整个解析的代码在while(1)循环内进行,若返回值为-1,则跳出循环。



(2) 根据参数判定是否创建守护进程

/* fork to the background */
  if ( daemon ) {
    LOG("enabling daemon mode");
    daemon_mode();
  }


(3) 初始化互斥锁

if( pthread_mutex_init(&global.db, NULL) != 0 ) {
    LOG("could not initialize mutex variable\n");
    closelog();
    exit(EXIT_FAILURE);
  }
  if( pthread_cond_init(&global.db_update, NULL) != 0 ) {
    LOG("could not initialize condition variable\n");
    closelog();
    exit(EXIT_FAILURE);
  }


(4) 检测终止信号(Ctrl + C)

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

  /* register signal handler for <CTRL>+C in order to clean up */
  if (signal(SIGINT, signal_handler) == SIG_ERR) {
    LOG("could not register signal handler\n");
    closelog();
    exit(EXIT_FAILURE);
  }

回调函数定义如下:

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;
}

主要作用是释放资源。

(5) 加载相应的输入、输出动态链接库

/* open input plugin */
  tmp = (size_t)(strchr(input, ' ')-input);
  global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);
  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);
  }
  global.in.init = dlsym(global.in.handle, "input_init");
  if ( global.in.init == NULL ) {
    LOG("%s\n", dlerror());
    exit(EXIT_FAILURE);
  }
  global.in.stop = dlsym(global.in.handle, "input_stop");
  if ( global.in.stop == NULL ) {
    LOG("%s\n", dlerror());
    exit(EXIT_FAILURE);
  }
  global.in.run = dlsym(global.in.handle, "input_run");
  if ( global.in.run == NULL ) {
    LOG("%s\n", dlerror());
    exit(EXIT_FAILURE);
  }

上述代码为输入插件的情况,输出与之相似。global的结构如下:

struct _globals {
  int stop;

  /* signal fresh frames */
  pthread_mutex_t db;
  pthread_cond_t  db_update;

  /* global JPG frame, this is more or less the "database" */
  unsigned char *buf;
  int size;

  /* input plugin */
  input in;

  /* output plugin */
  output out[MAX_OUTPUT_PLUGINS];
  int outcnt;

  /* pointer to control functions */
  int (*control)(int command, char *details);
};

该结构中,定义了互斥锁,以及输入、输出、控制结构。output和input类似。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、output结构的函数指针进行初试化。输入的input定义如下:

char *input  = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";

下述代码实质上,是取出了input_uvc.so

tmp = (size_t)(strchr(input, ' ')-input);
global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);

动态链接库的加载利用了dlopen函数:

global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);

调用动态链接库使用了dlsym函数:

global.in.init = dlsym(global.in.handle, "input_init");

"input_init"为函数接口的名称,声明在plugins目录下的input.h文件中,具体实现在plugins子目录中。



(6) 运行程序

/* start to read the input, push pictures into global buffer */
  DBG("starting input plugin\n");
  syslog(LOG_INFO, "starting input plugin");
  if ( global.in.run() ) {
    LOG("can not run input plugin\n");
    closelog();
    return 1;
  }

上述代码为输入插件的情况,输出与之相似。运行加载的run函数。



(7) 其他初始化



(8) 参数配置

参数配置在类似start_uvc_yuv.sh的shell脚本中,其形式如下:

./mjpg_streamer -o "output_http.so -w ./www" -i "input_uvc.so -y -d /dev/video2"



【代码评述】

总体来看,代码利用动态链接库实现了功能的动态加载,其加载过程在运行时发生。运行start_uvc_yuv.sh之类的shell脚本时,mjpg_streamer首先解析参数,然后根据参数加载对应的动态链接库,实现相应的功能。





转载请标明出处,仅供学习交流,勿用于商业目的

Copyright @ http://blog.csdn.net/tandesir
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: