diff --git a/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecodePlugin.kt b/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecodePlugin.kt index db95047..3b3ac1f 100644 --- a/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecodePlugin.kt +++ b/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecodePlugin.kt @@ -27,46 +27,11 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { // 纹理注册表 private lateinit var textureRegistry: TextureRegistry - // 解码器映射表 (纹理ID -> 解码器) - private val decoders = ConcurrentHashMap() + // 解码器 + private var decoder: VideoDecoder? = null - // 已释放的纹理ID集合,用于跟踪防止重用 - private val releasedTextureIds = HashSet() - - // 主线程Handler - private val mainHandler = Handler(Looper.getMainLooper()) - - // 是否是调试模式 - private var isDebugMode = false - - /** - * 输出调试日志 - 仅在调试模式下输出 - */ - private fun logDebug(message: String) { - if (isDebugMode) { - Log.d(TAG, message) - } - } - - /** - * 输出警告日志 - 仅在调试模式下输出 - */ - private fun logWarning(message: String) { - if (isDebugMode) { - Log.w(TAG, message) - } - } - - /** - * 输出错误日志 - 始终输出 - */ - private fun logError(message: String, e: Exception? = null) { - if (e != null) { - Log.e(TAG, message, e) - } else { - Log.e(TAG, message) - } - } + // 纹理ID + private var textureId: Long? = null /** * 插件绑定到Flutter引擎时调用 @@ -85,143 +50,30 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { * 处理Flutter方法调用 */ override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - try { - when (call.method) { - "getPlatformVersion" -> { - handleGetPlatformVersion(result) - } - "initDecoder" -> { - handleInitDecoder(call, result) - } - "decodeFrame" -> { - handleDecodeFrame(call, result) - } - "releaseDecoder" -> { - handleReleaseDecoder(call, result) - } - "getDecoderStats" -> { - handleGetDecoderStats(call, result) - } - else -> { - result.notImplemented() - } - } - } catch (e: Exception) { - logError("处理方法调用失败", e) - result.error("NATIVE_ERROR", "处理方法调用失败: ${e.message}", null) + when (call.method) { + "initDecoder" -> handleInitDecoder(call, result) + "decodeFrame" -> handleDecodeFrame(call, result) + "releaseDecoder" -> handleReleaseDecoder(call, result) + else -> result.notImplemented() } } - /** - * 获取平台版本 - */ - private fun handleGetPlatformVersion(result: Result) { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } - /** * 初始化解码器 */ private fun handleInitDecoder(call: MethodCall, result: Result) { try { - // 读取参数 val width = call.argument("width") ?: 640 val height = call.argument("height") ?: 360 - val frameRate = call.argument("frameRate") val codecType = call.argument("codecType") ?: "h264" - val isDebug = call.argument("isDebug") ?: false - - // 更新插件的调试模式标志 - this.isDebugMode = isDebug - - // 创建纹理 val textureEntry = textureRegistry.createSurfaceTexture() - val textureId = textureEntry.id() - - // 检查这个纹理ID是否已经被使用过 - if (releasedTextureIds.contains(textureId)) { - // 如果已经被使用过,说明Flutter引擎在重用纹理ID,这可能导致问题 - logWarning("警告: 纹理ID $textureId 已被使用过,这可能导致问题") - - // 记录这个纹理ID现在是活跃的 - releasedTextureIds.remove(textureId) + textureId = textureEntry.id() + decoder = VideoDecoder(context, textureEntry, width, height, codecType) { + // onFrameAvailable callback + channel.invokeMethod("onFrameAvailable", mapOf("textureId" to textureId)) } - - // 创建解码器配置 - val config = VideoDecoderConfig( - width = width, - height = height, - codecType = codecType, - frameRate = frameRate, - isDebug = isDebug, - isAsync = call.argument("isAsync") ?: true - ) - - // 创建解码器 - val decoder = VideoDecoder(context, textureEntry, config) - - // 设置回调 - decoder.callback = object : VideoDecoder.DecoderCallback { - override fun onFrameAvailable() { - // 通知Flutter刷新纹理 - runOnMainThread { - try { - // 根据当前帧数判断是否是预通知 - val decoder = decoders[textureId] - val stats = decoder?.getStatistics() ?: mapOf() - val renderedFrames = stats["renderedFrames"] as? Int ?: 0 - - if (renderedFrames == 0) { - // 这是初始化预通知 - logDebug("[预通知] 发送初始帧可用通知给Flutter,纹理ID: $textureId(无实际视频数据)") - - // 发送帧可用通知(带预通知标志) - channel.invokeMethod("onFrameAvailable", mapOf( - "textureId" to textureId, - "isPrenotification" to true - )) - - // 发送解码器状态通知(准备就绪) - sendDecoderState(textureId, "ready", stats) - } else { - // 这是实际帧通知 - logDebug("发送帧可用通知给Flutter,纹理ID: $textureId,已渲染帧数: $renderedFrames") - - // 发送帧可用通知(实际帧) - channel.invokeMethod("onFrameAvailable", mapOf( - "textureId" to textureId, - "isPrenotification" to false - )) - - // 发送解码器状态通知(渲染中) - sendDecoderState(textureId, "rendering", stats) - } - } catch (e: Exception) { - logError("通知Flutter更新纹理失败", e) - } - } - } - - override fun onError(error: String) { - logError("解码器错误: $error") - - // 发送错误状态通知 - val stats = decoders[textureId]?.getStatistics() ?: mapOf() - sendDecoderState(textureId, "error", stats + mapOf("errorMessage" to error)) - } - } - - // 保存解码器 - decoders[textureId] = decoder - - // 发送初始化状态 - sendDecoderState(textureId, "initializing", decoder.getStatistics()) - - // 返回纹理ID result.success(textureId) - } catch (e: Exception) { - logError("初始化解码器失败", e) result.error("INIT_FAILED", "初始化解码器失败: ${e.message}", null) } } @@ -231,38 +83,14 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { */ private fun handleDecodeFrame(call: MethodCall, result: Result) { try { - // 读取参数 - val textureId = call.argument("textureId")?.toLong() ?: - return result.error("INVALID_ARGS", "无效的纹理ID", null) - - val frameData = call.argument("frameData") ?: - return result.error("INVALID_ARGS", "无效的帧数据", null) - + val frameData = call.argument("frameData") ?: return result.error("INVALID_ARGS", "无效的帧数据", null) val frameType = call.argument("frameType") ?: 0 - val isIFrame = frameType == 0 // 0表示I帧,1表示P帧 - - // 获取解码器 - val decoder = decoders[textureId] ?: - return result.error("DECODER_NOT_FOUND", "找不到纹理ID对应的解码器", null) - - // 解码帧 - val success = decoder.decodeFrame(frameData, isIFrame) - - // 发送更新后的解码器状态(在帧解码后,无论成功与否) - val stats = decoder.getStatistics() - - // 根据是否有渲染帧确定状态 - val renderedFrames = stats["renderedFrames"] as? Int ?: 0 - val state = if (renderedFrames > 0) "rendering" else "ready" - - // 发送状态更新 - sendDecoderState(textureId, state, stats) - - // 返回结果 + val timestamp = call.argument("timestamp") ?: 0L + val frameSeq = call.argument("frameSeq") ?: 0 + val refIFrameSeq = call.argument("refIFrameSeq") + val success = decoder?.decodeFrame(frameData, frameType, timestamp, frameSeq, refIFrameSeq) ?: false result.success(success) - } catch (e: Exception) { - logError("解码帧失败", e) result.error("DECODE_FAILED", "解码帧失败: ${e.message}", null) } } @@ -272,123 +100,22 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { */ private fun handleReleaseDecoder(call: MethodCall, result: Result) { try { - // 读取参数 - val textureId = call.argument("textureId")?.toLong() ?: - return result.error("INVALID_ARGS", "无效的纹理ID", null) - - // 获取解码器 - val decoder = decoders[textureId] - if (decoder == null) { - // 如果找不到解码器,可能已经释放,直接返回成功 - result.success(true) - return - } - - // 发送释放状态 - sendDecoderState(textureId, "released", decoder.getStatistics()) - - // 释放解码器 - decoder.release() - - // 从映射中移除 - decoders.remove(textureId) - - // 记录已释放的纹理ID,以便检测重用 - releasedTextureIds.add(textureId) - - // 返回成功 + decoder?.release() + decoder = null + textureId = null result.success(true) - } catch (e: Exception) { - logError("释放解码器失败", e) result.error("RELEASE_FAILED", "释放解码器失败: ${e.message}", null) } } - - /** - * 获取解码器统计信息 - */ - private fun handleGetDecoderStats(call: MethodCall, result: Result) { - try { - // 获取纹理ID - val textureId = call.argument("textureId")?.toLong() ?: - return result.error("INVALID_ARGS", "无效的纹理ID", null) - - // 获取解码器 - val decoder = decoders[textureId] ?: - return result.error("DECODER_NOT_FOUND", "找不到纹理ID对应的解码器", null) - - // 获取统计信息 - val stats = decoder.getStatistics() - - // 添加插件级别的信息 - val enhancedStats = HashMap(stats) - enhancedStats["decoderCount"] = decoders.size - enhancedStats["textureId"] = textureId - - // 返回统计信息 - result.success(enhancedStats) - - } catch (e: Exception) { - logError("获取解码器统计信息失败", e) - result.error("STATS_FAILED", "获取解码器统计信息失败: ${e.message}", null) - } - } - - /** - * 发送解码器状态更新 - */ - private fun sendDecoderState(textureId: Long, state: String, stats: Map) { - runOnMainThread { - try { - logDebug("发送解码器状态更新: 纹理ID=$textureId, 状态=$state") - - // 构造参数 - val params = HashMap() - params["textureId"] = textureId - params["state"] = state - params["stats"] = stats - - // 发送状态更新 - channel.invokeMethod("onDecoderState", params) - } catch (e: Exception) { - logError("发送解码器状态更新失败", e) - } - } - } - - /** - * 在主线程上执行任务 - */ - private fun runOnMainThread(task: () -> Unit) { - if (Looper.myLooper() == Looper.getMainLooper()) { - task() - } else { - mainHandler.post(task) - } - } /** * 插件从Flutter引擎解绑时调用 */ override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - // 释放所有解码器 - for ((textureId, decoder) in decoders) { - try { - // 发送释放状态 - sendDecoderState(textureId, "released", decoder.getStatistics()) - - // 释放解码器 - decoder.release() - } catch (e: Exception) { - logError("插件分离时释放解码器失败", e) - } - } - - // 清除映射 - decoders.clear() - - // 移除方法调用处理器 + decoder?.release() + decoder = null + textureId = null channel.setMethodCallHandler(null) } } \ No newline at end of file diff --git a/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt b/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt index 86a2d31..982c0ad 100644 --- a/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt +++ b/android/src/main/kotlin/top/skychip/video_decode_plugin/VideoDecoder.kt @@ -15,955 +15,144 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock +import java.util.Collections +import java.util.concurrent.ConcurrentHashMap /** - * 视频解码器 - * 负责解码H264/H265视频数据并将其渲染到Surface上 - * 支持同步和异步两种工作模式 + * 视频解码器核心类。 + * 主要职责: + * 1. 初始化并配置Android MediaCodec解码器,支持H264/H265视频流解码。 + * 2. 管理解码输入帧队列,将解码后的视频帧渲染到Surface。 + * 3. 支持同步与异步解码模式,自动处理输入/输出缓冲区。 + * 4. 负责解码器的生命周期管理(启动、释放等)。 + * 5. 通过回调通知Flutter端有新帧可用。 + * + * 构造参数说明: + * - context: Android上下文 + * - textureEntry: Flutter纹理注册表的SurfaceTextureEntry,用于视频渲染 + * - width: 视频宽度 + * - height: 视频高度 + * - codecType: 编解码器类型("h264"或"h265") + * - onFrameAvailable: 新帧渲染回调 + * + * 主要成员变量: + * - surfaceTexture/surface: 视频渲染目标 + * - mediaCodec: Android MediaCodec解码器实例 + * - inputFrameQueue: 输入帧队列,支持并发 + * - running: 解码器运行状态 + * - frameSeqSet: 用于去重的线程安全Set,防止重复帧入队 + * + * 主要方法: + * - decodeFrame: 向解码器输入一帧数据 + * - release: 释放解码器和相关资源 */ class VideoDecoder( - private val context: Context, - private val textureEntry: TextureRegistry.SurfaceTextureEntry, - private val config: VideoDecoderConfig + context: Context, + textureEntry: TextureRegistry.SurfaceTextureEntry, + width: Int, + height: Int, + codecType: String, + private val onFrameAvailable: () -> Unit ) { companion object { private const val TAG = "VideoDecoder" - private const val TIMEOUT_US = 10000L // 使用更短的超时时间(10毫秒) - - // Mime types - private const val MIME_H264 = "video/avc" - private const val MIME_H265 = "video/hevc" - - // H264相关常量 - private const val NAL_UNIT_TYPE_SPS = 7 - private const val NAL_UNIT_TYPE_PPS = 8 - private const val NAL_UNIT_TYPE_IDR = 5 - private const val NAL_UNIT_TYPE_NON_IDR = 1 // P帧 - - // 队列相关常量 + private const val TIMEOUT_US = 10000L private const val INPUT_BUFFER_QUEUE_CAPACITY = 20 } - // 回调接口 - interface DecoderCallback { - fun onFrameAvailable() - fun onError(error: String) - } - - // 回调实例 - var callback: DecoderCallback? = null - - // SurfaceTexture 和 Surface 用于显示解码后的帧 private val surfaceTexture: SurfaceTexture = textureEntry.surfaceTexture() private val surface: Surface = Surface(surfaceTexture) - - // MediaCodec 解码器 private var mediaCodec: MediaCodec? = null - - // 解码状态 - private val isRunning = AtomicBoolean(false) - private val isDecoderConfigured = AtomicBoolean(false) - - // 帧计数 - private var frameCount = 0 - private var renderedFrameCount = 0 - private var droppedFrameCount = 0 - - // 跟踪I帧状态 - 使用AtomicBoolean防止并发问题 - private val hasSentSPS = AtomicBoolean(false) - private val hasSentPPS = AtomicBoolean(false) - private val hasSentIDR = AtomicBoolean(false) - - // 跟踪上一个关键帧时间 - private var lastIFrameTimeMs = 0L - - // 连续P帧计数 - private var consecutivePFrameCount = 0 - - // FPS统计相关变量 - private var fpsCalculationStartTime = 0L - private var framesForFps = 0 - private var currentFps = 0f - private val FPS_UPDATE_INTERVAL_MS = 1000 // 每秒更新一次FPS - - // 动态参数调整相关变量 - private var detectedGopSize = 0 - private var lastDetectedIFrameTime = 0L - private var iFrameIntervals = mutableListOf() - private val GOP_HISTORY_SIZE = 5 // 记录最近5个GOP间隔 - - // 用于避免重复处理相同SPS/PPS的缓存 - private var lastSPSHash: Int? = null - private var lastPPSHash: Int? = null - - // 最后有效输出时间戳,用于检测解码器卡住的情况 - private var lastOutputTimeMs = 0L - - // 主线程Handler,用于在主线程上更新纹理 - private val mainHandler = Handler(Looper.getMainLooper()) - - // 是否是调试模式 - private val isDebugMode: Boolean = config.isDebug - - // 是否是异步模式 - private val isAsyncMode: Boolean = config.isAsync - - // 异步模式下的帧缓冲队列 private val inputFrameQueue = LinkedBlockingQueue(INPUT_BUFFER_QUEUE_CAPACITY) - - // 异步模式下的状态锁 - private val codecLock = ReentrantLock() + private var running = true + private val frameSeqSet = Collections.newSetFromMap(ConcurrentHashMap()) - // 异步模式下的状态监控 - private var frameQueueHighWatermark = 0 - private var inputProcessCount = 0 - private var outputProcessCount = 0 - private var decodingJitterMs = 0L // 解码抖动时间(ms) - private var keyFrameInterval = 0L // 关键帧间隔时间(ms) - private var lastDecodingErrorTime = 0L // 最后一次解码错误的时间 - private var decodingErrorCount = 0 // 解码错误计数 + private data class FrameData( + val data: ByteArray, + val frameType: Int, + val timestamp: Long, + val frameSeq: Int, + val refIFrameSeq: Int? + ) - // 异步模式下的帧数据类 - private data class FrameData(val data: ByteArray, val isIFrame: Boolean) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as FrameData - if (!data.contentEquals(other.data)) return false - if (isIFrame != other.isIFrame) return false - - return true + init { + surfaceTexture.setDefaultBufferSize(width, height) + val mime = when (codecType) { + "h264" -> "video/avc" + "h265" -> "video/hevc" + else -> "video/avc" } - - override fun hashCode(): Int { - var result = data.contentHashCode() - result = 31 * result + isIFrame.hashCode() - return result - } - } - - // 异步回调类 - private inner class MediaCodecCallback : MediaCodec.Callback() { - override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { - if (!isRunning.get()) return - - try { - inputProcessCount++ - - // 记录当前队列大小最高水位 - val currentQueueSize = inputFrameQueue.size - if (currentQueueSize > frameQueueHighWatermark) { - frameQueueHighWatermark = currentQueueSize - } - - // 尝试从队列获取帧数据(非阻塞) - val frameData = inputFrameQueue.poll() - if (frameData != null) { + val format = MediaFormat.createVideoFormat(mime, width, height) + format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width * height) + val decoder = MediaCodec.createDecoderByType(mime) + decoder.setCallback(object : MediaCodec.Callback() { + override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { + if (!running) return + val frame = inputFrameQueue.poll() + if (frame != null) { + frameSeqSet.remove(frame.frameSeq) val inputBuffer = codec.getInputBuffer(index) if (inputBuffer != null) { - // 处理帧数据 - processInputFrame(codec, index, inputBuffer, frameData.data, frameData.isIFrame) + inputBuffer.clear() + inputBuffer.put(frame.data) + val start = System.nanoTime() + codec.queueInputBuffer( + index, + 0, + frame.data.size, + 0, + 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}") } else { - logError("获取输入缓冲区失败") - // 将帧数据放回队列前端 - inputFrameQueue.offerFirst(frameData) + codec.queueInputBuffer(index, 0, 0, 0, 0) } } else { - // 没有可用的帧,释放输入缓冲区 codec.queueInputBuffer(index, 0, 0, 0, 0) } - } catch (e: Exception) { - logError("处理输入缓冲区时出错", e) - lastDecodingErrorTime = System.currentTimeMillis() - decodingErrorCount++ } - } - - override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) { - if (!isRunning.get()) return - - try { - outputProcessCount++ - - val render = info.size > 0 && (info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0 - - if (render) { - // 计算关键帧间隔和解码抖动 - val currentTime = System.currentTimeMillis() - - // 如果是关键帧,计算间隔 - if ((info.flags and MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { - if (lastIFrameTimeMs > 0) { - keyFrameInterval = currentTime - lastIFrameTimeMs - } - lastIFrameTimeMs = currentTime - } - - // 计算解码抖动(与预期帧间隔的差异) - if (config.frameRate != null && config.frameRate!! > 0) { - val expectedFrameInterval = 1000 / config.frameRate!!.toFloat() - - if (lastOutputTimeMs > 0) { - val actualInterval = currentTime - lastOutputTimeMs - decodingJitterMs = Math.abs(actualInterval - expectedFrameInterval).toLong() - } - } - - // 释放并渲染 - codec.releaseOutputBuffer(index, true) - renderedFrameCount++ - lastOutputTimeMs = currentTime - logDebug("成功渲染帧 #$renderedFrameCount") - - // 通知Flutter刷新纹理 - mainHandler.post { - notifyFrameAvailable() - } - } else { - // 释放但不渲染 - codec.releaseOutputBuffer(index, false) - } - - // 检查解码错误 - if ((info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - logWarning("收到结束流标志") - // 忽略错误,继续解码 - } - } catch (e: Exception) { - logError("处理输出缓冲区时出错", e) - lastDecodingErrorTime = System.currentTimeMillis() - decodingErrorCount++ + 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() } - } - - override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { - logError("MediaCodec错误", e) - lastDecodingErrorTime = System.currentTimeMillis() - decodingErrorCount++ - callback?.onError("MediaCodec错误: ${e.message}") - } - - override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) { - logDebug("输出格式变更: $format") - } - } - - /** - * 输出调试日志 - 仅在调试模式下输出 - */ - private fun logDebug(message: String) { - if (isDebugMode) { - Log.d(TAG, message) - } - } - - /** - * 输出警告日志 - 仅在调试模式下输出 - */ - private fun logWarning(message: String) { - if (isDebugMode) { - Log.w(TAG, message) - } - } - - /** - * 输出错误日志 - 始终输出 - */ - private fun logError(message: String, e: Exception? = null) { - if (e != null) { - Log.e(TAG, message, e) - } else { - Log.e(TAG, message) - } + override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { + Log.e(TAG, "MediaCodec error", e) + } + override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {} + }) + decoder.configure(format, surface, null, 0) + decoder.start() + mediaCodec = decoder } - // 解码器初始化 - init { - try { - // 设置SurfaceTexture的默认缓冲区大小 - surfaceTexture.setDefaultBufferSize(config.width, config.height) - logDebug("初始化解码器: ${config.width}x${config.height}, 编码: ${config.codecType}, 模式: ${if (isAsyncMode) "异步" else "同步"}") - - // 初始化解码器 - if (setupDecoder()) { - isRunning.set(true) - - // 如果是异步模式,预热解码器 - if (isAsyncMode) { - // 在后台线程中预热解码器 - Thread { - try { - warmUpDecoder() - } catch (e: Exception) { - logError("预热解码器出错", e) - } - }.start() - } - - // 通知初始帧可用(让Flutter创建Texture View) - logDebug("[预通知] 解码器初始化成功,发送初始帧通知(无实际视频数据)") - mainHandler.post { - notifyFrameAvailableInitial() - } - } else { - logError("解码器初始化失败") - callback?.onError("解码器初始化失败") - } - } catch (e: Exception) { - logError("创建解码器实例失败", e) - callback?.onError("创建解码器实例失败: ${e.message}") - release() - } - } - - /** - * 通知初始帧可用 - 仅在初始化时调用,表明解码器已准备好但尚无实际帧 - */ - private fun notifyFrameAvailableInitial() { - if (!isRunning.get()) { - logDebug("[预通知] 解码器已停止,跳过初始帧可用通知") - return - } - - try { - logDebug("[预通知] 发送初始帧可用通知,目的是让Flutter创建纹理视图") - callback?.onFrameAvailable() - } catch (e: Exception) { - logError("通知初始帧可用时出错: ${e.message}", e) - } - } - - /** - * 通知帧可用 - 在实际解码帧后调用 - */ - private fun notifyFrameAvailable() { - if (!isRunning.get()) { - logDebug("解码器已停止,跳过帧可用通知") - return - } - - // FPS计算 - val currentTime = System.currentTimeMillis() - if (fpsCalculationStartTime == 0L) { - fpsCalculationStartTime = currentTime - framesForFps = 0 - } - - framesForFps++ - - // 每秒更新一次FPS - if (currentTime - fpsCalculationStartTime >= FPS_UPDATE_INTERVAL_MS) { - val elapsedSeconds = (currentTime - fpsCalculationStartTime) / 1000f - currentFps = framesForFps / elapsedSeconds - logDebug("当前渲染FPS: $currentFps") - fpsCalculationStartTime = currentTime - framesForFps = 0 - } - - try { - logDebug("发送帧可用通知,当前渲染帧数: $renderedFrameCount") - callback?.onFrameAvailable() - } catch (e: Exception) { - logError("通知帧可用时出错: ${e.message}", e) - } - } - - /** - * 设置解码器 - */ - private fun setupDecoder(): Boolean { - try { - // 选择适合的MIME类型 - val mime = when (config.codecType) { - "h264" -> MIME_H264 - "h265" -> MIME_H265 - else -> MIME_H264 // 默认使用H.264 - } - - // 创建MediaFormat - val format = MediaFormat.createVideoFormat(mime, config.width, config.height) - - // 配置参数 - format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, config.width * config.height) - - // 如果指定了帧率,设置帧率 - config.frameRate?.let { fps -> - format.setInteger(MediaFormat.KEY_FRAME_RATE, fps) - } - - // 创建解码器实例 - val decoder = MediaCodec.createDecoderByType(mime) - - if (isAsyncMode) { - // 异步模式 - logDebug("使用异步模式配置解码器") - - // 设置异步回调 - decoder.setCallback(MediaCodecCallback()) - - // 配置解码器 - decoder.configure(format, surface, null, 0) - decoder.start() - } else { - // 同步模式 - logDebug("使用同步模式配置解码器") - - // 配置解码器 - decoder.configure(format, surface, null, 0) - decoder.start() - } - - // 保存解码器实例 - mediaCodec = decoder - isDecoderConfigured.set(true) - - logDebug("解码器设置完成: ${decoder.codecInfo.name}") - return true - } catch (e: Exception) { - logError("设置解码器失败", e) - isDecoderConfigured.set(false) - callback?.onError("设置解码器失败: ${e.message}") + fun decodeFrame( + frameData: ByteArray, + frameType: Int, + timestamp: Long, + frameSeq: Int, + refIFrameSeq: Int? + ): Boolean { + if (!running || mediaCodec == null) return false + if (!frameSeqSet.add(frameSeq)) { return false } + return inputFrameQueue.offer(FrameData(frameData, frameType, timestamp, frameSeq, refIFrameSeq), 50, TimeUnit.MILLISECONDS) } - /** - * 快速检查NAL类型 - */ - private fun checkNalType(frame: ByteArray): Int { - try { - // 快速检查常见的4字节起始码 - if (frame.size > 4 && frame[0] == 0.toByte() && frame[1] == 0.toByte() && - frame[2] == 0.toByte() && frame[3] == 1.toByte()) { - return frame[4].toInt() and 0x1F - } - - // 快速检查常见的3字节起始码 - if (frame.size > 3 && frame[0] == 0.toByte() && frame[1] == 0.toByte() && - frame[2] == 1.toByte()) { - return frame[3].toInt() and 0x1F - } - - // 尝试搜索起始码 - for (i in 0 until frame.size - 4) { - if (frame[i] == 0.toByte() && frame[i+1] == 0.toByte() && - frame[i+2] == 0.toByte() && frame[i+3] == 1.toByte()) { - return frame[i+4].toInt() and 0x1F - } - - if (i < frame.size - 3 && frame[i] == 0.toByte() && - frame[i+1] == 0.toByte() && frame[i+2] == 1.toByte()) { - return frame[i+3].toInt() and 0x1F - } - } - } catch (e: Exception) { - logError("检查NAL类型出错", e) - } - - // 无法识别,使用传入的参数 - return -1 - } - - /** - * 解码视频帧 - 支持同步和异步两种模式 - */ - fun decodeFrame(frameData: ByteArray, isIFrame: Boolean): Boolean { - if (!isRunning.get() || !isDecoderConfigured.get() || frameData.isEmpty()) { - logWarning("解码器未运行或未配置或帧数据为空") - return false - } - - val codec = mediaCodec ?: return false - - try { - if (isAsyncMode) { - // 检查NAL类型,以便优先处理关键帧 - val nalType = checkNalType(frameData) - // 判断是否为关键参数集或I帧 - val isKeyFrame = nalType == NAL_UNIT_TYPE_SPS || - nalType == NAL_UNIT_TYPE_PPS || - nalType == NAL_UNIT_TYPE_IDR || - isIFrame - - // 异步模式 - 将帧数据添加到队列 - if (isKeyFrame) { - // 对于关键帧,使用较长的超时时间,确保能加入队列 - if (!inputFrameQueue.offer(FrameData(frameData, isIFrame), 500, TimeUnit.MILLISECONDS)) { - // 队列已满,尝试清理后再添加 - logWarning("队列已满且尝试添加关键帧,尝试清理非关键帧后再添加") - try { - // 尝试移除一个非关键帧 - var removed = false - val iterator = inputFrameQueue.iterator() - while (iterator.hasNext()) { - val item = iterator.next() - if (!item.isIFrame) { - iterator.remove() - droppedFrameCount++ - removed = true - break - } - } - - if (removed) { - // 再次尝试添加 - return inputFrameQueue.offer(FrameData(frameData, isIFrame), 100, TimeUnit.MILLISECONDS) - } else { - logWarning("无法移除非关键帧,丢弃当前帧") - droppedFrameCount++ - return false - } - } catch (e: Exception) { - logError("清理队列失败", e) - droppedFrameCount++ - return false - } - } - return true - } else { - // 对于非关键帧,使用较短的超时时间 - if (!inputFrameQueue.offer(FrameData(frameData, isIFrame), 50, TimeUnit.MILLISECONDS)) { - // 队列已满,丢弃此帧 - logWarning("输入队列已满,丢弃非关键帧") - droppedFrameCount++ - return false - } - return true - } - } else { - // 同步模式 - 直接处理帧 - // 检查NAL类型 - val nalType = checkNalType(frameData) - - // 实际使用的NAL类型 - val effectiveType = if (nalType != -1) nalType else if (isIFrame) NAL_UNIT_TYPE_IDR else NAL_UNIT_TYPE_NON_IDR - - // 如果是SPS或PPS且在缓存中已有相同内容,跳过 - if (effectiveType == NAL_UNIT_TYPE_SPS) { - val hash = frameData.hashCode() - if (lastSPSHash == hash) return true - lastSPSHash = hash - hasSentSPS.set(true) - } else if (effectiveType == NAL_UNIT_TYPE_PPS) { - val hash = frameData.hashCode() - if (lastPPSHash == hash) return true - lastPPSHash = hash - hasSentPPS.set(true) - } else if (effectiveType == NAL_UNIT_TYPE_IDR) { - hasSentIDR.set(true) - val currentTime = System.currentTimeMillis() - lastDetectedIFrameTime = currentTime - lastIFrameTimeMs = currentTime - consecutivePFrameCount = 0 - } else { - // P帧处理 - if (!hasSentIDR.get() && renderedFrameCount == 0) { - logWarning("丢弃P帧,因为尚未收到I帧") - droppedFrameCount++ - return false - } - - consecutivePFrameCount++ - } - - // 记录帧信息 - frameCount++ - - // 解码帧 - val inputBufferIndex = codec.dequeueInputBuffer(TIMEOUT_US) - if (inputBufferIndex < 0) { - logWarning("无法获取输入缓冲区,可能需要等待") - return false - } - - // 获取输入缓冲区 - val inputBuffer = codec.getInputBuffer(inputBufferIndex) - if (inputBuffer == null) { - logError("获取输入缓冲区失败") - return false - } - - // 填充数据 - inputBuffer.clear() - inputBuffer.put(frameData) - - // 提交缓冲区 - val flags = if (isIFrame) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0 - codec.queueInputBuffer( - inputBufferIndex, - 0, - frameData.size, - System.nanoTime() / 1000L, - flags - ) - - // 处理输出 - processOutputBuffers() - - return true - } - } catch (e: Exception) { - logError("解码帧失败", e) - return false - } - } - - /** - * 处理所有可用的输出缓冲区 - */ - private fun processOutputBuffers() { - val codec = mediaCodec ?: return - val bufferInfo = MediaCodec.BufferInfo() - - var outputDone = false - var errorDetected = false - - while (!outputDone && !errorDetected) { - try { - val outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0) // 不等待,只检查当前可用的 - - when { - outputBufferIndex >= 0 -> { - val render = bufferInfo.size > 0 && (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0 - - // 检查解码错误 - if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - logWarning("收到结束流标志") - // 忽略错误,继续解码 - } - - if (render) { - // 释放并渲染 - codec.releaseOutputBuffer(outputBufferIndex, true) - renderedFrameCount++ - lastOutputTimeMs = System.currentTimeMillis() - logDebug("成功渲染帧 #$renderedFrameCount") - - // 通知Flutter刷新纹理 - notifyFrameAvailable() - } else { - // 释放但不渲染 - codec.releaseOutputBuffer(outputBufferIndex, false) - } - } - outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { - logDebug("输出格式变更: ${codec.outputFormat}") - } - outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER -> { - outputDone = true - } - outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> { - logDebug("输出缓冲区已更改") - } - else -> { - // 其他错误情况 - logWarning("未知的输出缓冲区索引: $outputBufferIndex") - errorDetected = true - } - } - } catch (e: Exception) { - logError("处理输出缓冲区时出错", e) - errorDetected = true - } - } - - // 如果检测到错误,可以考虑重置编码器状态或清除缓冲区 - if (errorDetected) { - logWarning("检测到解码错误,等待下一个关键帧...") - // 在实际应用中,可以考虑在这里执行更复杂的恢复逻辑 - } - } - - /** - * 释放所有资源 - */ fun release() { - logDebug("释放解码器资源") - - isRunning.set(false) - isDecoderConfigured.set(false) - + running = false + inputFrameQueue.clear() try { - // 清空帧队列 - inputFrameQueue.clear() - - // 释放MediaCodec - mediaCodec?.let { codec -> - try { - // 尝试获取锁(如果在异步模式下有必要) - if (isAsyncMode) { - val lockAcquired = codecLock.tryLock(500, TimeUnit.MILLISECONDS) - if (lockAcquired) { - try { - codec.stop() - codec.release() - } finally { - codecLock.unlock() - } - } else { - // 无法获取锁,强制释放 - codec.stop() - codec.release() - } - } else { - // 同步模式直接释放 - codec.stop() - codec.release() - } - } catch (e: Exception) { - logError("释放MediaCodec失败", e) - } - } - mediaCodec = null - - try { - surface.release() - } catch (e: Exception) { - logError("释放Surface失败", e) - } - - try { - textureEntry.release() - } catch (e: Exception) { - logError("释放TextureEntry失败", e) - } - - callback = null - - logDebug("所有资源已释放") - } catch (e: Exception) { - logError("释放资源时出错", e) - } - } - - /** - * 获取解码统计信息 - */ - fun getStatistics(): Map { - // 使用线程安全方式读取统计信息 - return try { - // 对于异步模式,获取锁的读取 - if (isAsyncMode) { - val lockAcquired = codecLock.tryLock(100, TimeUnit.MILLISECONDS) - if (lockAcquired) { - try { - createStatisticsMap() - } finally { - codecLock.unlock() - } - } else { - // 无法获取锁,返回基本信息 - mapOf( - "totalFrames" to frameCount, - "renderedFrames" to renderedFrameCount, - "droppedFrames" to droppedFrameCount, - "fps" to currentFps - ) - } - } else { - // 同步模式直接返回 - createStatisticsMap() - } - } catch (e: Exception) { - logError("获取统计信息失败", e) - mapOf("error" to "获取统计信息失败: ${e.message}") - } - } - - /** - * 检查解码器健康状况 - 用于监控和诊断 - */ - private fun checkDecoderHealth(): Boolean { - val currentTime = System.currentTimeMillis() - - // 检查解码器是否长时间没有输出帧 - if (renderedFrameCount > 0 && - lastOutputTimeMs > 0 && - currentTime - lastOutputTimeMs > 5000) { - logWarning("解码器可能卡住了,已有${(currentTime - lastOutputTimeMs) / 1000}秒没有输出帧") - return false - } - - // 检查错误频率 - if (decodingErrorCount > 10) { - logWarning("解码器错误次数过多: $decodingErrorCount") - return false - } - - // 检查队列使用状况 - if (isAsyncMode && inputFrameQueue.size >= INPUT_BUFFER_QUEUE_CAPACITY * 0.9) { - logWarning("输入队列即将满: ${inputFrameQueue.size}/$INPUT_BUFFER_QUEUE_CAPACITY") - } - - return true - } - - /** - * 创建统计信息映射 - */ - private fun createStatisticsMap(): Map { - return mapOf( - "totalFrames" to frameCount, - "renderedFrames" to renderedFrameCount, - "droppedFrames" to droppedFrameCount, - "fps" to currentFps, - "hasSentSPS" to hasSentSPS.get(), - "hasSentPPS" to hasSentPPS.get(), - "hasSentIDR" to hasSentIDR.get(), - "consecutivePFrames" to consecutivePFrameCount, - "targetWidth" to config.width, - "targetHeight" to config.height, - "frameRate" to (config.frameRate ?: 0), - "isAsync" to isAsyncMode, - "queueSize" to inputFrameQueue.size, - "queueHighWatermark" to frameQueueHighWatermark, - "inputProcessCount" to inputProcessCount, - "outputProcessCount" to outputProcessCount, - "decodingJitterMs" to decodingJitterMs, - "keyFrameInterval" to keyFrameInterval, - "decodingErrorCount" to decodingErrorCount, - "lastDecodingErrorTime" to if (lastDecodingErrorTime > 0) lastDecodingErrorTime else 0, - "decoderHealthy" to checkDecoderHealth() - ) - } - - /** - * 处理输入帧 - 异步模式下的辅助方法 - */ - private fun processInputFrame(codec: MediaCodec, index: Int, inputBuffer: ByteBuffer, frameData: ByteArray, isIFrame: Boolean) { + mediaCodec?.stop() + mediaCodec?.release() + } catch (_: Exception) {} try { - // 检查NAL类型 - val nalType = checkNalType(frameData) - - // 实际使用的NAL类型 - val effectiveType = if (nalType != -1) nalType else if (isIFrame) NAL_UNIT_TYPE_IDR else NAL_UNIT_TYPE_NON_IDR - - // 如果是SPS或PPS且在缓存中已有相同内容,跳过 - if (effectiveType == NAL_UNIT_TYPE_SPS) { - val hash = frameData.hashCode() - if (lastSPSHash == hash) { - codec.queueInputBuffer(index, 0, 0, 0, 0) - return - } - lastSPSHash = hash - hasSentSPS.set(true) - } else if (effectiveType == NAL_UNIT_TYPE_PPS) { - val hash = frameData.hashCode() - if (lastPPSHash == hash) { - codec.queueInputBuffer(index, 0, 0, 0, 0) - return - } - lastPPSHash = hash - hasSentPPS.set(true) - } else if (effectiveType == NAL_UNIT_TYPE_IDR) { - hasSentIDR.set(true) - val currentTime = System.currentTimeMillis() - lastDetectedIFrameTime = currentTime - lastIFrameTimeMs = currentTime - consecutivePFrameCount = 0 - } else { - // P帧处理 - if (!hasSentIDR.get() && renderedFrameCount == 0) { - logWarning("丢弃P帧,因为尚未收到I帧") - droppedFrameCount++ - codec.queueInputBuffer(index, 0, 0, 0, 0) - return - } - - consecutivePFrameCount++ - } - - // 记录帧信息 - frameCount++ - - // 填充数据 - inputBuffer.clear() - inputBuffer.put(frameData) - - // 提交缓冲区 - val flags = if (isIFrame) MediaCodec.BUFFER_FLAG_KEY_FRAME else 0 - codec.queueInputBuffer( - index, - 0, - frameData.size, - System.nanoTime() / 1000L, - flags - ) - } catch (e: Exception) { - logError("处理输入帧失败", e) - // 释放输入缓冲区但不填充数据 - codec.queueInputBuffer(index, 0, 0, 0, 0) - } + surface.release() + } catch (_: Exception) {} } - - /** - * 预热解码器 - 发送一些静帧以稳定解码器状态 - */ - private fun warmUpDecoder() { - if (!isRunning.get() || !isDecoderConfigured.get()) { - return - } - - try { - logDebug("开始预热解码器...") - - // 创建简单的SPS和PPS帧 - val simpleSps = ByteArray(10) { 0 } - simpleSps[0] = 0x00 - simpleSps[1] = 0x00 - simpleSps[2] = 0x00 - simpleSps[3] = 0x01 - simpleSps[4] = 0x67 // NAL类型 = 7 (SPS) - - val simplePps = ByteArray(10) { 0 } - simplePps[0] = 0x00 - simplePps[1] = 0x00 - simplePps[2] = 0x00 - simplePps[3] = 0x01 - simplePps[4] = 0x68 // NAL类型 = 8 (PPS) - - // 创建简单的I帧 - val simpleIdrFrame = ByteArray(100) { 0 } - simpleIdrFrame[0] = 0x00 - simpleIdrFrame[1] = 0x00 - simpleIdrFrame[2] = 0x00 - simpleIdrFrame[3] = 0x01 - simpleIdrFrame[4] = 0x65 // NAL类型 = 5 (IDR) - - // 发送几个预热帧 - for (i in 0 until 3) { - decodeFrame(simpleSps, true) - Thread.sleep(20) - decodeFrame(simplePps, true) - Thread.sleep(20) - decodeFrame(simpleIdrFrame, true) - Thread.sleep(50) - } - - logDebug("预热解码器完成") - } catch (e: Exception) { - logError("预热解码器失败", e) - } - } -} - -/** - * LinkedBlockingQueue扩展方法,将元素放到队列前端 - */ -private fun LinkedBlockingQueue.offerFirst(element: T): Boolean { - // 创建一个新的临时队列 - val tempQueue = LinkedBlockingQueue() - - // 先添加新元素 - tempQueue.offer(element) - - // 然后添加所有现有元素 - this.drainTo(tempQueue) - - // 清空当前队列 - this.clear() - - // 将临时队列中的所有元素添加回当前队列 - return this.addAll(tempQueue) } \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index c0b2e6f..1137510 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -11,9 +11,9 @@ import 'package:video_decode_plugin/video_decode_plugin.dart'; // 用于存储H264文件中解析出的帧 class H264Frame { final Uint8List data; - final FrameType type; - - H264Frame(this.data, this.type); + final int frameType; // 0=I帧, 1=P帧 + final int? refIFrameSeq; + H264Frame(this.data, this.frameType, [this.refIFrameSeq]); } // H264 NAL 单元类型 @@ -131,6 +131,26 @@ class VideoView extends StatefulWidget { State createState() => _VideoViewState(); } +/// 视频解码主页面的状态管理类。 +/// 负责: +/// 1. 加载和解析H264文件 +/// 2. 初始化、释放视频解码器 +/// 3. 控制视频播放、停止、帧解码流程 +/// 4. 管理UI刷新与日志显示 +/// 5. 维护与解码相关的核心状态(如纹理ID、播放状态、错误信息等) +/// +/// 主要成员说明: +/// - _textureId: 当前解码器绑定的纹理ID +/// - _isInitialized: 解码器是否已初始化 +/// - _isPlaying: 是否正在播放 +/// - _statusText: 当前状态文本 +/// - _error: 错误信息 +/// - _h264FileData: 加载的H264文件数据 +/// - _h264Frames: 解析出的帧列表 +/// - _currentFrameIndex: 当前播放帧索引 +/// - _frameTimer: 帧播放定时器 +/// - _logs: 日志信息 +/// - _logScrollController: 日志滚动控制器 class _VideoViewState extends State { // 解码器状态 int? _textureId; @@ -139,11 +159,6 @@ class _VideoViewState extends State { String _statusText = "未初始化"; String _error = ""; - // 解码器状态信息 - DecoderState _decoderState = DecoderState.initializing; - String _decoderStateText = "初始化中"; - bool _isActuallyRendering = false; // 区分预通知和实际渲染状态 - // 帧统计 int _renderedFrameCount = 0; DateTime? _lastFrameTime; @@ -234,75 +249,39 @@ class _VideoViewState extends State { // 解析H264文件,提取NAL单元 void _parseH264File() { if (_h264FileData == null) return; - _log("开始解析H264文件..."); - List frames = []; - - // 查找起始码 0x00000001 或 0x000001 int startIndex = 0; - bool hasSps = false; - bool hasPps = false; - while (startIndex < _h264FileData!.length - 4) { - // 查找下一个起始码 int nextStartIndex = _findStartCode(_h264FileData!, startIndex + 3); if (nextStartIndex == -1) { nextStartIndex = _h264FileData!.length; } - - // 提取NAL单元,跳过起始码(3或4字节) int skipBytes = (_h264FileData![startIndex] == 0x00 && _h264FileData![startIndex + 1] == 0x00 && _h264FileData![startIndex + 2] == 0x00 && _h264FileData![startIndex + 3] == 0x01) ? 4 : 3; - if (nextStartIndex > startIndex + skipBytes) { - // 获取NAL类型 int nalType = _h264FileData![startIndex + skipBytes] & 0x1F; - - // 创建NAL单元数据 var nalData = Uint8List(nextStartIndex - startIndex); for (int i = 0; i < nalData.length; i++) { nalData[i] = _h264FileData![startIndex + i]; } - - // 根据NAL类型分类 - switch (nalType) { - case NalUnitType.SPS: - _log("找到SPS: 位置=${startIndex}, 长度=${nalData.length}"); - hasSps = true; - frames.add(H264Frame(nalData, FrameType.iFrame)); - break; - case NalUnitType.PPS: - _log("找到PPS: 位置=${startIndex}, 长度=${nalData.length}"); - hasPps = true; - frames.add(H264Frame(nalData, FrameType.iFrame)); - break; - case NalUnitType.CODED_SLICE_IDR: - _log("找到I帧: 位置=${startIndex}, 长度=${nalData.length}"); - frames.add(H264Frame(nalData, FrameType.iFrame)); - break; - case NalUnitType.CODED_SLICE_NON_IDR: - frames.add(H264Frame(nalData, FrameType.pFrame)); - break; - default: - // 其他类型的NAL单元也添加进去 - frames.add(H264Frame(nalData, FrameType.pFrame)); - break; + // 0=I帧, 1=P帧 + if (nalType == 7 || nalType == 8 || nalType == 5) { + frames.add(H264Frame(nalData, 0)); + } else { + frames.add(H264Frame(nalData, 1)); } } - startIndex = nextStartIndex; } - setState(() { _h264Frames = frames; }); - - _log("H264文件解析完成,找到 ${frames.length} 个帧,包含SPS=${hasSps}, PPS=${hasPps}"); + _log("H264文件解析完成,找到 "+frames.length.toString()+" 个帧"); } // 查找起始码的辅助方法 @@ -374,45 +353,19 @@ class _VideoViewState extends State { if (_isInitialized) { await _releaseDecoder(); } - _log("正在初始化解码器"); - try { - final config = VideoDecoderConfig( - width: 640, - height: 480, - codecType: CodecType.h264, - frameRate: 24, // 设置为接近原视频的24fps (23.976) - isDebug: true, // 打开调试日志 - ); - + final config = VideoDecoderConfig(width: 640, height: 480); final textureId = await VideoDecodePlugin.initDecoder(config); - if (textureId != null) { _textureId = textureId; - - // 设置帧回调 - VideoDecodePlugin.setFrameCallbackForTexture( - textureId, _onFrameAvailable); - - // 设置状态回调 - VideoDecodePlugin.setStateCallbackForTexture( - textureId, _onDecoderStateChanged); - setState(() { _isInitialized = true; _error = ""; _statusText = "就绪"; - _renderedFrameCount = 0; // 重置帧计数 - _decoderState = DecoderState.initializing; - _decoderStateText = "初始化中"; - _isActuallyRendering = false; + _renderedFrameCount = 0; }); - _log("解码器初始化成功,纹理ID: $_textureId"); - - // 自动发送测试帧以触发渲染 - await _sendTestIFrame(); } else { setState(() { _error = "获取纹理ID失败"; @@ -429,120 +382,40 @@ class _VideoViewState extends State { } } - // 解码器状态变化回调 - void _onDecoderStateChanged( - int textureId, DecoderState state, Map stats) { - if (!mounted) return; - - String stateText; - switch (state) { - case DecoderState.initializing: - stateText = "初始化中"; - break; - case DecoderState.ready: - stateText = "准备就绪"; - break; - case DecoderState.rendering: - stateText = "渲染中"; - // 标记实际渲染状态 - _isActuallyRendering = true; - break; - case DecoderState.error: - stateText = "出错"; - // 获取错误信息 - final errorMessage = stats['errorMessage'] as String?; - if (errorMessage != null) { - _log("解码器错误: $errorMessage"); - } - break; - case DecoderState.released: - stateText = "已释放"; - break; - default: - stateText = "未知状态"; + // 解码帧 + Future _decodeNextFrame(H264Frame frame, int frameSeq) async { + if (_textureId == null || !_isInitialized || !_isPlaying) { + return false; } - - // 更新解码器状态UI - setState(() { - _decoderState = state; - _decoderStateText = stateText; - - // 更新统计信息 - if (stats.isNotEmpty) { - _decoderFps = (stats['fps'] as num?)?.toDouble() ?? 0.0; - _renderedFrameCount = (stats['renderedFrames'] as int?) ?? 0; - - // 更新更多统计信息 - _totalFrames = (stats['totalFrames'] as int?) ?? 0; - _droppedFrames = (stats['droppedFrames'] as int?) ?? 0; - _hasSentIDR = (stats['hasSentIDR'] as bool?) ?? false; - _hasSentSPS = (stats['hasSentSPS'] as bool?) ?? false; - _hasSentPPS = (stats['hasSentPPS'] as bool?) ?? false; - - // 更新状态文本 - if (state == DecoderState.rendering) { - _statusText = _isPlaying - ? "播放中 (解码总帧: $_totalFrames, 丢弃: $_droppedFrames)" - : "已停止"; - } - } - }); - - String decoderInfo = "解码器状态更新: $_decoderStateText, " + - "帧数据: 渲染=$_renderedFrameCount, 总计=$_totalFrames, 丢弃=$_droppedFrames, " + - "FPS=${_decoderFps.toStringAsFixed(1)}, " + - "参数集: SPS=${_hasSentSPS}, PPS=${_hasSentPPS}, IDR=${_hasSentIDR}"; - - _log(decoderInfo); - } - - // 添加一个测试I帧来触发渲染 - Future _sendTestIFrame() async { - if (_textureId == null || !_isInitialized) { - _log("解码器未准备好,无法发送测试帧"); - return; - } - - _log("生成并发送测试I帧"); - - // 创建一个简单的NAL单元 (IDR帧) - // 5字节的起始码 + NAL类型5(I帧) + 一些简单的数据 - List testFrameData = [ - 0x00, 0x00, 0x00, 0x01, 0x65, // 起始码 + NAL类型 (0x65 = 101|0101 -> 类型5) - 0x88, 0x84, 0x21, 0x43, 0x14, 0x56, 0x32, 0x80 // 一些随机数据 - ]; - - Uint8List testFrame = Uint8List.fromList(testFrameData); - try { - _log("发送测试I帧: ${testFrame.length} 字节"); - - bool success = await VideoDecodePlugin.decodeFrameForTexture( - _textureId!, testFrame, FrameType.iFrame); - - _log("测试I帧发送结果: ${success ? '成功' : '失败'}"); + final timestamp = DateTime.now().microsecondsSinceEpoch; + final success = await VideoDecodePlugin.decodeFrame( + frameData: frame.data, + frameType: frame.frameType, + timestamp: timestamp, + frameSeq: frameSeq, + refIFrameSeq: frame.refIFrameSeq, + ); + if (!success) { + _log("解码帧失败,索引 $frameSeq (type=${frame.frameType})"); + } + return success; } catch (e) { - _log("发送测试帧错误: $e"); + _log("解码帧错误: $e"); + return false; } } Future _releaseDecoder() async { - _statsTimer?.cancel(); // 取消统计信息定时器 if (_textureId != null) { _log("正在释放解码器资源"); - try { - await VideoDecodePlugin.releaseDecoderForTexture(_textureId!); - + await VideoDecodePlugin.releaseDecoder(); setState(() { _textureId = null; _isInitialized = false; _statusText = "已释放"; - _isActuallyRendering = false; - _decoderState = DecoderState.released; - _decoderStateText = "已释放"; }); - _log("解码器资源释放成功"); } catch (e) { _log("释放解码器错误: $e"); @@ -602,7 +475,7 @@ class _VideoViewState extends State { if (nalType == NalUnitType.SPS || nalType == NalUnitType.PPS) { _log("发送${nalType == NalUnitType.SPS ? 'SPS' : 'PPS'}数据"); - await _decodeNextFrame(frame); + await _decodeNextFrame(frame, i); // 发送后等待一小段时间,确保解码器处理 await Future.delayed(Duration(milliseconds: 30)); } @@ -644,7 +517,7 @@ class _VideoViewState extends State { } final frame = _h264Frames[_currentFrameIndex]; - bool decodeSuccess = await _decodeNextFrame(frame); + bool decodeSuccess = await _decodeNextFrame(frame, _currentFrameIndex); // 只有在成功解码的情况下才显示日志信息 if (!decodeSuccess && _enablePacketLoss) { @@ -656,91 +529,6 @@ class _VideoViewState extends State { }); } - Future _decodeNextFrame(H264Frame frame) async { - if (_textureId == null || !_isInitialized || !_isPlaying) { - return false; - } - - try { - // 获取NAL类型 - int nalType = _getNalType(frame.data); - - // 模拟丢包 - if (_enablePacketLoss) { - bool shouldDrop = false; - - // 爆发式丢包模式 - if (_burstPacketLossMode && _burstPacketLossCounter > 0) { - shouldDrop = true; - _burstPacketLossCounter--; - } - // 随机丢包 - else if (math.Random().nextDouble() < _packetLossRate) { - shouldDrop = true; - - // 触发爆发式丢包 - if (_burstPacketLossMode) { - _burstPacketLossCounter = math.Random().nextInt(5) + 1; // 随机爆发1-5个包 - } - } - - // 特定类型NAL的丢包策略 - if (nalType == NalUnitType.CODED_SLICE_IDR && _dropIFrames) { - shouldDrop = true; - } else if ((nalType == NalUnitType.CODED_SLICE_NON_IDR || - nalType == NalUnitType.CODED_SLICE_EXTENSION) && - _dropPFrames) { - shouldDrop = true; - } else if ((nalType == NalUnitType.SPS || nalType == NalUnitType.PPS) && - _dropSPSPPS) { - shouldDrop = true; - } - - if (shouldDrop) { - _droppedFramesCount++; - String nalTypeName = NalUnitType.getName(nalType); - _log("丢弃帧:NAL类型 = $nalTypeName"); - - // 显示丢帧效果 - setState(() { - _showingErrorFrame = true; - }); - - // 1秒后重置丢帧效果指示器 - _errorFrameResetTimer?.cancel(); - _errorFrameResetTimer = Timer(Duration(milliseconds: 1000), () { - if (mounted) { - setState(() { - _showingErrorFrame = false; - }); - } - }); - - return false; // 直接返回false,不进行解码 - } - } - - // 解码帧 - final success = await VideoDecodePlugin.decodeFrameForTexture( - _textureId!, - frame.data, - frame.type, - ); - - if (!success) { - _log("解码帧失败,索引 $_currentFrameIndex (${frame.type})"); - } else { - String nalTypeName = NalUnitType.getName(nalType); - _log( - "解码帧成功,索引 $_currentFrameIndex (${frame.type}), NAL类型: $nalTypeName"); - } - return success; - } catch (e) { - _log("解码帧错误: $e"); - return false; - } - } - void _log(String message) { final timestamp = DateTime.now().toString().split('.').first; final logMessage = "[$timestamp] $message"; @@ -793,7 +581,7 @@ class _VideoViewState extends State { Container(color: Colors.black), // 无帧时显示加载指示 - if (_renderedFrameCount == 0 || !_isActuallyRendering) + if (_renderedFrameCount == 0) Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -803,11 +591,7 @@ class _VideoViewState extends State { ), SizedBox(height: 16), Text( - _decoderState == DecoderState.initializing - ? '初始化中...' - : _decoderState == DecoderState.ready - ? '准备就绪,等待首帧...' - : '加载中...', + '初始化中...', style: TextStyle(color: Colors.white70, fontSize: 14), ), ], @@ -835,63 +619,41 @@ class _VideoViewState extends State { ), ), - // 解码器状态指示 - if (_decoderState == DecoderState.error) - Container( - color: Colors.red.withOpacity(0.3), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.error_outline, size: 48, color: Colors.white), - SizedBox(height: 16), - Text( - '解码器错误', - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold), - ), - ], - ), - ), - ), - - // 显示帧计数 - 调试用 - Positioned( - right: 10, - top: 10, - child: Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), - borderRadius: BorderRadius.circular(4), - ), - constraints: BoxConstraints( - maxWidth: 150, // 限制最大宽度 - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, // 确保column只占用所需空间 - children: [ - Text( - '帧: $_renderedFrameCount', - style: TextStyle(color: Colors.white, fontSize: 12), - ), - if (_enablePacketLoss) - Text( - '丢帧: $_droppedFramesCount', - style: TextStyle( - color: _droppedFramesCount > 0 - ? Colors.orange - : Colors.white70, - fontSize: 12, - ), - ), - ], - ), - ), - ), + // // 显示帧计数 - 调试用 + // Positioned( + // right: 10, + // top: 10, + // child: Container( + // padding: EdgeInsets.all(5), + // decoration: BoxDecoration( + // color: Colors.black.withOpacity(0.5), + // borderRadius: BorderRadius.circular(4), + // ), + // constraints: BoxConstraints( + // maxWidth: 150, // 限制最大宽度 + // ), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.end, + // mainAxisSize: MainAxisSize.min, // 确保column只占用所需空间 + // children: [ + // Text( + // '帧: $_renderedFrameCount', + // style: TextStyle(color: Colors.white, fontSize: 12), + // ), + // if (_enablePacketLoss) + // Text( + // '丢帧: $_droppedFramesCount', + // style: TextStyle( + // color: _droppedFramesCount > 0 + // ? Colors.orange + // : Colors.white70, + // fontSize: 12, + // ), + // ), + // ], + // ), + // ), + // ), ], ); } @@ -947,106 +709,82 @@ class _VideoViewState extends State { style: TextStyle(fontWeight: FontWeight.bold)), - // 解码器状态行 - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - '解码器状态: $_decoderStateText', - style: TextStyle( - color: _getStateColor(), - fontWeight: FontWeight.bold), - ), - ), - - // 实际渲染状态 - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - '实际渲染: ${_isActuallyRendering ? "是" : "否"}', - style: TextStyle( - color: _isActuallyRendering - ? Colors.green - : Colors.orange, - ), - ), - ), - // FPS和帧数信息 - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - '解码器FPS: ${_decoderFps.toStringAsFixed(1)}', - style: TextStyle(color: Colors.green), - ), - ), - - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text('已渲染帧数: $_renderedFrameCount'), - ), - - // 丢弃帧信息 - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - '已丢弃帧数: $_droppedFramesCount', - style: TextStyle( - color: _droppedFramesCount > 0 - ? Colors.orange - : Colors.black), - ), - ), - - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text('当前帧索引: $_currentFrameIndex'), - ), - - // 参数集状态 - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('参数集状态:'), - Padding( - padding: const EdgeInsets.only( - left: 8.0, top: 2.0), - child: Text( - 'SPS: ${_hasSentSPS ? "已发送" : "未发送"}'), - ), - Padding( - padding: const EdgeInsets.only( - left: 8.0, top: 2.0), - child: Text( - 'PPS: ${_hasSentPPS ? "已发送" : "未发送"}'), - ), - Padding( - padding: const EdgeInsets.only( - left: 8.0, top: 2.0), - child: Text( - 'IDR: ${_hasSentIDR ? "已发送" : "未发送"}'), - ), - ], - ), - ), - - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text('总帧数: $_totalFrames'), - ), - - // 错误信息 - if (_error.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - '错误: $_error', - style: TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold), - ), - ), + // Padding( + // padding: const EdgeInsets.only(top: 4.0), + // child: Text( + // '解码器FPS: ${_decoderFps.toStringAsFixed(1)}', + // style: TextStyle(color: Colors.green), + // ), + // ), + // + // Padding( + // padding: const EdgeInsets.only(top: 4.0), + // child: Text('已渲染帧数: $_renderedFrameCount'), + // ), + // + // // 丢弃帧信息 + // Padding( + // padding: const EdgeInsets.only(top: 4.0), + // child: Text( + // '已丢弃帧数: $_droppedFramesCount', + // style: TextStyle( + // color: _droppedFramesCount > 0 + // ? Colors.orange + // : Colors.black), + // ), + // ), + // + // Padding( + // padding: const EdgeInsets.only(top: 4.0), + // child: Text('当前帧索引: $_currentFrameIndex'), + // ), + // + // // 参数集状态 + // Padding( + // padding: const EdgeInsets.only(top: 4.0), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text('参数集状态:'), + // Padding( + // padding: const EdgeInsets.only( + // left: 8.0, top: 2.0), + // child: Text( + // 'SPS: ${_hasSentSPS ? "已发送" : "未发送"}'), + // ), + // Padding( + // padding: const EdgeInsets.only( + // left: 8.0, top: 2.0), + // child: Text( + // 'PPS: ${_hasSentPPS ? "已发送" : "未发送"}'), + // ), + // Padding( + // padding: const EdgeInsets.only( + // left: 8.0, top: 2.0), + // child: Text( + // 'IDR: ${_hasSentIDR ? "已发送" : "未发送"}'), + // ), + // ], + // ), + // ), + // + // Padding( + // padding: const EdgeInsets.only(top: 4.0), + // child: Text('总帧数: $_totalFrames'), + // ), + // + // // 错误信息 + // if (_error.isNotEmpty) + // Padding( + // padding: const EdgeInsets.only(top: 4.0), + // child: Text( + // '错误: $_error', + // style: TextStyle( + // color: Colors.red, + // fontWeight: FontWeight.bold), + // ), + // ), // H264文件信息 Padding( @@ -1104,6 +842,7 @@ class _VideoViewState extends State { }, child: Text('刷新'), ), + ], ), ), @@ -1284,39 +1023,6 @@ class _VideoViewState extends State { ), ); } - - // 此方法保留用于手动获取最新统计信息,不再需要定时调用 - Future _updateDecoderStats() async { - if (_textureId == null || !_isInitialized) return; - - try { - // 获取所有解码器统计信息 - final stats = await VideoDecodePlugin.getDecoderStats(_textureId!); - - if (mounted) { - _log("手动更新解码器统计信息: $stats"); - } - } catch (e) { - _log("获取解码器统计信息失败: $e"); - } - } - - Color _getStateColor() { - switch (_decoderState) { - case DecoderState.initializing: - return Colors.orange; - case DecoderState.ready: - return Colors.green; - case DecoderState.rendering: - return Colors.blue; - case DecoderState.error: - return Colors.red; - case DecoderState.released: - return Colors.grey; - default: - return Colors.black; - } - } } // 添加错误帧绘制器 diff --git a/lib/video_decode_plugin.dart b/lib/video_decode_plugin.dart index a76b5c9..6526b6b 100644 --- a/lib/video_decode_plugin.dart +++ b/lib/video_decode_plugin.dart @@ -7,86 +7,22 @@ import 'package:flutter/services.dart'; import 'video_decode_plugin_platform_interface.dart'; -/// 视频帧类型 -enum FrameType { - /// I帧 - iFrame, - - /// P帧 - pFrame, -} - -/// 视频编码类型 -enum CodecType { - /// H.264编码 - h264, - - /// H.265编码 - h265, -} - -/// 解码器状态枚举 -enum DecoderState { - /// 初始化中 - initializing, - - /// 准备就绪,但还未开始实际渲染 - ready, - - /// 渲染中 - rendering, - - /// 出错 - error, - - /// 已释放 - released, -} - -/// 帧可用回调函数类型 -typedef FrameAvailableCallback = void Function(int textureId); - -/// 解码器状态回调函数类型 -typedef DecoderStateCallback = void Function( - int textureId, DecoderState state, Map stats); - -/// 解码器实例内部类 -class _DecoderInstance { - final int textureId; - FrameAvailableCallback? frameCallback; - DecoderStateCallback? stateCallback; - - _DecoderInstance(this.textureId); -} - /// 视频解码器配置 class VideoDecoderConfig { - /// 视频宽度,默认640 + /// 视频宽度 final int width; - /// 视频高度,默认360 + /// 视频高度 final int height; - /// 帧率,可为空 - final int? frameRate; - /// 编码类型,默认h264 - final CodecType codecType; - - /// 是否为调试模式,默认false - final bool isDebug; - - /// 是否使用异步解码模式,默认true - final bool isAsync; + final String codecType; /// 构造函数 VideoDecoderConfig({ - this.width = 640, - this.height = 360, - this.frameRate, - this.codecType = CodecType.h264, - this.isDebug = false, - this.isAsync = true, + required this.width, + required this.height, + this.codecType = 'h264', }); /// 转换为Map @@ -94,10 +30,7 @@ class VideoDecoderConfig { return { 'width': width, 'height': height, - 'frameRate': frameRate, - 'codecType': codecType.toString().split('.').last, - 'isDebug': isDebug, - 'isAsync': isAsync, + 'codecType': codecType, }; } } @@ -106,196 +39,44 @@ class VideoDecoderConfig { class VideoDecodePlugin { static const MethodChannel _channel = MethodChannel('video_decode_plugin'); - // 解码器映射表,支持多实例 - static final Map _decoders = {}; + static int? _textureId; - // 默认解码器ID - static int? _defaultTextureId; - - // 监听器初始化标志 - static bool _listenerInitialized = false; - - // 是否处于调试模式 - static bool _isDebugMode = false; - - // 解码器状态跟踪 - 防止释放后继续使用 - static final Map _isDecoderReleasing = {}; - - // 解码器状态锁 - 防止并发访问状态 - static final _decoderStateLock = Object(); - - // 错误日志抑制 - 防止重复日志 - static int _uninitializedErrorCount = 0; - static int _lastErrorLogTime = 0; - - /// 日志输出控制 - 调试信息 - static void _logDebug(String message) { - if (_isDebugMode) { - debugPrint('[VideoDecodePlugin] $message'); - } + /// 初始化解码器 + static Future initDecoder(VideoDecoderConfig config) async { + final textureId = await _channel.invokeMethod('initDecoder', config.toMap()); + _textureId = textureId; + return textureId; } - /// 日志输出控制 - 错误信息(总是输出) - static void _logError(String message, {bool throttle = false}) { - if (throttle) { - // 增加计数 - _uninitializedErrorCount++; - - // 检查是否需要输出汇总日志 - final now = DateTime.now().millisecondsSinceEpoch; - if (now - _lastErrorLogTime > 5000 || _uninitializedErrorCount >= 50) { - debugPrint( - '[VideoDecodePlugin] ERROR: $message (发生 $_uninitializedErrorCount 次)'); - _lastErrorLogTime = now; - _uninitializedErrorCount = 0; - } - } else { - // 直接输出日志 - debugPrint('[VideoDecodePlugin] ERROR: $message'); - } + /// 解码视频帧(参数扩展) + static Future decodeFrame({ + required Uint8List frameData, + required int frameType, // 0=I帧, 1=P帧 + required int timestamp, // 毫秒或微秒 + required int frameSeq, // 帧序号 + int? refIFrameSeq, // P帧时可选 + }) async { + if (_textureId == null) return false; + final params = { + 'textureId': _textureId, + 'frameData': frameData, + 'frameType': frameType, + 'timestamp': timestamp, + 'frameSeq': frameSeq, + if (refIFrameSeq != null) 'refIFrameSeq': refIFrameSeq, + }; + final result = await _channel.invokeMethod('decodeFrame', params); + return result ?? false; } - /// 初始化方法通道监听器 - static void _initializeMethodCallHandler() { - if (!_listenerInitialized) { - _channel.setMethodCallHandler((call) async { - switch (call.method) { - case 'onFrameAvailable': - final Map args = call.arguments; - final int textureId = args['textureId']; - - // 检查解码器是否正在释放 - bool isReleasing = false; - - // 同步访问解码器状态 - _withLock(_decoderStateLock, () { - isReleasing = _isDecoderReleasing[textureId] ?? false; - }); - - if (isReleasing) { - _logDebug('收到帧通知但解码器 $textureId 正在释放,忽略'); - return null; - } - - // 调用特定纹理ID的帧回调 - final decoder = _decoders[textureId]; - if (decoder != null && decoder.frameCallback != null) { - // 获取是否是预通知 - final bool isPrenotification = args['isPrenotification'] ?? false; - - if (isPrenotification) { - _logDebug('[预通知] 收到初始帧可用通知(无实际视频数据),纹理ID: $textureId'); - } else { - _logDebug('收到帧可用通知,纹理ID: $textureId'); - } - - // 调用回调函数 - decoder.frameCallback!(textureId); - } - - return null; - - case 'onDecoderState': - final Map args = call.arguments; - final int textureId = args['textureId']; - final String stateStr = args['state']; - final Map statsMap = args['stats']; - - // 检查解码器是否正在释放 - bool isReleasing = false; - - // 同步访问解码器状态 - _withLock(_decoderStateLock, () { - isReleasing = _isDecoderReleasing[textureId] ?? false; - }); - - if (isReleasing && stateStr != 'released') { - _logDebug('收到状态回调但解码器 $textureId 正在释放,忽略'); - return null; - } - - // 将状态字符串转换为枚举 - DecoderState state; - switch (stateStr) { - case 'initializing': - state = DecoderState.initializing; - break; - case 'ready': - state = DecoderState.ready; - break; - case 'rendering': - state = DecoderState.rendering; - break; - case 'error': - state = DecoderState.error; - break; - case 'released': - state = DecoderState.released; - break; - default: - state = DecoderState.initializing; - } - - // 将statsMap转换为强类型Map - final Map stats = {}; - statsMap.forEach((key, value) { - if (key is String) { - stats[key] = value; - } - }); - - // 调用状态回调 - final decoder = _decoders[textureId]; - if (decoder != null && decoder.stateCallback != null) { - _logDebug('调用解码器状态回调:纹理ID=$textureId, 状态=$stateStr'); - decoder.stateCallback!(textureId, state, stats); - } - - return null; - - default: - throw PlatformException( - code: 'Unimplemented', - details: 'The method ${call.method} is not implemented', - ); - } - }); - - _listenerInitialized = true; - } - } - - /// 执行同步操作的辅助方法 - static void _withLock(Object lock, Function() action) { - // 在Dart中,Object实例可以直接用作锁对象 - synchronized(lock, action); - } - - /// 在锁保护下执行操作并返回结果 - static T _withLockResult(Object lock, T Function() action) { - return synchronizedWithResult(lock, action); - } - - /// 检查解码器是否处于可用状态 - static bool _isDecoderReady(int textureId) { - bool isReleasing = false; - - _withLock(_decoderStateLock, () { - isReleasing = _isDecoderReleasing[textureId] ?? false; - }); - - return _decoders.containsKey(textureId) && !isReleasing; - } - - /// 设置解码器释放状态 - static void _setDecoderReleasing(int textureId, bool isReleasing) { - _withLock(_decoderStateLock, () { - if (isReleasing) { - _isDecoderReleasing[textureId] = true; - } else { - _isDecoderReleasing.remove(textureId); - } + /// 释放解码器资源 + static Future releaseDecoder() async { + if (_textureId == null) return true; + final result = await _channel.invokeMethod('releaseDecoder', { + 'textureId': _textureId, }); + _textureId = null; + return result ?? false; } /// 获取平台版本 @@ -308,343 +89,13 @@ class VideoDecodePlugin { return Platform.isAndroid || Platform.isIOS; } - /// 设置帧回调(默认解码器) - static void setFrameCallback(FrameAvailableCallback callback) { - if (_defaultTextureId != null) { - setFrameCallbackForTexture(_defaultTextureId!, callback); - } - } - - /// 为特定纹理ID设置帧回调 - static void setFrameCallbackForTexture( - int textureId, FrameAvailableCallback callback) { - _initializeMethodCallHandler(); - - final decoder = _decoders[textureId]; - if (decoder != null) { - decoder.frameCallback = callback; - } - } - - /// 设置解码器状态回调(默认解码器) - static void setStateCallback(DecoderStateCallback callback) { - if (_defaultTextureId != null) { - setStateCallbackForTexture(_defaultTextureId!, callback); - } - } - - /// 为特定纹理ID设置状态回调 - static void setStateCallbackForTexture( - int textureId, DecoderStateCallback callback) { - _initializeMethodCallHandler(); - - final decoder = _decoders[textureId]; - if (decoder != null) { - decoder.stateCallback = callback; - } - } - - /// 初始化解码器 - static Future initDecoder(VideoDecoderConfig config) async { - // 设置调试模式 - _isDebugMode = config.isDebug; - - // 重置错误计数 - _uninitializedErrorCount = 0; - - // 先释放之前的默认解码器 - if (_defaultTextureId != null) { - await releaseDecoder(); - } - - return await createDecoder(config); - } - - /// 创建新的解码器实例(支持多实例) - static Future createDecoder(VideoDecoderConfig config) async { - // 更新调试模式 - _isDebugMode = config.isDebug; - - // 重置错误计数 - _uninitializedErrorCount = 0; - - if (!isPlatformSupported) { - _logError('当前平台不支持视频解码插件'); - return null; - } - - // 确保监听器已初始化 - _initializeMethodCallHandler(); - - try { - _logDebug( - '创建解码器: ${config.width}x${config.height}, 编码: ${config.codecType}'); - final textureId = - await _channel.invokeMethod('initDecoder', config.toMap()); - - if (textureId != null) { - // 创建新解码器实例并保存 - final decoder = _DecoderInstance(textureId); - _decoders[textureId] = decoder; - - // 初始化解码器状态 - _setDecoderReleasing(textureId, false); - - // 设置为默认解码器 - _defaultTextureId = textureId; - _logDebug('解码器创建成功,纹理ID: $textureId'); - } - - return _defaultTextureId; - } catch (e) { - _logError('初始化解码器失败: $e'); - return null; - } - } - /// 获取默认纹理ID - static int? get textureId => _defaultTextureId; - - /// 获取所有活跃的纹理ID - static List get allTextureIds => _decoders.keys.toList(); - - /// 解码视频帧(默认解码器) - static Future decodeFrame( - Uint8List frameData, FrameType frameType) async { - // 使用本地变量缓存ID,防止并发修改 - final int? decoderId = _defaultTextureId; - - if (decoderId == null) { - // 使用节流日志报告错误,避免日志爆炸 - _logError('解码器未初始化', throttle: true); - return false; - } - - // 检查解码器是否正在释放 - if (!_isDecoderReady(decoderId)) { - _logDebug('解码器正在释放,忽略解码请求'); - return false; - } - - return decodeFrameForTexture(decoderId, frameData, frameType); - } - - /// 为特定纹理ID解码视频帧 - static Future decodeFrameForTexture( - int textureId, Uint8List frameData, FrameType frameType) async { - // 检查解码器是否存在且不在释放过程中 - if (!_isDecoderReady(textureId)) { - _logDebug('解码器不可用或正在释放,忽略解码请求'); - return false; - } - - try { - final bool isIFrame = frameType == FrameType.iFrame; - _logDebug( - '解码帧: textureId=$textureId, 大小=${frameData.length}字节, 类型=${isIFrame ? "I帧" : "P帧"}'); - - final result = await _channel.invokeMethod('decodeFrame', { - 'textureId': textureId, - 'frameData': frameData, - 'frameType': frameType.index, - }) ?? - false; - - if (!result) { - _logDebug('解码帧失败'); - } - - return result; - } catch (e) { - // 检查是否是因为解码器已释放导致的错误 - if (!_decoders.containsKey(textureId)) { - _logDebug('解码器已释放,忽略解码错误'); - return false; - } - _logError('解码帧失败: $e'); - return false; - } - } - - /// 释放默认解码器资源 - static Future releaseDecoder() async { - final int? decoderId = _defaultTextureId; - if (decoderId == null) { - return true; - } - - final result = await releaseDecoderForTexture(decoderId); - if (result) { - _defaultTextureId = null; - } - - return result; - } - - /// 释放特定纹理ID的解码器资源 - static Future releaseDecoderForTexture(int textureId) async { - // 检查解码器是否存在 - if (!_decoders.containsKey(textureId)) { - return true; - } - - // 标记解码器正在释放,防止新的解码请求 - _setDecoderReleasing(textureId, true); - - try { - _logDebug('释放解码器: textureId=$textureId'); - - // 清除回调,防止帧回调继续被调用 - clearCallbackForTexture(textureId); - - final result = await _channel.invokeMethod('releaseDecoder', { - 'textureId': textureId, - }) ?? - false; - - if (result) { - // 从映射表中移除 - _decoders.remove(textureId); - - // 如果释放的是默认解码器,重置默认ID - if (_defaultTextureId == textureId) { - _defaultTextureId = null; - } - - // 移除释放状态 - _setDecoderReleasing(textureId, false); - - // 重置错误计数 - _uninitializedErrorCount = 0; - - _logDebug('解码器释放成功: textureId=$textureId'); - } else { - // 释放失败,恢复状态 - _setDecoderReleasing(textureId, false); - _logError('解码器释放失败: textureId=$textureId'); - } - - return result; - } catch (e) { - // 发生异常,但仍然移除解码器,避免资源泄漏 - _decoders.remove(textureId); - if (_defaultTextureId == textureId) { - _defaultTextureId = null; - } - _setDecoderReleasing(textureId, false); - - _logError('释放解码器失败: $e'); - return false; - } - } - - /// 释放所有解码器 - static Future releaseAllDecoders() async { - bool allSuccess = true; - - // 复制键列表,因为我们会在迭代过程中修改映射 - final textureIds = List.from(_decoders.keys); - - _logDebug('释放所有解码器: 共${textureIds.length}个'); - - // 释放每个解码器 - for (final textureId in textureIds) { - final success = await releaseDecoderForTexture(textureId); - if (!success) { - allSuccess = false; - } - } - - // 清空状态 - _decoders.clear(); - _defaultTextureId = null; - - // 清空所有释放状态 - _withLock(_decoderStateLock, () { - _isDecoderReleasing.clear(); - }); - - // 重置错误计数 - _uninitializedErrorCount = 0; - - return allSuccess; - } - - /// 清除特定纹理ID的回调 - static void clearCallbackForTexture(int textureId) { - final decoder = _decoders[textureId]; - if (decoder != null) { - decoder.frameCallback = null; - decoder.stateCallback = null; - _logDebug('已清除纹理ID为$textureId的所有回调'); - } - } - - /// 清除所有回调 - static void clearAllCallbacks() { - for (final decoder in _decoders.values) { - decoder.frameCallback = null; - decoder.stateCallback = null; - } - _logDebug('已清除所有回调'); - } + static int? get textureId => _textureId; /// 注册插件(不需要手动调用) static void registerWith() { // 仅用于插件注册 } - - /// 获取解码器统计信息 - /// - /// [textureId] 纹理ID - /// 返回包含统计信息的Map,包括: - /// - totalFrames: 接收的总帧数 - /// - renderedFrames: 成功渲染的帧数 - /// - droppedFrames: 丢弃的帧数 - /// - fps: 当前渲染FPS - /// - hasSentSPS: 是否已发送SPS - /// - hasSentPPS: 是否已发送PPS - /// - hasSentIDR: 是否已发送IDR(I帧) - /// - consecutivePFrames: 当前连续P帧数 - /// - targetWidth: 目标宽度 - /// - targetHeight: 目标高度 - /// - frameRate: 目标帧率 - /// - decoderCount: 当前活跃的解码器数量 - /// - textureId: 纹理ID - static Future> getDecoderStats(int textureId) async { - // 检查解码器是否正在释放 - if (!_isDecoderReady(textureId)) { - _logDebug('解码器不可用或正在释放,无法获取统计信息'); - return {}; - } - - try { - _logDebug('获取解码器统计信息: textureId=$textureId'); - final params = { - 'textureId': textureId, - }; - - final result = await _channel.invokeMethod>( - 'getDecoderStats', params); - if (result == null) { - return {}; - } - - // 将Object?类型转换为明确的类型 - final Map typedResult = {}; - result.forEach((key, value) { - if (key is String) { - typedResult[key] = value; - } - }); - - _logDebug('获取解码器统计信息成功: $typedResult'); - return typedResult; - } catch (e) { - _logError('获取解码器统计信息失败: $e'); - return {}; - } - } } /// 在Dart中实现简单的同步锁