feat: 支持手动切换采集帧率

在 Android、Web 及 Flutter 控制端新增帧率选择 UI,被控端按屏幕刷新率生成支持的帧率档位并上报,允许在保持分辨率不变的情况下仅切换帧率。

另附带更新信号服务器的数据库配置。
This commit is contained in:
TongTongStudio
2026-07-31 03:26:40 +08:00
parent c3602493dd
commit 362b9f238a
23 changed files with 483 additions and 13 deletions

View File

@@ -60,6 +60,10 @@ class RemoteController {
/// 被控端/解码器上报分辨率。
void Function(int width, int height)? onResolutionReported;
/// 被控端上报当前采集帧率与支持的帧率档位。
void Function(int width, int height, int fps, List<int> supportedFps)?
onFpsReport;
/// 当前平台不支持原生硬解时回调。
void Function()? onSelfCodecNotSupported;
@@ -116,6 +120,8 @@ class RemoteController {
_webRtc!.onSelfCodecLost = () => onSelfCodecLost?.call();
_webRtc!.onStreamModeReport = (mode) => onStreamModeReport?.call(mode);
_webRtc!.onResolutionReported = (w, h) => onResolutionReported?.call(w, h);
_webRtc!.onFpsReport =
(w, h, fps, list) => onFpsReport?.call(w, h, fps, list);
_webRtc!.onSelfCodecNotSupported = () => onSelfCodecNotSupported?.call();
_webRtc!.initialize().catchError((e) {
onStatusChanged?.call('状态: 连接失败 - $e');

View File

@@ -82,6 +82,16 @@ class _ControllerHomeState extends State<ControllerHome> {
{'label': '480P', 'width': 854, 'height': 0, 'fps': 0},
];
/// 帧率档位:默认常用档位,收到被控端上报的 supported_fps 后以上报列表为准。
List<int> _fpsOptions = const [15, 24, 30, 60];
/// 被控端当前采集帧率0 表示尚未收到上报)。
int _currentFps = 0;
/// 被控端最近上报的实际采集尺寸仅切帧率时保持分辨率不变0 表示原生)。
int _lastReportedWidth = 0;
int _lastReportedHeight = 0;
@override
void initState() {
super.initState();
@@ -354,6 +364,16 @@ class _ControllerHomeState extends State<ControllerHome> {
_resizeWindowToAspect(aspect);
}
};
// 被控端上报当前帧率与支持的帧率档位 -> 同步帧率菜单
_controller!.onFpsReport = (w, h, fps, supportedFps) {
if (!mounted) return;
setState(() {
if (w > 0) _lastReportedWidth = w;
if (h > 0) _lastReportedHeight = h;
if (fps > 0) _currentFps = fps;
if (supportedFps.isNotEmpty) _fpsOptions = supportedFps;
});
};
_controller!.onSelfCodecNotSupported = () {
if (mounted) setState(() => _selfCodecSupported = false);
_showAlert('当前平台不支持自编码硬解,已回退到 WebRTC 媒体流。');
@@ -447,6 +467,47 @@ class _ControllerHomeState extends State<ControllerHome> {
);
}
/// 弹出帧率选择菜单iOS 风格 ActionSheet仅切帧率分辨率保持不变。
void _showFpsMenu() {
showCupertinoModalPopup<void>(
context: context,
builder: (ctx) => CupertinoActionSheet(
title: const Text('切换帧率'),
actions: [
for (final fps in _fpsOptions)
CupertinoActionSheetAction(
onPressed: () {
Navigator.of(ctx).pop();
if (fps <= 0 || fps == _currentFps) return;
// 分辨率沿用被控端最近上报的实际采集尺寸0 表示原生)
_controller?.sendResolutionChange(
_lastReportedWidth,
_lastReportedHeight,
fps,
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (fps == _currentFps) ...[
const Icon(CupertinoIcons.check_mark,
size: 18, color: CupertinoColors.activeBlue),
const SizedBox(width: 8),
],
Text('${fps}fps'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.of(ctx).pop(),
child: const Text('取消'),
),
),
);
}
/// 切换串流模式WebRTC 全托管 <-> 自编码(自建 MediaCodec 解码)。
void _toggleStreamMode() {
if (!_selfCodecSupported) {
@@ -534,6 +595,10 @@ class _ControllerHomeState extends State<ControllerHome> {
_recording = false;
_recordStatus = '';
_pendingRecordStart = false;
_currentFps = 0;
_lastReportedWidth = 0;
_lastReportedHeight = 0;
_fpsOptions = const [15, 24, 30, 60];
});
}
@@ -636,6 +701,26 @@ class _ControllerHomeState extends State<ControllerHome> {
],
),
),
// 帧率菜单按钮:显示被控端当前采集帧率
CupertinoButton(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
onPressed: _showFpsMenu,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.speedometer,
color: CupertinoColors.white, size: 20),
const SizedBox(width: 6),
Text(
_currentFps > 0 ? '${_currentFps}fps' : '帧率',
style: const TextStyle(
color: CupertinoColors.white,
fontSize: 14,
),
),
],
),
),
// 自编码串流开关:仅 Android 等支持原生硬解的平台可用。
CupertinoButton(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),

View File

@@ -40,6 +40,7 @@ class ControlMessage extends $pb.GeneratedMessage {
$core.int? height,
$core.int? fps,
$core.int? streamMode,
$core.Iterable<$core.int>? supportedFps,
}) {
final result = create();
if (action != null) result.action = action;
@@ -57,6 +58,7 @@ class ControlMessage extends $pb.GeneratedMessage {
if (height != null) result.height = height;
if (fps != null) result.fps = fps;
if (streamMode != null) result.streamMode = streamMode;
if (supportedFps != null) result.supportedFps.addAll(supportedFps);
return result;
}
@@ -89,6 +91,7 @@ class ControlMessage extends $pb.GeneratedMessage {
..aI(13, _omitFieldNames ? '' : 'height')
..aI(14, _omitFieldNames ? '' : 'fps')
..aI(15, _omitFieldNames ? '' : 'stream_mode')
..p<$core.int>(16, _omitFieldNames ? '' : 'supportedFps', $pb.PbFieldType.K3)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@@ -254,6 +257,10 @@ class ControlMessage extends $pb.GeneratedMessage {
$core.bool hasStreamMode() => $_has(14);
@$pb.TagNumber(15)
void clearStreamMode() => $_clearField(15);
/// 被控端支持的帧率档位列表REPORT_RESOLUTION 上报,按屏幕刷新率筛选,升序)。
@$pb.TagNumber(16)
$pb.PbList<$core.int> get supportedFps => $_getList(15);
}
const $core.bool _omitFieldNames =

View File

@@ -77,6 +77,10 @@ class WebRtcController {
/// 被控端上报分辨率(自编码解码器输出尺寸变化时也会触发)。
void Function(int width, int height)? onResolutionReported;
/// 被控端上报当前采集帧率与支持的帧率档位(仅 REPORT_RESOLUTION 触发)。
void Function(int width, int height, int fps, List<int> supportedFps)?
onFpsReport;
/// 当前平台Windows / Linux / Web / iOS 等)不支持原生硬解时回调。
void Function()? onSelfCodecNotSupported;
@@ -370,6 +374,9 @@ class WebRtcController {
_selfCodecHeight = msg.height;
}
onResolutionReported?.call(msg.width, msg.height);
// 同步帧率信息(当前帧率 + 被控端按刷新率筛选的帧率档位)
onFpsReport?.call(
msg.width, msg.height, msg.fps, msg.supportedFps.toList());
break;
default:
// ignore: avoid_print
@@ -794,6 +801,7 @@ class WebRtcController {
onSelfCodecReady = null;
onStreamModeReport = null;
onResolutionReported = null;
onFpsReport = null;
onSelfCodecNotSupported = null;
_pc = null;
// 重置网速统计基线,避免下次连接首帧显示错误的瞬时速率。

View File

@@ -54,4 +54,7 @@ message ControlMessage {
// 串流模式SET_STREAM_MODE / REPORT_STREAM_MODE
// 0 = WebRTC 全托管1 = 自编码(自建 MediaCodec 编解码 + video DataChannel 裸流透传)
int32 stream_mode = 15;
// 被控端支持的帧率档位列表REPORT_RESOLUTION 上报,按屏幕刷新率筛选,升序)。
repeated int32 supported_fps = 16;
}