1. 程式人生 > >【原創】IP攝像頭技術縱覽(一)---linux 核心編譯,USB攝像頭裝置識別

【原創】IP攝像頭技術縱覽(一)---linux 核心編譯,USB攝像頭裝置識別

IP攝像頭技術縱覽(一)— linux 核心編譯,USB攝像頭裝置識別

開始正文之前先來認識一下我的開發環境:

系統:ubuntu 10.04
開發板:AT91SAM9260 + Linux-2.6.30

USB攝像頭:UVC無驅攝像頭(著手開發時只是隨便買了個usb攝像頭,根本不知道攝像頭還有那麼多講究)
這裡先看下截圖,Sunplus Camera的無驅攝像頭
關於UVC攝像頭,這裡引用度孃的一段解釋:

UVC,全稱為:USB video class 或USB video device class,UVC是Microsoft與另外幾家裝置廠商聯合推出的為USB視訊捕獲裝置定義的協議標準,目前已成為USB org標準之一。如今的主流作業系統(如Windows XP SP2 and later, Linux 2.4.6 and later, MacOS 10.5 and later)都已提供UVC裝置驅動,因此符合UVC規格的硬體裝置在不需要安裝任何的驅動程式下即可在主機中正常使用。
使用 UVC 的好處是省略了驅動程式安裝這一環節,所以稱為無驅—-其實就是不用安裝驅動的意思。

開始正文前先來看一下我的開發板:
這裡寫圖片描述
有沒有很霸氣 (0^◇^0)/,IP64防護等級,絕對秒殺其他開發板,不服拍倒:-D。好了,閒話少提,進入正題。

本文屬於《IP攝像頭技術縱覽》系列文章之一:

Author: chad
Mail: [email protected]

本文可以自由轉載,但轉載請務必註明出處以及本宣告資訊。

引子

  天地初開,大道始行。進行Linux開發的第一步是建立交叉編譯工具鏈,不同的Linux核心版本,不同的硬體平臺,我們使用的交叉工具鏈是不同的。曾經,很長一段時間我一直有個疑惑:為什麼我用at91sam9260交叉編譯工具鏈編譯的程式只能在at91sam9260上執行,在mini2440上就不能執行?相反,用使用於mini2440的交叉編譯工具鏈編譯的程式在at91上也不能執行?mini2440與at91sam9260都是arm平臺,同樣使用linux系統,為何二進位制程式不能通用呢?

  交叉編譯工具鏈的建立只是第一步,為了實現usb攝像頭影象採集功能,又該如何配著linux核心,如何檢測usb攝像頭並採集影象呢?

  要搞清楚這些問題,本文將追本溯源,從頭到尾,圍繞以下問題展開:

  • 交叉編譯工具鏈是什麼?
  • 交叉編譯工具鏈有什麼組成?
  • 交叉編譯工具鏈如何工作?
  • 如何自己建立交叉編譯工具鏈?
  • 如何配置、編譯Linux核心?
  • 如何建立Linux檔案系統?
  • 如何編寫USB攝像頭視訊影象採集程式?
  • 編譯完成的程式檔案是什麼格式?
  • 在我們執行程式檔案時,程式是如何執行起來的?
  • 傳說中的linux虛擬儲存管理是什麼?它與程式的執行有什麼關係?
  • 。。。。。。。

      問題是無窮的,只有有問題的程式設計師才能越走越遠。我們是不建議重複發明輪子的,但如果我們不自己發明一次,我們永遠不知道輪子是怎麼來的。我在這裡只發問,引起大家的思考,然後給出部分答案(我也沒能力給出太多^_^o~ 努力!)。我相信,只有明白了大道,才能更好的開發。

一、核心編譯、檔案系統移植—茅廬初創

  上文已說,核心編譯的第一步是建立交叉編譯工具,那麼:

1. 交叉編譯工具鏈是什麼?

  交叉編譯是嵌入式開發過程中的一項重要技術,其主要特徵是某機器中執行的程式程式碼不是在本機編譯生成,而是由另一臺機器編譯生成,一般把前者稱為目標機,後者稱為主機。
  採用交叉編譯的主要原因在於,多數嵌入式目標系統不能提供足夠的資源供編譯過程使用,因而只好將編譯工程轉移到高效能的主機中進行,這就需要在強大的pc機上建立一個用於目標機的交叉編譯環境。

2. 交叉編譯工具鏈有什麼組成?

  交叉編譯工具鏈是一個由編譯器、聯結器和直譯器組成的綜合開發環境。
  Linux下的交叉編譯環境重要包括以下幾個部分:

 (1)針對目標系統的編譯器gcc/g++;
 (2)針對目標系統的二進位制工具binutils;
 (3)目標系統的標準c庫glibc,有時出於減小libc 庫大小的考慮,你也可以用別的c庫來代替glibc,例如uClibc、newlib等;
 (4)目標系統的Linux核心標頭檔案等。

3. 交叉編譯工具鏈如何工作—編譯原理?

  使用gcc編譯程式時,編譯過程可被細分為四個階段:

(1)預處理
(2)編譯
(3)彙編
(4)連結

以hello.c為例:

#include <stdio.h>
int main()
{
    printf("hello world.\n");
    return 0;
}
1、預處理(Preprocessing)

  預處理階段,編譯器將對原始碼檔案中的檔案包含(include)、預編譯語句(如巨集定義define等)進行分析,它把”stdio.h”的內容插入到hello.i檔案中,使用者使用-E選項進行檢視:

gcc -E hello.c -o hello.i

2、編譯(Compilation)

  gcc首先檢查語法的規範性以及是否有語法錯誤等,以確定程式碼實際要做的工作,使用者可以使用”-S”選項來進行檢視,該選項只進行編譯而不進行彙編,生成彙編程式碼:

gcc -S hello.i -o hello.s

3、彙編(Assembly)

  彙編階段是把編譯階段生成的“.s”檔案轉成目標檔案,使用者在此可使用選項”-c”就可看到彙編程式碼已轉化為”.o”的二進位制目的碼:

gcc -c hello.s -o hello.o

4、連結(Linking)

  在該階段涉及一個重要的概念:函式庫。上例程式中並沒有定義”printf”的函式實現,在預編譯中包含進的“stdio.h”中也只有該函式的宣告,而沒有定義函式的實現,”printf”函式是如何被呼叫的呢?最後的答案是:系統把這些函式實現都己經被放入名為libc.so.6的庫檔案中去了,在沒有特別指定時庫函式搜尋路徑時,gcc會到系統預設的搜尋路徑“/usr/Iib”下進行查詢,連結到libc.so.6庫中的”printf”實現,連結的最終結果是生成可執行ELF檔案:

gcc hello.o –o hello

4. 如何自己建立交叉編譯工具鏈?

  進行嵌入式Linux開發的第一步是建立交叉編譯工具鏈,在過去很長的一段時間裡,構建一套交叉編譯工具鏈對於嵌入式開發者來說簡直是一場惡夢,因為他們得手動跟蹤各種原始碼包(及其更新包)之間的依賴關係。直到buildroot的出現改變了這一事實。

  Buildroot是一個Makefiles和patches的命令集,它可以非常簡單的為你的目標系統產生一個交叉編譯工具鏈和根檔案系統,整個建立過程就如同編譯Linux核心一般。

在Linux中使用Buildroot建立整個ARM交叉編譯環境的整體過程為:

    (1)下載buildroot
    (2)安裝依賴庫軟體包
    (3)解壓buildroot壓縮包
    (4)進入原始碼目錄,執行make menuconfig配置
    (5)儲存退出生成.config 檔案
    (6)編譯
    (7)修改環境變數
    (8)測試arm-linux-gcc
    (9)hello.c測試

其中,導致不同平臺交叉編譯工具鏈不可通用的主要引數是:

#下面的註釋同時對比mini2440平臺與at91sam9260平臺
Target Architecture(arm) ---> 目標的架構,s3c2440 與 at9260 都是arm,這個相同
Target Architecture Variant(arm926t)  ---> 核心型別(s3c2440[arm920t] 而 at91sam9260[arm926EJ-S],但是配置時選擇arm926t),此處不同
Target ABI (OABI)   --->     目標使用的應用程式二進位制介面,此處不同
       ①EABI(Embedded ABI)    mini2440的選擇。
       ②OABI(Old ABI)         at91sam9260的選擇
>   Kernel Headers (Linux 3.18.x kernel headers)  --->  此處差別影響不大
    C library (glibc)  --->  都選擇的是glibc    
    glibc version (2.20)  --->  版本不一樣      

也正是上面的配置,決定了我們最終生成的交叉工具鏈是隻能針對特定的處理器和作業系統平臺的。

5. 如何配置、編譯Linux核心?

  搞明白了交叉編譯工具鏈以後,我們就該進入Linux核心配置階段了,如為了支援USB無驅攝像頭(UVC),我們需要進行的配置如下:

Device Drivers --->
    <*> Multimedia support --->
        <*> Video For Linux
        [*] Enable Video For Linux API 1 (DEPRECATED)
        [*] Video capture adapters --->
            [*] V4L USB devices --->
                <*> USB Video Class (UVC)
                [*] UVC input events device support
                [M] GSPCA based webcams --->

  Linux核心中已經集成了幾乎你能找到的所有的攝像頭的驅動程式,新增攝像頭支援時可以根據情況在編譯核心的時候進行鍼對性配置,或者索性把攝像頭驅動相關的全部編譯在核心中(這樣勢必造成核心尺寸增大,一般linux系統移植時已經設定好了Flash分割槽大小,所以,如果核心尺寸超過限值,將導致系統啟動異常),這樣你就可以宣稱你的開發板支援所有的攝像頭了(^o^)/。其中的GSPCA 是一個法國程式設計師在業餘時間製作的一個萬能USB 攝像頭驅動程式,我沒有用到,所以僅作為演示模組編譯,大家實際使用時候根據自己的攝像頭情況進行選擇。

配置好Linux核心以後,進行編譯:

make ARCH=arm CROSS_COMPILE=arm-linux- uImage

如果要編譯生成GSPCA核心模組,則使用如下命令:

make modules

關於Linux編譯後生成映象格式的簡要說明如下:

zImage是ARM Linux常用的一種壓縮映像檔案,uImage是U-boot專用的映像檔案,它是在zImage之前加上一個長度為0x40的“頭”,說明這個映像檔案的型別、載入位置、生成時間、大小等資訊。換句話說,如果直接從uImage的0x40位置開始執行,zImage和uImage沒有任何區別。另外,Linux2.4核心不支援uImage,Linux2.6核心加入了很多對嵌入式系統的支援,但是uImage的生成也需要設定。

格式 說明
vmlinux 編譯出來的最原始的核心檔案,未壓縮。
zImage 是vmlinux經過gzip壓縮後的檔案。
bzImage bz表示“big zImage”,不是用bzip2壓縮的。兩者的不同之處在於,zImage解壓縮核心到低端記憶體(第一個640K),bzImage解壓縮核心到高階記憶體(1M以上)。如果核心比較小,那麼採用zImage或bzImage都行,如果比較大應該用bzImage。
uImage U-boot專用的映像檔案,它是在zImage之前加上一個長度為0x40的tag。
vmlinuz 是bzImage/zImage檔案的拷貝或指向bzImage/zImage的連結。
initrd 是“initial ramdisk”的簡寫。一般被用來臨時的引導硬體到實際核心vmlinuz能夠接管並繼續引導的狀態。

我開發板的載入程式是uboot,所以這裡生成的是uImage 。

6.建立Linux檔案系統

  根檔案系統使用 busybox 製作,busybox 以小巧著稱,適合於嵌入式裝置的linux 檔案系統。具體制作及移植過程參考《at91sam9260 Linux 系統檔案系統定製》一文。

二、USB攝像頭初識

  Linux UVC driver(uvc) 該驅動適用於符合USB視訊類(USB Video Class)規範的攝像頭裝置,它包括V4L2核心裝置驅動和使用者空間工具補丁。大多數大容量儲存器裝置(如優盤)都遵循USB規範,因而僅用一個單一驅動就可以操作它們。與此類似,UVC相容外設只需要一個通用驅動即可。
  USB攝像頭大體上可以分為UVC cameras和non-UVC cameras。推薦購買UVC cameras。UVC是一個開放的標準,擁有維護良好的驅動,它屬於核心程式碼的一部分。non- UVC cameras通常情況下不比UVC cameras工作出色,驅動並不遵循通用的協議,需要針對每種攝像頭做出單獨的處理,Linux核心中已經集成了常見的攝像頭驅,所以,隨便買一個攝像頭進行測試一般都不成問題。

1、攝像頭型別的確定方法

  關於攝像頭型別的確定,最簡單的方法就是檢視USB攝像頭的硬體ID,硬體ID主要分為VID和PID,在Winows中可以在裝置管理器中檢視,方法如下圖:
這裡寫圖片描述
  在這個圖中能夠看到VID和PID為1871:0141。可以通過這個網頁(http://www.ideasonboard.org/uvc/)來檢視是否是否支援UVC。
很神奇,我的攝像頭不在這個網頁的列表中!但是網上隨便一搜,出來一大堆,摘錄個我們需要的資訊如下:
這裡寫圖片描述
可以確定,我的攝像頭是UVC制式的#^_^#。

VID和PID的意義如下:

根據USB規範的規定,所有的USB裝置都有供應商ID(VID)和產品識別碼(PID),主機通過不同的VID和PID來區別不同的裝置,VID和PID都是兩個位元組長,其中,供應商ID(VID)由供應商向USB執行論壇申請,每個供應商的VID是唯一的,PID由供應商自行決定,理論上來說,不同的產品、相同產品的不同型號、相同型號的不同設計的產品最好採用不同的PID,以便區別相同廠家的不同裝置。

  Linux環境下可以使用lsusb命令或其它硬體資訊檢視工具找出攝像頭的裝置號(Vendor ID)和產品號(Product ID),此處我就不再測試了。

2、USB攝像頭的識別

  前文已經簡要敘述了Linux核心配置、編譯以及檔案系統移植,所以此處假設Linux開發板已經可以正常執行,此時插入USB攝像頭如果提示如下說明核心配置正確:

usb 1-1: USB disconnect, address 2
usb 1-1: new full speed USB device using at91_ohci and address 3
usb 1-1: configuration #1 chosen from 1 choice
uvcvideo: Found UVC 1.00 device USB2.0 Camera (1871:0141)
input: USB2.0 Camera as /class/input/input1

  我的USB攝像頭裝置名稱是:/dev/video1,問我我的為什麼是video1?因為我配置Linux核心時多選擇了下面選項:

<*> Virtual Video Driver

3、編寫USB攝像頭簡單測試程式

  由於USB攝像頭影象採集並生成可預覽的JPEG檔案涉及:V4L2介面程式設計,視訊影象資料格式,jpeg庫移植這些方面。所以本處僅給出一個進行V4L2影象採集並將採集到的影象直接輸出到Framebuffer裝置(液晶屏)上的例項,下一篇文章將講解攝像頭視訊影象格式以及如何將採集到的資料儲存為jpeg檔案。

  下例為開源的V4L2測試例項(預設為YUYV格式轉RGB24),該例項要求系統中有fb0裝置存在,在ubuntu上也可以測試,只要ctl+F1切換到控制檯再執行程式即可。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>  
#include <fcntl.h>  
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <linux/fb.h>

#define uchar unsigned char
#define uint unsigned int
#define CLEAR(x) memset (&(x), 0, sizeof (x))

struct buffer {
    void * start;
    size_t length;
};

static char * dev_name = NULL;
static int fd = -1;
struct buffer * buffers = NULL;
static unsigned int n_buffers = 0;
static int time_in_sec_capture=5;
static int fbfd = -1;
static struct fb_var_screeninfo vinfo;
static struct fb_fix_screeninfo finfo;
static char *fbp=NULL;
static long screensize=0;

static void errno_exit (const char * s)
{
    fprintf (stderr, "%s error %d, %s\n",s, errno, strerror (errno));
    exit (EXIT_FAILURE);
}
/*yuv4:2:2格式轉換為rgb24格式*/
int convert_yuv_to_rgb_pixel(int y, int u, int v)
{
    uint pixel32 = 0;
    uchar *pixel = (uchar *)&pixel32;
    int r, g, b;
    r = y + (1.370705 * (v-128));
    g = y - (0.698001 * (v-128)) - (0.337633 * (u-128));
    b = y + (1.732446 * (u-128));
    if(r > 255) r = 255;
    if(g > 255) g = 255;
    if(b > 255) b = 255;
    if(r < 0) r = 0;
    if(g < 0) g = 0;
    if(b < 0) b = 0;
    pixel[0] = r * 220 / 256;
    pixel[1] = g * 220 / 256;
    pixel[2] = b * 220 / 256;

    return pixel32;
}

int convert_yuv_to_rgb_buffer(uchar *yuv, uchar *rgb, uint width,uint height)
{
    uint in, out = 0;
    uint pixel_16;
    uchar pixel_24[3];
    uint pixel32;
    int y0, u, y1, v;

    for(in = 0; in < width * height * 2; in += 4) {
        pixel_16 =
        yuv[in + 3] << 24 |
        yuv[in + 2] << 16 |
        yuv[in + 1] <<  8 |
        yuv[in + 0];//YUV422每個畫素2位元組,每兩個畫素共用一個Cr,Cb值,即u和v,RGB24每個畫素3個位元組
        y0 = (pixel_16 & 0x000000ff);
        u  = (pixel_16 & 0x0000ff00) >>  8;
        y1 = (pixel_16 & 0x00ff0000) >> 16;
        v  = (pixel_16 & 0xff000000) >> 24;
        pixel32 = convert_yuv_to_rgb_pixel(y0, u, v);
        pixel_24[0] = (pixel32 & 0x000000ff);
        pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
        pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
        rgb[out++] = pixel_24[0];
        rgb[out++] = pixel_24[1];
        rgb[out++] = pixel_24[2];//rgb的一個畫素
        pixel32 = convert_yuv_to_rgb_pixel(y1, u, v);
        pixel_24[0] = (pixel32 & 0x000000ff);
        pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
        pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
        rgb[out++] = pixel_24[0];
        rgb[out++] = pixel_24[1];
        rgb[out++] = pixel_24[2];
    }
    return 0;
}
static int xioctl (int fd,int request,void * arg)
{
    int r;
    do r = ioctl (fd, request, arg);
    while (-1 == r && EINTR == errno);
    return r;
}

inline int clip(int value, int min, int max) {
    return (value > max ? max : value < min ? min : value);
  }

static void process_image (const void * p){

    unsigned char* in=(char*)p;
    int width=320;
    int height=240;
    int istride=640;
    int x,y,j;
    int y0,u,y1,v,r,g,b;
    long location=0;

    convert_yuv_to_rgb_buffer(in,fbp,320,240);
}

static int read_frame (void)
{
    struct v4l2_buffer buf;
    unsigned int i;

    CLEAR (buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
        switch (errno) {
        case EAGAIN:
        return 0;
        case EIO:    



        default:
            errno_exit ("VIDIOC_DQBUF");
        }
    }

    assert (buf.index < n_buffers);
    printf("v4l2_pix_format->field(%d)\n", buf.field);

    process_image (buffers[buf.index].start);
    if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
        errno_exit ("VIDIOC_QBUF");

    return 1;
}

static void run (void)
{
    unsigned int count;
    int frames;
    frames = 30 * time_in_sec_capture;

    while (frames-- > 0) {
        for (;;) {
            fd_set fds;
            struct timeval tv;
            int r;
            FD_ZERO (&fds);
            FD_SET (fd, &fds);


            tv.tv_sec = 2;
            tv.tv_usec = 0;

            r = select (fd + 1, &fds, NULL, NULL, &tv);

            if (-1 == r) {
                if (EINTR == errno)
                    continue;
                errno_exit ("select");
            }

            if (0 == r) {
                fprintf (stderr, "select timeout/n");
                exit (EXIT_FAILURE);
            }
            read_frame ();

            }
    }
}

static void stop_capturing (void)
{
    enum v4l2_buf_type type;

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type))
        errno_exit ("VIDIOC_STREAMOFF");
}

static void start_capturing (void)
{
    unsigned int i;
    enum v4l2_buf_type type;

    for (i = 0; i < n_buffers; ++i) {
        struct v4l2_buffer buf;
        CLEAR (buf);

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
            errno_exit ("VIDIOC_QBUF");
        }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
        errno_exit ("VIDIOC_STREAMON");

}

static void uninit_device (void)
{
    unsigned int i;

    for (i = 0; i < n_buffers; ++i)
        if (-1 == munmap (buffers[i].start, buffers[i].length))
            errno_exit ("munmap");

    if (-1 == munmap(fbp, screensize)) {
          printf(" Error: framebuffer device munmap() failed.\n");
          exit (EXIT_FAILURE) ;
        }    
    free (buffers);
}


static void init_mmap (void)
{
    struct v4l2_requestbuffers req;

    //mmap framebuffer
    fbp = (char *)mmap(NULL,screensize,PROT_READ | PROT_WRITE,MAP_SHARED ,fbfd, 0);
    if ((int)fbp == -1) {
        printf("Error: failed to map framebuffer device to memory.\n");
        exit (EXIT_FAILURE) ;
    }
    memset(fbp, 0, screensize);
    CLEAR (req);

    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
        if (EINVAL == errno) {
            fprintf (stderr, "%s does not support memory mapping\n", dev_name);
            exit (EXIT_FAILURE);
        } else {
            errno_exit ("VIDIOC_REQBUFS");
        }
    }

    if (req.count < 4) {    //if (req.count < 2)
        fprintf (stderr, "Insufficient buffer memory on %s\n",dev_name);
        exit (EXIT_FAILURE);
    }

    buffers = calloc (req.count, sizeof (*buffers));

    if (!buffers) {
        fprintf (stderr, "Out of memory\n");
        exit (EXIT_FAILURE);
    }

    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
        struct v4l2_buffer buf;

        CLEAR (buf);

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;

        if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
            errno_exit ("VIDIOC_QUERYBUF");

        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start =mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);

        if (MAP_FAILED == buffers[n_buffers].start)
            errno_exit ("mmap");
    }

}



static void init_device (void)
{
    struct v4l2_capability cap;
    struct v4l2_cropcap cropcap;
    struct v4l2_crop crop;
    struct v4l2_format fmt;
    unsigned int min;


    // Get fixed screen information
    if (-1==xioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
        printf("Error reading fixed information.\n");
        exit (EXIT_FAILURE);
    }

    // Get variable screen information
    if (-1==xioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
        printf("Error reading variable information.\n");
        exit (EXIT_FAILURE);
    }
    screensize = 320*240 * vinfo.bits_per_pixel / 8;
    printf("vinfo.xres=%d\n",vinfo.xres);
    printf("vinfo.yres=%d\n",vinfo.yres);
    printf("vinfo.bits_per_pixel=%d\n",vinfo.bits_per_pixel);
    printf("screensize=%d\n",screensize);

    if (-1 == xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
        if (EINVAL == errno) {
            fprintf (stderr, "%s is no V4L2 device/n",dev_name);
            exit (EXIT_FAILURE);
        } else {
            errno_exit ("VIDIOC_QUERYCAP");
        }
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        fprintf (stderr, "%s is no video capture device\n",dev_name);
        exit (EXIT_FAILURE);
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        fprintf (stderr, "%s does not support streaming i/o\n",dev_name);
        exit (EXIT_FAILURE);
    }



    CLEAR (cropcap);

    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) {
        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        crop.c = cropcap.defrect;

        if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) {
            switch (errno) {
            case EINVAL:    
            break;
            default:
            break;
            }
        }
    }else {    }

    CLEAR (fmt);

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 320;  
    fmt.fmt.pix.height = 240;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
        errno_exit ("VIDIOC_S_FMT");

    init_mmap ();

}

static void close_device (void)
{
    if (-1 == close (fd))
    errno_exit ("close");
    fd = -1;
    close(fbfd);
}

static void open_device (void)
{
    struct stat st;  

    if (-1 == stat (dev_name, &st)) {
    fprintf (stderr, "Cannot identify '%s': %d, %s\n",dev_name, errno, strerror (errno));
    exit (EXIT_FAILURE);
    }

    if (!S_ISCHR (st.st_mode)) {
        fprintf (stderr, "%s is no device\n", dev_name);
        exit (EXIT_FAILURE);
    }

    //open framebuffer
    fbfd = open("/dev/fb0", O_RDWR);
    if (fbfd==-1) {
        printf("Error: cannot open framebuffer device.\n");
        exit (EXIT_FAILURE);
    }

    //open camera
    fd = open (dev_name, O_RDWR| O_NONBLOCK, 0);

    if (-1 == fd) {
        fprintf (stderr, "Cannot open '%s': %d, %s\n",dev_name, errno, strerror (errno));
        exit (EXIT_FAILURE);
    }
}

static void usage (FILE * fp,int argc,char ** argv)
{
fprintf (fp,
"Usage: %s [options]\n\n"
"Options:/n"
"-d | --device name Video device name [/dev/video]\n"
"-h | --help Print this message\n"
"-t | --how long will display in seconds\n"
"",
argv[0]);
}

static const char short_options [] = "d:ht:";
static const struct option long_options [] = {
{ "device", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ "time", no_argument, NULL, 't' },
{ 0, 0, 0, 0 }
};

int main (int argc,char ** argv)
{
    dev_name = "/dev/video0";

    for (;;)  
    {
        int index;
        int c;

        c = getopt_long (argc, argv,short_options, long_options,&index);
        if (-1 == c)
        break;

        switch (c) {
        case 0:
        break;

        case 'd':
        dev_name = optarg;
        break;

        case 'h':
        usage (stdout, argc, argv);
        exit (EXIT_SUCCESS);
        case 't':
          time_in_sec_capture = atoi(optarg);
            break;

        default:
        usage (stderr, argc, argv);
        exit (EXIT_FAILURE);
        }
    }

    open_device ();

    init_device ();

    start_capturing ();

    run ();

    stop_capturing ();

    uninit_device ();

    close_device ();

    exit (EXIT_SUCCESS);

    return 0;
}

編譯後測試結果如下圖:
這裡寫圖片描述

最後附上常見攝像頭VID和PID:

{USB_DEVICE (0x0733, 0x0430)}, /* Intel PC Camera Pro */ 
{USB_DEVICE (0x0733, 0x0401)}, /* Intel Create and Share */ 
{USB_DEVICE (0x99FA, 0x8988)}, /* Grandtec V.cap */ 
{USB_DEVICE (0x0733, 0x0402)}, /* ViewQuest M318B */ 
{USB_DEVICE (0x0733, 0x0110)}, /* ViewQuest VQ110 */ 
{USB_DEVICE (0x040A, 0x0002)}, /* Kodak DVC-325 */
{USB_DEVICE (0x055f, 0xc420)}, /* Mustek gSmart Mini 2 */ 
{USB_DEVICE (0x055f, 0xc520)}, /* Mustek gSmart Mini 3 */ 
{USB_DEVICE (0x041E, 0x400A)}, /* Creative PC-CAM 300 */ 
{USB_DEVICE (0x084D, 0x0003)}, /* D-Link DSC-350 */
{USB_DEVICE (0x041E, 0x400B)}, /* Creative PC-CAM 600 */
{USB_DEVICE (0x8086, 0x0630)}, /* Intel Pocket PC Camera */ 
{USB_DEVICE (0x8086, 0x0110)}, /* Intel Easy PC Camera */ 
{USB_DEVICE (0x0506, 0x00df)}, /* 3Com HomeConnect Lite */ 
{USB_DEVICE (0x040a, 0x0300)}, /* Kodak EZ200 */ 
{USB_DEVICE (0x04fc, 0x504b)}, /* Maxell MaxPocket LE 1.3 */
{USB_DEVICE (0x08ca, 0x2008)}, /* Aiptek Mini PenCam 2 M */ 
{USB_DEVICE (0x08ca, 0x0104)}, /* Aiptek PocketDVII 1.3 */ 
{USB_DEVICE (0x08ca, 0x2018)}, /* Aiptek Pencam SD 2M */ 
{USB_DEVICE (0x04fc, 0x504a)}, /* Aiptek Mini PenCam 1.3 */ 
{USB_DEVICE (0x055f, 0xc530)}, /* Mustek Gsmart LCD 3 */
{USB_DEVICE (0x055f, 0xc650)}, /* Mustek MDC5500Z */ 
{USB_DEVICE (0x052b, 0x1513)}, /* Megapix V4 */
{USB_DEVICE (0x08ca, 0x0103)}, /* Aiptek PocketDV */ 
{USB_DEVICE (0x0af9, 0x0010)}, /* Hama USB Sightcam 100 */
{USB_DEVICE (0x1776, 0x501c)}, /* Arowana 300K CMOS Camera */ 
{USB_DEVICE (0x08ca, 0x0106)}, /* Aiptek Pocket DV3100+ */ 
{USB_DEVICE (0x08ca, 0x2010)}, /* Aiptek PocketCam 3M */ 
{USB_DEVICE (0x0458, 0x7004)}, /* Genius VideoCAM Express V2 */ 
{USB_DEVICE (0x04fc, 0x0561)}, /* Flexcam 100 */
{USB_DEVICE (0x055f, 0xc430)}, /* Mustek Gsmart LCD 2 */ 
{USB_DEVICE (0x04fc, 0xffff)}, /* Pure DigitalDakota */ 
{USB_DEVICE (0xabcd, 0xcdee)}, /* Petcam */
{USB_DEVICE (0x04a5, 0x3008)}, /* Benq DC 1500 */ 
{USB_DEVICE (0x046d, 0x0960)}, /* Logitech Inc. ClickSmart 420 */ 
{USB_DEVICE (0x046d, 0x0901)}, /* Logitech Inc. ClickSmart 510 */ 
{USB_DEVICE (0x04a5, 0x3003)}, /* Benq DC 1300 */ 
{USB_DEVICE (0x0af9, 0x0011)}, /* Hama USB Sightcam 100 */ 
{USB_DEVICE (0x055f, 0xc440)}, /* Mustek DV 3000 */ 
{USB_DEVICE (0x041e, 0x4013)}, /* Creative Pccam750 */ 
{USB_DEVICE (0x060b, 0xa001)}, /* Maxell Compact Pc PM3 */ 
{USB_DEVICE (0x04a5, 0x300a)}, /* Benq DC3410 */ 
{USB_DEVICE (0x04a5, 0x300c)}, /* Benq DC1016 */ 
{USB_DEVICE (0x0461, 0x0815)}, /* Micro Innovation IC200 */
{USB_DEVICE (0x046d, 0x0890)}, /* Logitech QuickCam traveler */
{USB_DEVICE (0x10fd, 0x7e50)}, /* FlyCam Usb 100 */ 
{USB_DEVICE (0x06e1, 0xa190)}, /* ADS Instant VCD */ 
{USB_DEVICE (0x055f, 0xc220)}, /* Gsmart Mini */
{USB_DEVICE (0x0733, 0x2211)}, /* Jenoptik jdc 21 LCD */
{USB_DEVICE (0x046d, 0x0900)}, /* Logitech Inc. ClickSmart 310 */
{USB_DEVICE (0x055f, 0xc360)}, /* Mustek DV4000 Mpeg4 */ 
{USB_DEVICE (0x08ca, 0x2024)}, /* Aiptek DV3500 Mpeg4 */ 
{USB_DEVICE (0x046d, 0x0905)}, /* Logitech ClickSmart820 */ 
{USB_DEVICE (0x05da, 0x1018)}, /* Digital Dream Enigma 1.3 */
{USB_DEVICE (0x0c45, 0x6025)}, /* Xcam Shanga */
{USB_DEVICE (0x0733, 0x1311)}, /* Digital Dream Epsilon 1.3 */ 
{USB_DEVICE (0x041e, 0x401d)}, /* Creative Webcam NX ULTRA */ 
{USB_DEVICE (0x08ca, 0x2016)}, /* Aiptek PocketCam 2 Mega */ 
{USB_DEVICE (0x0734, 0x043b)}, /* 3DeMon USB Capture aka */
{USB_DEVICE (0x041E, 0x4018)}, /* Creative Webcam Vista (PD1100) */
{USB_DEVICE (0x0546, 0x3273)}, /* Polaroid PDC2030*/
{USB_DEVICE (0x041e, 0x401f)}, /* Creative Webcam Notebook PD1171*/ 
{USB_DEVICE (0x041e, 0x4017)}, /* Creative Webcam Mobile PD1090*/ 
{USB_DEVICE (0x046d, 0x08a2)}, /* Labtec Webcam Pro*/
{USB_DEVICE (0x055f, 0xd003)}, /* Mustek WCam300A*/ 
{USB_DEVICE (0x0458, 0x7007)}, /* Genius VideoCam V2*/
{USB_DEVICE (0x0458, 0x700c)}, /* Genius VideoCam V3*/ 
{USB_DEVICE (0x0458, 0x700f)}, /* Genius VideoCam Web V2*/ 
{USB_DEVICE (0x041e, 0x401e)}, /* Creative Nx Pro*/ 
{USB_DEVICE (0x0c45, 0x6029)}, /* [email protected]150 */ 
{USB_DEVICE (0x0c45, 0x6009)}, /* [email protected]120 */
{USB_DEVICE (0x0c45, 0x600d)}, /* [email protected]120 */
{USB_DEVICE (0x04fc, 0x5330)}, /* Digitrex 2110*/ 
{USB_DEVICE (0x055f, 0xc540)}, /* Gsmart D30*/ 
{USB_DEVICE (0x0ac8, 0x301b)}, /* Asam Vimicro*/
{USB_DEVICE (0x041e, 0x403a)}, /* Creative Nx Pro 2*/
{USB_DEVICE (0x055f, 0xc211)}, /* Kowa Bs888e Microcamera*/ 
{USB_DEVICE (0x0ac8, 0x0302)}, /* Z-star Vimicro zc0302*/
{USB_DEVICE (0x0572, 0x0041)}, /* Creative Notebook cx11646*/
{USB_DEVICE (0x08ca, 0x2022)}, /* Aiptek Slim 3200*/ 
{USB_DEVICE (0x046d, 0x0921)}, /* Labtec Webcam */
{USB_DEVICE (0x046d, 0x0920)}, /* QC Express */
{USB_DEVICE (0x0923, 0x010f)}, /* ICM532 cams */ 
{USB_DEVICE (0x055f, 0xc200)}, /* Mustek Gsmart 300 */
{USB_DEVICE (0x0733, 0x2221)}, /* Mercury Digital Pro 3.1p*/ 
{USB_DEVICE (0x041e, 0x4036)}, /* Creative Live ! */ 
{USB_DEVICE (0x055f, 0xc005)}, /* Mustek Wcam300A */ 
{USB_DEVICE (0x041E, 0x403b)}, /* Creative Webcam Vista (VF0010) */ 
{USB_DEVICE (0x0545, 0x8333)}, /* Veo Stingray */ 
{USB_DEVICE (0x0545, 0x808b)}, /* Veo Stingray */ 
{USB_DEVICE (0x10fd, 0x8050)}, /* Typhoon Webshot II USB 300k */ 
{USB_DEVICE (0x0000, 0x0000)}, /* MystFromOri Unknow Camera */

附記:

關於編譯完成的程式檔案是什麼格式?程式是如何執行起來的?傳說中的linux虛擬儲存管理是什麼?它與程式的執行有什麼關係?這幾個問題此處不再詳述,請參考以下幾篇文章中:
《UNIX/LINUX 平臺可執行檔案格式分析—施聰》
《Linux 從虛擬地址到實體地址》
《Linux 虛擬地址空間佈局及程式各個邏輯段詳解》

———未完待續