些朋友還分不清USB有2.0和3.0之分,下面我來和大家分享一下。
首先,從外觀來說,USB的接口是不同色的。
USB2.0 的接口是黑色的。
USB3.0的接口是藍色的
第二,從傳輸速來看
USB2.0的傳輸速度是480Mbps,理論的速度是60MB/S
USB3.0的傳輸速度是5GB,也就是600MB/S,是USB3.0的10倍
第三,看金屬接觸片,也就是USB接口處的針腳
一般來說,USB 2.0只用一排4個金屬接觸片的針腳
USB 3.0是兩排,上面5個金屬接觸片的針腳,下面4個金屬接觸片的針腳
一共9個針腳。
第四 看標識
USB 2.0 和 3.0的圖標差別如下圖所示
好了,希望我今天的分享能幫到你。
堅持寫作分享不易,希望給我點個關注,贊!
媒體 嗶哩嗶哩技術
2024年09月03日 12:02 上海
一、背景
PC直播姬中的直播素材之一——投屏源可與安卓或iOS移動端應用(直播姬、粉版)配合使用,將移動端畫面投射到PC直播姬中。投屏源最初僅支持無線投屏,即通過局域網 WiFi傳輸,但這樣的鏈路會受到網絡質量影響,而且如果Windows計算機和移動設備不在同一網段或者配置了局域網隔離,那么就無法投屏成功。
無線投屏的這些缺點,使用USB有線投屏即可克服。本文基于Windows平臺,介紹計算機與安卓/iOS通過USB交換數據的實現方式。
二、預備知識
在講述具體的內容之前,有必要先了解一下USB設備相關的一些基礎知識。USB(通用串行總線)是一種用于設備與設備之間互連的串行總線標準 [1]。其總線拓撲如下圖所示:
圖2-1 USB總線拓撲
圖2-1中,集線器和功能設備統稱為USB設備(為簡單起見,下文將使用“USB設備”來稱呼功能設備)。一個USB系統中只有一個主機,主機通過主機控制器(Host Controller)來與根集線器交互。
每個USB設備都被賦予了兩個數字標識:
在Windows平臺上,USB設備和設備驅動之間通過Vendor ID和Product ID關聯。新安裝的USB設備驅動會取代已安裝的具有相同Vendor ID和Product ID的驅動(如果有的話)。在使用有線投屏時,就需要安裝對應于用戶使用的移動設備USB驅動,來取代其原有的設備驅動。這一點在下文將詳細敘述。
三、iOS有線投屏
首先介紹iOS有線投屏,整個投屏鏈路組成如圖3-1所示。下文將分別講述Windows端和iOS端的實現細節。其中,Windows計算機上的接收端軟件作為連接的發起方,iOS設備上的應用程序作為接受方。
圖3-1 iOS有線投屏鏈路組成
3.1 Windows端實現
相比安卓,iOS有線投屏Windows端的實現較為簡便。其核心是libimobiledevice開源庫(LGPL2.1協議) [2]以及蘋果的iTunes桌面端軟件附帶的AppleMobileDeviceSupport服務程序(下文簡稱蘋果服務) [3]。
3.1.1 蘋果服務
需要在Windows計算機上安裝該程序,該程序將會創建一個Windows服務,名稱為Apple Mobile Device Service,負責與蘋果驅動通信。該服務是libimobiledevice與iOS設備通信的橋梁。
iTunes也使用該服務程序,因此如果已經安裝了iTunes桌面端,則無需再安裝服務程序。
蘋果服務安裝成功后,外部程序即可使用libimobiledevice與通過USB線纜連接到Windows計算機的iOS設備通信了。
3.1.2 枚舉/連接設備
在第一次連接iOS設備時,Windows計算機會為其安裝合適的驅動程序。設備管理器中會顯示詳細的設備信息。
圖3-2 iOS設備連接后設備管理器中的情況
正常情況下,連接iOS設備(這里使用了iPad),驅動安裝完成后,設備管理器中相關的設備節點如圖3-2所示。其中:
如果因為種種原因,導致這些驅動被其他具有相同Vendor ID和Product ID的驅動替換掉的話,投屏鏈路就無法建立。此時,iTunes也是處于無法和設備通信的狀態。出現這種問題時,只能手動卸載掉這些驅動,然后重新連接設備,讓Windows計算機重新安裝官方驅動。
設備連接邏輯本身較為簡單,如下圖所示:
3.1.3 數據傳輸
投屏的數據鏈路建立之后,Windows計算機上的接收端程序即可調用API:idevice_connection_send() 和idevice_connection_receive_timeout() 來發送和接收數據。收發數據的調用是同步的。
如果在數據傳輸過程中斷開USB電氣連接,當前或后續的讀寫調用會立即返回錯誤。在此之后需要重新枚舉設備。穩定連接速度實測為 35MiB/s 左右,使用USB2.0或者USB3.0接口,速度沒有太大的差別。
通信時數據包使用如下頭部:
struct TransferFrame {
uint32_t version;
uint32_t type;
uint32_t tag;
uint32_t payload_size;
uint32_t identifier;
};
首先進行握手,握手數據包使用上述結構,沒有載荷,流程見圖3-3。握手成功之后iOS移動端將音視頻數據編碼后封裝為FLV流,數據包加上上述頭部后發送給Windows設備上的接收端程序,接收端將數據中的有效載荷送入解封裝/解碼器,將結果合并進入直播場景中。
圖3-3 iOS有線投屏接收端邏輯流程圖
圖3-4 iOS有線投屏連接后載荷傳遞時序圖
3.2 iOS端實現
iOS端的首要任務就是通過ReplayKit采集屏幕、麥克風和設備音頻。隨后使用Audio/VideoToolBox將音視頻數據編碼,并封裝為FLV。最后通過基于usbmux協議的設備間通信,將FLV數據發給Windows計算機上的接收端程序。
3.2.1 屏幕錄制
得益于蘋果對iOS錄屏框架replaykit的不斷完善,在iOS 12時提供了穩定的API, 可以實現對iOS設備屏幕、麥克風和設備播放音頻的錄制。主要流程如下:
1)創建Broadcast Upload Extension,Xcode會新增Extension工程,選擇Target運行。如圖3-5所示:
圖3-5 iOS錄制工程
2)通過RPSystemBroadcastPickerView啟動屏幕錄制頁面,或者設置→控制中心→添加屏幕錄制,在控制面板長按屏幕錄制按鈕啟動頁面:
圖3-6 iOS屏幕錄制頁面
3)創建Broadcast Upload Extension后,會自動生成sampleHander類,當啟動屏幕錄制后,會觸發processSampleBuffer:withType方法,回調實時采集的音視頻數據:
圖3-7 iOS錄制回調
3.2.2 設備間通信簡介
usbmux是蘋果的私有協議,蘋果設計該協議的原因是為了自家的macOS APP能夠和iDevice進行通信,從而實現諸如iTunes備份iPhone、Xcode真機調試等功能。該協議提供了一種類似TCP socket的API,使得macOS和iOS設備之間的通信,如同是網絡上的兩個主機之間的通信。
Windows計算機則可以安裝AppleMobileDeviceSupport服務, 與iOS設備之間建立通信。在第一次連接iOS設備時,Windows計算機會為其安裝合適的驅動程序,設備管理器中會顯示詳細的設備信息,可參見圖3-2。
3.2.3 建立連接
基于usbmux協議,iOS端啟用應用后,創建Socket,監聽協商的端口號,啟動TCP Server。PC端則充當Client的角色,根據協商的端口號,調用libimobiledevice庫連接iOS設備。
由于iOS啟動屏幕錄制后的Extension是與宿主APP相獨立的一個進程,Extension與宿主APP的數據交互,同樣的可以基于TCP Client/Server的機制,Extension內創建TCP Client連接宿主APP的TCP Server,進行數據的傳輸。傳輸數據所使用的結構參見3.1.4小節。
3.2.4 錄制FLV數據封裝
通過Replay Extension采集到屏幕的視頻幀畫面,其分辨率與設備屏幕尺寸等比例,可經過渲染對視頻幀畫面進行處理,如:裁剪,縮放,再使用VideoToolBox進行H.264編碼,得到編碼后的數據包。
采集的音頻數據包含麥克風的聲音和設備播放的音頻,可以音頻數據進行混音操作,合成單路音頻流,然后使用AudioToolBox進行AAC編碼。最后將編碼后的音視頻數據封裝成FLV Packet。Packet經由TCP Server中轉,發送至Windows計算機上的接收端程序。
圖3-8 iOS錄制數據封裝流程
以上就是iOS有線投屏的全部內容。接下來是安卓有線投屏。
四、安卓有線投屏
安卓有線投屏目前存在兩類方案:ADB方案和配件方案。使用ADB方案進行數據透傳需要打開手機“開發者選項”中的“USB調試”功能,對于普通用戶而言不是一個好選擇,操作復雜且存在安全風險,不適合用于線上場景;配件方案對于絕大多數機型來說都不需要開啟USB調試,且不需要額外權限,但需要啟動App。本文選擇配件方案。
表4-1 安卓有線投屏技術選型
安卓有線投屏的鏈路組成如圖4-1所示。下文將分別講述Windows計算機和安卓端的實現細節。其中,Windows計算機上的接收端軟件作為連接的發起方,iOS設備上的應用程序作為接受方。
圖4-1 安卓有線投屏鏈路組成
4.1 Windows端實現
安卓有線投屏Windows端的實現稍顯復雜。數據鏈路使用libusb開源庫(LGPL2.1協議) [5]和libusbK驅動 [6]建立。連接時首先定位到目標設備,然后按照谷歌的安卓開放配件協議(AOA)1.0 [7]使iOS設備進入配件模式。
4.1.1 枚舉設備
雖然libusb提供了枚舉設備的接口,但其提供的信息較少,即使是要獲取設備名,也需要先打開設備。而很少有設備可以在不安裝libusb相關驅動(比如libusbK)的情況下被libusb打開。于是這里直接調用Windows計算機提供的SetupAPI來獲取設備信息。libusb在Windows平臺上也是通過SetupAPI來獲取USB設備信息的,但可能是出于跨平臺考慮,許多信息沒有通過接口暴露出來,而是供其內部使用。
使用SetupAPI可以拿到很多信息,例如設備描述、設備實例ID、設備類、父類、子類等等。即使有這些信息,要精確地僅枚舉安卓設備仍很困難,目前采用的方法是枚舉WPD(Windows Portable Devices)設備。該類設備在設備管理器中被列為“便攜設備”。
圖4-2 Windows設備管理器中的便攜設備
雖然U盤或移動硬盤這種存儲設備也被列為便攜設備,但這些設備的設備實例路徑前綴并非USB,通過SetupAPI就可以很容易地過濾。蘋果設備可以通過Vendor ID過濾,蘋果公司的Vendor ID是固定的(0x05AC)。
目前的過濾方案可以過濾大部分的非安卓設備,但可能仍有一些設備會逃過過濾。這些設備并非安卓,并且存在Windows計算機可訪問的存儲空間,但又不是U盤、移動硬盤這種純粹的存儲設備,比較有可能的是數碼相機。在下一節可以看到,我們需要為選擇的設備安裝libusbK驅動,然后才能和設備通信,如果選擇了錯誤的設備,安裝的驅動會把原有驅動替換掉,在此之后使用當前的PC是無法訪問設備的存儲空間的。要再次訪問設備存儲,需要卸載libusbK驅動,并重新連接USB線纜。
此外,可能一些設備已經在之前建立過連接,我們已經為其裝過驅動,這些設備也應該列入候選列表中。在下一節可以看到,我們安裝的驅動有特殊的類GUID,可以很容易地過濾。
實際代碼中,首先使用Windows設備管理API:SetupDiGetClassDevsW() 篩選設備,主要參數填寫如下:
調用成功后,即可使用SetupDiEnumDeviceInfo() 進行實際的枚舉操作。我們感興趣的信息是設備實例ID,可唯一標識連接到系統的某個USB設備。該ID可通過SetupDiGetDeviceInstanceIdW() 獲得,ID字符串形如USB\VID_1234&PID_5678…。雖然微軟文檔不建議對該字符串進行解析,但沒有其他更合適的方法拿Vendor ID 和Product ID了。
除此之外,還需要調用SetupDiGetDevicePropertyW() 獲取以下信息:
還有一點需要說明的是,為了后續libusb能夠正常工作,我們需要確保獲取到的設備是具有同一Vendor ID 和Product ID的父根設備。可使用Windows API:CM_Get_Parent() 和CM_Get_Device_IDW() 不斷向上遍歷,直到再向上Vendor ID 和Product ID不同的時候再停止,此時的設備就是我們需要的設備。新版libusb似乎已經可以支持子設備,但目前并未驗證。
4.1.2 連接設備
當我們選定了要連接的設備,同時就得到了設備的Vendor ID 和Product ID。除此之外還有設備實例ID,讓我們能夠區分連接到同一臺PC的多臺同一型號的安卓設備,這些設備具有相同的Vendor ID和Product ID,但Windows計算機為這些設備分配的設備實例ID是不同的。
由于我們后續的邏輯基于libusb,因此需要使用上一步拿到的信息獲取libusb的設備對象。先使用libusb_get_device_list() 獲取USB設備列表進行遍歷,然后使用libusb_get_device_descriptor() 獲取設備的描述信息,和上面拿到的信息進行比對,來找到目標設備。
詳細的連接邏輯如下圖所示:
圖4-3 安卓有線投屏接收端發起連接邏輯
連接流程可以概括為:
上述流程中,第一次安裝的驅動需要和想要連接的設備的Vendor ID和Product ID對應。這個驅動將會在選擇設備后,由一個事先準備好的驅動模板填入Vendor ID和Product ID生成。驅動模板使用libusbK附帶的驅動開發包 (UsbK Development Kit) 中的驅動安裝包創建向導 (Driver Install Creator Wizard) 基于任一USB設備生成的驅動安裝包修改而成。該流程對于第二次安裝的驅動來說也是一樣的。
驅動安裝包文件中的二進制文件可以直接使用,而且驅動文件已經打過簽名。INF文件是我們修改的目標,里面記錄了對應設備的Vendor ID和Product ID,顯示名稱、制造商名稱、類GUID等等。類GUID可以改為我們獨有的GUID,以便于在設備枚舉階段篩選出已經裝過驅動的設備。
INF文件修改完成后,執行同一目錄中的dpinst64.exe文件即可安裝驅動。需要注意的是,該可執行文件需要管理員權限,執行前最好進行文件校驗,以防篡改。
接下來說明配件模式相關的數據。在步驟2中,發送的指令可攜帶以下信息:
表4-2 安卓配件信息
發送時序如圖4-4所示:
圖4-4 安卓有線投屏切換配件模式時序圖
首先使用libusb_open() 打開設備,并使用libusb_claim_interface() 打開讀寫接口,然后就可以使用libusb_control_transfer() 發送和接收數據了,具體指令格式可參閱谷歌AOAv1文檔,這些信息用于匹配安卓端的應用程序。切換完成之后,需要重新枚舉設備,找到固定ID:0x18D1和0x2D00的設備,使用libusb_open() 打開設備,并使用libusb_claim_interface() 打開讀寫接口進行后續數據通信。
如果安卓端安裝有對應的應用程序,轉換為配件模式后,安卓端會彈出如圖4-5左側的系統消息;如果沒有對應的應用,則安卓端會彈出如圖4-5右側的系統消息。圖中紅框標出的部分對應“配件描述”。
圖4-5 安卓配件提示
安卓端用于處理配件模式的應用需要做出聲明,參見4.2.2小節。
如果安裝有對應的應用,點擊“確定”按鈕將拉起該應用;如果安裝有多個對應的應用,則會彈出應用選擇彈窗,選擇其中一個應用后會將其拉起。在該應用中使用安卓Framework層提供的USBManager可打開對應的附件,得到文件描述符,可對該文件描述符進行讀寫操作。到此通信鏈路即建立完成。
4.1.3 數據傳輸
投屏的數據鏈路建立之后,Windows計算機可以調用libusb API:libusb_bulk_transfer() 來進行設備通信。建議設置1秒超時,以防止出現無限等待的情況。
如果在數據傳輸過程中斷開USB電氣連接,當前或后續的讀寫調用會立即返回錯誤。在此之后需要重新枚舉設備。穩定連接速度實測為 30MiB/s 左右。使用USB2.0或者USB3.0接口,速度沒有太大的差別。
通信時數據包使用的頭部與iOS有線投屏相同。握手和后續的接收數據流程也與iOS有線投屏基本相同,參考流程圖3-3即可。
4.1.4 驅動卸載
在前面的連接步驟中,我們為選定的設備安裝了libusbK驅動,這一過程會替換掉設備原本的驅動,換言之,設備將失去原本驅動所能提供的功能。例如:Windows計算機訪問安卓的文件系統,或者Windows計算機通過安卓連接網絡。因此,有必要提供卸載功能,使得用戶在不使用投屏時,仍可以使用原驅動的功能。
驅動卸載分為以下兩步:
1)移除設備
使用Windows API:SetupDiGetClassDevsW(),傳入我們定義的設備類GUID,以及枚舉器 ”USB”。然后使用SetupDiEnumDeviceInfo() 進行枚舉,保存枚舉到的設備的以下信息:
然后調用SetupDiCallClassInstaller(),傳入DIF_REMOVE進行設備移除。
2)卸載驅動軟件
以上一步獲得的INF文件名,調用SetupUninstallOEMInfW(),刪除驅動。
4.2 安卓端實現
在安卓端,對音視頻編碼后,通過UsbManager獲取配件文件描述符,進行讀寫即可完成數據傳輸至另一端。其中,應用層程序通過安卓Framework層獲得UsbManager代理對象,經過授權后拿到FileDescriptor,此時Framework 層會通過UsbDeviceManager打開 "/dev/usb_accessory" 并獲取必要的描述信息,以供附件匹配,在確認匹配和授權后,應用層即可進行IO操作。
4.2.1 設備模式說明
安卓從3.1版本開始支持USB配件和主機兩種模式,兩種模式的示意見圖4-6。安卓設備作為USB主機時,可連接U盤等USB設備,并由安卓設備提供電力;而在配件模式時,配件作為USB主機連接至安卓設備,并為安卓設備供電。在有線投屏場景中,Windows計算機就是“配件”。
圖4-6 安卓設備USB主機/配件模式(圖片來源:Google官網)
4.2.2 啟用配件
如4.1.2小節所述,若一個安卓應用想要用于配件模式通信,它聲明的信息就必須與配件提供的信息對應。具體來說,安卓應用需要:
4.2.3 連接配件
當有配件連接安卓設備,并使安卓設備進入配件模式,且安卓設備上安裝有匹配配件信息的安卓應用時,應用的響應流程如圖4-7所示:
圖4-7 安卓設備對配件模式的響應
4.2.4 錄制FLV數據封裝
數據輸出流程包括采集、編碼和封裝三個主要階段,最終封裝為FLV格式的數據。投屏中使用MediaProjection進行屏幕采集,AudioRecord進行麥克風采集。通過MediaCodec對視頻和音頻分別進行編碼,生成AVC視頻流和AAC音頻流。
設備連接時,通過UsbManager申請UsbAccessory操作權限,并獲取ParcelFileDescriptor以實現IO輸出。隨后,我們將AVC和AAC數據隊列按時序交替封裝成FLV Packet,形成連續的數據流,并通過此IO輸出傳輸至Windows端。
五、總結
搭建iOS有線投屏鏈路需要:
搭建安卓有線投屏鏈路需要:
相對無線局域網來說,有線投屏連接更為穩定,僅需一根質量尚可的USB數據線纜,即使是USB2.0的帶寬也可滿足目前要求。但其也有缺點:數據鏈路的搭建邏輯較為復雜;需要安裝額外的USB驅動程序;有線連接本身可能就會造成一些阻礙。但對于需要穩定投屏,或者電腦和手機無法通過局域網連接的用戶來說,有線投屏可能是唯一的選擇。
參考
[1] | USB-IF, "Universal Serial Bus 2.0 Specification," 2000. |
[2] | libimobiledevice, "libimobiledevice," [Online]. Available: https://libimobiledevice.org. |
[3] | Apple, "iTunes - Apple," [Online]. Available: https://www.apple.com/itunes. |
[4] | Microsoft, "Windows Portable Devices," [Online]. Available: https://learn.microsoft.com/en-us/windows/win32/windows-portable-devices. |
[5] | libusb, "Windows," [Online]. Available: https://github.com/libusb/libusb/wiki/Windows#How_to_use_libusb_on_Windows. |
[6] | T. L. Robinson, "libusbK," [Online]. Available: https://sourceforge.net/projects/libusbk. |
[7] | Google, "Android 開放配件協議 1.0," [Online]. Available: https://source.android.com/docs/core/interaction/accessories/aoa?hl=zh-cn. |
-End-
作者丨ucclkp、大雞排、青藤
、背景
PC直播姬中的直播素材之一——投屏源可與安卓或iOS移動端應用(直播姬、粉版)配合使用,將移動端畫面投射到PC直播姬中。投屏源最初僅支持無線投屏,即通過局域網 WiFi傳輸,但這樣的鏈路會受到網絡質量影響,而且如果Windows計算機和移動設備不在同一網段或者配置了局域網隔離,那么就無法投屏成功。
無線投屏的這些缺點,使用USB有線投屏即可克服。本文基于Windows平臺,介紹計算機與安卓/iOS通過USB交換數據的實現方式。
二、預備知識
在講述具體的內容之前,有必要先了解一下USB設備相關的一些基礎知識。USB(通用串行總線)是一種用于設備與設備之間互連的串行總線標準 [1]。其總線拓撲如下圖所示:
圖2-1 USB總線拓撲
圖2-1中,集線器和功能設備統稱為USB設備(為簡單起見,下文將使用“USB設備”來稱呼功能設備)。一個USB系統中只有一個主機,主機通過主機控制器(Host Controller)來與根集線器交互。
每個USB設備都被賦予了兩個數字標識:
在Windows平臺上,USB設備和設備驅動之間通過Vendor ID和Product ID關聯。新安裝的USB設備驅動會取代已安裝的具有相同Vendor ID和Product ID的驅動(如果有的話)。在使用有線投屏時,就需要安裝對應于用戶使用的移動設備USB驅動,來取代其原有的設備驅動。這一點在下文將詳細敘述。
三、iOS有線投屏
首先介紹iOS有線投屏,整個投屏鏈路組成如圖3-1所示。下文將分別講述Windows端和iOS端的實現細節。其中,Windows計算機上的接收端軟件作為連接的發起方,iOS設備上的應用程序作為接受方。
圖3-1 iOS有線投屏鏈路組成
3.1 Windows端實現
相比安卓,iOS有線投屏Windows端的實現較為簡便。其核心是libimobiledevice開源庫(LGPL2.1協議) [2]以及蘋果的iTunes桌面端軟件附帶的AppleMobileDeviceSupport服務程序(下文簡稱蘋果服務) [3]。
3.1.1 蘋果服務
需要在Windows計算機上安裝該程序,該程序將會創建一個Windows服務,名稱為Apple Mobile Device Service,負責與蘋果驅動通信。該服務是libimobiledevice與iOS設備通信的橋梁。
iTunes也使用該服務程序,因此如果已經安裝了iTunes桌面端,則無需再安裝服務程序。
蘋果服務安裝成功后,外部程序即可使用libimobiledevice與通過USB線纜連接到Windows計算機的iOS設備通信了。
3.1.2 枚舉/連接設備
在第一次連接iOS設備時,Windows計算機會為其安裝合適的驅動程序。設備管理器中會顯示詳細的設備信息。
圖3-2 iOS設備連接后設備管理器中的情況
正常情況下,連接iOS設備(這里使用了iPad),驅動安裝完成后,設備管理器中相關的設備節點如圖3-2所示。其中:
如果因為種種原因,導致這些驅動被其他具有相同Vendor ID和Product ID的驅動替換掉的話,投屏鏈路就無法建立。此時,iTunes也是處于無法和設備通信的狀態。出現這種問題時,只能手動卸載掉這些驅動,然后重新連接設備,讓Windows計算機重新安裝官方驅動。
設備連接邏輯本身較為簡單,如下圖所示:
3.1.3 數據傳輸
投屏的數據鏈路建立之后,Windows計算機上的接收端程序即可調用API:idevice_connection_send() 和idevice_connection_receive_timeout() 來發送和接收數據。收發數據的調用是同步的。
如果在數據傳輸過程中斷開USB電氣連接,當前或后續的讀寫調用會立即返回錯誤。在此之后需要重新枚舉設備。穩定連接速度實測為 35MiB/s 左右,使用USB2.0或者USB3.0接口,速度沒有太大的差別。
通信時數據包使用如下頭部:
struct TransferFrame {
uint32_t version;
uint32_t type;
uint32_t tag;
uint32_t payload_size;
uint32_t identifier;
};
首先進行握手,握手數據包使用上述結構,沒有載荷,流程見圖3-3。握手成功之后iOS移動端將音視頻數據編碼后封裝為FLV流,數據包加上上述頭部后發送給Windows設備上的接收端程序,接收端將數據中的有效載荷送入解封裝/解碼器,將結果合并進入直播場景中。
圖3-3 iOS有線投屏接收端邏輯流程圖
圖3-4 iOS有線投屏連接后載荷傳遞時序圖
3.2 iOS端實現
iOS端的首要任務就是通過ReplayKit采集屏幕、麥克風和設備音頻。隨后使用Audio/VideoToolBox將音視頻數據編碼,并封裝為FLV。最后通過基于usbmux協議的設備間通信,將FLV數據發給Windows計算機上的接收端程序。
3.2.1 屏幕錄制
得益于蘋果對iOS錄屏框架replaykit的不斷完善,在iOS 12時提供了穩定的API, 可以實現對iOS設備屏幕、麥克風和設備播放音頻的錄制。主要流程如下:
1)創建Broadcast Upload Extension,Xcode會新增Extension工程,選擇Target運行。如圖3-5所示:
圖3-5 iOS錄制工程
2)通過RPSystemBroadcastPickerView啟動屏幕錄制頁面,或者設置→控制中心→添加屏幕錄制,在控制面板長按屏幕錄制按鈕啟動頁面:
圖3-6 iOS屏幕錄制頁面
3)創建Broadcast Upload Extension后,會自動生成sampleHander類,當啟動屏幕錄制后,會觸發processSampleBuffer:withType方法,回調實時采集的音視頻數據:
圖3-7 iOS錄制回調
3.2.2 設備間通信簡介
usbmux是蘋果的私有協議,蘋果設計該協議的原因是為了自家的macOS APP能夠和iDevice進行通信,從而實現諸如iTunes備份iPhone、Xcode真機調試等功能。該協議提供了一種類似TCP socket的API,使得macOS和iOS設備之間的通信,如同是網絡上的兩個主機之間的通信。
Windows計算機則可以安裝AppleMobileDeviceSupport服務, 與iOS設備之間建立通信。在第一次連接iOS設備時,Windows計算機會為其安裝合適的驅動程序,設備管理器中會顯示詳細的設備信息,可參見圖3-2。
3.2.3 建立連接
基于usbmux協議,iOS端啟用應用后,創建Socket,監聽協商的端口號,啟動TCP Server。PC端則充當Client的角色,根據協商的端口號,調用libimobiledevice庫連接iOS設備。
由于iOS啟動屏幕錄制后的Extension是與宿主APP相獨立的一個進程,Extension與宿主APP的數據交互,同樣的可以基于TCP Client/Server的機制,Extension內創建TCP Client連接宿主APP的TCP Server,進行數據的傳輸。傳輸數據所使用的結構參見3.1.4小節。
3.2.4 錄制FLV數據封裝
通過Replay Extension采集到屏幕的視頻幀畫面,其分辨率與設備屏幕尺寸等比例,可經過渲染對視頻幀畫面進行處理,如:裁剪,縮放,再使用VideoToolBox進行H.264編碼,得到編碼后的數據包。
采集的音頻數據包含麥克風的聲音和設備播放的音頻,可以音頻數據進行混音操作,合成單路音頻流,然后使用AudioToolBox進行AAC編碼。最后將編碼后的音視頻數據封裝成FLV Packet。Packet經由TCP Server中轉,發送至Windows計算機上的接收端程序。
圖3-8 iOS錄制數據封裝流程
以上就是iOS有線投屏的全部內容。接下來是安卓有線投屏。
四、安卓有線投屏
安卓有線投屏目前存在兩類方案:ADB方案和配件方案。使用ADB方案進行數據透傳需要打開手機“開發者選項”中的“USB調試”功能,對于普通用戶而言不是一個好選擇,操作復雜且存在安全風險,不適合用于線上場景;配件方案對于絕大多數機型來說都不需要開啟USB調試,且不需要額外權限,但需要啟動App。本文選擇配件方案。
表4-1 安卓有線投屏技術選型
安卓有線投屏的鏈路組成如圖4-1所示。下文將分別講述Windows計算機和安卓端的實現細節。其中,Windows計算機上的接收端軟件作為連接的發起方,iOS設備上的應用程序作為接受方。
圖4-1 安卓有線投屏鏈路組成
4.1 Windows端實現
安卓有線投屏Windows端的實現稍顯復雜。數據鏈路使用libusb開源庫(LGPL2.1協議) [5]和libusbK驅動 [6]建立。連接時首先定位到目標設備,然后按照谷歌的安卓開放配件協議(AOA)1.0 [7]使iOS設備進入配件模式。
4.1.1 枚舉設備
雖然libusb提供了枚舉設備的接口,但其提供的信息較少,即使是要獲取設備名,也需要先打開設備。而很少有設備可以在不安裝libusb相關驅動(比如libusbK)的情況下被libusb打開。于是這里直接調用Windows計算機提供的SetupAPI來獲取設備信息。libusb在Windows平臺上也是通過SetupAPI來獲取USB設備信息的,但可能是出于跨平臺考慮,許多信息沒有通過接口暴露出來,而是供其內部使用。
使用SetupAPI可以拿到很多信息,例如設備描述、設備實例ID、設備類、父類、子類等等。即使有這些信息,要精確地僅枚舉安卓設備仍很困難,目前采用的方法是枚舉WPD(Windows Portable Devices)設備。該類設備在設備管理器中被列為“便攜設備”。
圖4-2 Windows設備管理器中的便攜設備
雖然U盤或移動硬盤這種存儲設備也被列為便攜設備,但這些設備的設備實例路徑前綴并非USB,通過SetupAPI就可以很容易地過濾。蘋果設備可以通過Vendor ID過濾,蘋果公司的Vendor ID是固定的(0x05AC)。
目前的過濾方案可以過濾大部分的非安卓設備,但可能仍有一些設備會逃過過濾。這些設備并非安卓,并且存在Windows計算機可訪問的存儲空間,但又不是U盤、移動硬盤這種純粹的存儲設備,比較有可能的是數碼相機。在下一節可以看到,我們需要為選擇的設備安裝libusbK驅動,然后才能和設備通信,如果選擇了錯誤的設備,安裝的驅動會把原有驅動替換掉,在此之后使用當前的PC是無法訪問設備的存儲空間的。要再次訪問設備存儲,需要卸載libusbK驅動,并重新連接USB線纜。
此外,可能一些設備已經在之前建立過連接,我們已經為其裝過驅動,這些設備也應該列入候選列表中。在下一節可以看到,我們安裝的驅動有特殊的類GUID,可以很容易地過濾。
實際代碼中,首先使用Windows設備管理API:SetupDiGetClassDevsW() 篩選設備,主要參數填寫如下:
調用成功后,即可使用SetupDiEnumDeviceInfo() 進行實際的枚舉操作。我們感興趣的信息是設備實例ID,可唯一標識連接到系統的某個USB設備。該ID可通過SetupDiGetDeviceInstanceIdW() 獲得,ID字符串形如USB\VID_1234&PID_5678…。雖然微軟文檔不建議對該字符串進行解析,但沒有其他更合適的方法拿Vendor ID 和Product ID了。
除此之外,還需要調用SetupDiGetDevicePropertyW() 獲取以下信息:
還有一點需要說明的是,為了后續libusb能夠正常工作,我們需要確保獲取到的設備是具有同一Vendor ID 和Product ID的父根設備。可使用Windows API:CM_Get_Parent() 和CM_Get_Device_IDW() 不斷向上遍歷,直到再向上Vendor ID 和Product ID不同的時候再停止,此時的設備就是我們需要的設備。新版libusb似乎已經可以支持子設備,但目前并未驗證。
4.1.2 連接設備
當我們選定了要連接的設備,同時就得到了設備的Vendor ID 和Product ID。除此之外還有設備實例ID,讓我們能夠區分連接到同一臺PC的多臺同一型號的安卓設備,這些設備具有相同的Vendor ID和Product ID,但Windows計算機為這些設備分配的設備實例ID是不同的。
由于我們后續的邏輯基于libusb,因此需要使用上一步拿到的信息獲取libusb的設備對象。先使用libusb_get_device_list() 獲取USB設備列表進行遍歷,然后使用libusb_get_device_descriptor() 獲取設備的描述信息,和上面拿到的信息進行比對,來找到目標設備。
詳細的連接邏輯如下圖所示:
圖4-3 安卓有線投屏接收端發起連接邏輯
連接流程可以概括為:
上述流程中,第一次安裝的驅動需要和想要連接的設備的Vendor ID和Product ID對應。這個驅動將會在選擇設備后,由一個事先準備好的驅動模板填入Vendor ID和Product ID生成。驅動模板使用libusbK附帶的驅動開發包 (UsbK Development Kit) 中的驅動安裝包創建向導 (Driver Install Creator Wizard) 基于任一USB設備生成的驅動安裝包修改而成。該流程對于第二次安裝的驅動來說也是一樣的。
驅動安裝包文件中的二進制文件可以直接使用,而且驅動文件已經打過簽名。INF文件是我們修改的目標,里面記錄了對應設備的Vendor ID和Product ID,顯示名稱、制造商名稱、類GUID等等。類GUID可以改為我們獨有的GUID,以便于在設備枚舉階段篩選出已經裝過驅動的設備。
INF文件修改完成后,執行同一目錄中的dpinst64.exe文件即可安裝驅動。需要注意的是,該可執行文件需要管理員權限,執行前最好進行文件校驗,以防篡改。
接下來說明配件模式相關的數據。在步驟2中,發送的指令可攜帶以下信息:
表4-2 安卓配件信息
發送時序如圖4-4所示:
圖4-4 安卓有線投屏切換配件模式時序圖
首先使用libusb_open() 打開設備,并使用libusb_claim_interface() 打開讀寫接口,然后就可以使用libusb_control_transfer() 發送和接收數據了,具體指令格式可參閱谷歌AOAv1文檔,這些信息用于匹配安卓端的應用程序。切換完成之后,需要重新枚舉設備,找到固定ID:0x18D1和0x2D00的設備,使用libusb_open() 打開設備,并使用libusb_claim_interface() 打開讀寫接口進行后續數據通信。
如果安卓端安裝有對應的應用程序,轉換為配件模式后,安卓端會彈出如圖4-5左側的系統消息;如果沒有對應的應用,則安卓端會彈出如圖4-5右側的系統消息。圖中紅框標出的部分對應“配件描述”。
圖4-5 安卓配件提示
安卓端用于處理配件模式的應用需要做出聲明,參見4.2.2小節。
如果安裝有對應的應用,點擊“確定”按鈕將拉起該應用;如果安裝有多個對應的應用,則會彈出應用選擇彈窗,選擇其中一個應用后會將其拉起。在該應用中使用安卓Framework層提供的USBManager可打開對應的附件,得到文件描述符,可對該文件描述符進行讀寫操作。到此通信鏈路即建立完成。
4.1.3 數據傳輸
投屏的數據鏈路建立之后,Windows計算機可以調用libusb API:libusb_bulk_transfer() 來進行設備通信。建議設置1秒超時,以防止出現無限等待的情況。
如果在數據傳輸過程中斷開USB電氣連接,當前或后續的讀寫調用會立即返回錯誤。在此之后需要重新枚舉設備。穩定連接速度實測為 30MiB/s 左右。使用USB2.0或者USB3.0接口,速度沒有太大的差別。
通信時數據包使用的頭部與iOS有線投屏相同。握手和后續的接收數據流程也與iOS有線投屏基本相同,參考流程圖3-3即可。
4.1.4 驅動卸載
在前面的連接步驟中,我們為選定的設備安裝了libusbK驅動,這一過程會替換掉設備原本的驅動,換言之,設備將失去原本驅動所能提供的功能。例如:Windows計算機訪問安卓的文件系統,或者Windows計算機通過安卓連接網絡。因此,有必要提供卸載功能,使得用戶在不使用投屏時,仍可以使用原驅動的功能。
驅動卸載分為以下兩步:
1)移除設備
使用Windows API:SetupDiGetClassDevsW(),傳入我們定義的設備類GUID,以及枚舉器 ”USB”。然后使用SetupDiEnumDeviceInfo() 進行枚舉,保存枚舉到的設備的以下信息:
然后調用SetupDiCallClassInstaller(),傳入DIF_REMOVE進行設備移除。
2)卸載驅動軟件
以上一步獲得的INF文件名,調用SetupUninstallOEMInfW(),刪除驅動。
4.2 安卓端實現
在安卓端,對音視頻編碼后,通過UsbManager獲取配件文件描述符,進行讀寫即可完成數據傳輸至另一端。其中,應用層程序通過安卓Framework層獲得UsbManager代理對象,經過授權后拿到FileDescriptor,此時Framework 層會通過UsbDeviceManager打開 "/dev/usb_accessory" 并獲取必要的描述信息,以供附件匹配,在確認匹配和授權后,應用層即可進行IO操作。
4.2.1 設備模式說明
安卓從3.1版本開始支持USB配件和主機兩種模式,兩種模式的示意見圖4-6。安卓設備作為USB主機時,可連接U盤等USB設備,并由安卓設備提供電力;而在配件模式時,配件作為USB主機連接至安卓設備,并為安卓設備供電。在有線投屏場景中,Windows計算機就是“配件”。
圖4-6 安卓設備USB主機/配件模式(圖片來源:Google官網)
4.2.2 啟用配件
如4.1.2小節所述,若一個安卓應用想要用于配件模式通信,它聲明的信息就必須與配件提供的信息對應。具體來說,安卓應用需要:
4.2.3 連接配件
當有配件連接安卓設備,并使安卓設備進入配件模式,且安卓設備上安裝有匹配配件信息的安卓應用時,應用的響應流程如圖4-7所示:
圖4-7 安卓設備對配件模式的響應
4.2.4 錄制FLV數據封裝
數據輸出流程包括采集、編碼和封裝三個主要階段,最終封裝為FLV格式的數據。投屏中使用MediaProjection進行屏幕采集,AudioRecord進行麥克風采集。通過MediaCodec對視頻和音頻分別進行編碼,生成AVC視頻流和AAC音頻流。
設備連接時,通過UsbManager申請UsbAccessory操作權限,并獲取ParcelFileDescriptor以實現IO輸出。隨后,我們將AVC和AAC數據隊列按時序交替封裝成FLV Packet,形成連續的數據流,并通過此IO輸出傳輸至Windows端。
五、總結
搭建iOS有線投屏鏈路需要:
搭建安卓有線投屏鏈路需要:
相對無線局域網來說,有線投屏連接更為穩定,僅需一根質量尚可的USB數據線纜,即使是USB2.0的帶寬也可滿足目前要求。但其也有缺點:數據鏈路的搭建邏輯較為復雜;需要安裝額外的USB驅動程序;有線連接本身可能就會造成一些阻礙。但對于需要穩定投屏,或者電腦和手機無法通過局域網連接的用戶來說,有線投屏可能是唯一的選擇。
參考
[1] | USB-IF, "Universal Serial Bus 2.0 Specification," 2000. |
[2] | libimobiledevice, "libimobiledevice," [Online]. Available: https://libimobiledevice.org. |
[3] | Apple, "iTunes - Apple," [Online]. Available: https://www.apple.com/itunes. |
[4] | Microsoft, "Windows Portable Devices," [Online]. Available: https://learn.microsoft.com/en-us/windows/win32/windows-portable-devices. |
[5] | libusb, "Windows," [Online]. Available: https://github.com/libusb/libusb/wiki/Windows#How_to_use_libusb_on_Windows. |
[6] | T. L. Robinson, "libusbK," [Online]. Available: https://sourceforge.net/projects/libusbk. |
[7] | Google, "Android 開放配件協議 1.0," [Online]. Available: https://source.android.com/docs/core/interaction/accessories/aoa?hl=zh-cn. |
作者:ucclkp、大雞排、青藤
來源-微信公眾號:嗶哩嗶哩技術
出處:https://mp.weixin.qq.com/s/eI8xxjfvm7yWQXBvaVTtvw