2025-04-30 18:00:54 +08:00

244 lines
7.8 KiB
Markdown
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.

# Video Decode Plugin
> 当前版本0.0.1
> 最低 Flutter SDK 要求:>=3.3.0
> 最低 Dart SDK 要求:>=3.3.4
## 说明
- 目前只支持Android端解码
## 安装
```yaml
dependencies:
video_decode_plugin: ^0.0.1 # 使用最新版本
```
## 环境要求
- Flutter SDK: >=3.3.0
- Dart SDK: >=3.3.4
- Android:
- minSdkVersion: 21 (Android 5.0)
- targetSdkVersion: 最新版本
- iOS:
- 最低版本: 11.0
- 开发环境: Xcode 最新版本
## 快速开始
### 在星锁项目中调试
1. 需切换到`develop_liyi`分支
2. 将本仓库代码拉取到本地后,在项目中增加本地的插件依赖
```dart
video_decode_plugin:
path: ../video_decode_plugin
```
3. `appRouters.dart`找到打开路由配置文件,在文件最下方切换配置路由地址
```dart
// GetPage<dynamic>(name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), // 插件播放页面
GetPage<dynamic>(name: Routers.h264WebView, page: () => H264WebView()), // webview播放页面
```
4. 使用插件调试则打开上一个注释同时注释掉weview的播放页面
### 初始化解码器
```dart
import 'package:video_decode_plugin/video_decode_plugin.dart';
// 创建解码器配置
final config = VideoDecoderConfig(
width: 640, // 视频宽度
height: 480, // 视频高度
codecType: CodecType.h264, // 编解码类型h264或h265
frameRate: 30, // 目标帧率(可选)
isDebug: true, // 是否启用详细日志
);
// 初始化解码器获取纹理ID
final textureId = await VideoDecodePlugin.initDecoder(config);
// 设置帧回调
VideoDecodePlugin.setFrameCallback((textureId) {
// 当新帧可用时被调用
setState(() {
// 更新UI
});
});
```
### 渲染视频
```dart
// 使用Flutter的Texture组件显示视频
Texture(
textureId: textureId,
filterQuality: FilterQuality.low,
)
```
### 解码视频帧
```dart
// 解码I帧
await VideoDecodePlugin.decodeFrame(
frameData, // Uint8List类型的H.264/H.265帧数据
FrameType.iFrame
);
// 解码P帧
await VideoDecodePlugin.decodeFrame(
frameData, // Uint8List类型的H.264/H.265帧数据
FrameType.pFrame
);
```
### 获取解码统计信息
```dart
final stats = await VideoDecodePlugin.getDecoderStats(textureId);
print('已渲染帧数: ${stats['renderedFrames']}');
print('丢弃帧数: ${stats['droppedFrames']}');
```
### 释放资源
```dart
await VideoDecodePlugin.releaseDecoder();
```
## 高级用法
### 多实例支持
插件支持同时创建和管理多个解码器实例:
```dart
// 创建第一个解码器
final textureId1 = await VideoDecodePlugin.createDecoder(config1);
// 创建第二个解码器
final textureId2 = await VideoDecodePlugin.createDecoder(config2);
// 为特定纹理ID设置回调
VideoDecodePlugin.setFrameCallbackForTexture(textureId1, (id) {
// 处理第一个解码器的帧
});
// 为特定纹理ID解码帧
await VideoDecodePlugin.decodeFrameForTexture(textureId2, frameData, frameType);
// 释放特定解码器
await VideoDecodePlugin.releaseDecoderForTexture(textureId1);
```
### 优化I帧和SPS/PPS处理
对于H.264视频流,建议按照以下顺序处理帧:
1. 首先发送SPS序列参数集NAL类型7
2. 其次发送PPS图像参数集NAL类型8
3. 然后发送IDR帧即I帧NAL类型5
4. 最后发送P帧NAL类型1
```dart
// 发送SPS和PPS数据
await VideoDecodePlugin.decodeFrame(spsData, FrameType.iFrame);
await VideoDecodePlugin.decodeFrame(ppsData, FrameType.iFrame);
// 发送IDR帧
await VideoDecodePlugin.decodeFrame(idrData, FrameType.iFrame);
// 发送P帧
await VideoDecodePlugin.decodeFrame(pFrameData, FrameType.pFrame);
```
## 完整示例
请参考示例应用,了解如何:
- 从文件或网络流加载H.264视频
- 正确解析和处理NAL单元
- 高效地解码和渲染视频帧
- 监控解码性能并进行故障排除
## 版本历史
### 0.0.1 - 2025.04
- ✨ 初始版本发布
- 🎯 基础功能实现:
- H.264/H.265 解码支持
- 实时视频流解码
- 基础错误处理
### 现有问题
- 解码后在flutter渲染实际帧数较低
- 方案一使用原生activity渲染视频数据
- 方案二多个TextureView并行交替渲染
- 方案三:待补充
- 方案四:待补充
### 后续计划
- [ ] 解码帧数较低
- [ ] 增加更多错误处理
- [ ] 完善文档和示例
- [ ] 添加单元测试
- [ ] 支持更多编解码格式
- [ ] 优化内存管理
## 插件架构与职责
本插件为Flutter平台下的高性能、可扩展、易调试的视频解码渲染插件专注于Android端H.264/H.265实时流解码。
- **Flutter端职责**
- 负责NALU分割、SPS/PPS/I帧依赖链管理、滑动窗口丢帧、业务层帧流控制。
- 依赖链完整性校验P帧仅在依赖I帧已解码时才推送。
- 通过MethodChannel将帧数据、类型、序号等元数据传递到原生端。
- **Android端职责**
- 极简高效解码与渲染,主线程安全回调。
- 兜底依赖链校验,解码器生命周期管理,异常自愈。
## 文件结构与说明
### Dart端lib/
- **video_decode_plugin.dart**:插件主入口,负责解码器初始化、帧推送、回调注册、依赖链管理、与原生通信等。
- **frame_dependency_manager.dart**SPS/PPS/I帧依赖链滑动窗口管理支持I帧序号窗口、依赖校验、SPS/PPS缓存。
- **nalu_utils.dart**NALU分割与类型识别工具支持H.264/H.265帧的NALU单元分离、类型提取。
- **video_decode_plugin_platform_interface.dart**插件平台接口定义支持多平台扩展默认实现为MethodChannel。
- **video_decode_plugin_method_channel.dart**插件MethodChannel实现负责与原生端通信。
### Android端android/src/main/kotlin/top/skychip/video_decode_plugin/
- **VideoDecodePlugin.kt**插件原生入口注册MethodChannel管理解码器生命周期处理Flutter端方法调用。
- **VideoDecoder.kt**解码器核心实现负责MediaCodec解码、输入/输出队列、渲染线程、依赖链兜底校验、主线程回调、资源释放。
- **VideoDecoderConfig.kt**:解码器配置参数定义,支持分辨率、编解码类型、帧率、调试模式等。
## H.264解码注意事项
### 1. 马赛克/花屏出现的根因
- **依赖链断裂**P/B帧依赖的I帧未被解码成功或I帧本身丢失/损坏后续P/B帧全部解码失败必然花屏。
- **SPS/PPS/I帧推送顺序错误**未按SPS→PPS→I帧顺序推送或I帧前未收到最新SPS/PPS解码器无法正确解码I帧。
- **解码链断裂后未及时reset解码器**解码器内部状态异常后续即使收到新I帧也无法恢复需reset解码器。
### 2. 外部调用者(业务方)必须做好的前置操作
- **推送顺序**务必保证每个I帧前都已推送最新SPSNAL类型7、PPSNAL类型8再推送I帧NAL类型5最后推送P/B帧。
- **依赖链完整性校验**业务端需实现滑动窗口I帧序号管理P帧推送前校验refIFrameSeq是否在窗口内否则丢弃。
- **丢帧策略**:强烈建议业务端实现丢帧与依赖链管理,避免无效帧流入原生端。
- **异常自愈**:检测到解码失败/花屏/长时间无I帧时建议主动reset解码器等待下一个I帧恢复链路。
- **日志监控**:建议业务端和原生端均输出详细日志,便于端到端排查依赖链断裂、丢包、乱序等问题。
### 3. 推荐推送流程(伪代码)
```dart
// 发送SPS和PPS
await VideoDecodePlugin.sendFrame(frameData: sps, frameType: 0, ...);
await VideoDecodePlugin.sendFrame(frameData: pps, frameType: 0, ...);
// 发送I帧
await VideoDecodePlugin.sendFrame(frameData: iFrame, frameType: 0, ...);
// 发送P帧
await VideoDecodePlugin.sendFrame(frameData: pFrame, frameType: 1, refIFrameSeq: iFrameSeq, ...);
```