4. 视频服务器
4.1 摄像头的驱动[' uvc子系统']: usb video class
内核中自带了满足uvc格式的摄像头驱动,如果你手中的摄像头满足uvc规范,该摄像头就是 免驱,只需要对内核进行配置,将uvc模块对应的代码编译到uImage
判断摄像头满足uvc格式规范?
#:' lsusb
再将摄像头插入开发板
#:' lsusb
Bus 001 Device 003: ID 046d:0825
网络搜索 uvc官网 有一个的页面:
列出了uvc框架支持的usb摄像头ID,核对lsusb命令即可。
配置内核,将uvc子系统编译进内核
$:' cp arch/arm/config/x6818-config .config
$:' make menuconfig
Device Drivers --->
<*> Multimedia support --->
[*] Video capture adapters --->
[*] V4L USB devices --->
<*> USB Video Class (UVC)
// 检查是否选中状态,未选中就需要重新编译内核
$:' make uImage
// 让开发板加载包含uvc模块的新的内核,已经包含就不需重新编译
#:' ls /dev/video*
再次插入摄像头
发现多了一个 video9 ,就是插入摄像头的设备文件
4.2 应用程序
4.2.1操作摄像头,抓取图像数据
[' v4l2']: video for linux ver2 ( v four linux)
它属于摄像头软件的中间层
向下统一摄像头驱动的格式
向上为应用软件访问控制摄像头提供统一的接口
简化应用层软件控制摄像头的编程工作
v4l2用户态编程:
v4l2提供的有小程序: // v4l_demo.zip/capture.c
open设备
ioctl设置工作参数
ioctl(fd, VIDIOC_STREAMON, &type); //开始摄像头开始工作
// 获取图像数据
ioctl(fd, VIDIOC_DQBUF, &buf);
ioctl(fd, VIDIOC_QBUF, &buf);
mjpeg-streamer包含了按照v4l2框架去操作摄像头的代码
而且其中也包含了按照http协议向客户端发送图像数据的代码
重点: 移植部署运行mjpeg-streamer
4.2.2mjpeg-streamer的移植: // mjpg-streamer.tar.bz2
$:' cd project
$:' mkdir video
$:' cd video
$:' cp /mnt/hgfs/project/env/mjpg-streamer.tar.bz2 .
$:' tar xvf mjpg-streamer.tar.bz2
$:' cd mjpg-streamer/
$:' vi README
make clean all
./mjpg-streamer ....
start.sh
$:' vi Makefile
CC = gcc
$:' find ./ -name "Makefile" -exec sed -i "s/CC = gcc/CC = arm-cortex_a9-linux-gnueabi-gcc/g" {} \;
sed: 操作文件
awk: 操作行处理
结合正则表达式 功能非常强大
$:' make
$:' file mjpg_streamer
// 确认文件是ARM运行平台
4.2.3 部署到开发板
$:' cp mjpg_streamer ../../rootfs/home/bin/
// 拷贝可执行文件 mjpg_streamer
$:' cp *.so ../../rootfs/home/lib/ -a
// 拷贝相关的共享库
$:' cp www/ ../../rootfs/home/ -r
// 拷贝www目录
www目录作用: 浏览器连接视频采集服务。
4.2.4 运行
#:' /home/bin/mjpg_streamer --help
#:' /home/bin/mjpg_streamer -i "input_uvc.so --help"
#:' /home/bin/mjpg_streamer -i "/home/lib/input_uvc.so -d /dev/video9 -y -r 320x240 -f 30" -o "/home/lib/output_http.so -w /home/www"
// 有摄像头的情况下启动www服务。
-i: 指定输入插件
-d: 指定访问的摄像头设备文件
-y: 采集图像的格式为YUYV
-r: 采集图像的大小
-f: 帧频率
-o: 指定输出插件
-w: 网页资源文件所在目录
打开浏览器,输入" http://192.168.1.6:8080/ "
如果手里没有摄像头,可以使用如下方案:
#:' /home/bin/mjpg_streamer -i "/home/lib/input_testpicture.so -r 320x240 -d 500" -o "/home/lib/output_http.so -w /home/www -p 80"
// input_testpicture.so不是采集摄像头数据,是自身有两张图片,将这两张图片交替的发送给客户端,用于模拟测试摄像头获取图像帧
打开浏览器,输入" http://192.168.1.6:8080/ "
4.3 mjpg-streamer 源码分析
4.3.1 mjpg_streamer.c
高内聚 低耦合:模块与模块之间的关联度,越小越好。不编译某个模块,程序一样可以编译运行。
int main(int argc, char *argv[])
{
/*共享库的运行阶段加载有两种方式
gcc xxx -o a.out -lpthread
./a.out 操作系统加载共享库
程序中自主主动加载共享库(插件库)
*/
"input_uvc.so"
global.in[i].handle = dlopen(global.in[i].plugin, RTLD_LAZY);
/*找到input_uvc.so中的input_init函数对应代码在内存中的位置*/
global.in[i].init = dlsym(global.in[i].handle, "input_init");
global.in[i].run = dlsym(global.in[i].handle, "input_run");
/*执行input_uvc.so中的input_init函数*/
global.in[i].init(&global.in[i].param, i);
"output_http.so"
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
global.out[i].init = dlsym(global.out[i].handle, "output_init");
global.out[i].run = dlsym(global.out[i].handle, "output_run");
global.out[i].init(&global.out[i].param, i);
global.in[i].run(i);
global.out[i].run(global.out[i].param.id);
pause();
return 0;
}
4.3.2 输入插件 plugins/input_uvc/
按照v4l2编程步骤去操作uvc格式的摄像头
官方例程: capture.c
Video for linux 2 example (v4l2 demo) - MetalSeed - 博客频道 - CSDN.NET.png
$:' vi plugins/input_uvc/input_uvc.c
/*打开摄像头 设置工作参数*/
int input_init(input_parameter *param, int id)
{
init_videoIn(cams[id].videoIn, dev, width, he ight, fps, format, 1, cams[id].pglobal, id)
{
init_v4l2(vd)
{
/*打开"/dev/video9"设备文件*/
vd->fd = OPEN_VIDEO(vd->videodevice, O_RDWR)
/*查询当前硬件的工作能力*/
xioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap)
/*图像格式设置*/
ret = xioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);
...
}
}
}
int input_run(int id)
{
pthread_create(&(cams[id].threadID), NULL, cam_thread, &(cams[id]));
}
void *cam_thread(void *arg)
{
while(!pglobal->stop)
{
uvcGrab(pcontext->videoIn)
{
video_enable(vd)
{
/*VIDIOC_STREAMON:让摄像头开始工作*/
ret = xioctl(vd->fd, VIDIOC_STREAMON, &type);
}
/*获取一帧图像*/
xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf)
}
/* copy JPG picture to global buffer */
memcpy_picture(pglobal->in[pcontext->id].buf, pcontext->videoIn->tmpbuffer, pcontext->videoIn->buf.bytesused);
}
}
BUG修改:input_uvc/v4l2uvc.c
428 do{
429 memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
430 vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
431 vd->buf.memory = V4L2_MEMORY_MMAP;
432
433 ret = xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf);
434 if(ret < 0) {
435 perror("Unable to dequeue buffer");
436 // goto err;
437 }
438 }while(ret < 0);
4.3.3 输出插件 plugins/output_http/
将图像数据封装成http数据包
通过TCP方式下客户端发送
$:' vi plugins/output_http/output_http.c
int output_init(output_parameter *param, int id)
{
port = htons(8080);
...
}
int output_run(int id)
{
pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));
}
void *server_thread(void *arg)
{
socket(aip2->ai_family, aip2->ai_socktype, 0)
bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen)
listen(pcontext->sd[i], 10)
while(!pglobal->stop)
{
accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len)
pthread_create(&client, NULL, &client_thread, pcfd)
}
}
void *client_thread(void *arg)
{
_readline(lcfd.fd, &iobuf, buffer, sizeof(buffer) - 1, 5)
{
_read(fd, iobuf, &c, 1, timeout)
{
read(fd, &iobuf->buffer, IO_BUFFER)
}
}
else if(strstr(buffer, "GET /?action=stream") != NULL)
{
req.type = A_STREAM;//确定客户端请求类型
}
switch(req.type) {
case A_STREAM:
send_stream(lcfd.fd, input_number)
{
while(!pglobal->stop)
{
/*将pglobal->buf数据拷贝到 frame缓冲区*/
memcpy(frame, pglobal->in[input_number].buf, frame_size);
write(fd, frame, frame_size)
}
}
break;
}