363 lines
8.9 KiB
Dart
363 lines
8.9 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';
|
|||
|
|
|
|||
|
|
/// 视频帧类型
|
|||
|
|
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;
|
|||
|
|
|
|||
|
|
/// 构造函数
|
|||
|
|
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,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 转换为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,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 视频解码插件主类
|
|||
|
|
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 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'];
|
|||
|
|
|
|||
|
|
// 调用特定纹理ID的帧回调
|
|||
|
|
final decoder = _decoders[textureId];
|
|||
|
|
if (decoder != null && decoder.frameCallback != null) {
|
|||
|
|
decoder.frameCallback!(textureId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
default:
|
|||
|
|
throw PlatformException(
|
|||
|
|
code: 'Unimplemented',
|
|||
|
|
details: 'The method ${call.method} is not implemented',
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
_listenerInitialized = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取平台版本
|
|||
|
|
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 {
|
|||
|
|
// 先释放之前的默认解码器
|
|||
|
|
if (_defaultTextureId != null) {
|
|||
|
|
await releaseDecoder();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await createDecoder(config);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 创建新的解码器实例(支持多实例)
|
|||
|
|
static Future<int?> createDecoder(VideoDecoderConfig config) async {
|
|||
|
|
if (!isPlatformSupported) {
|
|||
|
|
debugPrint('当前平台不支持视频解码插件');
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保监听器已初始化
|
|||
|
|
_initializeMethodCallHandler();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
final textureId =
|
|||
|
|
await _channel.invokeMethod<int>('initDecoder', config.toMap());
|
|||
|
|
|
|||
|
|
if (textureId != null) {
|
|||
|
|
// 创建新解码器实例并保存
|
|||
|
|
final decoder = _DecoderInstance(textureId);
|
|||
|
|
_decoders[textureId] = decoder;
|
|||
|
|
|
|||
|
|
// 设置为默认解码器
|
|||
|
|
_defaultTextureId = textureId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return _defaultTextureId;
|
|||
|
|
} catch (e) {
|
|||
|
|
debugPrint('初始化解码器失败: $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 {
|
|||
|
|
if (_defaultTextureId == null) {
|
|||
|
|
debugPrint('解码器未初始化');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return decodeFrameForTexture(_defaultTextureId!, frameData, frameType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 为特定纹理ID解码视频帧
|
|||
|
|
static Future<bool> decodeFrameForTexture(
|
|||
|
|
int textureId, Uint8List frameData, FrameType frameType) async {
|
|||
|
|
if (!_decoders.containsKey(textureId)) {
|
|||
|
|
debugPrint('找不到纹理ID: $textureId');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
return await _channel.invokeMethod<bool>('decodeFrame', {
|
|||
|
|
'textureId': textureId,
|
|||
|
|
'frameData': frameData,
|
|||
|
|
'frameType': frameType.index,
|
|||
|
|
}) ??
|
|||
|
|
false;
|
|||
|
|
} catch (e) {
|
|||
|
|
debugPrint('解码帧失败: $e');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 释放默认解码器资源
|
|||
|
|
static Future<bool> releaseDecoder() async {
|
|||
|
|
if (_defaultTextureId == null) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final result = await releaseDecoderForTexture(_defaultTextureId!);
|
|||
|
|
if (result) {
|
|||
|
|
_defaultTextureId = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 释放特定纹理ID的解码器资源
|
|||
|
|
static Future<bool> releaseDecoderForTexture(int textureId) async {
|
|||
|
|
if (!_decoders.containsKey(textureId)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
final result = await _channel.invokeMethod<bool>('releaseDecoder', {
|
|||
|
|
'textureId': textureId,
|
|||
|
|
}) ??
|
|||
|
|
false;
|
|||
|
|
|
|||
|
|
if (result) {
|
|||
|
|
// 从映射表中移除
|
|||
|
|
_decoders.remove(textureId);
|
|||
|
|
|
|||
|
|
// 如果释放的是默认解码器,重置默认ID
|
|||
|
|
if (_defaultTextureId == textureId) {
|
|||
|
|
_defaultTextureId = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
} catch (e) {
|
|||
|
|
debugPrint('释放解码器失败: $e');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 释放所有解码器
|
|||
|
|
static Future<bool> releaseAllDecoders() async {
|
|||
|
|
bool allSuccess = true;
|
|||
|
|
|
|||
|
|
// 复制键列表,因为我们会在迭代过程中修改映射
|
|||
|
|
final textureIds = List<int>.from(_decoders.keys);
|
|||
|
|
|
|||
|
|
// 释放每个解码器
|
|||
|
|
for (final textureId in textureIds) {
|
|||
|
|
final success = await releaseDecoderForTexture(textureId);
|
|||
|
|
if (!success) {
|
|||
|
|
allSuccess = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清空状态
|
|||
|
|
_decoders.clear();
|
|||
|
|
_defaultTextureId = null;
|
|||
|
|
|
|||
|
|
return allSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 清除特定纹理ID的回调
|
|||
|
|
static void clearCallbackForTexture(int textureId) {
|
|||
|
|
final decoder = _decoders[textureId];
|
|||
|
|
if (decoder != null) {
|
|||
|
|
decoder.frameCallback = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 清除所有回调
|
|||
|
|
static void clearAllCallbacks() {
|
|||
|
|
for (final decoder in _decoders.values) {
|
|||
|
|
decoder.frameCallback = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 注册插件(不需要手动调用)
|
|||
|
|
static void registerWith() {
|
|||
|
|
// 仅用于插件注册
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取解码器统计信息
|
|||
|
|
///
|
|||
|
|
/// [textureId] 纹理ID
|
|||
|
|
/// 返回包含统计信息的Map,包括:
|
|||
|
|
/// - totalFramesReceived: 接收的总帧数
|
|||
|
|
/// - framesRendered: 成功渲染的帧数
|
|||
|
|
/// - framesDropped: 丢弃的帧数
|
|||
|
|
/// - lastFrameTimestamp: 最后一帧时间戳
|
|||
|
|
/// - averageProcessingTimeMs: 平均处理时间(毫秒)
|
|||
|
|
/// - decoderCount: 当前活跃的解码器数量
|
|||
|
|
static Future<Map<String, dynamic>> getDecoderStats(int textureId) async {
|
|||
|
|
try {
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return typedResult;
|
|||
|
|
} catch (e) {
|
|||
|
|
if (kDebugMode) {
|
|||
|
|
print('获取解码器统计信息失败: $e');
|
|||
|
|
}
|
|||
|
|
return {};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|