4.2 渲染停滞
视频在二次渲染后会出现偶现的画面停滞现象 , 主要是SurfaceTexture的OnFrameAvailableListener不返回数据了 。该问题的根本原因是GPU的渲染和视频帧的读取不同步 , 进而导致SurfaceTexture的底层核心BufferQueue读取Buffer出了问题 。下面我们通过BufferQueue的机制和核心源码深入研究下:
首先从二次渲染的工作流程入手 。从图像流(来自Camera预览、视频解码、GL绘制场景等)中获得帧数据 , 此时OnFrameAvailableListener会回调 。再调用updateTexImage() , 会根据内容流中最近的图像更新SurfaceTexture对应的GL纹理对象 。我们再对纹理对象做处理 , 比如添加滤镜等效果 。SurfaceTexture底层核心管理者是BufferQueue , 本身基于生产者消费者模式 。
BufferQueue管理的Buffer状态分为:FREE、DEQUEUED、QUEUED、ACQUIRED、SHARED 。当Producer需要填充数据时 , 需要先Dequeue一个Free状态的Buffer , 此时Buffer的状态为DEQUEUED , 成功后持有者为Producer 。随后Producer填充数据完毕后 , 进行Queue操作 , Buffer状态流转为QUEUED , 且Owner变为BufferQueue , 同时会回调BufferQueue持有的ConsumerListener的onFrameAvailable , 进而通知Consumer可对数据进行二次处理了 。Consumer先通过Acquire操作 , 获取处于QUEUED状态的Buffer , 此时Owner为Consumer 。当Consumer消费完Buffer后 , 会执行Release , 该Buffer会流转回BufferQueue以便重用 。BufferQueue核心数据为GraphicBuffer , 而GraphicBuffer会根据场景、申请的内存大小、申请方式等的不同而有所不同 。
SurfaceTexture的核心流程如下图:

文章插图
通过上图可知 , 我们的Producer是Video , 填充视频帧后 , 再对纹理进行特效处理(滤镜等) , 最后再渲染出来 。前面我们分析了BufferQueue的工作流程 , 但是在Producer要填充数据、执行dequeueBuffer操作时 , 如果有Buffer已经QUEUED , 且申请的dequeuedCount大于mMaxDequeuedBufferCount , 就不会再继续申请Free Buffer了 , Producer就无法DequeueBuffer , 也就导致onFrameAvailable无法最终调用 , 核心源码如下:
status_t BufferQueueProducer::dequeueBuffer(int *outSlot,sp<android::Fence> *outFence, uint32_t width, uint32_t height, PixelFormat format, uint32_t usage,FrameEventHistoryDelta* outTimestamps) { ...... int found = BufferItem::INVALID_BUFFER_SLOT; while (found == BufferItem::INVALID_BUFFER_SLOT) { status_t status = waitForFreeSlotThenRelock(FreeSlotCaller::Dequeue, & found); if (status != NO_ERROR) { return status; } } ......}status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller, int*found) const{ ...... while (tryAgain) { int dequeuedCount = 0; int acquiredCount = 0; for (int s : mCore -> mactiveBuffers) { if (mSlots[s].mBufferState.isDequeued()) { ++dequeuedCount; } if (mSlots[s].mBufferState.isAcquired()) { ++acquiredCount; } } // Producers are not allowed to dequeue more than // mMaxDequeuedBufferCount buffers. // This check is only done if a buffer has already been queued if (mCore -> mBufferHasBeenQueued && dequeuedCount >= mCore -> mMaxDequeuedBufferCount) { BQ_LOGE("%s: attempting to exceed the max dequeued buffer count " "(%d)", callerString, mCore -> mMaxDequeuedBufferCount); return INVALID_OPERATION; } } ....... }5. 码流适配
视频的监控体系发现 , Android 9.0的系统出现大量的编解码失败问题 , 错误信息都是相同的 。在MediaCodec的Configure时候出异常了 , 主要原因是我们强制使用了CQ码流 , Android 9.0以前并无问题 , 但9.0及以后对CQ码流增加了新的校验机制而我们没有适配 。核心流程代码如下:
status_t ACodec::configureCodec( const char *mime, const sp<AMessage> &msg) { ....... if (encoder) { if (mIsVideo || mIsImage) { if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) { return INVALID_OPERATION; } } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC) && !msg->findInt32("bitrate", &bitrate)) { return INVALID_OPERATION; } } .......}static bool findVideoBitrateControlInfo(const sp<AMessage> &msg, OMX_VIDEO_CONTROLRATETYPE *mode, int32_t *bitrate, int32_t *quality) { *mode = getVideoBitrateMode(msg); bool isCQ = (*mode == OMX_Video_ControlRateConstantQuality); return (!isCQ && msg->findInt32("bitrate", bitrate)) || (isCQ && msg->findInt32("quality", quality));}9.0前并无对CQ码流的强校验 , 如果不支持该码流也会使用默认支持的码流 , static OMX_VIDEO_CONTROLRATETYPE getBitrateMode(const sp<AMessage> &msg) { int32_t tmp; if (!msg->findInt32("bitrate-mode", &tmp)) { return OMX_Video_ControlRateVariable; } return static_cast<OMX_VIDEO_CONTROLRATETYPE>(tmp);}
推荐阅读
- Android NDK-深入理解JNI
- Android如何使用注解进行代码检查
- 抖音 Android 性能优化系列:启动优化之理论和工具篇
- Android缩放手势的检测
- 自动识别 Android 不合理的内存分配
- Android面试题集锦之 Service
- 怎样搭高质量的Android项目框架,框架的结构具体描述?
- 谷歌Android 12L的适配机型,看得我们有点懵
- 浅谈Android类加载器
- Android 12推出正式版!这次的升级到底值不值得更新呢?
