OpenGL ES 与 GLSurfaceView 渲染视频帧

栏目: 后端 · 发布时间: 4年前

内容简介:杨胖雨:《情深深雨濛濛》

杨胖雨:《情深深雨濛濛》

杨胖雨对 《情深深雨濛濛》的翻唱可以说是炒鸡好听啊,你不妨听听。

OpenGL ES 与 GLSurfaceView 渲染视频帧

01 前言

大家好,本文是   iOS/Android 音视频专题   的第六篇,该专题中 AVPlayer 项目代码将在 Github 进行托管,你可在微信公众号 GeekDev 后台回复  资料   获取项目地址。

在上篇文章   OpenGL ES for Android 世界   中我们已经对 OpenGL ES 有了大致的了解,在本篇文章中我们将使用 OpengGL ES 将解码后的视频进行播放。

本期内容:

  • View 与 Surface 的渲染机制

  • SurfaceView/GLSurfaceView 与 Surface 的关系

  • GLSurfaceView 与 Renderer

  • SurfaceTexture 与 Surface

  • MediaCodec 解码视频并渲染

  • 结束语

02  View 与 Surface 的渲染机制

现在, 你已经对 OpenGLES 有所了解,但是在将视频渲染到视频屏幕之前,我们需要对 GLSurfaceView 与 Surface 有所了解。

如果提到 Surface 可能大部分开发者接触的比较少,在 Android 绘制系统中,Surface 是一个非常重要的概念,它向 Applicaiton 提供了 Canvas,向 SufaceFlinger 提供了可供显示的图像缓存。在  Surface 内部维护了图像 buffer 对象,这个最终就会交由  SufaceFlinger 合成显示。

在 Android 窗口中每个 Window 对象都会创建一个 Surface,这些窗口包括 Activity,Dialog,状态栏等,而 我们使用的普通 View 与所属 Window 共享 Surface 实例,普通的 View 不会自己创建一个 Surface 对象,而是将内容绘制到所属 Window 中。之所以强调是普通 View,是由于 SurfaceView / GlSurafaceView 不会共享所属 Window 的 Surface,它会自己内部维护一个 Surface。

当 Window 被创建时,Window Manger 为每个 Window 创建一个 Surface,当窗口需要重绘时,Window  调用 lockCanvas 方法锁定并返回 Canvas,Window 通过遍历 View 层级,并调用 View 的 OnDraw(Canvas canvas)  方法将 Canvas 传递给 View ,View 通过 Canvas 绘制任何内容。 这一系列操作完成后 Surface 将被 unlock ,由 SurfaceFlinger 合成到屏幕上。

03 SurfaceView/GLSurfaceView 与 Surface 的关系

SurfaceView 是 View 的子类,与普通 View 不同之处在于,它内部有自己专用的 Surface,与宿主 Window 不共享 Surface。由于, SurfaceView 与宿主 Window 的分离,对 SurfaceView 的渲染操作我们可以放到单独的线程,这样的设计是因为一些游戏,视频应用的渲染极其复杂,为了不影响对主线程事件的响应,需要将这些渲染任务独立于主线程。

SurfaceView 的工作相对简单,最重要的任务仅仅是创建了 Surface,并在宿主窗口上打了一个洞,用以显示 Surface 的内容。 下面是部分源码:

public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback 

{

private static final String TAG = "SurfaceView";

private static final boolean DEBUG = false;

final ArrayList<SurfaceHolder.Callback> mCallbacks

= new ArrayList<SurfaceHolder.Callback>();

------ 这里是重点 -----

final Surface mSurface = new Surface(); // Current surface in use

}

OpenGL ES 与 GLSurfaceView 渲染视频帧

原来蘑菇可以这么可爱

04 GLSurfaceView 与 Renderer

前面说了  SurfaceView ,而 GLSurfaceView 才是我们今天的重点,在上篇  《OpenGL ES for Android 世界》 文章中,我们已经对  GLSurfaceView 有了初步的介绍,你可能还记得我们利用 GLSurfaceView 在屏幕上绘制了一个三角形。

通过  GLSurfaceView 前缀我们大致可以猜到,它一定是和 OpenGL 相关的,正如你猜测的那样,GLSurfaceView 确实是封装了 GL 的相关内容,严格来说是使用 EGL 搭建了 GL 环境。 让我们可以通过 Render 接口,就可以直接渲染我们要显示的内容。

GLSurfaceView 是对 SurfaceView 的扩展,不仅添加了 EGL 管理,而且为我们创建了一个 Renderer 线程, SurfaceView 的设计允许我们在主线程外执行渲染操作,而  GLSurfaceView 继承自 SurfaceView,并在内部创建了一个 GLThread,你的所有绘制任务,都将在  GLThread 线程中执行。

GLSurfaceView 有一个 setRenderer(Renderer renderer)  方法,它允许我们实现自己的渲染逻辑,Renderer 接口的定义如下:

public interface Renderer { 

/** Surface 创建成功,GL 环境已经准备完成 */

void onSurfaceCreated(GL10 gl, EGLConfig config);

/** Surface 的宽高发生变化,一般在横竖屏或者 GLSurfaceView 宽高方式变化时 */

void onSurfaceChanged(GL10 gl, int width, int height);

/** 在该方法内实现我们的 GL 渲染逻辑 */

void onDrawFrame(GL10 gl);

}

当我们解码完成拿到每帧的 Texture 时,我们将在 Renderer 的 OnDrawFrame  方法中将  Texture 绘制到屏幕上。

05 SurfaceTexture 与 Surface


SurfaceView 是 Surface + View 的结合体,而 SurfaceTexture 是 Surface + GL Texture 的结合体,SurfaceTexture 可以将 Surface 中最近的图像数据更新到 GL Texture 中。通过  GL Texture 我们就可以拿到视频帧,然后直接渲染到 GLSurfaceView 中。

通过 setOnFrameAvailableListener(listener) 可以向 SurfaceTexture 注册监听事件,当 Surface 有新的图像可用时,调用 SurfaceTexture 的  updateTexImage()  方法将图像内容更新到 GL Texture 中,然后做绘制操作。

下面是 SurfaceTexture 的基本创建流程:

// step:1 创建绑定的纹理

int textures[] = new int[1];

GLES20.glGenTextures(1, textures, 0);

int texId = textures[0];

GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);

// step:2 通过 texId 将与 SurfaceTexture 绑定

SurfaceTexture surfaceTexture = new SurfaceTexture(texId);

// step:3 注册监听事件

surfaceTexture.setOnFrameAvailableListener(new surfaceTexture.OnFrameAvailableListener() {

@Override

public void onFrameAvailable(SurfaceTexture surfaceTexture) {

// request render

}

});

06 MediaCodec 解码视频并渲染

好了,上边我们说了那么多,都是解码视频帧的基础工作,现在,可以干点正事了。

解码并渲染一个视频的标准流程如下:

  • 初始化 GLSurfaceView 设置,并制定 Renderer

  • 初始化 SurfaceTexture,并注册 onFrameAvaiableListener 监听

  • 初始化分离器,选择视频轨道

  • 初始化解码器,并配置 Surface

  • 实现 Renderer 接口,渲染视频纹理

step1 : 初始化 GLSurfaceView 设置,并制定 Renderer

private void step1() {

mSurfaceView = findViewById(surfaceView);

// openGL ES 2.0

mSurfaceView.setEGLContextClientVersion(2);

mSurfaceView.setRenderer(mRenderer)

// 设置渲染模式  GLSurfaceView.RENDERMODE_WHEN_DIRTY 只有使用  requestRender() 是才会触发渲染

mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

}

step2:  初始化 SurfaceTexture,并注册 onFrameAvaiableListener 监听

private void step2() {

mSurfaceTexture = new AVSurfaceTexture();

mSurfaceTexture.getSurfaceTexture().setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {

     // 当 MediaCodec 的 releaseOutputBuffer(idx,true) 调用后

// OnFrame 被触发

@Override

public void onFrameAvailable(SurfaceTexture surfaceTexture) {

// 通知 Renderer

mSurfaceView.requestRender();

}

});

}

step3:  初始化分离器,选择视频轨道

private void step3(){

// step 1:创建一个媒体分离器

mMediaExtractor = new MediaExtractor();

// step 2:为媒体分离器装载媒体文件路径

// 指定文件路径

Uri videoPathUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.demo_video);

try {

mMediaExtractor.setDataSource(this, videoPathUri, null);

} catch (IOException e) {

e.printStackTrace();

}

// step 3:获取并选中指定类型的轨道

// 媒体文件中的轨道数量 (一般有视频,音频,字幕等)

int trackCount = mMediaExtractor.getTrackCount();

// mime type 指示需要分离的轨道类型

String extractMimeType = "video/";

MediaFormat trackFormat = null;

// 记录轨道索引id,MediaExtractor 读取数据之前需要指定分离的轨道索引

int trackID = -1;

for (int i = 0; i < trackCount; i++) {

trackFormat = mMediaExtractor.getTrackFormat(i);

if (trackFormat.getString(MediaFormat.KEY_MIME).startsWith(extractMimeType)) {

trackID = i;

break;

}

}

// 媒体文件中存在视频轨道

// step 4:选中指定类型的轨道

if (trackID != -1)

mMediaExtractor.selectTrack(trackID);

}

step4:  初始化解码器,并配置 Surface

 private void step4() {

MediaFormat trackFormat = mMediaExtractor.getTrackFormat(mMediaExtractor.getSampleTrackIndex());

try {

mMediaCodec = MediaCodec.createDecoderByType(trackFormat.getString(MediaFormat.KEY_MIME));

/** configure 中指定 Surface */

mMediaCodec.configure(trackFormat,mSurfaceTexture.getSurface(),null,0);

mMediaCodec.start();

} catch (IOException e) {

e.printStackTrace();

}

}

step5: 实现 Renderer 接口,渲染视频纹理  

private GLSurfaceView.Renderer mRenderer = new GLSurfaceView.Renderer() {

@Override

public void onSurfaceCreated(GL10 gl, EGLConfig config) {

mProgram = new GPUTextureProgram(GPUTextureProgram.ProgramType.TEXTURE_EXT);

}

@Override

public void onSurfaceChanged(GL10 gl, int width, int height) {

GLES20.glViewport(0,0 ,width,height);

}

@Override

public void onDrawFrame(GL10 gl) {

// 更新视频纹理

mSurfaceTexture.updateTexImage();

// 将纹理绘制到屏幕中

mProgram.draw(mSurfaceTexture.getTextureID());

}

};

最终,我们的效果如下:

该示例代码你可以在 AVPlayer 的 DemoMediaPlayer 中找到完整的代码示例。

07 结束语

现在, 我们初步实现了一个播放器雏形,没有暂停, 快慢速,更没有播放声音,也没有实现音视频同步,先不要着急,关注  GeekDev  公众号你将在第一时间获取最新内容。 如果你想了解更多信息, 可关注微信公众号 ( GeekDev 并回复   资料  获取。

往期内容:

iOS/Android 音视频开发专题介绍

iOS/Android 音视频概念介绍

MediaCodec/OpenMAX/StageFright 介绍

使用 MediaExtractor 及 MediaCodec 解码音视频

《OpenGL ES for Android 世界》

下期预告:

《 AVPlayer 添加音效 

   

OpenGL ES 与 GLSurfaceView 渲染视频帧


以上所述就是小编给大家介绍的《OpenGL ES 与 GLSurfaceView 渲染视频帧》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Rust编程之道

Rust编程之道

张汉东 / 电子工业出版社 / 2019-1 / 128

Rust 是一门利用现代化的类型系统,有机地融合了内存管理、所有权语义和混合编程范式的编程语言。它不仅能科学地保证程序的正确性,还能保证内存安全和线程安全。同时,还有能与C/C++语言媲美的性能,以及能和动态语言媲美的开发效率。 《Rust编程之道》并非对语法内容进行简单罗列讲解,而是从四个维度深入全面且通透地介绍了Rust 语言。从设计哲学出发,探索Rust 语言的内在一致性;从源码分析入......一起来看看 《Rust编程之道》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具