2025-04-30 18:00:54 +08:00
2025-04-30 18:00:54 +08:00
2025-04-30 16:12:22 +08:00
2025-04-28 09:11:51 +08:00
2025-04-30 18:00:54 +08:00
2025-04-28 09:11:51 +08:00
2025-04-28 09:11:51 +08:00
2025-04-28 09:11:51 +08:00
2025-04-28 09:11:51 +08:00
2025-04-28 09:11:51 +08:00
2025-04-28 09:11:51 +08:00
2025-04-30 18:00:54 +08:00

Video Decode Plugin

当前版本0.0.1
最低 Flutter SDK 要求:>=3.3.0
最低 Dart SDK 要求:>=3.3.4

说明

  • 目前只支持Android端解码

安装

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. 将本仓库代码拉取到本地后,在项目中增加本地的插件依赖

  video_decode_plugin:
    path: ../video_decode_plugin
  1. appRouters.dart找到打开路由配置文件,在文件最下方切换配置路由地址
    // GetPage<dynamic>(name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), // 插件播放页面
    GetPage<dynamic>(name: Routers.h264WebView, page: () => H264WebView()), // webview播放页面
  1. 使用插件调试则打开上一个注释同时注释掉weview的播放页面

初始化解码器

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
  });
});

渲染视频

// 使用Flutter的Texture组件显示视频
Texture(
  textureId: textureId,
  filterQuality: FilterQuality.low,
)

解码视频帧

// 解码I帧
await VideoDecodePlugin.decodeFrame(
  frameData,  // Uint8List类型的H.264/H.265帧数据
  FrameType.iFrame
);

// 解码P帧
await VideoDecodePlugin.decodeFrame(
  frameData,  // Uint8List类型的H.264/H.265帧数据
  FrameType.pFrame
);

获取解码统计信息

final stats = await VideoDecodePlugin.getDecoderStats(textureId);
print('已渲染帧数: ${stats['renderedFrames']}');
print('丢弃帧数: ${stats['droppedFrames']}');

释放资源

await VideoDecodePlugin.releaseDecoder();

高级用法

多实例支持

插件支持同时创建和管理多个解码器实例:

// 创建第一个解码器
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
// 发送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.dartSPS/PPS/I帧依赖链滑动窗口管理支持I帧序号窗口、依赖校验、SPS/PPS缓存。
  • nalu_utils.dartNALU分割与类型识别工具支持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. 推荐推送流程(伪代码)

// 发送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, ...);
Description
可视对讲的原生端解码插件
Readme 48 MiB
Languages
Dart 46.8%
Swift 28.3%
Kotlin 22.9%
Ruby 2%