我是靠谱客的博主 端庄跳跳糖,这篇文章主要介绍Android音视频开发入门(2)MediaPlayer 生命周期及create()分析1. 状态图和及生命周期2. 从创建到setDataSource过程,现在分享给大家,希望可以做个参考。

MediaPlayer是Android中的一个多媒体播放类,我们用它来控制音视频流或本地音视频资源的播放过程。
这篇blog我们就来从MediaPlayer的生命周期、用法、工作流程、源码来分析。

1. 状态图和及生命周期

1.1 MediaPlayer状态图

反正状态图,都会丢出下面这么一个图噶。
在这里插入图片描述
其中蓝色的椭圆表示的是MediaPlayer的状态,弧线表示状态进行过渡。有两种类型的弧,单箭头弧表示同步函数调用,双箭头弧表示一步函数调用

这个图讲的很全,所以我们要看图说话,从每个椭圆每条线去分析MeadiaPlayer的所有状态。

1.2 Idle状态以及End状态

这里给大家教下英文单词, Idle就是空闲中的意思。
在MediaPlayer创建实例或者调用reset函数后,播放器就被创建了,这个时候就是一定处于 idle状态
任何时候调用 relase()函数后,就会变成 End状态
在这两个状态之间的就是MediaPlayer的生命周期

1.3 Error状态

idle状态时候调用 start、pause、stop、seekTo等等函数,都会触发OnErrorListener.onError,使MediaPlayer处于 onError状态
所以,当我们不再使用MediaPlayer的时候,一定要调用 relase() 方法释放资源
当MediaPlayer处于onError状态时,它将不能再被调用,因为本次生命周期已经结束了。

由于支持的音视频格式分辨率过高,输入数据流超时,或者其他各种各样的原因将导致播放失败。在这种错误的条件下,如果用户事先通过 setOnErrorListener注册过OnErrorListener,之后error的回调就能看到返回的错误信息
一旦有错误,MediaPlayer就会进入到Error状态,要用 reset()函数才能重新建立MediaPlayer,这个时候就会回到Idle状态了。

1.4 Initalized状态

当调用 setDataSource(FileDescriptor)、setDataSource(String)、setDataSouce(Context,uri)、setDataSource(FileDescriptor,long,long)其中一个函数时,MediaPlayer的Idle状态就会变成 Initalized状态
如果setDataSource在非Idle状态时调用,会抛出 IllegalStateException 异常
当重载setDataSource时,需要抛出IllegalArgumentExcption和IOExeception这两个异常

1.5 Prepared状态

MediaPlayer有两种途径到达Prepared状态

  1. 同步方法:使用本地音视频文件
    调用 prepare() 同步的将 lnitalized状态 变为 Prepared状态
  2. 异步方法:使用网络音视频,需要缓冲
    调用 prepareAsync()异步的将 lnitalized状态变为 Preparing状态,最后再到Prepared状态 ,Preparing状态是个短暂的状态

如果在应用层事先实现 setOnPreparedListener,那么就可以回调 onPrepared 函数

1.6 Started状态

当MediaPlayer处于 Prepared状态状态时,应用就可以开始设置一些属性了,比如音量、循环模式等。
然后就是调用 start()进入到Started状态,如果用户事先注册过 setOnBufferingUpdateListener,那么Started状态就会回调它的onBufferingUpdate,这个回调函数主要使应用程序保持跟踪音视频流的缓冲状态,如果MediaPlayer已经处于Started状态,再调用start()是没有任何用的。

1.7 Paused状态

当调用 paused()时,MediaPlayer 瞬间 从Started变为Prepared状态。
但是在播放器内部,这个过程是异步的。
如果在Paused状态下调用 start()playback()会恢复之前暂停时的位置,接着开始播放,这时候又变成了 Started状态

当然了,如果 已经是Paused状态,调用paused() 是没有任何用处的。

1.8 Stopped状态

当调用stop()时,MediaPlayer无论处于哪种状态都会进入Stopped状态

如果已经处于Stopped状态时,调用 stop()是没有任何用处的

1.9 PlaybackCompleted状态

可以通过 getCurrentPosition()获取播放的位置
当MediaPlayer播放结束时:

  1. 如果设置了setLooping(true)
    MediaPlayer依然处于Started状态
  2. 如果设置了setLooping(false),并且事先重写过setOnCompletionListener
    播放器会回调上面那个接口的 onCompletion(),然后进入 PlaybackCompleted(播放完成)状态。当处于这个状态时,调用start()将重启播放器从头开始播放数据

2. 从创建到setDataSource过程

2.1从创建到setDisplay过程

在这里插入图片描述
可以从时序图中看出一下步骤:

  1. 通过getService() 从 ServiceManager获取对应的MediaPlayerService
  2. 调用native_setup() 创建播放器
  3. 调用setDataSource() 把URL地址传入低层
  4. 通过setDisplay()传入 SurfaceHolder,以便将解码出的数据放到SurfaceHolder中的Surface
  5. 显示在SurfaceView上

2.1 创建(create(Context,uri))过程

当应用层调用下面代码时

复制代码
1
2
MediaPlayer.create(this, Uri.parse(" url") );

create会这样做:

复制代码
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
//create源码 public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder, AudioAttributes audioAttributes, int audioSessionId) { try { MediaPlayer mp = new MediaPlayer(); //声音处理,如果为空就new一个 final AudioAttributes aa = audioAttributes != null ? audioAttributes : new AudioAttributes.Builder().build(); //设置音频属性 mp.setAudioAttributes(aa); //设置声音会话Id,视频和音频是分开渲染的 mp.setAudioSessionId(audioSessionId); //从这里setDataSource,传入uri统一资源标志符 mp.setDataSource(context, uri); //判断SurfaceHolder是否为空,这是一个控制器,用来操作Surface,处理它在Canvas上作画的效果和动画,控制表面、大小、像素 if (holder != null) { //给Surface设置一个控制器 mp.setDisplay(holder); } //开始准备 mp.prepare(); return mp; } catch (IOException ex) { Log.d(TAG, "create failed:", ex); // fall through } catch (IllegalArgumentException ex) { Log.d(TAG, "create failed:", ex); // fall through } catch (SecurityException ex) { Log.d(TAG, "create failed:", ex); // fall through } return null; }

create(context,uri)总结
通过MediaPlayer.create(Context,uri),内部会new出一个 MediaPlayer对象,并为它设置一些配置,这个时候已经经历了IDLE->Initalized状态
并setDataSource,做好prepare的动作,只需要调用start(),就能开始播放数据了。

MediaPlayer可以通过new的方式创建(要手动调用setDataSource()),也可以通过create方式创建(不用手动调用了),我们来看看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
public MediaPlayer() { super(new AudioAttributes.Builder().build(), AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER); //定义一个Looper Looper looper; if ((looper = Looper.myLooper()) != null) { //如果myLooper不为空就赋值给myLooper,并实例化一个EventHandler对象 mEventHandler = new EventHandler(this, looper); } else if ((looper = Looper.getMainLooper()) != null) { //如果主线程Looper不为空,就赋值给looper,并实例化一个EventHandler对象 mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; } //时间数据容器,一般provider都是和数据联系起来的,如ContentProvider、VideoProvider mTimeProvider = new TimeProvider(this); mOpenSubtitleSources = new Vector<InputStream>(); //通过Binder机制获取到原生的OPS服务 IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); mAppOps = IAppOpsService.Stub.asInterface(b); //通过之后获取到的服务,就能使用native_setup方法开始创建MediaPlayer了,而且还是软引用, //接下来就是放在C++层去创建MediaPlayer了 native_setup(new WeakReference<MediaPlayer>(this)); }

MediaPlayer构造函数总结

  1. 定义Looper
  2. 初始化一个TimeProvider
  3. 通过Binder获取原生ops服务
  4. 进入c++层创建一个弱引用的MediaPlayer

所以接下来我们就进入C++层,去分析Native层如何创建MediaPlayer的了。
在分析native_setup之前,请注意 .so文件一般都是在静态代码块中加载的。在MediaPlayer中有一段静态代码块,用于加载和链接库文件media_jni.so

复制代码
1
2
3
4
5
static { System.loadLibrary("media_jni"); native_init(); }

所以我们要去查看 "media_jni"这个c代码,Android Studio是看不到JNI文件的
我们要去Android源码大全这上面去找,代码是通过 loadLibrary(xxx_jni)加载的,那么我的搜索关键字就是 libxxx_jni
然后我们就找到目录/frameworks/base/media/jni下的android_media_MediaPlayer.cpp文件,从它来分析,因为它的第一个函数 android_media_MediaPlayer_init就是从Java静态代码快调过来的nativie_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
//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; } //获取成员变量mNativeContext,它是一个long型,实际对应的是一个内存地址 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; } ... }

init方法总结

  1. 被通知init后,从Java层获取一个MediaPlayer
  2. 从MediaPlayer获取一些变量
  3. 从MediaPlayer获取 postEventFromNative()并执行

这个方法顾名思义:Native通知Java层,我初始化好了,你可以开始做一些事情了。
这个时候我们就要去Java层中看 postEventFromNative()这个方法做什么了。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void postEventFromNative(Object mediaplayer_ref, int what, int arg1, int arg2, Object obj) { //得到弱引用的MediaPlayer final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get(); if (mp == null) { return; } ... //如果handler不为空,则发送一条message if (mp.mEventHandler != null) { Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); mp.mEventHandler.sendMessage(m); } }

postEventFromNative总结
总的来说,就是拿到Native层创建好的MediaPlayer,并且向Handler发送一条message,至于后面做了啥之后再讲。

我们之前在create()的时候,创建MediaPlayer之后,会走native_setup方法,我们来看看这个方法做了什么:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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(); ... // 给MediaPlayer创建一个Listener,便于我们在Java层设置的 setPrepareListener、setOnCompleteListener能产生回调 sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this); mp->setListener(listener); // 对于Java层来说,C++中的MediaPlayer是不透明的,也无需关心其对应的逻辑,各司其职就行了 setMediaPlayer(env, thiz, mp); }

native_setup总结

  1. 设置监听器
  2. C++层自己处理这个MediaPlayer

2.2 setDataSource过程

上面是一个MediaPlayer的构造过程,在构造完,着实的获取到MediaPlayer这个对象之后,我们就会去 setDataSource(),我们来看看它的源码(注:这一段代码比较无聊,可以大致浏览一下并直接看总结):
(注:下面一段是传入的uri为文件资源形式的代码,就比如我们传了一个本地的媒体文件

复制代码
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
56
57
58
59
60
61
62
63
private void setDataSource(String path, Map<String, String> headers, List<HttpCookie> cookies) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { String[] keys = null; String[] values = null; if (headers != null) { keys = new String[headers.size()]; values = new String[headers.size()]; int i = 0; //把HTPP/RTSP中包含的key、value分别装到两个数组中 for (Map.Entry<String, String> entry: headers.entrySet()) { keys[i] = entry.getKey(); values[i] = entry.getValue(); ++i; } } setDataSource(path, keys, values, cookies); } private void setDataSource(String path, String[] keys, String[] values, List<HttpCookie> cookies) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { //解析path 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); if (file.exists()) { FileInputStream is = new FileInputStream(file); //得到文件标识符 FileDescriptor fd = is.getFD(); //传入文件标志符 setDataSource(fd); is.close(); } else { throw new IOException("setDataSource failed."); } } public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException { setDataSource(fd, 0, 0x7ffffffffffffffL); } public void setDataSource(FileDescriptor fd, long offset, long length) throws IOException, IllegalArgumentException, IllegalStateException { //进入native层 _setDataSource(fd, offset, length); }

setDataSource()总结
解析uri,如果得到的路径是文件,则把该文件资源的资源标识符丢给native层处理。

native层并没有setDataSource函数,但是有一个函数名映射函数说明,这是JNI中常用的动态注册方法:
在这里插入图片描述
所以我们找到 setDataSourceFD():

复制代码
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
static void android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) { //获得MediaPlayer sp<MediaPlayer> mp = getMediaPlayer(env, thiz); ... //在JNI中获取 java.io.FileDescription 这里开始调用JNIEnv*中的 GetIntField函数获取对应的变量 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); ALOGV("setDataSourceFD: fd %d", fd); //进行 setDataSource 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) { //如果没有异常 if (opStatus != (status_t) OK) { //如果在 setDataSource过程中opStatus不Ok sp<MediaPlayer> mp = getMediaPlayer(env, thiz); //则通知MEDIA_ERROR if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0); } } else { // Throw exception! ... } }

setDataSourceFD()总结
拿到标识符(uri的内容)后,用mp->setDataSource(fd, offset, length)得到的结果,进行异常处理,如果有异常就抛出。

在setDataSource的时候我们是以本地文件的形式来走的,如果我们当时走的是网络请求,即我们uri内容是 HTTP/RTSP,那么就会nativeSetDataSource()方法,在jni层,对应的是setDataSourceAndHeader()

复制代码
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_setDataSourceAndHeaders( JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path, jobjectArray keys, jobjectArray values) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); ...//解析网络路径、判空、判内存溢出 //下面是通过Binder机制,将httpServiceBinderObj传给IPC返回给binder,然后强制转换成IMediaHTTPService 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()的过程就完成了。

这里最后总结一下setDataSource做了什么:

拿到uri,判断uri的内容是否为文件资源 (用大白话讲:就是你用的是本地文件,还是一个指定的网络url)

  • 如果是文件资源,就去检查它的 文件描述符(判断类型啥的),没问题的话,就mp-> setDataSource()
  • 如果是HTTP/RTSP(网络请求),则通过Binder,去一次做IPC(这里暂且推断为:网络请求),然后就mp-> setDataSource()

这样通过JNI, JAVA和C++相互调用,有这么几个好处:

  • 安全
    封装在native层的代码是.so形式的,破坏风险小。
  • 效率高
    在运行速度上C++更高效,所以对于复杂的渲染,放在native层做最好
  • 连通性强
    因为正向调用就传值,反向调用就把处理后的值通知回去,其实就是一条路

2.3 setDisplay过程

接下来看看在create()阶段里,setDataSource后 的setDisplay(holder)做了什么
首先我们都知道 Holder是容器、管家的意思,RecyclerView有ViewHolder来控制每个item,那SurfaceHolder同样的,也是控制着每一个Surface。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setDisplay(SurfaceHolder sh) { //给Surface设置一个控制器 mSurfaceHolder = sh; Surface surface; if (sh != null) { surface = sh.getSurface(); } else { surface = null; } //到native层,给视频设置Surface _setVideoSurface(surface); //更新Surface到屏幕上 updateSurfaceScreenOn(); }

在定义完SurfaceHolder后,就要去native层了,我们在同样的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
static void setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive) { ... decVideoSurfaceRef(env, thiz); sp<IGraphicBufferProducer> new_st; if (jsurface) { //得到Java层的Surface sp<Surface> surface (android_view_Surface_getSurface(env, jsurface)); if (surface != NULL) { //如果Surface不为空,则获取 IGraphicBufferProducer new_st = surface -> getIGraphicBufferProducer(); ... //调用 incStrong new_st -> incStrong(( void*)decVideoSurfaceRef); } else { ... } } env -> SetLongField(thiz, fields.surface_texture, (jlong) new_st.get()); //如果MediaPlayer还没有被初始化,setDataSource将会失败,但setDataSource之前就setDisplay了 //在prepare/prepareAsync中调用setVideoSurfaceTexture 可以覆盖该cas mp -> setVideoSurfaceTexture(new_st); } static void decVideoSurfaceRef(JNIEnv *env, jobject thiz) { ... sp<IGraphicBufferProducer> old_st = getVideoSurfaceTexture(env, thiz); if (old_st != NULL) { old_st->decStrong((void*)decVideoSurfaceRef); } }

这里有几个很陌生的类,我们先来理清一下较为基础的:

  • SurfaceTexture:
    它是API 11之后加入的类。这个类可以从 视频解码里面获取图像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收图像流之后,不需要显示出来。SurfaceTexture不要显示到屏幕上。
    因此我们可以用SurfaceTexture接收解码出来的图像流,然后从SurfaceTexture中取得图像帧的副本进行处理,处理完后就交给一个SurfaceView来显示。
  • Surface:
    处理被屏幕排序的原生的Buffer,Android中的Surface就是就一个画图的平台。对于View及其子类,都是画在Surface上的。
    各Surface对象通过SurfaceFlinger合成到frameBuffer。每个Surface都是双缓冲的(双线程),它的介绍在Android自定义控件开发入门与实战(15)SurfaceView,我已经分析的很清楚了
  • SurfaceView
    SurfaceView是一个View,内嵌一个专门用来绘制的Surface。
    可以这么说他们的关系:Surface是计算器,SurfaceView是画布
  • SurfaceHolder
    上面已经简单比喻出它的用法。它可以监听Surface的操作。
  • IGraphicBufferProducer
    它是图形缓冲的管理者,它是App和BufferQueue的重要桥梁,承担着整个应用进程中的UI显示需求。

Surface、SurfaceHolder、SurfaceView就像MVC有木有。

这时候再来总结一下native层的setVideoSurface做了什么

  1. 先拿到旧的 SurfaceTexture,用旧的获取到 Producer,然后调用它的decStrong()
  2. nartive层拿到了Surface
  3. 从Surface获取缓冲数据的管理者
  4. 用管理者调用 incStrong()

现在不知道decStrong和incStrong具体做了什么,我们可以先理解为处理Surface中的数据吧。

拿现在来概括一下 setDisplay做了什么

  1. SurfaceHolder 交给native层一个Surface,让它处理数据
  2. natve层处理好后,SurfaceHolder来通知更新SurfaceView

2.4 开始prepare后的流程

我们前面分析了MediaPlayer从创建到 setDataSource的过程,尽管分析了代码,但是没有从MediaPlayer生态上认识各类库之间的调用关系。下图是从别的blog上找的:MediaPlayer各个具体类之间的依赖关系图
在这里插入图片描述
从上图可以看出,MediaPlayer是C/S架构。他们之间用Binder机制实现IPC通信。
从框架结构上看,IMediaPlayerService.h、IMediaPlayerClient.h和mediaplayer.h这3个头文件中定义了MediaPlayer的接口和架构。

在给播放器设置数据源且展现了Surface后,你应当开始调用prepare或prepareAsync函数。
对于文件类型,调用prepare() 将暂时阻塞,因为它是一个同步函数,直到MediaPlayer已经准备好数据即将播放,也就是播放回调了 onPrepared(),进入Prepared函数。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
25
public void prepare() throws IOException, IllegalStateException { _prepare(); scanInternalSubtitleTracks(); } //native的 prepare如下: static void android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz) { ... //1 sp<IGraphicBufferProducer> st = getVideoSurfaceTexture(env, thiz); //2 mp->setVideoSurfaceTexture(st); //3 process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." ); } static sp<IGraphicBufferProducer> getVideoSurfaceTexture(JNIEnv* env, jobject thiz) { IGraphicBufferProducer * const p = (IGraphicBufferProducer*)env->GetLongField(thiz, fields.surface_texture); return sp<IGraphicBufferProducer>(p); }

我们上一节就一直看过1、2、3的代码了。
1是从getVideoSurfaceTexture()方法获取一个 IGraphicBufferProducer 类型指针
2是 把1中得到的指针传给 MediaPlayer。BpGraphicBufferProducer是 GraphicBufferProducer在客户端这边的代理对象,负责和SurfaceFlinger交互。GraphicBufferProducer通过gbp(IGraphicBufferProducer 对象)向BufferQueue获取Buffer,然后填充UI信息,填充完毕会通知SurfaceFlinger
3是走 mp->prepare()并通知结果的函数。

我们知道还有一个 prepareAsync(),前面的思路都是从create走过来的,如果一个网络URL被发过来,这个时候就要用到prepareAsync()了:

复制代码
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
public void startPlayUrl(String url) { Uri uri = Uri.parse(url); MediaPlayer mp = new MediaPlayer(); try { mp.setDataSource(this, uri); } catch (IOException e) { e.printStackTrace(); } mp.setOnPreparedListener(prepareListener); mp.setOnVideoSizeChangedListener(videoSizeChangedListener); mp.setOnErrorListener(errorListener); mp.prepareAsync(); } public native void prepareAsync() throws IllegalStateException; static void android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz) { sp<MediaPlayer>..in to make it stick. sp<IGraphicBufferProducer> st = getVideoSurfaceTexture(env, thiz); mp->setVideoSurfaceTexture(st); process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." ); }

发现他的代码除了最后一行,和prepare()无异

接下来我们来看的mp-> prepareAsync()中 MediaPlayer的 prepareAsync():

复制代码
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
status_t MediaPlayer::prepareAsync() { ALOGV("prepareAsync"); //互斥锁头 Mutex::Autolock _l(mLock); return prepareAsync_l(); } status_t MediaPlayer::prepareAsync_l() { if ( (mPlayer != 0) && ( mCurrentState & (MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) { //设置音频流类型,在IMediaPlayer.cpp中对应的transact操作是SET_AUDIO_STREAM_TYPE if (mAudioAttributesParcel != NULL) { mPlayer->setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, *mAudioAttributesParcel); } else { mPlayer->setAudioStreamType(mStreamType); } //将当前状态置为 MEDIA_PLAYER_PREPARING 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操作如下:

复制代码
1
2
3
4
5
6
PREPARE_ASYNC: { CHECK_INTERFACE(IMediaPlayer, data, reply); reply->writeInt32(prepareAsync()); return NO_ERROR; }

接着分析 MediaPlayerService::Client::prepareAsync()

复制代码
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
status_t MediaPlayerService::Client::prepareAsync() { ... status_t ret = p->prepareAsync(); ... } //serivce中调用了 AwesomePlayer的prepareAsync() //注意 AwesomePlayer在Andorid6.0之后已经弃用 status_t AwesomePlayer::prepareAsync() { ATRACE_CALL(); Mutex::Autolock autoLock(mLock); if (mFlags & PREPARING) { return UNKNOWN_ERROR; // async prepare already pending } mIsAsyncPrepare = true; //调用了 prepareAsync_l() return prepareAsync_l(); } status_t AwesomePlayer::prepareAsync_l() { if (mFlags & PREPARING) { //如果是Preparing状态,就返回 return UNKNOWN_ERROR; } if (!mQueueStarted) { //队列不是开始状态时,设置成开始状态 mQueue.start(); mQueueStarted = true; } //修改状态为Preparing modifyFlags(PREPARING, SET); //这里AwesomeEvent接收到时间,进行回调 mAsyncPrepareEvent = new AwesomeEvent( this, &AwesomePlayer::onPrepareAsyncEvent); //将回调时间通过队列通知出去 mQueue.postEvent(mAsyncPrepareEvent); return OK; }

总结AwesomePlayer层的 prepareAsync():

  1. 首先判断mFlags是不是Preparing状态。
  2. 启动mQueue
  3. 修改状态为Preparing,表示正在处理文件的音视频流
  4. 然后实例化一个 AwesomeEvent,放到之前启动的mQueue中进行通知。
  5. 队列中处理的结果就是调用 onPrepareAsyncEvent,后面的过程就是初始化解码器,将流解码出来,也能知道视频流的宽高等属性,然后处于Prepared,不再向下跟踪,prepare的流程就完成了。

接下来,再回到Java层中之前的prepare函数中的 scanInternalSubtitleTracks()

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
private void scanInternalSubtitleTracks() { //设置一个字幕控制锚点 setSubtitleAnchor(); populateInbandTracks(); if (mSubtitleController != null) { //如果字幕控制器不为null,则设置为默认的字幕控制器 mSubtitleController.selectDefaultTrack(); } }

总结:
这个函数用来扫描内嵌字幕并进行跟踪。

接下来看看MediaPlayer中的start()

复制代码
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
public void start() throws IllegalStateException { final int delay = getStartDelayMs(); if (delay == 0) { //如果没有延时播放,则直接调用startImpl() startImpl(); } else { //否则Thread延时,然后再调用startImpl() ... } } private void startImpl() { //这个方法里判断视频是否为受限制的,如果是,则设置声音为0 baseStart(); //设置唤醒为true stayAwake(true); _start(); } private void stayAwake(boolean awake) { if (mWakeLock != null) { if (awake && !mWakeLock.isHeld()) { //获取锁 mWakeLock.acquire(); } else if (!awake && mWakeLock.isHeld()) { //释放锁 mWakeLock.release(); } } mStayAwake = awake; //更新Surface updateSurfaceScreenOn(); }

总结Java层start():

  1. 判断是否延时播放
  2. 判断是否媒体是否受限制
  3. 设置唤醒,即获取锁
  4. 调用低层的 _start()
  5. 更新Surface

从Paused状态变为Started状态,如果playback已经处于Stopped状态,或之前从来没有处于过Started状态,playback将会开始start
其中,锁是用来干什么的呢? stayAwake()和setWakeMode()是串联的,在setWakeMode()中:

复制代码
1
2
3
4
5
6
//首先获取PM实例 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); //通过newWakeLock来生成一个 mWakeLock实例。mode表示视频的几个模式,比如保持屏幕常亮、屏幕可以变暗、键盘灯关闭等等。 //获取WakeLock后通过acquire获取相应的锁,然后进行别的 mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer.class.getName())

这里就可以看出来,这里的锁是屏幕的一种唤醒锁,通过设置锁的模式,来改变一些播放时的模式

接下来我们就要到framework层去看 _start(),其实根据前面,我们想都知道它的方法大概是这样的:

复制代码
1
2
3
4
5
...start(){ ... process_media_player_call(...,mp->start(),...) }

事实上就是这样的。
从MediaPlayer调用start(),就进入了视频播放环节,最终会到C++的mediaplayer.cpp中实现,我们先来观察一下 /frameworks/av/include/media/mediaplayer.h:

复制代码
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class MediaPlayer : public BnMediaPlayerClient, public virtual IMediaDeathNotifier { public: MediaPlayer(); ~MediaPlayer(); void died(); void disconnect(); status_t setDataSource( const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers); status_t setDataSource(int fd, int64_t offset, int64_t length); status_t setDataSource(const sp<IDataSource> &source); status_t setVideoSurfaceTexture( const sp<IGraphicBufferProducer>& bufferProducer); status_t setListener(const sp<MediaPlayerListener>& listener); status_t getBufferingSettings(BufferingSettings* buffering /* nonnull */); status_t setBufferingSettings(const BufferingSettings& buffering); status_t prepare(); status_t prepareAsync(); status_t start(); status_t stop(); status_t pause(); bool isPlaying(); status_t setPlaybackSettings(const AudioPlaybackRate& rate); status_t getPlaybackSettings(AudioPlaybackRate* rate /* nonnull */); status_t setSyncSettings(const AVSyncSettings& sync, float videoFpsHint); status_t getSyncSettings( AVSyncSettings* sync /* nonnull */, float* videoFps /* nonnull */); status_t getVideoWidth(int *w); status_t getVideoHeight(int *h); status_t seekTo( int msec, MediaPlayerSeekMode mode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC); status_t notifyAt(int64_t mediaTimeUs); status_t getCurrentPosition(int *msec); status_t getDuration(int *msec); status_t reset(); status_t setAudioStreamType(audio_stream_type_t type); status_t getAudioStreamType(audio_stream_type_t *type); status_t setLooping(int loop); bool isLooping(); status_t setVolume(float leftVolume, float rightVolume); void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL); status_t invoke(const Parcel& request, Parcel *reply); status_t setMetadataFilter(const Parcel& filter); status_t getMetadata(bool update_only, bool apply_filter, Parcel *metadata); status_t setAudioSessionId(audio_session_t sessionId); audio_session_t getAudioSessionId(); status_t setAuxEffectSendLevel(float level); status_t attachAuxEffect(int effectId); status_t setParameter(int key, const Parcel& request); status_t getParameter(int key, Parcel* reply); status_t setRetransmitEndpoint(const char* addrString, uint16_t port); status_t setNextMediaPlayer(const sp<MediaPlayer>& player); media::VolumeShaper::Status applyVolumeShaper( const sp<media::VolumeShaper::Configuration>& configuration, const sp<media::VolumeShaper::Operation>& operation); sp<media::VolumeShaper::State> getVolumeShaperState(int id); // Modular DRM status_t prepareDrm(const uint8_t uuid[16], const Vector<uint8_t>& drmSessionId); status_t releaseDrm(); // AudioRouting status_t setOutputDevice(audio_port_handle_t deviceId); audio_port_handle_t getRoutedDeviceId(); status_t enableAudioDeviceCallback(bool enabled); ... };

从这些接口可以看出,MediaPlayer类实现了一个MediaPlayer的基本播放控制。
MediaPlayer中还定义了 DeathNotifier,这是为了IPC做准备的。

通过低层的start()后会返回一个状态,告知Java层,是否进入Started状态。

pasue()和start()、prepare()的流程都是差不多的。也是通过 mp->pause()来返回对应的状态的。

总结

OK,到这里我们大致知道了 MediaPlayer的状态以及创建流程,我们做一个总结:

MediaPlayer的生命周期,或者说状态,有 IdleInitialzedPreparedPreparingStartedStoppedPausedPlaybackCompletedErrorEnd

  1. 我们Java层从最开始的 Idle状态进入,第一件做的事情就是setDataSource,Java层的本质,就是通知C++层
  2. C++层setDataSource后,通知Java层下一步
  3. Java层就会setDisplay,这也是通知C++去解析传进来的dataSource
  4. 如果传进来的是文件形式,则MediaPlayer处理完后调用prepare()直接进入Prepared状态,如果是HTTP(即网络流),先变为Preparing状态,会通过 prepareAsync()去做一个异步的处理数据流,等处理完后,才会进入 Prepared状态
  5. setDisplay成功后,Java层的 Suface、SufaceView、SurfaceHolder会以MVC的形式,更新数据/视图
  6. 调用start()方法后,开始正式的进入了音视频的播放。

最后

以上就是端庄跳跳糖最近收集整理的关于Android音视频开发入门(2)MediaPlayer 生命周期及create()分析1. 状态图和及生命周期2. 从创建到setDataSource过程的全部内容,更多相关Android音视频开发入门(2)MediaPlayer内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(71)

评论列表共有 0 条评论

立即
投稿
返回
顶部