From 5e13dbf4f62906b592302a564d0cb6620c24d334 Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 6 May 2025 09:27:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E8=87=AA=E9=80=82?= =?UTF-8?q?=E5=BA=94=E5=B8=A7=E7=8E=87=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 ----- .../video_decode_plugin/VideoDecoder.kt | 58 +++++++++++++++++++ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 37ee4a4..5ce3cdf 100644 --- a/README.md +++ b/README.md @@ -173,21 +173,6 @@ await VideoDecodePlugin.decodeFrame(pFrameData, FrameType.pFrame); - 实时视频流解码 - 基础错误处理 -### 现有问题 -- 解码后在flutter渲染实际帧数较低 - - 方案一:使用原生activity渲染视频数据 - - 方案二:多个TextureView并行交替渲染 - - 方案三:待补充 - - 方案四:待补充 - - -### 后续计划 -- [ ] 解码帧数较低 -- [ ] 增加更多错误处理 -- [ ] 完善文档和示例 -- [ ] 添加单元测试 -- [ ] 支持更多编解码格式 -- [ ] 优化内存管理 ## 插件架构与职责 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 ade4ab9..de73381 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 @@ -79,6 +79,17 @@ class VideoDecoder( // 兜底:记录最近一次I帧的frameSeq,P/B帧依赖校验 @Volatile private var lastIFrameSeq: Int? = null + // 解码输出帧时间戳队列(用于动态帧率统计和平滑) + private val decodeTimestampQueue = ArrayDeque(20) // 最多保存20帧时间戳 + private val decodeTimestampLock = ReentrantLock() // 线程安全保护 + + // EMA平滑参数 + @Volatile private var smoothedFps: Double = 18.0 // 平滑后的渲染帧率 + private val alpha = 0.2 // EMA平滑系数,越大响应越快 + private val minFps = 8 // 渲染帧率下限,防止过低 + private val maxFps = 30 // 渲染帧率上限,防止过高 + private val maxStep = 2.0 // 单次最大调整幅度,防止突变 + // 输入帧结构体 private data class FrameData( val data: ByteArray, @@ -144,6 +155,14 @@ class VideoDecoder( } override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) { if (!running) return + // 记录解码输出时间戳 + val now = SystemClock.elapsedRealtime() + decodeTimestampLock.withLock { + if (decodeTimestampQueue.size >= 20) { + decodeTimestampQueue.removeFirst() + } + decodeTimestampQueue.addLast(now) + } // 解码后帧入输出缓冲区,由渲染线程处理 val frame = DecodedFrame(codec, index, MediaCodec.BufferInfo().apply { set(0, info.size, info.presentationTimeUs, info.flags) @@ -168,6 +187,7 @@ class VideoDecoder( renderThread = Thread { var hasNotifiedFlutter = false var renderedFrameCount = 0 // 渲染帧计数器 + val fpsAdjustInterval = 10 // 每渲染10帧调整一次帧率 while (renderThreadRunning) { // 计算每帧渲染间隔 val frameIntervalMs = if (renderFps > 0) 1000L / renderFps else 66L @@ -182,6 +202,13 @@ class VideoDecoder( mainHandler.post { onFrameRendered() } hasNotifiedFlutter = true } + // 每渲染N帧动态调整一次帧率 + if (renderedFrameCount % fpsAdjustInterval == 0) { + val measuredFps = calculateDecodeFps() + val newFps = updateSmoothedFps(measuredFps) + renderFps = newFps + Log.i(TAG, "[AutoFps] measuredFps=$measuredFps, smoothedFps=$smoothedFps, renderFps=$renderFps") + } } catch (e: Exception) { Log.e(TAG, "[RenderThread] Exception", e) } @@ -190,6 +217,8 @@ class VideoDecoder( val sleepMs = frameIntervalMs - loopCost if (sleepMs > 0) { try { Thread.sleep(sleepMs) } catch (_: Exception) {} + } else { + // 若解码极慢,sleepMs为负,直接进入下一帧,防止阻塞 } } // 清理剩余帧,防止内存泄漏 @@ -257,5 +286,34 @@ class VideoDecoder( surface.release() } catch (_: Exception) {} } + + /** + * 计算最近N帧的平均解码帧率(fps) + */ + private fun calculateDecodeFps(): Double { + decodeTimestampLock.withLock { + if (decodeTimestampQueue.size < 2) return renderFps.toDouble() + val first = decodeTimestampQueue.first() + val last = decodeTimestampQueue.last() + val frameCount = decodeTimestampQueue.size - 1 + val durationMs = (last - first).coerceAtLeast(1L) + return frameCount * 1000.0 / durationMs + } + } + + /** + * EMA平滑更新渲染帧率 + * @param measuredFps 当前测得的解码帧率 + * @return 平滑后的渲染帧率(取整) + */ + private fun updateSmoothedFps(measuredFps: Double): Int { + // measuredFps边界保护 + val safeFps = measuredFps.coerceIn(minFps.toDouble(), maxFps.toDouble()) + val targetFps = alpha * safeFps + (1 - alpha) * smoothedFps + val delta = targetFps - smoothedFps + val step = delta.coerceIn(-maxStep, maxStep) + smoothedFps = (smoothedFps + step).coerceIn(minFps.toDouble(), maxFps.toDouble()) + return smoothedFps.toInt() + } // endregion } \ No newline at end of file