fanxiushu 2016-10-08 轉載或引用,請註明原始作者

做這個事情寫這篇文章之前,壓根沒朝模擬USB攝像頭這方面去想過。

直到CSDN上一位朋友提出問題,才想到還有這麼一個玩意。
因此花了4,5天時間,利用自己之前開發的USB驅動,實現了一個虛擬USB攝像頭的例項程式碼。
稍後會公佈到CSDN上。

記得最早的一篇文章也是介紹虛擬攝像頭驅動的開發的,只是當時採用的是windows的流核心實現的,
windows實現視訊驅動(包括攝像頭,音效卡等等多媒體相關的)本身就是採用流核心作為微埠框架來實現的。
因此很容易想到使用AVStream流核心框架來實現虛擬攝像頭,
事實上,USB介面的攝像頭,在經過底層的USB匯流排層,上升到驅動的功能層,
依然需要利用AVStream微埠實現攝像頭的功能。

而USB介面的攝像頭,如果它的USB介面傳輸的協議是符合UVC(USB Video Class)標準的,
則windows會自動載入自己的usbvideo驅動,而無需再額外開發驅動,這就是大家所說的是免驅的。
不光是USB攝像頭,因為USB的通用性和普遍性,很多很多的USB介面裝置,包括USB鍵盤,U盤, USB滑鼠,USB音效卡等等,
這些USB裝置,他們的USB通訊協議都符合某些通用的標準的,因此當把這些裝置插入電腦,系統都會載入通用的驅動,
而無需再額外開發自己的驅動程式。
這就是所謂的免驅,其實並不是不要驅動,而是這些驅動已經作為系統核心一部分整合進去了。

開發USB虛擬匯流排驅動的目的正如第一章開頭所述,只是為了把真實的USB介面的硬體裝置“延長”到其他機器,
http://blog.csdn.net/fanxiushu/article/details/51420096(USB裝置驅動開發之遠端訪問USB裝置(一USB裝置資料採集端))
對於浩繁的各種通用性以USB協議為基礎的協議,不可能全部研究,也沒這個精力。

當然,為了演示如何呼叫先前開發的USB驅動程式碼的介面,
以及完善在攝像頭這塊開發中的一些空缺,
而且稍微看了下UVC協議部分,USBLyzer抓包分析了下UVC協議,相對來說並不複雜,
因此花了幾天時間完成這麼一個功能,希望對正在做這些方面的朋友有些幫助,
有興趣的朋友可利用我的USB驅動作為基礎,開發模擬出一些其他符合通用協議”裝置“, 比如模擬USB音效卡,USB滑鼠等。
對於不熟悉windows驅動的朋友也可以使用,只要熟悉相關的USB協議,全部都能在應用層實現。
相關程式碼請到CSDN下載。

首先研究UVC協議,檢視官方的UVC文件,全是英文的,可是英文對我不太友好,
中文版的挺少見,估計是既熟悉英語,又精通相關技術的人較少,因此沒翻譯的。
(不過有時看翻譯的比看原文還累,還不如干脆看原文)。
於是是通過 USBLyzer抓包軟體,抓包分析了一個普通USB攝像頭的通訊資料包,
看它的互動無非就是裝置,配置,介面等描述符,然後就是 CLASS資訊,最後就是大量的視訊傳輸的 ISO Transfer。
這樣把通訊過程分成3個部分。
一,模擬USB描述符資料,
二,模擬CLASS資訊資料,
三,ISO同步傳輸實現視訊資料的傳輸。

如果只看官方的UVC文件,你將摸不著門,還好有現成的,隨便拿個攝像頭,用USBLyzer或者類似的USB抓包軟體抓包分析,
就知道傳輸了些什麼,然後再結合UVC文件,看看每個包對應欄位的解釋,這就叫實踐出真知。
而且還有個最大的程式碼級別的幫手,那就是開源的linux核心程式碼。
這些通用協議,linux核心都有對應的實現程式碼,在這裡對我們來說尤其有用的就是linux對UVC資料包的各種結構宣告,
具體位置在 include\uapi\linux\usb\video.h中,(linux3.8原始碼),直接copy過來使用即可。

在填充USB描述符的時候,主要是三個,一個是裝置描述符,一個是字串描述符,一個是配置描述符。
前兩個沒什麼好說的,最麻煩的是第三個:配置描述符。
但是,一般來說,只要隨便仿照一個通用攝像頭的配置資料填充虛擬資料即可,至少我是這麼幹的。
配置描述符中必須有一個 control interface和至少一個 stream interface。但是一般就配置兩個介面就可以了,
一個 控制介面,一個視訊流介面,至於endpoint,為了簡單,把控制介面的interrupt埠去掉了,
只留下視訊流介面的一個iso埠,用於傳輸視訊資料。
配置描述符的資料結構有點多,具體都填寫了哪些,可檢視CSDN上的原始碼,其實都是仿照某個真實攝像頭的資料填充的。

接下來的就是CLASS資訊,CLASS資訊其實對應的windows的USB請求的 URB_FUNCTION_CLASS_INTERFACE 命令,
UVC採用這個命令來發送各種命令給攝像頭硬體控制介面或者視訊流介面,達到控制攝像頭行為的目的。
UVC描述得這類命令包括
GET_CUR,GET_MIN,GET_MAX,GET_RES,GET_LEN,GET_INFO,GET_DEF, SET_CUR等
有些命令是發給控制介面,有些命令是發給視訊介面的,有些是兩個介面都發送。
這些命令如何詳細使用,實話說我也不大清楚,有興趣的同學可仔細研究UVC文件。
不過為了簡單,就只實現stream interface 的四個命令就足夠讓攝像頭運行了。
包括 GET_MIN, GET_MAX, GET_CUR, SET_CUR。
因此程式碼中只實現了這四個命令,若你對UVC協議非常熟悉,或者遇到某些軟體需要其他命令支援,可進行修改。

最後就是視訊資料的傳輸了,
模擬的配置描述符中,只配置了一個埠,而且型別是ISO同步傳輸,
雖然UVC文件說可以使用同步傳輸或者批量傳輸來作為攝像頭的資料傳輸通道,
但是我所見到攝像頭的清一色的都是 ISO同步傳輸的,所以這裡採用同步傳輸。
UVC規定每個視訊資料都得有個12或者2個位元組的頭來描述這個視訊資料包資訊,
程式碼採用的是2個位元組的頭來描述資料包,
接著就是如何界定資料包的範圍了,一開始我的理解是一個 URB_FUNCTION_ISOCH_TRANSFER 請求就是一個數據包。
結果總是得不到視訊資料,
後來仔細研究了linux程式碼中相關部分的實現,原來是 URB_FUNCTION_ISOCH_TRANSFER 傳輸的每個 PACKET才是一個包,
每個PACKET都得加個2個位元組或者12個位元組的視訊頭。

如此之後,攝像頭就能工作了。
總得來說,只要具備了前面的虛擬USB匯流排驅動基礎,後邊的UVC相關的東西並不複雜,就是稍微繁瑣點。

釋出到CSDN上的程式碼,提供的模擬攝像頭介面如下:

struct frame_t
{
    char* buffer;
    int   length;
    int   width;
    int   height;
    int   delay_msec; ///停留時間
    ////
    void* param;  ///
};

typedef int (*FRAME_CALLBACK)(frame_t* frame);

struct uvc_vcam_t
{
    int pid;
    int vid;
    const char* manu_fact;
    const char* product;
    FRAME_CALLBACK  frame_callback; ///
    void* param;
};

//// function
void* vcam_create(uvc_vcam_t* uvc);

void vcam_destroy(void* handle);

非常簡單,只需要在安裝USB虛擬匯流排驅動之後,就可正常執行。
程式碼介面只有一個 frame_callback回撥函式,用來在獲取每個視訊幀的時候,填寫每幀的視訊資料(採用YUY2格式),
就能讓這個虛擬攝像頭工作起來。

程式碼作為例子,並沒提供實際的資料來源,只是在main.cpp程式碼中簡單的動態模擬了一段文字大小不斷變化。
你若有實際需要,可自行擴充套件功能,
但是慎重申明,請勿使用本程式碼作為基礎開發出具有欺騙性功能的攝像頭從事欺詐活動。

CSDN上程式碼地址:
http://download.csdn.net/detail/fanxiushu/9648010

在win10下的安裝和執行效果圖:
圖片