您的位置:首页 > 科技 > IT业 > seo站外推广业务外包_新加坡最近疫情_西安网站建设推广优化_深圳google推广

seo站外推广业务外包_新加坡最近疫情_西安网站建设推广优化_深圳google推广

2025/4/7 19:49:20 来源:https://blog.csdn.net/gxhuhuhu/article/details/146987111  浏览:    关键词:seo站外推广业务外包_新加坡最近疫情_西安网站建设推广优化_深圳google推广
seo站外推广业务外包_新加坡最近疫情_西安网站建设推广优化_深圳google推广

文章目录

  • 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
<org.webrtc.SurfaceViewRendererandroid:id="@+id/surfaceView"android:layout_width="match_parent"android:layout_height="match_parent"/>// 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;@Overridepublic 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.javapublic 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 存放当前正在处理的framependingFrame = 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 <= 0) {// FPS reduction is disabled.shouldRenderFrame = true;} else {final long currentTimeNs = System.nanoTime();if (currentTimeNs < nextFrameTimeNs) {logD("Skipping frame rendering - fps reduction is active.");shouldRenderFrame = false;} else {nextFrameTimeNs += minRenderPeriodNs;// The time for the next frame should always be in the future.nextFrameTimeNs = Math.max(nextFrameTimeNs, currentTimeNs);shouldRenderFrame = true;}}}final long startTimeNs = System.nanoTime();// 计算原始帧宽高比final float frameAspectRatio = frame.getRotatedWidth() / (float) frame.getRotatedHeight();// 如果用户自定义了宽高比,就用用户自定义的final float drawnAspectRatio;synchronized (layoutLock) {drawnAspectRatio = layoutAspectRatio != 0f ? layoutAspectRatio : frameAspectRatio;}// 根据原始宽高比和用户自定义宽高比,计算缩放率final float scaleX;final float scaleY;if (frameAspectRatio > drawnAspectRatio) {scaleX = drawnAspectRatio / frameAspectRatio;scaleY = 1f;} else {scaleX = 1f;scaleY = frameAspectRatio / drawnAspectRatio;}// 准备好变换矩阵,主要操作为镜像、缩放drawMatrix.reset();drawMatrix.preTranslate(0.5f, 0.5f);drawMatrix.preScale(mirrorHorizontally ? -1f : 1f, mirrorVertically ? -1f : 1f);drawMatrix.preScale(scaleX, scaleY);drawMatrix.preTranslate(-0.5f, -0.5f);try {if (shouldRenderFrame) {//OpenGL 清理画布GLES20.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */);GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);int drawWidth = eglBase.surfaceWidth();int drawHeight = eglBase.surfaceHeight();if (eglBase.surfaceWidth() <= 1080){drawWidth = 2304;drawHeight = 1294;}// 绘制frameDrawer.drawFrame(frame, drawer, drawMatrix, 0, 0,drawWidth, drawHeight);// swap显示到屏幕final long swapBuffersStartTimeNs = System.nanoTime();if (usePresentationTimeStamp) {eglBase.swapBuffers(frame.getTimestampNs());} else {eglBase.swapBuffers();}final long currentTimeNs = System.nanoTime();synchronized (statisticsLock) {++framesRendered;renderTimeNs += (currentTimeNs - startTimeNs);renderSwapBufferTimeNs += (currentTimeNs - swapBuffersStartTimeNs);}}notifyCallbacks(frame, shouldRenderFrame);} catch (GlUtil.GlOutOfMemoryException e) {logE("Error while drawing frame", e);final ErrorCallback errorCallback = this.errorCallback;if (errorCallback != null) {errorCallback.onGlOutOfMemory();}// Attempt to free up some resources.drawer.release();frameDrawer.release();bitmapTextureFramebuffer.release();// Continue here on purpose and retry again for next frame. In worst case, this is a continous// problem and no more frames will be drawn.} finally {frame.release();}
}

可以看到renderFrameOnRenderThread主要做了绘制前的准备工作,包括清理画布、生成变化矩阵,最后由frameDrawer.drawFrame进行绘制,并通过eglBase.swapBuffers将GPU缓存交换到surface上进行显示

VideoFrameDrawer

在VideoFrameDrawer的drawFrame方法中,再次进行矩阵变换后,最终交由GlGenericDrawer.drawOes进行最后的处理。从这部分的源码可以看到非常多的OpenGL相关操作的内容。OpenGL的详细使用内容,我也不太熟悉,下面就简单看一些关键部分

@Override
public void drawOes(int oesTextureId, float[] texMatrix, int frameWidth, int frameHeight,int viewportX, int viewportY, int viewportWidth, int viewportHeight) {//准备工作,初始化GlShaderprepareShader(ShaderType.OES, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight);// 激活纹理区域GLES20.glActiveTexture(GLES20.GL_TEXTURE0);// 绑定纹理,GL_TEXTURE_EXTERNAL_OES// 专门用于渲染来自摄像头、视频解码器或其他硬件加速源的图像数据GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId);// 定义渲染区域(视口)的位置和大小GLES20.glViewport(viewportX, viewportY, viewportWidth, viewportHeight);// 绘制一个四边形(2个三角形组成),将纹理渲染到视口中。GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);// 解绑纹理GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
}

总结

以上就是WebRTC收到视频帧以后的整个绘制流程,其中关于OpenGL、EGL相关的初始化,都在Surface创建的时候进行初始化的,有兴趣的也可以从源码中很清晰的看到。从源码中,我们可以看到Surface、EGL、OpenGL的使用全都解耦了,每个类都分工明确,没有一丝冗余的功能,这些思路非常值得我们学习。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com