mtp简介
MTP,全称是Media Transfer Protocol(媒体传输协议)。它是微软的一个为计算机和便携式设备之间传输图像、音乐等所定制的协议。
Android从3.0开始支持MTP。MTP的应用分两种角色,一个是作为Initiator,另一个作为Responder。以”Android平板电脑”连接”PC”为例,他们的关系如图所示。
Initiator —— 在MTP中所有的请求都有Initiator发起。例如,PC请求获取Android平板电脑上的文件数据。
Responder —— 它会处理Initiator的请求;除此之外,Responder也会发送Event事件。
这里要注意的是:对于一个MTP事件,比如从PC拷贝数据到Android手机中,整个数据处理是双向通信的。
mtp框架
来张经典的Android框架图:
从图中可以看到,在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协议的具体内容而已。