使用MediaCodec与OpenGL ES播放视频出现延迟问题排查
使用MediaCodec解码本地视频并结合OpenGL ES做自定义画面处理,短时长、低画质视频可正常播放,但测试1080P、时长2分42秒的视频时,实际播放时长变为3分08秒,存在明显延迟。
从代码逻辑来看,延迟主要源于串行处理瓶颈、帧同步误差累积、喂帧效率不足三个核心问题,高清大码率视频会放大这些问题的影响。
1. 拆分多线程并行处理
当前所有逻辑(喂帧、解码、渲染、编码)串行在同一个循环中,1080P单帧处理耗时更长,串行执行会导致延迟累积。需拆分独立线程并行处理:
- 单独启动喂帧线程,持续向解码器供给输入数据,保证解码器满负荷工作
- 解码输出+OpenGL渲染保留在EGL线程
- 编码器逻辑单独开线程处理输出
示例代码:
// 启动独立喂帧线程 val feedInputThread = Thread { while (!allInputExtracted) { feedInputToDecoder() Thread.sleep(10) // 避免空转,可根据设备性能调整 } } feedInputThread.start()
2. 移除手动睡眠,依赖EGL帧同步
代码中用delay(sleepTime)控制帧间隔的方式存在严重误差:手动睡眠无法抵消渲染耗时,且Thread.sleep()本身精度有限,会导致每帧延迟叠加。正确的做法是依赖系统垂直同步(VSync),通过eglPresentationTimeANDROID精准控制帧显示时间。
删除以下代码块:
val sleepTime: Long = (bufferInfo.presentationTimeUs) / 1000 - (System.currentTimeMillis() - firstFrameShownIn) if (sleepTime > 0 ){ delay(sleepTime) }
保留EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, bufferInfo.presentationTimeUs * 1000)即可,系统会根据该时间结合VSync调度帧显示,避免手动同步的误差。
3. 优化解码器喂帧逻辑
当前feedInputToDecoder()每次循环仅喂一帧,解码器可能因输入不足处于闲置状态。修改为批量喂帧,直到无可用输入buffer为止:
fun feedInputToDecoder() { while (!allInputExtracted) { val inBufferId = decoder!!.dequeueInputBuffer(10000) // 设置10ms超时,避免空转 if (inBufferId >= 0) { val buffer = decoder!!.getInputBuffer(inBufferId) val sampleSize = extractor!!.readSampleData(buffer!!, 0) if (sampleSize >= 0) { decoder!!.queueInputBuffer(inBufferId, 0, sampleSize, extractor!!.sampleTime, extractor!!.sampleFlags) extractor!!.advance() } else { decoder!!.queueInputBuffer(inBufferId, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) allInputExtracted = true } } else if (inBufferId == MediaCodec.INFO_TRY_AGAIN_LATER) { break // 无可用输入buffer,退出本次循环 } } }
4. 提升SurfaceTexture帧处理效率
- 提高帧监听线程优先级,避免系统调度导致的帧通知延迟:
thread = HandlerThread("FrameHandlerThread", Process.THREAD_PRIORITY_DISPLAY) thread!!.start()
- 优化帧等待逻辑,移除固定超时,改为等待帧可用信号:
fun waitTillFrameAvailable() { synchronized(lock) { while (!frameAvailable) { try { lock.wait() // 等待帧可用信号,避免固定超时的资源浪费 } catch (e: InterruptedException) { Thread.currentThread().interrupt() return } } frameAvailable = false } }
5. 优化OpenGL渲染性能
1080P帧渲染是耗时大户,需针对性优化:
- 避免在渲染循环中重复创建Bitmap:
textToBitmap("TEST", outputVideoWidth, outputVideoHeight)提前创建并复用,不要每帧生成 - 减少OpenGL状态切换:若
glViewport参数无变化,不要每帧重复调用 - 优化着色器代码:移除片段着色器中不必要的复杂计算,降低GPU负载
6. 调整MediaCodec超时时间
将mediaCodedTimeoutUs从0调整为10000微秒(10毫秒),0会导致循环空转浪费CPU资源,合理的超时可让线程在无数据时休眠,平衡响应性与资源消耗。
内容的提问来源于stack exchange,提问作者YOUSEF-ELSAYED

