WebRTC源码解析:Android如何渲染画面
文章目录
- SurfaceViewRenderer
- SurfaceEglRenderer / EglRenderer
- OpenGL\OpenGL ES\EGL
- renderFrameOnRenderThread
- VideoFrameDrawer
- 总结
在WebRTC链接建立成功后,如果想要将对方推送的视频流展示出来,需要调用 VideoTrack.addSink(SurfaceViewRenderer)实现。接下来我们将介绍在调用addSink后涉及到的类,以及WebRTC是如何将视频数据渲染到View。以下是整个调用流程图
首先我们来看看VideoSink接口的定义:
/** * Java version of rtc::VideoSinkInterface. */ public interface VideoSink { /** * Implementations should call frame.retain() if they need to hold a reference to the frame after * this function returns. Each call to retain() should be followed by a call to frame.release() * when the reference is no longer needed. */ @CalledByNative void onFrame(VideoFrame frame); }
从接口定义就可以猜到,WebRTC在Native部分将视频解码后会通过onFrame回调到Java层,这里我们不研究WebRTC是如何解码的,我们直接从拿到解码后的数据开始分析。
SurfaceViewRenderer
SurfaceViewRenderer是WebRTC提供的用于预览视频流的SurfaceView,它的使用方式如下:
// activity.xml // MainActivity.kt VideoTrack.addSink(binding.surfaceView)
根据SurfaceViewRenderer的定义,它继承VideoSink,所以第一步我们要看SurfaceViewRenderer是如何处理onFrame回调的。
public class SurfaceViewRenderer extends SurfaceView implements SurfaceHolder.Callback, VideoSink, RendererCommon.RendererEvents { private final SurfaceEglRenderer eglRenderer; @Override public void onFrame(VideoFrame frame) { eglRenderer.onFrame(frame); } }
当SurfaceViewRenderer收到onFrame回调时,会直接交给SurfaceEglRenderer处理。
为什么还需要SurfaceViewRenderer呢,既然这里什么操作都没有直接给SurfaceEglRenderer处理,直接使用SurfaceEglRenderer不就好了?
答案在SurfaceViewRenderer的定义中,SurfaceViewRenderer是继承SurfaceView并实现了SurfaceHolder.Callback接口的,说明它既能作为View直接渲染,同时还对Surface的生命周期进行了处理。
@Override public void surfaceCreated(final SurfaceHolder holder) { ThreadUtils.checkIsOnMainThread(); surfaceWidth = surfaceHeight = 0; updateSurfaceSize(); }
从这部分代码可以看出来,SurfaceViewRenderer的主要功能是对Surface宽高等属性进行设置,实际的渲染都交给了SurfaceEglRenderer,那么我们继续看SurfaceEglRenderer做了什么。
SurfaceEglRenderer / EglRenderer
首先我们看SurfaceEglRenderer的定义
public class SurfaceEglRenderer extends EglRenderer implements SurfaceHolder.Callback { public class EglRenderer implements VideoSink {
SurfaceEglRenderer 直接继承EglRenderer并实现SurfaceHolder.Callback接口,这里我们把EglRenderer的定义也列出来,它直接实现VideoSink。所以从这个定义我们就可以分析出,EglRenderer是最纯粹的处理视频数据的地方,而SurfaceEglRenderer增加了对Surface生命周期管理的包装。我们看看源码是否正确。
//SurfaceEglRenderer.java // 1. surface创建时 @Override public void surfaceCreated(final SurfaceHolder holder) { ThreadUtils.checkIsOnMainThread(); createEglSurface(holder.getSurface()); } // 以下代码来自:EglRenderer.java public void createEglSurface(Surface surface) { createEglSurfaceInternal(surface); } private final EglSurfaceCreation eglSurfaceCreationRunnable = new EglSurfaceCreation(); // 2. 异步处理surface private void createEglSurfaceInternal(Object surface) { eglSurfaceCreationRunnable.setSurface(surface); postToRenderThread(eglSurfaceCreationRunnable); } private class EglSurfaceCreation implements Runnable { private Object surface; // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression. @SuppressWarnings("NoSynchronizedMethodCheck") public synchronized void setSurface(Object surface) { this.surface = surface; } // 3. 核心处理surface的地方 @Override @SuppressWarnings("NoSynchronizedMethodCheck") public synchronized void run() { if (surface != null && eglBase != null && !eglBase.hasSurface()) { if (surface instanceof Surface) { eglBase.createSurface((Surface) surface); } else if (surface instanceof SurfaceTexture) { eglBase.createSurface((SurfaceTexture) surface); } else { throw new IllegalStateException("Invalid surface: " + surface); } eglBase.makeCurrent(); // Necessary for YUV frames with odd width. GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); } } }
SurfaceEglRenderer对Surface的处理方式是异步的将其设置给eglBase,EGL的内容我们会在后面介绍。我们再来看对视频帧的处理
// SurfaceEglRenderer.java // VideoSink interface. @Override public void onFrame(VideoFrame frame) { // 1. 对onFrame进行简单的事件上报,包括(初次收到frame,frame分辨率变化) // 随后交由EglRenderer处理 updateFrameDimensionsAndReportEvents(frame); super.onFrame(frame); } // EglRenderer.java @Override public void onFrame(VideoFrame frame) { synchronized (statisticsLock) { ++framesReceived; } final boolean dropOldFrame; synchronized (handlerLock) { if (renderThreadHandler == null) { logD("Dropping frame - Not initialized or already released."); return; } synchronized (frameLock) { dropOldFrame = (pendingFrame != null); if (dropOldFrame) { pendingFrame.release(); } // pendingFrame 存放当前正在处理的frame pendingFrame = frame; // 引用计数+1,以防被回收 pendingFrame.retain(); // 异步处理 frame // 疑问点1 : renderThreadHandler.post(this::renderFrameOnRenderThread); } } if (dropOldFrame) { synchronized (statisticsLock) { ++framesDropped; } } }
可以看到SurfaceEglRenderer并不参与视频渲染的工作,而是做简单的事件上报,例如首次收到视频帧,视频帧分辨率变化,这些事件回调定义在RendererCommon.RendererEvents。真正处理frame的是EglRenderer,它会使用pendingFrame保存最新收到的frame,随后交给hander异步处理。
这里有一个疑问(以上代码注释中疑问点1):如果处理frame处理速度远低于frame到来的速度,那么会不断的调用handler.post并在handler积累runnable,这样不会有性能问题吗?
通过查询我得到的答案是不会,原因有几点,首先是pendingFrame的使用,当新的frame来临时,会先判断pendingFrame是否不为空,也就是是否有未处理的帧,如果有未处理的帧,会直接通过pendingFrame.release()抛弃掉,并将最新的frame赋值给pendingFrame。这样无论frame来的多快,永远只处理最新的frame。其次是renderFrameOnRenderThread方法中,当pendingFrame为空时会立马return,即使handler中post了过多的runnable,也会是空执行。
接下来就该看renderFrameOnRenderThread它是如何处理视频帧了,在此之前我觉得应该补充一下前文提到的EGL了。
OpenGL\OpenGL ES\EGL
随着计算机图形学的发展,越来越多的 2D 和 3D 图形的绘制需求产生,然而直接操作 GPU 进行绘制对于开发者来说过于复杂。因此,出现了一系列绘制 API,其中就包括 OpenGL。OpenGL 是一个跨平台、开源的 2D 和 3D 图形绘制 API。而 OpenGL ES 是 OpenGL 的精简版,它主要应用在一些系统资源受限的嵌入式设备,例如手机、游戏主机等。
由于 OpenGL 仅是一个图形渲染绘制接口,它并不包括将 GPU 渲染结果显示到显示器的过程。这种设计的原因是,首先 OpenGL 作为跨平台接口,不同操作系统有不同的窗口管理机制;其次,这样可以解耦窗口管理和图形渲染。所以,想让 OpenGL 能够在不同系统间运行,还需要依赖例如 WGL(Windows)、GLX(Linux)、CGL(macOS)、EGL(Android)等用于衔接 OpenGL 和原生窗口的 API。
简单来说,OpenGL 和 OpenGL ES 负责绘制图形,而 EGL 等则负责将图形显示到屏幕上。
以 Android 系统为例,完整的工作流程如下:
1.创建原生窗口(Surface)
2.初始化 EGL,并将其和 Surface 绑定
3.通过 OpenGL 发送渲染指令
4.GPU 接收后进行渲染绘制等操作,绘制结果保存在 GPU 缓冲区
5.通过 EGL 将 GPU 缓冲区数据交给窗口系统
6.窗口系统进行刷新
renderFrameOnRenderThread
了解完OpenGL相关内容后,我们继续回到WebRTC源码
private void renderFrameOnRenderThread() { //从pendingFrame中读取最新视频帧 final VideoFrame frame; synchronized (frameLock) { if (pendingFrame == null) { return; } frame = pendingFrame; pendingFrame = null; } // 异常判断 if (eglBase == null || !eglBase.hasSurface()) { frame.release(); return; } // 如果设置了fps,做帧率控制, final boolean shouldRenderFrame; synchronized (fpsReductionLock) { if (minRenderPeriodNs == Long.MAX_VALUE) { // Rendering is paused. shouldRenderFrame = false; } else if (minRenderPeriodNs // FPS reduction is disabled. shouldRenderFrame = true; } else { final long currentTimeNs = System.nanoTime(); if (currentTimeNs