由于MTP協定在移動裝置中的廣泛應用,使其成為在裝置互聯産品中,必備的元件之一。設想在開車途中,接入車載裝置,聽聽手機中的歌;或者在休息時,借用車載的大螢幕看看手機中的高清電影,這些都使得旅途變得輕松惬意。
本文盡量避免介紹MTP協定(文檔已經寫的很清楚),主要針對某個具體裝置(Google Nexus 4),介紹MTP開發入門知識。
1. MTP裝置模型
了解MTP裝置模型要有基礎的USB協定知識。MTP裝置通過USB Descriptor描述裝置的通信端點(Endpoint)。下面列出四太子的USB Descriptor:
# lsusb -v -d18d1:
Bus 001 Device 003: ID 18d1:4ee1 Google Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x18d1 Google Inc.
idProduct 0x4ee1
bcdDevice 2.28
iManufacturer 1 LGE
iProduct 2 Nexus 4
iSerial 3 0054700f490d669a
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 39
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 3
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 255 Vendor Specific Subclass
bInterfaceProtocol 0
iInterface 4 MTP
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x001c 1x 28 bytes
bInterval 6
Device Qualifier (for other device speed):
bLength 10
bDescriptorType 6
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
bNumConfigurations 1
Device Status: 0x0000
(Bus Powered)
可以看出N4裝置隻提供了一個Configuration,該Configuration提供了一個Interface,而這個Interface有3個Endpoint(bulk in, bulk out & interrupt in),都是用于MTP協定通信的。
2. MTP裝置的枚舉
MTP協定中,未規定如何判斷一個USB裝置是否是MTP裝置,一般可以采用以下規律:
1. 通過device descriptor的”bDeviceClass”字段判斷是否是USB_CLASS_PTP(6);
2. 通過udev,查詢裝置屬性是否包含”ID_MTP_DEVICE”;
3. 枚舉所有的Interface,查詢其iInterface對應的字元串是否是”MTP”(這個步驟對于多Configuration與多Interface的MTP裝置是必須的,因為我們要特别指定USB通信所采用的Configuration與Interface)。
3. MTP通信模型
裝置連接配接後,USB Host扮演的是Initiator角色,動作由Initiator主動發起;而裝置扮演Responder,響應Initiator的請求。另外,Responder也可以向Initiator上報一些事件,用于觸發Initiator做相應的動作(比如,裝置的解鎖動作,可以觸發Initiator重新擷取裝置的存儲清單)。
Initiator與Responder的通信通過上面USB Descriptor描述的3個Endpoint完成:Initiator通過bulk out endpoint向裝置寫入請求,Responder通過bulk in endpoint向Initiator傳回請求的資料,而MTP Event則是通過interrupt in endpoint由Responder向Initiator送出事件資訊。
4. MTP通信資料封裝
MTP的所有資料要以特定的容器進行封裝,封裝的格式如下:
Byte Offset | Length | Field Name | Description |
---|---|---|---|
4 | ContainerLength | Total amount of data to be sent (including this header) | |
4 | 2 | ContainerType | Defines the type of this container |
6 | 2 | Code | Operation, Response or Event Code as defined in the MTP specification. |
8 | 4 | TransactionID | See section 4.3.3 Transaction IDs. |
12 | ContainerLength-12 | Payload | The data which is to be sent in this phase. |
5. MTP通信的關鍵過程
-
Open Session:
_container->length = sizeof( mtpContainer ) + sizeof( guint32 );
_container->type = USB_CONTAINER_COMMAND;
_container->code = OC_OPENSESSION;
_container->transactionId = transactionId ++;
memcpy( _buf, ( void * )container, sizeof( mtpContainer ));
memcpy( _buf + _offset, params, container->length - sizeof( mtpContainer ) );//這裡params儲存了預先生成的session id
libusb_bulk_transfer( mtpObj->handle, mtpObj->bulkOutEp, _buf, container->length, &_len, 0 );//通過libusb的bulk transfer将請求通過bulk out endpoint發出
之後要等待裝置響應,即讀bulk in endpoint,buffer的設定要至少為一個該endpoint的maxPacketSize大小,否則在傳輸大資料時,會libusb會報overflow,資料會丢失:
libusb_bulk_transfer( mtpObj->handle, mtpObj->bulkInEp, _header, mtpObj->maxPacketSize, &_len, 0 );
擷取的資料要判斷傳回值,正常情況下code應該與請求的code一緻,或者為0x2001,表示請求成功完成,其他情況要進行異常處理。
- GetDeviceInfo,通過這個指令可以得到裝置的基本資訊,包括支援的指令清單、檔案類型、屬性與事件類型。MTP中所有的字元串都是utf-16編碼的,使用其他編碼的系統要自行轉換。
- 擷取storageIds,如果是裝置已鎖定的情況下接入,擷取的storage裝置個數是0。這時需要等待interrupt in endpoint給出storage add事件,再重新擷取裝置的storage資訊。
- 通過GetNumObjects、GetObjectHandles&GetObjectInfo指令,可以擷取指定類型檔案的個數、Handle和基本資訊(如檔案名、大小等等)。
- 擷取指定檔案的内容有2種方式:GetObject&GetPartialObject,MTP協定指出要使用GetPartialObject來替換GetObject,因為GetPartialObject支援檔案的随機通路。
至此,MTP協定開發入門介紹結束了,libusb的使用,可以參考之前的文章libusb異步中斷傳輸使用說明。結合libusb異步傳輸,可以實作單線程的多個endpoint的輸入處理,簡化程式設計。