搬运转载请注明出处,否则将追究版权责任。交流qq群:859640274
大家好久不见,又有两个多月没有发文章了。最近新型肺炎闹得挺凶,希望大家不要出去乱跑且身体健康。本篇博客是视频编辑 SDK 解析文章中的第二篇,文章中我会介绍将上一篇文章中解码出来的视频帧通过 OpenGL 绘制出来的方式。WsVideoEditor 中的代码也已经更新了。大家在看文章的时候一定要结合项目中的代码来看。
本文分为以下章节,读者可按需阅读:
讲解 OpenGL 的教程目前有很多,所以这一章笔者不会去教大家如何入门或者使用 OpenGL。本章笔者只会从抽象的角度来和大家讨论一下笔者对于 OpenGL 的理解。至于如何入门 OpenGL 则会推荐几个有用的网站。
图1:OpenGL之我的理解.png
如图1,我们知道 OpenGL/OpenGL ES 是一个图形图像渲染框架,它的规范由Khronos组织制定,各个显卡厂商在驱动中实现规范,再由各个系统厂商集成到系统中,最终提供各种语言的 API 给开发者使用。
当然图形图像渲染框架不仅仅只有 OpenGL 这一种。Apple 的 Metal(不跨平台)、Google 的 vulkan(跨平台)、微软的 DirectX(不跨平台) 都是 OpenGL 的竞品。
那么什么是图形图像渲染框架呢?做 Android、iOS、前端、Flutter 的同学一定都用过 Canvas,在各自的平台中 Canvas 就是一个比较上层的图形图像渲染框架。
图2:Canvas对比.png
如图2,我们在使用 Canvas 绘制一个三角形的时候一般有以下步骤,在 OpenGL 中也是类似:
除了像 Canvas 一样绘制 2D 图像,OpenGL 最主要的功能还是进行 3D 绘制,这就是 Canvas 们无法企及的地方了。
要了解 OpenGL 是如何工作的,首先我们得知道:OpenGL 运行在哪里? 没错有些读者已经知道了:OpenGL 运行在 GPU 上面,至于在 GPU 上运行的好坏我就不赘述了。
我们在平时的开发当中,绝大部分时间都在与内存和 CPU 打交道。突然让我们写运行在 GPU 上面的程序,我想大部分人都会水土不服,毕竟这是一个思维上的转变。大多数教程一上来就是告诉大家如何调用 OpenGL 的 api,然后拼凑出一个程序来,大家也照猫画虎的敲出代码,但最终很多人并没有理解 OpenGL 是如何运行的,这也是它难学的地方。那么下面我会通过一张图来粗略的讲讲 OpenGL 是如何运行的。
图3:OpenGL是如何运行的
图3中有1、2、3、4、5 个步骤,这几个步骤组合起来的代码就表示绘制一个三角形到屏幕上。可运行的代码可以在learning-opengl这里找到,图中的代码只是关键步骤。我这里也只是讲解 OpenGL 的运行方式,更具体的代码使用还需要读者去前面的网站中学习。
这里我推荐两个教程,让让大家能够学习 OpenGL 的具体用法,毕竟仰望星空的同时脚踏实地也非常重要:
我的老本行是 Android 开发,所以这一章我会讲解视频编辑SDK在 Android 层的代码。代码已经更新 WsVideoEditor,本章需结合代码食用。另外本章节强依赖 从零开始仿写一个抖音App——视频编辑SDK开发(一) 的第三章SDK架构以及运行机制介绍,大家一定要先读一下。本章会省略很多已知知识。
图4:编辑SDK架构.png
图4是编辑 SDK 的架构图,从中我们可以看见 WsMediaPlayer 代理了 Native 的 NativeWSMediaPlayer 的 Java 类。该类具有一个播放器应该有的各种 API,例如 play、pause、seek 等等。其实很多 Android 中的系统类都是以这种形式存在的,例如 Bitmap、Surface、Canvas 等等。说到底 Java 只是 Android 系统方便开发者开发 App 的上层语言,系统中大部分的功能最终都会走到 Native 中去,所以读者需要习惯这种代码逻辑。那么我们就来看看这个类的运行方式吧。
mPlayer = new WsMediaPlayer()
:会创建一个 NativeWSMediaPlayer 对象,初始化的时候会创建两个对象 mPlayer.setProject(videoEditorProjectBuilder.build())
:最终走到了 NativeWSMediaPlayer::SetProject
中,这里只是将 EditorProject 设置给 VideoDecodeService 和 FrameRenderer。mPlayer.loadProject()
:最终走到了 wsvideoeditor::LoadProject 中,这里的主要逻辑是对每一段视频使用 FFmpeg 进行解封装,获取到各个视频的时长、长宽、等等信息,然后存入 EditorProject 中以便之后使用。至于 FFmpeg 的使用可以参见这几篇文章:从零开始仿写一个抖音App——音视频开篇、零开始仿写一个抖音App——基于FFmpeg的极简视频播放器 如果把播放视频比作:一个绘画者每隔 30ms 就向画布上绘制一幅连环画的话。那么绘画者就是 WsMediaPlayer,连环画就是视频,画布就是 WsMediaPlayerView。
init()
:创建 WsMediaPlayerView 是调用,初始化一些参数,注册回调。setPreviewPlayer
:将 WsMediaPlayer 交给 PlayerGLThread,以绘制。onResume()/onPause()
:需要手动在 Activity 中调用,用于启动/暂停绘制。onSurfaceTextureAvailable
:在 TextureView.draw
的时候被系统调用,表示我们可以开始进行绘制了。我们在这里就创建了一个 PlayerGLThread,用于在非主线程进行 30ms 的定时循环绘制。同时还获取了绘制窗口的大小。onSurfaceTextureSizeChanged
:当绘制窗口改变的时候,更新窗口大小,最终会作用在 OpenGL 的绘制窗口上。onSurfaceTextureDestroyed
:资源销毁。TextureView.draw
调用与 WsMediaPlayer 被设置,这两个条件同时满足时启动线程。mPlayer.onAttachedView(mWidth, mHeight)
来向 Native 同步绘制区域的大小。!mFinished && !mRenderPaused
为 true,那么调用 mPlayer.drawFrame()
进行绘制。我在从零开始仿写一个抖音App——视频编辑SDK开发(一) 的第四章VideoDecodeService解析中讲解了如何解码出视频帧,在上一章中讲解了如何在 Android 层准备好 OpenGL 的渲染环境。这些都为本章打下了基础,没有看过的同学一定要仔细阅读啊。同样本章的代码已经上传至WsVideoEditor,请结合代码食用本章。
图5:编辑SDK运行机制
图5是视频编辑 SDK 的运行机制,本次我们解析的功能是在 FrameRender 中渲染 VideoDecodeService 提供的视频帧,也就是视频播放功能。下面我们就从第二章中提到的 WsMediaPlayer.draw 方法入手。
current_time_ = current_time = GetRenderPos()
获取到的时间戳,是我构造的测试代码。decoded_frames_unit = video_decode_service_->GetRenderFrameAtPtsOrNull(current_time)
来从 VideoDecodeService 中获取到视频帧,因为 VideoDecodeService 有一个单独的线程自己对视频进行解码(代码解析前面提到过)。所以这里可能出现获取不到视频帧的情况,这也是后续需要完善的地方。frame_renderer_.Render(current_time, std::move(decoded_frames_unit))
来渲染。current_original_frame_texture_ = avframe_rgba_texture_converter_.Convert
来将AVFrame 转化成 WsTexture。此时视频帧已经从内存中被拷贝到了显存中了,WsTexture.gl_texture_ 可以理解为显存中纹理(视频帧)数据对象的指针。GetWsFinalDrawProgram()->DrawGlTexture
将视频帧真正的绘制到屏幕上。通过上一小结的介绍,我们知道了绘制视频帧的大致流程,但是我们只是粗略的介绍了整个渲染流程。所以这一节作为上一节的补充,会简单介绍一下我们的 OpenGL 缓存逻辑和绘制逻辑。
又是一篇大几千字的干货出炉,希望这篇文章能让你满意,废话不多说,我们下篇文章见。