状态图及生命周期
MediaPlayer类用于音视频文件的播放控制
1.MediaPlayer的状态图
图中的椭圆代表MediaPlayer驻留的状态。
弧代表播放控制且驱动MediaPlayer状态进行过渡。
有两种类型的弧,单箭头表示同步函数调用,双箭头表示异步函数调用。
2.Idle
状态及End
状态
在MediaPlayer创建实例或者调用reset
函数后,播放器就创建了,此时处于Idle(就绪)状态,调用release
函数后,就会变成End(结束)状态,这两种状态之间的就是MediaPlayer的生命周期。
3.Error
状态
在构造一个新MediaPlayer或者调用reset函数后,上层应用程序调用getCurrentPosition、getVedioHeight、getDuration、getVedioWidth、setAudioStreamType(int)、setLooping(boolean)、setVolume(float,float)、pause、start、stop、seekTo(int)、prepare、prepareAsync这些函数会出错。如果调用reset函数后再调用它们,用户提供的回调函数OnErrorListener
.onError将触发MediaPlayer状态到Error(错误)状态,所以一旦不再使用MediaPlayer,就需要调用release函数,以便MediaPlayer资源得到合理释放。
当其处于End状态时,它将不能再使用,也不能回到其他状态,因为本次生命周期已经终止。
由于支持的音视频格式分辨率过高,输入数据流超时,或者其他原因导致播放失败。如果用户事先通过setOnErrorListener注册过OnErrorListener,当player内部调用OnErrorListener.onError回调函数时,将会返回错误信息。一旦有错误它就会进入Error状态,为了重新使用播放器,可以调用reset函数,此时重新恢复到Idle状态,所以需要给播放器设置错误监听,出错后就可以从播放器内部返回的信息中找到错误原因。
4.Initialized
状态
当调用setDataSource类函数时,将传递播放器的Idle状态到Initialized(初始化)状态,如果setDataSource在非Idle状态时调用,会抛出IllegalStateException
异常。当重载setDataSource时,需要抛出IllegalArgumentException
和IOException
异常。
5.Prepared
状态
有两种方式可以到准备状态(同步,异步)。同步方式主要使用本地音视频文件(调用prepare
方法变成prepared状态),异步方式主要使用网络数据,需要缓冲数据(调用prepareAsync
方法变成preparing
状态,最后到prepared状态)。如果应用层事先注册过setOnPreparedListener
,播放器内部将回调用户设置的OnPreparedListener中的onPrepared回调函数。注意:preparing是一个瞬间状态,时间很短。
6.Started
状态
进入prepared状态后,上层应用即可设置一些属性如音量等。在播放控制开始之前,必须调用start
函数并成功返回,此时播放器状态开始由prepared状态变成started状态。当处于started状态时,如果用户事先注册过setOnBufferingUpdateListener
,播放器内部会开始回调OnBufferingUpdateListener
,播放器内部会开始回调OnBufferingUpdateListener.onBufferingUpdate
,这个回调函数主要使应用程序保持跟踪音视频流的buffering(缓冲)status
,如果播放器已经处于started状态,再调用start函数是没有任何作用的。
7.Paused
状态
播放器在播放控制时可以是paused(暂停)和Stopped(停止)状态的,且当前的播放时进度可以被调整,当调用MediaPlayer.pause函数时,MediaPlayer从started变成paused状态,这个过程瞬间完成,而反过来在播放器内部是异步过程的。在状态更新并调用isPlaying
函数前,将有一些耗时。已经缓冲过的数据流,也要耗费数秒。
当start函数从paused状态恢复过来时,playback恢复之前暂停时的位置,接着开始播放,此时又变回started状态。如果在paused状态下调用pause函数,状态维持不变。
8.Stopped
状态
当调用stop函数时,播放器无论正处于started、paused、prepared、playbackcompleted之中哪一种状态,都将进入stopped状态。一旦处于该状态,playback
将不能开始,直到重新调用prepare
类函数,且处于prepared状态下才可以开始。在该状态下调用stop函数将会保持状态。在Seek操作完成后,如果是现在MediaPlayer注册了setOnSeekCompleteListener
,播放器内部将回调OnSeekComplete.onSeekComplete
函数。当然seekTo函数也可以在其他状态下被调用如paused、prepared、playbackcompleted状态。
9.PlaybackCompleted
状态
当前播放的位置可以通过getCurrentPosition
函数来获取,通过它可以跟踪播放进度。当播放到数据流的末尾时,一次播放过程完成。在MediaPlayer中实现调用setLooping(boolean)
并设置为true表示循环播放。播放器仍然处于Started状态。如果设置为false并且事先注册过setOnCompletionListener
,内部会回调OnCompletion.onCompletion
函数,这就表明播放器开始进入PlaybackCompleted(播放完成)状态。此时调用start函数将重启播放器从头开始播放数据。
需要理解的概念
1.SurfaceTexture
它是Android3.0(API11)加入的一个类,和SurfaceView很像,可以从视频解码里面获取图像流(image stream)。和SurfaceView不同的是,SurfaceTexture在接受图像流之后,不需要显示出来。SurfaceTexture不需要显示到屏幕上,因此我们可以用SurfaceTexture接收解码出来的图像流,然后从它之中取得图像帧的副本进行处理,处理完毕后再送给另一个SurfaceView进行展示
2.Surface
处理被屏幕排序的原生的Buffer,Android中的Surface就是一个用来画图形(graphic)或图像(image)的地方。对于View及其子类,都是画在Surface上的,各Surface对象通过SurfaceFlinger
合成到frameBuffer
。每个Surface都是双缓冲的(实际上就是两个线程,一个渲染线程,一个UI更新线程),它有一个backBuffer
和一个frontBuffer
。在Surface中创建的Canvas
对象,可用来管理Surface绘图操作,Canvas对应Bitmap,存储Surface中的内容
3.SurfaceView
在Camera、MediaRecorder、MediaPlayer中SurfaceView经常被用来显示图像。它是View的子类,实现了Parcelable接口,其中内嵌了一个专门用于绘制的Surface
,SurfaceView可以控制这个Surface的格式和尺寸,以及Surface的绘制位置。可以理解Surface就是管理数据的地方,SurfaceView就是展示数据的地方
4.SurfaceHolder
它是一个管理SurfaceHolder的容器。SurfaceHolder是一个接口,其可悲理解为一个Surface的监听器。通过回调函数addCallback(SurfaceHolder.Callback callback)
监听Surface的创建,通过获取Surface中的Canvas对象,把它锁定。得到的Canvas对象在完成修改Surface中的数据后释放同步锁,并提交改变Surface的状态及图像,展示新的图像数据
创建、setDataSource、setDisplay过程
1.从创建到setDisplay过程
从时序图可以看到,通过getService
从ServiceManager
获取对应的MediaPlayerService
,然后调用native_up
函数创建播放器,接着调用setDataSource
把URL地址传入底层。当准备好后,通过setDisplay
传入SurfaceHolder
,以便将解码出的数据放到SurfaceHolder中的Surface
,最后显示在SurfaceView
中。
2.创建过程
create源码
当外部调用MediaPlayer.create时进入MediPlayer的创建过程。其内部会new出MediaPlayer
对象,并setDataSource
,做好prepare
的动作。此时外部只需调用start
函数就能播放音视频资源了。
实例化MediaPlayer的方式
1.直接new
的方式
MediaPlayer mp=new MediaPlayer();
2.也可以使用create
的方式,如
MediaPlayer mp=MediaPlayer.create(this,R.raw.test); 此时就不用调用setDataSource了
MediaPlayer构造器源码
baseRegisterPlayer
函数使用了Binder为我们创建的MediaPlayer注册一张身份证,里面有着它的相关信息
在我们了解native_setup之前我们可先着眼于MediaPlayer中的一段静态代码块,一般都是在静态代码块中加载.so
文件
上图加载和链接库文件media_jni.so
,早于构造函数,在加载类时就执行。一般全局性的数据、变量都可以放在这里
接下来进入android_media_MediaPlayer.cpp
分析,函数android_media_MediaPlayer_native_init
就是从静态代码块调过来的native_init
android_media_MediaPlayer_native_init部分代码
上面的方式是通过JNI调用java层的MediaPlayer类,然后拿到mNativeContext
的指针,接着调用了MediaPlayer.java中的静态方法postEventFromNative
,把Native的事件回调到java层,使用了EventHandler post
事件回到主线程中,用软引用指向原生的MediaPlayer,以保证Native代码是安全的,代码如下图
之前在java层中MediaPlayer.java中的构造器中最后一行有一个native_setup
,我们在android_media_MediaPlayer.cpp中找到对应的函数
可以看到会设置一些回调用的listener及创建C++中的MediaPlayer对象
以上就是MediaPlayer的构造过程,构造后要设置数据源
3.setDataSource过程
先看setDataSource中传入的参数是文件描述符的情况
接下来进入JNI层,发现找不到android_media_MediaPlayer_setDataSource函数,但发现有一个函数名映射函数声明,这是JNI中常用的动态注册方法
这里只截图部分
接下来对android_media_MediaPlayer_setDataSourceFD
函数进行分析
注意jniGetFDFromFileDescriptor
从这里开始调用JNIEnv*
中的GetIntField
函数获取对应的变量(下图不一定是真正调用)
接着分析process_media_player_call
函数
当mp->setDataSource(fd,offest,length)
函数得到状态后,对各种状态进行通知,有异常的直接抛出
接下来看看以HTTP/RTSP传入JNI,在java层的nativeSetDataSource
如下
在JNI中通过映射表可对应到android_media_MediaPlayer_setDataSourceAndHandlers
函数
android_media_MediaPlayer_setDataSourceAndHandlers函数
至此setDataSource过程完成。这里要注意两点,一点是从java->JNI->c++的正向调用
过程,一点是c++->JNI->java
的过程
这样回调的好处
安全性:封装在Native层的代码是so形式的,破坏性风险小
效率高:在运行速度上c++执行时间断,且底层也是用c++语言编写的。对于复杂的渲染及对时间要求高的渲染,放在Native层最好
连通性:正向调用将值传入,反向调用把处理过的值通知回去,相当于一个管道
4.setDisplay过程
最后一行通过JNI返回的Surface,时时做好更新准备
查看android_media_MediaPlayer.cpp中对应_setVideoSurface
的函数
decVideoSurfaceRef
总结
SurfaceView中调用getHolder
函数可以获得当前SurfaceView中的Surface对应的SurfaceHolder,SurfaceHolder
开始对Surface
进行管理操作。以MVC模式类比: M:Surface(图像数据) V:SurfaceView(图像展示) C:SurfaceHolder(图像数据管理)。MediaPlayer.java中的setDisplay操作就是对将要显示的视频进行预设置