feat:增加自适应帧率调整
This commit is contained in:
parent
8ed55f1bb3
commit
5e13dbf4f6
15
README.md
15
README.md
@ -173,21 +173,6 @@ await VideoDecodePlugin.decodeFrame(pFrameData, FrameType.pFrame);
|
||||
- 实时视频流解码
|
||||
- 基础错误处理
|
||||
|
||||
### 现有问题
|
||||
- 解码后在flutter渲染实际帧数较低
|
||||
- 方案一:使用原生activity渲染视频数据
|
||||
- 方案二:多个TextureView并行交替渲染
|
||||
- 方案三:待补充
|
||||
- 方案四:待补充
|
||||
|
||||
|
||||
### 后续计划
|
||||
- [ ] 解码帧数较低
|
||||
- [ ] 增加更多错误处理
|
||||
- [ ] 完善文档和示例
|
||||
- [ ] 添加单元测试
|
||||
- [ ] 支持更多编解码格式
|
||||
- [ ] 优化内存管理
|
||||
|
||||
## 插件架构与职责
|
||||
|
||||
|
||||
@ -79,6 +79,17 @@ class VideoDecoder(
|
||||
// 兜底:记录最近一次I帧的frameSeq,P/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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user