[TOC]
概述 MediaPlayer是Android中的一个多媒体播放类,我们能通过它控制音视频流或本地音视频资源的播放过程。
MediaPlayer类用于视频/音频文件的播放控制。本节主要覆盖MediaPlayer如下知识点。
• MediaPlayer的状态图
• Idle状态
• End状态
• Error状态
• Initialized状态
• Prepared状态
• Preparing状态
• Started状态
• Paused状态
• Stopped状态
• PlaybackCompleted状态
Idle状态及End状态 在MediaPlayer创建实例或者调用reset函数后,播放器就被创建了,这时处于Idle(就绪)状态,调用release函数后,就会变成End(结束)状态,在这两种状态之间的就是MediaPlayer的生命周期。
Error状态 在构造一个新MediaPlayer或者调用reset函数后,上层应用程序调用getCurrentPosition、getVideoHeight、getDuration、getVideoWidth、setAudioStreamType(int)、setLooping(boolean)、setVolume(float,float)、pause、start、stop、seekTo(int)、prepare、prepareAsync这些函数会出错。如果调用reset函数后再调用它们,用户提供的回调函数OnErrorListener.onError将触发MediaPlayer状态到Error(错误)状态,所以一旦不再使用MediaPlayer,就需要调用release函数,以便MediaPlayer资源得到合理释放。
当MediaPlayer处于End(结束)状态时,它将不能再被使用,这时不能再回到MediaPlayer的其他状态,因为本次生命周期已经终止。
由于支持的音视频格式分辨率过高,输入数据流超时,或者其他各种各样的原因将导致播放失败。在这种错误的条件下,如果用户事先通过setOnErrorListener注册过OnErrorListener,当player内部调用OnErrorListener.onError回调函数时,将会返回错误信息。一旦有错误,MediaPlayer会进入Error(错误)状态,为了重新使用MediaPlayer,调用reset函数,这时将重新恢复到Idle(就绪)状态,所以需要给MediaPlayer设置错误监听,出错后就可以从播放器内部返回的信息中找到错误原因。
Initialized状态 当调用setDataSource(FileDescriptor)、setDataSource(String)、setDataSource(Context, Uri)、setDataSource(FileDescriptor, long, long)其中一个函数时,将传递MediaPlayer的Idle状态变成Initialized(初始化)状态,如果setDataSource在非Idle状态时调用,会抛出IllegalStateException异常。当重载setDataSource时,需要抛出IllegalArgumentException和IOException这两个异常。
Prepared状态 当外部调用MediaPlayer.create(this, "http://www.xxx.mp4")时,进入MediaPlayer的创建过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public static MediaPlayer create (Context context, Uri uri, SurfaceHolder holder, AudioAttributes audioAttributes, int audioSessionId) { try { MediaPlayer mp = new MediaPlayer (audioSessionId); final AudioAttributes aa = audioAttributes != null ? audioAttributes : new AudioAttributes .Builder().build(); mp.setAudioAttributes(aa); mp.native_setAudioSessionId(audioSessionId); mp.setDataSource(context, uri); if (holder != null ) { mp.setDisplay(holder); } mp.prepare(); return mp; } catch (IOException ex) { Log.d(TAG, "create failed:" , ex); } catch (IllegalArgumentException ex) { Log.d(TAG, "create failed:" , ex); } catch (SecurityException ex) { Log.d(TAG, "create failed:" , ex); } return null ; }
以上代码可以总结为,当MediaPlayer通过create的方式创建播放器时,内部new出MediaPlayer对象,并setDataSource,做好prepare的动作。这时外部只需调用start函数,就能播放音视频资源了。
实例化MediaPlayer有如下两种方式。
(1)可以使用直接new的方式:
1 MediaPlayer mp = new MediaPlayer ();
(2)也可以使用create的方式,如:
1 MediaPlayer mp = new MediaPlayer (this ,R.raw.test);
上面两种实例化MediaPlayer的方式,都要经过new MediaPlayer,下面看看构造中做了什么操作。
播放页处理主页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private MediaPlayer (int sessionId) { super (new AudioAttributes .Builder().build(), AudioPlaybackConfiguration.PLAYER_TYPE_JAM_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>(); AttributionSource attributionSource = AttributionSource.myAttributionSource(); if (attributionSource.getPackageName() == null ) { attributionSource = attributionSource.withPackageName("" ); } try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) { native_setup(new WeakReference <MediaPlayer>(this ), attributionSourceState.getParcel()); } baseRegisterPlayer(sessionId); }
接下来看Native层如何创建一个MediaPlayer。在介绍native_setup之前,请注意一般都是在静态代码块中加载.so文件的,在MediaPlayer中有一段静态代码块,用于加载和链接库文件media_jni.so,早于构造函数,在加载类时就执行。一般全局性的数据、变量都可以放在这里。下面是加载和链接media_jni.so文件的代码:
1 2 3 4 5 6 static { System.loadLibrary("media_jni" ); native_init(); } private static native final void native_init () ;
我们看看native_init的函数的具体实现:
下面开始进入android_media_MediaPlayer.cpp分析,第一个函数android_media_MediaPlayer_native_init就是从Java静态代码块调过来的native_init:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static void android_media_MediaPlayer_native_init (JNIEnv *env) { jclass clazz; clazz = env->FindClass ("android/media/MediaPlayer" ); if (clazz == NULL ) { return ; } fields.context = env->GetFieldID (clazz, "mNativeContext" , "J" ); if (fields.context == NULL ) { return ; } fields.post_event = env->GetStaticMethodID (clazz, "postEventFromNative" , "(Ljava/lang/Object;IIILjava/lang/Object;)V" ); if (fields.post_event == NULL ) { return ; } gPlaybackParamsFields.init (env); gSyncParamsFields.init (env); gVolumeShaperFields.init (env); }
上面这种方式是通过JNI调用Java层的MediaPlayer类,然后拿到mNativeContext的指针,接着调用了MediaPlayer.java中的静态方法postEventFromNative,把Native的事件回调到Java层,使用EventHandler post事件回到主线程中,用软引用指向原生的MediaPlayer,以保证Native代码是安全的。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 == null ) { return ; } if (mp.mEventHandler != null ) { Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); mp.mEventHandler.sendMessage(m); } }
之前我们在Java层的MediaPlayer.java文件的构造函数中,分析到最后有一个native_setup,在android_media_MediaPlayer.cpp中找到对应的函数,代码如下:
1 2 3 4 5 6 7 private native void native_setup (Object mediaplayerThis, @NonNull Parcel attributionSource) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static void android_media_MediaPlayer_native_setup (JNIEnv *env, jobject thiz, jobject weak_this, jobject jAttributionSource) { ALOGV ("native_setup" ); Parcel* parcel = parcelForJavaObject (env, jAttributionSource); android::content::AttributionSourceState attributionSource; attributionSource.readFromParcel (parcel); sp<MediaPlayer> mp = sp<MediaPlayer>::make (attributionSource); if (mp == NULL ) { jniThrowException (env, "java/lang/RuntimeException" , "Out of memory" ); return ; } sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener (env, thiz, weak_this); mp->setListener (listener); setMediaPlayer (env, thiz, mp); }
可以看到会设置一些回调用的listener及创建C++中的MediaPlayer对象。
setDataSource过程 上面就是MediaPlayer的构造过程。构造后接下来要设置数据源,进而到了setDataSource过程,下面看看setDataSource做了什么操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 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 ) { nativeSetDataSource( MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies), path, keys, values); return ; } final File file = new File (path); try (FileInputStream is = new FileInputStream (file)) { setDataSource(is.getFD()); } } private native void nativeSetDataSource ( IBinder httpServiceBinder, String path, String[] keys, String[] values) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
调用nativeSetDataSource代码逻辑开始进入JNI层,
1 2 3 4 5 6 { "nativeSetDataSource" , "(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;" "[Ljava/lang/String;)V" , (void *)android_media_MediaPlayer_setDataSourceAndHeaders },
我们看一下android_media_MediaPlayer_setDataSourceAndHeaders方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 static void android_media_MediaPlayer_setDataSourceAndHeaders ( JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path, jobjectArray keys, jobjectArray values) { sp<MediaPlayer> mp = getMediaPlayer (env, thiz); if (mp == NULL ) { jniThrowException (env, "java/lang/IllegalStateException" , NULL ); return ; } if (path == NULL ) { jniThrowException (env, "java/lang/IllegalArgumentException" , NULL ); return ; } const char *tmp = env->GetStringUTFChars (path, NULL ); if (tmp == NULL ) { return ; } ALOGV ("setDataSource: path %s" , tmp); String8 pathStr (tmp) ; env->ReleaseStringUTFChars (path, tmp); tmp = NULL ; KeyedVector<String8, String8> headersVector; if (!ConvertKeyValueArraysToKeyedVector ( env, keys, values, &headersVector)) { return ; } sp<IMediaHTTPService> httpService; if (httpServiceBinderObj != NULL ) { sp<IBinder> binder = ibinderForJavaObject (env, httpServiceBinderObj); 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." ); }
至此,setDataSource过程就完成了。这里需要注意两点,一点是从Java→JNI→C++的正向调用过程(前面从Java层到Native层都是正向过程),一点是C++→JNI→Java的过程(如mp->setDataSource( httpService, pathStr,headersVector.size() > 0? &headersVector : NULL),这样来回调的好处是什么?好处有如下这几点。
安全性,封装在Native层的代码是so形式的,破坏性风险小。
效率高,在运行速度上C++执行时间短,且底层也是用C++语言编写的。对于复杂的渲染及对时间要求高的渲染,放在Native层是最好不过的选择。
连通性,正向调用将值传入,反向调用把处理过的值通知回去。相当于一根管道。
setDisplay过程 接下来看看在setDataSource之后,开始进行的mp.setDisplay(holder):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void setDisplay (SurfaceHolder sh) { mSurfaceHolder = sh; Surface surface; if (sh != null ) { surface = sh.getSurface(); } else { surface = null ; } _setVideoSurface(surface); updateSurfaceScreenOn(); }
对于上面代码中的_setVideoSurface,同样在android_media_MediaPlayer.cpp中找到其对应的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 static void setVideoSurface (JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive) { sp<MediaPlayer> mp = getMediaPlayer (env, thiz); if (mp == NULL ) { if (mediaPlayerMustBeAlive) { jniThrowException (env, "java/lang/IllegalStateException" , NULL ); } return ; } decVideoSurfaceRef (env, thiz); sp<IGraphicBufferProducer> new_st; if (jsurface) { sp<Surface> surface (android_view_Surface_getSurface(env, jsurface)) ; if (surface != NULL ) { new_st = surface->getIGraphicBufferProducer (); if (new_st == NULL ) { jniThrowException (env, "java/lang/IllegalArgumentException" , "The surface does not have a binding SurfaceTexture!" ); return ; } new_st->incStrong ((void *)decVideoSurfaceRef); } else { jniThrowException (env, "java/lang/IllegalArgumentException" , "The surface has been released" ); return ; } } env->SetLongField (thiz, fields.surface_texture, (jlong)new_st.get ()); mp->setVideoSurfaceTexture (new_st); }
这里有如下几个概念需要理解。
SurfaceTexture:SurfaceTexture是Android 3.0(API 11)加入的一个类。这个类跟SurfaceView很像,可以从视频解码里面获取图像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收图像流之后,不需要显示出来。SurfaceTexture不需要显示到屏幕上,因此我们可以用SurfaceTexture接收解码出来的图像流,然后从SurfaceTexture中取得图像帧的副本进行处理,处理完毕后再送给另一个SurfaceView用于显示。
Surface:处理被屏幕排序的原生的Buffer,Android中的Surface就是一个用来画图形(graphic)或图像(image)的地方。对于View及其子类,都是画在Surface上的,各Surface对象通过SurfaceFlinger合成到frameBuffer。每个Surface都是双缓冲的(实际上就是两个线程,一个渲染线程,一个UI更新线程),它有一个backBuffer和一个frontBuffer。在Surface中创建的Canvas对象,可用来管理Surface绘图操作,Canvas对应Bitmap,存储Surface中的内容。
SurfaceView:在Camera、MediaRecorder、MediaPlayer中SurfaceView经常被用来显示图像。SurfaceView是View的子类,实现了Parcelable接口,其中内嵌了一个专门用于绘制的Surface,SurfaceView可以控制这个Surface的格式和尺寸,以及Surface的绘制位置。可以理解Surface就是管理数据的地方,SurfaceView就是展示数据的地方。
SurfaceHolder:顾名思义,是一个管理SurfaceHolder的容器。SurfaceHolder是一个接口,其可被理解为一个Surface的监听器。通过回调函数addCallback(SurfaceHolder.Callback callback)监听Surface的创建,通过获取Surface中的Canvas对象,锁定之。所得到的Canvas对象在完成修改Surface中的数据后,释放同步锁,并提交改变Surface的状态及图像,展示新的图像数据。
最后总结一下,SurfaceView中调用getHolder函数,可以获得当前SurfaceView中的Surface对应的SurfaceHolder,SurfaceHolder开始对Surface进行管理操作。这里按MVC模式可以更好地理解M:Surface(图像数据)、V:SurfaceView(图像展示)、C:SurfaceHolder(图像数据管理)。MediaPlayer.java中的setDisplay操作就是对将要显示的视频进行预设置。以上就是setDisplay的过程,Java层中setDisplay的最后一行,就是通过JNI返回的Surface,时时做好更新准备。
prepare调用 上节中我们分析了MediaPlayer从创建到setDataSource的过程,尽管分析了代码,但是没有从MediaPlayer生态上认识各类库之间的依赖调用关系。
MediaPlayer的核心的源文件所在的目录:
1 2 3 4 frameworks/av/media/libmedia/include/media/IMediaPlayerClient.h frameworks/av/media/libmedia/include/media/mediaplayer.h frameworks/av/media/libmedia/include/media/IMediaPlayer.h frameworks/av/media/libmedia/include/media/IMediaPlayerService.h
在这些头文件中,mediaplayer.h提供了对上层的接口,而其他的几个头文件提供的是一些接口类(即包含了纯虚函数的类),这些接口类必须被实现类继承才能够使用。
MediaPlayer各个具体类之间的依赖关系图如图所示。
在运行的时候,整个MediaPlayer可以大致上分成Client和Server两个部分,它们分别在两个进程中运行,它们之间使用Binder机制实现IPC通信。从框架结构上看,IMediaPlayer Service.h、IMediaPlayerClient.h和mediaplayer.h这3个头文件中定义了MediaPlayer的接口和架构,在目录中有专门的MediaPlayerService.cpp和mediaplayer.cpp文件对应上面3个头文件,用于MediaPlayer架构的实现。
在给播放器设置数据源且展现了Surface后,你应当开始调用prepare或prepareAsync函数。对于文件类型,调用prepare函数将暂时阻塞,因为prepare是一个同步函数,直到MediaPlayer已经准备好数据即将播放,也就是播放器回调了onPrepared函数,进入Prepared状态。
prepare函数的执行过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void prepare () throws IOException, IllegalStateException { _prepare(); scanInternalSubtitleTracks(); synchronized (mDrmLock) { mDrmInfoResolved = true ; } }
Native层的android_media_MediaPlayer_prepare函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void android_media_MediaPlayer_prepare (JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer (env, thiz); if (mp == NULL ) { jniThrowException (env, "java/lang/IllegalStateException" , NULL ); return ; } sp<IGraphicBufferProducer> st = getVideoSurfaceTexture (env, thiz); mp->setVideoSurfaceTexture (st); process_media_player_call ( env, thiz, mp->prepare (), "java/io/IOException" , "Prepare failed." ); }
上面代码中的getVideoSurfaceTexture获取一个IGraphicBufferProducer类型指针,2中是setVideoSurfaceTexture,主要给MediaPlayer传入IGraphicBufferProducer。这里IGraphicBufferProducer就是App和BufferQueue的重要桥梁,GraphicBufferProducer承担着单个应用进程中的UI显示需求,与BufferQueue打交道的就是它。
BpGraphicBufferProducer是GraphicBufferProducer在客户端这边的代理对象,负责和SurfaceFlinger交互,GraphicBufferProducer通过gbp(IGraphicBufferProducer类对象)向BufferQueue获取Buffer,然后填充UI信息,填充完毕会通知SurfaceFlinger。
process_media_player_call是对MediaPlayer调用prepare函数后是否有异常的检测,如果出现参数不合法,或是I/O异常,就会抛出异常。
我们知道MediaPlayer还有一个prepareAsync函数,前面的思路都是顺着MediaPlayer中的create函数来的。如果是下面这种场景,即一个网络URL被发送过来,就是网络流数据传入MediaPlayer,这时就要用到prepareAsync函数了:
prepareAsync调用 1 2 3 4 5 public native void prepareAsync () throws IllegalStateException;
可以看到prepareAsync是一个Native方法,那我们什么时候回用到这个方法呢?
如果是下面这种场景,即一个网络URL被发送过来,就是网络流数据传入MediaPlayer,这时就要用到prepareAsync函数了
分析MediaPlayer中的prepareAsync函数:在setDataSource中且展现了Surface后,开始调用prepare或prepareAsync函数,对于网络视频流类型,尽量调用prepareAsync函数,因为是异步的,不会导致没有足够的数据影响起播。
下面分析android_media_MediaPlayer_prepareAsync函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static void android_media_MediaPlayer_prepareAsync (JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer (env, thiz); if (mp == NULL ) { jniThrowException (env, "java/lang/IllegalStateException" , NULL ); return ; } sp<IGraphicBufferProducer> st = getVideoSurfaceTexture (env, thiz); mp->setVideoSurfaceTexture (st); process_media_player_call ( env, thiz, mp->prepareAsync (), "java/io/IOException" , "Prepare Async failed." ); }
可以看到基本和android_media_MediaPlayer_prepare调用是一样的
除了最后process_media_player_call中的mp->prepareAsync在判断状态时不一样,它的操作结果经过回调通知Java层。
下面看看frameworks/av/media/libmedia/mediaplayer.cpp中的prepareAsync函数(C++代码):
1 2 3 4 5 6 7 8 9 10 11 status_t MediaPlayer::prepareAsync () { ALOGV ("prepareAsync" ); Mutex::Autolock _l(mLock); return prepareAsync_l (); }
接着分析prepareAsync_l函数实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 status_t MediaPlayer::prepareAsync_l () { if ( (mPlayer != 0 ) && ( mCurrentState & (MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) { if (mAudioAttributesParcel != NULL ) { mPlayer->setParameter (KEY_PARAMETER_AUDIO_ATTRIBUTES, *mAudioAttributesParcel); } else { mPlayer->setAudioStreamType (mStreamType); } mCurrentState = MEDIA_PLAYER_PREPARING; return mPlayer->prepareAsync (); } ALOGE ("prepareAsync called in state %d, mPlayer(%p)" , mCurrentState, mPlayer.get ()); return INVALID_OPERATION; }
下面继续分析prepareAsync函数,mp->prepareAsync对应的BnMediaPlayer操作如下:
接着分析MediaPlayerService::Client::prepareAsync函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 status_t MediaPlayerService::Client::prepareAsync (){ ALOGV ("[%d] prepareAsync" , mConnId); sp<MediaPlayerBase> p = getPlayer (); if (p == 0 ) return UNKNOWN_ERROR; status_t ret = p->prepareAsync (); #if CALLBACK_ANTAGONIZER ALOGD ("start Antagonizer" ); if (ret == NO_ERROR) mAntagonizer->start (); #endif return ret; }
接着分析MediaPlayerService::Client::prepareAsync函数:
这里调用了AwesomePlayer的prepareAsync函数: