diff --git a/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt b/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt index b0765f0..b73ef1d 100644 --- a/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt +++ b/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt @@ -49,7 +49,7 @@ class VideoDecoder( companion object { private const val TAG = "VideoDecoder" private const val TIMEOUT_US = 10000L - private const val INPUT_BUFFER_QUEUE_CAPACITY = 50 // 输入缓冲区容量 + private const val INPUT_BUFFER_QUEUE_CAPACITY = 100 // 增大输入缓冲区容量 } // region 成员变量定义 @@ -64,19 +64,23 @@ class VideoDecoder( private var running = true // 解码器运行状态 private val frameSeqSet = Collections.newSetFromMap(ConcurrentHashMap()) // 防止重复帧入队 - // 解码输出缓冲区,容量为100帧 - private val outputFrameQueue = LinkedBlockingQueue(50) + // 解码输出缓冲区,增大容量 + private val outputFrameQueue = LinkedBlockingQueue(100) // 渲染线程控制 - // 定时渲染调度器 private var scheduler = Executors.newSingleThreadScheduledExecutor() + private var lastRenderTimeMs = 0L // 记录上次渲染时间 + + // 这些变量移到init块中,因为它们依赖renderFps + private var renderIntervalMs: Long = 0 + private val renderJitterMs = 2L // 允许的渲染时间抖动范围 // 主线程Handler,用于安全切换onFrameRendered到主线程 private val mainHandler = Handler(Looper.getMainLooper()) // 渲染帧率(fps),可由外部控制,默认30 @Volatile - var renderFps: Int = 30 + var renderFps: Int = 20 // 兜底:记录最近一次I帧的frameSeq,P/B帧依赖校验 @Volatile @@ -128,6 +132,9 @@ class VideoDecoder( // region 初始化与解码器配置 init { + // 初始化渲染相关参数 + renderIntervalMs = (1000.0 / renderFps).toLong() + // 配置Surface尺寸 surfaceTexture.setDefaultBufferSize(width, height) // 选择MIME类型 @@ -139,11 +146,19 @@ class VideoDecoder( // 创建并配置MediaFormat val format = MediaFormat.createVideoFormat(mime, width, height) format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width * height) - format.setInteger(MediaFormat.KEY_FRAME_RATE, renderFps) - format.setInteger(MediaFormat.KEY_LOW_LATENCY, 1); - format.setInteger(MediaFormat.KEY_PRIORITY, 0) // 最高优先级 - format.setInteger(MediaFormat.KEY_OPERATING_RATE, renderFps * 2) // 解码速率 - format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0) // 禁用B帧 + // 移除可能导致flush的配置 + // format.setInteger(MediaFormat.KEY_FRAME_RATE, renderFps) + // format.setInteger(MediaFormat.KEY_OPERATING_RATE, renderFps) + format.setInteger(MediaFormat.KEY_LOW_LATENCY, 1) + format.setInteger(MediaFormat.KEY_PRIORITY, 0) + format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0) + + // 高通解码器特定配置 + format.setInteger("vendor.qti-ext-dec-low-latency.enable", 1) + format.setInteger("vendor.qti-ext-dec-picture-order.enable", 0) + format.setInteger("vendor.qti-ext-dec-timestamp-mode.value", 0) // 使用原始时间戳 + format.setInteger("vendor.qti-ext-dec-drv-flush.disable", 1) // 禁用驱动层flush + // 创建解码器 val decoder = MediaCodec.createDecoderByType(mime) // 设置解码回调 @@ -210,16 +225,23 @@ class VideoDecoder( decoder.start() mediaCodec = decoder - // 启动定时渲染任务,实现完全线性调度 - // 说明:本方案通过ScheduledExecutorService定时驱动渲染,每帧间隔严格等距,不依赖阻塞或sleep + // 优化渲染任务调度 var hasNotifiedFlutter = false - var renderedFrameCount = 0 // 渲染帧计数器 + var renderedFrameCount = 0 val renderTask = Runnable { try { + val now = System.currentTimeMillis() + // 控制渲染间隔,避免过快渲染 + val timeSinceLastRender = now - lastRenderTimeMs + if (timeSinceLastRender < renderIntervalMs - renderJitterMs) { + return@Runnable + } + val frame = outputFrameQueue.poll() if (frame != null) { frame.codec.releaseOutputBuffer(frame.bufferIndex, true) - latestRenderedTimestampMs = System.currentTimeMillis() + lastRenderTimeMs = now + latestRenderedTimestampMs = now renderedFrameCount++ if (!hasNotifiedFlutter) { mainHandler.post { onFrameRendered() } @@ -229,13 +251,13 @@ class VideoDecoder( } else { Log.w(TAG, "[Render] 渲染空转,无帧可渲染,当前outputFrameQueue=${outputFrameQueue.size}") } - // 若outputFrameQueue为空,跳过本次渲染,实现线性调度 } catch (e: Exception) { Log.e(TAG, "[RenderTask] Exception", e) } } - // 固定20fps渲染(50ms间隔) - scheduler.scheduleAtFixedRate(renderTask, 0, 50, java.util.concurrent.TimeUnit.MILLISECONDS) + + // 使用更短的调度间隔,但在任务中控制实际渲染间隔 + scheduler.scheduleAtFixedRate(renderTask, 0, renderIntervalMs/2, TimeUnit.MILLISECONDS) } // endregion