Android Media Player 框架分析-Nuplayer(1)
来源:互联网 发布:axure mac汉化教程 编辑:程序博客网 时间:2024/04/20 05:45
由于工作岗位调整,开始接触Media相关部分的代码,起初希望在网络上找一下大神们分析软文学习一下就好,由于Google在新的Android版本上修改了Nuplayer的地位,原本NuPlayer主要在系统中负责流媒体的播放,但现在Android基本已经全面弃用AwesomePlayer,很多网络文章介绍的多为之前的AwesomePlayer,所以最终没能找到需要的东西,只好自己入手分析。本次分析主要侧重于对Android中NuPlayer在播放本地文件时的工作流程的分析,由于本人初次接触Media模块很多基本的概念不全,加之对代码也只看了大约两周左右,所以可能存在诸多错误,若有发现,请及时指正,多谢,闲话不多说,切入正题。
Android中,我们通常使用SDK中的MediaPlayer来播放一个media资源,对于一个Application的开发者而言,可以十分容易的通过几个简单的接口来对此进行操作,之所以能如此完全依靠Google在Framework中的强大支持才得以实现,本文重在分析Framework中的Server端流程,但SDK中的使用流程便是对框架分析的一个很好的切入口,所以首先看一下在App中如何播放一个媒体文件。
Android在Java层中提供了一个MediaPlayer的类来作为播放媒体资源的接口,在使用中我们通常会编写以下的代码:
MediaPlayer mp = new MediaPlayer()mp.setDataSource("/sdcard/filename.mp3");mp.prepare();mp.start();只需寥寥的几行代码我们就可以在Android播放一个媒体文件,当然MediaPlayer的接口远不止示例中的几个,其他的接口使用上都比较简单,不做过多描述,下面放上一张MediaPlayer的状态图,以供参考,好吧,我承认因为懒,我从度娘上盗了张图。
我们的分析主要从刚才的示例代码开始,来看看我们简单的几个步骤会在内部执行什么样的操作。
1.创建MediaPlayer:
首先来看看MediaPlayer的构造函数:
public MediaPlayer() { Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else if ((looper = Looper.getMainLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; } mTimeProvider = new TimeProvider(this); mOpenSubtitleSources = new Vector<InputStream>(); IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); mAppOps = IAppOpsService.Stub.asInterface(b); /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ native_setup(new WeakReference<MediaPlayer>(this)); }
函数中最重要的一句是最后的一句native_setup这个JNI的函数,之上的代码主要是创建了一个Handler和一下辅助类,其中这个Handler的主要作用是接收mediaserver端发来的一些状态消息,这一点我们在之后遇到了再说。先来说说这个native函数,直接到android_media_MediaPlayer.cpp中来看看这个函数的实现。
static voidandroid_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this){ ALOGV("native_setup"); sp<MediaPlayer> mp = new MediaPlayer(); if (mp == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; } // create new listener and give it to MediaPlayer sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this); mp->setListener(listener); // Stow our new C++ MediaPlayer in an opaque field in the Java object. setMediaPlayer(env, thiz, mp);}
首先setup函数在C++端创建了一个MediaPlayer对象,之后创建了JNIMediaPlayerListener,然后调用setListener将其设置到创建的mp对象中,之后通过setMediaPlayer将mp的地址回传给Java端的MediaPlayer保存为一个long对象。JNI中的相互调用就不一一说了,要不写一周也写不完了。目前我们知道了其实Java端的MediaPlayer对象相当于一个代理,实际上在C++端我们也创建了一个同名的对象,如果大家对Android熟悉,就能知道真正干活的通常都是这个C++的对象,那么我们就得看看这个C++的MediaPlayer类究竟干了些啥,JNIMediaPlayerListener又是什么。
JNIMediaPlayerListener是干嘛用的,先来看看定义
// ----------------------------------------------------------------------------// ref-counted object for callbacksclass MediaPlayerListener: virtual public RefBase{public: virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) = 0;};
class JNIMediaPlayerListener: public MediaPlayerListener{public: JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz); ~JNIMediaPlayerListener(); virtual void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL);private: JNIMediaPlayerListener(); jclass mClass; // Reference to MediaPlayer class jobject mObject; // Weak ref to MediaPlayer Java object to call on};
该类继承与MediaPlayerListener,但实际上基类只是一个接口,从定义的函数notify来看是通知Java层一些消息的,看一下参数中有一个Parcel的对象,于是基本可以猜想这个notify的消息是从其他进程发过来的,至于是不是我们在后边遇到了再看,其他的三个参数比较容易理解,第一个是message的类型,后两个是附加的extra。
那么通过调用setListener将该对象设置给MediaPlayer的意图就很明显了,在C++端遇到一些消息后回传给Java层。
看一下notify函数的具体定义,看看猜测的是否正确。
void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj){ JNIEnv *env = AndroidRuntime::getJNIEnv(); if (obj && obj->dataSize() > 0) { jobject jParcel = createJavaParcelObject(env); if (jParcel != NULL) { Parcel* nativeParcel = parcelForJavaObject(env, jParcel); nativeParcel->setData(obj->data(), obj->dataSize()); env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, jParcel); env->DeleteLocalRef(jParcel); } } else { env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL); } if (env->ExceptionCheck()) { ALOGW("An exception occurred while notifying an event."); LOGW_EX(env); env->ExceptionClear(); }}
很明显在notify中调用了Java端post_event所指向的一个函数,而该函数即为postEventFromNative,这部分就不再赘述了,简单说一下,Java端的MediaPlayer有一个静态代码段,其中调用了native_init,而该函数实际是C++中的android_media_MediaPlayer_native_init,此函数中对post_event等其他Java端的成员做了索引。如果不熟悉可以自行学习一下JNI的知识,网上资料很全。
扯了这么多,我们来看看C++端的MediaPlayer做了些什么事情,先看看构造函数:
MediaPlayer::MediaPlayer(){ ALOGV("constructor"); mListener = NULL; mCookie = NULL; mStreamType = AUDIO_STREAM_MUSIC; mAudioAttributesParcel = NULL; mCurrentPosition = -1; mSeekPosition = -1; mCurrentState = MEDIA_PLAYER_IDLE; mPrepareSync = false; mPrepareStatus = NO_ERROR; mLoop = false; mLeftVolume = mRightVolume = 1.0; mVideoWidth = mVideoHeight = 0; mLockThreadId = 0; mAudioSessionId = AudioSystem::newAudioUniqueId(); AudioSystem::acquireAudioSessionId(mAudioSessionId, -1); mSendLevel = 0; mRetransmitEndpointValid = false;}
由此可见,其涉及到的东西还是挺多的,构造函数中没太多的东西,主要是一些常规初始化,并且分配了一个唯一的ID号给mAudioSessionId,然后通过AudioSystem报了个到罢了。其他的成员变量从名字上也能了解大概,先不一一赘述,遇到再说。到此创建MediaPlayer的工作算是已经圆满完成了,接下来就是setDataSource了。
public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { Log.d("MediaPlayer", "setDataSource path = " + path); setDataSource(path, null, null); }
中间步骤就算了,直接到C++来看看吧:
status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length){ ALOGV("setDataSource(%d, %" PRId64 ", %" PRId64 ")", fd, offset, length); status_t err = UNKNOWN_ERROR; const sp<IMediaPlayerService>& service(getMediaPlayerService()); if (service != 0) { sp<IMediaPlayer> player(service->create(this, mAudioSessionId)); if ((NO_ERROR != doSetRetransmitEndpoint(player)) || (NO_ERROR != player->setDataSource(fd, offset, length))) { player.clear(); } err = attachNewPlayer(player); } return err;}从语义上来看通过调用服务进程的create函数创建了一个IMediaPlayer代理对象,先看看IMediaPlayerService的Stub端。
具体怎么查找stub端是哪个类就不赘述了,要不写不完了,烦人的步骤直接跳过去,看看stub类MediaPlayerService的create方法:
sp<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client, int audioSessionId){ pid_t pid = IPCThreadState::self()->getCallingPid(); int32_t connId = android_atomic_inc(&mNextConnId); sp<Client> c = new Client( this, pid, connId, client, audioSessionId, IPCThreadState::self()->getCallingUid()); ALOGV("Create new client(%d) from pid %d, uid %d, ", connId, pid, IPCThreadState::self()->getCallingUid()); wp<Client> w = c; { Mutex::Autolock lock(mLock); mClients.add(w); } return c;}在服务端创建了一个内部类Client的对象,将其加入到mClients中,其实现在不需要看这些类的具体定义,只需要看看刚刚C++的MediaPlayer类的定义,是一个IMediaPlayerClient接口的Bn端子类,所以目的就很明确了,MediaPlayerServer创建一个Client对象,而该对象用于保存客户端MediaPlayer的代理对象,用于将server端的一些消息和状态通知给Client端的MediaPlayer。而Client这个类又是IMediaPlayer这个Binder接口的Bn端,这样一来client和server端就建立起了双向的连接,client(MediaPlayer)端可以请求server(MediaPlayerService::Client)端做自己需要的工作,而server端会返回client端需要的状态信息等,而MediaPlayerService::Client对象究竟是不是真正做事情的,还是另一个转发者,我们现在不需要关心,总之到目前为止,两端算是已经搭上线了,它们之间都是通过Binder接口进行回调来实现的,这样一来,刚刚我们说过的JNIMediaPlayerListener的作用也就明确了,将C++的client端收到的消息反馈给最终的Java端,也就是player的使用者。
那么我们再来看看setDataSource剩下的工作,通过得到的代理对象调用了server端的setDataSource方法,之后调用attachNewPlayer方法保存得到的代理对象:
status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length){ ALOGV("setDataSource fd=%d, offset=%lld, length=%lld", fd, offset, length); struct stat sb; int ret = fstat(fd, &sb); if (ret != 0) { ALOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno)); return UNKNOWN_ERROR; } ALOGV("st_dev = %llu", static_cast<uint64_t>(sb.st_dev)); ALOGV("st_mode = %u", sb.st_mode); ALOGV("st_uid = %lu", static_cast<unsigned long>(sb.st_uid)); ALOGV("st_gid = %lu", static_cast<unsigned long>(sb.st_gid)); ALOGV("st_size = %llu", sb.st_size); if (offset >= sb.st_size) { ALOGE("offset error"); ::close(fd); return UNKNOWN_ERROR; } if (offset + length > sb.st_size) { length = sb.st_size - offset; ALOGV("calculated length = %lld", length); } player_type playerType = MediaPlayerFactory::getPlayerType(this, fd, offset, length); sp<MediaPlayerBase> p = setDataSource_pre(playerType); if (p == NULL) { return NO_INIT; } // now set data source setDataSource_post(p, p->setDataSource(fd, offset, length)); return mStatus;}好吧,路漫漫,看来事情远没有看起来那么容易。
该函数前面一段是检验参数用的,包括通过Linux系统调用fstat来检查fd,忘了说了,在Binder调用中fd也是可以在不同进程中传递的,具体的请自行Google。到此为止,我们还没有看到任何一个真正的用于播放文件的player,那么此时主角应该要登场了,先看看MediaPlayerFactory::getPlayerType,这个函数返回一个index值来决定是创建哪种player,这个要创建的player就是真正用来播放文件的,它的任务是把数据取出正确的送给Codec,然后将解码数据拿出来送到相应的输出设备,player_type有三种,分别如下:
enum player_type { STAGEFRIGHT_PLAYER = 3, NU_PLAYER = 4, // Test players are available only in the 'test' and 'eng' builds. // The shared library with the test player is passed passed as an // argument to the 'test:' url in the setDataSource call. TEST_PLAYER = 5,};getPlayerType函数通过一个宏来展开,具体的内容不看了,如果有兴趣自己研究下,值得一提的是,这个函数在传入fd这种资源的时候,读代码很容易产生幻觉,注意其中StagefrightPlayerFactory的scoreFactory有两个if语句,这两个在新的android版本上默认是不会走的,所以最终通过调用getDefaultType返回了NU_PLAYER的enum值。
static player_type getDefaultPlayerType() { char value[PROPERTY_VALUE_MAX]; if (property_get("media.stagefright.use-awesome", value, NULL) && (!strcmp("1", value) || !strcasecmp("true", value))) { return STAGEFRIGHT_PLAYER; } return NU_PLAYER;}于是,顺理成章的,我们要创建的Player也就是最终的NuPlayerDriver了。最后通过createPlayer创建了NuPlayerDriver对象,过程不再赘述,都很简单。
值得注意的是在setDataSource_pre中因为NuPlayerDriver的hardwareOutput方法返回false,所以创建了一个AudioOutput对象,而这个对象包含了一个AudioTrack,老司机一看就能想到,这玩意最后用来播放音频了,至于是不是,我们以后再说吧。
好,接下来通过setDataSource_post的第二个参数调用了NuPlayerDriver的setDataSource函数。
status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) { ALOGV("setDataSource(%p) file(%d)", this, fd); Mutex::Autolock autoLock(mLock); if (mState != STATE_IDLE) { return INVALID_OPERATION; } mState = STATE_SET_DATASOURCE_PENDING; mPlayer->setDataSourceAsync(fd, offset, length); while (mState == STATE_SET_DATASOURCE_PENDING) { mCondition.wait(mLock); } return mAsyncResult;}本来看起来以为就要结束了,这时候又杀出来个mPlayer,头瞬间就炸了,看看定义。
NuPlayerDriver::NuPlayerDriver(pid_t pid) : mState(STATE_IDLE), mIsAsyncPrepare(false), mAsyncResult(UNKNOWN_ERROR), mSetSurfaceInProgress(false), mDurationUs(-1), mPositionUs(-1), mSeekInProgress(false), mLooper(new ALooper), mPlayerFlags(0), mAtEOS(false), mLooping(false), mAutoLoop(false), mStartupSeekTimeUs(-1) { ALOGV("NuPlayerDriver(%p)", this); mLooper->setName("NuPlayerDriver Looper"); mLooper->start( false, /* runOnCallingThread */ true, /* canCallJava */ PRIORITY_AUDIO); mPlayer = new NuPlayer(pid); mLooper->registerHandler(mPlayer); mPlayer->setDriver(this);}此时弄出来了个NuPlayer,为啥?翻翻设计模式吧。Google的代码就是这么傲娇。
看看NuPlayer的setDataSourceAsync方法,一看到这个后缀Async就有一种不翔的预感------消息机制:
void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) { sp<AMessage> msg = new AMessage(kWhatSetDataSource, this); sp<AMessage> notify = new AMessage(kWhatSourceNotify, this); sp<GenericSource> source = new GenericSource(notify, mUIDValid, mUID); status_t err = source->setDataSource(fd, offset, length); if (err != OK) { ALOGE("Failed to set data source!"); source = NULL; } msg->setObject("source", source); msg->post();}满满的消息机制,这玩意倒是不复杂,不过当消息漫天飞来飞去的时候,对代码阅读有点阻碍,不过算不上大问题,先不管这个消息了,看看具体做了些啥,先是new了两个Message然后创建了一个GenericSource对象,创建后调用source的setDataSource,通过传给GenericSource构造函数的参数可以知道这个notify的消息是用来让source发回调消息给Player的,而msg这个消息,显然是一个通知。那么这个Source是啥呢,简单来说,就是对media资源的一个封装而已。看看函数把。
status_t NuPlayer::GenericSource::setDataSource( int fd, int64_t offset, int64_t length) { resetDataSource(); mFd = dup(fd); mOffset = offset; mLength = length; // delay data source creation to prepareAsync() to avoid blocking // the calling thread in setDataSource for any significant time. return OK;}好嘛,简单直接,是吧,存下来就好了,因为还没确定你是不是真的要播放呢,没必要搞那么多事,万一你是撩完就跑呢。那就省点精力,能下一步做的事情就下一步做好了。
现在来看看这个msg消息是干嘛的好了,直接看看处理的地方,至于这个消息机制的具体知识看看下一篇再介绍好了,写微博好累,写微博好累,写微博好累,重要的事情说三次。。。
case kWhatSetDataSource: { ALOGV("kWhatSetDataSource"); CHECK(mSource == NULL); status_t err = OK; sp<RefBase> obj; CHECK(msg->findObject("source", &obj)); if (obj != NULL) { mSource = static_cast<Source *>(obj.get()); } else { err = UNKNOWN_ERROR; } CHECK(mDriver != NULL); sp<NuPlayerDriver> driver = mDriver.promote(); if (driver != NULL) { driver->notifySetDataSourceCompleted(err); } break; }都是类型转换,重点最后driver->notifySetDataSourceCompleted(err);
void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) { Mutex::Autolock autoLock(mLock); CHECK_EQ(mState, STATE_SET_DATASOURCE_PENDING); mAsyncResult = err; mState = (err == OK) ? STATE_UNPREPARED : STATE_IDLE; mCondition.broadcast();}改了下状态而已,刚才在调用NuPlayerDriver::setDataSource方法时,使用了异步机制,既然是异步的,那想知道调用的结果如何,只好等通知了,通知的方式就是Condition,也就是条件变量,这玩意很常见了,不明白的话Google一下pthread_cond_t就明白了。
好了,到此setDataSource就结束了,过程简单明了,真正麻烦的在后面的start调用,以后再说,流程图还没时间画,以后有时间在补一张。
- Android Media Player 框架分析-Nuplayer(1)
- Android Media Player 框架分析-AHandler AMessage ALooper
- Android NuPlayer播放框架
- Android NuPlayer播放框架
- 使用Media Player框架
- android Nuplayer 分析
- android HLS Nuplayer分析
- Nuplayer源代码分析1
- NuPlayer 分析(一)
- Media Player of Android
- android media player 状态机
- Android media player
- ③NuPlayer播放框架之类NuPlayer源码分析
- 【Android多媒体】Android5.0 NuPlayer多媒体框架【1】
- Media Player Classic - HC 源代码分析 2:核心类 (CMainFrame)(1)
- Media Player Classic - HC 源代码分析 1:整体结构
- Android多媒体之Media Player
- VLC Media Player for Android
- 练习
- 11282
- 高级加密标准(AES、Rijndael)
- Redis
- Unity的Rigidbody和Colider和CharacterController
- Android Media Player 框架分析-Nuplayer(1)
- 总结
- Java4android学习笔记28-29
- 字符串匹配的Boyer-Moore算法
- 掌握时区管理,提高工作效率
- 结构体中用字符串排序的sort自定义函数和 结构体的操作
- Spring Boot使用Swagger2构建RESTful文档
- Android线性布局
- Item21 Perfer std::make_unique and std::make_shared to direct use of new