feat(streaming): 添加自编码串流模式切换功能
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||
|
||||
import 'self_codec_decoder.dart';
|
||||
|
||||
import '../config/ice_servers.dart';
|
||||
import '../models/signal_message.dart';
|
||||
import '../proto/control_message.pb.dart';
|
||||
@@ -26,6 +28,10 @@ class WebRtcController {
|
||||
|
||||
RTCPeerConnection? _pc;
|
||||
RTCDataChannel? _dataChannel;
|
||||
RTCDataChannel? _videoDataChannel;
|
||||
SelfCodecDecoder? _selfCodecDecoder;
|
||||
int _currentStreamMode = streamModeWebRtc;
|
||||
bool _creatingDecoder = false;
|
||||
|
||||
/// 远端视频渲染器,由调用方持有并显示。
|
||||
final RTCVideoRenderer renderer = RTCVideoRenderer();
|
||||
@@ -40,6 +46,25 @@ class WebRtcController {
|
||||
/// 远端视频流就绪(renderer 已绑定视频轨道)。
|
||||
void Function(RTCVideoRenderer renderer)? onRemoteStream;
|
||||
|
||||
/// 自编码解码纹理已就绪(textureId >= 0)。
|
||||
void Function(int)? onSelfCodecReady;
|
||||
|
||||
/// 自编码解码器已释放(连接断开时)。
|
||||
void Function()? onSelfCodecLost;
|
||||
|
||||
/// 被控端上报当前生效的串流模式。
|
||||
void Function(int mode)? onStreamModeReport;
|
||||
|
||||
/// 被控端上报分辨率(自编码解码器输出尺寸变化时也会触发)。
|
||||
void Function(int width, int height)? onResolutionReported;
|
||||
|
||||
/// 当前平台(Windows / Linux / Web / iOS 等)不支持原生硬解时回调。
|
||||
void Function()? onSelfCodecNotSupported;
|
||||
|
||||
static const String kVideoChannelLabel = 'video_channel';
|
||||
static const int streamModeWebRtc = 0;
|
||||
static const int streamModeSelfCodec = 1;
|
||||
|
||||
WebRtcController({
|
||||
required this.signaling,
|
||||
required this.deviceId,
|
||||
@@ -83,6 +108,17 @@ class WebRtcController {
|
||||
_dataChannel = await _pc!.createDataChannel(kDataChannelLabel, dcInit);
|
||||
_setupDataChannel(_dataChannel!);
|
||||
|
||||
// 创建自编码视频透传通道(无序、不可靠,类似 RTP,最低延迟)。
|
||||
// 被控端仅在收到 SET_STREAM_MODE=自编码 后才经此通道推送 H.264 裸流。
|
||||
final videoInit = RTCDataChannelInit()
|
||||
..ordered = false
|
||||
..maxRetransmits = 0;
|
||||
_videoDataChannel = await _pc!.createDataChannel(
|
||||
kVideoChannelLabel,
|
||||
videoInit,
|
||||
);
|
||||
_setupVideoDataChannel(_videoDataChannel!);
|
||||
|
||||
// 创建并发送 Offer。
|
||||
final constraints = {
|
||||
'mandatory': {
|
||||
@@ -104,18 +140,22 @@ class WebRtcController {
|
||||
}
|
||||
|
||||
void _onDataChannel(RTCDataChannel channel) {
|
||||
// 被控端也可能主动创建 DataChannel,统一处理。
|
||||
_setupDataChannel(channel);
|
||||
// 被控端也可能主动创建 DataChannel,按标签统一处理。
|
||||
if (channel.label == kDataChannelLabel) {
|
||||
_dataChannel ??= channel;
|
||||
_setupDataChannel(channel);
|
||||
} else if (channel.label == kVideoChannelLabel) {
|
||||
_videoDataChannel ??= channel;
|
||||
_setupVideoDataChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
void _setupDataChannel(RTCDataChannel channel) {
|
||||
channel.onMessage = (RTCDataChannelMessage message) {
|
||||
// 处理来自被控端的消息(本控制端主要发送,此处仅做日志记录)。
|
||||
if (message.isBinary) {
|
||||
try {
|
||||
final command = ControlMessage.fromBuffer(message.binary);
|
||||
// ignore: avoid_print
|
||||
print('DataChannel message: ${command.action}');
|
||||
_handleControlMessage(command);
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
print('DataChannel message decode failed: $e');
|
||||
@@ -131,6 +171,78 @@ class WebRtcController {
|
||||
};
|
||||
}
|
||||
|
||||
void _setupVideoDataChannel(RTCDataChannel channel) {
|
||||
channel.onMessage = (RTCDataChannelMessage message) {
|
||||
if (message.isBinary) {
|
||||
_selfCodecDecoder?.feed(message.binary);
|
||||
}
|
||||
};
|
||||
channel.onDataChannelState = (RTCDataChannelState state) {
|
||||
if (state == RTCDataChannelState.RTCDataChannelOpen) {
|
||||
_onVideoChannelOpen();
|
||||
} else if (state == RTCDataChannelState.RTCDataChannelClosed) {
|
||||
_selfCodecDecoder?.setEnabled(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void _onVideoChannelOpen() {
|
||||
_ensureSelfCodecDecoder().then((_) {
|
||||
_selfCodecDecoder?.setEnabled(_currentStreamMode == streamModeSelfCodec);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _ensureSelfCodecDecoder() async {
|
||||
if (_selfCodecDecoder != null || _creatingDecoder) return;
|
||||
_creatingDecoder = true;
|
||||
try {
|
||||
final decoder = SelfCodecDecoder(
|
||||
onSize: (w, h) => onResolutionReported?.call(w, h),
|
||||
onError: (err) => debugPrint('自编码解码器: $err'),
|
||||
);
|
||||
final textureId = await decoder.create();
|
||||
if (textureId != null) {
|
||||
_selfCodecDecoder = decoder;
|
||||
onSelfCodecReady?.call(textureId);
|
||||
} else {
|
||||
// 当前平台(Windows / Linux / Web / iOS 等)无原生硬解能力,回退 WebRTC 媒体流。
|
||||
onSelfCodecNotSupported?.call();
|
||||
}
|
||||
} finally {
|
||||
_creatingDecoder = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleControlMessage(ControlMessage msg) {
|
||||
switch (msg.action) {
|
||||
case Action.REPORT_STREAM_MODE:
|
||||
_currentStreamMode = msg.streamMode;
|
||||
_selfCodecDecoder?.setEnabled(msg.streamMode == streamModeSelfCodec);
|
||||
onStreamModeReport?.call(msg.streamMode);
|
||||
break;
|
||||
case Action.REPORT_RESOLUTION:
|
||||
onResolutionReported?.call(msg.width, msg.height);
|
||||
break;
|
||||
default:
|
||||
// ignore: avoid_print
|
||||
print('DataChannel message: ${msg.action}');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// 请求被控端切换屏幕串流模式(SET_STREAM_MODE 指令)。
|
||||
/// [mode] 为 0=WebRTC 全托管 / 1=自编码。
|
||||
Future<void> sendStreamMode(int mode) async {
|
||||
_currentStreamMode = mode;
|
||||
if (mode == streamModeSelfCodec) {
|
||||
await _ensureSelfCodecDecoder();
|
||||
await _selfCodecDecoder?.setEnabled(true);
|
||||
} else {
|
||||
await _selfCodecDecoder?.setEnabled(false);
|
||||
}
|
||||
sendControlCommand(ControlCommands.streamMode(mode));
|
||||
}
|
||||
|
||||
void _onIceCandidate(RTCIceCandidate candidate) {
|
||||
final payload = {
|
||||
'sdpMid': candidate.sdpMid,
|
||||
@@ -413,6 +525,20 @@ class WebRtcController {
|
||||
await renderer.dispose();
|
||||
} catch (_) {}
|
||||
_dataChannel = null;
|
||||
try {
|
||||
await _videoDataChannel?.close();
|
||||
} catch (_) {}
|
||||
_videoDataChannel = null;
|
||||
try {
|
||||
await _selfCodecDecoder?.dispose();
|
||||
} catch (_) {}
|
||||
_selfCodecDecoder = null;
|
||||
_currentStreamMode = streamModeWebRtc;
|
||||
onSelfCodecLost?.call();
|
||||
onSelfCodecReady = null;
|
||||
onStreamModeReport = null;
|
||||
onResolutionReported = null;
|
||||
onSelfCodecNotSupported = null;
|
||||
_pc = null;
|
||||
// 重置网速统计基线,避免下次连接首帧显示错误的瞬时速率。
|
||||
_prevBytesReceived = 0;
|
||||
|
||||
Reference in New Issue
Block a user