video_decode_plugin/lib/video_decode_plugin.dart

225 lines
6.4 KiB
Dart
Raw Normal View History

2025-04-21 10:56:28 +08:00
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';
2025-04-30 16:12:22 +08:00
import 'nalu_utils.dart';
import 'frame_dependency_manager.dart';
2025-04-21 10:56:28 +08:00
/// 视频解码器配置
class VideoDecoderConfig {
2025-04-29 17:11:38 +08:00
/// 视频宽度
2025-04-21 10:56:28 +08:00
final int width;
2025-04-29 17:11:38 +08:00
/// 视频高度
2025-04-21 10:56:28 +08:00
final int height;
/// 编码类型默认h264
2025-04-29 17:11:38 +08:00
final String codecType;
2025-04-21 10:56:28 +08:00
/// 构造函数
VideoDecoderConfig({
2025-04-29 17:11:38 +08:00
required this.width,
required this.height,
this.codecType = 'h264',
2025-04-21 10:56:28 +08:00
});
/// 转换为Map
Map<String, dynamic> toMap() {
return {
'width': width,
'height': height,
2025-04-29 17:11:38 +08:00
'codecType': codecType,
2025-04-21 10:56:28 +08:00
};
}
}
/// 视频解码插件主类
class VideoDecodePlugin {
static const MethodChannel _channel = MethodChannel('video_decode_plugin');
2025-04-29 17:11:38 +08:00
static int? _textureId;
2025-04-21 16:08:23 +08:00
2025-04-30 16:12:22 +08:00
/// onFrameRendered回调类型解码并已开始渲染
static void Function(int textureId)? _onFrameRendered;
/// 设置onFrameRendered监听
static void setOnFrameRenderedListener(
void Function(int textureId) callback) {
_onFrameRendered = callback;
_channel.setMethodCallHandler(_handleMethodCall);
}
static Future<void> _handleMethodCall(MethodCall call) async {
if (call.method == 'onFrameRendered') {
final int? textureId = call.arguments['textureId'];
if (_onFrameRendered != null && textureId != null) {
_onFrameRendered!(textureId);
}
}
}
2025-04-29 17:11:38 +08:00
/// 初始化解码器
static Future<int?> initDecoder(VideoDecoderConfig config) async {
2025-04-30 16:12:22 +08:00
final textureId =
await _channel.invokeMethod<int>('initDecoder', config.toMap());
2025-04-29 17:11:38 +08:00
_textureId = textureId;
return textureId;
}
2025-04-30 16:12:22 +08:00
/// 解码视频帧(参数扩展,仅供内部调用)
static Future<bool> _decodeFrame({
2025-04-29 17:11:38 +08:00
required Uint8List frameData,
required int frameType, // 0=I帧, 1=P帧
required int timestamp, // 毫秒或微秒
2025-04-30 16:12:22 +08:00
required int frameSeq, // 帧序号
int? refIFrameSeq, // P帧时可选
2025-04-29 17:11:38 +08:00
}) 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<bool>('decodeFrame', params);
return result ?? false;
2025-04-21 16:08:23 +08:00
}
2025-04-29 17:11:38 +08:00
/// 释放解码器资源
static Future<bool> releaseDecoder() async {
if (_textureId == null) return true;
final result = await _channel.invokeMethod<bool>('releaseDecoder', {
'textureId': _textureId,
2025-04-21 16:08:23 +08:00
});
2025-04-29 17:11:38 +08:00
_textureId = null;
return result ?? false;
2025-04-21 16:08:23 +08:00
}
2025-04-21 10:56:28 +08:00
/// 获取平台版本
static Future<String?> getPlatformVersion() {
return VideoDecodePluginPlatform.instance.getPlatformVersion();
}
/// 检查当前平台是否支持
static bool get isPlatformSupported {
return Platform.isAndroid || Platform.isIOS;
}
/// 获取默认纹理ID
2025-04-29 17:11:38 +08:00
static int? get textureId => _textureId;
2025-04-21 10:56:28 +08:00
2025-04-30 16:12:22 +08:00
static final _depManager = FrameDependencyManager();
///
/// [frameData]:帧数据
/// [frameType]:帧类型 0=I帧, 1=P帧
/// [timestamp]:帧时间戳(绝对时间戳)
/// [frameSeq]:帧序号
/// [splitNalFromIFrame]true时遇到I帧自动从I帧分割NALU并依赖管理false时直接发送原始数据适配SPS/PPS/I帧独立推送场景
///
static Future<void> sendFrame({
required List<int> frameData,
required int frameType,
required int timestamp,
required int frameSeq,
bool splitNalFromIFrame = false,
2025-04-30 16:31:25 +08:00
int? refIFrameSeq, // P帧时可选
2025-04-30 16:12:22 +08:00
}) async {
if (splitNalFromIFrame && frameType == 0) {
// 优先使用缓存的SPS/PPS
if (_depManager.sps != null && _depManager.pps != null) {
await _decodeFrame(
frameData: _depManager.sps!,
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq - 2,
refIFrameSeq: frameSeq - 2,
);
await _decodeFrame(
frameData: _depManager.pps!,
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq - 1,
refIFrameSeq: frameSeq - 1,
);
await _decodeFrame(
frameData: Uint8List.fromList(frameData),
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: frameSeq,
);
_depManager.updateIFrameSeq(frameSeq);
return;
}
// 首次无缓存时分割并缓存SPS/PPS
final nalus = NaluUtils.splitNalus(frameData);
2025-04-30 16:56:30 +08:00
2025-04-30 16:12:22 +08:00
List<int>? sps, pps;
for (final nalu in nalus) {
2025-04-30 16:56:30 +08:00
if (nalu.type == 7)
sps = nalu.data;
2025-04-30 16:12:22 +08:00
else if (nalu.type == 8) pps = nalu.data;
}
if (sps != null) {
_depManager.updateSps(Uint8List.fromList(sps));
}
if (pps != null) {
_depManager.updatePps(Uint8List.fromList(pps));
}
if (_depManager.sps == null || _depManager.pps == null) {
print('[VideoDecodePlugin] 丢弃I帧: 未缓存SPS/PPS');
return;
}
await _decodeFrame(
frameData: _depManager.sps!,
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq - 2,
refIFrameSeq: frameSeq - 2,
);
await _decodeFrame(
frameData: _depManager.pps!,
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq - 1,
refIFrameSeq: frameSeq - 1,
);
await _decodeFrame(
frameData: Uint8List.fromList(frameData),
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: frameSeq,
);
_depManager.updateIFrameSeq(frameSeq);
return;
}
// 兼容直接推送SPS/PPS/I帧/P帧等场景直接发送
await _decodeFrame(
frameData: Uint8List.fromList(frameData),
frameType: frameType,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: frameType == 0 ? frameSeq : _depManager.lastIFrameSeq,
);
// 若为I帧更新依赖管理
if (frameType == 0) _depManager.updateIFrameSeq(frameSeq);
// P帧依赖链完整性校验
if (frameType == 1) {
if (!_depManager.isIFrameDecoded(refIFrameSeq)) {
2025-04-30 16:56:30 +08:00
print(
'[丢帧] P帧依赖的I帧未解码丢弃 frameSeq=$frameSeq, refIFrameSeq=$refIFrameSeq');
2025-04-30 16:12:22 +08:00
return;
}
}
2025-04-21 10:56:28 +08:00
}
}