mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-02-01 10:31:54 +08:00
feat(rustdesk): 优化视频编码协商和添加公共服务器支持
- 调整视频编码优先级为 H264 > H265 > VP8 > VP9,优先使用硬件编码 - 对接 RustDesk 客户端质量预设 (Low/Balanced/Best) 到 BitratePreset - 添加 secrets.toml 编译时读取机制,支持配置公共服务器 - 默认公共服务器: rustdesk.mofeng.run:21116 - 前端 ID 服务器输入框添加问号提示,显示公共服务器信息 - 用户留空时自动使用公共服务器
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user