Ehome:智能家居之基于USB摄像头免驱的视频采集服务器


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  

高内聚 低耦合模块与模块之间的关联度,越小越好。不编译某个模块,程序一样可以编译运行。


    $:' ctags -R *
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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜源Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值