0.前言
在前面的文章中我们介绍了MediaPlayer的初始化流程,以及设置输入媒体源setDataSource()的流程。在此之后,我们需要调用setDisplay()方法来设置输出渲染的Surface。
在介绍具体的方法之前,我们先简单介绍Android图形系统中的一些概念,这有助于我们理解后续的代码处理过程。
1. Android图形系统相关知识
在介绍MediaPlayer的渲染流程之前,我们先介绍一下Android的图形系统架构。这里会参考和借用以下一些文章中的内容,这些文章都非常有价值,值得一读:
《Android显示系统之——Surface和SurfaceFlinger》: huanzhiyazi
《Android 12(S) 图像显示系统 - 基本概念(一)》: 二的次方
《Android 图形显示简述
》:redspider110
1.1 帧的组成
帧的概念根据其所被讨论时所处的不同位置,往往会有所不同。对于Android的显示系统而言,一帧所表示的就是我们能看到的一张完整屏幕大小的图片。
一帧的内容是由不同的渲染源生成的。首先各个渲染源各自独立生成局部的子界面(窗口),这些子界面(窗口)可能会重叠和包含,然后再由一个合成服务,将所有的子界面(窗口)合成为完整的帧。
如上图所示,这是一个视频播放器的界面,整个界面包含:状态栏(status bar)、系统导航栏(system bar)、APP主窗口(main)、视频播放窗口(media player)4 个部分,每个部分的界面都是独立渲染的,其中 status bar 和 system bar 都属于 SystemUI进程,main 属于 APP进程,media player可以属于 APP进程也可以有自己独立的进程。可见一个进程既可以独立渲染一个界面,也可以包含多个渲染源,每个源独立渲染一个界面。
通过上述例子我们可以简单概括下Android图形显示系统的几个主要功能:
- 提供不同进程/组件,可以单独渲染一个子界面(窗口)的方式。
- 将不同渲染源的子界面(窗口)进行合成,渲染成完整的帧。
- 将合成后的帧在显示设备上输出显示。
1.2 Android图形系统架构
1.2.1 主要架构
上面是谷歌官方文档上对于图形系统的一张架构图,这张图我们可以了解图形系统中的主要组件,但是个人感觉并没有很好地表明图形系统中的数据流向和组件间的结构关系,我们可以结合下面一张图来理解。
Android的图形系统中主要包含以下角色:
- 图像流生产者
- 图像流消费者
- 图像缓存区组件
这里需要强调的一点是,我们提及的生产者,消费者,实际上都是面向图像“流”进行处理,而不是针对单一的Buffer。其实也好理解,图像流的pipeline建立起来之后,实际上是持续不断的处理过程。
1.2.2 图像流生产者
图形流生产者的工作是填充图形缓存区,是图形的“绘制者”。根据绘制方式和用途的不同,图像生产者的输入来源可能来自以下几种方式:
- 媒体文件解码。这里以MediaPlayer/MediaCodec相关组件为代表,主要是输入的媒体文件中读取并解码出图片帧,还支持将解码后的图片渲染到Surface上。
- 相机设备数据采集。在系统组件中可以通过Camera2 API来获取相机的数据流,用于预览、录制、拍照。
- UI界面的渲染。在App中的各类View和Window窗口,以及App外的桌面环境Launcher,这些UI一般通过HWUI进行GPU渲染(实现上通过Skia库,调用OpenGL/Vulkan接口渲染),或者通过Canvas进行CPU软渲染(实现上也还是通过Skia库,但是不调用硬件加速)。
- 开发者的自定义渲染。这部分其实和3中类似,但不局限于UI渲染,主要是开发者使用Canvas或OpenGL/Vulkan对View进行渲染。
生产者的主要操作对象是Surface,它表示的是一个绘制窗口的概念,负责管理一个窗口的各种属性,比如宽高、标志位、像素密度、格式(RGB颜色格式等)等,并且提供了访问和操作操作图像缓存区的接口。
1.2.3 图像流消费者
图像流消费者的任务是使用图形缓存区,是图像数据的应用方。一般常见的消费者有:
- 帧合成服务SurfaceFlinger。在1.1节中我们介绍了图形显示系统的主要任务,其中之一便是需要将不同的渲染窗口合成为完整的帧,而在Android系统中,SurfaceFlinger就是主要负责这一功能的系统服务组件。SurfaceFlinger可以使用OpenGL/HWC(Hardware Composer, 使用OEM厂商提供的专用于帧合成服务的硬件设备模块)来进行帧合成。
- 其他需要图像数据获取/处理的应用。这些应用往往需要获取到图像数据,比如相机应用,需要获取到相机设备返回的数据流,进行图像预览/保存。又例如剪辑特效类软件,需要对解码后的帧数据进行后处理。这类应用往往使用到OpenGL,图像数据以外部纹理的形式作为输入给应用使用,Android中对应的组件是
SurfaceTexture。
1.2.4 图像缓存区组件
图像缓存区组件,主要是为整个Android图形系统提供图像数据流转的支持。
1.2.4.1 BufferQueue
上面我们提到了,Android图形组件之间的实际上都是面向图像“流”进行处理,而BufferQueue的设计结构正是这样一种对于“图形流”数据载体的体现。
BufferQueue是将缓冲区池与队列相结合的数据结构,它使用 Binder IPC在进程之间传递图像缓冲区。
对于生产者,其使用IGraphicBufferProducer接口,通过dequeueBuffer()方法,可以从BufferQueue中申请一块空的Buffer,在生产者进程中对数据进行填充后,可以使用queueBuffer()方法,将其加入到BufferQueue的待处理队列中。
而对于消费者,其使用IGraphicBufferConsumer接口,通过acquireBuffer()方法,可以从BufferQueue中获取到一块已经由生产者填充好数据的图像缓存区,在消费者进程中使用完数据后,可以调用releaseBuffer()方法,将缓存区进行归还。
1.2.4.2 Galloc
Gralloc 的含义为是 Graphics Alloc 图形分配,是Android中负责申请和释放GraphicBuffer的HAL层模块,由硬件驱动提供实现,为BufferQueue机制提供了基础。gralloc分配的图形Buffer是进程间共享的,且根据其Flag支持不同硬件设备的读写。
在这里我们可以简单理解Galloc是图像Buffer的分配器则可。
2. MediaPlayer中涉及到的图形组件
上文简单介绍了一些Android图形系统的知识,本文的重点还是讨论MediaPlayer,在具体介绍MediaPlayer的渲染创建流程前,我们先简单介绍一下MediaPlayer代码中可能会涉及到的图形组件。
2.1 SurfaceView和SurfaceHolder
由上文我们可以得知,MediaPlayer在图形系统中,是作为图像流生产者的角色出现的。MediaPlayer在图形渲染方面的主要任务,就是将解码后的帧数据,渲染至输入的Surface上。
上文中我们介绍了Surface的概念,它表示的是一个绘制窗口的概念,而在使用MeidaPlayer的过程中,我们会用到SurfaceView和SurfaceHolder,简单介绍一下:
(1)SurfaceView是视图类View的子类,其中内嵌了一个专门用于绘制的Surface,SurfaceView可以控制这个Surface的格式和尺寸,以及Surface的绘制位置。可以理解为Surface就是管理数据的地方,SurfaceView就是展示数据的地方,而我们就是通过SurfaceView看到的Surface的部分或者全部内容。
(2)SurfaceHolder 是一个接口,用于管理和控制 Surface 的生命周期及属性。它是 Surface 的代理类,提供了更安全的访问方式,并允许注册回调监听 Surface 的状态变化。以下是SurfaceHolder的核心方法:
// 获取关联的 Surface
Surface getSurface();
// 获取 Canvas(需在绘制完成后调用 unlockCanvasAndPost())
Canvas lockCanvas();
void unlockCanvasAndPost(Canvas canvas);
// 设置 Surface 属性
void setFixedSize(int width, int height);
void setFormat(int format);
// 注册回调
void addCallback(SurfaceHolder.Callback callback);
SurfaceHolder.Callback是SurfaceHolder接口内部的静态子接口,其中定义了三个接口方法:
public void sufaceChanged(SurfaceHolder holder,int format,int width,int height); // Surface的大小发生改变时回调。
public void surfaceCreated(SurfaceHolder holder); // Surface创建时触发,一般在这里调用画面的线程。
public void surfaceDestroyed(SurfaceHolder holder); // 销毁时触发,一般在这里将画面的线程停止、释放。
2.2 Surface
再介绍下和Surface相关的C++层概念,这些内容会在MediaPlayer代码中出现:
(1) ANativeWindow是Android中的一个抽象数据类型,用于表示窗口和图形表面,它提供了一种与底层窗口系统进行交互的通用接口。这些函数指针定义了一组操作函数,用于处理与窗口系统交互的特定操作,如绘制、缓冲区管理等。ANativeWindow的抽象化使得可以在不同的窗口系统上进行图形渲染,而不用关心具体的窗口系统实现。
(p.s. ANativeWindow也是Android的EGL接口中EGLNativeWindowType的对应实现类型。)
(2) android::Surface类是ANativeWindow的一个实现,它封装了IGraphicBufferProducer和其他相关的功能,用于简化图形缓冲区的创建和使用。它提供了一组方法,用于设置、显示和操作图形缓冲区。
(3) IGraphicBufferProducer是图形缓冲区生产者的抽象(IGraphicBufferProducer.hal),它定义了一组方法用于管理和交互图形缓冲区,requestBuffer/queueBuffer/dequeueBuffer是它最常用的接口。这是一个binder接口,用于跨进程间的使用。
BufferQueue对App端的接口为IGraphicBufferProducer,实现类为Surface,对SurfaceFlinger端的接口为IGraphicBufferConsumer,实现类为SurfaceFlingerConsumer。
BufferQueue中对每一个GraphiBuffer都有BufferState标记着它的状态:
2.3 SurfaceTexture
SurfaceTexture 是 Surface 和 OpenGL ES (GLES) 纹理的组合。SurfaceTexture 实例用于提供输出到 GLES 纹理的接口。
SurfaceTexture 包含一个以应用为使用方的 BufferQueue 实例。当生产方将新的缓冲区排入队列时,onFrameAvailable() 回调会通知应用。然后,应用调用 updateTexImage()。此时会释放先前占用的缓冲区,从队列中获取新缓冲区并执行 EGL调用,从而使 GLES 可将此缓冲区作为外部纹理使用。
上面是谷歌文档的说明,看起来很绕口。事实上我们可以这么理解,SurfaceTexture在BufferQueue中是属于消费者的位置,我们可以使用它获取一张OpenGL纹理,作为Shader的输入使用。
而对应的生产者是Surface,我们可以使用SurfaceTexture来创建一个Surface,将生产和消费关联起来,而创建的Surface,可以作为Camera2、MediaPlayer、MediaCodec(解码渲染)等的输出。这样,我们就可以通过SurfaceTexture获得这些组件的输出,进一步的,SurfaceTexture可以创建一张OpenGLES外部纹理GL_TEXTURE_EXTERNAL_OES,我们可以将其送给shader进行渲染。
最常用的是相机应用,以下是一个相机应用使用SurfaceTexture的架构图。
此外,SurfaceTexture,也可以传递给TextureView进行渲染。
3. MediaPlayer的渲染初始化流程
本节主要是分析从MediaPlayer到底层NuPlayer之间的调用过程,从逻辑上看其实并不复杂,就是将Surface结构从java层传递到底层的NuPlayer对象。主要的难点可能是涉及到图形系统的相关组件概念,这些在前两节的相关知识中有所介绍。
MediaPlayer主要通过setDisplay()接口来设置和初始化用于渲染的Surface。
3.1 setDisplay()
3.1.1 主要流程
3.1.2 代码走读
public void setDisplay(SurfaceHolder sh) {
mSurfaceHolder = sh;
Surface surface;
if (sh != null) {
// 根据SurfaceHolder拿到对应的Surface
surface = sh.getSurface();
} else {
surface = null;
}
// 调用native方法,为C++对象设置surface
_setVideoSurface(surface);
// 调用surfaceHolder设置屏幕常亮
updateSurfaceScreenOn();
}
// 对应的native方法
private native void _setVideoSurface(Surface surface);
JNI也是一样的,调用到对应frameworks/base/media/jni/android_media_MediaPlayer.cpp下的native方法实现:
static void
android_media_MediaPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
{
setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
}
static void
setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
{
// 根据java obj保留的引用,获取到C++的player实例对象
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL) {
if (mediaPlayerMustBeAlive) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
}
return;
}
// 减少对已有的旧的IGraphicBufferProducer(或者是说图像缓存区)的引用
decVideoSurfaceRef(env, thiz);
sp<IGraphicBufferProducer> new_st;
if (jsurface) {
// 获取Surface对应的C++对象
sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
if (surface != NULL) {
// 通过Surface的IGraphicBufferProducer接口对图像缓存区进行管理
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;
}
}
// 设置cpp对象IGraphicBufferProducer的引用给java的mNativeSurfaceTexture字段
env->SetLongField(thiz, fields.surface_texture, (jlong)new_st.get());
// This will fail if the media player has not been initialized yet. This
// can be the case if setDisplay() on MediaPlayer.java has been called
// before setDataSource(). The redundant call to setVideoSurfaceTexture()
// in prepare/prepareAsync covers for this case.
// 调用C++MediaPlayer的方法
mp->setVideoSurfaceTexture(new_st);
}
而在frameworks/base/media/jni/android_media_MediaPlayer.cpp中的MediaPlayer调用时,会调用mPlayer,即MediaPlayerService::Client的setVideoSurfaceTexture()方法。
status_t MediaPlayer::setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer)
{
ALOGV("setVideoSurfaceTexture");
Mutex::Autolock _l(mLock);
if (mPlayer == 0) return NO_INIT;
return mPlayer->setVideoSurfaceTexture(bufferProducer);
}
在frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp中MediaPlayerService::Client调用setVideoSurfaceTexture()的情况。
status_t MediaPlayerService::Client::setVideoSurfaceTexture(
const sp<IGraphicBufferProducer>& bufferProducer)
{
ALOGV("[%d] setVideoSurfaceTexture(%p)", mConnId, bufferProducer.get());
// 获取mPlayer,也就是上文setDataSource中创建的NuPlayerDriver
sp<MediaPlayerBase> p = getPlayer();
if (p == 0) return UNKNOWN_ERROR;
// 如果MediaPlayerService::Client中已经设置了的WindowBinder和本次调用是同一个
// 也就是已经设置过了,则不再重复处理,直接返回ok
sp<IBinder> binder(IInterface::asBinder(bufferProducer));
if (mConnectedWindowBinder == binder) {
return OK;
}
sp<ANativeWindow> anw;
if (bufferProducer != NULL) {
// 通过IGraphicBufferProducer接口对象创建回Surface对象
anw = new Surface(bufferProducer, true /* controlledByApp */);
// connect,确定Surface的有效性
status_t err = nativeWindowConnect(anw.get(), "setVideoSurfaceTexture");
if (err != OK) {
ALOGE("setVideoSurfaceTexture failed: %d", err);
// Note that we must do the reset before disconnecting from the ANW.
// Otherwise queue/dequeue calls could be made on the disconnected
// ANW, which may result in errors.
reset();
Mutex::Autolock lock(mLock);
disconnectNativeWindow_l();
return err;
}
}
// Note that we must set the player's new GraphicBufferProducer before
// disconnecting the old one. Otherwise queue/dequeue calls could be made
// on the disconnected ANW, which may result in errors.
// 调用到NuPlayerDriver的setVideoSurfaceTexture()方法
status_t err = p->setVideoSurfaceTexture(bufferProducer);
mLock.lock();
// 如果有mConnectedWindow不为空,说明已经设置过另外的一个Surface。
// 这里需要先释放掉旧的,再设置新的Surface。会调用到nativeWindowDisconnect()
disconnectNativeWindow_l();
if (err == OK) {
// 保留新设置的Surface引用
mConnectedWindow = anw;
mConnectedWindowBinder = binder;
mLock.unlock();
} else {
mLock.unlock();
// 出错则disconnect当前设置的Surface
status_t err = nativeWindowDisconnect(
anw.get(), "disconnectNativeWindow");
if (err != OK) {
ALOGW("nativeWindowDisconnect returned an error: %s (%d)",
strerror(-err), err);
}
}
return err;
}
继续深入,在frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp中对于NuPlayerDrvier调用setVideoSurfaceTexture()方法。
status_t NuPlayerDriver::setVideoSurfaceTexture(
const sp<IGraphicBufferProducer> &bufferProducer) {
ALOGV("setVideoSurfaceTexture(%p)", this);
Mutex::Autolock autoLock(mLock);
if (mSetSurfaceInProgress) {
return INVALID_OPERATION;
}
switch (mState) {
case STATE_SET_DATASOURCE_PENDING:
case STATE_RESET_IN_PROGRESS:
return INVALID_OPERATION;
default:
break;
}
mSetSurfaceInProgress = true;
// 调用NuPlayer对应的方法
mPlayer->setVideoSurfaceTextureAsync(bufferProducer);
// 阻塞等待完成
while (mSetSurfaceInProgress) {
mCondition.wait(mLock);
}
return OK;
}
对应的NuPlayer的调用,会发起一个kWhatSetVideoSurface事件。
void NuPlayer::setVideoSurfaceTextureAsync(
const sp<IGraphicBufferProducer> &bufferProducer) {
sp<AMessage> msg = new AMessage(kWhatSetVideoSurface, this);
if (bufferProducer == NULL) {
msg->setObject("surface", NULL);
} else {
msg->setObject("surface", new Surface(bufferProducer, true /* controlledByApp */));
}
msg->post();
}
之后NuPlayer会在事件循环中处理对应的事件,具体NuPlayer做了什么,我们在后文中进行分析。