2025-04-21 15:11:23 +08:00
|
|
|
|
# Video Decode Plugin
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-28 09:07:23 +08:00
|
|
|
|
> 当前版本:0.0.1
|
|
|
|
|
|
> 最低 Flutter SDK 要求:>=3.3.0
|
|
|
|
|
|
> 最低 Dart SDK 要求:>=3.3.4
|
2025-04-28 09:24:11 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 说明
|
|
|
|
|
|
- 目前只支持Android端解码
|
|
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
## 安装
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```yaml
|
|
|
|
|
|
dependencies:
|
2025-04-28 09:07:23 +08:00
|
|
|
|
video_decode_plugin: ^0.0.1 # 使用最新版本
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-28 09:07:23 +08:00
|
|
|
|
## 环境要求
|
|
|
|
|
|
|
|
|
|
|
|
- Flutter SDK: >=3.3.0
|
|
|
|
|
|
- Dart SDK: >=3.3.4
|
|
|
|
|
|
- Android:
|
|
|
|
|
|
- minSdkVersion: 21 (Android 5.0)
|
|
|
|
|
|
- targetSdkVersion: 最新版本
|
|
|
|
|
|
- iOS:
|
|
|
|
|
|
- 最低版本: 11.0
|
|
|
|
|
|
- 开发环境: Xcode 最新版本
|
|
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
## 快速开始
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-28 09:24:11 +08:00
|
|
|
|
### 在星锁项目中调试
|
2025-04-28 09:25:05 +08:00
|
|
|
|
1. 需切换到`develop_liyi`分支
|
2025-04-28 09:25:36 +08:00
|
|
|
|
|
2025-04-28 09:27:42 +08:00
|
|
|
|
2. 将本仓库代码拉取到本地后,在项目中增加本地的插件依赖
|
|
|
|
|
|
```dart
|
|
|
|
|
|
video_decode_plugin:
|
|
|
|
|
|
path: ../video_decode_plugin
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. `appRouters.dart`找到打开路由配置文件,在文件最下方切换配置路由地址
|
2025-04-28 09:24:11 +08:00
|
|
|
|
```dart
|
|
|
|
|
|
// GetPage<dynamic>(name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), // 插件播放页面
|
|
|
|
|
|
GetPage<dynamic>(name: Routers.h264WebView, page: () => H264WebView()), // webview播放页面
|
|
|
|
|
|
```
|
2025-04-28 09:27:42 +08:00
|
|
|
|
4. 使用插件调试则打开上一个注释,同时注释掉weview的播放页面
|
2025-04-28 09:24:11 +08:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
### 初始化解码器
|
2025-04-21 10:56:28 +08:00
|
|
|
|
|
|
|
|
|
|
```dart
|
|
|
|
|
|
import 'package:video_decode_plugin/video_decode_plugin.dart';
|
|
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
// 创建解码器配置
|
|
|
|
|
|
final config = VideoDecoderConfig(
|
|
|
|
|
|
width: 640, // 视频宽度
|
|
|
|
|
|
height: 480, // 视频高度
|
|
|
|
|
|
codecType: CodecType.h264, // 编解码类型:h264或h265
|
|
|
|
|
|
frameRate: 30, // 目标帧率(可选)
|
|
|
|
|
|
isDebug: true, // 是否启用详细日志
|
2025-04-21 10:56:28 +08:00
|
|
|
|
);
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
// 初始化解码器,获取纹理ID
|
|
|
|
|
|
final textureId = await VideoDecodePlugin.initDecoder(config);
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
// 设置帧回调
|
|
|
|
|
|
VideoDecodePlugin.setFrameCallback((textureId) {
|
|
|
|
|
|
// 当新帧可用时被调用
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
// 更新UI
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
### 渲染视频
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
```dart
|
|
|
|
|
|
// 使用Flutter的Texture组件显示视频
|
|
|
|
|
|
Texture(
|
|
|
|
|
|
textureId: textureId,
|
|
|
|
|
|
filterQuality: FilterQuality.low,
|
|
|
|
|
|
)
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
### 解码视频帧
|
|
|
|
|
|
|
|
|
|
|
|
```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
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
### 获取解码统计信息
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```dart
|
2025-04-21 15:11:23 +08:00
|
|
|
|
final stats = await VideoDecodePlugin.getDecoderStats(textureId);
|
|
|
|
|
|
print('已渲染帧数: ${stats['renderedFrames']}');
|
|
|
|
|
|
print('丢弃帧数: ${stats['droppedFrames']}');
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
### 释放资源
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```dart
|
2025-04-21 15:11:23 +08:00
|
|
|
|
await VideoDecodePlugin.releaseDecoder();
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
## 高级用法
|
|
|
|
|
|
|
|
|
|
|
|
### 多实例支持
|
|
|
|
|
|
|
|
|
|
|
|
插件支持同时创建和管理多个解码器实例:
|
2025-04-21 10:56:28 +08:00
|
|
|
|
|
|
|
|
|
|
```dart
|
2025-04-21 15:11:23 +08:00
|
|
|
|
// 创建第一个解码器
|
|
|
|
|
|
final textureId1 = await VideoDecodePlugin.createDecoder(config1);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建第二个解码器
|
|
|
|
|
|
final textureId2 = await VideoDecodePlugin.createDecoder(config2);
|
|
|
|
|
|
|
|
|
|
|
|
// 为特定纹理ID设置回调
|
|
|
|
|
|
VideoDecodePlugin.setFrameCallbackForTexture(textureId1, (id) {
|
|
|
|
|
|
// 处理第一个解码器的帧
|
2025-04-21 10:56:28 +08:00
|
|
|
|
});
|
2025-04-21 15:11:23 +08:00
|
|
|
|
|
|
|
|
|
|
// 为特定纹理ID解码帧
|
|
|
|
|
|
await VideoDecodePlugin.decodeFrameForTexture(textureId2, frameData, frameType);
|
|
|
|
|
|
|
|
|
|
|
|
// 释放特定解码器
|
|
|
|
|
|
await VideoDecodePlugin.releaseDecoderForTexture(textureId1);
|
2025-04-21 10:56:28 +08:00
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
### 优化I帧和SPS/PPS处理
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
对于H.264视频流,建议按照以下顺序处理帧:
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
1. 首先发送SPS(序列参数集,NAL类型7)
|
|
|
|
|
|
2. 其次发送PPS(图像参数集,NAL类型8)
|
|
|
|
|
|
3. 然后发送IDR帧(即I帧,NAL类型5)
|
|
|
|
|
|
4. 最后发送P帧(NAL类型1)
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
```dart
|
|
|
|
|
|
// 发送SPS和PPS数据
|
|
|
|
|
|
await VideoDecodePlugin.decodeFrame(spsData, FrameType.iFrame);
|
|
|
|
|
|
await VideoDecodePlugin.decodeFrame(ppsData, FrameType.iFrame);
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
// 发送IDR帧
|
|
|
|
|
|
await VideoDecodePlugin.decodeFrame(idrData, FrameType.iFrame);
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
// 发送P帧
|
|
|
|
|
|
await VideoDecodePlugin.decodeFrame(pFrameData, FrameType.pFrame);
|
|
|
|
|
|
```
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
## 完整示例
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-21 15:11:23 +08:00
|
|
|
|
请参考示例应用,了解如何:
|
|
|
|
|
|
- 从文件或网络流加载H.264视频
|
|
|
|
|
|
- 正确解析和处理NAL单元
|
|
|
|
|
|
- 高效地解码和渲染视频帧
|
|
|
|
|
|
- 监控解码性能并进行故障排除
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-28 09:07:23 +08:00
|
|
|
|
## 版本历史
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
2025-04-28 09:38:17 +08:00
|
|
|
|
### 0.0.1 - 2025.04
|
2025-04-28 09:07:23 +08:00
|
|
|
|
- ✨ 初始版本发布
|
|
|
|
|
|
- 🎯 基础功能实现:
|
|
|
|
|
|
- H.264/H.265 解码支持
|
|
|
|
|
|
- 实时视频流解码
|
|
|
|
|
|
- 基础错误处理
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
2025-04-30 18:00:54 +08:00
|
|
|
|
## 插件架构与职责
|
|
|
|
|
|
|
|
|
|
|
|
本插件为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帧前都已推送最新SPS(NAL类型7)、PPS(NAL类型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, ...);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-04-28 01:08:21 +00:00
|
|
|
|
|