diff --git a/apps/desktop/src/config_center/config_center.cpp b/apps/desktop/src/config_center/config_center.cpp
index 7ffbb487..089f2f3a 100644
--- a/apps/desktop/src/config_center/config_center.cpp
+++ b/apps/desktop/src/config_center/config_center.cpp
@@ -1,6 +1,7 @@
#include "config_center.h"
#include
+#include
#include
#include
#include
@@ -37,6 +38,21 @@ bool IsValidTurnModeValue(long value) {
value <= static_cast(ConfigCenter::TURN_MODE::FORCE_TCP);
}
+int ReadStartingBitrate(const CSimpleIniA& ini, const char* section,
+ const char* key, int fallback) {
+ const char* value = ini.GetValue(section, key, nullptr);
+ if (!value) return fallback;
+ const char* end = value + std::strlen(value);
+ int bps = 0;
+ const auto parsed = std::from_chars(value, end, bps);
+ if (parsed.ec != std::errc{} || parsed.ptr != end ||
+ bps < 300'000 || bps > 20'000'000) {
+ LOG_WARN("Invalid {} configuration, using {} bps", key, fallback);
+ return fallback;
+ }
+ return bps;
+}
+
} // namespace
ConfigCenter::ConfigCenter(const std::string& config_path)
@@ -129,6 +145,20 @@ int ConfigCenter::Load() {
ini_->SetBoolValue(section_, "enable_srtp", true);
persist_config_migration = true;
}
+ direct_start_bitrate_bps_ = ReadStartingBitrate(
+ *ini_, section_, "direct_start_bitrate_bps", kDefaultDirectStartBitrateBps);
+ relay_start_bitrate_bps_ = ReadStartingBitrate(
+ *ini_, section_, "relay_start_bitrate_bps", kDefaultRelayStartBitrateBps);
+ // Populate old configurations and repair invalid values so the file shows
+ // the estimates that will actually be used for subsequent connections.
+ for (const auto& entry :
+ {std::make_pair("direct_start_bitrate_bps", direct_start_bitrate_bps_),
+ std::make_pair("relay_start_bitrate_bps", relay_start_bitrate_bps_)}) {
+ if (std::to_string(entry.second) != ini_->GetValue(section_, entry.first, "")) {
+ ini_->SetLongValue(section_, entry.first, entry.second);
+ persist_config_migration = true;
+ }
+ }
enable_self_hosted_ =
ini_->GetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
@@ -244,6 +274,8 @@ int ConfigCenter::Save() {
ini_->SetLongValue(section_, "video_encode_format",
static_cast(video_encode_format_));
ini_->SetLongValue(section_, "turn_mode", static_cast(turn_mode_));
+ ini_->SetLongValue(section_, "direct_start_bitrate_bps", direct_start_bitrate_bps_);
+ ini_->SetLongValue(section_, "relay_start_bitrate_bps", relay_start_bitrate_bps_);
ini_->SetBoolValue(section_, "enable_turn",
turn_mode_ != TURN_MODE::DISABLED);
ini_->SetBoolValue(section_, "enable_srtp", true);
@@ -292,6 +324,8 @@ std::string ConfigCenter::ExportSharedHostConfig() const {
host.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
host.SetLongValue(section_, "signal_server_port", signal_server_port_);
host.SetLongValue(section_, "turn_mode", static_cast(turn_mode_));
+ host.SetLongValue(section_, "direct_start_bitrate_bps", direct_start_bitrate_bps_);
+ host.SetLongValue(section_, "relay_start_bitrate_bps", relay_start_bitrate_bps_);
host.SetLongValue(section_, "video_encode_format", static_cast(video_encode_format_));
host.SetBoolValue(section_, "trusted_devices_only", trusted_devices_only_.load());
host.SetBoolValue(section_, "lock_on_disconnect", lock_on_disconnect_.load());
@@ -313,6 +347,10 @@ int ConfigCenter::ApplySharedHostConfig(const std::string& contents) {
const long port = host.GetLongValue(section_, "signal_server_port", 0);
const long turn = host.GetLongValue(section_, "turn_mode", static_cast(turn_mode_));
const long codec = host.GetLongValue(section_, "video_encode_format", static_cast(video_encode_format_));
+ const int direct_bps = ReadStartingBitrate(
+ host, section_, "direct_start_bitrate_bps", kDefaultDirectStartBitrateBps);
+ const int relay_bps = ReadStartingBitrate(
+ host, section_, "relay_start_bitrate_bps", kDefaultRelayStartBitrateBps);
const bool trusted = host.GetBoolValue(section_, "trusted_devices_only", false);
const bool lock = host.GetBoolValue(section_, "lock_on_disconnect", true);
MiniRtcSignalServerAddress address{};
@@ -322,12 +360,16 @@ int ConfigCenter::ApplySharedHostConfig(const std::string& contents) {
if (StoreValues({{"enable_self_hosted", self_hosted ? "true" : "false"},
{"signal_server_host", server}, {"signal_server_port", std::to_string(port)},
{"turn_mode", std::to_string(turn)}, {"video_encode_format", std::to_string(codec)},
+ {"direct_start_bitrate_bps", std::to_string(direct_bps)},
+ {"relay_start_bitrate_bps", std::to_string(relay_bps)},
{"trusted_devices_only", trusted ? "true" : "false"},
{"lock_on_disconnect", lock ? "true" : "false"}}) != 0) return -1;
enable_self_hosted_ = self_hosted;
signal_server_host_ = server;
signal_server_port_ = static_cast(port);
turn_mode_ = static_cast(turn);
+ direct_start_bitrate_bps_ = direct_bps;
+ relay_start_bitrate_bps_ = relay_bps;
video_encode_format_ = static_cast(codec);
trusted_devices_only_.store(trusted);
lock_on_disconnect_.store(lock);
@@ -614,6 +656,14 @@ ConfigCenter::TURN_MODE ConfigCenter::GetTurnMode() const {
return turn_mode_;
}
+int ConfigCenter::GetDirectStartBitrateBps() const {
+ return direct_start_bitrate_bps_;
+}
+
+int ConfigCenter::GetRelayStartBitrateBps() const {
+ return relay_start_bitrate_bps_;
+}
+
bool ConfigCenter::IsForceRelay() const {
return turn_mode_ == TURN_MODE::FORCE_UDP || turn_mode_ == TURN_MODE::FORCE_TCP;
}
diff --git a/apps/desktop/src/config_center/config_center.h b/apps/desktop/src/config_center/config_center.h
index 3cb2e015..1bbb4198 100644
--- a/apps/desktop/src/config_center/config_center.h
+++ b/apps/desktop/src/config_center/config_center.h
@@ -67,6 +67,8 @@ class ConfigCenter {
bool IsIpDirectEnabled() const;
VIDEO_ENCODE_FORMAT GetVideoEncodeFormat() const;
TURN_MODE GetTurnMode() const;
+ int GetDirectStartBitrateBps() const;
+ int GetRelayStartBitrateBps() const;
bool IsForceRelay() const;
bool IsEnableSrtp() const;
std::string GetSignalServerHost() const;
@@ -106,6 +108,10 @@ class ConfigCenter {
bool enable_ip_direct_ = true;
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
TURN_MODE turn_mode_ = TURN_MODE::AUTO_UDP_TCP;
+ static constexpr int kDefaultDirectStartBitrateBps = 1'500'000;
+ static constexpr int kDefaultRelayStartBitrateBps = 500'000;
+ int direct_start_bitrate_bps_ = kDefaultDirectStartBitrateBps;
+ int relay_start_bitrate_bps_ = kDefaultRelayStartBitrateBps;
std::string signal_server_host_ = "";
std::string signal_server_host_default_ = "api.crossdesk.cn";
int signal_server_port_ = 0;
diff --git a/apps/desktop/src/gui/runtime/gui_runtime.cpp b/apps/desktop/src/gui/runtime/gui_runtime.cpp
index 13b32a2e..644aca0b 100644
--- a/apps/desktop/src/gui/runtime/gui_runtime.cpp
+++ b/apps/desktop/src/gui/runtime/gui_runtime.cpp
@@ -316,6 +316,8 @@ int GuiRuntime::CreateConnectionPeer() {
? true
: false;
params_.turn_mode = static_cast(config_center_->GetTurnMode());
+ params_.direct_start_bitrate_bps = config_center_->GetDirectStartBitrateBps();
+ params_.relay_start_bitrate_bps = config_center_->GetRelayStartBitrateBps();
params_.enable_srtp = config_center_->IsEnableSrtp();
params_.video_content_type = MiniRtcVideoContentType::ScreenContent;
// Bootstrap outgoing media with fixed defaults. Live overrides come only
diff --git a/apps/desktop/src/gui/runtime/lan_runtime.cpp b/apps/desktop/src/gui/runtime/lan_runtime.cpp
index 44892d61..4c666c92 100644
--- a/apps/desktop/src/gui/runtime/lan_runtime.cpp
+++ b/apps/desktop/src/gui/runtime/lan_runtime.cpp
@@ -47,6 +47,8 @@ void GuiRuntime::InitializeLan() {
ConfigCenter::VIDEO_ENCODE_FORMAT::AV1;
params.enable_srtp = true;
params.turn_mode = MiniRtcTurnMode::MiniRtcTurnDisabled;
+ params.direct_start_bitrate_bps = config_center_->GetDirectStartBitrateBps();
+ params.relay_start_bitrate_bps = config_center_->GetRelayStartBitrateBps();
params.video_content_type = MiniRtcVideoContentType::ScreenContent;
params.video_quality = MiniRtcVideoQuality::MiniRtcVideoQualityHigh;
params.video_frame_rate = 60;
diff --git a/deps/submodules/minirtc b/deps/submodules/minirtc
index 97a64019..195f44c0 160000
--- a/deps/submodules/minirtc
+++ b/deps/submodules/minirtc
@@ -1 +1 @@
-Subproject commit 97a64019300940a63982ea3b94116646f12abc3f
+Subproject commit 195f44c0fe68f3d0e8831cf9c1ab084b64582dbe
diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md
index 05e29626..2b5e7af6 100644
--- a/docs/USER_GUIDE.md
+++ b/docs/USER_GUIDE.md
@@ -186,6 +186,22 @@ macOS 保留一个 CrossDesk Dock 应用图标,主窗口与远程控制窗口
+### 起始带宽配置
+
+退出客户端后,编辑缓存目录中 `config.ini` 的 `[Settings]` 节,重启后生效:
+
+```ini
+[Settings]
+direct_start_bitrate_bps = 1500000
+relay_start_bitrate_bps = 500000
+```
+
+两个值的单位均为 bps,分别控制本机原生媒体发送端在 P2P / IP 直连和 TURN 中继路径上的拥塞控制起始估计。桌面端默认值为 1.5 Mbps 和 0.5 Mbps,允许范围为 300000–20000000;缺失或非法值使用对应默认值,启动时补写到配置文件。连接后仍按网络反馈动态调整,桌面墙带宽预算继续生效。观看远程画面时,应修改发送画面的被控端配置。浏览器连接使用独立的带宽控制,不受这两个选项影响。
+
+该文件在 macOS / Linux 上默认位于 `~/.cache/CrossDesk/`(设置 `XDG_CACHE_HOME` 时使用该目录下的 `CrossDesk/`);Windows 安装版 Release 位于 `%LOCALAPPDATA%\CrossDesk\cache\`,Windows Debug 使用工作目录下的 `cache/`,便携版使用可执行文件旁的 `data/`。
+
+Windows 已配置后台主机时,以 `%ProgramData%\CrossDeskUnattended\shared-identity.json` 的 `host_config` 中保存的 INI 配置为准。管理员须在 GUI 与后台服务均停止时修改其中这两个配置项,再重启服务和 GUI;单独修改用户缓存或 `host/config.ini` 会在启动时被共享配置覆盖。
+
### 桌面墙画面传输
在 **设置 → 桌面墙** 中统一配置墙内设备,保存后立即应用到当前桌面墙会话,并用于后续连接;普通连接不受影响。
diff --git a/docs/USER_GUIDE_EN.md b/docs/USER_GUIDE_EN.md
index 60d8e21d..6ed6a00c 100644
--- a/docs/USER_GUIDE_EN.md
+++ b/docs/USER_GUIDE_EN.md
@@ -187,6 +187,22 @@ The direct IP switch in the Network group is independent of the relay settings u
+### Starting bandwidth configuration
+
+Quit the client, edit the `[Settings]` section in its cache directory's `config.ini`, then restart:
+
+```ini
+[Settings]
+direct_start_bitrate_bps = 1500000
+relay_start_bitrate_bps = 500000
+```
+
+Both values are in bps and set the local native media sender's initial congestion-control estimate for P2P / direct IP and TURN relay paths, respectively. Desktop defaults are 1.5 Mbps and 0.5 Mbps; the accepted range is 300000–20000000. Missing or invalid values use the corresponding default, which is written to the file on startup. Network feedback continues to adjust bandwidth, and DeskHub bandwidth budgets still apply. To change the stream you watch remotely, edit the host that sends the video. Browser connections use separate bandwidth control and are unaffected.
+
+The file is in `~/.cache/CrossDesk/` on macOS / Linux (`CrossDesk/` under `XDG_CACHE_HOME` when set), and `%LOCALAPPDATA%\CrossDesk\cache\` for installed Windows Release builds. Windows Debug builds use `cache/` under the working directory; portable builds use `data/` beside the executable.
+
+With a Windows background host configured, the INI string in `host_config` inside `%ProgramData%\CrossDeskUnattended\shared-identity.json` is authoritative. An administrator must stop both the GUI and background service before editing these two entries there, then restart both. Editing only the user cache or `host/config.ini` is overwritten by the shared configuration on startup.
+
### DeskHub video transmission
Use **Settings → DeskHub** to configure wall sessions together. Saving applies changes immediately to current DeskHub sessions and future connections; ordinary connections are unaffected.