feat: 支持手动切换采集帧率
在 Android、Web 及 Flutter 控制端新增帧率选择 UI,被控端按屏幕刷新率生成支持的帧率档位并上报,允许在保持分辨率不变的情况下仅切换帧率。 另附带更新信号服务器的数据库配置。
This commit is contained in:
@@ -34,6 +34,8 @@ struct ControlMessage {
|
||||
var height: Int32 = 0 // field 13, varint
|
||||
var fps: Int32 = 0 // field 14, varint
|
||||
var streamMode: Int32 = 0 // field 15, varint (0=WebRTC 1=自编码)
|
||||
/// 被控端支持的帧率档位列表(REPORT_RESOLUTION 上报,按屏幕刷新率筛选,升序)。
|
||||
var supportedFps: [Int32] = [] // field 16, repeated varint (packed)
|
||||
|
||||
// MARK: - Encode
|
||||
|
||||
@@ -54,6 +56,16 @@ struct ControlMessage {
|
||||
w.writeVarintField(13, UInt64(bitPattern: Int64(height)))
|
||||
w.writeVarintField(14, UInt64(bitPattern: Int64(fps)))
|
||||
w.writeVarintField(15, UInt64(bitPattern: Int64(streamMode)))
|
||||
// repeated int32 supported_fps = 16(packed 编码);空列表不编码
|
||||
if !supportedFps.isEmpty {
|
||||
var payload = ProtoWriter()
|
||||
for v in supportedFps {
|
||||
payload.writeVarint(UInt64(bitPattern: Int64(v)))
|
||||
}
|
||||
w.writeVarint(UInt64(16 << 3 | 2))
|
||||
w.writeVarint(UInt64(payload.data.count))
|
||||
w.data.append(payload.data)
|
||||
}
|
||||
return w.data
|
||||
}
|
||||
|
||||
@@ -81,6 +93,19 @@ struct ControlMessage {
|
||||
case (13, 0): guard let v = r.readVarint() else { return nil }; msg.height = Int32(truncatingIfNeeded: Int64(bitPattern: v))
|
||||
case (14, 0): guard let v = r.readVarint() else { return nil }; msg.fps = Int32(truncatingIfNeeded: Int64(bitPattern: v))
|
||||
case (15, 0): guard let v = r.readVarint() else { return nil }; msg.streamMode = Int32(truncatingIfNeeded: Int64(bitPattern: v))
|
||||
case (16, 2):
|
||||
// packed repeated int32:先读长度,再在区间内逐个读 varint
|
||||
guard let len = r.readVarint() else { return nil }
|
||||
let end = r.offset + Int(len)
|
||||
guard end <= data.endIndex else { return nil }
|
||||
while r.offset < end {
|
||||
guard let v = r.readVarint() else { return nil }
|
||||
msg.supportedFps.append(Int32(truncatingIfNeeded: Int64(bitPattern: v)))
|
||||
}
|
||||
case (16, 0):
|
||||
// 兼容非 packed 编码(逐项 varint)
|
||||
guard let v = r.readVarint() else { return nil }
|
||||
msg.supportedFps.append(Int32(truncatingIfNeeded: Int64(bitPattern: v)))
|
||||
default:
|
||||
// 未知字段按 wire type 跳过
|
||||
if !r.skip(wireType: wire) { return nil }
|
||||
|
||||
@@ -56,6 +56,10 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
@Published var statsText: String = ""
|
||||
@Published var streamMode: StreamMode = .webrtc
|
||||
@Published var selectedResolution: ResolutionOption = ResolutionOption.all[0]
|
||||
/// 帧率档位:默认常用档位,收到被控端上报的 supported_fps 后以上报列表为准
|
||||
@Published var fpsOptions: [Int] = [15, 24, 30, 60]
|
||||
/// 被控端当前采集帧率(0 表示尚未收到上报)
|
||||
@Published var currentFps: Int = 0
|
||||
/// 远端视频宽高比(宽/高),用于让触控层与画面精确对齐
|
||||
@Published var videoAspect: CGFloat = 9.0 / 16.0
|
||||
@Published var showAuthSheet: Bool = false
|
||||
@@ -82,6 +86,9 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
private var pendingAuthValue: String = ""
|
||||
/// 是否为本端主动切换串流模式(避免上报回环再次发送)
|
||||
private var suppressStreamModeSend = false
|
||||
/// 被控端最近上报的实际采集尺寸(仅切帧率时保持分辨率不变,0 表示原生)
|
||||
private var lastReportedWidth: Int32 = 0
|
||||
private var lastReportedHeight: Int32 = 0
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
@@ -138,6 +145,10 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
statsText = ""
|
||||
streamMode = .webrtc
|
||||
selectedResolution = ResolutionOption.all[0]
|
||||
fpsOptions = [15, 24, 30, 60]
|
||||
currentFps = 0
|
||||
lastReportedWidth = 0
|
||||
lastReportedHeight = 0
|
||||
videoAspect = 9.0 / 16.0
|
||||
}
|
||||
|
||||
@@ -192,6 +203,14 @@ final class ControllerViewModel: NSObject, ObservableObject {
|
||||
webRTCClient?.requestResolutionChange(width: option.width, height: option.height, fps: 0)
|
||||
}
|
||||
|
||||
/// 仅切换帧率:分辨率沿用被控端最近上报的实际采集尺寸(0 表示原生)
|
||||
func selectFps(_ fps: Int) {
|
||||
guard fps > 0, fps != currentFps else { return }
|
||||
webRTCClient?.requestResolutionChange(width: lastReportedWidth,
|
||||
height: lastReportedHeight,
|
||||
fps: Int32(fps))
|
||||
}
|
||||
|
||||
func toggleStreamMode(_ selfCodecOn: Bool) {
|
||||
let newMode: StreamMode = selfCodecOn ? .selfCodec : .webrtc
|
||||
guard newMode != streamMode else { return }
|
||||
@@ -441,10 +460,17 @@ extension ControllerViewModel: WebRTCClientDelegate {
|
||||
|
||||
func webRTCClient(didReportResolution width: Int, height: Int) {
|
||||
guard width > 0, height > 0 else { return }
|
||||
lastReportedWidth = Int32(width)
|
||||
lastReportedHeight = Int32(height)
|
||||
if streamMode == .webrtc {
|
||||
videoAspect = CGFloat(width) / CGFloat(height)
|
||||
}
|
||||
}
|
||||
|
||||
func webRTCClient(didReportFps fps: Int, supportedFps: [Int]) {
|
||||
if fps > 0 { currentFps = fps }
|
||||
if !supportedFps.isEmpty { fpsOptions = supportedFps }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RTCVideoViewDelegate(WebRTC 模式下画面尺寸变化)
|
||||
|
||||
@@ -147,6 +147,25 @@ private struct ControlPanelView: View {
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
// 帧率选择(档位来自被控端 REPORT_RESOLUTION 上报)
|
||||
Menu {
|
||||
ForEach(viewModel.fpsOptions, id: \.self) { fps in
|
||||
Button {
|
||||
viewModel.selectFps(fps)
|
||||
} label: {
|
||||
if fps == viewModel.currentFps {
|
||||
Label("\(fps)fps", systemImage: "checkmark")
|
||||
} else {
|
||||
Text("\(fps)fps")
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label(viewModel.currentFps > 0 ? "\(viewModel.currentFps)fps" : "帧率",
|
||||
systemImage: "speedometer")
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
viewModel.disconnect()
|
||||
} label: {
|
||||
|
||||
@@ -12,6 +12,8 @@ protocol WebRTCClientDelegate: AnyObject {
|
||||
func webRTCClient(didReportStreamMode mode: Int)
|
||||
/// 被控端上报当前采集分辨率
|
||||
func webRTCClient(didReportResolution width: Int, height: Int)
|
||||
/// 被控端上报当前采集帧率与支持的帧率档位(按屏幕刷新率筛选,升序)
|
||||
func webRTCClient(didReportFps fps: Int, supportedFps: [Int])
|
||||
}
|
||||
|
||||
/// WebRTC 客户端:负责 PeerConnection 的创建、Offer/Answer 协商、
|
||||
@@ -313,6 +315,8 @@ extension WebRTCClient: RTCDataChannelDelegate {
|
||||
self.delegate?.webRTCClient(didReportStreamMode: Int(msg.streamMode))
|
||||
case .reportResolution:
|
||||
self.delegate?.webRTCClient(didReportResolution: Int(msg.width), height: Int(msg.height))
|
||||
self.delegate?.webRTCClient(didReportFps: Int(msg.fps),
|
||||
supportedFps: msg.supportedFps.map(Int.init))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user