Merge branch 'main_v2' into 'main'

feat:v2版本优化

See merge request liyi/video_decode_plugin!2
This commit is contained in:
李仪 2025-06-18 03:24:32 +00:00
commit 1015321979
2 changed files with 87 additions and 76 deletions

View File

@ -19,6 +19,7 @@ import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import android.os.SystemClock
import java.util.ArrayDeque
import java.util.concurrent.Executors
/**
* 视频解码器核心类
@ -43,12 +44,12 @@ class VideoDecoder(
width: Int,
height: Int,
codecType: String,
private val onFrameRendered: () -> Unit
private val onFrameRendered: () -> Unit,
) {
companion object {
private const val TAG = "VideoDecoder"
private const val TIMEOUT_US = 10000L
private const val INPUT_BUFFER_QUEUE_CAPACITY = 15 // 输入缓冲区容量
private const val INPUT_BUFFER_QUEUE_CAPACITY = 50 // 输入缓冲区容量
}
// region 成员变量定义
@ -64,37 +65,49 @@ class VideoDecoder(
private val frameSeqSet = Collections.newSetFromMap(ConcurrentHashMap<Int, Boolean>()) // 防止重复帧入队
// 解码输出缓冲区容量为100帧
private val outputFrameQueue = LinkedBlockingQueue<DecodedFrame>(15)
private val outputFrameQueue = LinkedBlockingQueue<DecodedFrame>(50)
// 渲染线程控制
@Volatile private var renderThreadRunning = true
private var renderThread: Thread? = null
// 定时渲染调度器
private var scheduler = Executors.newSingleThreadScheduledExecutor()
// 主线程Handler用于安全切换onFrameRendered到主线程
private val mainHandler = Handler(Looper.getMainLooper())
// 渲染帧率fps可由外部控制默认18
@Volatile var renderFps: Int = 15
@Volatile
var renderFps: Int = 20
// 兜底记录最近一次I帧的frameSeqP/B帧依赖校验
@Volatile private var lastIFrameSeq: Int? = null
@Volatile
private var lastIFrameSeq: Int? = null
// 解码输出帧时间戳队列(用于动态帧率统计和平滑)
private val decodeTimestampQueue = ArrayDeque<Long>(20) // 最多保存20帧时间戳
private val decodeTimestampLock = ReentrantLock() // 线程安全保护
// EMA平滑参数
@Volatile private var smoothedFps: Double = 15.0 // 平滑后的渲染帧率
@Volatile
private var smoothedFps: Double = 25.0 // 平滑后的渲染帧率
private val alpha = 0.2 // EMA平滑系数越大响应越快
private val minFps = 8 // 渲染帧率下限,防止过低
private val maxFps = 30 // 渲染帧率上限,防止过高
private val maxStep = 2.0 // 单次最大调整幅度,防止突变
// 1. 新增成员变量
@Volatile private var latestRenderedTimestampMs: Long? = null
private val MAX_ALLOWED_DELAY_MS = 350 // 最大允许延迟,单位毫秒
@Volatile private var timestampBaseMs: Long? = null
@Volatile private var firstFrameRelativeTimestamp: Long? = null
@Volatile
private var latestRenderedTimestampMs: Long? = null
private val MAX_ALLOWED_DELAY_MS = 550 // 最大允许延迟,单位毫秒
@Volatile
private var timestampBaseMs: Long? = null
@Volatile
private var firstFrameRelativeTimestamp: Long? = null
// 帧重排序缓冲区及I帧记录
private val reorderBuffer = mutableMapOf<Int, FrameData>() // key: frameSeq
private val receivedIFrames = mutableSetOf<Int>() // 已收到的I帧frameSeq
private val reorderLock = ReentrantLock() // 线程安全
private val MAX_REORDER_BUFFER_SIZE = 50
// 输入帧结构体
private data class FrameData(
@ -102,7 +115,7 @@ class VideoDecoder(
val frameType: Int,
val timestamp: Long,
val frameSeq: Int,
val refIFrameSeq: Int?
val refIFrameSeq: Int?,
)
// 解码后帧结构体,显式携带时间戳(单位:微秒)
@ -110,7 +123,7 @@ class VideoDecoder(
val codec: MediaCodec,
val bufferIndex: Int,
val info: MediaCodec.BufferInfo,
val timestampUs: Long // 帧时间戳,单位微秒
val timestampUs: Long, // 帧时间戳,单位微秒
)
// endregion
@ -128,6 +141,9 @@ 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);
// 创建解码器
val decoder = MediaCodec.createDecoderByType(mime)
// 设置解码回调
@ -198,63 +214,36 @@ class VideoDecoder(
decoder.start()
mediaCodec = decoder
// 启动渲染线程
renderThreadRunning = true
renderThread = Thread {
var hasNotifiedFlutter = false
var renderedFrameCount = 0 // 渲染帧计数器
val fpsAdjustInterval = 10 // 每渲染10帧调整一次帧率
while (renderThreadRunning) {
// 计算每帧渲染间隔
val frameIntervalMs = if (renderFps > 0) 1000L / renderFps else 66L
val loopStart = SystemClock.elapsedRealtime()
try {
// 阻塞式等待新帧,避免空转
val frame = outputFrameQueue.take()
// 启动定时渲染任务,实现完全线性调度
// 说明本方案通过ScheduledExecutorService定时驱动渲染每帧间隔严格等距不依赖阻塞或sleep
var hasNotifiedFlutter = false
var renderedFrameCount = 0 // 渲染帧计数器
val renderTask = Runnable {
try {
val frame = outputFrameQueue.poll()
if (frame != null) {
frame.codec.releaseOutputBuffer(frame.bufferIndex, true)
// 7. 渲染线程用系统时间推进
latestRenderedTimestampMs = System.currentTimeMillis()
renderedFrameCount++
// 只在首次渲染时回调Flutter
if (!hasNotifiedFlutter) {
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)
}
// 控制渲染节奏
val loopCost = SystemClock.elapsedRealtime() - loopStart
val sleepMs = frameIntervalMs - loopCost
if (sleepMs > 0) {
try { Thread.sleep(sleepMs) } catch (_: Exception) {}
} else {
// 若解码极慢sleepMs为负直接进入下一帧防止阻塞
}
}
// 清理剩余帧,防止内存泄漏
while (true) {
val frame = outputFrameQueue.poll() ?: break
try {
frame.codec.releaseOutputBuffer(frame.bufferIndex, false)
} catch (_: Exception) {}
// 若outputFrameQueue为空跳过本次渲染实现线性调度
} catch (e: Exception) {
Log.e(TAG, "[RenderTask] Exception", e)
}
}
renderThread?.start()
// 固定20fps渲染50ms间隔
scheduler.scheduleAtFixedRate(renderTask, 0, 50, java.util.concurrent.TimeUnit.MILLISECONDS)
}
// endregion
// region 核心方法
/**
* 向解码器输入一帧数据所有类型均允许入队去重
* 向解码器输入一帧数据支持I/P帧乱序P帧依赖未满足时暂存
*/
fun decodeFrame(
frameData: ByteArray,
@ -280,30 +269,49 @@ class VideoDecoder(
val absTimestamp = base + (timestamp - firstRel)
// 3. decodeFrame延迟丢弃判断用系统时间
val now = System.currentTimeMillis()
val diff = now - absTimestamp
// Log.d(TAG, "[decodeFrame] absTimestamp=$absTimestamp, now=$now, now-absTimestamp=$diff, maxDelay=$MAX_ALLOWED_DELAY_MS")
if (absTimestamp < now - MAX_ALLOWED_DELAY_MS) {
Log.w(TAG, "[decodeFrame] Drop frame due to delay: absFrameTs=$absTimestamp, now=$now, maxDelay=$MAX_ALLOWED_DELAY_MS")
return false
}
var allow = false
if (frameType == 0) { // I帧
lastIFrameSeq = frameSeq
allow = true
} else {
val lastI = lastIFrameSeq
if (lastI == null) {
Log.w(TAG, "[decodeFrame] Drop P/B frame: no I-frame yet, frameSeq=$frameSeq, refIFrameSeq=$refIFrameSeq")
return false
}
allow = (refIFrameSeq == lastI)
if (!allow) {
Log.w(TAG, "[decodeFrame] Drop P/B frame: refIFrameSeq=$refIFrameSeq != lastIFrameSeq=$lastI, frameSeq=$frameSeq")
return false
// ===== 帧重排序缓冲区机制 =====
reorderLock.withLock {
if (frameType == 0) { // I帧
receivedIFrames.add(frameSeq)
lastIFrameSeq = frameSeq
// I帧直接入解码队列
inputFrameQueue.offer(FrameData(frameData, frameType, timestamp, frameSeq, refIFrameSeq), 50, TimeUnit.MILLISECONDS)
// 检查缓冲区入队所有依赖于该I帧的P帧
val readyPFrames = reorderBuffer.values.filter { it.refIFrameSeq == frameSeq }
.sortedBy { it.frameSeq }
for (pFrame in readyPFrames) {
inputFrameQueue.offer(pFrame, 50, TimeUnit.MILLISECONDS)
reorderBuffer.remove(pFrame.frameSeq)
}
// 清理过期P帧如缓冲区过大
if (reorderBuffer.size > MAX_REORDER_BUFFER_SIZE) {
val toRemove = reorderBuffer.keys.sorted().take(reorderBuffer.size - MAX_REORDER_BUFFER_SIZE)
toRemove.forEach { reorderBuffer.remove(it) }
}
return true
} else { // P帧
val lastI = lastIFrameSeq
// 只有依赖的I帧已收到才允许入队否则暂存
if (refIFrameSeq != null && receivedIFrames.contains(refIFrameSeq)) {
inputFrameQueue.offer(FrameData(frameData, frameType, timestamp, frameSeq, refIFrameSeq), 50, TimeUnit.MILLISECONDS)
return true
} else {
// 暂存到重排序缓冲区
reorderBuffer[frameSeq] = FrameData(frameData, frameType, timestamp, frameSeq, refIFrameSeq)
// 控制缓冲区大小
if (reorderBuffer.size > MAX_REORDER_BUFFER_SIZE) {
val toRemove = reorderBuffer.keys.sorted().take(reorderBuffer.size - MAX_REORDER_BUFFER_SIZE)
toRemove.forEach { reorderBuffer.remove(it) }
}
Log.w(TAG, "[decodeFrame] P-frame cached: frameSeq=$frameSeq, refIFrameSeq=$refIFrameSeq, waiting for I-frame.")
return false
}
}
}
// 4. 入队时FrameData仍用原始相对时间戳
return inputFrameQueue.offer(FrameData(frameData, frameType, timestamp, frameSeq, refIFrameSeq), 50, TimeUnit.MILLISECONDS)
}
/**
@ -312,10 +320,9 @@ class VideoDecoder(
fun release() {
running = false
inputFrameQueue.clear()
// 停止渲染线程
renderThreadRunning = false
// 停止定时渲染任务
try {
renderThread?.join(200)
scheduler.shutdownNow()
} catch (_: Exception) {}
outputFrameQueue.clear()
try {

View File

@ -180,6 +180,8 @@ class VideoDecodePlugin {
frameSeq: frameSeq - 1,
refIFrameSeq: frameSeq - 1,
);
// i帧
frameData = NaluUtils.filterNalusByType(frameData, 5);
await _decodeFrame(
frameData: Uint8List.fromList(frameData),
frameType: 0,
@ -223,6 +225,8 @@ class VideoDecodePlugin {
frameSeq: frameSeq - 1,
refIFrameSeq: frameSeq - 1,
);
// i帧
frameData = NaluUtils.filterNalusByType(frameData, 5);
await _decodeFrame(
frameData: Uint8List.fromList(frameData),
frameType: 0,