MTP协议总结与Android源码分析

前段时间有做MTP协议扩展的相关的内容,在这里总结一下。

(注意协议方面有很多细节一篇简短的文章是不可能面面俱到,这里只是学习总结,本人接触协议的时间也不是很长,难免有纰漏,有错误之处请不吝指教)。

协议部分

概述

Media Transfer Protocol即媒体传输协议,主要是用来管理移动设备上的图片、视频、音频等媒体信息,典型的有Android设备,相机设备。

MTP协议是应用层协议,底层协议可以走USB或TCP/IP协议,只要能够无差错传输即可。

tcp_underlying

MTP协议框架上面定义了很多多媒体相关的命令,例如获取设备信息,获取对象信息,本文着重介绍MTP协议框架,然后举例特定的命令帮助理解。

协议模型

MTP协议有两个角色,类似于客户端与服务端,在MTP协议里面有特定的称号,发起请求的叫Initiator,对请求进行响应的叫Responder。Initiator通常对应于PC/MAC宿主机,Responder对应于被管理的设备,例如Android手机。任何操作都需要Inititaor发起,然后Reponder进行响应。

Initiator对Reponder的大多数请求都是需要打开一个Session会话,类似于HTTP里面的Session,用于保存上下文相关的环境信息,例如,在MTP传输媒体文件过程中,Inititor传输媒体文件到Reponder是需要发送两次请求才能完成的,第一次请求发送SendObjectInfo的消息,告诉Reponder即将要发送的媒体信息,包括大小,格式,媒体文件名称等;第二次请求发送SendObject传输实际的文件,在这两次请求中第二次请求需要使用第一次请求保存的相关信息,所以就需要保持在一个Session会话里面。MTP按理论上说是可以支持多Session会话的。

还有另一个关键的概念,就是Transaction,对应于Initiator发起请求,然后数据传输,Responder响应一次完整的过程,有点类似于数据库里面的事务,比如Initiator发起一次请求,在Reponder没有响应之前,是不能进行另一次请求。所以在USB单Session实现中,Initiator是不能同时发送多次请求的。

传输模型

前面介绍到Transaction对应于Initiator发起请求,然后数据传输,Responder响应。所以对于的请求,数据,响应传输分为三个阶段:

  • Request Phase
  • Data Phase
  • Reponse Phase

其中Data Phase是可选的,并且是单向的。
单向的定义就规定了Data Phase的数据流要么是Initiator到Reponder(以下简称I->R),要么是Reponder到Initiator(以下简称R->I)。
MTP协议也规定Data Phase是可选的,就是意味着Initiator发送完成请求后,Reponder就直接响应,不需要传输数据,因为有的MTP消息不需要传输数据,Request与Repond本身就可以传递少量的参数。

下图就是传输的三种情况:

mtp_transfer_i-r
mtp_transfer_i-r
mtp_transfer_i-r

数据流的字段是有Request的字段OperationCode决定的,根据不同的功能决定Data阶段的数据流向。比如Initiator读取媒体信息GetObjectInfo的数据流向就是R->I;Initiator发送媒体文件信息SenObjectInfo的数据流向就是I->R; 读取设备上媒体文件的个数GetObjectNum由于Reponse中携带的参数已经能够满足表示数量,所以就不需要Data Phase。

下面来说一下Request与Reponse的Dataset,用来表示能够携带哪些参数。Request与Response的Dataset是一样的。需要特别注意的是,不同的底层协议对于Dataset的存放方式是不同的,MTP SEPC只给出的是USB的实现方式。

mtp_req_resp_dataset

最关键的是操作码OperationCode定义Request请求要进行什么样的操作,MTP Responder 该处理什么样的任务,然后根据功能决定Data的数据流向。
对于USB来说是单Session的实现方式,在其实现的数据集是不包含sessionID这个字段的,但是在发送大多数Request之前,也还是需要发送OpenSession这个Request请求。

TransactionID由Initiator指定,在一次完整的Request到Response都要指定相同的值,不需要每一次都相同。

Request与Response可以携带0到5个参数,根据OperationCode的功能来决定。

还有一种比较特殊的消息就是Reponder可以直接发送Event给Initiator,用来通知Initiator,Reponder出现了一些状况或者发生了一些变化,可以与Transaction关联,也可以不可Transaction关联,根据Event的事件来定。比如设备上新增了一条媒体文件的信息,就需要通过Event事件来通知Initiator来更新。

mtp_transfer_event

Event是不能传递二进制数据的,只能携带0到3个参数,其Dataset为:

mtp_transfer_event_dataset

常用功能的Request与Reponse的OperationCode,Event的EventCode,在MTP Spec规格文档里面有定义,不同的Code对应什么样含义以及携带什么样的参数,还给出了要厂商可以扩展的Code范围。

之前看Android源码的时候就有点懵,spec上面的定义的Dataset与源码里面的对不上,后来看到sepc文档最后的这个表格才知道不同的实现方式数据的存放是不一样的,下面这个就是USB定义的MTP数据包,Request,Data,Response都要携带定义的头部信息,Initator与Responder都要读取USB数据包来解析MTP数据包。

mtp_transfer_usb_container_dataset

Request与Response的Payload就是携带的那0到5个参数,Data的Payload就是二进制数据,可能是媒体文件,也有可能是自定义的数据格式。

对象模型
mtp_transfer_objectinfo.png

StorageID:对应一个设备上的存储分区,表现形式就是无符号32位的整数uint32, 高16位表示存储设备,低16位表示对应存储设备的分区。例如Android设备,有内部存储与外置SD卡,SD卡可能有多个分区,就对应不同的StorageID。

ObjectHandle:实际就是一个int32的对象id,对应设备上的一个个媒体文件对象,可能是文件夹或者是媒体文件,MTP读取文件或者发送文件都需要这个id,其通常包含一个父Object,类似于文件系统的目录树,根路径的值比较特殊,0xFFFFFFFF

ObjectFormat:媒体类型

....其余的不难理解,需要时查询文档

源码部分

Android源码的处理过程:

处理MTP请求的应用就是提供媒体数据库的MediaProvider,对应的包名为com.android.providers.media,对于源码树的位置packages/providers/MediaProvider。
其本身是一个系统应用,在AndroidManifest里面监听USB状态变化的广播:

<receiver android:name=".MtpReceiver">
   <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
   </intent-filter>
   <intent-filter>
        <action android:name="android.hardware.usb.action.USB_STATE" />
   </intent-filter>
</receiver>

当连上USB数据线,就会将MtpService.java启动,然后加载动态库,开启一个线程,jni调用MTPServer.cpp的run方法,不断地从mtp驱动读消息,处理,响应。而MTP的Object对应的就是文件数据里面的一个个文件。

mtp_transfer_mtpserver_main_flow

MtpServer.cpp的run方法:

void MtpServer::run() {
    int fd = mFD;//打开的mtp驱动文件描述符

    while (1) {
        int ret = mRequest.read(fd);//读取Request请求,放到mRequest封装的类里面
        //...
        MtpOperationCode operation = mRequest.getOperationCode();
        MtpTransactionID transaction = mRequest.getTransactionID();

 

        // FIXME need to generalize this
        bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
                    || operation == MTP_OPERATION_SET_OBJECT_REFERENCES
                    || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
                    || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);//根据OperationCode来处理Data Phase的数据流向
        if (dataIn) {
            int ret = mData.read(fd);
            //...
        } else {
            mData.reset();
        }

        if (handleRequest()) {//handleRequest根据不同的OperationCode功能处理不同的任务
            if (!dataIn && mData.hasData()) {
                mData.setOperationCode(operation);
                mData.setTransactionID(transaction);
                ALOGV("sending data:");
                ret = mData.write(fd);
            }

            mResponse.setTransactionID(transaction);
           
            ret = mResponse.write(fd);//响应
            const int savedErrno = errno;
        } else {
            ALOGV("skipping response\n");
        }
    }
    //....

}

对照MTP SEPC文档的附录接口看代码比较简单,就是读取解析结构化数据或者封装需要发送的结构化数据,但是有一点需要特别注意,
处理发送文件与读取文件的操作不是在用户空间完成的,而是通过IOCTL,调用驱动接口在内核空间完成,这样会比较高效,不用再用户空间拷贝一份处理给内核空间,而是有内核空间直接发送。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容