Files
VibeCoding/webrtc_controller_flutter/lib/webrtc/self_codec_decoder.dart
TongTongStudio 4a33f3db0d feat(webrtc): 新增解码耗时统计并修复黑屏问题
1. 新增自编码与全托管模式的解码耗时探针(VideoSink),统计帧率与解码耗时。
2. 修复 SPS 与 PPS 作为独立 CONFIG 单元发送时被误丢弃导致 H.264 永久黑屏的问题。
3. 优化分片重组缓冲,丢弃过期单元并限制缓冲堆积以防内存泄漏。
2026-07-25 01:50:00 +08:00

135 lines
4.4 KiB
Dart
Raw Permalink 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 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
/// 自编码解码器(控制端)。
///
/// 接收来自 `video` DataChannel 的二进制分片(由被控端 [SelfCodecEncoder] 产生),
/// 通过原生 Android `MediaCodec` 硬解码,并渲染到 Flutter 纹理(`Texture`)。
///
/// 为保证与 `WebRTCControlled` 的编码器在二进制协议上完全一致,协议的
/// 分片重组、H.264 参数集解析与硬解、纹理渲染全部在原生侧完成;
/// 本类仅负责与原生侧通信。对应 Android 端 `SelfCodecDecoder`。
///
/// 注意:原生解码器目前仅在 Android 平台实现。其它平台Windows / Linux /
/// Web / iOS调用 [create] 会失败并回调 [onError],调用方应回退到
/// WebRTC 默认媒体流。
class SelfCodecDecoder {
static const MethodChannel _channel =
MethodChannel('com.ttstd.fluttercontroller/self_codec');
/// 串流模式常量(与 ControlMessage.stream_mode 对齐)。
static const int streamModeWebRtc = 0;
static const int streamModeSelfCodec = 1;
int? _textureId;
bool _disposed = false;
bool _handlerSet = false;
/// 解码输出尺寸变化(宽/高,单位像素)。
final void Function(int width, int height)? onSize;
/// 错误回调(如当前平台不支持硬解)。
final void Function(String error)? onError;
SelfCodecDecoder({this.onSize, this.onError}) {
_channel.setMethodCallHandler(_handleNativeCall);
_handlerSet = true;
}
Future<dynamic> _handleNativeCall(MethodCall call) async {
switch (call.method) {
case 'onSize':
final args = call.arguments;
final w = args is Map ? (args['width'] as int? ?? 0) : 0;
final h = args is Map ? (args['height'] as int? ?? 0) : 0;
if (w > 0 && h > 0) onSize?.call(w, h);
break;
case 'onError':
final msg = call.arguments is String ? call.arguments as String : '';
if (msg.isNotEmpty) onError?.call(msg);
break;
}
}
/// 创建解码纹理。成功返回纹理 id>=0失败返回 null。
Future<int?> create() async {
if (_disposed) return null;
try {
final id = await _channel.invokeMethod<int>('create');
if (id == null || id < 0) return null;
_textureId = id;
return _textureId;
} on PlatformException catch (e) {
onError?.call('创建自编码解码纹理失败: ${e.message}');
return null;
} catch (e) {
onError?.call('创建自编码解码纹理失败: $e');
return null;
}
}
/// 已创建的纹理 id未创建或已释放时为 null。
int? get textureId => _textureId;
/// 用于渲染解码画面的纹理控件。
Widget build(BuildContext context) {
final id = _textureId;
if (id == null) return const SizedBox.shrink();
return Texture(textureId: id);
}
/// 喂入来自 video DataChannel 的二进制分片。
Future<void> feed(Uint8List chunk) async {
if (_disposed || _textureId == null) return;
try {
await _channel.invokeMethod<void>('feed', chunk);
} catch (_) {
// 解码未就绪或被释放时忽略。
}
}
/// 启用/停用解码(仅自编码模式为 true
Future<void> setEnabled(bool enabled) async {
if (_disposed || _textureId == null) return;
try {
await _channel.invokeMethod<void>('setEnabled', enabled);
} catch (_) {
// 忽略。
}
}
/// 读取解码耗时统计。返回包含以下键的 Map毫秒失败返回 null
/// - lastMs最近一帧解码耗时
/// - avgMs最近窗口内的平均解码耗时
/// - maxMs峰值解码耗时
/// - count累计已解码帧数。
Future<Map<String, dynamic>?> getStats() async {
if (_disposed || _textureId == null) return null;
try {
final res = await _channel.invokeMethod<dynamic>('getStats');
if (res is Map) return res.cast<String, dynamic>();
} catch (_) {
// 解码未就绪或被释放时忽略。
}
return null;
}
/// 释放原生解码资源。重复调用安全。
Future<void> dispose() async {
if (_disposed) return;
_disposed = true;
if (_handlerSet) {
_channel.setMethodCallHandler(null);
_handlerSet = false;
}
try {
await _channel.invokeMethod<void>('dispose');
} catch (_) {
// 忽略。
}
_textureId = null;
}
}