mjpg_streamer源码学习笔记(一)

本文详细解读了mjpg_streamer的源码,涉及参数初始化、命令行解析以及动态链接库的应用,重点介绍了如何通过输入和输出插件处理视频流的获取和推送。
摘要由CSDN通过智能技术生成

找实习好难,还是好好沉淀吧!!!

mjpg_streamer是最基础的嵌入式linux视频流推流库,虽然停止更新了但还是想深扒一下它的源码。毕竟之前也没有好好看过库文件的源码,且对多线程理解比较不透彻。

视频流推流应该由两部分程序组成,一部分从摄像头获取拍摄到的数据,另一部分通过各种方式推送拍到的图像数据。mjpg_streamer程序通过动态链接库将各部分函数动态编译。

主函数

部分参数的初始化与申明

//char *input  = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
    char *input[MAX_INPUT_PLUGINS];
    char *output[MAX_OUTPUT_PLUGINS];
    int daemon = 0, i, j;
    size_t tmp = 0;

    output[0] = "output_http.so --port 8080";
    global.outcnt = 0;
    global.incnt = 0;

global是个结构体,作为全局变量申明。用来存储支持几种输入输出方式并保存各种方式的各项参数。各项参数保存在结构体input和output中,这些结构体包含的内容我们后面再细看。

当没有指定输出方式时以http方式作为默认的输出方式。

解析命令行参数

while(1) {
        int c = 0;
        static struct option long_options[] = {
            {"help", no_argument, NULL, 'h'},
            {"input", required_argument, NULL, 'i'},
            {"output", required_argument, NULL, 'o'},
            {"version", no_argument, NULL, 'v'},
            {"background", no_argument, NULL, 'b'},
            {NULL, 0, NULL, 0}
        };

        c = getopt_long(argc, argv, "hi:o:vb", long_options, NULL);

        /* no more options to parse */
        if(c == -1) break;

        switch(c) {
        case 'i':
            input[global.incnt++] = strdup(optarg);
 /*
 * strdup() 函数将参数 s 指向的字符串复制到一个字符串指针上去,这个字符串指针事先可以没被初始化。
 * 在复制时,strdup() 会给这个指针分配空间,使用 malloc() 函数进行分配,如果不再使用这个指针,
 * 相应的用 free() 来释放掉这部分空间。
 */
            break;

        case 'o':
            output[global.outcnt++] = strdup(optarg);
            break;

        case 'v':
            printf("MJPG Streamer Version: %s\n",
#ifdef GIT_HASH
            GIT_HASH
#else
            SOURCE_VERSION
#endif
            );
            return 0;
            break;

        case 'b':
            daemon = 1;
            break;

        case 'h': /* fall through */
        default:
            help(argv[0]);
            exit(EXIT_FAILURE);
        }
    }

通过getopt_long()函数来解析命令行参数,-i和-o命令代指input和output也就是上面说的两个部分的功能。后面接的参数被保存在两个字符串数组中,里面的内容用来指定以什么方式进行输入输出操作。

获取输入输出方式并存储各项参数

for(i = 0; i < global.incnt; i++) {
        /* this mutex and the conditional variable are used to synchronize access to the global picture buffer */
        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);
        }

        tmp = (size_t)(strchr(input[i], ' ') - input[i]);
        global.in[i].stop      = 0;
        global.in[i].context   = NULL;
        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);
        }
        global.in[i].init = dlsym(global.in[i].handle, "input_init");
        if(global.in[i].init == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }

        /*
         * dlsym函数的功能就是可以从共享库(动态库)中获取符号(全局变量与函数符号)地址,
         * 通常用于获取函数符号地址,这样可用于对共享库中函数的包装
         */

        global.in[i].stop = dlsym(global.in[i].handle, "input_stop");
        if(global.in[i].stop == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        global.in[i].run = dlsym(global.in[i].handle, "input_run");
        if(global.in[i].run == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        /* try to find optional command */
        global.in[i].cmd = dlsym(global.in[i].handle, "input_cmd");

        global.in[i].param.parameters = strchr(input[i], ' ');

        /*strchr()
         *
         * 表示在字符串 s 中查找字符 c,返回字符 c 第一次在字符串 s 中出现的位置,如果未找到字符 c,
         * 则返回 NULL。也就是说,strchr 函数在字符串 s 中从前到后(或者称为从左到右)查找字符 c
         */

        for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
            global.in[i].param.argv[j] = NULL;
        }

        split_parameters(global.in[i].param.parameters, &global.in[i].param.argc, global.in[i].param.argv);
        global.in[i].param.global = &global;
        global.in[i].param.id = i;

        if(global.in[i].init(&global.in[i].param, i)) {
            LOG("input_init() return value signals to exit\n");
            closelog();
            exit(0);
        }
    }

这段代码是用来给命令行指定的输入方式的各项参数进行初始化。

首先先初始化一个互斥锁和一个条件变量。

然后通过dlopen()函数打开对应的input方式链接库,再通过dlsym()函数获取各对应函数的地址赋值给结构体中的函数名。

最后通过split_parameters函数将该种方式调用input函数所需要的参数解析并保存。

整体调用加上参数近似于

"input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0"

同理output的各种方式参数初始化也类似于input。源码如下

for(i = 0; i < global.outcnt; i++) {
        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);
        }
        global.out[i].init = dlsym(global.out[i].handle, "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");
        if(global.out[i].stop == NULL) {
            LOG("%s\n", dlerror());
            exit(EXIT_FAILURE);
        }
        global.out[i].run = dlsym(global.out[i].handle, "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].param.parameters = strchr(output[i], ' ');

        for (j = 0; j<MAX_PLUGIN_ARGUMENTS; j++) {
            global.out[i].param.argv[j] = NULL;
        }
        split_parameters(global.out[i].param.parameters, &global.out[i].param.argc, global.out[i].param.argv);

        global.out[i].param.global = &global;
        global.out[i].param.id = i;
        if(global.out[i].init(&global.out[i].param, i)) {
            LOG("output_init() return value signals to exit\n");
            closelog();
            exit(EXIT_FAILURE);
        }
    }

调用各种方式的run函数

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

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
mjpg-streamer 可以通过文件或者是HTTP方式访问linux UVC兼容摄像头。可以在公司,通过访问家里安装的摄像头查看家里的情况,对于有小孩的家长很有帮助。 1.安装辅助工具 在树莓派上执行: sudo apt-get install libjpeg8-dev sudo apt-get install cmake 2.解压master,zip 在树莓派上执行:unzip master.zip 3.编辑源文件 在树莓派上执行: cd mjpg-streamer-master/mjpg-streamer-experimental/plugins/input_raspicam (移动到mjpg-streamer-master/mjpg-streamer-experimental/plugins/input_raspicam) nano input_raspicam.c (用nano编辑input_raspicam.c文件) 进入nano页面后,按下ctrl+w(搜索),输入fps,回车 将fps改成30,width=320,height=240 4.编译mjpg软件 在树莓派上执行: cd .. cd ..(同一个命令执行两次,目的是为了返回到目录mjpg-streamer-experimental) make clean all 5.制作mjpg的启动脚本 在树莓派上执行: cd nano jk.sh 将下面两条命令复制进去 cd mjpg-streamer-master/mjpg-streamer-experimental #USB摄像头 ./mjpg_streamer -i "./input_uvc.so" -o "./output_http.so -w ./www" #树莓派摄像头 #./mjpg_streamer -i "./input_raspicam.so" -o "./output_http.so -w ./www" 像上面一样,按ctrl+x,再按y,再回车(保存,退出) 继续,在树莓派上执行: chmod 744 jk.sh 6.执行mjpg的启动脚本(启动mjpg) 在树莓派上执行: sh jk.sh 最后,在浏览器上打开:(我用的是chrome浏览器) http://你树莓派的ip:8080
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值