feat: 支持手动切换采集帧率
在 Android、Web 及 Flutter 控制端新增帧率选择 UI,被控端按屏幕刷新率生成支持的帧率档位并上报,允许在保持分辨率不变的情况下仅切换帧率。 另附带更新信号服务器的数据库配置。
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
// 重置网速统计基线,避免下次连接首帧显示错误的瞬时速率。
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user