feat(rustdesk): 优化视频编码协商和添加公共服务器支持

- 调整视频编码优先级为 H264 > H265 > VP8 > VP9,优先使用硬件编码
- 对接 RustDesk 客户端质量预设 (Low/Balanced/Best) 到 BitratePreset
- 添加 secrets.toml 编译时读取机制,支持配置公共服务器
- 默认公共服务器: rustdesk.mofeng.run:21116
- 前端 ID 服务器输入框添加问号提示,显示公共服务器信息
- 用户留空时自动使用公共服务器
This commit is contained in:
mofeng-git
2026-01-02 17:22:34 +08:00
parent be4de59f3b
commit 28ecf951df
29 changed files with 776 additions and 316 deletions

View File

@@ -5,6 +5,8 @@
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::secrets;
/// RustDesk configuration
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -15,6 +17,7 @@ pub struct RustDeskConfig {
/// Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100"
/// Port defaults to 21116 if not specified
/// If empty, uses the public server from secrets.toml
pub rendezvous_server: String,
/// Relay server address (hbbr), if different from rendezvous server
@@ -70,13 +73,41 @@ impl Default for RustDeskConfig {
impl RustDeskConfig {
/// Check if the configuration is valid for starting the service
/// Returns true if enabled and has a valid server (user-configured or public)
pub fn is_valid(&self) -> bool {
self.enabled
&& !self.rendezvous_server.is_empty()
&& !self.effective_rendezvous_server().is_empty()
&& !self.device_id.is_empty()
&& !self.device_password.is_empty()
}
/// Check if using the public server (user left rendezvous_server empty)
pub fn is_using_public_server(&self) -> bool {
self.rendezvous_server.is_empty() && secrets::rustdesk::has_public_server()
}
/// Get the effective rendezvous server (user-configured or public fallback)
pub fn effective_rendezvous_server(&self) -> &str {
if self.rendezvous_server.is_empty() {
secrets::rustdesk::PUBLIC_SERVER
} else {
&self.rendezvous_server
}
}
/// Get public server info for display (server address and public key)
/// Returns None if no public server is configured
pub fn public_server_info() -> Option<PublicServerInfo> {
if secrets::rustdesk::has_public_server() {
Some(PublicServerInfo {
server: secrets::rustdesk::PUBLIC_SERVER.to_string(),
public_key: secrets::rustdesk::PUBLIC_KEY.to_string(),
})
} else {
None
}
}
/// Generate a new random device ID
pub fn generate_device_id() -> String {
generate_device_id()
@@ -111,10 +142,11 @@ impl RustDeskConfig {
/// Get the rendezvous server address with default port
pub fn rendezvous_addr(&self) -> String {
if self.rendezvous_server.contains(':') {
self.rendezvous_server.clone()
let server = self.effective_rendezvous_server();
if server.contains(':') {
server.to_string()
} else {
format!("{}:21116", self.rendezvous_server)
format!("{}:21116", server)
}
}
@@ -127,9 +159,10 @@ impl RustDeskConfig {
format!("{}:21117", s)
}
}).or_else(|| {
// Default: same host as rendezvous server
if !self.rendezvous_server.is_empty() {
let host = self.rendezvous_server.split(':').next().unwrap_or("");
// Default: same host as effective rendezvous server
let server = self.effective_rendezvous_server();
if !server.is_empty() {
let host = server.split(':').next().unwrap_or("");
if !host.is_empty() {
Some(format!("{}:21117", host))
} else {
@@ -142,6 +175,16 @@ impl RustDeskConfig {
}
}
/// Public server information for display to users
#[derive(Debug, Clone, Serialize, Deserialize)]
#[typeshare]
pub struct PublicServerInfo {
/// Public server address
pub server: String,
/// Public key for client connection
pub public_key: String,
}
/// Generate a random 9-digit device ID
pub fn generate_device_id() -> String {
use rand::Rng;
@@ -196,9 +239,6 @@ mod tests {
fn test_relay_addr() {
let mut config = RustDeskConfig::default();
// No server configured
assert!(config.relay_addr().is_none());
// Rendezvous server configured, relay defaults to same host
config.rendezvous_server = "example.com".to_string();
assert_eq!(config.relay_addr(), Some("example.com:21117".to_string()));
@@ -207,4 +247,19 @@ mod tests {
config.relay_server = Some("relay.example.com".to_string());
assert_eq!(config.relay_addr(), Some("relay.example.com:21117".to_string()));
}
#[test]
fn test_effective_rendezvous_server() {
let mut config = RustDeskConfig::default();
// When user sets a server, use it
config.rendezvous_server = "custom.example.com".to_string();
assert_eq!(config.effective_rendezvous_server(), "custom.example.com");
// When empty, falls back to public server (if configured)
config.rendezvous_server = String::new();
// This will return PUBLIC_SERVER from secrets
let effective = config.effective_rendezvous_server();
assert!(!effective.is_empty() || !secrets::rustdesk::has_public_server());
}
}

View File

@@ -22,6 +22,7 @@ use tracing::{debug, error, info, warn};
use crate::hid::HidController;
use crate::video::encoder::registry::{EncoderRegistry, VideoEncoderType};
use crate::video::encoder::BitratePreset;
use crate::video::stream_manager::VideoStreamManager;
use super::bytes_codec::{read_frame, write_frame};
@@ -507,7 +508,7 @@ impl Connection {
*self.state.write() = ConnectionState::Active;
// Select the best available video codec
// Priority: VP8 > VP9 > H264 > H265 (VP8/VP9 are more widely supported by software decoders)
// Priority: H264 > H265 > VP8 > VP9 (H264/H265 leverage hardware encoding)
let negotiated = self.negotiate_video_codec();
self.negotiated_codec = Some(negotiated);
info!("Negotiated video codec: {:?}", negotiated);
@@ -519,28 +520,29 @@ impl Connection {
}
/// Negotiate video codec - select the best available encoder
/// Priority: VP8 > VP9 > H264 > H265 (VP8/VP9 have better software decoder support)
/// Priority: H264 > H265 > VP8 > VP9 (H264/H265 leverage hardware encoding on embedded devices)
fn negotiate_video_codec(&self) -> VideoEncoderType {
let registry = EncoderRegistry::global();
// Check availability in priority order
// VP8 is preferred because it has the best compatibility with software decoders
if registry.is_format_available(VideoEncoderType::VP8, false) {
return VideoEncoderType::VP8;
}
if registry.is_format_available(VideoEncoderType::VP9, false) {
return VideoEncoderType::VP9;
}
// H264 is preferred because it has the best hardware encoder support (RKMPP, VAAPI, etc.)
// and most RustDesk clients support H264 hardware decoding
if registry.is_format_available(VideoEncoderType::H264, false) {
return VideoEncoderType::H264;
}
if registry.is_format_available(VideoEncoderType::H265, false) {
return VideoEncoderType::H265;
}
if registry.is_format_available(VideoEncoderType::VP8, false) {
return VideoEncoderType::VP8;
}
if registry.is_format_available(VideoEncoderType::VP9, false) {
return VideoEncoderType::VP9;
}
// Fallback to VP8 (should always be available via libvpx)
warn!("No video encoder available, defaulting to VP8");
VideoEncoderType::VP8
// Fallback to H264 (should be available via hardware or software encoder)
warn!("No video encoder available, defaulting to H264");
VideoEncoderType::H264
}
/// Handle misc message with Arc writer
@@ -575,8 +577,30 @@ impl Connection {
Ok(())
}
/// Handle Option message from client (includes codec preference)
/// Handle Option message from client (includes codec and quality preferences)
async fn handle_option_message(&mut self, opt: &hbb::OptionMessage) -> anyhow::Result<()> {
// Handle image quality preset
// RustDesk ImageQuality: NotSet=0, Low=2, Balanced=3, Best=4
// Map to One-KVM BitratePreset: Low->Speed, Balanced->Balanced, Best->Quality
let image_quality = opt.image_quality;
if image_quality != 0 {
let preset = match image_quality {
2 => Some(BitratePreset::Speed), // Low -> Speed (1 Mbps)
3 => Some(BitratePreset::Balanced), // Balanced -> Balanced (4 Mbps)
4 => Some(BitratePreset::Quality), // Best -> Quality (8 Mbps)
_ => None,
};
if let Some(preset) = preset {
info!("Client requested quality preset: {:?} (image_quality={})", preset, image_quality);
if let Some(ref video_manager) = self.video_manager {
if let Err(e) = video_manager.set_bitrate_preset(preset).await {
warn!("Failed to set bitrate preset: {}", e);
}
}
}
}
// Check if client sent supported_decoding with a codec preference
if let Some(ref supported_decoding) = opt.supported_decoding {
let prefer = supported_decoding.prefer;
@@ -616,9 +640,9 @@ impl Connection {
}
}
// Log other options for debugging
// Log custom_image_quality (accept but don't process)
if opt.custom_image_quality > 0 {
debug!("Client requested image quality: {}", opt.custom_image_quality);
debug!("Client sent custom_image_quality: {} (ignored)", opt.custom_image_quality);
}
if opt.custom_fps > 0 {
debug!("Client requested FPS: {}", opt.custom_fps);
@@ -665,7 +689,7 @@ impl Connection {
let state = self.state.clone();
let conn_id = self.id;
let shutdown_tx = self.shutdown_tx.clone();
let negotiated_codec = self.negotiated_codec.unwrap_or(VideoEncoderType::VP8);
let negotiated_codec = self.negotiated_codec.unwrap_or(VideoEncoderType::H264);
let task = tokio::spawn(async move {
info!("Starting video streaming for connection {} with codec {:?}", conn_id, negotiated_codec);
@@ -1298,12 +1322,12 @@ async fn run_video_streaming(
// Get encoding config for logging
if let Some(config) = video_manager.get_encoding_config().await {
info!(
"RustDesk connection {} using shared video pipeline: {:?} {}x{} @ {} kbps",
"RustDesk connection {} using shared video pipeline: {:?} {}x{} @ {}",
conn_id,
config.output_codec,
config.resolution.width,
config.resolution.height,
config.bitrate_kbps
config.bitrate_preset
);
}