![a395f90bf5c1059985ddd61d76aee885.png](https://i-blog.csdnimg.cn/blog_migrate/9953a63d37149b49801e0e9e3a3e2f21.png)
过完这个暑假,又到了一年一度的秋招季节。很多同学已经开始了准备工作,制作简历、疯狂刷题、或者就是回顾在校期间做过的项目。但是每年都有很多同学在校期间没做过项目,简历上不知道怎么写。今天就来给大家推荐一个,非常适合嵌入式开发方向的同学。
项目名称:基于web的远程视频传输
软件平台:Ubuntu
硬件平台:NanoPi Duo2
备注:硬件平台选择性很多,mini2440、NanoPi系列、树莓派等等。
mjpg-streamer
MJPG-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
如果不想麻烦,公众号后台【mjpg】,直接获取! 安装 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 和电脑可以组成一个局域网;
ifconfigwlan0 Link encap:Ethernet HWaddr c0:84:7d:a0:27:14 inet addr:192.168.0.105 Bcast:192.168.0.255 Mask:255.255.255.0 inet6 addr: fe80::9e9a:8bbc:69f1:24f0/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:8731 errors:0 dropped:0 overruns:0 frame:0 TX packets:3727 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes: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、命令解析
int main(int argc, char *argv[]){ char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0"; char *output[MAX_OUTPUT_PLUGINS];//10 int 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; 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} }; 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、输入功能分析
int input_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、输出功能
int output_init(output_parameter *param)
int output_stop(int id)
int output_run(int id)
备战秋招
刻不容缓
暑期智能硬件实训火热招生
有需要的同学抓紧锁定席位