本文轉載自:http://www.itdadao.com/articles/c15a509940p0.html
在 cortex-a8 中,可接入攝像頭的接口通常可以分為兩種, CAMERA 接口和 USB 接口的攝像頭。這一章主要是介紹 USB 攝像頭的設備驅動程序。在我們印象中,驅動程序都是一個蘿卜一個坑,拿到一個硬件就需要去安裝它相對應的驅動程序。有時候稍有不對還會導致電腦崩潰,是不是讓人很郁悶?這一章我們講 USB 攝像頭設備驅動,那么是不是支持所有的 USB 攝像頭驅動呢?帶着這個疑問開始我們這一章的攝像頭學習之旅吧。
14. 1 確定 USB 攝像頭支持 UVC (在 PC 上)
WEBEE 在某寶上搜索 USB 攝像頭,發現了攝像頭形狀千奇百怪,那到底哪一種適合這一章我們學習呢?攝像頭的市場並不僅僅只是針對我們這些程序猿,很多參數並不會在介紹頁面上寫出來,你去實體店上買,那些賣家很可能也不知道。所以在購買 USB 攝像頭要認准這些參數哦,不然,按照這一章的教材,很可能答不到效果哦,那么可能就需要自己對我們的應用層的測試代碼進行修改哦。
那什么 USB 攝像頭適合我們這一章的教程呢,這里有幾個關鍵字: 1.支持
UVC(免驅), 2.YUV 或者 MJPEG 格式輸出。
在寫這一章教程的時候, WEBEE 手頭剛好有一個 USB 攝像頭,想當年還是買電腦的時候送的,不知道現在能不能用上。那拿到攝像頭,我們需要怎么做呢?
14.1.1 把攝像頭插入 PC 機的 USB 接口,查看 ID
注:如果你是在 Ubuntu 等 linux操作系統下請看 1~2,在 windows 下請直接看看 3 。1. 在 linux 類操作系統下插入 USB 攝像頭,用 dmesg 打印信息
#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* 查看設備節點
這里的 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 驅動教程基本到此結束,后會有期啊,兄弟們。