Android mtp 流程分析及移植

2017/10/16 Android

mtp简介

MTP,全称是Media Transfer Protocol(媒体传输协议)。它是微软的一个为计算机和便携式设备之间传输图像、音乐等所定制的协议。

Android从3.0开始支持MTP。MTP的应用分两种角色,一个是作为Initiator,另一个作为Responder。以”Android平板电脑”连接”PC”为例,他们的关系如图所示。

mtp_2

Initiator —— 在MTP中所有的请求都有Initiator发起。例如,PC请求获取Android平板电脑上的文件数据。

Responder —— 它会处理Initiator的请求;除此之外,Responder也会发送Event事件。

这里要注意的是:对于一个MTP事件,比如从PC拷贝数据到Android手机中,整个数据处理是双向通信的。

mtp框架

来张经典的Android框架图:

mtp_frame

从图中可以看到,在kernel层中分为MTP驱动和USB驱动,其实呢,真正和底层直接通信的依然是USB驱动负责,MTP驱动只是负责将数据进行打包封装,然后作为一层分别与上层和USB之间进行通信。

再来看一下JNI层,那,如果要对MTP进行定制化的开发,这一层就需要额外的关注了。相关的源码位置位于:frameworks/av/media/mtp frameworks/base/media/jni/目录下

在JNI层,MtpServer会不断地监听Kernel的消息”MTP请求”,并对相应的消息进行相关处理。同时,MTP的Event事件也是通过MtpServer发送给MTP驱动的。 MtpStorage对应一个”存储单元”;例如,SD卡就对应一个MtpStorage。 MtpPacket和MtpEventPacket负责对MTP消息进行打包。android_mtp_MtpServer是一个JNI类,它是”JNI层的MtpServer 和 Java层的MtpServer”沟通的桥梁。android_mtp_MtpDatabase也是一个JNI类,JNI层通过它实现了对MtpDatabase(Framework层)的操作。

在Framework层,MtpServer相当于一个服务器,它通过和底层进行通信从而提供了MTP的相关服务。MtpDatabase充当着数据库的功能,但它本身并没有数据库对数据进行保存,本质上是通过MediaProvider数据库获取所需要的数据。MtpStorage对应一个”存储单元”,它和”JNI层的MtpStorage”相对应。

在Application层,MtpReceiver负责接收广播,接收到广播后会启动/关闭MtpService;例如,MtpReceiver收到”Android设备 和 PC连上”的消息时,会启动MtpService。 MtpService的作用是提供管理MTP的服务,它会启动MtpServer,以及将本地存储内容和MTP的内容同步。 MediaProvider在MTP中的角色,是本地存储内容查找和本地内容同步;例如,本地新增一个文件时,MediaProvider会通知MtpServer从而进行MTP数据同步。

mtp移植教程

说了那么多,那如何在linux中移植MTP协议呢?

    if (pipe(mtppipe) < 0) {
        LOGE("Error creating MTP pipe\n");
        return false;
    }
    /* To enable MTP debug, use the twrp command line feature to
     * twrp set tw_mtp_debug 1
     */ 
    //DIR* source=opendir("/tmp");
    //DIR* destination=opendir("/data/media/0/backup");
    //copy_folder("/tmp","/data/media/0/backup");
    twrpMtp *mtp = new twrpMtp(0);
    mtppid = mtp->forkserver(mtppipe);
    //property_set("sys.usb.config", "mtp,adb");
    if (mtppid) {
        close(mtppipe[0]); // Host closes read side
        mtp_write_fd = mtppipe[1];
        //DataManager::SetValue("tw_mtp_enabled", 1);
        //Add_All_MTP_Storage();
        Add_Remove_MTP_Storage(MTP_MESSAGE_ADD_STORAGE);
        return true;
    } else {
        close(mtppipe[0]);
        close(mtppipe[1]);
        //gui_err("mtp_fail=Failed to enable MTP");
        return false;
    }

首先fork了进程,通过管道进行通信。那先来看一下父进程做了什么?很简单,初始化了mtp_message这个结构体,然后写进mtp_write_fd,供子进程读取。该结构体便是控制设备在PC端的显示storage_id表示该MTP设备的ID号,path表示挂载的目录,maxFileSize表示文件挂载的大小,该值为0表示挂载的大小不做限制,display是该设备在PC端显示的名称


            LOGI("a[] = %s\n",a);
            LOGI("mtp_display = %s\n",mtp_display);
            const char *mtp_test=a;
            mtp_message.message_type = MTP_MESSAGE_ADD_STORAGE;
            mtp_message.storage_id = 1;
            mtp_message.path = "/data/media/0";
            mtp_message.maxFileSize = 0;
            mtp_message.display = PROJECT_NAME;
            LOGI("TEST MTP_DISPLAy\n");
            LOGI("sending message to add %i '%s' '%s'\n", mtp_message.storage_id, mtp_message.path, mtp_message.display);
            if (write(mtp_write_fd, &mtp_message, sizeof(mtp_message)) <= 0) {
                LOGI("error sending message to add storage %i\n", mtp_message.storage_id);
                return false;
            } else {
                LOGI("Message sent, add storage ID: %i\n",mtp_message.storage_id);
                return true;
            }

分析了父进程的流程,再来分析一下子进程所做的事情,MTP的整个流程都在这了,很简单,主要就几句代码,可是就这几句代码,揭示了MTP的大致思想。首先实例化了一个twmtp_MtpServer,这里的twmtp_MtpServer是个性化制定的设备信息(mtp_MtpServer.cpp和MtpServer.cpp的区别)。之后set_storages,注意了,就是这里,父进程中的写进去的mtp_message就是从这里开始读了,上文我们说道了MtpStorage对应一个”存储单元”就是在这里体现了,之后会调用add_storage函数将父进程读到的信息写入,这样子一个MtpStorage就创建。set_read_pipe指定了读取的管道,这样子就和父进程建立起了链接,之后mtp->start,开始启动MTP服务了

int twrpMtp::start(void) {
    MTPI("Starting MTP\n");
    twmtp_MtpServer *mtp = new twmtp_MtpServer();
    mtp->set_storages(mtpstorages);
    mtp->set_read_pipe(mtp_read_pipe);
    mtp->start();
    return 0;
}

来看一下start函数,是否使用PTP(英语“图片传输协议(picture transfer protocol)”的缩写,手机连接之后可选,用于传输图片),实例化一个MtpDatabase,MtpDatabase在MTP中,充当着数据库的功能。但它本身并没有数据库对数据进行保存,本质上是通过MediaProvider数据库获取所需要的数据。例如,当在PC上,需要读取某个文件时,MtpDatabase会在MediaProvider数据库中查询出文件的相关信息(包括文件名、大小、扩展名等);然后将这些信息交给MtpServer,MtpServer将消息传递给JNI,在JNI中会通过文件名打开,然后再文件句柄等信息传递给Kernel;Kernel根据文件句柄读取文件信息,并传给PC。

之后打开mtp驱动创建的设备节点/dev/mtp_usb,底层驱动我们待会分析。

然后server = new MtpServer(mtpdb, usePtp, 0, 0664, 0775);到这里,终于看到MTP协议相关的server了,整个MTP定义的协议都在这个class当中了,这个是MTP最核心的东西了。

add_storage();前文提到,就是在这里将我们初始化的MtpStorage加入咯

ThreadPtr mtpptr = &twmtp_MtpServer::mtppipe_thread;喏,这里就是读取那个mtp_message了,通过创建了一个线程,用于不断读取管道的数据来添加mtp设备

void twmtp_MtpServer::start()
{
    usePtp =  false;
    MyMtpDatabase* mtpdb = new MyMtpDatabase();
    /* Sleep for a bit before we open the MTP USB device because some
     * devices are not ready due to the kernel not responding to our
     * sysfs requests right away.
     */
    usleep(800000);
#ifdef USB_MTP_DEVICE
#define STRINGIFY(x) #x 
#define EXPAND(x) STRINGIFY(x)
    const char* mtp_device = EXPAND(USB_MTP_DEVICE);
    MTPI("Using '%s' for MTP device.\n", EXPAND(USB_MTP_DEVICE));
#else   
    const char* mtp_device = "/dev/mtp_usb";
#endif
    //fp_mtp =fopen( "/recovery/mtp.log", "a+" );
    int fd = open(mtp_device, O_RDWR);
    if (fd < 0) {
        MTPE("could not open MTP driver, errno: %d\n", errno);
        return;
    } 
    MTPD("fd: %d\n", fd);
    server = new MtpServer(mtpdb, usePtp, 0, 0664, 0775);
    refserver = server;
    MTPI("created new mtpserver object\n");
    add_storage();
    MTPD("Starting add / remove mtppipe monitor thread\n");
    pthread_t thread;
    ThreadPtr mtpptr = &twmtp_MtpServer::mtppipe_thread;
    PThreadPtr p = *(PThreadPtr*)&mtpptr;
    pthread_create(&thread, NULL, p, this);
    // This loop restarts the MTP process if the device is unplugged and replugged in
    while (true) {
        server->run(fd);
        fd = open(mtp_device, O_RDWR);
        usleep(800000);
    }
}

再来看看run(fd);函数中的上半段,首先调用read函数从/dev/mtp_usb中读取数据存入到mBuffer(实际调用的是 MtpDataPacket::read函数,该函数还初始化了mPacketSize,mOffset两个变量),之后调用了mRequest.getOperationCode mRequest.getTransactionID()两个函数,打开看一下,其实就是对mBuffer里面的数据进行处理,要分析为什么这么来的,那就得看MTP数据包协议了。

void MtpServer::run(int fd) {
    if (fd < 0)
        return;

    mFD = fd;
    MTPI("MtpServer::run fd: %d\n", fd);

    while (1) {
        MTPD("About to read device...\n");
        int ret = mRequest.read(fd);
        if (ret < 0) {
            if (errno == ECANCELED) {
                // return to top of loop and wait for next command
                MTPD("request read returned %d ECANCELED, starting over\n", ret);
                continue;
            }
            MTPE("request read returned %d, errno: %d, exiting MtpServer::run loop\n", ret, errno);
            break;
        }
        MtpOperationCode operation = mRequest.getOperationCode();
        MtpTransactionID transaction = mRequest.getTransactionID();

        MTPD("operation: %s", MtpDebug::getOperationCodeName(operation));
        mRequest.dump();
    
        // 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);
        if (dataIn) {
            int ret = mData.read(fd);
            if (ret < 0) {
                if (errno == ECANCELED) {
                    // return to top of loop and wait for next command
                    MTPD("data read returned %d ECANCELED, starting over\n", ret);
                    continue;
                }
                MTPD("data read returned %d, errno: %d, exiting MtpServer::run loop\n", ret, errno);
                break;
            }
            MTPD("received data:");
            mData.dump();
        } else {
            mData.reset();
        }
    

之后handleRequest函数用于处理读到的不同的操作进行不同的操作,每个函数内容都比较多,这里就分析一下从PC端拷贝一个数据到手机上的操作,主要做了以下操作,打开看一下,其实都是一些用于解析数据头,之后解析数据的问题了,有兴趣去了解MTP协议相关的东西,可以参考去查看一下mtp wiki

            case MTP_OPERATION_SEND_OBJECT_INFO:
                MTPE("about to call doSendObjectInfo()\n");
                //response = MTP_RESPONSE_OBJECT_WRITE_PROTECTED;
                response = doSendObjectInfo();
                break;
            case MTP_OPERATION_SEND_OBJECT:
                MTPE("about to call doSendObject()\n");
                //response = MTP_RESPONSE_OBJECT_WRITE_PROTECTED;
                response = doSendObject();
                break;


mtp 与kernel层通信

kernel层MTP相关的代码在drivers/usb/gadget/function/f_mtp.c中,通过这部分代码,成功创建了/dev/mtp_usb节点

/* file operations for /dev/mtp_usb */
static const struct file_operations mtp_fops = {
    .owner = THIS_MODULE,
    .read = mtp_read,
    .write = mtp_write,
    .unlocked_ioctl = mtp_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = compat_mtp_ioctl,
#endif
    .open = mtp_open,
    .release = mtp_release,
};

static struct miscdevice mtp_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = mtp_shortname,
    .fops = &mtp_fops,
};
static int __mtp_setup(struct mtp_instance *fi_mtp)
{
    ...
    ret = misc_register(&mtp_device);
    ...
}

从file_operations可以知道,上层调用的write函数在kernel层实则调用的是mtp_write函数。现在我们主要来关注一下mtp_read、mtp_write这两个函数的实现。

  • 先来看一下mtp_write(),主要的几个处理copy_from_user(req->buf, buf, xfer)首先会将用户空间的请求传入到req->buf中,之后通过usb_ep_queue函数将req中的消息传入到USB消息队列中,由USB驱动进行之后的数据传送,至于mtp_write函数的其他部分都是些对数据处理的过程。总的来说,mtp_write会将”用户空间”发来的消息拷贝到”内核空间”,并将该消息打包;然后,将打包好的消息添加到USB消息队列中。USB驱动负责将消息队列中的消息传递给PC
    static ssize_t mtp_write(struct file *fp, const char __user *buf,
      size_t count, loff_t *pos)
    {
      while (count > 0 || sendZLP) {
      if (xfer && copy_from_user(req->buf, buf, xfer)) {
      }
      ...
      ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
      ...
      }
    }
    
  • 再来看一下mtp_read()函数,其实就是mtp_write()的逆过程,先从usb消息队列中读取pc发送给Android设备的请求并保存在req中,之后等待read_wq工作队列将已有的消息处理完毕,最后将pc的请求数据通过copy_to_user拷贝到用户空间。
    static ssize_t mtp_read(struct file *fp, char __user *buf,
      size_t count, loff_t *pos)
    {
    ...
    ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
    ...
    ret = wait_event_interruptible(dev->read_wq,
                  dev->rx_done || dev->state != STATE_BUSY);
    ...
    if (copy_to_user(buf, req->buf, xfer))
    ...
    }
    

    总结

    其实通过全文的分析,MTP协议并没有想象中那么复杂,只是对数据进行了封装,整个数据的传送过程实质还是通过USB协议进行的。至于在开发中碰到的数据处理问题,需要的是多去了解MTP协议的具体内容而已。

Search

    Table of Contents