Linux USB攝像頭驅動【轉】

本文轉載自:http://www.itdadao.com/articles/c15a509940p0.htmlhtml

 

 

在 cortex-a8 中,可接入攝像頭的接口一般能夠分爲兩種, CAMERA 接口和 USB 接口的攝像頭。這一章主要是介紹 USB 攝像頭的設備驅動程序。在咱們印象中,驅動程序都是一個蘿蔔一個坑,拿到一個硬件就須要去安裝它相對應的驅動程序。有時候稍有不對還會致使電腦崩潰,是否是讓人很鬱悶?這一章咱們講 USB 攝像頭設備驅動,那麼是否是支持全部的 USB 攝像頭驅動呢?帶着這個疑問開始咱們這一章的攝像頭學習之旅吧。

14. 1 肯定 USB 攝像頭支持 UVC (在 PC 上) node

WEBEE 在某寶上搜索 USB 攝像頭,發現了攝像頭形狀千奇百怪,那到底哪種適合這一章咱們學習呢?攝像頭的市場並不只僅只是針對咱們這些程序猿,不少參數並不會在介紹頁面上寫出來,你去實體店上買,那些賣家極可能也不知道。因此在購買 USB 攝像頭要認準這些參數哦,否則,按照這一章的教材,極可能答不到效果哦,那麼可能就須要本身對咱們的應用層的測試代碼進行修改哦。linux

那什麼 USB 攝像頭適合咱們這一章的教程呢,這裏有幾個關鍵字: 1.支持
UVC(免驅), 2.YUV 或者 MJPEG 格式輸出。web

在寫這一章教程的時候, WEBEE 手頭恰好有一個 USB 攝像頭,想當年仍是買電腦的時候送的,不知道如今能不能用上。那拿到攝像頭,咱們須要怎麼作呢?

14.1.1 把攝像頭插入 PC 機的 USB 接口,查看 ID算法


注:若是你是在 Ubuntu 等 linux操做系統下請看 1~2,在 windows 下請直接看看 3 。1. 在 linux 類操做系統下插入 USB 攝像頭,用 dmesg 打印信息windows


#dmesg
[ 8705.813109] uvcvideo: Found UVC 1.00 device USB2.0 UVC PC
Camera (174f:5931)
[ 8705.867695] uvcvideo: UVC non compliance - GET_DEF(PROBE) not
supported. Enabling workaround.
[ 8705.886554] uvcvideo: Found UVC 1.00 device USB2.0 Camera
(1e4e:0102)
[ 8705.888801] uvcvideo: UVC non compliance - GET_DEF(PROBE) not
supported. Enabling workaround.
[8705.889829] input: USB2.0 Camera as
/devices/pci0000:00/0000:00:1a.7/usb1/1 -1/1 -1:1.0/input/input12
[ 8705.890440] usbcore: registered new interface driver uvcvideo
[ 8705.890446] USB Video Class driver (1.1.1)
[ 8827.856129] pool[5982]: segfault at 0 ip (null) sp afabc0ec error 14 in
gnome-screenshot[8048000+12000]數組

第一個 UVC 1.00 device USB2.0 UVC PC Camera 是筆記本自帶的攝像頭它的 VID:PID 是 174f:5931 ;第二個 UVC 1.00 device USB2.0 Camera 也就是咱們插入的 USB 攝像頭他的 VID:PID 是 1e4e:0102。這裏的 ID 號能夠在下一步 UVC 官方的文檔中進一步肯定是否被支持。架構

2. 用 ls /dev/video* 查看設備節點


app

這裏的 video0 是筆記本自帶的攝像頭的設備節點, video1 纔是咱們剛接入的 USB 攝像頭。框架

3. 在 windows 操做系統下插入 USB 攝像頭插入,打開設備管理器

第一個 USB2.0 Camera 是咱們接入的 USB 攝像頭,第二個 USB2.0 UVCPC Camera 是筆記本自帶的攝像頭。

右鍵屬性 -> 詳細信息 –> 屬性 選擇硬件 ID 查看

能夠獲得插入的 USB 攝像頭 VID:PID 爲 1e4e: 0102 。 這裏的 ID 號能夠在下一步 UVC 官方的文檔中進一步肯定是否被支持。

14. 1.2 肯定 USB 攝像頭種類

經過這個文檔《攝像頭驅動VID+PID 大全》 來肯定芯片類型,這個文件在附帶的文件夾下;經過這個網頁 http://www.ideasonboard.org/uvc/ 來查看是否支持 UVC,這個網站是 USB Video Class Linux device driver 的主頁,裏面有 UVC 的詳細的介紹。根據前面的打印信息,根據本身的 ID 號, WEBEE 這裏是搜索 USB 攝像頭的 VID 號: 1e4e 和 PID 號: 0102。

經過攝像頭的 ID,能夠看到該攝像頭是否支持 UVC 和其餘信息。綠勾表明支持。



14.1.3 安裝並使用 xawtv 測試 (Ubuntu 下)

1. 安裝 xawtv 測試軟件
#sudo apt-get install xawtv

2. 執行 xawtv 後面帶 usb 攝像頭的設備節點
#xawtv /dev/videoX


獲得圖像, PC 端測試結束。

14. 2 移植到 WEBEE210 開發板

肯定 USB 攝像頭在 PC 上能夠用以後,就須要讓它在咱們的開發板也能用上這個攝像頭。可是接入咱們以前板子上的 USB 接口,發現內核卻沒顯示 PC機上打印的信息。

這是由於 UVC 是 Microsoft 與另外幾家設備廠商聯合推出的爲 USB 視頻捕獲設備定義的協議標準,目前已成爲 USB org 標準之一。現在的主流操做系統(如Windows XP SP2 and later, Linux 2.4.6 and later, MacOS 10.5 and later)都已提供 UVC 設備驅動,所以符合 UVC 規格的硬件設備在不須要安裝任何的驅動程序下便可在主機中正常使用,這也是上面說的免驅的意思。使用 UVC 技術的包括攝像頭、數碼相機、類比影像轉換器、電視棒及靜態影像相機等設備。

可是以前在咱們板子上的內核並無把這個驅動包含進來,因此如今爲了能在板子上運行,有兩種方法, 1.從新配置內核,把 UVC 編進內核,並測試是否能夠用 2.本身從零寫這個驅動。

由於這個 usb 攝像頭涉及到了不少東西,從零寫起來比較複雜,字裏行間很難讓你們理解,因此這裏先用第一種方法實現,在後面的章節會分析內核的這個驅動,你也能夠明白這個驅動的前因後果,再加上你本身的代碼閱讀和悟性,相信你能夠搞懂的。 -.-

注:若是你買的是 webee 配套的攝像頭直接跳到 14.3 

0. 好了,打開咱們的內核目錄

注:這裏的內核是基於移植好 OHCI 主控制器的內核,用以前的內核配置好也是不能用的,由於 usb 主控制器是會被用到的。 請務必先看第 10 章並移植好內核,該實驗須要此基礎上開發。(或者能夠在文件夾下用咱們配置好 ohci 的內核)


#make menuconfig

1. 進入 USB support
Device Drivers --->
   [*] USB support --->

如圖配置:



2. 選中 Multimedia support


Device Drivers --->
      <*> Multimedia support --->

如圖配置:

3. 再進入 Media USB Adapters
Device Drivers --->
    <*> Multimedia support --->
         <*>Media USB Adapters --->

如圖配置


注:若是你不想編譯成模塊,能夠把 UVC 這一項改成*,以後就不用 insmod 了

4. 進入 V4L platform devices


Device Drivers --->

    <*> Multimedia support --->

           <*>V4L platform devices --->

如圖配置



5. 編譯內核


#make uImage

6. 編譯模塊並拷貝下面三個 ko 文件到文件系統下


#make modules
# cp ./drivers/media/v4l2-core/videobuf2-memops.ko /nfs/ko
# cp ./drivers/media/v4l2-core/videobuf2-vmalloc.ko /nfs/ko
#cp ./drivers/media/usb/uvc/uvcvideo.ko /nfs/ko

把生成的./arch/arm/boot/uImage 燒進開發板,從新啓動,進行下一步。

14.3 官方淘寶店上攝像頭的配置


若是你手頭上和 Webee 同樣有不知型號的攝像頭,你能夠按 14.2 節去試一下能不能用。可是若是你沒有的話,強烈建議在咱們官方的淘寶店購買 USB 攝像頭, 也就是上面這一張圖片, 以後按照這一節的教程,你是能夠很順利的完成這一章的實驗。由於 Webee 已經用這個攝像頭實驗過了。並且這個攝像頭還能夠用在接下來第八部分的綜合實驗上哦。

這一節和上一節( 14.2)差很少, 只是在內核配置中添加了適配這款攝像頭的配置而已。 若是你買的不是 webee 的攝像頭, 配置完 14.2 後能夠跳過這一節 …

0. 打開咱們的內核目錄
注:這裏的內核也是基於移植好 OHCI 主控制器的內核,用以前的內核配置好也是不能用的,由於 usb 主控制器是會被用到的。 請務必先看第 10 章並移植好內核,該實驗須要此基礎上開發。(或者能夠在文件夾下用咱們配置好 ohci 的內核)


#make menuconfig

1. 進入 USB support

Device Drivers --->
    [*] USB support --->

如圖配置:


2. 選中 Multimedia support

Device Drivers --->
      <*> Multimedia support --->

如圖配置:


3. 再進入 Media USB Adapters


Device Drivers --->
     <*> Multimedia support --->
           <*>Media USB Adapters --->

如圖配置

注:若是你不想編譯成模塊,能夠把 UVC 這一項改成*,以後就不用 insmod 了


4. 進入 GSPCA base webcams


Device Drivers --->
   <*> Multimedia support --->
        <*>Media USB Adapters --->
            <*>GSPCA base webcams

如圖配置

5. 進入 V4L platform devices


 Device Drivers --->
      <*> Multimedia support --->
            <*>V4L platform devices --->

如圖配置

6. 編譯內核


#make uImage

7. 編譯模塊並拷貝下面三個 ko 文件到文件系統下


#make modules
# cp ./drivers/media/v4l2-core/videobuf2-memops.ko /nfs/ko
# cp ./drivers/media/v4l2-core/videobuf2-vmalloc.ko /nfs/ko
#cp ./drivers/media/usb/uvc/uvcvideo.ko /nfs/ko

把生成的./arch/arm/boot/uImage 燒進開發板,從新啓動,進行下一步。

14. 4 在 WEBEE210 的 LCD 上顯示 USB 攝像頭圖像

1. 重啓開發板,加載模塊。


#cd ./ko
#insmod videobuf2-memops.ko
#insmod videobuf2-vmalloc.ko
#insmod uvcvideo.ko

出現以下信息


2.插入 USB 攝像頭到 webee210 板子上,出現以下信息

ls /dev/video* ,如圖出現 video0


3. 執行 qt 測試程序這文件夾下有兩個 qt 程序: qt_camera_yuv_ts 和 qt_camera_mjpeg_ts。先拷貝生成的 qt_camera_mjpeg_ts 文件到 QT 文件系統下,再執行。


# ./ qt_camera_mjpeg_ts -qws

出現圖像:

這樣,咱們的 usb 攝像頭實驗現象就出來了。

14. 5 QT 測試程序淺析

對於 qt 應用程序,除了作開啓 v4l2 視頻設備的一些初始化工做外,還要注意到一個編碼轉化的問題。 若是是 YUV 輸出的話, 這裏的轉碼是 YUV422 轉RGB888, 若是是 MJPE 的話,則不須要這個函數。

14.5.1 YUV 格式輸出


VideoDevice *vd;
/* 初始化一個 VideoDevice 設備 */
void ProcessImage::paintEvent(QPaintEvent *)
{
/*捕獲圖片*/
rs = vd->get_frame((void **)&p,&len);
/*將 yuv442 轉爲 rgb24 碼*/
convert_yuv_to_rgb_buffer(p,pp,WIDTH,HEIGHT/*QWidget::width(),QWidget::height()*/);
frame->loadFromData((uchar *)pp,/*len*/WIDTH * HEIGHT * 3*sizeof(char));
/*用 label 控件將圖片顯示於 LCD*/
label->setPixmap(QPixmap::fromImage(*frame,Qt::AutoColor));
// label->show();
rs = vd->unget_frame();
// label->drawFrame();
}

這裏要科普一下 YUV 與 RGB 編碼的相關知識。YUV 是編譯 true-color 顏色空間( color space)的種類, Y'UV, YUV, YCbCr,YPbPr 等專有名詞均可以稱爲 YUV,彼此有重疊。「 Y」表示明亮度( Luminance、Luma),「U」和「V」則是色度、濃度( Chrominance、 Chroma), Y'UV, YUV,YCbCr, YPbPr 經常有些混用的狀況,其中 YUV 和 Y'UV 一般用來描述模擬信號,而相反的 YCbCr 與 YPbPr 則是用來描述數位的影像信號,例如在一些壓縮格式內 MPEG、 JPEG 中,但在現今, YUV 一般已經在電腦系統上普遍使用。

RGB 顏色模型或紅綠藍顏色模型,是一種加色模型,將紅( Red)、綠( Green)、藍( Blue)三原色的色光以不一樣的比例相加,以產生多種多樣的色光。RGB24(or RGB888)每像素 24 位(比特 s per pixel, bpp)編碼的 RGB 值:使用三個 8 位無符號整數( 0 到 255)表示紅色、綠色和藍色的強度。這是當前主流的標準表示方法,用於真彩色和 JPEG 或者 TIFF 等圖像文件格式裏的通用顏色交換。它能夠產生一千六百萬種顏色組合,對人眼來講其中不少已經分辨不開。RGB32 模式實際就是 24 比特模式,餘下的 8 比特不分配到象素中,這種模式是爲了提升數據輸送的速度( 32 比特爲一個 DWORD, DWORD 全稱爲 DoubleWord,通常而言一個 Word 爲 16 比特或 2 個字節,處理器可直接對其運算而不需額外的轉換)。一樣在一些特殊狀況下,如 DirectX、 OpenGL 等環境,餘下的8 比特用來表示象素的透明度( Alpha)。

Uvc 攝像頭通常的視頻輸出格式爲 yuv 或 mjpg。 若是你的攝像頭是 YUV 格式輸出,可是咱們的 LCD 顯示屏幕是 RGB24 的顯示模式,因此咱們須要把 YUV格式的圖像轉爲 RGB 格式才能在 LCD 上顯示。

YUV 與 RGB 的轉換有某種對應關係,因此能夠經過算法進行轉換。下面是一種 YUV 轉 RGB 的算法:

int ProcessImage::convert_yuv_to_rgb_buffer(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
unsigned int in, out = 0;
unsigned int pixel_16;
unsigned char pixel_24[3];
unsigned int 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];
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];
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;
}


int ProcessImage::convert_yuv_to_rgb_pixel(int y, int u, int v)
{
unsigned int pixel32 = 0;
unsigned char *pixel = (unsigned char *)&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;
}

這裏的算法用到了浮點,因此運算起來比較慢,因此在 LCD 顯示的時候會有卡頓現象。關於編碼的高效率轉化,有人多人在研究,包括硬解和軟件。想要提供算法的效率,提供刷屏率,能夠經過優化算法實現。

14.5. 2 mjpeg 格式輸出


VideoDevice *vd;
/* 初始化一個 VideoDevice 設備 */
void ProcessImage::paintEvent(QPaintEvent *)
{
/*捕獲圖片*/
rs = vd->get_frame((void **)&p,&len);
/*不用轉碼*/
//convert_yuv_to_rgb_buffer(p,pp,WIDTH,HEIGHT/*QWidget::width(),QWidget::height()*/);
/*直接把捕獲的數據傳遞進去*/
frame->loadFromData((uchar *)p,/*len*/WIDTH * HEIGHT * 3*sizeof(char));
/*用 label 控件將圖片顯示於 LCD*/
label->setPixmap(QPixmap::fromImage(*frame,Qt::AutoColor));
// label->show();
rs = vd->unget_frame();
// label->drawFrame();
}

14. 6 V4L2 架構淺析


14. 6.1 什麼是 V4L2

V4L2 即 Video4Linux2,它是 Linux 內核中關於視頻設備的內核驅動框架,爲上層的訪問底層的視頻設備提供了統一的接口。凡是內核中的子系統都有抽象底層硬件的差別,爲上層提供統一的接口和提取出公共代碼避免代碼冗餘等好處。

V4L2 支持三類設備:視頻輸入輸出設備、 VBI 設備和 radio 設備,分別會在/dev 目錄下產生 videoX、 radioX 和 vbiX 設備節點。咱們常見的視頻輸入設備主要是攝像頭。 V4L2 在 Linux 系統中的架構如圖 14.1 所示:



Linux 系統中視頻輸入設備主要包括如下四個部分:
字符設備驅動程序核心: V4L2 自己就是一個字符設備,具備字符設備全部的特性,暴露接口給用戶空間。
V4L2 驅動核心: 主要是構建一個內核中標準視頻設備驅動的框架,爲視頻操做提供統一的接口函數。
平臺 V4L2 設備驅動: 在 V4L2 框架下,根據平臺自身的特性實現與平臺相關的 V4L2 驅動部分,包括註冊 video_device 和 v4l2_dev。
具體的 sensor 驅動: 主要上電、提供工做時鐘、視頻圖像裁剪、流 IO 開啓等,實現各類設備控制方法供上層調用並註冊 v4l2_subdev。

14. 6.2 定位 USB 攝像頭驅動源碼

在前面的測試中,發現插入 USB 攝像頭後,會打印出下面這些信息:



認真學習的讀者,應該對前面幾行信息至關熟悉,這就是第十章講過的 OHCI驅動和 hub.c 的相關知識,這裏咱們只關心如圖上最後三行信息。。

使用 Source Insight打開內核工程,搜索」 Found UVC」,搜索結果如圖 14.2:

點擊進去,在\drivers\media\usb\uvc\uvc_driver.c 文件的第 1864 行:


uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",

這看上去好像有點熟悉,沒錯,這就是內核輸出信息的倒數第三行打印信息。


uvcvideo: Found UVC 1.00 device USB2.0 Camera (1e4e:0102)

14. 6.3 USB 攝像頭設備驅動(uvc_driver.c) 

前面說過 Webee 使用的攝像頭是符合 UVC(免驅)和 YUV 格式輸出。 這種攝像頭相對沒有那麼複雜,很是適合初學者學習 USB 攝像頭驅動。經過 14.5.2小節的分析知道 uvc_driver.c 這個文件就是咱們的 UVC 攝像頭設備驅動了。

14. 6.3.1 入口函數

仍是老樣子,分析一個驅動,從它的入口函數開始:

static int __init uvc_init(void)
{
int ret;
uvc_debugfs_init();
ret = usb_register(&uvc_driver.driver);
if (ret < 0) {
uvc_debugfs_cleanup();
return ret;
}
printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
return 0;
}

uvc_init()函數主要經過 usb_register(driver)宏來註冊一個 usb_driver,這個宏實際上是調用了 usb_register_driver 函數。這與第十章的 10.4 小節的 USB 鼠標驅動的入口函數作的工做基本一致。

#define usb_register(driver) \
usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

14. 6.3.2 uvc_driver 實例

struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe,
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,
.supports_autosuspend = 1,
},
};
struct uvc_driver {
struct usb_driver driver;
};

當發現內核有與 uvc_ids 匹配的 USB 攝像頭就會調用 uvc_probe 函數


static struct usb_device_id uvc_ids[] = {
/* LogiLink Wireless Webcam */
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0416,
.idProduct = 0xa91a,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX },
……
/* Generic USB Video Class */
{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },
{}
};

uvc_ids 是 usb_device_id 類型的,在 usb 鼠標驅動的章節裏也講解過,具體怎麼匹配,本身回去看看 10.4 小節的內容吧,這裏不重複了,不是重點。

14. 6.3.3 uvc_probe 函數

當內核發現當前插入的 USB 攝像頭被匹配後,最終就會調用 uvc_probe 函數,下面是 uvc_probe 函數的主體,爲了方便分析主幹,把不重要的省略掉。


/* 參考: \drivers\media\usb\uvc\Uvc_driver.c */
static int uvc_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
/* 經過接口獲取 usb_device */
struct usb_device *udev = interface_to_usbdev(intf);
struct uvc_device *dev;
/* 爲 uvc_device 分配內存 */
dev = kzalloc(sizeof *dev, GFP_KERNEL)
/* 初始化各類鎖,初始化 uvc_device 成員 ... */
......
/* 初始化自旋鎖、引用計數等 */
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
goto error;
/* Initialize controls. */
if (uvc_ctrl_init_device(dev) < 0)
goto error;
/* Scan the device for video chains. */
if (uvc_scan_device(dev) < 0)
goto error;
/* Register video device nodes. */
if (uvc_register_chains(dev) < 0)
goto error;
......
error:
uvc_unregister_video(dev);
return -ENODEV;
}

14. 6.3.4 uvc_register_chains 函數


/* 參考: \drivers\media\usb\uvc\Uvc_driver.c */
static int uvc_register_chains(struct uvc_device *dev)
{
struct uvc_video_chain *chain;
int ret;
list_for_each_entry(chain, &dev->chains, list) {
ret = uvc_register_terms(dev, chain);
if (ret < 0)
return ret;
}
......
return 0;
}

uvc_register_chains 函數遍歷全部 chains 對鏈表上的每一個 chain 都調用uvc_register_terms()函數。

14. 6.3.5 uvc_register_terms 函數


/* 參考: \drivers\media\usb\uvc\Uvc_driver.c */
/* Register all video devices in all chains. */
static int uvc_register_terms(struct uvc_device *dev,
struct uvc_video_chain *chain)
{
...
uvc_register_video(dev, stream);
...
return 0;
}

uvc_register_terms()函數調用 uvc_register_video(),它是一個主體函數。

14. 6.3.6 uvc_register_video 函數


static int uvc_register_video(struct uvc_device *dev, struct uvc_streaming *stream)
{
struct video_device *vdev;
/* 初始化流接口 */
uvc_video_init(stream);
/* 分配 video_device 結構體 */
video_device_alloc();
/* 設置 video_device 的 v4l2_device、 v4l2_file_operations 等成員 */
vdev->v4l2_dev = &dev->vdev;
vdev->fops = &uvc_fops; //後面再分析,暫且記住它
vdev->release = uvc_release;
/* 註冊一個 video_devices 結構體 */
video_register_device(vdev, VFL_TYPE_GRABBER, -1);
}
struct video_device *video_device_alloc(void)
{
return kzalloc(sizeof(struct video_device), GFP_KERNEL);
}

uvc_register_video()函數主要作了三件事:

1. 分配 video_device 結構體

2. 初始化 video_device v4l2_file_operations 成員,裏面包含各類函數指針,裏面的 open、 read、 write、 ioctl 就是底層 USB 攝像頭驅動的重點工做。

3. 註冊一個 video_devices 結構體

14. 6.3.7 video_register_device 函數


/* 參考: \include\media\v4l2-dev.h */
static inline int __must_check
video_register_device(struct video_device *vdev,int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}

video_register_device()函數又是經過調用 __video_register_device()函數來實現的,它的實現位於\drivers\media\v4l2-core\v4l2-dev.c。從路徑上能夠看到,這屬於 v4l2 核心的事了。對,沒錯,是 v4l2 核心本分工做。

14. 6.4 V4L2 核心(v4l2-dev.c) 

14. 6.4.1 __video_register_device 函數


/* 參考: \drivers\media\v4l2-core\v4l2-dev.c */
/* __video_register_device:register video4linux devices */
int __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use, struct module *owner)
{
...
const char *name_base;
...
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
case VFL_TYPE_VBI:
name_base = "vbi";
break;
...
}
/* 設置各類 ctrl 屬性,用於用戶程序 ioctl 設置攝像頭屬性 */
if (vdev->ioctl_ops)
determine_valid_ioctls(vdev);
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
/* Part 4: register the device with sysfs */
...
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
...
/* Part 6: Activate this minor. The char device can now be used. */
...
/* 以次設備號爲下標,將 video_device 型 vdev 實例存到
* video_device[]數組裏,以供其餘函數提取,如在 v4l2_open
* 函數經過 video_devdata(filp)提取 video_device
*/
video_device[vdev->minor] = vdev;
}

__video_register_device()函數首先根據 type 來肯定設備節點,如何知道type 是什麼呢?很簡單,還記得 uvc_register_video()函數?裏面就將 type 設置爲 VFL_TYPE_GRABBER 了,因此咱們的設備節點是/dev/vide0%d.

而後設置各類 ctrl 屬性,用於用戶程序 ioctl 設置攝像頭屬性,後面會分析如何調用到這裏設置的 ctrl 屬性。

其次建立了 VIDEO_MAJOR = 81 ,即主設備號爲 81 的字符設備,咱們說過USB 攝像頭驅動其實就是一個字符設備驅動,重點關注 v4l2_fops 結構體。

最後將 video_device 型 vdev 實例存到 video_device[]數組裏, 以供其餘函數提取,如在 v4l2_open 函數經過 video_devdata(filp)提取 video_device。

14. 6.4.2 v4l2_fops 實例


/* 參考: \drivers\media\v4l2-core\v4l2-dev.c */
static const struct file_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
...
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};

v4l2_fops 實例是一個 file_operations 結構體型的,這不,又回到熟悉字符設備驅動的那套架構了嗎?

14. 6.4.3 v4l2_open 函數

當應用程序調用 open 函數,例如: open("/dev/video0",....),首先就會調用到 v4l2 核內心的 open 函數,也就是 v4l2_open 函數。咱們來看看 v4l2_open函數作了什麼工做呢?

static int v4l2_open(struct inode *inode, struct file *filp)
{
struct video_device *vdev;
...
/* 根據次設備號從數組中獲得 video_device */
vdev = video_devdata(filp);
...
/* 若是 vdev->fops->open 存在,而且 video_device 已經註冊,
* 就調用 vdev->fops->open(filp)函數,即調用到底層驅動的 open 方法
*/
if (vdev->fops->open) {
if (video_is_registered(vdev))
ret = vdev->fops->open(filp);
else
ret = -ENODEV;
}
}
/* 將__video_register_device 函數裏設置好的 video_device[]返回 */
struct video_device *video_devdata(struct file *file)
{
return video_device[iminor(file->f_path.dentry->d_inode)];
}

那這個 vdev->fops->open(filp),將調用底層的驅動裏的 open 方法,對於咱們的 USB 攝像頭驅動裏的什麼函數呢?

14. 6.4.4 uvc_v4l2_open 函數

前面的答案就是, vdev->fops->open(filp)至關於調用 uvc_v4l2_open()函數。這個函數的實如今\drivers\media\usb\uvc\uvc_v4l2.c 裏。


/* ------------------------------------------------------------------------
* V4L2 file operations
*/
static int uvc_v4l2_open(struct file *file)
{
struct uvc_streaming *stream;
struct uvc_fh *handle;
int ret = 0;
uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
stream = video_drvdata(file);
if (stream->dev->state & UVC_DEV_DISCONNECTED)
return -ENODEV;
ret = usb_autopm_get_interface(stream->dev->intf);
if (ret < 0)
return ret;
/* Create the device handle. */
handle = kzalloc(sizeof *handle, GFP_KERNEL);
if (handle == NULL) {
usb_autopm_put_interface(stream->dev->intf);
return -ENOMEM;
}
if (atomic_inc_return(&stream->dev->users) == 1) {
ret = uvc_status_start(stream->dev);
if (ret < 0) {
usb_autopm_put_interface(stream->dev->intf);
atomic_dec(&stream->dev->users);
kfree(handle);
return ret;
}
}
v4l2_fh_init(&handle->vfh, stream->vdev);
v4l2_fh_add(&handle->vfh);
handle->chain = stream->chain;
handle->stream = stream;
handle->state = UVC_HANDLE_PASSIVE;
file->private_data = http://blog.csdn.net/Alan445947767/article/details/handle;
return 0;
}

關於 USB 攝像頭驅動裏的 uvc_v4l2_open()函數, webee 就再也不繼續分析下去了,相對比較複雜,咱們的目的是抓 V4L2 的框架,有興趣的讀者本身研究一下唄。

14. 6.4.5 v4l2_read 函數


static ssize_t v4l2_read(struct file *filp, char __user *buf, size_t sz, loff_t *off)
{
struct video_device *vdev = video_devdata(filp);
...
/* 若是底層驅動的 read 方法不存在,則返回錯誤 */
if (!vdev->fops->read)
return -EINVAL;
/* 若是 video_device 已經註冊,則調用到底層驅動的 read 方法 */
if (video_is_registered(vdev))
ret = vdev->fops->read(filp, buf, sz, off);
...
}

vdev->fops->read(filp, buf, sz, off)最後就至關於調用 uvc_v4l2_read()函數,這個函數也是在\drivers\media\usb\uvc\uvc_v4l2.c 文件裏實現,咱們不繼續分析,有興趣的讀者本身研究研究哈。

14. 6.4.6 v4l2_ioctl 函數


static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(filp);
...
if (vdev->fops->unlocked_ioctl) {
...
if (video_is_registered(vdev))
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
}
else if (vdev->fops->ioctl) {
/* Linux3.8 已經將 ioctl 改成 unlocked_ioctl,
* 爲了兼容低版本的內核因此存在 else 分支
*/
...
}
}

同 樣 地 , vdev->fops->unlocked_ioctl(filp, cmd, arg); 最 後 相 當 於 調 用uvc_v4l2_ioctl() 函 數 , 它 又 調 用 video_usercopy(file, cmd, arg,uvc_v4l2_do_ioctl);函數, video_usercopy()函數的做用從名字上能夠猜想,它是根據用戶空間傳遞過來的 cmd 命令,調用 uvc_v4l2_do_ioctl()函數來解析 arg參數。

14. 6.4.7 uvc_v4l2_do_ioctl 函數

uvc_v4l2_do_ioctl()函數是一個很是龐大的函數,有將近 600 行的代碼,這裏,咱們只摘取部分代碼。


static long uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg)
{
struct video_device *vdev = video_devdata(file);
struct uvc_fh *handle = file->private_data;
struct uvc_video_chain *chain = handle->chain;
struct uvc_streaming *stream = handle->stream;
long ret = 0;
switch (cmd) {
/* Query capabilities */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = arg;
memset(cap, 0, sizeof *cap);
strlcpy(cap->driver, "uvcvideo", sizeof cap->driver);
strlcpy(cap->card, vdev->name, sizeof cap->card);
usb_make_path(stream->dev->udev,
cap->bus_info, sizeof(cap->bus_info));
cap->version = LINUX_VERSION_CODE;
cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING | chain->caps;
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
else
cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
break;
}
case VIDIOC_G_PRIORITY:

case VIDIOC_S_PRIORITY:
...
case VIDIOC_QUERYCTRL:

case VIDIOC_G_CTRL:

}

uvc_v4l2_do_ioctl 根據應用程序傳進來的 cmd 命令來判斷要執行哪一個switch 分 支 。 cmd 命 令 有 很 多 種 了 , 這 裏 隨 便 列 舉 幾 種 , 比 如 :VIDIOC_QUERYCAP、 VIDIOC_G_PRIORITY、 VIDIOC_S_PRIORITY 等等。那這些 cmd 是何時被設置的呢?

14. 6.4.8 ctrl 屬性的函數調用流程
  uvc_probe
       uvc_register_chains
           uvc_register_terms
               uvc_register_video
                    video_register_device
                          __video_register_device
                               determine_valid_ioctls

14. 6.4.9 determine_valid_ioctls 函數

這些 ctrl 屬性就是 USB 攝像頭的各類屬性,好比亮度的調節,打開、關閉STREAM 等等操做,這些是 v4l2 核心最最複雜的工做了,沒有之一。


/* 參考: \drivers\media\v4l2-core\v4l2-dev.c */
static void determine_valid_ioctls(struct video_device *vdev)
{
...
SET_VALID_IOCTL(ops, VIDIOC_QUERYCAP, vidioc_querycap);
SET_VALID_IOCTL(ops, VIDIOC_REQBUFS, vidioc_reqbufs);
SET_VALID_IOCTL(ops, VIDIOC_QUERYBUF, vidioc_querybuf);
SET_VALID_IOCTL(ops, VIDIOC_QBUF, vidioc_qbuf);
SET_VALID_IOCTL(ops, VIDIOC_EXPBUF, vidioc_expbuf);
SET_VALID_IOCTL(ops, VIDIOC_DQBUF, vidioc_dqbuf);
SET_VALID_IOCTL(ops, VIDIOC_STREAMON, vidioc_streamon);
SET_VALID_IOCTL(ops, VIDIOC_STREAMOFF, vidioc_streamoff);
...
}


14. 7 本章小結

相信本章是不少讀者最感興趣的一章之一,本章首先動手移植了 USB 攝像頭驅動到 webee210 開發板,成功顯示在 7 寸 LCD 上,有木有高大上的感受啊?其次分析了 qt 測試程序。最重點是 V4L2 架構的分析,主要包括符合 UVC 架構的 USB 攝像頭設備驅動,還有 V4L2 核心的主要核心工做。限於時間和文章的篇幅, V4L2 架構的知識沒有分析的太深,有機會之後再慢慢分析。

到此,網蜂的 Linux 驅動教程基本到此結束,後會有期啊,兄弟們。

相關文章
相關標籤/搜索