Android Media Player 框架分析-Nuplayer(1)
由於工作崗位調整,開始接觸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的類來作為播放媒體資源的介面,在使用中我們通常會編寫以下的程式碼:
只需寥寥的幾行程式碼我們就可以在Android播放一個媒體檔案,當然MediaPlayer的介面遠不止示例中的幾個,其他的介面使用上都比較簡單,不做過多描述,下面放上一張MediaPlayer的狀態圖,以供參考,好吧,我承認因為懶,我從度娘上盜了張圖。MediaPlayer mp = new MediaPlayer() mp.setDataSource("/sdcard/filename.mp3"); mp.prepare(); mp.start();
我們的分析主要從剛才的示例程式碼開始,來看看我們簡單的幾個步驟會在內部執行什麼樣的操作。
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做了些什麼事情,先看看建構函式:
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呼叫,以後再說,流程圖還沒時間畫,以後有時間在補一張。