1. 程式人生 > >Ehome:智慧家居之基於USB攝像頭免驅的視訊採集伺服器

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;
}