由于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 |
---|---|---|---|
0 | 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的输入处理,简化程序设计。