讲解mjpg-streamer
其功能:
1、控制摄像头采集数据(通过ioctl采集数据,所有不支持CMOS,CMOS之前写驱动的时候是通过read,所有需要修改mjpg-streamer的源码或者CMOS驱动);
2、把采集的数据传输到路由器上或者无线网卡上;
我们的JZ2440可以接一个usb-hub,上面接上无线网卡和摄像头,可以一起使用
1. 如何将mjpg-streamer移植到开发板上
文件系统:fs_mini_mdev_new_auto_wifi_ap.tar.bz2
(1) libjpeg 的移植
tar xzvf libjpeg-turbo-1.2.1.tar.gz
cd libjpeg-turbo-1.2.1
mkdir tmp
./configure --prefix=/work/jz2440/libjpeg-turbo-1.2.1/tmp --host=arm-linux
make
make install
cp /work/jz2440/libjpeg-turbo-1.2.1/tmp/lib/*so* /work/nfs_root/fs_mini_mdev_new/lib/ -d
(2) mjpg-streamer 的移植:
tar xvf mjpg-streamer-r63.tar.gz
cd mjpg-streamer-r63
修改所有的Makefile
--(1) 将 CC=gcc 修改为 CC=arm-linux-gcc
--(2) 修改plugins/input_uvc/Makfile
a. 将
CFLAGS += -O2 -DLINUX -D_GNU_SOURCE -Wall -shared -fPIC
改为
CFLAGS += -O2 -DLINUX -D_GNU_SOURCE -Wall -shared -fPIC -I /work/jz2440/libjpeg-turbo-1.2.1/tmp/include
注意:
-I /work/jz2440/libjpeg-turbo-1.2.1/tmp/include // 是编译libjpeg 生成的文件
b. 将
input_uvc.so: $(OTHER_HEADERS) input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo
$(CC) $(CFLAGS) -ljpeg -o $@ input_uvc.c v4l2uvc.lo
jpeg_utils.lo dynctrl.lo
改为
input_uvc.so: $(OTHER_HEADERS) input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo
$(CC) $(CFLAGS) -ljpeg -L /work/jz2440/libjpeg-turbo-1.2.1/tmp/lib -o
$@ input_uvc.c v4l2uvc.lo jpeg_utils.lo dynctrl.lo
make
cp mjpg_streamer /work/nfs_root/fs_mini_mdev_new/bin/
cp *so* /work/nfs_root/fs_mini_mdev_new/lib/ -d
运行指令:
二合一摄像头(-i表示输入渠道,-o表示输出渠道)
mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
一般摄像头
mjpg_streamer -i "input_uvc.so -f 10 -r 320*240 -y" -o "output_http.so -w www"(-y表示压缩图片成jpeg格式,方便传输)
2.分析源码
摄像头数据地址buf、源地址input 、目的地址output,都保存在Mipg_streamer.h这个头文件中的_globals结构体中
编译mjpg-streamer生成的so库中input开头的表示各个输入渠道,out开头的用于表示各个输出渠道
通过-i和-o指定输入和输出渠道后,每个input和output表示一个结构体:这个结构体有init函数、open函数、cmd函数、stop函数
举例:当执行mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"指令时,对应的输入源代码参考input._uvc.c文件,里面有各个函数:
input_init
init_videoIn(videoIn,dev,width,height,fps,format)//设置图像长、宽、帧率、格式
设置vdIn结构体
init_v4l2//里面就是v4l2应用程序的框架
open()//打开摄像头的设备节点,根据传入的参数或者默认参数决定
ioctl(VIDIOC_QUERYCAP)//查看打开的设备的属性,下面会判断是否是视频捕获设备
ioctl(VIDIOC_S_FMT)//设置摄像头的输出格式(分辨率、输出格式)
ioctl(VIDIOC_S_PARM)//设置摄像头的参数,比如输出帧率
ioctl(VIDIOC_REQBUFS)//申请缓存
for(缓存个数)
ioctl(VIDIOC_QUERYBUF)//获取内核空间的视频缓冲区的信息
mmap()//根据获得的信息做映射操作
for(缓存个数)
ioctl(VIDIOC_QBUF)//将一个空的缓冲区放入视频缓冲区队列
分配临时缓冲区(vd->framebuffer)用来接收摄像头数据,MJPEG需要分配两个,见代码
if(dynctrls)//动态控制摄像头功能初始化
initDynCtrls
input_cmd()//通过命令行动态控制摄像头参数,比如高级摄像头的焦距控制
input_run
分配一段buf用来存储视频数据(仓库)
pthread_create(cam_thread)//创建线程,线程函数是cam_thread
cam_thread
while(!pglobal->stop)//stop会在按下CTRL+C的时候会被置1
uvcGrab//获得一帧数据
video_enable//使能视频捕获设备
ioctl(VIDIOC_STREANMON)//将一个空的缓冲区放入视频缓冲区队列
ioctl(VIDIOC_DQBUF)//从视频缓冲区队列取出一个已经存有一帧视频数据的缓冲区buf
根据获得的信息处理数据及根据视频格式把数据复制到init阶段创建的vd->framebuffer中去,MJPEG格式稍微有点不同,其是放到tempbuffer中
ioctl(VIDIOC_QBUF)//把这个已获得数据的空的缓冲区放入视频缓冲区队列
判断数据的格式是YUV或者jpeg,如果是jpeg这直接放入globl里面的buf(仓库),否则将YUV转换为jpeg后在保存在buf
pthread_cond_broadcase(&pglobal->db_update)//通知输出通道可以去数据发出了
对于output_http.so,就是使用sock编程还模拟http协议,查看源码,其中init函数做socket的初始化,run函数从buf中取出数据,然后发送数据
(见output_http.c)
output_init()
仅解析指令参数,然后给相应的变量赋值
output_run()
pthread_create(server_thread)//创建线程,线程函数是server_thread
server_thread
socket(PF_INET,SOCK_STREAM,0)//创建一个socket(其是作为服务器)
setsockopt()//当端口被其他进程使用时,通过这个函数里面指定的参数可以设置重复使用同一个IP和端口号
bind()//绑定端口和IP地址
listen()//启动检测数据,第二个参数表示最多客户端数量
while((!pglobal->stop))//同input_run里面的分析
分配一个cfd结构体
accept()//休眠等待客户端连接,并建立连接
pthread_create(client_thread)//创建线程,线程函数是client_thread
client_thread//参数是上面设置的cfd结构体
init_iobuffer(&iobuf)//初始化一个iobuf,在后面仅是一个临时缓存
init_request(&req)//http协议,需要客户端给服务器发送一个请求,而request就是用来保存请求的
_readline()//从客户端接受一些数据,用来表示客户端发来的请求,才知道给客户发什么数据
这个函数里面调用select函数,当客户端发来数据或者超时的时候返回去调用read函
数从客户端获得数据并保存在iobuf中,接着从iobuf中将数据拷贝到buffer中(这个过程
并不是简单的复制,可以查看代码分析)
因此客户端必须发送一个请求字符串,以换行符作为结束,比如当使用浏览器输入URL
的时候http://192.168.1.17:8080/?action=stream(action=stream这个就表示请求)
从buffer中提取请求并保存在request中
·· do
_readline() 再次读取一次客户端发送过来的字符串
处理buffer中的数据并保存,比如如果有用户名,则保存到req.client;有密码保存到req.credential
如果服务器有账户和密码,则比较客户端传入的账户密码是否匹配
根据第一次客户端发来的请求做不同动作
case A_SNAPSHOT(拍照请求)
send_snapshot(发送照片)
pthread_cond_wait(等待input通道通知有数据可取了)
分配一帧缓冲区,并从global->buf(仓库)中取出数据保存
构造一个http数据头在buffer数据,并将这个数据发送出去(这个数据头表示请求)
如果上一步成功,就把一帧图片发送出去
case A_STREAM
send_stream
构造一个http数据头在buffer数据,并将这个数据发送出去(这个数据头表示请求)
while(!pglobal->stop)
pthread_cond_wait(等待input通道通知有数据可取了)
如果帧framer空间不够就分配空间,并从global->buf(仓库)中取出数据保存
构造一个http数据头(报文)在buffer数据,并将这个数据发送出去(这个数据头表示请求,里面有下面发送帧图片数据大小)
如果上一步成功,就把一帧图片发送出去
接着发送一个“boundarydonotcross”字符串,表示一帧数据已接受完
input_init();
output_init();
input_run();
output_run();
3.自己写客服端(在“在LCD上显示摄像头”的7th_pc上修改程序)
(1).发送一个请求字符串
"GET /?action=snapshot
"
"GET /?action=stream
"
"GET /?action=command
"
(2).再发送一次字符串
如果我们不使用登录密码功能!则只需发送任意长度为小于2字节的字符串,就会跳过处理账户和密码的代码比如:
"f
"
如果发送的请求是:"GET /?action=snapshot
"
(3).需要接收一次字符串(是服务器发过来的报文)
(4).接收一帧图片
如果发送的请求是:"GET /?action=stream "
(3).需要接收一次字符串(是服务器发过来的报文)
while(1)
{
(4).再接收一次报文,解析它,得到一帧图片的大小(size)
(5).接收size个字节的数据
}
1、在ubuntu上连接2440上无线网卡的wifi热点
2、按住ctrl+Alt+F1进入文本模式,输入用户名和密码
3、运行客户端程序( ./应用程序 开发板IP地址)
4、ctrl+Alt+F4
启动内核:
set ipaddr 192.168.7.122 && set serverip 192.168.7.111 && tftp 0x30000000 uImage
set bootargs root=/dev/nfs nfsroot=192.168.7.202:/work/nfs_root/fs_mini_mdev_new ip=192.168.7.17 console=ttySAC0,115200 && bootm 0x30000000