项目名称:基于web的远程视频传输
软件平台:Ubuntu
硬件平台:NanoPi Duo2
备注:硬件平台选择性很多,mini2440、NanoPi系列、树莓派等等。
mjpg-streamerMJPG-streamer
是一款开源基于IP地址的视频流服务器,它的输入插件从摄像头读取视频数据,这个输入插件产生视频数据并将视频数据复制到内存中。它有多个输出插件将这些视频数据经过处理,其中最重要的输出插件是网站服务器插件,它将视频数据传送到用户浏览器中。MJPG-streamer的工作就是将其中的一个输入插件和多个输出插件绑定在一起,所有的工作都是通过它的各个插件完成的。
第一步:
下载源码源码地址:https://sourceforge.net/p/mjpg-streamer/code/HEAD/tree/
可以直接下载源码包的压缩文件:mjpg-streamer-code-r182.zip
或者通过 svn 下载:svn checkout https://svn.code.sf.net/p/mjpg-streamer/code/ mjpg-streamer-code
备注:环境中需要先安装 svn
如果不想麻烦,百度网盘:链接:https://pan.baidu.com/s/1B29YOHRYDkzGpkD0h8HsmA
提取码:qjpd
,直接获取!
安装mjpg-streamer
需要先安装 libjpeg,libjpeg源码地址https://sourceforge.net/projects/libjpeg/可以使用 wget 直接下载。
第二步:
交叉编译
1、安装交叉编译环境(以NanoPi Duo2平台为例)
* 下载交叉编译器
arm-cortexa9-linux-gnueabihf-4.9.3-20160512.tar.xz
注:NanoPi官网提供下载链接
* 放至 Ubuntu 的 opt 目录下,并且解压
tar -xf arm-cortexa9-linux-gnueabihf-4.9.3-20160512.tar.xz
* 修改文件 /root/.bashrc
,
* 在最后加上export PATH=$PATH:/opt/4.9.3/bin
* 使修改的文件生效source /root/.bashrc
2、编译 libjpeg
* 解压后进入目录;
* 配置编译环境
./configure --host=arm-linux- --prefix=/home/jpeg-install
* 编译make
* 安装make install
* 安装完毕会在/home
目录下看见jpeg-install
目录。
3、编译 mjpg-streamer
-
修改顶层 Makefile
CC=arm-linux-gcc
-
修改插件的 Makefile(plugins/input_uvc/Makefile)
CC=arm-linux-gcc以及动态库路径$(CC) $(CFLAGS) -L /home/jpeg-install -ljpeg -o $@ input_uvc.so
-
修改插件 Makefile (plugins/output_http/Makefile)
CC=arm-linux-gcc
- 编译
make
- 编译
最终生成的文件打包复制到开发板上,需要安装 libjpeg,mjpg-streamer不需要安装。
第三步:
运行
1、配置网络,保证 NanoPi 和电脑可以组成一个局域网;
ifconfig
wlan0Link encap:Ethernet HWaddr c0:84:7d:a0:27:14
inetaddr:192.168.0.105 Bcast:192.168.0.255
Mask:255.255.255.0inet6addr: fe80::9e9a:8bbc:69f1:24f0/64
Scope:LinkUPBROADCAST RUNNING MULTICAST MTU:1500 Metric:1RXpackets:8731 errors:0 dropped:0
overruns:0 frame:0TXpackets:3727 errors:0 dropped:0
overruns:0 carrier:0collisions:0 txqueuelen:1000
RXbytes:6593454 (6.5 MB) TX bytes:502413 (502.4 KB)
开发板的地址是:192.168.0.105
,这个很重要。
2、打开笔记本的浏览器,输入:http://192.168.0.105:8080/
选择 Stream
源码分析
1、几个重要文件
- input_testpicture.so:这是一个图像测试插件,它将预设好的图像编译成一个头文件,可以在没有摄像头的情况下传输图像,从而方便调试程序。
- input_uvc.so:此文件调用USB摄像头驱动程序 V4L2,从摄像头读取视频数据。
- output_http.so:这是一个功能齐全的网站服务器,它不仅可以从单一文件夹中处理文件,还可以执行一定的命令,它可以从输入插件中处理一幅图像,也可以将输入插件的视频文件根据现有M-JPEG标准以HTTP视频数据服务流形式输出。
- input_control.so:这个文件实现对摄像头参数的控制。
- output_file.so:这个插件的功能是将输入插件的JPEG图像存储到特定的文件夹下,它可以用来抓取图像。
2、命令解析
intmain(int argc, char *argv[]){char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
char *output[MAX_OUTPUT_PLUGINS];//10int daemon=0, i; size_t tmp=0;output[0] = "output_http.so --port 8080";
global.outcnt = 0;global.control = control;/* parameter parsing */while(1) {int option_index = 0, c=0;
staticstruct 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} };c = getopt_long_only(argc, argv, "", long_options, &option_index);
这里主要遇到一个功能函数getopt_long_only()
,该函数功能主要是解析命令行选项,也就是将input
中的-h -i -o -v -b
的参数解析出来,该函数具体如何使用,可参照getopt_long_only
。
3、调用输入插件
/* 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); }dlopen()函数负责打开 *.so 插件,dlsym() 负责调用插件中的相关函数。当输入./mjpg_streamer -i "./input_uvc.so"系统将会调用 input_uvc.so 中的 input_init 函数,对输入设备进行初始化。
4、调用输出插件
/* open output plugin */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); }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); }
5、输入功能分析
intinput_init(input_parameter *param)
这个函数代码实现对 usb 摄像头的格式、帧、请求buf,队列buf等的一些设置,
主要实现也是在函数
init_videoIn(videoIn, dev, width, height, fps, format, 1)
int input_stop(void)
{
DBG("will cancel input thread\n");
pthread_cancel(cam);
return 0;
}
输入停止,主要是取消线程。
6、输出功能
intoutput_init(output_parameter *param)
int output_stop(intid)
int output_run(intid)