818 lines
24 KiB
Dart
818 lines
24 KiB
Dart
import 'dart:async';
|
||
import 'dart:io';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter/services.dart';
|
||
|
||
import 'video_decode_plugin_platform_interface.dart';
|
||
|
||
/// H.265/HEVC NAL单元类型定义
|
||
class HevcNalUnitType {
|
||
static const int TRAIL_N = 0; // 尾随图片 - 非参考图片
|
||
static const int TRAIL_R = 1; // 尾随图片 - 参考图片
|
||
|
||
static const int TSA_N = 2; // 时间子层访问 - 非参考图片
|
||
static const int TSA_R = 3; // 时间子层访问 - 参考图片
|
||
|
||
static const int STSA_N = 4; // 分步时间子层访问 - 非参考图片
|
||
static const int STSA_R = 5; // 分步时间子层访问 - 参考图片
|
||
|
||
static const int RADL_N = 6; // 随机访问解码先导 - 非参考图片
|
||
static const int RADL_R = 7; // 随机访问解码先导 - 参考图片
|
||
|
||
static const int RASL_N = 8; // 随机访问跳过先导 - 非参考图片
|
||
static const int RASL_R = 9; // 随机访问跳过先导 - 参考图片
|
||
|
||
static const int RSV_VCL_N10 = 10; // 保留的非IRAP VCL NAL单元类型
|
||
static const int RSV_VCL_R11 = 11; // 保留的非IRAP VCL NAL单元类型
|
||
static const int RSV_VCL_N12 = 12; // 保留的非IRAP VCL NAL单元类型
|
||
static const int RSV_VCL_R13 = 13; // 保留的非IRAP VCL NAL单元类型
|
||
static const int RSV_VCL_N14 = 14; // 保留的非IRAP VCL NAL单元类型
|
||
static const int RSV_VCL_R15 = 15; // 保留的非IRAP VCL NAL单元类型
|
||
|
||
static const int BLA_W_LP = 16; // 有前导的无损拼接访问
|
||
static const int BLA_W_RADL = 17; // 有RADL的无损拼接访问
|
||
static const int BLA_N_LP = 18; // 无前导的无损拼接访问
|
||
|
||
static const int IDR_W_RADL = 19; // 有RADL的瞬时解码刷新 (IDR)
|
||
static const int IDR_N_LP = 20; // 无前导的瞬时解码刷新 (IDR)
|
||
|
||
static const int CRA_NUT = 21; // 清理随机访问
|
||
|
||
static const int RSV_IRAP_VCL22 = 22; // 保留的IRAP VCL NAL单元类型
|
||
static const int RSV_IRAP_VCL23 = 23; // 保留的IRAP VCL NAL单元类型
|
||
|
||
static const int RSV_VCL24 = 24; // 保留的VCL NAL单元类型
|
||
static const int RSV_VCL25 = 25; // 保留的VCL NAL单元类型
|
||
static const int RSV_VCL26 = 26; // 保留的VCL NAL单元类型
|
||
static const int RSV_VCL27 = 27; // 保留的VCL NAL单元类型
|
||
static const int RSV_VCL28 = 28; // 保留的VCL NAL单元类型
|
||
static const int RSV_VCL29 = 29; // 保留的VCL NAL单元类型
|
||
static const int RSV_VCL30 = 30; // 保留的VCL NAL单元类型
|
||
static const int RSV_VCL31 = 31; // 保留的VCL NAL单元类型
|
||
|
||
// 非VCL NAL单元类型
|
||
static const int VPS = 32; // 视频参数集
|
||
static const int SPS = 33; // 序列参数集
|
||
static const int PPS = 34; // 图像参数集
|
||
static const int AUD = 35; // 访问单元分隔符
|
||
static const int EOS = 36; // 序列结束
|
||
static const int EOB = 37; // 比特流结束
|
||
static const int FD = 38; // 填充数据
|
||
|
||
static const int PREFIX_SEI = 39; // 前缀辅助增强信息
|
||
static const int SUFFIX_SEI = 40; // 后缀辅助增强信息
|
||
|
||
static const int RSV_NVCL41 = 41; // 保留的非VCL NAL单元类型
|
||
static const int RSV_NVCL42 = 42; // 保留的非VCL NAL单元类型
|
||
static const int RSV_NVCL43 = 43; // 保留的非VCL NAL单元类型
|
||
static const int RSV_NVCL44 = 44; // 保留的非VCL NAL单元类型
|
||
static const int RSV_NVCL45 = 45; // 保留的非VCL NAL单元类型
|
||
static const int RSV_NVCL46 = 46; // 保留的非VCL NAL单元类型
|
||
static const int RSV_NVCL47 = 47; // 保留的非VCL NAL单元类型
|
||
|
||
static const int UNSPEC48 = 48; // 未指定的类型
|
||
static const int UNSPEC49 = 49; // 未指定的类型
|
||
static const int UNSPEC50 = 50; // 未指定的类型
|
||
static const int UNSPEC51 = 51; // 未指定的类型
|
||
static const int UNSPEC52 = 52; // 未指定的类型
|
||
static const int UNSPEC53 = 53; // 未指定的类型
|
||
static const int UNSPEC54 = 54; // 未指定的类型
|
||
static const int UNSPEC55 = 55; // 未指定的类型
|
||
static const int UNSPEC56 = 56; // 未指定的类型
|
||
static const int UNSPEC57 = 57; // 未指定的类型
|
||
static const int UNSPEC58 = 58; // 未指定的类型
|
||
static const int UNSPEC59 = 59; // 未指定的类型
|
||
static const int UNSPEC60 = 60; // 未指定的类型
|
||
static const int UNSPEC61 = 61; // 未指定的类型
|
||
static const int UNSPEC62 = 62; // 未指定的类型
|
||
static const int UNSPEC63 = 63; // 未指定的类型
|
||
|
||
// 帧类型别名,方便判断
|
||
// I帧类型:IDR_W_RADL, IDR_N_LP, BLA_W_LP, BLA_W_RADL, BLA_N_LP, CRA_NUT
|
||
static const List<int> KEY_FRAMES = [
|
||
IDR_W_RADL,
|
||
IDR_N_LP,
|
||
BLA_W_LP,
|
||
BLA_W_RADL,
|
||
BLA_N_LP,
|
||
CRA_NUT
|
||
];
|
||
|
||
// 参数集类型:VPS, SPS, PPS
|
||
static const List<int> PARAMETER_SETS = [VPS, SPS, PPS];
|
||
|
||
/// 判断是否为关键帧NAL类型
|
||
static bool isKeyFrame(int nalUnitType) {
|
||
return KEY_FRAMES.contains(nalUnitType);
|
||
}
|
||
|
||
/// 判断是否为参数集NAL类型
|
||
static bool isParameterSet(int nalUnitType) {
|
||
return PARAMETER_SETS.contains(nalUnitType);
|
||
}
|
||
|
||
/// 判断是否为IDR帧
|
||
static bool isIdrFrame(int nalUnitType) {
|
||
return nalUnitType == IDR_W_RADL || nalUnitType == IDR_N_LP;
|
||
}
|
||
|
||
/// 获取NAL单元类型名称
|
||
static String getName(int type) {
|
||
switch (type) {
|
||
case TRAIL_N:
|
||
return "TRAIL_N";
|
||
case TRAIL_R:
|
||
return "TRAIL_R";
|
||
case TSA_N:
|
||
return "TSA_N";
|
||
case TSA_R:
|
||
return "TSA_R";
|
||
case STSA_N:
|
||
return "STSA_N";
|
||
case STSA_R:
|
||
return "STSA_R";
|
||
case RADL_N:
|
||
return "RADL_N";
|
||
case RADL_R:
|
||
return "RADL_R";
|
||
case RASL_N:
|
||
return "RASL_N";
|
||
case RASL_R:
|
||
return "RASL_R";
|
||
case BLA_W_LP:
|
||
return "BLA_W_LP";
|
||
case BLA_W_RADL:
|
||
return "BLA_W_RADL";
|
||
case BLA_N_LP:
|
||
return "BLA_N_LP";
|
||
case IDR_W_RADL:
|
||
return "IDR_W_RADL";
|
||
case IDR_N_LP:
|
||
return "IDR_N_LP";
|
||
case CRA_NUT:
|
||
return "CRA_NUT";
|
||
case VPS:
|
||
return "VPS";
|
||
case SPS:
|
||
return "SPS";
|
||
case PPS:
|
||
return "PPS";
|
||
case AUD:
|
||
return "AUD";
|
||
case EOS:
|
||
return "EOS";
|
||
case EOB:
|
||
return "EOB";
|
||
case FD:
|
||
return "FD";
|
||
case PREFIX_SEI:
|
||
return "PREFIX_SEI";
|
||
case SUFFIX_SEI:
|
||
return "SUFFIX_SEI";
|
||
default:
|
||
if (type >= 10 && type <= 15) return "RSV_VCL_${type}";
|
||
if (type >= 22 && type <= 23) return "RSV_IRAP_VCL${type}";
|
||
if (type >= 24 && type <= 31) return "RSV_VCL${type}";
|
||
if (type >= 41 && type <= 47) return "RSV_NVCL${type}";
|
||
if (type >= 48 && type <= 63) return "UNSPEC${type}";
|
||
return "未知(${type})";
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 视频帧类型
|
||
enum FrameType {
|
||
/// I帧
|
||
iFrame,
|
||
|
||
/// P帧
|
||
pFrame,
|
||
}
|
||
|
||
/// 视频编码类型
|
||
enum CodecType {
|
||
/// H.264编码
|
||
h264,
|
||
|
||
/// H.265编码
|
||
h265,
|
||
}
|
||
|
||
/// 帧可用回调函数类型
|
||
typedef FrameAvailableCallback = void Function(int textureId);
|
||
|
||
/// 解码器实例内部类
|
||
class _DecoderInstance {
|
||
final int textureId;
|
||
FrameAvailableCallback? frameCallback;
|
||
|
||
_DecoderInstance(this.textureId);
|
||
}
|
||
|
||
/// 视频解码器配置
|
||
class VideoDecoderConfig {
|
||
/// 视频宽度,默认640
|
||
final int width;
|
||
|
||
/// 视频高度,默认360
|
||
final int height;
|
||
|
||
/// 帧率,可为空
|
||
final int? frameRate;
|
||
|
||
/// 编码类型,默认h264
|
||
final CodecType codecType;
|
||
|
||
/// 缓冲区大小(帧数),默认25帧
|
||
final int bufferSize;
|
||
|
||
/// 解码线程数,默认1线程
|
||
final int threadCount;
|
||
|
||
/// 是否为调试模式,默认false
|
||
final bool isDebug;
|
||
|
||
/// 是否启用硬件解码,默认true
|
||
final bool enableHardwareDecoder;
|
||
|
||
/// 是否启用动态阈值,默认true
|
||
final bool enableDynamicThresholds;
|
||
|
||
/// 初始最大连续P帧数,默认10
|
||
final int initialMaxPFrames;
|
||
|
||
/// 初始I帧超时时间(毫秒),默认500
|
||
final int initialIFrameTimeoutMs;
|
||
|
||
/// 最小最大连续P帧数,默认5
|
||
final int minMaxPFrames;
|
||
|
||
/// 最大最大连续P帧数,默认30
|
||
final int maxMaxPFrames;
|
||
|
||
/// 构造函数
|
||
VideoDecoderConfig({
|
||
this.width = 640,
|
||
this.height = 360,
|
||
this.frameRate,
|
||
this.codecType = CodecType.h264,
|
||
this.bufferSize = 25,
|
||
this.threadCount = 1,
|
||
this.isDebug = false,
|
||
this.enableHardwareDecoder = true,
|
||
this.enableDynamicThresholds = true,
|
||
this.initialMaxPFrames = 10,
|
||
this.initialIFrameTimeoutMs = 500,
|
||
this.minMaxPFrames = 5,
|
||
this.maxMaxPFrames = 30,
|
||
});
|
||
|
||
/// 转换为Map
|
||
Map<String, dynamic> toMap() {
|
||
return {
|
||
'width': width,
|
||
'height': height,
|
||
'frameRate': frameRate,
|
||
'codecType': codecType.toString().split('.').last,
|
||
'bufferSize': bufferSize,
|
||
'threadCount': threadCount,
|
||
'isDebug': isDebug,
|
||
'enableHardwareDecoder': enableHardwareDecoder,
|
||
'enableDynamicThresholds': enableDynamicThresholds,
|
||
'initialMaxPFrames': initialMaxPFrames,
|
||
'initialIFrameTimeoutMs': initialIFrameTimeoutMs,
|
||
'minMaxPFrames': minMaxPFrames,
|
||
'maxMaxPFrames': maxMaxPFrames,
|
||
};
|
||
}
|
||
}
|
||
|
||
/// 视频解码插件主类
|
||
class VideoDecodePlugin {
|
||
static const MethodChannel _channel = MethodChannel('video_decode_plugin');
|
||
|
||
// 解码器映射表,支持多实例
|
||
static final Map<int, _DecoderInstance> _decoders = {};
|
||
|
||
// 默认解码器ID
|
||
static int? _defaultTextureId;
|
||
|
||
// 监听器初始化标志
|
||
static bool _listenerInitialized = false;
|
||
|
||
// 是否处于调试模式
|
||
static bool _isDebugMode = false;
|
||
|
||
// 解码器状态跟踪 - 防止释放后继续使用
|
||
static final Map<int, bool> _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) {
|
||
_channel.setMethodCallHandler((call) async {
|
||
switch (call.method) {
|
||
case 'onFrameAvailable':
|
||
final Map<dynamic, dynamic> 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) {
|
||
// 获取解码器统计信息来检查是否是预通知
|
||
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;
|
||
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<T>(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<String?> getPlatformVersion() {
|
||
return VideoDecodePluginPlatform.instance.getPlatformVersion();
|
||
}
|
||
|
||
/// 检查当前平台是否支持
|
||
static bool get isPlatformSupported {
|
||
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 Future<int?> initDecoder(VideoDecoderConfig config) async {
|
||
// 设置调试模式
|
||
_isDebugMode = config.isDebug;
|
||
|
||
// 重置错误计数
|
||
_uninitializedErrorCount = 0;
|
||
|
||
// 先释放之前的默认解码器
|
||
if (_defaultTextureId != null) {
|
||
await releaseDecoder();
|
||
}
|
||
|
||
return await createDecoder(config);
|
||
}
|
||
|
||
/// 创建新的解码器实例(支持多实例)
|
||
static Future<int?> 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<int>('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<int> get allTextureIds => _decoders.keys.toList();
|
||
|
||
/// 解码视频帧(默认解码器)
|
||
static Future<bool> 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<bool> 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<bool>('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<bool> 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<bool> releaseDecoderForTexture(int textureId) async {
|
||
// 检查解码器是否存在
|
||
if (!_decoders.containsKey(textureId)) {
|
||
return true;
|
||
}
|
||
|
||
// 标记解码器正在释放,防止新的解码请求
|
||
_setDecoderReleasing(textureId, true);
|
||
|
||
try {
|
||
_logDebug('释放解码器: textureId=$textureId');
|
||
|
||
// 清除回调,防止帧回调继续被调用
|
||
clearCallbackForTexture(textureId);
|
||
|
||
final result = await _channel.invokeMethod<bool>('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<bool> releaseAllDecoders() async {
|
||
bool allSuccess = true;
|
||
|
||
// 复制键列表,因为我们会在迭代过程中修改映射
|
||
final textureIds = List<int>.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;
|
||
_logDebug('已清除纹理ID为$textureId的回调');
|
||
}
|
||
}
|
||
|
||
/// 清除所有回调
|
||
static void clearAllCallbacks() {
|
||
for (final decoder in _decoders.values) {
|
||
decoder.frameCallback = null;
|
||
}
|
||
_logDebug('已清除所有回调');
|
||
}
|
||
|
||
/// 注册插件(不需要手动调用)
|
||
static void registerWith() {
|
||
// 仅用于插件注册
|
||
}
|
||
|
||
/// 获取解码器统计信息
|
||
///
|
||
/// [textureId] 纹理ID
|
||
/// 返回包含统计信息的Map,包括:
|
||
/// - totalFramesReceived: 接收的总帧数
|
||
/// - framesRendered: 成功渲染的帧数
|
||
/// - framesDropped: 丢弃的帧数
|
||
/// - lastFrameTimestamp: 最后一帧时间戳
|
||
/// - averageProcessingTimeMs: 平均处理时间(毫秒)
|
||
/// - decoderCount: 当前活跃的解码器数量
|
||
static Future<Map<String, dynamic>> getDecoderStats(int textureId) async {
|
||
// 检查解码器是否正在释放
|
||
if (!_isDecoderReady(textureId)) {
|
||
_logDebug('解码器不可用或正在释放,无法获取统计信息');
|
||
return {};
|
||
}
|
||
|
||
try {
|
||
_logDebug('获取解码器统计信息: textureId=$textureId');
|
||
final params = {
|
||
'textureId': textureId,
|
||
};
|
||
|
||
final result = await _channel.invokeMethod<Map<Object?, Object?>>(
|
||
'getDecoderStats', params);
|
||
if (result == null) {
|
||
return {};
|
||
}
|
||
|
||
// 将Object?类型转换为明确的类型
|
||
final Map<String, dynamic> typedResult = {};
|
||
result.forEach((key, value) {
|
||
if (key is String) {
|
||
typedResult[key] = value;
|
||
}
|
||
});
|
||
|
||
_logDebug('获取解码器统计信息成功: $typedResult');
|
||
return typedResult;
|
||
} catch (e) {
|
||
_logError('获取解码器统计信息失败: $e');
|
||
return {};
|
||
}
|
||
}
|
||
|
||
/// 获取当前渲染FPS
|
||
///
|
||
/// 返回当前解码器的实时渲染帧率
|
||
/// 如果解码器未初始化或获取失败,返回0.0
|
||
static Future<double> getCurrentFps([int? textureId]) async {
|
||
final targetTextureId = textureId ?? _defaultTextureId;
|
||
if (targetTextureId == null) {
|
||
return 0.0;
|
||
}
|
||
|
||
try {
|
||
final stats = await getDecoderStats(targetTextureId);
|
||
return stats['fps'] as double? ?? 0.0;
|
||
} catch (e) {
|
||
_logError('获取FPS失败: $e');
|
||
return 0.0;
|
||
}
|
||
}
|
||
|
||
/// 获取动态阈值参数
|
||
///
|
||
/// 返回当前解码器使用的动态阈值参数
|
||
/// 包括检测到的GOP大小、最大连续P帧数限制、I帧超时时间等
|
||
static Future<Map<String, dynamic>> getDynamicThresholdParams(
|
||
[int? textureId]) async {
|
||
final targetTextureId = textureId ?? _defaultTextureId;
|
||
if (targetTextureId == null) {
|
||
return {};
|
||
}
|
||
|
||
try {
|
||
final stats = await getDecoderStats(targetTextureId);
|
||
return {
|
||
'detectedGopSize': stats['detectedGopSize'] as int? ?? 0,
|
||
'dynamicMaxConsecutivePFrames':
|
||
stats['dynamicMaxConsecutivePFrames'] as int? ?? 0,
|
||
'dynamicIFrameTimeoutMs': stats['dynamicIFrameTimeoutMs'] as int? ?? 0,
|
||
'enableDynamicThresholds':
|
||
stats['enableDynamicThresholds'] as bool? ?? false,
|
||
};
|
||
} catch (e) {
|
||
_logError('获取动态阈值参数失败: $e');
|
||
return {};
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 在Dart中实现简单的同步锁
|
||
void synchronized(Object lock, Function() action) {
|
||
// 在单线程的Dart中,我们不需要真正的锁
|
||
// 但我们保留这个结构以便将来可能的改进
|
||
action();
|
||
}
|
||
|
||
/// 在同步锁中执行并返回结果的版本
|
||
T synchronizedWithResult<T>(Object lock, T Function() action) {
|
||
return action();
|
||
}
|
||
|
||
|
||
|
||
|