From f9038b39c4d7ac4e9e9fa485575fe4cd8f07e644 Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 21 Apr 2025 16:08:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:v1=E7=89=88=E6=9C=AC=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video_decode_plugin/VideoDecodePlugin.kt | 87 +++++-- .../video_decode_plugin/VideoDecoder.kt | 154 +++++++----- lib/video_decode_plugin.dart | 237 ++++++++++++++++-- 3 files changed, 390 insertions(+), 88 deletions(-) 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 1d637e2..cd3e2b9 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 @@ -35,6 +35,38 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { // 主线程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) + } + } /** * 插件绑定到Flutter引擎时调用 @@ -75,7 +107,7 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { } } } catch (e: Exception) { - Log.e(TAG, "处理方法调用失败", e) + logError("处理方法调用失败", e) result.error("NATIVE_ERROR", "处理方法调用失败: ${e.message}", null) } } @@ -102,6 +134,9 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { val isDebug = call.argument("isDebug") ?: false val enableHardwareDecoder = call.argument("enableHardwareDecoder") ?: true + // 更新插件的调试模式标志 + this.isDebugMode = isDebug + // 创建纹理 val textureEntry = textureRegistry.createSurfaceTexture() val textureId = textureEntry.id() @@ -109,7 +144,7 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { // 检查这个纹理ID是否已经被使用过 if (releasedTextureIds.contains(textureId)) { // 如果已经被使用过,说明Flutter引擎在重用纹理ID,这可能导致问题 - Log.w(TAG, "警告: 纹理ID $textureId 已被使用过,这可能导致问题") + logWarning("警告: 纹理ID $textureId 已被使用过,这可能导致问题") // 记录这个纹理ID现在是活跃的 releasedTextureIds.remove(textureId) @@ -136,16 +171,28 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { // 通知Flutter刷新纹理 runOnMainThread { try { - Log.d(TAG, "发送帧可用通知给Flutter,纹理ID: $textureId") + // 根据当前帧数判断是否是预通知 + val decoder = decoders[textureId] + val stats = decoder?.getStatistics() ?: mapOf() + val renderedFrames = stats["renderedFrames"] as? Int ?: 0 + + if (renderedFrames == 0) { + // 这是初始化预通知 + logDebug("[预通知] 发送初始帧可用通知给Flutter,纹理ID: $textureId(无实际视频数据)") + } else { + // 这是实际帧通知 + logDebug("发送帧可用通知给Flutter,纹理ID: $textureId,已渲染帧数: $renderedFrames") + } + channel.invokeMethod("onFrameAvailable", mapOf("textureId" to textureId)) } catch (e: Exception) { - Log.e(TAG, "通知Flutter更新纹理失败", e) + logError("通知Flutter更新纹理失败", e) } } } override fun onError(error: String) { - Log.e(TAG, "解码器错误: $error") + logError("解码器错误: $error") } } @@ -156,7 +203,7 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { result.success(textureId) } catch (e: Exception) { - Log.e(TAG, "初始化解码器失败", e) + logError("初始化解码器失败", e) result.error("INIT_FAILED", "初始化解码器失败: ${e.message}", null) } } @@ -167,13 +214,18 @@ 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 textureId = call.argument("textureId")?.toLong() ?: + return result.error("INVALID_ARGS", "无效的纹理ID", 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 decoder = decoders[textureId] ?: + return result.error("DECODER_NOT_FOUND", "找不到纹理ID对应的解码器", null) // 解码帧 val success = decoder.decodeFrame(frameData, isIFrame) @@ -182,7 +234,7 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { result.success(success) } catch (e: Exception) { - Log.e(TAG, "解码帧失败", e) + logError("解码帧失败", e) result.error("DECODE_FAILED", "解码帧失败: ${e.message}", null) } } @@ -193,7 +245,8 @@ 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 textureId = call.argument("textureId")?.toLong() ?: + return result.error("INVALID_ARGS", "无效的纹理ID", null) // 获取解码器 val decoder = decoders[textureId] @@ -216,7 +269,7 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { result.success(true) } catch (e: Exception) { - Log.e(TAG, "释放解码器失败", e) + logError("释放解码器失败", e) result.error("RELEASE_FAILED", "释放解码器失败: ${e.message}", null) } } @@ -227,10 +280,12 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { private fun handleGetDecoderStats(call: MethodCall, result: Result) { try { // 获取纹理ID - val textureId = call.argument("textureId")?.toLong() ?: return result.error("INVALID_ARGS", "无效的纹理ID", null) + 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 decoder = decoders[textureId] ?: + return result.error("DECODER_NOT_FOUND", "找不到纹理ID对应的解码器", null) // 获取统计信息 val stats = decoder.getStatistics() @@ -244,7 +299,7 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { result.success(enhancedStats) } catch (e: Exception) { - Log.e(TAG, "获取解码器统计信息失败", e) + logError("获取解码器统计信息失败", e) result.error("STATS_FAILED", "获取解码器统计信息失败: ${e.message}", null) } } @@ -269,7 +324,7 @@ class VideoDecodePlugin : FlutterPlugin, MethodCallHandler { try { decoder.release() } catch (e: Exception) { - Log.e(TAG, "插件分离时释放解码器失败", e) + logError("插件分离时释放解码器失败", e) } } 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 490c5f9..fe1c7e9 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 @@ -68,10 +68,10 @@ class VideoDecoder( private var renderedFrameCount = 0 private var droppedFrameCount = 0 - // 跟踪I帧状态 - private var hasSentSPS = false - private var hasSentPPS = false - private var hasSentIDR = true + // 跟踪I帧状态 - 使用AtomicBoolean防止并发问题 + private val hasSentSPS = AtomicBoolean(false) + private val hasSentPPS = AtomicBoolean(false) + private val hasSentIDR = AtomicBoolean(false) // 跟踪上一个关键帧时间 private var lastIFrameTimeMs = 0L @@ -89,41 +89,99 @@ class VideoDecoder( // 主线程Handler,用于在主线程上更新纹理 private val mainHandler = Handler(Looper.getMainLooper()) - // 日志控制 - private var logVerbose = false - private var frameLogThreshold = 30 // 每30帧输出一次详细日志 + // 是否是调试模式 + private val isDebugMode: Boolean = config.isDebug + + /** + * 输出调试日志 - 仅在调试模式下输出 + */ + 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) + } + } // 解码器初始化 init { try { - logVerbose = config.isDebug - - // 捕获所有日志 - Log.d(TAG, "初始化解码器: ${config.width}x${config.height}, 编码: ${config.codecType}") - // 设置SurfaceTexture的默认缓冲区大小 surfaceTexture.setDefaultBufferSize(config.width, config.height) + logDebug("初始化解码器: ${config.width}x${config.height}, 编码: ${config.codecType}") // 初始化解码器 if (setupDecoder()) { isRunning.set(true) // 通知初始帧可用(让Flutter创建Texture View) - Log.d(TAG, "解码器初始化成功,发送初始帧通知") + logDebug("[预通知] 解码器初始化成功,发送初始帧通知(无实际视频数据)") mainHandler.post { - notifyFrameAvailable() + notifyFrameAvailableInitial() } } else { - Log.e(TAG, "解码器初始化失败") + logError("解码器初始化失败") callback?.onError("解码器初始化失败") } } catch (e: Exception) { - Log.e(TAG, "创建解码器实例失败", e) + 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 + } + + try { + logDebug("发送帧可用通知,当前渲染帧数: $renderedFrameCount") + callback?.onFrameAvailable() + } catch (e: Exception) { + logError("通知帧可用时出错: ${e.message}", e) + } + } + /** * 设置解码器 */ @@ -158,33 +216,16 @@ class VideoDecoder( mediaCodec = decoder isDecoderConfigured.set(true) - Log.d(TAG, "解码器设置完成: ${decoder.codecInfo.name}") + logDebug("解码器设置完成: ${decoder.codecInfo.name}") return true } catch (e: Exception) { - Log.e(TAG, "设置解码器失败", e) + logError("设置解码器失败", e) isDecoderConfigured.set(false) callback?.onError("设置解码器失败: ${e.message}") return false } } - /** - * 通知帧可用 - */ - private fun notifyFrameAvailable() { - if (!isRunning.get()) { - Log.d(TAG, "解码器已停止,跳过帧可用通知") - return - } - - try { - Log.d(TAG, "发送帧可用通知,当前渲染帧数: $renderedFrameCount") - callback?.onFrameAvailable() - } catch (e: Exception) { - Log.e(TAG, "通知帧可用时出错: ${e.message}", e) - } - } - /** * 快速检查NAL类型 */ @@ -215,7 +256,7 @@ class VideoDecoder( } } } catch (e: Exception) { - Log.e(TAG, "检查NAL类型出错", e) + logError("检查NAL类型出错", e) } // 无法识别,使用传入的参数 @@ -227,7 +268,7 @@ class VideoDecoder( */ fun decodeFrame(frameData: ByteArray, isIFrame: Boolean): Boolean { if (!isRunning.get() || !isDecoderConfigured.get() || frameData.isEmpty()) { - Log.w(TAG, "解码器未运行或未配置或帧数据为空") + logWarning("解码器未运行或未配置或帧数据为空") return false } @@ -245,20 +286,21 @@ class VideoDecoder( val hash = frameData.hashCode() if (lastSPSHash == hash) return true lastSPSHash = hash - hasSentSPS = true + hasSentSPS.set(true) } else if (effectiveType == NAL_UNIT_TYPE_PPS) { val hash = frameData.hashCode() if (lastPPSHash == hash) return true lastPPSHash = hash - hasSentPPS = true + hasSentPPS.set(true) } else if (effectiveType == NAL_UNIT_TYPE_IDR) { - hasSentIDR = true + hasSentIDR.set(true) lastIFrameTimeMs = System.currentTimeMillis() consecutivePFrameCount = 0 } else { // P帧处理 - if (!hasSentIDR) { - Log.w(TAG, "丢弃P帧,因为尚未收到I帧") + if (!hasSentIDR.get() && renderedFrameCount == 0) { + logWarning("丢弃P帧,因为尚未收到I帧") + droppedFrameCount++ return false } @@ -271,14 +313,14 @@ class VideoDecoder( // 解码帧 val inputBufferIndex = codec.dequeueInputBuffer(TIMEOUT_US) if (inputBufferIndex < 0) { - Log.w(TAG, "无法获取输入缓冲区,可能需要等待") + logWarning("无法获取输入缓冲区,可能需要等待") return false } // 获取输入缓冲区 val inputBuffer = codec.getInputBuffer(inputBufferIndex) if (inputBuffer == null) { - Log.e(TAG, "获取输入缓冲区失败") + logError("获取输入缓冲区失败") return false } @@ -301,7 +343,7 @@ class VideoDecoder( return true } catch (e: Exception) { - Log.e(TAG, "解码帧失败", e) + logError("解码帧失败", e) return false } } @@ -325,14 +367,14 @@ class VideoDecoder( if (render) { renderedFrameCount++ lastOutputTimeMs = System.currentTimeMillis() - Log.d(TAG, "成功渲染帧 #$renderedFrameCount") + logDebug("成功渲染帧 #$renderedFrameCount") // 通知Flutter刷新纹理 notifyFrameAvailable() } } outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { - Log.d(TAG, "输出格式变更: ${codec.outputFormat}") + logDebug("输出格式变更: ${codec.outputFormat}") } outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER -> { outputDone = true @@ -345,7 +387,7 @@ class VideoDecoder( * 释放所有资源 */ fun release() { - Log.d(TAG, "释放解码器资源") + logDebug("释放解码器资源") isRunning.set(false) isDecoderConfigured.set(false) @@ -356,7 +398,7 @@ class VideoDecoder( codec.stop() codec.release() } catch (e: Exception) { - Log.e(TAG, "释放MediaCodec失败", e) + logError("释放MediaCodec失败", e) } } mediaCodec = null @@ -364,20 +406,20 @@ class VideoDecoder( try { surface.release() } catch (e: Exception) { - Log.e(TAG, "释放Surface失败", e) + logError("释放Surface失败", e) } try { textureEntry.release() } catch (e: Exception) { - Log.e(TAG, "释放TextureEntry失败", e) + logError("释放TextureEntry失败", e) } callback = null - Log.d(TAG, "所有资源已释放") + logDebug("所有资源已释放") } catch (e: Exception) { - Log.e(TAG, "释放资源时出错", e) + logError("释放资源时出错", e) } } @@ -389,9 +431,9 @@ class VideoDecoder( "totalFrames" to frameCount, "renderedFrames" to renderedFrameCount, "droppedFrames" to droppedFrameCount, - "hasSentSPS" to hasSentSPS, - "hasSentPPS" to hasSentPPS, - "hasSentIDR" to hasSentIDR, + "hasSentSPS" to hasSentSPS.get(), + "hasSentPPS" to hasSentPPS.get(), + "hasSentIDR" to hasSentIDR.get(), "consecutivePFrames" to consecutivePFrameCount, "targetWidth" to config.width, "targetHeight" to config.height, diff --git a/lib/video_decode_plugin.dart b/lib/video_decode_plugin.dart index 24c8abd..f43ec89 100644 --- a/lib/video_decode_plugin.dart +++ b/lib/video_decode_plugin.dart @@ -102,6 +102,47 @@ class VideoDecodePlugin { // 监听器初始化标志 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 const int _ERROR_LOG_THRESHOLD = 5; // 每5秒最多输出一次汇总 + + /// 日志输出控制 - 调试信息 + static void _logDebug(String message) { + if (_isDebugMode) { + debugPrint('[VideoDecodePlugin] $message'); + } + } + + /// 日志输出控制 - 错误信息(总是输出) + 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 void _initializeMethodCallHandler() { if (!_listenerInitialized) { @@ -111,10 +152,38 @@ class VideoDecodePlugin { 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) { - decoder.frameCallback!(textureId); + // 获取解码器统计信息来检查是否是预通知 + getDecoderStats(textureId).then((stats) { + final renderedFrames = stats['renderedFrames'] ?? 0; + if (renderedFrames == 0) { + _logDebug('[预通知] 收到初始帧可用通知(无实际视频数据),纹理ID: $textureId'); + } else { + _logDebug('收到帧可用通知,纹理ID: $textureId,已渲染帧数: $renderedFrames'); + } + + // 调用回调函数 + decoder.frameCallback!(textureId); + }).catchError((error) { + // 如果无法获取统计信息,仍然调用回调但不区分类型 + _logDebug('收到帧可用通知,纹理ID: $textureId'); + decoder.frameCallback!(textureId); + }); } return null; @@ -130,6 +199,39 @@ class VideoDecodePlugin { } } + /// 执行同步操作的辅助方法 + 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 getPlatformVersion() { return VideoDecodePluginPlatform.instance.getPlatformVersion(); @@ -160,6 +262,12 @@ class VideoDecodePlugin { /// 初始化解码器 static Future initDecoder(VideoDecoderConfig config) async { + // 设置调试模式 + _isDebugMode = config.isDebug; + + // 重置错误计数 + _uninitializedErrorCount = 0; + // 先释放之前的默认解码器 if (_defaultTextureId != null) { await releaseDecoder(); @@ -170,8 +278,14 @@ class VideoDecodePlugin { /// 创建新的解码器实例(支持多实例) static Future createDecoder(VideoDecoderConfig config) async { + // 更新调试模式 + _isDebugMode = config.isDebug; + + // 重置错误计数 + _uninitializedErrorCount = 0; + if (!isPlatformSupported) { - debugPrint('当前平台不支持视频解码插件'); + _logError('当前平台不支持视频解码插件'); return null; } @@ -179,6 +293,8 @@ class VideoDecodePlugin { _initializeMethodCallHandler(); try { + _logDebug( + '创建解码器: ${config.width}x${config.height}, 编码: ${config.codecType}'); final textureId = await _channel.invokeMethod('initDecoder', config.toMap()); @@ -187,13 +303,17 @@ class VideoDecodePlugin { final decoder = _DecoderInstance(textureId); _decoders[textureId] = decoder; + // 初始化解码器状态 + _setDecoderReleasing(textureId, false); + // 设置为默认解码器 _defaultTextureId = textureId; + _logDebug('解码器创建成功,纹理ID: $textureId'); } return _defaultTextureId; } catch (e) { - debugPrint('初始化解码器失败: $e'); + _logError('初始化解码器失败: $e'); return null; } } @@ -207,42 +327,69 @@ class VideoDecodePlugin { /// 解码视频帧(默认解码器) static Future decodeFrame( Uint8List frameData, FrameType frameType) async { - if (_defaultTextureId == null) { - debugPrint('解码器未初始化'); + // 使用本地变量缓存ID,防止并发修改 + final int? decoderId = _defaultTextureId; + + if (decoderId == null) { + // 使用节流日志报告错误,避免日志爆炸 + _logError('解码器未初始化', throttle: true); return false; } - return decodeFrameForTexture(_defaultTextureId!, frameData, frameType); + // 检查解码器是否正在释放 + if (!_isDecoderReady(decoderId)) { + _logDebug('解码器正在释放,忽略解码请求'); + return false; + } + + return decodeFrameForTexture(decoderId, frameData, frameType); } /// 为特定纹理ID解码视频帧 static Future decodeFrameForTexture( int textureId, Uint8List frameData, FrameType frameType) async { - if (!_decoders.containsKey(textureId)) { - debugPrint('找不到纹理ID: $textureId'); + // 检查解码器是否存在且不在释放过程中 + if (!_isDecoderReady(textureId)) { + _logDebug('解码器不可用或正在释放,忽略解码请求'); return false; } try { - return await _channel.invokeMethod('decodeFrame', { + 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) { - debugPrint('解码帧失败: $e'); + // 检查是否是因为解码器已释放导致的错误 + if (!_decoders.containsKey(textureId)) { + _logDebug('解码器已释放,忽略解码错误'); + return false; + } + _logError('解码帧失败: $e'); return false; } } /// 释放默认解码器资源 static Future releaseDecoder() async { - if (_defaultTextureId == null) { + final int? decoderId = _defaultTextureId; + if (decoderId == null) { return true; } - final result = await releaseDecoderForTexture(_defaultTextureId!); + final result = await releaseDecoderForTexture(decoderId); if (result) { _defaultTextureId = null; } @@ -252,11 +399,20 @@ class VideoDecodePlugin { /// 释放特定纹理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, }) ?? @@ -270,11 +426,30 @@ class VideoDecodePlugin { if (_defaultTextureId == textureId) { _defaultTextureId = null; } + + // 移除释放状态 + _setDecoderReleasing(textureId, false); + + // 重置错误计数 + _uninitializedErrorCount = 0; + + _logDebug('解码器释放成功: textureId=$textureId'); + } else { + // 释放失败,恢复状态 + _setDecoderReleasing(textureId, false); + _logError('解码器释放失败: textureId=$textureId'); } return result; } catch (e) { - debugPrint('释放解码器失败: $e'); + // 发生异常,但仍然移除解码器,避免资源泄漏 + _decoders.remove(textureId); + if (_defaultTextureId == textureId) { + _defaultTextureId = null; + } + _setDecoderReleasing(textureId, false); + + _logError('释放解码器失败: $e'); return false; } } @@ -286,6 +461,8 @@ class VideoDecodePlugin { // 复制键列表,因为我们会在迭代过程中修改映射 final textureIds = List.from(_decoders.keys); + _logDebug('释放所有解码器: 共${textureIds.length}个'); + // 释放每个解码器 for (final textureId in textureIds) { final success = await releaseDecoderForTexture(textureId); @@ -298,6 +475,14 @@ class VideoDecodePlugin { _decoders.clear(); _defaultTextureId = null; + // 清空所有释放状态 + _withLock(_decoderStateLock, () { + _isDecoderReleasing.clear(); + }); + + // 重置错误计数 + _uninitializedErrorCount = 0; + return allSuccess; } @@ -306,6 +491,7 @@ class VideoDecodePlugin { final decoder = _decoders[textureId]; if (decoder != null) { decoder.frameCallback = null; + _logDebug('已清除纹理ID为$textureId的回调'); } } @@ -314,6 +500,7 @@ class VideoDecodePlugin { for (final decoder in _decoders.values) { decoder.frameCallback = null; } + _logDebug('已清除所有回调'); } /// 注册插件(不需要手动调用) @@ -332,7 +519,14 @@ class VideoDecodePlugin { /// - averageProcessingTimeMs: 平均处理时间(毫秒) /// - decoderCount: 当前活跃的解码器数量 static Future> getDecoderStats(int textureId) async { + // 检查解码器是否正在释放 + if (!_isDecoderReady(textureId)) { + _logDebug('解码器不可用或正在释放,无法获取统计信息'); + return {}; + } + try { + _logDebug('获取解码器统计信息: textureId=$textureId'); final params = { 'textureId': textureId, }; @@ -351,12 +545,23 @@ class VideoDecodePlugin { } }); + _logDebug('获取解码器统计信息成功: $typedResult'); return typedResult; } catch (e) { - if (kDebugMode) { - print('获取解码器统计信息失败: $e'); - } + _logError('获取解码器统计信息失败: $e'); return {}; } } } + +/// 在Dart中实现简单的同步锁 +void synchronized(Object lock, Function() action) { + // 在单线程的Dart中,我们不需要真正的锁 + // 但我们保留这个结构以便将来可能的改进 + action(); +} + +/// 在同步锁中执行并返回结果的版本 +T synchronizedWithResult(Object lock, T Function() action) { + return action(); +}