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.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_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> </manifest>

View File

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

View File

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx4G org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=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