diff --git a/README.md b/README.md index 087ae06..d4755fd 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,6 @@ > 最低 Flutter SDK 要求:>=3.3.0 > 最低 Dart SDK 要求:>=3.3.4 - -## 说明 -- 目前只支持Android端解码 - ## 安装 ```yaml @@ -15,206 +11,69 @@ dependencies: video_decode_plugin: ^0.0.1 # 使用最新版本 ``` -## 环境要求 +## 插件简介与环境要求 + +本插件为Flutter平台下的裸流H.264/H.265实时视频解码渲染插件,支持Android与iOS - Flutter SDK: >=3.3.0 - Dart SDK: >=3.3.4 -- Android: - - minSdkVersion: 21 (Android 5.0) - - targetSdkVersion: 最新版本 -- iOS: - - 最低版本: 11.0 - - 开发环境: Xcode 最新版本 +- Android: minSdkVersion 21+ +- iOS: 11.0+ -## 快速开始 - -### 在星锁项目中调试 -1. 需切换到`develop_liyi`分支 - -2. 将本仓库代码拉取到本地后,在项目中增加本地的插件依赖 -```dart - video_decode_plugin: - path: ../video_decode_plugin -``` - -3. `appRouters.dart`找到打开路由配置文件,在文件最下方切换配置路由地址 -```dart - // GetPage(name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), // 插件播放页面 - GetPage(name: Routers.h264WebView, page: () => H264WebView()), // webview播放页面 -``` -4. 使用插件调试则打开上一个注释,同时注释掉weview的播放页面 - -### 初始化解码器 +## 快速开始(单实例标准用法) ```dart import 'package:video_decode_plugin/video_decode_plugin.dart'; -// 创建解码器配置 +// 1. 创建解码器配置 final config = VideoDecoderConfig( width: 640, // 视频宽度 height: 480, // 视频高度 - codecType: CodecType.h264, // 编解码类型:h264或h265 - frameRate: 30, // 目标帧率(可选) - isDebug: true, // 是否启用详细日志 + codecType: 'h264', // 编解码类型:h264或h265 ); -// 初始化解码器,获取纹理ID +// 2. 初始化解码器,获取纹理ID final textureId = await VideoDecodePlugin.initDecoder(config); -// 设置帧回调 -VideoDecodePlugin.setFrameCallback((textureId) { - // 当新帧可用时被调用 - setState(() { - // 更新UI - }); +// 3. 设置帧渲染回调(可选) +// 第一帧被渲染时触发的回调 +VideoDecodePlugin.setOnFrameRenderedListener((textureId) { + // 新帧渲染时回调 + setState(() {}); }); -``` -### 渲染视频 - -```dart -// 使用Flutter的Texture组件显示视频 -Texture( - textureId: textureId, - filterQuality: FilterQuality.low, +// 4. 渲染视频(Flutter端) +AspectRatio( + aspectRatio: 640/480, // 建议与视频源一致 + child: Container( + color: Colors.transparent, + child: Texture( + textureId: textureId!, + filterQuality: FilterQuality.medium, + ), + ), ) -``` -### 解码视频帧 - -```dart -// 解码I帧 -await VideoDecodePlugin.decodeFrame( - frameData, // Uint8List类型的H.264/H.265帧数据 - FrameType.iFrame +// 5. 推送视频帧(SPS/PPS/I帧/P帧) +await VideoDecodePlugin.sendFrame( + frameData: sps, frameType: 0, timestamp: ..., frameSeq: ... +); +await VideoDecodePlugin.sendFrame( + frameData: pps, frameType: 0, timestamp: ..., frameSeq: ... +); +await VideoDecodePlugin.sendFrame( + frameData: iFrame, frameType: 0, timestamp: ..., frameSeq: ... +); +await VideoDecodePlugin.sendFrame( + frameData: pFrame, frameType: 1, timestamp: ..., frameSeq: ..., refIFrameSeq: ... ); -// 解码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 +// 6. 释放资源 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平台下的高性能、可扩展、易调试的视频解码渲染插件,专注于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, ...); @@ -225,15 +84,54 @@ await VideoDecodePlugin.sendFrame(frameData: iFrame, frameType: 0, ...); await VideoDecodePlugin.sendFrame(frameData: pFrame, frameType: 1, refIFrameSeq: iFrameSeq, ...); ``` -## iOS端视频解码与渲染实现说明 +## 必须做的前置操作 + +- **推送顺序**:务必保证每个I帧前都已推送最新SPS(NAL类型7)、PPS(NAL类型8),再推送I帧(NAL类型5),最后推送P帧(NAL类型1)。 +- **依赖链完整性校验**:业务端需实现I帧序号管理,P帧推送前校验refIFrameSeq是否为已解码I帧,否则丢弃。 +- **丢帧策略**:强烈建议业务端实现丢帧与依赖链管理,避免无效帧流入原生端。 +- **异常自愈**:检测到解码失败/花屏/长时间无I帧时,建议主动reset解码器,等待下一个I帧恢复链路。 +- **日志监控**:建议业务端和原生端均输出详细日志,便于端到端排查依赖链断裂、丢包、乱序等问题。 + +## 重要注意事项 + +- **只推送标准NALU**:仅推送type=1的P帧、type=5的I帧、type=7/8的SPS/PPS进入解码器,SEI等异常NALU自动丢弃。 +- **Flutter端布局建议**:建议用AspectRatio、FittedBox等包裹Texture,确保宽高比一致,避免白边。父容器背景色建议设为透明。 +- **iOS端Texture机制说明**:Flutter Texture机制下无法直接控制原生UIView属性(contentMode等),如需更强原生控制力可考虑PlatformView方案。 +- **Android/iOS平台差异**:iOS端VideoToolbox对NALU格式、推送顺序要求更严格,务必保证数据流规范。 + +## 常见问题与排查建议 + +- **花屏/马赛克**:多因I帧依赖链断裂、SPS/PPS/I帧推送顺序错误、数据丢包、NALU内容异常导致。请重点排查推送顺序与依赖链。 +- **白边/拉伸**:多因Flutter端布局宽高比不一致或父容器背景色非透明。建议用AspectRatio/FittedBox包裹Texture。 +- **P帧丢弃**:如P帧依赖的I帧未解码,P帧会被自动丢弃,防止花屏。 +- **iOS端解码失败**:请确保推送NALU类型、顺序、内容均规范,iOS端容错性低于Android。 +- **Android帧率较低**:确保使用了硬解码、三层缓冲区被正常使用 + +## iOS端实现架构简述 - iOS端基于VideoToolbox实现H264/H265硬件解码,输出CVPixelBuffer。 - 插件内部实现了解码输入缓冲区、输出缓冲区,解码与渲染完全解耦。 - 独立渲染线程定时从输出缓冲区取帧,刷新Flutter纹理,支持EMA自适应帧率平滑调整,提升流畅度与健壮性。 - P帧推送前会校验依赖的I帧是否已解码,若依赖链断裂则P帧直接丢弃,避免马赛克。 - 仅推送标准NALU(type=1的P帧、type=5的I帧、type=7/8的SPS/PPS)进入解码器,SEI等异常NALU自动丢弃。 -- Flutter端建议用AspectRatio、FittedBox等包裹Texture,确保宽高比一致,避免白边。 - 由于Flutter Texture机制无法直接控制原生UIView属性,建议Flutter端容器背景色设为透明,布局自适应。 - 如需更强原生控制力,可考虑自定义PlatformView方案。 +## 版本历史 + +### 0.0.1 - 2025.04 +- ✨ 初始版本发布 +- 🎯 基础功能实现: + - H.264 解码支持 + - 实时视频流解码 + - 基础错误处理 + +## H.264解码注意事项 + +### 马赛克/花屏出现的根因 +- **依赖链断裂**:P/B帧依赖的I帧未被解码成功,或I帧本身丢失/损坏,后续P/B帧全部解码失败,必然花屏。 +- **SPS/PPS/I帧推送顺序错误**:未按SPS→PPS→I帧顺序推送,或I帧前未收到最新SPS/PPS,解码器无法正确解码I帧。 +- **解码链断裂后未及时reset解码器**:解码器内部状态异常,后续即使收到新I帧也无法恢复,需reset解码器。 + +