今天我们要讨论的是关键帧的音视频开发圈的一位朋友在社群里提的问题,如下:
在使用关键帧公众号提供的 Android 视频封装的 Demo 时发现一个问题:相机采集的数据使用 Surface 编码时,如果采集数据输出的纹理和编码器使用的纹理是共享一个纹理,由于采集和编码在两个线程上,如果编码速度跟不上采集速度,就有可能出现编码还在编第 1 帧时,相机已经采集好第 2、3、4、5 帧并把共享纹理的数据更新为第 5 帧了,编码器编码完第一帧后取到的数据是第 5 帧,这样录制的视频看起来就会卡顿,这种情况有什么优化方案吗?
以下是回答,欢迎大家留言讨论补充:
这里先介绍一下这个问题的背景:在我们提供的 Android 视频封装的 Demo 中,使用 KFSurfaceTexture 录制 MP4 文件并使用 Surface 编码时大致过程如下:
- 1、相机采集输出数据,会更新到相机的 OES 纹理上,这个纹理的 id 是
mSurfaceTextureId。- 2、当采集完一帧数据时,相机关联的 SurfaceTexture 会回调一次
onFrameAvailable方法,这里会拼装一帧KFTextureFrame数据,其中包含了纹理mSurfaceTextureId。这帧KFTextureFrame数据一方面会给预览渲染展示,同时也会送给编码器KFVideoSurfaceEncoder。- 3、在
KFVideoSurfaceEncoder中会新起一个线程进行编码,将纹理mSurfaceTextureId绘制到 MediaCodec 绑定的 Surface,绘制完成后通过mEGLContext.swapBuffers通知 MediaCodec 数据已经准备好,可以开始编码。由于整个流程中相机采集模块和编码模块是共用了纹理
mSurfaceTextureId,而采集和编码又是在不同的线程,所以就可能出现题目中出现的因为采集和编码速度不一致导致最后录制的视频看起来丢帧了所以卡顿的问题。要优化这个问题可以做一个纹理缓存池给相机和编码器共用:
- 1、创建一个 FBO,通过 FBO 切换绑定纹理来实现将纹理 a 的数据绘制到纹理 b,这里面有两种场景:
- 将相机输出的纹理数据拷贝到纹理缓存池一个空闲纹理上
- 将纹理缓存池中的待编码纹理数据绘制到 MediaCodec 绑定的 Surface 上
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENTO, GL_TEXTURE_2D, textureID, 0); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { // error } glBindFramebuffer(GL_FRAMEBUFFER, 0);
- 2、纹理缓存池的纹理数量需要设置一个上限,当纹理缓存池没有空闲可用纹理时,则需要做丢帧策略清空某些纹理。这里的丢帧策略可以对缓存池中的待编码纹理做均匀丢帧,尽量提升画面流畅度。
- 3、由于纹理缓存池梳理有上限,所以需要对纹理数据进行封装,增加标记字段用于标记当前纹理是否可用。一个纹理
可用则表示相机采集来的数据可以拷贝到该纹理中,不可用则表示该纹理数据在等待编码器拷贝去编码。编码器拷贝完缓存池的某一个纹理后,即标记该纹理为可用状态。
如果你也对音视频技术感兴趣,比如,符合下面的情况:
- 在校大学生 → 学习音视频开发
- iOS/Android 客户端开发 → 转入音视频领域
- 直播/短视频业务开发 → 深入音视频底层 SDK 开发
- 音视频 SDK 开发 → 提升技能,解决优化瓶颈
可以长按识别或扫描下面二维码,了解一下这个社群,根据自己的情况按需加入: