【学习】从零开始的Android音视频开发(2)——MediaPlayer的状态和创建过程

状态图及生命周期

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时,需要抛出IllegalArgumentExceptionIOException异常。

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过程

在这里插入图片描述

从时序图可以看到,通过getServiceServiceManager获取对应的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操作就是对将要显示的视频进行预设置

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值