You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 20:24:58