1 Video Class 基礎概念
Usb協議中,除了通用的軟硬件電氣接口規范等,還包含了各種各樣的Class協議,用來為不同的功能定義各自的標准接口和具體的總線上的數據交互格式和內容。這些Class協議的數量非常多,最常見的比如支持U盤功能的Mass Storage Class,以及通用的數據交換協議:CDC class。此外還包括Audio Class, Print Class等等。
理論上說,即使沒有這些Class,通過專用驅動也能夠實現各種各樣的應用功能。但是,正如Mass Storage Class的使用,使得各個廠商生產的U盤都能通過操作系統自帶的統一的驅動程序來使用,對U盤的普及使用起了極大的推動作用,制定其它這些Class也是為了同樣的目的。
Video Class 協議的目的是給USB接口的視頻設備提供一個統一的數據交換規范。最初版本是在2003年9月才添加到USB Class規范中的,1.1的版本更是在2005年才發布。相比之下,Mass Storage Class 早在1998年就發布了。支持Video Class協議的多媒體芯片也是在2005年才陸續發布。所以USB 視頻設備目前的現狀是,在設備一端,多數依舊還采用原先的各種包含通用USB功能的多媒體處理芯片,主機端需要安裝專用的驅動程序,基本上各個產品之間不具備兼容性。甚至對於操作系統而言,也只有在XP的SP2以后,才包含了對通用的Video class協議的支持。所以即使是某些多媒體設備(比如Logitech最新的幾款攝像頭)包含了對Video Class的支持,在Win2000等操作系統上依然需要安裝驅動程序。不過,應該說使用Video Class無疑會是一個趨勢,在相應的多媒體芯片陸續投入市場后,支持Video Class的多媒體設備應該會在一兩年內會迅速普及開來。
除了在硬件上通過相應的多媒體芯片支持Video Class的設備以外,對於包含了操作系統的智能手機,當然也可以在手機端通過驅動程序來實現對Video Class的支持,就好像原先支持任何一種專用的USB驅動一樣。只不過數據交換的格式不是自己隨意制訂的,而是按照Video Class的規范來實現的。
由於目前支持Video Class的設備還很少,所以在Linux上還沒有開源的Video Class的主機端驅動,設備端的Video Class驅動就更沒有見到開源的代碼了。本文在介紹USB Video Class架構的基礎上,主要是探討Linux操作系統下設備端Video Class驅動的實現。不過在其它平台下的實現思路應該也是類似的。
Usb協議中,除了通用的軟硬件電氣接口規范等,還包含了各種各樣的Class協議,用來為不同的功能定義各自的標准接口和具體的總線上的數據交互格式和內容。這些Class協議的數量非常多,最常見的比如支持U盤功能的Mass Storage Class,以及通用的數據交換協議:CDC class。此外還包括Audio Class, Print Class等等。
理論上說,即使沒有這些Class,通過專用驅動也能夠實現各種各樣的應用功能。但是,正如Mass Storage Class的使用,使得各個廠商生產的U盤都能通過操作系統自帶的統一的驅動程序來使用,對U盤的普及使用起了極大的推動作用,制定其它這些Class也是為了同樣的目的。
Video Class 協議的目的是給USB接口的視頻設備提供一個統一的數據交換規范。最初版本是在2003年9月才添加到USB Class規范中的,1.1的版本更是在2005年才發布。相比之下,Mass Storage Class 早在1998年就發布了。支持Video Class協議的多媒體芯片也是在2005年才陸續發布。所以USB 視頻設備目前的現狀是,在設備一端,多數依舊還采用原先的各種包含通用USB功能的多媒體處理芯片,主機端需要安裝專用的驅動程序,基本上各個產品之間不具備兼容性。甚至對於操作系統而言,也只有在XP的SP2以后,才包含了對通用的Video class協議的支持。所以即使是某些多媒體設備(比如Logitech最新的幾款攝像頭)包含了對Video Class的支持,在Win2000等操作系統上依然需要安裝驅動程序。不過,應該說使用Video Class無疑會是一個趨勢,在相應的多媒體芯片陸續投入市場后,支持Video Class的多媒體設備應該會在一兩年內會迅速普及開來。
除了在硬件上通過相應的多媒體芯片支持Video Class的設備以外,對於包含了操作系統的智能手機,當然也可以在手機端通過驅動程序來實現對Video Class的支持,就好像原先支持任何一種專用的USB驅動一樣。只不過數據交換的格式不是自己隨意制訂的,而是按照Video Class的規范來實現的。
由於目前支持Video Class的設備還很少,所以在Linux上還沒有開源的Video Class的主機端驅動,設備端的Video Class驅動就更沒有見到開源的代碼了。本文在介紹USB Video Class架構的基礎上,主要是探討Linux操作系統下設備端Video Class驅動的實現。不過在其它平台下的實現思路應該也是類似的。
2 USB Video Class 協議結構
2.1 設備拓撲結構
在拓撲結構上Video Class 將視頻設備抽象為幾個主要的硬件功能模塊:
2.1 設備拓撲結構
在拓撲結構上Video Class 將視頻設備抽象為幾個主要的硬件功能模塊:
輸入端點 Input Terminal
輸出端點 Output Terminal
camera端點 Camera Terminal
選擇單元 Selector Unit
處理單元 Processing Unit
拓展單元 Extension Unit
輸出端點 Output Terminal
camera端點 Camera Terminal
選擇單元 Selector Unit
處理單元 Processing Unit
拓展單元 Extension Unit
下圖是一幅摘自USB_Video_Example 1.1.pdf (
www.usb.org)的拓撲結構示例圖:
圖1 USB Video Camera Topology Example
從sensor和另一個復合視頻設備得到的數據流由IT 和 CT輸入,經SU選擇送PU處理,再由OT綁定到指定的USB端點。最后由USB端點與主機交互將數據發送到host端。在實際設備中,可能沒有其中的某些功能模塊,也可能其中的幾個模塊都是由同一硬件來完成的。
2.2 協議層次結構
上圖中,左半部的框架組成了Video Class中的控制接口界面,右半部的框架組成了視頻流傳輸接口界面。這兩部分構成了Video Class的主要協議框架。
2.2.1 Descriptor Layout
與Class相關的信息,當然是主機端通過向設備端獲取描述符(Descriptor)來得到的, 下圖摘自USB_Video_Class_1.1.pdf , 給出了一個Video Class協議描述符應用示例的Layout。
圖2 Video Camera Descriptor Layout Example
可以看到,在Descriptor Layout中,在標准描述符里,除了Device Descriptor, Configuration Descriptor, Interface Descriptor, Endpoint Descriptor,String Descriptor以外,還有一個USB2.0 協議中后期才新加的IAD 即 Interface Association Descriptor,用來描述多個相關Interface之間的關系,在Video Class中,IAD用來描述VideoControl Interface和VideoStreaming Interface之間的關系。
圖中深色的部分就是Video Class 協議相關的專用描述符(Class Specific Descriptor)了。主要就是對硬件圖像采集和處理模塊的物理拓撲結構和功能的描述,以及對視頻傳輸格式(包括編碼格式,碼率等等視頻圖像相關參數)的描述。
通過從設備處獲得這些描述符,主機可以得知視頻設備端的結構及其所支持的功能。而控制這些功能模塊,對數據源和數據流進行配置,則需要通過Request來完成。
2.3 Request
Request是由主機向設備端發起的功能請求,包括所有USB設備都需要支持的Standard Device Requests 和與Class相關的Class Specific Requests :
2.3.1 Standard Device Requests
下圖列出了USB Spec中規定的標准Request
圖3 Standard Device Requests
這其中,有一部分Request是由USB控制芯片在硬件一級就直接完成的應答工作,比如SET_ADDRESS,有些則需要由軟件來做進一步的處理,比如Set_Configuration。軟硬件的這種任務的划分還與具體的硬件芯片相關。因為這部分是標准協議相關,本文就不詳述。
2.3.2 Class Specific Requests
Class Specific Requests的數據結構Layout與標准Request是一樣的,只是內容不同而已。VideoClass的Class Specific Requests主要根據Interface分為兩類,其下又根據具體功能模塊做進一步的划分:
Ø VideoControl Requests
- Camera Terminal Control Requests
- Selector Unit Control Requests
- Processing Unit Control Requests
- Extension Unit Control Requests
Ø VideoStreaming Requests
- Interface Control Requests
這其中,Interface Control Requests因為是用來在主機和設備之間協商數據交互格式和分辨率,流量等信息的,所以一般來說是必須實現的。
而Camera Terminal Control Requests 和 Processing Unit Control Requests中的內容,則是目前常用的即時通訊軟件如MSN / QQ 等在其視頻控制界面上集成的控制參數。
其中,Camera Terminal Control Requests包含了對曝光時間,曝光模式,對焦,變焦,平移等sensor獲取數據階段時的參數控制。
而Processing Unit Control Requests中則包含了亮度,增益,色調,對比度,白平衡等等sensor在獲取到圖像數據后,圖像處理階段的相關參數。
不過實際上,以上兩者的划分在硬件層次並不是絕對的。很多參數的控制在sensor硬件級別上是同一層次的。不過,將這些功能抽象成這兩類,正如在硬件的拓撲結構上將功能模塊抽象出來一樣,有利於通用化的程序設計
3.1 驅動架構
3.1.1 平台及軟件基礎
本文討論的是USB Video Class在Linux操作系統上的設備端實現。具體是在Omap平台上,基於USB Gadget的驅動架構來實現的。
USB Gadget驅動分為兩層,底層是處理與USB控制芯片硬件相關的內容,對上層屏蔽了大部分硬件相關的設置,並處理了一部分標准Request和EP0相關的標准操作流程。上層則是Class相關的部分,官方的Gadget驅動中,已經包含了File Storage(U盤功能),RNDIS(USB網卡功能)等的支持。考慮到Video Class的數據交換過程與File Storage有很多相似的地方,所以本文在Video Class的實現中,在大的框架上仿照了File Storage驅動的架構。
3.1.2 基本框架和數據流程
在本文實現的Video Class驅動中,整體的框架基本上分為兩大部分。
一部分是負責處理模塊的初始化過程,並負責處理Usb總線上的Descriptor和Requests的交互過程。包括USB總線上的控制和查詢包的接收,解釋,分配和應答。
另一部分,是在初始化過程中啟動的一個獨立的內核線程。負責具體的控制指令的執行和圖像數據的獲取及傳輸工作。這其中的許多操作都有可能引起睡眠,或者需要對文件進行操作,因此必須有一個線程做為依托。
模塊的流程基本上是這樣的:
在init函數中向Gadget底層驅動注冊VideoClass數據結構。所有的描述符都定義為全局結構變量,在模塊初始化過程中,進一步完成對描述符的填充過程,啟動獨立的內核線程,並注冊EP0的complete回調函數。
在啟動的內核線程中打開並初始化camera設備。將camera設置為默認的參數配置。讀取圖像數據並將數據填充到BUF里而后提交Request到VideoStream Interface里的BULK IN端點中。而后睡眠等待由於數據傳送完畢被喚醒或有(異常)Exception發生被喚醒。
如果是數據傳送完畢,則繼續讀取,填充並發送圖像數據,如果有異常發生,則轉而處理異常。
另一方面,哪些情況會引發異常呢?主要是驅動程序與BUS總線交互信息的這部分模塊中,如果發生主機端重新設置Configuration,改變USB設備配置,或者發生總線插拔等引起總線狀態變化的時候,會產生一個相應的異常,等待內核線程被喚醒並處理這些異常。
此外,在處理Requests的時候,有時候需要與camera驅動模塊交互控制信息,同樣需要操作文件句柄,目前的做法是在ep0 request的回調函數(context)中啟動一個bottom half task,在Bottom half中完成相應的控制。
從總體結構上說,這樣做很難看,理想的話應該在上述獨立的內核線程中統一處理與Camera模塊相關的操作,但是目前的架構,要喚醒該線程,只有兩個途徑,一是數據傳輸完畢或被取消,二是有總線狀態變化相關的異常發生。如果硬加一個異常用來處理Requests似乎也很難看,而且對架構需要有較大的調整。所以目前只好簡化的采用了前面所說的方案。
然后關於什么時候打開設備,開始往USB總線上放置數據,目前的處理方式也不是非常理想。目前是在模塊初始化后立即獲取第一幀圖像而后等待主機端讀取,實際上,主機端可能並不馬上安排圖像數據的傳輸。但是如果在主機端需要的時候才去讀取數據的話,因為sensor獲取數據需要一段曝光時間再加上壓縮數據所需的時間,不可能立刻響應,所以在時間上肯定不能滿足開始一段的數據傳輸請求。也需要繼續仔細分析在何時啟動camera模塊最為合適。
3.2 與Camera驅動和V4L2子系統的配合
在linux內核中,2002年12月起發布了V4L2 (Video For Linux Two ) 0.1版本的規范,V4l2試圖為所有和視頻相關的設備都提供統一的接口,這其中當然也就包括了Camera設備。
而USB Video Class這一部分內容恰恰與視頻設備也是密切相關的,所以在某些平台產品的實現中,甚至是在VideoClass中直接包含了Camera的驅動程序。這樣做對於單一產品來說,可以大大簡化驅動的層次結構,應該說是處理Camera的最直接簡潔的辦法。
但是,在本文的實現中,考慮到Linux內核中合理的模塊划分的原則,也是為了符合Gadget驅動的其他Class實現的一貫風格,所以還是盡量使用V4L2的接口來控制Camera模塊。這樣做也有利於代碼的移植。減小不同功能模塊之間的耦合性。
理想的方式自然是所有的與Camera相關的操作都通過V4L2接口來實現,不過目前的實現中還是有些例外,引發例外的主要因素是效率問題。
由於在USB總線上傳輸圖像,受到總線速度的限制,特別是在非USB2.0接口的芯片中,所以勢必要采用JPEG或MPEG編碼對數據進行壓縮。V4L2子系統的框架中,包含了對編碼器的支持,但是,也許是筆者對V4L2子系統的學習還不夠深入,其對編碼器的支持應該是對硬件編碼芯片的支持,即使如此,也沒有見到相關的代碼,而且,在實現中使用的手機主板上也沒有硬件的編解碼芯片,所以在實現中Camera驅動通過V4L2子系統對應用層提供的是原始的未經編碼的數據流,由應用程序調用IJG ( Independent JPEG Group )JPEG庫函數來實現jpeg的編解碼工作。
所以如果通過V4L2的read接口來獲取數據,勢必只能得到原始的圖像數據,而且在V4L2的實現中,通過Read方式獲取的數據,需要通過多次內存拷貝才能到達調用者處。所以也會很大程度的影響圖像處理的速度。
要對圖像進行壓縮,要不在用戶空間調用IJG庫,要不在內核中再實現一個JPEG壓縮算法。按前者來實現的話,涉及到Video Class如何去啟動一個用戶程序來讀取Camera數據並在壓縮后再傳送給內核,也不是完全沒法實現,但是無疑是一個非常糟糕的實現辦法。
后者的話,涉及到這個JPEG壓縮算法應該在什么地方實現,以及由誰來調用的問題(Video Class 還是 V4L2)。考慮到在存在硬件編碼芯片的情況下,該芯片的管理和使用應該會納入V4L2子系統中,所以考慮到兼容性,目前實現的方式是將JPEG壓縮算法作為一個獨立的模塊插入內核,由V4L2子系統調用相關函數對圖像數據進行壓縮,然后再在Camera驅動中Export一個額外的函數接口,USB Video Class通過該函數接口啟動Camera圖像的讀取和壓縮過程,以區別標准的V4L2子系統的數據處理流程。壓縮后的圖像數據直接寫入通過指針參數傳遞進來的內存地址空間中,盡可能的減少內存拷貝的次數,加速圖像的傳遞。
這樣做帶來的問題就是需要在V4L2架構中添加額外的接口,增加了USB Video Class和V4L2子系統之間的耦合性,不能完全將這兩個模塊隔離開來。應該還有更好的解決方案。
3.3 JPEG編碼相關
Jpeg的編解碼,在Linux操作系統中,基本上采用的都是IJG( www.ijg.org)的JPEG庫函數Libjpeg,這是一個相當可靠穩定和高效的開源項目,支持JPEG標准(不包括JPEG2000)的絕大多數編碼方式。如非無奈,確實沒有必要另外再寫一個編碼程序。但是由於需要在內核中使用,所以只好自己再編一個了。
JPEG編碼相關的代碼除了IJG的源代碼以外,在網上還可以搜索到若干,但是無疑IJG的代碼是最完善的。其它我能搜到的代碼,多多少少都有一些BUG,而且也只是實現了JPEG標准的最基本的功能(當然,對於Video Class的應用來說已經是足夠了)。最重要的是,多數是用浮點數運算來實現的,撇開速度不說,在本文的實現中OMAP平台的CPU也不支持浮點數運算。。。所以,本文實現中,最終是參考了網上搜到的某個算法的流程(主要是IJG的架構太復雜,一來沒有時間精力和能力進行完整的分析,二來也不適合在內核中使用如此復雜的架構),在快速離散余弦變化的整數算法上仿照了IJG庫的算法兩者綜合起來完成的。最終的代碼還有很多需要改進的地方,不過,對於VideoClass來說,應該勉強夠用了。這其中的具體問題,打算在另外單獨的文檔中再說明。
3.4 操作系統相關
說操作系統相關,可能說大了一些,這里主要涉及的內容是在本文的實現中,在WIN2000和WINXP平台的MSN測試中,遇到的一些問題。
由於VideoClass的協議只是規定了數據傳輸的格式和內容,對具體實現中的一些細節並沒有作硬性的規定,所以導致有些細節可能存在不兼容的實現方式。(當然,我想主要還是本文的實現,由於能力有限,沒有充分考慮到各種情況下的容錯性,如果驅動做得好應該可以避免出現問題)。所以在WIN2000和WINXP的MSN測試中,遇到了一些平台相關的問題,有些功能在2000下能正常工作在XP下存在Bug,有些卻相反。有些已經解決,有些只是猜測了可能的原因,羅列如下:
3.4.1 視頻窗口關閉再打開后,沒有圖像
開始是在XP的MSN上發現有這樣的問題,2000下沒有,分析BUS數據可以看到,XP在關閉視頻窗口的時候,會執行一個Abort Pipe的操作,這個操作應該會中斷BULK傳輸,但是在設備端,Gadget底層驅動接收不到這個事件(也有可能是Gadget底層驅動的BUG),所以在VideoClass中無從得知這個傳輸已經被取消了,這樣睡眠在等待數據傳送完畢或失敗上的線程也就無法被喚醒,自然也就不會繼續發送數據。造成主機端再度打開視頻窗口時接收不到圖像數據。而在2000下的MSN中,關閉視頻窗口的動作系統不會發送這個Abort Pipe事件,所以也就沒有問題。
考慮到每次打開視頻窗口的時候,主機端都會設置Streaming Interface的圖像分辨率,碼率等參數。而這之后主機端才會讀取圖像數據,所以后來解決的辦法是在主機端設置Streaming Interface的時候,將之前已經放入BULK IN傳輸節點的數據 Dequeue出來,這樣會造成這個傳輸的失敗,從而喚醒睡眠的線程。但是如果僅僅這樣做,XP能夠正常工作了,2000又顯示不了圖像了。分析認為由於部分數據丟失,所以造成第一幀圖像的數據是不完整的,無法正常解壓縮顯示,但是XP下的MSN有較好的容錯性,能夠丟棄這一幀圖像,繼續讀取之后的數據,而2000下的MSN容錯能力較差,無法再正常解讀后面的圖像數據。所以最終的解決辦法是在發現傳輸失敗后,將當前這一幀的圖像數據從頭開始重新發送,這樣在XP和2000下就都能正常工作了。
不知道這種解決方案是否僅僅是一種治標的方案,有待以后繼續研究。
3.4.2 某些分辨率下圖像無法正常顯示
在Win2000中如果提供160*120分辨率的圖像,圖像非常容易就停止刷新了,而BUS上實際數據還是在發送的。而在160*112(兩者都是16的整倍數)的分辨率的情況下,就幾乎不會發生這種情況。如果說這有可能還是JPEG的壓縮算法有點問題,那另外一種情況就一定是XP 和 2000的區別了:如果設備這端通過描述符和Streaming Interface申明只能支持160*120 或者 160*112 的分辨率,2000可以接受這種分辨率,而XP根本就不能接受,總線上的控制傳輸就停止了,在界面上則顯示檢測不到Camera或Camera正在被其它設備打開占用,只有在進一步提供更高的320*240的分辨率的情況下,XP才會承認Camera的存在!其它問題倒不大,就是在本文的實現平台上,受軟件編碼JPEG速度的限制,320*240的分辨率下,視頻的幀頻會低一些,影響圖像的流暢性。
3.5 其它
3.5.1 特殊效果的控制
應該說,VideoClass的Control Request基本上涵蓋了V4L2標准界面提供的大部分控制參數,但是,還是有一部分沒有涵蓋,至於特定驅動專有的控制就更無法體現了,尤其是在MSN等應用程序的界面上,更不可能提供這些參數的控制了。但是,我們還是可以想辦法trick過這個問題。
比如手機上常見的圖像效果的設定,雖然不是特別有意義,但是既然是很常見的,為什么不能把它也做到Web Cam中呢?所以,如果一定要做,我們可以利用MSN控制界面上的原有的控制界面,借用其中一兩個控制參數來實現圖像效果的設定。
本文的實現中選擇采用色調來控制圖像效果,因為實際上這個參數是很不常用的,甚至只能在XP的高級設定中找到,對於99.9%的用戶我相信都不會去改變這個參數。而它的字面含義與我們實現的功能也不算一點關系都沒有,畢竟有很多效果實際上就是改變一下圖像的顏色(當然還有一部分例外了)。
類似的可以用一些我們認為常用的設置替換既有的參數。這樣做的缺點就是控制參數的字面含義與實際功能不太吻合,優點當然就是可以提供給用戶更多更常用的圖像設置。比如設置一個黑白,素描之類的圖像的效果,玩玩抽象派視頻聊天。
圖1 USB Video Camera Topology Example
從sensor和另一個復合視頻設備得到的數據流由IT 和 CT輸入,經SU選擇送PU處理,再由OT綁定到指定的USB端點。最后由USB端點與主機交互將數據發送到host端。在實際設備中,可能沒有其中的某些功能模塊,也可能其中的幾個模塊都是由同一硬件來完成的。
2.2 協議層次結構
上圖中,左半部的框架組成了Video Class中的控制接口界面,右半部的框架組成了視頻流傳輸接口界面。這兩部分構成了Video Class的主要協議框架。
2.2.1 Descriptor Layout
與Class相關的信息,當然是主機端通過向設備端獲取描述符(Descriptor)來得到的, 下圖摘自USB_Video_Class_1.1.pdf , 給出了一個Video Class協議描述符應用示例的Layout。
圖2 Video Camera Descriptor Layout Example
可以看到,在Descriptor Layout中,在標准描述符里,除了Device Descriptor, Configuration Descriptor, Interface Descriptor, Endpoint Descriptor,String Descriptor以外,還有一個USB2.0 協議中后期才新加的IAD 即 Interface Association Descriptor,用來描述多個相關Interface之間的關系,在Video Class中,IAD用來描述VideoControl Interface和VideoStreaming Interface之間的關系。
圖中深色的部分就是Video Class 協議相關的專用描述符(Class Specific Descriptor)了。主要就是對硬件圖像采集和處理模塊的物理拓撲結構和功能的描述,以及對視頻傳輸格式(包括編碼格式,碼率等等視頻圖像相關參數)的描述。
通過從設備處獲得這些描述符,主機可以得知視頻設備端的結構及其所支持的功能。而控制這些功能模塊,對數據源和數據流進行配置,則需要通過Request來完成。
2.3 Request
Request是由主機向設備端發起的功能請求,包括所有USB設備都需要支持的Standard Device Requests 和與Class相關的Class Specific Requests :
2.3.1 Standard Device Requests
下圖列出了USB Spec中規定的標准Request
圖3 Standard Device Requests
這其中,有一部分Request是由USB控制芯片在硬件一級就直接完成的應答工作,比如SET_ADDRESS,有些則需要由軟件來做進一步的處理,比如Set_Configuration。軟硬件的這種任務的划分還與具體的硬件芯片相關。因為這部分是標准協議相關,本文就不詳述。
2.3.2 Class Specific Requests
Class Specific Requests的數據結構Layout與標准Request是一樣的,只是內容不同而已。VideoClass的Class Specific Requests主要根據Interface分為兩類,其下又根據具體功能模塊做進一步的划分:
Ø VideoControl Requests
- Camera Terminal Control Requests
- Selector Unit Control Requests
- Processing Unit Control Requests
- Extension Unit Control Requests
Ø VideoStreaming Requests
- Interface Control Requests
這其中,Interface Control Requests因為是用來在主機和設備之間協商數據交互格式和分辨率,流量等信息的,所以一般來說是必須實現的。
而Camera Terminal Control Requests 和 Processing Unit Control Requests中的內容,則是目前常用的即時通訊軟件如MSN / QQ 等在其視頻控制界面上集成的控制參數。
其中,Camera Terminal Control Requests包含了對曝光時間,曝光模式,對焦,變焦,平移等sensor獲取數據階段時的參數控制。
而Processing Unit Control Requests中則包含了亮度,增益,色調,對比度,白平衡等等sensor在獲取到圖像數據后,圖像處理階段的相關參數。
不過實際上,以上兩者的划分在硬件層次並不是絕對的。很多參數的控制在sensor硬件級別上是同一層次的。不過,將這些功能抽象成這兩類,正如在硬件的拓撲結構上將功能模塊抽象出來一樣,有利於通用化的程序設計
3.1 驅動架構
3.1.1 平台及軟件基礎
本文討論的是USB Video Class在Linux操作系統上的設備端實現。具體是在Omap平台上,基於USB Gadget的驅動架構來實現的。
USB Gadget驅動分為兩層,底層是處理與USB控制芯片硬件相關的內容,對上層屏蔽了大部分硬件相關的設置,並處理了一部分標准Request和EP0相關的標准操作流程。上層則是Class相關的部分,官方的Gadget驅動中,已經包含了File Storage(U盤功能),RNDIS(USB網卡功能)等的支持。考慮到Video Class的數據交換過程與File Storage有很多相似的地方,所以本文在Video Class的實現中,在大的框架上仿照了File Storage驅動的架構。
3.1.2 基本框架和數據流程
在本文實現的Video Class驅動中,整體的框架基本上分為兩大部分。
一部分是負責處理模塊的初始化過程,並負責處理Usb總線上的Descriptor和Requests的交互過程。包括USB總線上的控制和查詢包的接收,解釋,分配和應答。
另一部分,是在初始化過程中啟動的一個獨立的內核線程。負責具體的控制指令的執行和圖像數據的獲取及傳輸工作。這其中的許多操作都有可能引起睡眠,或者需要對文件進行操作,因此必須有一個線程做為依托。
模塊的流程基本上是這樣的:
在init函數中向Gadget底層驅動注冊VideoClass數據結構。所有的描述符都定義為全局結構變量,在模塊初始化過程中,進一步完成對描述符的填充過程,啟動獨立的內核線程,並注冊EP0的complete回調函數。
在啟動的內核線程中打開並初始化camera設備。將camera設置為默認的參數配置。讀取圖像數據並將數據填充到BUF里而后提交Request到VideoStream Interface里的BULK IN端點中。而后睡眠等待由於數據傳送完畢被喚醒或有(異常)Exception發生被喚醒。
如果是數據傳送完畢,則繼續讀取,填充並發送圖像數據,如果有異常發生,則轉而處理異常。
另一方面,哪些情況會引發異常呢?主要是驅動程序與BUS總線交互信息的這部分模塊中,如果發生主機端重新設置Configuration,改變USB設備配置,或者發生總線插拔等引起總線狀態變化的時候,會產生一個相應的異常,等待內核線程被喚醒並處理這些異常。
此外,在處理Requests的時候,有時候需要與camera驅動模塊交互控制信息,同樣需要操作文件句柄,目前的做法是在ep0 request的回調函數(context)中啟動一個bottom half task,在Bottom half中完成相應的控制。
從總體結構上說,這樣做很難看,理想的話應該在上述獨立的內核線程中統一處理與Camera模塊相關的操作,但是目前的架構,要喚醒該線程,只有兩個途徑,一是數據傳輸完畢或被取消,二是有總線狀態變化相關的異常發生。如果硬加一個異常用來處理Requests似乎也很難看,而且對架構需要有較大的調整。所以目前只好簡化的采用了前面所說的方案。
然后關於什么時候打開設備,開始往USB總線上放置數據,目前的處理方式也不是非常理想。目前是在模塊初始化后立即獲取第一幀圖像而后等待主機端讀取,實際上,主機端可能並不馬上安排圖像數據的傳輸。但是如果在主機端需要的時候才去讀取數據的話,因為sensor獲取數據需要一段曝光時間再加上壓縮數據所需的時間,不可能立刻響應,所以在時間上肯定不能滿足開始一段的數據傳輸請求。也需要繼續仔細分析在何時啟動camera模塊最為合適。
3.2 與Camera驅動和V4L2子系統的配合
在linux內核中,2002年12月起發布了V4L2 (Video For Linux Two ) 0.1版本的規范,V4l2試圖為所有和視頻相關的設備都提供統一的接口,這其中當然也就包括了Camera設備。
而USB Video Class這一部分內容恰恰與視頻設備也是密切相關的,所以在某些平台產品的實現中,甚至是在VideoClass中直接包含了Camera的驅動程序。這樣做對於單一產品來說,可以大大簡化驅動的層次結構,應該說是處理Camera的最直接簡潔的辦法。
但是,在本文的實現中,考慮到Linux內核中合理的模塊划分的原則,也是為了符合Gadget驅動的其他Class實現的一貫風格,所以還是盡量使用V4L2的接口來控制Camera模塊。這樣做也有利於代碼的移植。減小不同功能模塊之間的耦合性。
理想的方式自然是所有的與Camera相關的操作都通過V4L2接口來實現,不過目前的實現中還是有些例外,引發例外的主要因素是效率問題。
由於在USB總線上傳輸圖像,受到總線速度的限制,特別是在非USB2.0接口的芯片中,所以勢必要采用JPEG或MPEG編碼對數據進行壓縮。V4L2子系統的框架中,包含了對編碼器的支持,但是,也許是筆者對V4L2子系統的學習還不夠深入,其對編碼器的支持應該是對硬件編碼芯片的支持,即使如此,也沒有見到相關的代碼,而且,在實現中使用的手機主板上也沒有硬件的編解碼芯片,所以在實現中Camera驅動通過V4L2子系統對應用層提供的是原始的未經編碼的數據流,由應用程序調用IJG ( Independent JPEG Group )JPEG庫函數來實現jpeg的編解碼工作。
所以如果通過V4L2的read接口來獲取數據,勢必只能得到原始的圖像數據,而且在V4L2的實現中,通過Read方式獲取的數據,需要通過多次內存拷貝才能到達調用者處。所以也會很大程度的影響圖像處理的速度。
要對圖像進行壓縮,要不在用戶空間調用IJG庫,要不在內核中再實現一個JPEG壓縮算法。按前者來實現的話,涉及到Video Class如何去啟動一個用戶程序來讀取Camera數據並在壓縮后再傳送給內核,也不是完全沒法實現,但是無疑是一個非常糟糕的實現辦法。
后者的話,涉及到這個JPEG壓縮算法應該在什么地方實現,以及由誰來調用的問題(Video Class 還是 V4L2)。考慮到在存在硬件編碼芯片的情況下,該芯片的管理和使用應該會納入V4L2子系統中,所以考慮到兼容性,目前實現的方式是將JPEG壓縮算法作為一個獨立的模塊插入內核,由V4L2子系統調用相關函數對圖像數據進行壓縮,然后再在Camera驅動中Export一個額外的函數接口,USB Video Class通過該函數接口啟動Camera圖像的讀取和壓縮過程,以區別標准的V4L2子系統的數據處理流程。壓縮后的圖像數據直接寫入通過指針參數傳遞進來的內存地址空間中,盡可能的減少內存拷貝的次數,加速圖像的傳遞。
這樣做帶來的問題就是需要在V4L2架構中添加額外的接口,增加了USB Video Class和V4L2子系統之間的耦合性,不能完全將這兩個模塊隔離開來。應該還有更好的解決方案。
3.3 JPEG編碼相關
Jpeg的編解碼,在Linux操作系統中,基本上采用的都是IJG( www.ijg.org)的JPEG庫函數Libjpeg,這是一個相當可靠穩定和高效的開源項目,支持JPEG標准(不包括JPEG2000)的絕大多數編碼方式。如非無奈,確實沒有必要另外再寫一個編碼程序。但是由於需要在內核中使用,所以只好自己再編一個了。
JPEG編碼相關的代碼除了IJG的源代碼以外,在網上還可以搜索到若干,但是無疑IJG的代碼是最完善的。其它我能搜到的代碼,多多少少都有一些BUG,而且也只是實現了JPEG標准的最基本的功能(當然,對於Video Class的應用來說已經是足夠了)。最重要的是,多數是用浮點數運算來實現的,撇開速度不說,在本文的實現中OMAP平台的CPU也不支持浮點數運算。。。所以,本文實現中,最終是參考了網上搜到的某個算法的流程(主要是IJG的架構太復雜,一來沒有時間精力和能力進行完整的分析,二來也不適合在內核中使用如此復雜的架構),在快速離散余弦變化的整數算法上仿照了IJG庫的算法兩者綜合起來完成的。最終的代碼還有很多需要改進的地方,不過,對於VideoClass來說,應該勉強夠用了。這其中的具體問題,打算在另外單獨的文檔中再說明。
3.4 操作系統相關
說操作系統相關,可能說大了一些,這里主要涉及的內容是在本文的實現中,在WIN2000和WINXP平台的MSN測試中,遇到的一些問題。
由於VideoClass的協議只是規定了數據傳輸的格式和內容,對具體實現中的一些細節並沒有作硬性的規定,所以導致有些細節可能存在不兼容的實現方式。(當然,我想主要還是本文的實現,由於能力有限,沒有充分考慮到各種情況下的容錯性,如果驅動做得好應該可以避免出現問題)。所以在WIN2000和WINXP的MSN測試中,遇到了一些平台相關的問題,有些功能在2000下能正常工作在XP下存在Bug,有些卻相反。有些已經解決,有些只是猜測了可能的原因,羅列如下:
3.4.1 視頻窗口關閉再打開后,沒有圖像
開始是在XP的MSN上發現有這樣的問題,2000下沒有,分析BUS數據可以看到,XP在關閉視頻窗口的時候,會執行一個Abort Pipe的操作,這個操作應該會中斷BULK傳輸,但是在設備端,Gadget底層驅動接收不到這個事件(也有可能是Gadget底層驅動的BUG),所以在VideoClass中無從得知這個傳輸已經被取消了,這樣睡眠在等待數據傳送完畢或失敗上的線程也就無法被喚醒,自然也就不會繼續發送數據。造成主機端再度打開視頻窗口時接收不到圖像數據。而在2000下的MSN中,關閉視頻窗口的動作系統不會發送這個Abort Pipe事件,所以也就沒有問題。
考慮到每次打開視頻窗口的時候,主機端都會設置Streaming Interface的圖像分辨率,碼率等參數。而這之后主機端才會讀取圖像數據,所以后來解決的辦法是在主機端設置Streaming Interface的時候,將之前已經放入BULK IN傳輸節點的數據 Dequeue出來,這樣會造成這個傳輸的失敗,從而喚醒睡眠的線程。但是如果僅僅這樣做,XP能夠正常工作了,2000又顯示不了圖像了。分析認為由於部分數據丟失,所以造成第一幀圖像的數據是不完整的,無法正常解壓縮顯示,但是XP下的MSN有較好的容錯性,能夠丟棄這一幀圖像,繼續讀取之后的數據,而2000下的MSN容錯能力較差,無法再正常解讀后面的圖像數據。所以最終的解決辦法是在發現傳輸失敗后,將當前這一幀的圖像數據從頭開始重新發送,這樣在XP和2000下就都能正常工作了。
不知道這種解決方案是否僅僅是一種治標的方案,有待以后繼續研究。
3.4.2 某些分辨率下圖像無法正常顯示
在Win2000中如果提供160*120分辨率的圖像,圖像非常容易就停止刷新了,而BUS上實際數據還是在發送的。而在160*112(兩者都是16的整倍數)的分辨率的情況下,就幾乎不會發生這種情況。如果說這有可能還是JPEG的壓縮算法有點問題,那另外一種情況就一定是XP 和 2000的區別了:如果設備這端通過描述符和Streaming Interface申明只能支持160*120 或者 160*112 的分辨率,2000可以接受這種分辨率,而XP根本就不能接受,總線上的控制傳輸就停止了,在界面上則顯示檢測不到Camera或Camera正在被其它設備打開占用,只有在進一步提供更高的320*240的分辨率的情況下,XP才會承認Camera的存在!其它問題倒不大,就是在本文的實現平台上,受軟件編碼JPEG速度的限制,320*240的分辨率下,視頻的幀頻會低一些,影響圖像的流暢性。
3.5 其它
3.5.1 特殊效果的控制
應該說,VideoClass的Control Request基本上涵蓋了V4L2標准界面提供的大部分控制參數,但是,還是有一部分沒有涵蓋,至於特定驅動專有的控制就更無法體現了,尤其是在MSN等應用程序的界面上,更不可能提供這些參數的控制了。但是,我們還是可以想辦法trick過這個問題。
比如手機上常見的圖像效果的設定,雖然不是特別有意義,但是既然是很常見的,為什么不能把它也做到Web Cam中呢?所以,如果一定要做,我們可以利用MSN控制界面上的原有的控制界面,借用其中一兩個控制參數來實現圖像效果的設定。
本文的實現中選擇采用色調來控制圖像效果,因為實際上這個參數是很不常用的,甚至只能在XP的高級設定中找到,對於99.9%的用戶我相信都不會去改變這個參數。而它的字面含義與我們實現的功能也不算一點關系都沒有,畢竟有很多效果實際上就是改變一下圖像的顏色(當然還有一部分例外了)。
類似的可以用一些我們認為常用的設置替換既有的參數。這樣做的缺點就是控制參數的字面含義與實際功能不太吻合,優點當然就是可以提供給用戶更多更常用的圖像設置。比如設置一個黑白,素描之類的圖像的效果,玩玩抽象派視頻聊天。