- Android音视频开发
- 何俊林
- 1980字
- 2020-08-27 17:06:39
2.3 开始prepare后的流程
在2.2节中分析了MediaPlayer从创建到setDataSource的过程,尽管分析了代码,但是没有从MediaPlayer生态上认识各类库之间的依赖调用关系。
MediaPlayer部分的头文件在frameworks/base/include/media/目录中,这个目录和libmedia.so库源文件的目录frameworks/base/media/libmedia/相对应。主要的头文件有以下几个。
• IMediaPlayerClient.h
• mediaplayer.h
• IMediaPlayer.h
• IMediaPlayerService.h
• MediaPlayerInterface.h
在这些头文件中,mediaplayer.h提供了对上层的接口,而其他的几个头文件提供的是一些接口类(即包含了纯虚函数的类),这些接口类必须被实现类继承才能够使用。
MediaPlayer各个具体类之间的依赖关系图如图2-3所示。
图2-3 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函数的执行过程如下:
Native层的android_media_MediaPlayer_prepare函数:
我们曾经介绍过上面代码中的1、2、3,1中的getVideoSurfaceTexture获取一个IGraphicBufferProducer类型指针,2中是setVideoSurfaceTexture,主要给MediaPlayer传入IGraphicBufferProducer。这里IGraphicBufferProducer就是App和BufferQueue的重要桥梁,GraphicBufferProducer承担着单个应用进程中的UI显示需求,与BufferQueue打交道的就是它。
BpGraphicBufferProducer是GraphicBufferProducer在客户端这边的代理对象,负责和SurfaceFlinger交互,GraphicBufferProducer通过gbp(IGraphicBufferProducer类对象)向BufferQueue获取Buffer,然后填充UI信息,填充完毕会通知SurfaceFlinger。
3中是一个判定并且进行通知的函数,这个process_media_player_call是对MediaPlayer调用prepare函数后是否有异常的检测,如果出现参数不合法,或是I/O异常,就会抛出异常。
我们知道MediaPlayer还有一个prepareAsync函数,前面的思路都是顺着MediaPlayer中的create函数来的。
如果是下面这种场景,即一个网络URL被发送过来,就是网络流数据传入MediaPlayer,这时就要用到prepareAsync函数了:
分析MediaPlayer中的prepareAsync函数:在setDataSource中且展现了Surface后,开始调用prepare或prepareAsync函数,对于网络视频流类型,尽量调用prepareAsync函数,因为是异步的,不会导致没有足够的数据影响起播,代码如下:
这是一个native函数声明,下面分析android_media_MediaPlayer_prepareAsync函数:
从代码来分析,除了最后process_media_player_call中的mp->prepareAsync在判断状态时不一样,其他都和prepare函数是一样的。它的操作结果经过回调通知Java层。
下面看看media/mediaplayer.h中的prepareAsync函数(C++代码):
接着分析prepareAsync_l函数实现代码:
下面继续分析prepareAsync函数,mp->prepareAsync对应的BnMediaPlayer操作如下:
接着分析MediaPlayerService::Client::prepareAsync函数:
这里调用了AwesomePlayer的prepareAsync函数:
接着分析AwesomePlayer::prepareAsync_l函数:
总结一下上面的代码,首先判断mFlags,此时不是PREPARING。接着启动mQueue(类TimedEventQueue)。之后修改mFlags的状态为PREPARING,表示现在正在准备处理文件的音视频流。然后实例化一个AwesomeEvent,放到之前启动的mQueue中进行通知。
队列中处理的结果就是调用AwesomePlayer::onPrepareAsyncEvent函数。后面的过程就是初始化解码器,将流解码出来,也能知道视频流的宽高等属性,然后处于Prepared状态,不再向下跟踪。prepare的流程就完成了。
接下来,再回到Java层中之前的prepare函数中的scanInternalSubtitleTracks函数:
这个函数用于扫描内嵌字幕并进行跟踪,接下来看看MediaPlayer中的start函数:
从Paused状态变为Started状态,如果playback已经处于Stopped状态,或之前从来没有处于过Started状态,playback将会开始start。
总结一下上面的代码,start函数用于start或者重新恢复播放,如果playback先前已暂停,playback将从Paused状态变为Started状态,如果playback已经处于Stopped状态,或之前从来没有处于过Started状态,playback将会开始start。
以上的stayAwake用于对屏幕进行操作。
首先执行PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);,通过Context.getSystemService函数获取PowerManager实例。然后通过PowerManager的newWakeLock(int flags, String tag)来生成WakeLock实例。int flags指示要获取哪种WakeLock,不同的锁对CPU、屏幕、键盘灯有不同的影响。获取WakeLock实例后通过acquire获取相应的锁,然后进行其他业务逻辑的操作,最后使用release释放(释放是必需的)。
关于int flags,各种锁的类型对CPU、屏幕、键盘的影响如下。
• PARTIAL_WAKE_LOCK:保持CPU运转,屏幕和键盘灯有可能是关闭的。
• SCREEN_DIM_WAKE_LOCK:保持CPU运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯。
• SCREEN_BRIGHT_WAKE_LOCK:保持CPU运转,允许保持屏幕高亮显示,允许关闭键盘灯。
• FULL_WAKE_LOCK:保持CPU运转,保持屏幕高亮显示,键盘灯也保持亮度。
• ACQUIRE_CAUSES_WAKEUP:正常唤醒锁实际上并不打开照明。相反,一旦打开,它们会一直保持。当获得WakeLock时,这个标志会使屏幕或/和键盘立即打开。一个典型的应用就是,可以立即看到对用户来说重要的通知。
最后,通过updateSurfaceScreenOn函数更新屏幕上的Surface。下面还是回到最上面的start函数中。在JNI中,对应的是android_media_MediaPlayer_start函数,代码如下:
从MediaPlayer调用start函数开始,就进入了视频播放环节,最终会到C++的mediaplayer.cpp中实现,我们先分析一下mediaplayer.h(路径为\frameworks\av\include\media\mediaplayer.h):
从接口中可以看出MediaPlayer类实现了一个MediaPlayer的基本播放控制操作,如播放(start)、停止(stop)、暂停(pause)、重置(reset)等。
另外的一个类DeathNotifier是在MediaPlayer类中定义的,它继承了IBinder类中的DeathRecipient类,这些类都是为进程间通信做准备的:
对于MediaPlayerClient和MediaPlayerService通过IPC进行通信,后面内容中会进行分析。
可以发现调用start函数后,底层返回了一个状态,以便我们知道已经处于Started状态还是没有处于Started状态。这时需要用process_media_player_call判定这个返回的状态,然后通知Java层中的回调事件。
接下来,再分析一下MediaPlayer的pause函数:
在对应的JNI中找到android_media_MediaPlayer_pause函数:
查看pause函数,可以看到和start函数的流程类似,也是通过mp->pause返回对应的状态,然后通知上层来暂停的。