feat:增加自适应帧率调整

This commit is contained in:
liyi 2025-05-06 09:27:46 +08:00
parent 8ed55f1bb3
commit 5e13dbf4f6
2 changed files with 58 additions and 15 deletions

View File

@ -173,21 +173,6 @@ await VideoDecodePlugin.decodeFrame(pFrameData, FrameType.pFrame);
- 实时视频流解码
- 基础错误处理
### 现有问题
- 解码后在flutter渲染实际帧数较低
- 方案一使用原生activity渲染视频数据
- 方案二多个TextureView并行交替渲染
- 方案三:待补充
- 方案四:待补充
### 后续计划
- [ ] 解码帧数较低
- [ ] 增加更多错误处理
- [ ] 完善文档和示例
- [ ] 添加单元测试
- [ ] 支持更多编解码格式
- [ ] 优化内存管理
## 插件架构与职责

View File

@ -79,6 +79,17 @@ class VideoDecoder(
// 兜底记录最近一次I帧的frameSeqP/B帧依赖校验
@Volatile private var lastIFrameSeq: Int? = null
// 解码输出帧时间戳队列(用于动态帧率统计和平滑)
private val decodeTimestampQueue = ArrayDeque<Long>(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
}