video_decode_plugin/lib/video_decode_plugin.dart
2025-05-15 16:47:25 +08:00

340 lines
9.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
import 'nalu_utils.dart';
import 'frame_dependency_manager.dart';
/// 视频解码器配置
class VideoDecoderConfig {
/// 视频宽度
final int width;
/// 视频高度
final int height;
/// 编码类型默认h264
final String codecType;
/// 构造函数
VideoDecoderConfig({
required this.width,
required this.height,
this.codecType = 'h264',
});
/// 转换为Map
Map<String, dynamic> toMap() {
return {
'width': width,
'height': height,
'codecType': codecType,
};
}
}
/// 视频解码插件主类
class VideoDecodePlugin {
static const MethodChannel _channel = MethodChannel('video_decode_plugin');
static int? _textureId;
/// 获取默认纹理ID
static int? get textureId => _textureId;
static final _depManager = FrameDependencyManager();
/// 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);
}
}
}
/// 初始化解码器
static Future<int?> initDecoder(VideoDecoderConfig config) async {
final textureId =
await _channel.invokeMethod<int>('initDecoder', config.toMap());
_textureId = textureId;
return textureId;
}
/// 解码视频帧(参数扩展,仅供内部调用)
static Future<bool> _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<bool>('decodeFrame', params);
return result ?? false;
}
/// 释放解码器资源
static Future<bool> releaseDecoder() async {
if (_textureId == null) return true;
final result = await _channel.invokeMethod<bool>('releaseDecoder', {
'textureId': _textureId,
});
_textureId = null;
_depManager.updateSps(null);
_depManager.updatePps(null);
_depManager.reset();
return result ?? false;
}
/// 获取平台版本
static Future<String?> getPlatformVersion() {
return VideoDecodePluginPlatform.instance.getPlatformVersion();
}
/// 检查当前平台是否支持
static bool get isPlatformSupported {
return Platform.isAndroid || Platform.isIOS;
}
///
/// [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,
int? refIFrameSeq, // P帧时可选
}) async {
if (Platform.isAndroid) {
await _sendFrameAndroid(
frameData: frameData,
frameType: frameType,
timestamp: timestamp,
frameSeq: frameSeq,
splitNalFromIFrame: splitNalFromIFrame,
refIFrameSeq: refIFrameSeq,
);
} else if (Platform.isIOS) {
await _sendFrameIOS(
frameData: frameData,
frameType: frameType,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: refIFrameSeq,
);
}
// 其他平台暂不支持
}
static Future<void> _sendFrameAndroid({
required List<int> frameData,
required int frameType,
required int timestamp,
required int frameSeq,
bool splitNalFromIFrame = false,
int? refIFrameSeq,
}) 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);
List<int>? sps, pps;
for (final nalu in nalus) {
if (nalu.type == 7)
sps = nalu.data;
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帧等场景直接发送
// P帧依赖链完整性校验提前
if (frameType == 1) {
if (!_depManager.isIFrameDecoded(refIFrameSeq)) {
print('[丢帧] P帧依赖的I帧未解码丢弃 frameSeq=$frameSeq, refIFrameSeq=$refIFrameSeq, dropCount=${_depManager.dropCount}, threshold=${_depManager.dropThresholdValue}');
return;
}
// 仅推送type=1的NALU提升平台一致性
frameData = NaluUtils.filterNalusByType(frameData, 1);
}
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);
}
static Future<void> _sendFrameIOS({
required List<int> frameData,
required int frameType,
required int timestamp,
required int frameSeq,
int? refIFrameSeq,
}) async {
// 仅P帧做AnnexB起始码检测和修正
if (frameType == 1) {
// 仅推送type=1的NALU提升平台一致性
frameData = NaluUtils.filterNalusByType(frameData, 1);
if (frameData.isEmpty) {
print('[VideoDecodePlugin][警告] iOS端P帧未找到type=1的NALU已丢弃');
return;
}
int startIndex = -1;
for (int i = 0; i < frameData.length - 3; i++) {
if (frameData[i] == 0x00 &&
frameData[i + 1] == 0x00 &&
frameData[i + 2] == 0x00 &&
frameData[i + 3] == 0x01) {
startIndex = i;
break;
}
}
if (startIndex == -1) {
print('[VideoDecodePlugin][警告] iOS端P帧仅type=1 NALU后无AnnexB起始码已丢弃');
return;
}
if (startIndex > 0) {
frameData = frameData.sublist(startIndex);
}
}
if (frameType == 0) {
// I帧需分割NALU严格按SPS->PPS->I帧顺序推送
final nalus = NaluUtils.splitNalus(frameData);
List<int>? sps, pps, idr;
for (final nalu in nalus) {
if (nalu.type == 7) sps = nalu.data;
if (nalu.type == 8) pps = nalu.data;
if (nalu.type == 5) idr = nalu.data;
}
if (sps != null) {
await _decodeFrame(
frameData: Uint8List.fromList(sps),
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: frameSeq,
);
}
if (pps != null) {
await _decodeFrame(
frameData: Uint8List.fromList(pps),
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: frameSeq,
);
}
if (idr != null) {
await _decodeFrame(
frameData: Uint8List.fromList(idr),
frameType: 0,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: frameSeq,
);
}
_depManager.updateIFrameSeq(frameSeq);
return;
}
// 其他类型帧直接推送
await _decodeFrame(
frameData: Uint8List.fromList(frameData),
frameType: frameType,
timestamp: timestamp,
frameSeq: frameSeq,
refIFrameSeq: refIFrameSeq,
);
if (frameType == 0) _depManager.updateIFrameSeq(frameSeq);
}
}