feat:完成20FPS渲染需求
This commit is contained in:
parent
d837a1206b
commit
369d35cd2e
@ -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>
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user