feat:完成20FPS渲染需求

This commit is contained in:
liyi 2025-04-30 10:24:42 +08:00
parent d837a1206b
commit 369d35cd2e
3 changed files with 100 additions and 10 deletions

View File

@ -10,4 +10,10 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<activity
android:name="top.skychip.video_decode_plugin.NativeVideoPlayerActivity"
android:exported="false"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"/>
</application>
</manifest>

View File

@ -17,6 +17,7 @@ import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import android.os.SystemClock
/**
* 视频解码器核心类
@ -41,6 +42,11 @@ import java.util.concurrent.ConcurrentHashMap
* - inputFrameQueue: 输入帧队列支持并发
* - running: 解码器运行状态
* - frameSeqSet: 用于去重的线程安全Set防止重复帧入队
* - outputFrameQueue: 解码输出缓冲区容量为10帧
* - renderThreadRunning: 渲染线程控制
* - renderThread: 渲染线程
* - mainHandler: 主线程Handler用于安全切换onFrameAvailable到主线程
* - renderFps: 渲染帧率fps可由外部控制默认15
*
* 主要方法
* - decodeFrame: 向解码器输入一帧数据
@ -57,7 +63,7 @@ class VideoDecoder(
companion object {
private const val TAG = "VideoDecoder"
private const val TIMEOUT_US = 10000L
private const val INPUT_BUFFER_QUEUE_CAPACITY = 20
private const val INPUT_BUFFER_QUEUE_CAPACITY = 50
}
private val surfaceTexture: SurfaceTexture = textureEntry.surfaceTexture()
@ -67,6 +73,25 @@ class VideoDecoder(
private var running = true
private val frameSeqSet = Collections.newSetFromMap(ConcurrentHashMap<Int, Boolean>())
// 解码输出缓冲区容量为10帧
private val outputFrameQueue = LinkedBlockingQueue<DecodedFrame>(50)
// 渲染线程控制
@Volatile private var renderThreadRunning = true
private var renderThread: Thread? = null
// 主线程Handler用于安全切换onFrameAvailable到主线程
private val mainHandler = Handler(Looper.getMainLooper())
// 首帧原始时间戳(微秒),用于归零
private var firstTimestampUs: Long? = null
// 上一帧归一化时间戳(微秒),用于误差兼容
private var lastTimestampUs: Long = 0L
// 容忍误差区间15fps一帧时长单位微秒
private val toleranceUs = 66000L
// 渲染帧率fps可由外部控制默认15
@Volatile var renderFps: Int = 20
private data class FrameData(
val data: ByteArray,
val frameType: Int,
@ -75,6 +100,14 @@ class VideoDecoder(
val refIFrameSeq: Int?
)
// 解码后帧结构,显式携带时间戳(单位:微秒)
private data class DecodedFrame(
val codec: MediaCodec,
val bufferIndex: Int,
val info: MediaCodec.BufferInfo,
val timestampUs: Long // 帧时间戳,单位微秒
)
init {
surfaceTexture.setDefaultBufferSize(width, height)
val mime = when (codecType) {
@ -96,15 +129,16 @@ class VideoDecoder(
inputBuffer.clear()
inputBuffer.put(frame.data)
val start = System.nanoTime()
val ptsUs = frame.timestamp * 1000L
codec.queueInputBuffer(
index,
0,
frame.data.size,
0,
ptsUs,
if (frame.frameType == 0) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0
)
val end = System.nanoTime()
Log.d(TAG, "queueInputBuffer cost: ${end - start} ns, frameSeq=${frame.frameSeq}, type=${frame.frameType}")
Log.d(TAG, "queueInputBuffer cost: "+ (end - start) + " ns, frameSeq="+frame.frameSeq+", type="+frame.frameType+", ptsUs="+ptsUs)
} else {
codec.queueInputBuffer(index, 0, 0, 0, 0)
}
@ -114,11 +148,16 @@ class VideoDecoder(
}
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
if (!running) return
val start = System.nanoTime()
codec.releaseOutputBuffer(index, true)
val end = System.nanoTime()
Log.d(TAG, "releaseOutputBuffer cost: ${end - start} ns")
onFrameAvailable()
// 将解码后帧放入输出缓冲区,由渲染线程处理
val frame = DecodedFrame(codec, index, MediaCodec.BufferInfo().apply {
set(0, info.size, info.presentationTimeUs, info.flags)
}, info.presentationTimeUs)
if (!outputFrameQueue.offer(frame)) {
// 缓冲区满,丢弃最旧帧再插入
outputFrameQueue.poll()
outputFrameQueue.offer(frame)
Log.w(TAG, "outputFrameQueue full, drop oldest frame")
}
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
Log.e(TAG, "MediaCodec error", e)
@ -128,12 +167,50 @@ class VideoDecoder(
decoder.configure(format, surface, null, 0)
decoder.start()
mediaCodec = decoder
// 启动渲染线程
renderThreadRunning = true
renderThread = Thread {
while (renderThreadRunning) {
val frameIntervalMs = if (renderFps > 0) 1000L / renderFps else 66L
val loopStart = SystemClock.elapsedRealtime()
try {
val frame = outputFrameQueue.poll()
if (frame != null) {
Log.i(TAG, "[RenderThread] 定时渲染帧: frame.timestampUs=${frame.timestampUs}")
val start = System.nanoTime()
frame.codec.releaseOutputBuffer(frame.bufferIndex, true)
val end = System.nanoTime()
Log.d(TAG, "[RenderThread] releaseOutputBuffer cost: "+ (end - start) + " ns, frame.timestampUs=${frame.timestampUs}")
// 确保onFrameAvailable在主线程执行避免FlutterJNI线程异常
mainHandler.post { onFrameAvailable() }
} else {
Log.d(TAG, "[RenderThread] 定时渲染无帧可用")
}
} 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) {}
}
}
// 清理剩余帧
while (true) {
val frame = outputFrameQueue.poll() ?: break
try {
frame.codec.releaseOutputBuffer(frame.bufferIndex, false)
} catch (_: Exception) {}
}
}
renderThread?.start()
}
fun decodeFrame(
frameData: ByteArray,
frameType: Int,
timestamp: Long,
timestamp: Long, // 单位:毫秒,要求外部递增
frameSeq: Int,
refIFrameSeq: Int?
): Boolean {
@ -141,12 +218,19 @@ class VideoDecoder(
if (!frameSeqSet.add(frameSeq)) {
return false
}
// 直接使用外部传入的递增时间戳,无需归零和对齐
return inputFrameQueue.offer(FrameData(frameData, frameType, timestamp, frameSeq, refIFrameSeq), 50, TimeUnit.MILLISECONDS)
}
fun release() {
running = false
inputFrameQueue.clear()
// 停止渲染线程
renderThreadRunning = false
try {
renderThread?.join(200)
} catch (_: Exception) {}
outputFrameQueue.clear()
try {
mediaCodec?.stop()
mediaCodec?.release()

View File

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
#org.gradle.java.home=C:/Users/liyi/other/jdk-17.0.1
org.gradle.java.home=C:/Users/liyi/other/jdk-17.0.1