您的位置:首页 > 移动开发 > Android开发

Android Media Player 框架分析-Nuplayer(1)

2016-11-28 22:51 519 查看
由于工作岗位调整,开始接触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 void
android_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 callbacks
class 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做了些什么事情,先看看构造函数:

4000

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调用,以后再说,流程图还没时间画,以后有时间在补一张。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: