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:
@@ -19,7 +19,7 @@ pub mod vp8;
|
||||
pub mod vp9;
|
||||
|
||||
// Core traits and types
|
||||
pub use traits::{EncodedFormat, EncodedFrame, Encoder, EncoderConfig, EncoderFactory};
|
||||
pub use traits::{BitratePreset, EncodedFormat, EncodedFrame, Encoder, EncoderConfig, EncoderFactory};
|
||||
|
||||
// WebRTC codec abstraction
|
||||
pub use codec::{CodecFrame, VideoCodec, VideoCodecConfig, VideoCodecFactory, VideoCodecType};
|
||||
|
||||
@@ -1,11 +1,96 @@
|
||||
//! Encoder traits and common types
|
||||
|
||||
use bytes::Bytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::video::format::{PixelFormat, Resolution};
|
||||
use crate::error::Result;
|
||||
|
||||
/// Bitrate preset for video encoding
|
||||
///
|
||||
/// Simplifies bitrate configuration by providing three intuitive presets
|
||||
/// plus a custom option for advanced users.
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "value")]
|
||||
pub enum BitratePreset {
|
||||
/// Speed priority: 1 Mbps, lowest latency, smaller GOP
|
||||
/// Best for: slow networks, remote management, low-bandwidth scenarios
|
||||
Speed,
|
||||
/// Balanced: 4 Mbps, good quality/latency tradeoff
|
||||
/// Best for: typical usage, recommended default
|
||||
Balanced,
|
||||
/// Quality priority: 8 Mbps, best visual quality
|
||||
/// Best for: local network, high-bandwidth scenarios, detailed work
|
||||
Quality,
|
||||
/// Custom bitrate in kbps (for advanced users)
|
||||
Custom(u32),
|
||||
}
|
||||
|
||||
impl BitratePreset {
|
||||
/// Get bitrate value in kbps
|
||||
pub fn bitrate_kbps(&self) -> u32 {
|
||||
match self {
|
||||
Self::Speed => 1000,
|
||||
Self::Balanced => 4000,
|
||||
Self::Quality => 8000,
|
||||
Self::Custom(kbps) => *kbps,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get recommended GOP size based on preset
|
||||
///
|
||||
/// Speed preset uses shorter GOP for faster recovery from packet loss.
|
||||
/// Quality preset uses longer GOP for better compression efficiency.
|
||||
pub fn gop_size(&self, fps: u32) -> u32 {
|
||||
match self {
|
||||
Self::Speed => (fps / 2).max(15), // 0.5 second, minimum 15 frames
|
||||
Self::Balanced => fps, // 1 second
|
||||
Self::Quality => fps * 2, // 2 seconds
|
||||
Self::Custom(_) => fps, // Default 1 second for custom
|
||||
}
|
||||
}
|
||||
|
||||
/// Get quality preset name for encoder configuration
|
||||
pub fn quality_level(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Speed => "low", // ultrafast/veryfast preset
|
||||
Self::Balanced => "medium", // medium preset
|
||||
Self::Quality => "high", // slower preset, better quality
|
||||
Self::Custom(_) => "medium",
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from kbps value, mapping to nearest preset or Custom
|
||||
pub fn from_kbps(kbps: u32) -> Self {
|
||||
match kbps {
|
||||
0..=1500 => Self::Speed,
|
||||
1501..=6000 => Self::Balanced,
|
||||
6001..=10000 => Self::Quality,
|
||||
_ => Self::Custom(kbps),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BitratePreset {
|
||||
fn default() -> Self {
|
||||
Self::Balanced
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BitratePreset {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Speed => write!(f, "Speed (1 Mbps)"),
|
||||
Self::Balanced => write!(f, "Balanced (4 Mbps)"),
|
||||
Self::Quality => write!(f, "Quality (8 Mbps)"),
|
||||
Self::Custom(kbps) => write!(f, "Custom ({} kbps)", kbps),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoder configuration
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EncoderConfig {
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod encoder;
|
||||
pub mod format;
|
||||
pub mod frame;
|
||||
pub mod h264_pipeline;
|
||||
pub mod pacer;
|
||||
pub mod shared_video_pipeline;
|
||||
pub mod stream_manager;
|
||||
pub mod streamer;
|
||||
@@ -18,6 +19,7 @@ pub mod video_session;
|
||||
pub use capture::VideoCapturer;
|
||||
pub use convert::{MjpegDecoder, MjpegToYuv420Converter, PixelConverter, Yuv420pBuffer};
|
||||
pub use decoder::{MjpegVaapiDecoder, MjpegVaapiDecoderConfig};
|
||||
pub use pacer::{EncoderPacer, PacerStats};
|
||||
pub use device::{VideoDevice, VideoDeviceInfo};
|
||||
pub use encoder::{JpegEncoder, H264Encoder, H264EncoderType};
|
||||
pub use format::PixelFormat;
|
||||
|
||||
72
src/video/pacer.rs
Normal file
72
src/video/pacer.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
//! Encoder Pacer - Placeholder for future backpressure control
|
||||
//!
|
||||
//! Currently a pass-through that allows all frames.
|
||||
//! TODO: Implement effective backpressure control.
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use tracing::debug;
|
||||
|
||||
/// Encoder pacing statistics
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PacerStats {
|
||||
/// Total frames processed
|
||||
pub frames_processed: u64,
|
||||
/// Frames skipped (currently always 0)
|
||||
pub frames_skipped: u64,
|
||||
/// Keyframes processed
|
||||
pub keyframes_processed: u64,
|
||||
}
|
||||
|
||||
/// Encoder pacer (currently pass-through)
|
||||
///
|
||||
/// This is a placeholder for future backpressure control.
|
||||
/// Currently allows all frames through without throttling.
|
||||
pub struct EncoderPacer {
|
||||
frames_processed: AtomicU64,
|
||||
keyframes_processed: AtomicU64,
|
||||
}
|
||||
|
||||
impl EncoderPacer {
|
||||
/// Create a new encoder pacer
|
||||
pub fn new(_max_in_flight: usize) -> Self {
|
||||
debug!("Creating encoder pacer (pass-through mode)");
|
||||
Self {
|
||||
frames_processed: AtomicU64::new(0),
|
||||
keyframes_processed: AtomicU64::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if encoding should proceed (always returns true)
|
||||
pub async fn should_encode(&self, is_keyframe: bool) -> bool {
|
||||
self.frames_processed.fetch_add(1, Ordering::Relaxed);
|
||||
if is_keyframe {
|
||||
self.keyframes_processed.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
true // Always allow encoding
|
||||
}
|
||||
|
||||
/// Report lag from receiver (currently no-op)
|
||||
pub async fn report_lag(&self, _frames_lagged: u64) {
|
||||
// TODO: Implement effective backpressure control
|
||||
// Currently this is a no-op
|
||||
}
|
||||
|
||||
/// Check if throttling (always false)
|
||||
pub fn is_throttling(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Get pacer statistics
|
||||
pub fn stats(&self) -> PacerStats {
|
||||
PacerStats {
|
||||
frames_processed: self.frames_processed.load(Ordering::Relaxed),
|
||||
frames_skipped: 0,
|
||||
keyframes_processed: self.keyframes_processed.load(Ordering::Relaxed),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get in-flight count (always 0)
|
||||
pub fn in_flight(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ use crate::video::encoder::vp8::{VP8Config, VP8Encoder};
|
||||
use crate::video::encoder::vp9::{VP9Config, VP9Encoder};
|
||||
use crate::video::format::{PixelFormat, Resolution};
|
||||
use crate::video::frame::VideoFrame;
|
||||
use crate::video::pacer::EncoderPacer;
|
||||
|
||||
/// Encoded video frame for distribution
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -64,14 +65,14 @@ pub struct SharedVideoPipelineConfig {
|
||||
pub input_format: PixelFormat,
|
||||
/// Output codec type
|
||||
pub output_codec: VideoEncoderType,
|
||||
/// Target bitrate in kbps
|
||||
pub bitrate_kbps: u32,
|
||||
/// Bitrate preset (replaces raw bitrate_kbps)
|
||||
pub bitrate_preset: crate::video::encoder::BitratePreset,
|
||||
/// Target FPS
|
||||
pub fps: u32,
|
||||
/// GOP size
|
||||
pub gop_size: u32,
|
||||
/// Encoder backend (None = auto select best available)
|
||||
pub encoder_backend: Option<EncoderBackend>,
|
||||
/// Maximum in-flight frames for backpressure control
|
||||
pub max_in_flight_frames: usize,
|
||||
}
|
||||
|
||||
impl Default for SharedVideoPipelineConfig {
|
||||
@@ -80,54 +81,70 @@ impl Default for SharedVideoPipelineConfig {
|
||||
resolution: Resolution::HD720,
|
||||
input_format: PixelFormat::Yuyv,
|
||||
output_codec: VideoEncoderType::H264,
|
||||
bitrate_kbps: 1000,
|
||||
bitrate_preset: crate::video::encoder::BitratePreset::Balanced,
|
||||
fps: 30,
|
||||
gop_size: 30,
|
||||
encoder_backend: None,
|
||||
max_in_flight_frames: 8, // Default: allow 8 frames in flight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedVideoPipelineConfig {
|
||||
/// Create H264 config
|
||||
pub fn h264(resolution: Resolution, bitrate_kbps: u32) -> Self {
|
||||
/// Get effective bitrate in kbps
|
||||
pub fn bitrate_kbps(&self) -> u32 {
|
||||
self.bitrate_preset.bitrate_kbps()
|
||||
}
|
||||
|
||||
/// Get effective GOP size
|
||||
pub fn gop_size(&self) -> u32 {
|
||||
self.bitrate_preset.gop_size(self.fps)
|
||||
}
|
||||
|
||||
/// Create H264 config with bitrate preset
|
||||
pub fn h264(resolution: Resolution, preset: crate::video::encoder::BitratePreset) -> Self {
|
||||
Self {
|
||||
resolution,
|
||||
output_codec: VideoEncoderType::H264,
|
||||
bitrate_kbps,
|
||||
bitrate_preset: preset,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create H265 config
|
||||
pub fn h265(resolution: Resolution, bitrate_kbps: u32) -> Self {
|
||||
/// Create H265 config with bitrate preset
|
||||
pub fn h265(resolution: Resolution, preset: crate::video::encoder::BitratePreset) -> Self {
|
||||
Self {
|
||||
resolution,
|
||||
output_codec: VideoEncoderType::H265,
|
||||
bitrate_kbps,
|
||||
bitrate_preset: preset,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create VP8 config
|
||||
pub fn vp8(resolution: Resolution, bitrate_kbps: u32) -> Self {
|
||||
/// Create VP8 config with bitrate preset
|
||||
pub fn vp8(resolution: Resolution, preset: crate::video::encoder::BitratePreset) -> Self {
|
||||
Self {
|
||||
resolution,
|
||||
output_codec: VideoEncoderType::VP8,
|
||||
bitrate_kbps,
|
||||
bitrate_preset: preset,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create VP9 config
|
||||
pub fn vp9(resolution: Resolution, bitrate_kbps: u32) -> Self {
|
||||
/// Create VP9 config with bitrate preset
|
||||
pub fn vp9(resolution: Resolution, preset: crate::video::encoder::BitratePreset) -> Self {
|
||||
Self {
|
||||
resolution,
|
||||
output_codec: VideoEncoderType::VP9,
|
||||
bitrate_kbps,
|
||||
bitrate_preset: preset,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create config with legacy bitrate_kbps (for compatibility during migration)
|
||||
pub fn with_bitrate_kbps(mut self, bitrate_kbps: u32) -> Self {
|
||||
self.bitrate_preset = crate::video::encoder::BitratePreset::from_kbps(bitrate_kbps);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Pipeline statistics
|
||||
@@ -136,12 +153,16 @@ pub struct SharedVideoPipelineStats {
|
||||
pub frames_captured: u64,
|
||||
pub frames_encoded: u64,
|
||||
pub frames_dropped: u64,
|
||||
/// Frames skipped due to backpressure (pacer)
|
||||
pub frames_skipped: u64,
|
||||
pub bytes_encoded: u64,
|
||||
pub keyframes_encoded: u64,
|
||||
pub avg_encode_time_ms: f32,
|
||||
pub current_fps: f32,
|
||||
pub errors: u64,
|
||||
pub subscribers: u64,
|
||||
/// Current number of frames in-flight (waiting to be sent)
|
||||
pub pending_frames: usize,
|
||||
}
|
||||
|
||||
|
||||
@@ -305,18 +326,21 @@ pub struct SharedVideoPipeline {
|
||||
/// Pipeline start time for PTS calculation (epoch millis, 0 = not set)
|
||||
/// Uses AtomicI64 instead of Mutex for lock-free access
|
||||
pipeline_start_time_ms: AtomicI64,
|
||||
/// Encoder pacer for backpressure control
|
||||
pacer: EncoderPacer,
|
||||
}
|
||||
|
||||
impl SharedVideoPipeline {
|
||||
/// Create a new shared video pipeline
|
||||
pub fn new(config: SharedVideoPipelineConfig) -> Result<Arc<Self>> {
|
||||
info!(
|
||||
"Creating shared video pipeline: {} {}x{} @ {} kbps (input: {})",
|
||||
"Creating shared video pipeline: {} {}x{} @ {} (input: {}, max_in_flight: {})",
|
||||
config.output_codec,
|
||||
config.resolution.width,
|
||||
config.resolution.height,
|
||||
config.bitrate_kbps,
|
||||
config.input_format
|
||||
config.bitrate_preset,
|
||||
config.input_format,
|
||||
config.max_in_flight_frames
|
||||
);
|
||||
|
||||
let (frame_tx, _) = broadcast::channel(16); // Reduced from 64 for lower latency
|
||||
@@ -324,6 +348,9 @@ impl SharedVideoPipeline {
|
||||
let nv12_size = (config.resolution.width * config.resolution.height * 3 / 2) as usize;
|
||||
let yuv420p_size = nv12_size; // Same size as NV12
|
||||
|
||||
// Create pacer for backpressure control
|
||||
let pacer = EncoderPacer::new(config.max_in_flight_frames);
|
||||
|
||||
let pipeline = Arc::new(Self {
|
||||
config: RwLock::new(config),
|
||||
encoder: Mutex::new(None),
|
||||
@@ -342,6 +369,7 @@ impl SharedVideoPipeline {
|
||||
sequence: AtomicU64::new(0),
|
||||
keyframe_requested: AtomicBool::new(false),
|
||||
pipeline_start_time_ms: AtomicI64::new(0),
|
||||
pacer,
|
||||
});
|
||||
|
||||
Ok(pipeline)
|
||||
@@ -379,9 +407,9 @@ impl SharedVideoPipeline {
|
||||
};
|
||||
|
||||
let encoder_config = H264Config {
|
||||
base: EncoderConfig::h264(config.resolution, config.bitrate_kbps),
|
||||
bitrate_kbps: config.bitrate_kbps,
|
||||
gop_size: config.gop_size,
|
||||
base: EncoderConfig::h264(config.resolution, config.bitrate_kbps()),
|
||||
bitrate_kbps: config.bitrate_kbps(),
|
||||
gop_size: config.gop_size(),
|
||||
fps: config.fps,
|
||||
input_format: h264_input_format,
|
||||
};
|
||||
@@ -413,9 +441,9 @@ impl SharedVideoPipeline {
|
||||
VideoEncoderType::H265 => {
|
||||
// Determine H265 input format based on backend and input format
|
||||
let encoder_config = if use_yuyv_direct {
|
||||
H265Config::low_latency_yuyv422(config.resolution, config.bitrate_kbps)
|
||||
H265Config::low_latency_yuyv422(config.resolution, config.bitrate_kbps())
|
||||
} else {
|
||||
H265Config::low_latency(config.resolution, config.bitrate_kbps)
|
||||
H265Config::low_latency(config.resolution, config.bitrate_kbps())
|
||||
};
|
||||
|
||||
let encoder = if use_yuyv_direct {
|
||||
@@ -441,7 +469,7 @@ impl SharedVideoPipeline {
|
||||
Box::new(H265EncoderWrapper(encoder))
|
||||
}
|
||||
VideoEncoderType::VP8 => {
|
||||
let encoder_config = VP8Config::low_latency(config.resolution, config.bitrate_kbps);
|
||||
let encoder_config = VP8Config::low_latency(config.resolution, config.bitrate_kbps());
|
||||
|
||||
let encoder = if let Some(ref backend) = config.encoder_backend {
|
||||
let codec_name = get_codec_name(VideoEncoderType::VP8, Some(*backend))
|
||||
@@ -458,7 +486,7 @@ impl SharedVideoPipeline {
|
||||
Box::new(VP8EncoderWrapper(encoder))
|
||||
}
|
||||
VideoEncoderType::VP9 => {
|
||||
let encoder_config = VP9Config::low_latency(config.resolution, config.bitrate_kbps);
|
||||
let encoder_config = VP9Config::low_latency(config.resolution, config.bitrate_kbps());
|
||||
|
||||
let encoder = if let Some(ref backend) = config.encoder_backend {
|
||||
let codec_name = get_codec_name(VideoEncoderType::VP9, Some(*backend))
|
||||
@@ -589,6 +617,19 @@ impl SharedVideoPipeline {
|
||||
self.frame_tx.receiver_count()
|
||||
}
|
||||
|
||||
/// Report that a receiver has lagged behind
|
||||
///
|
||||
/// Call this when a broadcast receiver detects it has fallen behind
|
||||
/// (e.g., when RecvError::Lagged is received). This triggers throttle
|
||||
/// mode in the encoder to reduce encoding rate.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `frames_lagged` - Number of frames the receiver has lagged
|
||||
pub async fn report_lag(&self, frames_lagged: u64) {
|
||||
self.pacer.report_lag(frames_lagged).await;
|
||||
}
|
||||
|
||||
/// Request encoder to produce a keyframe on next encode
|
||||
///
|
||||
/// This is useful when a new client connects and needs an immediate
|
||||
@@ -604,9 +645,15 @@ impl SharedVideoPipeline {
|
||||
pub async fn stats(&self) -> SharedVideoPipelineStats {
|
||||
let mut stats = self.stats.lock().await.clone();
|
||||
stats.subscribers = self.frame_tx.receiver_count() as u64;
|
||||
stats.pending_frames = if self.pacer.is_throttling() { 1 } else { 0 };
|
||||
stats
|
||||
}
|
||||
|
||||
/// Get pacer statistics for debugging
|
||||
pub fn pacer_stats(&self) -> crate::video::pacer::PacerStats {
|
||||
self.pacer.stats()
|
||||
}
|
||||
|
||||
/// Check if running
|
||||
pub fn is_running(&self) -> bool {
|
||||
*self.running_rx.borrow()
|
||||
@@ -662,7 +709,8 @@ impl SharedVideoPipeline {
|
||||
let _ = self.running.send(true);
|
||||
|
||||
let config = self.config.read().await.clone();
|
||||
info!("Starting {} pipeline", config.output_codec);
|
||||
let gop_size = config.gop_size();
|
||||
info!("Starting {} pipeline (GOP={})", config.output_codec, gop_size);
|
||||
|
||||
let pipeline = self.clone();
|
||||
|
||||
@@ -678,6 +726,7 @@ impl SharedVideoPipeline {
|
||||
let mut local_keyframes: u64 = 0;
|
||||
let mut local_errors: u64 = 0;
|
||||
let mut local_dropped: u64 = 0;
|
||||
let mut local_skipped: u64 = 0;
|
||||
|
||||
// Track when we last had subscribers for auto-stop feature
|
||||
let mut no_subscribers_since: Option<Instant> = None;
|
||||
@@ -728,8 +777,18 @@ impl SharedVideoPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// === Lag-feedback based flow control ===
|
||||
// Check if this is a keyframe interval
|
||||
let is_keyframe_interval = frame_count % gop_size as u64 == 0;
|
||||
|
||||
// Note: pacer.should_encode() currently always returns true
|
||||
// TODO: Implement effective backpressure control
|
||||
let _ = pipeline.pacer.should_encode(is_keyframe_interval).await;
|
||||
|
||||
match pipeline.encode_frame(&video_frame, frame_count).await {
|
||||
Ok(Some(encoded_frame)) => {
|
||||
// Send frame to all subscribers
|
||||
// Note: broadcast::send is non-blocking
|
||||
let _ = pipeline.frame_tx.send(encoded_frame.clone());
|
||||
|
||||
// Update local counters (no lock)
|
||||
@@ -762,6 +821,8 @@ impl SharedVideoPipeline {
|
||||
s.keyframes_encoded += local_keyframes;
|
||||
s.errors += local_errors;
|
||||
s.frames_dropped += local_dropped;
|
||||
s.frames_skipped += local_skipped;
|
||||
s.pending_frames = if pipeline.pacer.is_throttling() { 1 } else { 0 };
|
||||
s.current_fps = current_fps;
|
||||
|
||||
// Reset local counters
|
||||
@@ -770,6 +831,7 @@ impl SharedVideoPipeline {
|
||||
local_keyframes = 0;
|
||||
local_errors = 0;
|
||||
local_dropped = 0;
|
||||
local_skipped = 0;
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(n)) => {
|
||||
@@ -958,15 +1020,22 @@ impl SharedVideoPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set bitrate
|
||||
pub async fn set_bitrate(&self, bitrate_kbps: u32) -> Result<()> {
|
||||
/// Set bitrate using preset
|
||||
pub async fn set_bitrate_preset(&self, preset: crate::video::encoder::BitratePreset) -> Result<()> {
|
||||
let bitrate_kbps = preset.bitrate_kbps();
|
||||
if let Some(ref mut encoder) = *self.encoder.lock().await {
|
||||
encoder.set_bitrate(bitrate_kbps)?;
|
||||
self.config.write().await.bitrate_kbps = bitrate_kbps;
|
||||
self.config.write().await.bitrate_preset = preset;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set bitrate using raw kbps value (converts to appropriate preset)
|
||||
pub async fn set_bitrate(&self, bitrate_kbps: u32) -> Result<()> {
|
||||
let preset = crate::video::encoder::BitratePreset::from_kbps(bitrate_kbps);
|
||||
self.set_bitrate_preset(preset).await
|
||||
}
|
||||
|
||||
/// Get current config
|
||||
pub async fn config(&self) -> SharedVideoPipelineConfig {
|
||||
self.config.read().await.clone()
|
||||
@@ -1038,13 +1107,14 @@ fn parse_h265_nal_types(data: &[u8]) -> Vec<(u8, usize)> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::video::encoder::BitratePreset;
|
||||
|
||||
#[test]
|
||||
fn test_pipeline_config() {
|
||||
let h264 = SharedVideoPipelineConfig::h264(Resolution::HD1080, 4000);
|
||||
let h264 = SharedVideoPipelineConfig::h264(Resolution::HD1080, BitratePreset::Balanced);
|
||||
assert_eq!(h264.output_codec, VideoEncoderType::H264);
|
||||
|
||||
let h265 = SharedVideoPipelineConfig::h265(Resolution::HD720, 2000);
|
||||
let h265 = SharedVideoPipelineConfig::h265(Resolution::HD720, BitratePreset::Speed);
|
||||
assert_eq!(h265.output_codec, VideoEncoderType::H265);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,6 +613,14 @@ impl VideoStreamManager {
|
||||
self.webrtc_streamer.set_video_codec(codec).await
|
||||
}
|
||||
|
||||
/// Set bitrate preset for the shared video pipeline
|
||||
///
|
||||
/// This allows external consumers (like RustDesk) to adjust the video quality
|
||||
/// based on client preferences.
|
||||
pub async fn set_bitrate_preset(&self, preset: crate::video::encoder::BitratePreset) -> crate::error::Result<()> {
|
||||
self.webrtc_streamer.set_bitrate_preset(preset).await
|
||||
}
|
||||
|
||||
/// Publish event to event bus
|
||||
async fn publish_event(&self, event: SystemEvent) {
|
||||
if let Some(ref events) = *self.events.read().await {
|
||||
|
||||
@@ -13,6 +13,7 @@ use tokio::sync::{broadcast, RwLock};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use super::encoder::registry::{EncoderBackend, EncoderRegistry, VideoEncoderType};
|
||||
use super::encoder::BitratePreset;
|
||||
use super::format::Resolution;
|
||||
use super::frame::VideoFrame;
|
||||
use super::shared_video_pipeline::{
|
||||
@@ -123,8 +124,8 @@ pub struct VideoSessionManagerConfig {
|
||||
pub default_codec: VideoEncoderType,
|
||||
/// Default resolution
|
||||
pub resolution: Resolution,
|
||||
/// Default bitrate (kbps)
|
||||
pub bitrate_kbps: u32,
|
||||
/// Bitrate preset
|
||||
pub bitrate_preset: BitratePreset,
|
||||
/// Default FPS
|
||||
pub fps: u32,
|
||||
/// Session timeout (seconds)
|
||||
@@ -138,7 +139,7 @@ impl Default for VideoSessionManagerConfig {
|
||||
Self {
|
||||
default_codec: VideoEncoderType::H264,
|
||||
resolution: Resolution::HD720,
|
||||
bitrate_kbps: 8000,
|
||||
bitrate_preset: BitratePreset::Balanced,
|
||||
fps: 30,
|
||||
session_timeout_secs: 300,
|
||||
encoder_backend: None,
|
||||
@@ -325,10 +326,10 @@ impl VideoSessionManager {
|
||||
resolution: self.config.resolution,
|
||||
input_format: crate::video::format::PixelFormat::Mjpeg, // Common input
|
||||
output_codec: codec,
|
||||
bitrate_kbps: self.config.bitrate_kbps,
|
||||
bitrate_preset: self.config.bitrate_preset,
|
||||
fps: self.config.fps,
|
||||
gop_size: 30,
|
||||
encoder_backend: self.config.encoder_backend,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Create new pipeline
|
||||
|
||||
Reference in New Issue
Block a user