多媒体框架
16lz
2021-01-24
多媒体播放框架
多媒体核心框架涉及到的代码文件有: java层:frameworks/base/media/java/android/media/MediaPlayer.java
jni层:
frameworks/base/media/jni/android_media_MediaPlayer.cpp
native层:
frameworks/av/media/libmedia/Mediaplayer.cpp
frameworks/av/include/media/Mediaplayer.h
frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
frameworks/av/media/libmediaplayerservice/libmediaplayerservice/MediaPlayerService.h
frameworks/av/media/libmediaplayerservice/libmediaplayerservice/MediaPlayerFactory.cpp
MediaPlayerFactory.cpp以 下就是具体的播放器的实现了。
媒体播放的抽象层:
frameworks/av/include/media/MediaPlayerInterface.h 以上文件是播放器的abstract base class,类似于硬件抽象层(HAL),是每个具体的播放器都需要根据这个基类来实现对应的MediaPlayerInterface。多媒体播放的核心框架主要涉及到两条典型的数据流:
一个是命令+命令的响应(cmd+respond)
一个是事件(unsolicited)但凡用户跟后台服务一般都有如上的两个接口,如phone,wifi等核心模块,莫不是遵循以上的结构,只不过实现方式有差异。在这里命令和事件通道都是通过ibinder来实现,而phone和wifi模块的命令和事件通道都是通过socket通讯机制来实现的。那么为什么phone和wifi不通过ibinder来实现呢?因为phone的后台服务pppd以及wifi的后台服务wpa_supplicant,他们是linux的原生态代码,这些代码是在android诞生之前就有的,所以当他们提供的通讯机制就是使用的socket,那android本着最少修改的原则,当然就是还是原用了他本身自带的socket通讯来实现的;而多媒体服务这块,则是android自己重新规划实现的,所以他自然就使用他们自己的ibinder通讯机制。在很多场景下,android在条件合适的情况下,一般都会优先使用ibinder通讯,因为ibinder通讯相比socket通讯效率更高,安全性更好,这个后面单独开blog来论述。 在讲native层的代码框架前,先上过类图:
frameworks/av/include/media/IMediaPlayer.h定义了ImediaPlayer的binder接口 frameworks/av/include/media/IMediaPlayerService.h定义了IMediaPlayerService的binder接口 frameworks/av/include/media/IMediaPlayerClient.h定义了IMediaPlayerClient的binder接口
上图中的黄色标识部分就是多媒体框架中的后台服务(media.player service),他运行于一个独立的进程中,他既包含了binder的服务器端(BnMediaPlayerService,BnMediaPlayer),也包含了binder的客服端(BpMediaPlayerClient);而蓝色标识的部分,则是媒体播放器的客服端,即player user process,他同样运行于用户所在的进程,客服端同样也包含binder的服务器端(BnMediaPlayerClient),也包含了binder的客服端(BpMediaPlayer,BpMediaPlayerService) 另外media.player service进程包含了两个binder实例,即两个binder服务器端(BnMediaPlayerService,BnMediaPlayer),他们在同一个进程里。那当一个remote ipc过来时,他们怎么区分出来该调用是该路由给BnMediaPlayerService还是路由给BnMediaPlayer呢?该问题同样在我的另外的blog中来阐述。在这里只是提出这个问题,为下一篇blog做些知识上的铺垫。
从binder的分类来讲: IMediaPlayerService对应的binder是注册到service manager中去的实名binder,见如下代码段:
void MediaPlayerService::instantiate() { defaultServiceManager()->addService( String16("media.player"), new MediaPlayerService());}
而ImediaPlayer和IMediaPlayerClient对应的binder都是匿名binder,在这里先说这些binder的作用。IMediaPlayerServicebinder其实只是起个桥梁的作用,目的是为了获取两个匿名binder:ImediaPlayer和IMediaPlayerClient。而ImediaPlayer binder对应前面讲个的用于应用通过这个binder向media service后台服务发送命令和接受命令的响应;而IMediaPlayerClient则是被media service后台服务用来向应用返回媒体播放事件的。 IMediaPlayerService实名binder的获取方式:
// establish binder interface to MediaPlayerService/*static*/const sp<IMediaPlayerService>&IMediaDeathNotifier::getMediaPlayerService(){ ALOGV("getMediaPlayerService"); Mutex::Autolock _l(sServiceLock); if (sMediaPlayerService == 0) { sp<IServiceManager> sm = defaultServiceManager();//实名服务的管理者 sp<IBinder> binder; do { binder = sm->getService(String16("media.player"));//提供实名服务对应的名字,返回的是binder实例在客服端进程中的引用句柄号。 if (binder != 0) { break; } ALOGW("Media player service not published, waiting..."); usleep(500000); // 0.5 s } while (true); if (sDeathNotifier == NULL) { sDeathNotifier = new DeathNotifier(); } binder->linkToDeath(sDeathNotifier); sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);//将Bpbinder cast to BpMediaPlayerService } ALOGE_IF(sMediaPlayerService == 0, "no media player service!?"); return sMediaPlayerService;}而匿名binder是怎么获取的呢? 分两部分讲,首先说IMediaPlayerClientbinder是如果告诉后台服务的:
virtual sp<IMediaPlayer> create(//frameworks/av/media/libmedia/IMediaPlayerService.cpp const sp<IMediaPlayerClient>& client, int audioSessionId) { Parcel data, reply; data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); data.writeStrongBinder(client->asBinder());//将IMediaPlayerClientbinder实体传递到binder驱动中 data.writeInt32(audioSessionId); remote()->transact(CREATE, data, &reply); return interface_cast<IMediaPlayer>(reply.readStrongBinder());//获取ImediaPlayer binder实体在当前进程中的引用句柄,并将它封装在BpMediaPlayer实例中。 }
而服务器端是如何将ImediaPlayer binder实体传递到应用段所在的进程的呢?
status_t BnMediaPlayerService::onTransact(//frameworks/av/media/libmedia/IMediaPlayerService.cpp uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){ switch (code) { case CREATE: { CHECK_INTERFACE(IMediaPlayerService, data, reply); sp<IMediaPlayerClient> client = interface_cast<IMediaPlayerClient>(data.readStrongBinder());//获取BpMediaPlayerClient int audioSessionId = data.readInt32(); sp<IMediaPlayer> player = create(client, audioSessionId);//创建BnMediaPlayer实体 reply->writeStrongBinder(player->asBinder());//将BnMediaPlayer binder实体写到驱动中。 return NO_ERROR; } break; case DECODE_URL: {
以上就是匿名binder实体如何通过实名binder实体来进行传递的概要说明。
而播放器的播放调用过程如下: MediaPlayer mMediaPlayer = new MediaPlayer( );
mMediaPlayer.setDataSource(mContext, mUri);
mMediaPlayer.setDisplay(mSurfaceHolder);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.prepareAsync();
mMediaPlayer.start();
下面以setDataSource调用过程为列来说明用户的命令是如何从客服端发送到媒体后台服务的。
MediaPlayer::setDataSource //MediaPlayer.cpp |-->const sp<IMediaPlayerService>& service(getMediaPlayerService());//获取BpMeidaPlayerService |-->sp<IMediaPlayer> player(service->create(this, mAudioSessionId));//返回BpMediaPlayer |-->player->setDataSource(url, headers)//调用BpMediaPlayer.setDataSource(ImediaPlayer.cpp) 以上调用会通过binder调用到binder实体对应的函数: MediaPlayerService::Client::setDataSource |-->MediaPlayerFactory::getPlayerType()//获取播放器的类型|-->setDataSource_pre(playerType)//根据playerType创建具体的播放器实例,例如ffplayer(见ffplayer.cpp) |-->p->setDataSource//调用具体播放器的setDataSource函数
下面再说下媒体播放器的事件是如何通知到用户的。分两部分来论述:媒体后台服务器端和用户端 媒体后台服务器端:
MediaPlayerService::Client::notify //MediaPlayerService.cpp |-->sp<IMediaPlayerClient> c; |-->c = client->mClient;//获取BpMediaPlayerClient,即binder的客服端 |-->c->notify();以上调用会通过binder调用到BnMediaPlayer,即binder实体端: MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)//MediaPlayer.cpp
|-->listener->notify() 而以上MediaPlayerService::Client::notify函数会通过如下过程传递到具体的播放器中,以供播放器来上报事件。
MediaPlayerService::Client::createPlayer|--->MediaPlayerFactory::createPlayer(playerType, this, notify) |--->p->setNotifyCallback //MediaPlayerInterface.h |->mNotify = notifyFunc |->sendEvent //上面初始化的mNotify函数,被sendEvent函数调用 |->mNotify(mCookie, msg, ext1, ext2, obj);
所以在具体的播放器实现中,可以直接调用MediaPlayerBase::sendEvent来将事件发送到用户。
播放器的用户端: 回到MediaPlayer.cpp文件中的MediaPlayer::setListener函数,该函数设置了侦听者,到收到事件时,会调用该侦听者。该侦听者的初始化过程如下:
android_media_MediaPlayer_native_setup//android_media_MediaPlayer.cpp |-->sp<MediaPlayer> mp = new MediaPlayer(); |-->sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this); |-->mp->setListener(listener);//即为<span style="font-size:18px;">MediaPlayer::setListener函数</span> 以上过程,将<span style="font-size:18px;">侦听者设置为JNIMediaPlayerListener,所以当事件产生时,会调用</span> void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj) |-->env->CallStaticVoidMethod(mClass, fields.post_event...);//从jni调到MediaPlayer.java |--->postEventFromNative //MediaPlayer.java |-->Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); |-->mp.mEventHandler.sendMessage(m); //MediaPlayer.mEventHandler = new EventHandler(this, looper);
具体到用户的播放器界面中,用户可以通过实现如下函数: MediaPlayer.OnCompletionListener
MediaPlayer.OnTimedTextListener
MediaPlayer.OnInfoListener MediaPlayer.OnPreparedListener MediaPlayer.OnBufferingUpdateListener MediaPlayer.OnBufferingUpdateListener 来实现对相应事件的处理。
相关的参考文档: http://developer.android.com/guide/topics/media/mediaplayer.html
http://developer.android.com/guide/topics/media/audio-capture.html#audiocapture
更多相关文章
- Android(安卓)后台服务
- Android中UI线程与后台线程交互设计的5种方法
- Android(安卓)如何保证App切换到后台,或页面跳转后,重新打开APP、
- 杂谈:后台进程对Android性能的影响
- 处理Android应用在后台被杀死
- Android(安卓)管理多个fragment(处理Activity被回收的情况)
- Android之所以不需要关闭后台运行程序 的 理由
- PermissionX现在支持Java了!还有Android(安卓)11权限变更讲解
- android 监听应用前后台运行状态