1. 程式人生 > >Android系統播放器MediaPlayer原始碼分析(一)

Android系統播放器MediaPlayer原始碼分析(一)

前言

對於MediaPlayer播放器的原始碼分析內容相對來說比較多,會從Java->JNI->C/C++慢慢分析,後面會慢慢更新。另外,部落格只作為自己學習記錄的一種方式,對於其他的不過多的評論。

MediaPlayerDemo

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private static final String TAG = "MainActivity";
    private SurfaceView surfaceView;
private MediaPlayer mediaPlayer; private SurfaceHolder holder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { surfaceView=
findViewById(R.id.surfaceView); mediaPlayer=new MediaPlayer(); holder = surfaceView.getHolder(); holder.addCallback(this); try { mediaPlayer.setDataSource("/sdcard/m.mp4"); mediaPlayer.prepare(); /** * 阻塞準備,多用於網路視訊流 */
mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mediaPlayer.start(); } }); } catch (IOException e) { e.printStackTrace(); } } @Override public void surfaceCreated(SurfaceHolder holder) { mediaPlayer.setDisplay(holder); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { mediaPlayer.release();//釋放 } }

從建立到setDisplay過程時序圖

建立過程

建立MediaPlayer物件有兩種,一種是create,一種是直接new,先來看create方法。

public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
            AudioAttributes audioAttributes, int audioSessionId) {
        try {
            //1. new一個MediaPlayer
            MediaPlayer mp = new MediaPlayer(); 
            //2.如果音訊相關處理為空,就建立一個音訊屬性
            final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                new AudioAttributes.Builder().build(); 
            mp.setAudioAttributes(aa); //3.設定音訊屬性
            mp.setAudioSessionId(audioSessionId); //4.設定聲音的會話id,因為聲音和視訊是分開渲染的,而且在後面根據音視訊進行分別解碼的時候,就需要根據id來判斷流是音訊還是視訊
            mp.setDataSource(context, uri);//5.設定資料來源
            if (holder != null) { // surfaceHolder,控制surface
                mp.setDisplay(holder); 
            }
            mp.prepare(); //準備
            return mp;
        } catch (IOException ex) {
        //省略,這個就是對各種Exception的處理
        }
        return null;
    }

總結,這裡可以看出來,使用create()建立播放器的時候,內部會new一個新的mediaplayer,並且設定了資料來源,並且做好了prepare準備工作,也就是說在外部再呼叫一下start()就可以播放音視訊資源了。
還有一種方式就是new MediaPlayer也就是我demo裡的那種,下面看下原始碼

 public MediaPlayer() {
        Looper looper; //定義一個looper
        //如果Mylooper不為空,就賦值
        if ((looper = Looper.myLooper()) != null) {
        	  //例項化mEventHandler物件
            mEventHandler = new EventHandler(this, looper);
        } //如果主執行緒的looper不為空,也可以賦值到looper中
        else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
        }
        //時間資料容器
        mTimeProvider = new TimeProvider(this);
        mOpenSubtitleSources = new Vector<InputStream>();

        /* Native setup requires a weak reference to our object.
         * It's easier to create it here than in C++.
         * 使用native_setup開始建立MediaPlayer了,此處為軟引用,為了在C++中更好的建立MediaPlayer
         */
        native_setup(new WeakReference<MediaPlayer>(this));
    }

EventHandler是為MediaPlayer的一個內部類,繼承與Handler,用來處理各種訊息:MEDIA_PREPARED,MEDIA_PLAYBACK_COMPLETE,MEDIA_STOPPED,MEDIA_SEEK_COMPLETE等等,分別呼叫不同的介面來進行處理。最後是呼叫了native層的方法來進行建立,那麼肯定需要先載入.so檔案,所以在MediaPlayer中有一段靜態程式碼塊,用來載入和連結庫檔案

 static {
        System.loadLibrary("media_jni");//media_jni.so
        native_init();
    }

進入到android_media_MediaPlayer.cpp分析,檔案目錄在:frameworks/base/media/jni/android_media_MediaPlayer.cpp

static void
android_media_MediaPlayer_native_init(JNIEnv *env)
{
    jclass clazz;//類的控制代碼
    //這裡是通過Native層呼叫Java層,獲取到MediaPlayer物件
    clazz = env->FindClass("android/media/MediaPlayer");
    if (clazz == NULL) {
        return;
    }
    // 獲取Java層的mNativeContext成員變數,實際上對應的是一個記憶體地址,就是jni層的MediaPlayer物件
    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }
    //獲取Java層靜態方法,第一個引數是類,第二個引數是方法名,第三個引數是該方法的簽名
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }

native_init方法是通過JNI呼叫Java層的MediaPlayer類,然後拿到mNativeContext的指標,接著呼叫了MediaPlayer.java的靜態方法postEventFromNative,這個方法的作用就是把Native事件回撥到Java層,然後使用EventHandler post事件回到主執行緒,使用軟引用指向原生的MediaPlayer,是為了保證native層的程式碼是安全的。

static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
   //在jni層建立了一個MediaPlayer
    sp<MediaPlayer> mp = new MediaPlayer();
    //給MediaPlayer建立了一個一個listener
    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.將這個建立好的jni物件儲存到Java中
    setMediaPlayer(env, thiz, mp);
}

來看下這個JNIMediaPlayerListener,也是在這個cpp檔案裡

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
};

有個notify方法,從下面可以看出,它的作用就是從JNI層向Java層通知

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());
            //向Java層傳送通知事件
            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);
    }
   
}

fields.post_event也就是Java層的MediaPlayer.java中的postEventFromNative,可以看下它的實現

 private static void postEventFromNative(Object mediaplayer_ref,
                                            int what, int arg1, int arg2, Object obj)
    {
        final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
        if (mp.mEventHandler != null) {
            Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
            mp.mEventHandler.sendMessage(m);
        }
    }
    上面也說過是使用mEventHandler來處理的

來看下setMediaPlayer(env, thiz, mp)這個函式

static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
{
    Mutex::Autolock l(sLock);//鎖
    sp<MediaPlayer> old = (MediaPlayer*)env->GetLongField(thiz, fields.context); 
    if (player.get()) {
        player->incStrong((void*)setMediaPlayer);
    }
    if (old != 0) {
        old->decStrong((void*)setMediaPlayer);
    }
    //將新的Player物件儲存在Java層的mNativeContext中
    env->SetLongField(thiz, fields.context, (jlong)player.get());
    return old;
}

總結一下,在Java使用new或者create()建立MediaPlayer的時候,最後都會呼叫到native_up函式,這個函式的作用就是建立c++層的MediaPlayer以及去設定一些回撥用的listener,然後將其儲存在Java層中,到此建立過程就完成了。

setDataSource過程

 private void setDataSource(String path, String[] keys, String[] values,List<HttpCookie> cookies)
            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
        final Uri uri = Uri.parse(path);
        final String scheme = uri.getScheme();
        if ("file".equals(scheme)) {
            path = uri.getPath();
        } else if (scheme != null) {
            // 1、處理非檔案資源
            nativeSetDataSource(
            MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
                path,
                keys,
                values);
            return;
        }
        //2、處理檔案型別
        final File file = new File(path);
        if (file.exists()) {
            FileInputStream is = new FileInputStream(file);
            FileDescriptor fd = is.getFD();
            setDataSource(fd);
            is.close();
        } else {
            throw new IOException("setDataSource failed.");
        }
    }

先來看下處理檔案型別的,點進去發現最後都是調到了native層,

static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    //省略,一些異常程式碼
    //獲取path變數
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor)// 對mp->setDataSource(fd, offset, length)函式得到的狀態,進行相應的通知
    process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}

static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
{
    if (exception == NULL) {  // Don't throw exception. Instead, send an event.
       if (opStatus != (status_t) OK) { //不丟擲異常,傳送一個事件來替代異常
            sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
            //通知MEDIA_ERROR,也就是傳送了一個通知事件
            if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);
        }
    } else {  // Throw exception! 省略了
    }
}

再來看下非檔案型別的:

static void
android_media_MediaPlayer_setDataSourceAndHeaders(
        JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,jobjectArray keys, jobjectArray values) {
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
  	//省略異常程式碼
    const char *tmp = env->GetStringUTFChars(path, NULL);
    if (tmp == NULL) {  // Out of memory
        return;
    }
    ALOGV("setDataSource: path %s", tmp);
    String8 pathStr(tmp);
    //釋放
    env->ReleaseStringUTFChars(path, tmp);
    tmp = NULL;
    // We build a KeyedVector out of the key and val arrays
    KeyedVector<String8, String8> headersVector;
    if (!ConvertKeyValueArraysToKeyedVector(
            env, keys, values, &headersVector)) {
        return;
    }
    sp<IMediaHTTPService> httpService;
    if (httpServiceBinderObj != NULL) {
    //通過Binder機制將httpServiceBinderObj傳給IPC並返回binder
    //後面會具體分析,這裡只是JNI層
        sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
        //強制轉換成IMediaHTTPService
        httpService = interface_cast<IMediaHTTPService>(binder);
    }
	//這裡和上面的檔案型別的操作一樣
    status_t opStatus =
        mp->setDataSource(
                httpService,
                pathStr,
                headersVector.size() > 0? &headersVector : NULL);
    process_media_player_call(
            env, thiz, opStatus, "java/io/IOException",
            "setDataSource failed." );
}

setDisPlay過程

setDisplay是用來設定播放視訊的

 public void displaysetDisplay(SurfaceHolder sh) {
        mSurfaceHolder = sh;//1. surfaceHolder用來控制Surface的
        Surface surface;
        if (sh != null) {
            surface = sh.getSurface();
        } else {
            surface = null;
        }
        _setVideoSurface(surface);//2. 給視訊設定Surface,Surface通俗點說就是畫畫的地方
        updateSurfaceScreenOn();//3.將畫面更新到螢幕上
    }

_setVideoSurface對應JNI層的程式碼是:

static void
setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    //省略丟擲異常程式碼
    //減少Java層mNativeSurfaceTexture儲存的對Jni層之前對ISurfaceTexture的引用
    decVideoSurfaceRef(env, thiz);
    //IGraphicBufferProducer
    sp<IGraphicBufferProducer> new_st;
    if (jsurface) {
    //得到Java層的Surface
        sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
        if (surface != NULL) {//不為空,獲取IGraphicBufferProducer
            new_st = surface->getIGraphicBufferProducer();
           	//省略  為空丟擲異常
            }
            //incStrong,智慧指標的引用計數 +1
            new_st->incStrong((void*)decVideoSurfaceRef);
        } else {
         //異常程式碼
        }
    }
    //重新設定Java層的mNativeSurfacetexture儲存jni層的物件的引用
    env->SetLongField(thiz, fields.surface_texture, (jlong)new_st.get());

    //如果mediaPlayer還沒有被初始化,setDataSource就會失敗,,
    //但是在setDataSource之前就呼叫了setDisplay(),
    //在prepare/prepareAsync中呼叫setVideoSurfaceTexture,就會覆蓋這個情況
    mp->setVideoSurfaceTexture(new_st);
}

static void
decVideoSurfaceRef(JNIEnv *env, jobject thiz)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL) {
        return;