view camera (#11040)

* view camera

Signed-off-by: 21pages <sunboeasy@gmail.com>

* `No cameras` prompt if no cameras available,  `peerGetSessionsCount` use
connType as parameter

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix, use video_service_name rather than display_idx as key in qos,etc

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: Adwin White <adwinw01@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
21pages
2025-03-10 21:06:53 +08:00
committed by GitHub
parent df4a101316
commit f0f999dc27
96 changed files with 3999 additions and 458 deletions

View File

@@ -18,12 +18,7 @@
// to-do:
// https://slhck.info/video/2017/03/01/rate-control.html
use super::{
display_service::{check_display_changed, get_display_info},
service::ServiceTmpl,
video_qos::VideoQoS,
*,
};
use super::{display_service::check_display_changed, service::ServiceTmpl, video_qos::VideoQoS, *};
#[cfg(target_os = "linux")]
use crate::common::SimpleCallOnReturn;
#[cfg(target_os = "linux")]
@@ -65,7 +60,6 @@ use std::{
time::{self, Duration, Instant},
};
pub const NAME: &'static str = "video";
pub const OPTION_REFRESH: &'static str = "refresh";
lazy_static::lazy_static! {
@@ -133,10 +127,34 @@ impl VideoFrameController {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VideoSource {
Monitor,
Camera,
}
impl VideoSource {
pub fn service_name_prefix(&self) -> &'static str {
match self {
VideoSource::Monitor => "monitor",
VideoSource::Camera => "camera",
}
}
pub fn is_monitor(&self) -> bool {
matches!(self, VideoSource::Monitor)
}
pub fn is_camera(&self) -> bool {
matches!(self, VideoSource::Camera)
}
}
#[derive(Clone)]
pub struct VideoService {
sp: GenericService,
idx: usize,
source: VideoSource,
}
impl Deref for VideoService {
@@ -153,14 +171,15 @@ impl DerefMut for VideoService {
}
}
pub fn get_service_name(idx: usize) -> String {
format!("{}{}", NAME, idx)
pub fn get_service_name(source: VideoSource, idx: usize) -> String {
format!("{}{}", source.service_name_prefix(), idx)
}
pub fn new(idx: usize) -> GenericService {
pub fn new(source: VideoSource, idx: usize) -> GenericService {
let vs = VideoService {
sp: GenericService::new(get_service_name(idx), true),
sp: GenericService::new(get_service_name(source, idx), true),
idx,
source,
};
GenericService::run(&vs, run);
vs.sp
@@ -292,7 +311,10 @@ impl DerefMut for CapturerInfo {
}
}
fn get_capturer(current: usize, portable_service_running: bool) -> ResultType<CapturerInfo> {
fn get_capturer_monitor(
current: usize,
portable_service_running: bool,
) -> ResultType<CapturerInfo> {
#[cfg(target_os = "linux")]
{
if !is_x11() {
@@ -309,6 +331,7 @@ fn get_capturer(current: usize, portable_service_running: bool) -> ResultType<Ca
ndisplay
);
}
let display = displays.remove(current);
#[cfg(target_os = "linux")]
@@ -382,8 +405,59 @@ fn get_capturer(current: usize, portable_service_running: bool) -> ResultType<Ca
})
}
fn get_capturer_camera(current: usize) -> ResultType<CapturerInfo> {
let cameras = camera::Cameras::get_sync_cameras();
let ncamera = cameras.len();
if ncamera <= current {
bail!("Failed to get camera {}, cameras len: {}", current, ncamera,);
}
let Some(camera) = cameras.get(current) else {
bail!(
"Camera of index {} doesn't exist or platform not supported",
current
);
};
let capturer = camera::Cameras::get_capturer(current)?;
let (width, height) = (camera.width as usize, camera.height as usize);
let origin = (camera.x as i32, camera.y as i32);
let name = &camera.name;
let privacy_mode_id = get_privacy_mode_conn_id().unwrap_or(INVALID_PRIVACY_MODE_CONN_ID);
let _capturer_privacy_mode_id = privacy_mode_id;
log::debug!(
"#cameras={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}, name:{}",
ncamera,
current,
&origin,
width,
height,
num_cpus::get_physical(),
num_cpus::get(),
name,
);
return Ok(CapturerInfo {
origin,
width,
height,
ndisplay: ncamera,
current,
privacy_mode_id,
_capturer_privacy_mode_id: privacy_mode_id,
capturer,
});
}
fn get_capturer(
source: VideoSource,
current: usize,
portable_service_running: bool,
) -> ResultType<CapturerInfo> {
match source {
VideoSource::Monitor => get_capturer_monitor(current, portable_service_running),
VideoSource::Camera => get_capturer_camera(current),
}
}
fn run(vs: VideoService) -> ResultType<()> {
let _raii = Raii::new(vs.idx);
let _raii = Raii::new(vs.sp.name());
// Wayland only support one video capturer for now. It is ok to call ensure_inited() here.
//
// ensure_inited() is needed because clear() may be called.
@@ -406,7 +480,7 @@ fn run(vs: VideoService) -> ResultType<()> {
let display_idx = vs.idx;
let sp = vs.sp;
let mut c = get_capturer(display_idx, last_portable_service_running)?;
let mut c = get_capturer(vs.source, display_idx, last_portable_service_running)?;
#[cfg(windows)]
if !scrap::codec::enable_directx_capture() && !c.is_gdi() {
log::info!("disable dxgi with option, fall back to gdi");
@@ -423,11 +497,12 @@ fn run(vs: VideoService) -> ResultType<()> {
drop(video_qos);
let (mut encoder, encoder_cfg, codec_format, use_i444, recorder) = match setup_encoder(
&c,
display_idx,
sp.name(),
quality,
client_record,
record_incoming,
last_portable_service_running,
vs.source,
) {
Ok(result) => result,
Err(err) => {
@@ -441,26 +516,29 @@ fn run(vs: VideoService) -> ResultType<()> {
}));
setup_encoder(
&c,
display_idx,
sp.name(),
quality,
client_record,
record_incoming,
last_portable_service_running,
vs.source,
)?
}
};
#[cfg(feature = "vram")]
c.set_output_texture(encoder.input_texture());
#[cfg(target_os = "android")]
if let Err(e) = check_change_scale(encoder.is_hardware()) {
try_broadcast_display_changed(&sp, display_idx, &c, true).ok();
bail!(e);
if vs.source.is_monitor() {
if let Err(e) = check_change_scale(encoder.is_hardware()) {
try_broadcast_display_changed(&sp, display_idx, &c, true).ok();
bail!(e);
}
}
VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate());
VIDEO_QOS
.lock()
.unwrap()
.set_support_changing_quality(display_idx, encoder.support_changing_quality());
.set_support_changing_quality(&sp.name(), encoder.support_changing_quality());
log::info!("initial quality: {quality:?}");
if sp.is_option_true(OPTION_REFRESH) {
@@ -500,10 +578,12 @@ fn run(vs: VideoService) -> ResultType<()> {
client_record,
&mut send_counter,
&mut second_instant,
display_idx,
&sp.name(),
)?;
if sp.is_option_true(OPTION_REFRESH) {
let _ = try_broadcast_display_changed(&sp, display_idx, &c, true);
if vs.source.is_monitor() {
let _ = try_broadcast_display_changed(&sp, display_idx, &c, true);
}
log::info!("switch to refresh");
bail!("SWITCH");
}
@@ -527,10 +607,12 @@ fn run(vs: VideoService) -> ResultType<()> {
#[cfg(all(windows, feature = "vram"))]
if c.is_gdi() && encoder.input_texture() {
log::info!("changed to gdi when using vram");
VRamEncoder::set_fallback_gdi(display_idx, true);
VRamEncoder::set_fallback_gdi(sp.name(), true);
bail!("SWITCH");
}
check_privacy_mode_changed(&sp, display_idx, &c)?;
if vs.source.is_monitor() {
check_privacy_mode_changed(&sp, display_idx, &c)?;
}
#[cfg(windows)]
{
if crate::platform::windows::desktop_changed()
@@ -540,7 +622,7 @@ fn run(vs: VideoService) -> ResultType<()> {
}
}
let now = time::Instant::now();
if last_check_displays.elapsed().as_millis() > 1000 {
if vs.source.is_monitor() && last_check_displays.elapsed().as_millis() > 1000 {
last_check_displays = now;
// This check may be redundant, but it is better to be safe.
// The previous check in `sp.is_option_true(OPTION_REFRESH)` block may be enough.
@@ -575,7 +657,7 @@ fn run(vs: VideoService) -> ResultType<()> {
{
#[cfg(feature = "vram")]
if try_gdi == 1 && !c.is_gdi() {
VRamEncoder::set_fallback_gdi(display_idx, false);
VRamEncoder::set_fallback_gdi(sp.name(), false);
}
try_gdi = 0;
}
@@ -635,7 +717,9 @@ fn run(vs: VideoService) -> ResultType<()> {
Err(err) => {
// This check may be redundant, but it is better to be safe.
// The previous check in `sp.is_option_true(OPTION_REFRESH)` block may be enough.
try_broadcast_display_changed(&sp, display_idx, &c, true)?;
if vs.source.is_monitor() {
try_broadcast_display_changed(&sp, display_idx, &c, true)?;
}
#[cfg(windows)]
if !c.is_gdi() {
@@ -657,7 +741,9 @@ fn run(vs: VideoService) -> ResultType<()> {
let timeout_millis = 3_000u64;
let wait_begin = Instant::now();
while wait_begin.elapsed().as_millis() < timeout_millis as _ {
check_privacy_mode_changed(&sp, display_idx, &c)?;
if vs.source.is_monitor() {
check_privacy_mode_changed(&sp, display_idx, &c)?;
}
frame_controller.try_wait_next(&mut fetched_conn_ids, 300);
// break if all connections have received current frame
if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() {
@@ -676,32 +762,35 @@ fn run(vs: VideoService) -> ResultType<()> {
Ok(())
}
struct Raii(usize);
struct Raii(String);
impl Raii {
fn new(display_idx: usize) -> Self {
VIDEO_QOS.lock().unwrap().new_display(display_idx);
Raii(display_idx)
fn new(name: String) -> Self {
log::info!("new video service: {}", name);
VIDEO_QOS.lock().unwrap().new_display(name.clone());
Raii(name)
}
}
impl Drop for Raii {
fn drop(&mut self) {
log::info!("stop video service: {}", self.0);
#[cfg(feature = "vram")]
VRamEncoder::set_not_use(self.0, false);
VRamEncoder::set_not_use(self.0.clone(), false);
#[cfg(feature = "vram")]
Encoder::update(scrap::codec::EncodingUpdate::Check);
VIDEO_QOS.lock().unwrap().remove_display(self.0);
VIDEO_QOS.lock().unwrap().remove_display(&self.0);
}
}
fn setup_encoder(
c: &CapturerInfo,
display_idx: usize,
name: String,
quality: f32,
client_record: bool,
record_incoming: bool,
last_portable_service_running: bool,
source: VideoSource,
) -> ResultType<(
Encoder,
EncoderCfg,
@@ -711,14 +800,15 @@ fn setup_encoder(
)> {
let encoder_cfg = get_encoder_config(
&c,
display_idx,
name.to_string(),
quality,
client_record || record_incoming,
last_portable_service_running,
source,
);
Encoder::set_fallback(&encoder_cfg);
let codec_format = Encoder::negotiated_codec();
let recorder = get_recorder(record_incoming, display_idx);
let recorder = get_recorder(record_incoming, name);
let use_i444 = Encoder::use_i444(&encoder_cfg);
let encoder = Encoder::new(encoder_cfg.clone(), use_i444)?;
Ok((encoder, encoder_cfg, codec_format, use_i444, recorder))
@@ -726,15 +816,16 @@ fn setup_encoder(
fn get_encoder_config(
c: &CapturerInfo,
_display_idx: usize,
_name: String,
quality: f32,
record: bool,
_portable_service: bool,
_source: VideoSource,
) -> EncoderCfg {
#[cfg(all(windows, feature = "vram"))]
if _portable_service || c.is_gdi() {
if _portable_service || c.is_gdi() || _source == VideoSource::Camera {
log::info!("gdi:{}, portable:{}", c.is_gdi(), _portable_service);
VRamEncoder::set_not_use(_display_idx, true);
VRamEncoder::set_not_use(_name, true);
}
#[cfg(feature = "vram")]
Encoder::update(scrap::codec::EncodingUpdate::Check);
@@ -800,7 +891,7 @@ fn get_encoder_config(
}
}
fn get_recorder(record_incoming: bool, display: usize) -> Arc<Mutex<Option<Recorder>>> {
fn get_recorder(record_incoming: bool, video_service_name: String) -> Arc<Mutex<Option<Recorder>>> {
#[cfg(windows)]
let root = crate::platform::is_root();
#[cfg(not(windows))]
@@ -819,7 +910,7 @@ fn get_recorder(record_incoming: bool, display: usize) -> Arc<Mutex<Option<Recor
server: true,
id: Config::get_id(),
dir: crate::ui_interface::video_save_directory(root),
display,
video_service_name,
tx,
})
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
@@ -1004,7 +1095,9 @@ fn try_broadcast_display_changed(
(cap.origin.0, cap.origin.1, cap.width, cap.height),
) {
log::info!("Display {} changed", display);
if let Some(msg_out) = make_display_changed_msg(display_idx, Some(display)) {
if let Some(msg_out) =
make_display_changed_msg(display_idx, Some(display), VideoSource::Monitor)
{
let msg_out = Arc::new(msg_out);
sp.send_shared(msg_out.clone());
// switch display may occur before the first video frame, add snapshot to send to new subscribers
@@ -1021,10 +1114,16 @@ fn try_broadcast_display_changed(
pub fn make_display_changed_msg(
display_idx: usize,
opt_display: Option<DisplayInfo>,
source: VideoSource,
) -> Option<Message> {
let display = match opt_display {
Some(d) => d,
None => get_display_info(display_idx)?,
None => match source {
VideoSource::Monitor => display_service::get_display_info(display_idx)?,
VideoSource::Camera => camera::Cameras::get_sync_cameras()
.get(display_idx)?
.clone(),
},
};
let mut misc = Misc::new();
misc.set_switch_display(SwitchDisplay {
@@ -1033,13 +1132,24 @@ pub fn make_display_changed_msg(
y: display.y,
width: display.width,
height: display.height,
cursor_embedded: display_service::capture_cursor_embedded(),
cursor_embedded: match source {
VideoSource::Monitor => display_service::capture_cursor_embedded(),
VideoSource::Camera => false,
},
#[cfg(not(target_os = "android"))]
resolutions: Some(SupportedResolutions {
resolutions: if display.name.is_empty() {
vec![]
} else {
crate::platform::resolutions(&display.name)
resolutions: match source {
VideoSource::Monitor => {
if display.name.is_empty() {
vec![]
} else {
crate::platform::resolutions(&display.name)
}
}
VideoSource::Camera => camera::Cameras::get_camera_resolution(display_idx)
.ok()
.into_iter()
.collect(),
},
..SupportedResolutions::default()
})
@@ -1059,7 +1169,7 @@ fn check_qos(
client_record: bool,
send_counter: &mut usize,
second_instant: &mut Instant,
display_idx: usize,
name: &str,
) -> ResultType<()> {
let mut video_qos = VIDEO_QOS.lock().unwrap();
*spf = video_qos.spf();
@@ -1082,7 +1192,7 @@ fn check_qos(
}
if second_instant.elapsed() > Duration::from_secs(1) {
*second_instant = Instant::now();
video_qos.update_display_data(display_idx, *send_counter);
video_qos.update_display_data(&name, *send_counter);
*send_counter = 0;
}
drop(video_qos);