diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 2044388f8..7b4bc3787 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -72,6 +72,11 @@ message Features { bool privacy_mode = 1; } +message SupportedEncoding { + bool h264 = 1; + bool h265 = 2; +} + message PeerInfo { string username = 1; string hostname = 2; @@ -82,6 +87,7 @@ message PeerInfo { string version = 7; int32 conn_id = 8; Features features = 9; + SupportedEncoding encoding = 10; } message LoginResponse { @@ -434,9 +440,17 @@ enum ImageQuality { } message VideoCodecState { - int32 ScoreVpx = 1; - int32 ScoreH264 = 2; - int32 ScoreH265 = 3; + enum PerferCodec { + Auto = 0; + VPX = 1; + H264 = 2; + H265 = 3; + } + + int32 score_vpx = 1; + int32 score_h264 = 2; + int32 score_h265 = 3; + PerferCodec perfer = 4; } message OptionMessage { diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs index b48053e0b..1a18a79c3 100644 --- a/libs/scrap/src/common/codec.rs +++ b/libs/scrap/src/common/codec.rs @@ -16,7 +16,11 @@ use hbb_common::{ ResultType, }; #[cfg(feature = "hwcodec")] -use hbb_common::{config::Config2, lazy_static}; +use hbb_common::{ + config::{Config2, PeerConfig}, + lazy_static, + message_proto::video_codec_state::PerferCodec, +}; #[cfg(feature = "hwcodec")] lazy_static::lazy_static! { @@ -113,7 +117,6 @@ impl Encoder { // TODO pub fn update_video_encoder(id: i32, update: EncoderUpdate) { - log::info!("encoder update: {:?}", update); #[cfg(feature = "hwcodec")] { let mut states = PEER_DECODER_STATES.lock().unwrap(); @@ -130,49 +133,75 @@ impl Encoder { } } } - let current_encoder_name = HwEncoder::current_name(); + let name = HwEncoder::current_name(); if states.len() > 0 { let best = HwEncoder::best(); let enabled_h264 = best.h264.is_some() && states.len() > 0 - && states.iter().all(|(_, s)| s.ScoreH264 > 0); + && states.iter().all(|(_, s)| s.score_h264 > 0); let enabled_h265 = best.h265.is_some() && states.len() > 0 - && states.iter().all(|(_, s)| s.ScoreH265 > 0); + && states.iter().all(|(_, s)| s.score_h265 > 0); - // score encoder - let mut score_vpx = SCORE_VPX; - let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score); - let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score); - - // score decoder - score_vpx += states.iter().map(|s| s.1.ScoreVpx).sum::(); - if enabled_h264 { - score_h264 += states.iter().map(|s| s.1.ScoreH264).sum::(); - } - if enabled_h265 { - score_h265 += states.iter().map(|s| s.1.ScoreH265).sum::(); + // Preference first + let mut preference = PerferCodec::Auto; + let preferences: Vec<_> = states + .iter() + .filter(|(_, s)| { + s.perfer == PerferCodec::VPX.into() + || s.perfer == PerferCodec::H264.into() && enabled_h264 + || s.perfer == PerferCodec::H265.into() && enabled_h265 + }) + .map(|(_, s)| s.perfer) + .collect(); + if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) { + preference = preferences[0].enum_value_or(PerferCodec::Auto); } - if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 { - *current_encoder_name.lock().unwrap() = Some(best.h265.unwrap().name); - } else if enabled_h264 && score_h264 >= score_vpx && score_h264 >= score_h265 { - *current_encoder_name.lock().unwrap() = Some(best.h264.unwrap().name); - } else { - *current_encoder_name.lock().unwrap() = None; + match preference { + PerferCodec::VPX => *name.lock().unwrap() = None, + PerferCodec::H264 => { + *name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name)) + } + PerferCodec::H265 => { + *name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name)) + } + PerferCodec::Auto => { + // score encoder + let mut score_vpx = SCORE_VPX; + let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score); + let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score); + + // score decoder + score_vpx += states.iter().map(|s| s.1.score_vpx).sum::(); + if enabled_h264 { + score_h264 += states.iter().map(|s| s.1.score_h264).sum::(); + } + if enabled_h265 { + score_h265 += states.iter().map(|s| s.1.score_h265).sum::(); + } + + if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 { + *name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name)); + } else if enabled_h264 + && score_h264 >= score_vpx + && score_h264 >= score_h265 + { + *name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name)); + } else { + *name.lock().unwrap() = None; + } + } } + log::info!( - "connection count:{}, h264:{}, h265:{}, score: vpx({}), h264({}), h265({}), set current encoder name {:?}", + "connection count:{}, used preference:{:?}, encoder:{:?}", states.len(), - enabled_h264, - enabled_h265, - score_vpx, - score_h264, - score_h265, - current_encoder_name.lock().unwrap() - ) + preference, + name.lock().unwrap() + ) } else { - *current_encoder_name.lock().unwrap() = None; + *name.lock().unwrap() = None; } } #[cfg(not(feature = "hwcodec"))] @@ -192,34 +221,51 @@ impl Encoder { #[cfg(not(feature = "hwcodec"))] return None; } + + pub fn supported_encoding() -> (bool, bool) { + #[cfg(feature = "hwcodec")] + if check_hwcodec_config() { + let best = HwEncoder::best(); + ( + best.h264.as_ref().map_or(false, |c| c.score > 0), + best.h265.as_ref().map_or(false, |c| c.score > 0), + ) + } else { + (false, false) + } + #[cfg(not(feature = "hwcodec"))] + (false, false) + } } #[cfg(feature = "hwcodec")] impl Drop for Decoder { fn drop(&mut self) { *MY_DECODER_STATE.lock().unwrap() = VideoCodecState { - ScoreVpx: SCORE_VPX, + score_vpx: SCORE_VPX, ..Default::default() }; } } impl Decoder { - pub fn video_codec_state() -> VideoCodecState { + pub fn video_codec_state(_id: &str) -> VideoCodecState { // video_codec_state is mainted by creation and destruction of Decoder. // It has been ensured to use after Decoder's creation. #[cfg(feature = "hwcodec")] if check_hwcodec_config() { - return MY_DECODER_STATE.lock().unwrap().clone(); + let mut state = MY_DECODER_STATE.lock().unwrap(); + state.perfer = Self::codec_preference(_id).into(); + state.clone() } else { return VideoCodecState { - ScoreVpx: SCORE_VPX, + score_vpx: SCORE_VPX, ..Default::default() }; } #[cfg(not(feature = "hwcodec"))] VideoCodecState { - ScoreVpx: SCORE_VPX, + score_vpx: SCORE_VPX, ..Default::default() } } @@ -237,9 +283,9 @@ impl Decoder { #[cfg(feature = "hwcodec")] { let mut state = MY_DECODER_STATE.lock().unwrap(); - state.ScoreVpx = SCORE_VPX; - state.ScoreH264 = decoder.hw.h264.as_ref().map_or(0, |d| d.info.score); - state.ScoreH265 = decoder.hw.h265.as_ref().map_or(0, |d| d.info.score); + state.score_vpx = SCORE_VPX; + state.score_h264 = decoder.hw.h264.as_ref().map_or(0, |d| d.info.score); + state.score_h265 = decoder.hw.h265.as_ref().map_or(0, |d| d.info.score); } decoder @@ -316,6 +362,23 @@ impl Decoder { } return Ok(ret); } + + #[cfg(feature = "hwcodec")] + fn codec_preference(id: &str) -> PerferCodec { + let codec = PeerConfig::load(id) + .options + .get("codec-preference") + .map_or("".to_owned(), |c| c.to_owned()); + if codec == "vp9" { + PerferCodec::VPX + } else if codec == "h264" { + PerferCodec::H264 + } else if codec == "h265" { + PerferCodec::H265 + } else { + PerferCodec::Auto + } + } } #[cfg(feature = "hwcodec")] diff --git a/src/client.rs b/src/client.rs index 28101896e..dbea079ea 100644 --- a/src/client.rs +++ b/src/client.rs @@ -784,6 +784,7 @@ pub struct LoginConfigHandler { pub conn_id: i32, features: Option, session_id: u64, + pub supported_encoding: Option<(bool, bool)>, } impl Deref for LoginConfigHandler { @@ -808,6 +809,7 @@ impl LoginConfigHandler { self.remember = !config.password.is_empty(); self.config = config; self.session_id = rand::random(); + self.supported_encoding = None; } pub fn should_auto_login(&self) -> String { @@ -958,8 +960,7 @@ impl LoginConfigHandler { msg.disable_clipboard = BoolOption::Yes.into(); n += 1; } - // TODO: add option - let state = Decoder::video_codec_state(); + let state = Decoder::video_codec_state(&self.id); msg.video_codec_state = hbb_common::protobuf::MessageField::some(state); n += 1; @@ -1111,6 +1112,10 @@ impl LoginConfigHandler { self.conn_id = pi.conn_id; // no matter if change, for update file time self.save_config(config); + #[cfg(feature = "hwcodec")] + { + self.supported_encoding = Some((pi.encoding.h264, pi.encoding.h265)); + } } pub fn get_remote_dir(&self) -> String { @@ -1163,6 +1168,18 @@ impl LoginConfigHandler { msg_out.set_login_request(lr); msg_out } + + pub fn change_prefer_codec(&self) -> Message { + let state = scrap::codec::Decoder::video_codec_state(&self.id); + let mut misc = Misc::new(); + misc.set_option(OptionMessage { + video_codec_state: hbb_common::protobuf::MessageField::some(state), + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + msg_out + } } pub enum MediaData { diff --git a/src/server/connection.rs b/src/server/connection.rs index 869df4196..8b0b3f192 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -635,6 +635,16 @@ impl Connection { pi.hostname = MOBILE_INFO2.lock().unwrap().clone(); pi.platform = "Android".into(); } + #[cfg(feature = "hwcodec")] + { + let (h264, h265) = scrap::codec::Encoder::supported_encoding(); + pi.encoding = Some(SupportedEncoding { + h264, + h265, + ..Default::default() + }) + .into(); + } if self.port_forward_socket.is_some() { let mut msg_out = Message::new(); @@ -1351,6 +1361,12 @@ impl Connection { } } } + if let Some(q) = o.video_codec_state.clone().take() { + scrap::codec::Encoder::update_video_encoder( + self.inner.id(), + scrap::codec::EncoderUpdate::State(q), + ); + } } async fn on_close(&mut self, reason: &str, lock: bool) { diff --git a/src/server/video_service.rs b/src/server/video_service.rs index e6ea713a1..e639fbe24 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -483,6 +483,7 @@ fn run(sp: GenericService) -> ResultType<()> { let mut try_gdi = 1; #[cfg(windows)] log::info!("gdi: {}", c.is_gdi()); + let codec_name = Encoder::current_hw_encoder_name(); while sp.ok() { #[cfg(windows)] @@ -508,6 +509,9 @@ fn run(sp: GenericService) -> ResultType<()> { *SWITCH.lock().unwrap() = true; bail!("SWITCH"); } + if codec_name != Encoder::current_hw_encoder_name() { + bail!("SWITCH"); + } check_privacy_mode_changed(&sp, c.privacy_mode_id)?; #[cfg(windows)] { diff --git a/src/ui/header.css b/src/ui/header.css index fb7b57fda..e248b46d5 100644 --- a/src/ui/header.css +++ b/src/ui/header.css @@ -94,3 +94,4 @@ span#fullscreen.active { button:disabled { opacity: 0.3; } + diff --git a/src/ui/header.tis b/src/ui/header.tis index 35a132c90..6ee3cad01 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -145,6 +145,9 @@ class Header: Reactor.Component { } function renderDisplayPop() { + var codecs = handler.supported_hwcodec(); + var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]); + return
  • {translate('Adjust Window')}
  • @@ -157,6 +160,13 @@ class Header: Reactor.Component {
  • {svg_checkmark}{translate('Balanced')}
  • {svg_checkmark}{translate('Optimize reaction time')}
  • {svg_checkmark}{translate('Custom')}
  • + {show_codec ?
    +
    +
  • {svg_checkmark}Auto
  • +
  • {svg_checkmark}VP9
  • + {codecs[0] ?
  • {svg_checkmark}H264
  • : ""} + {codecs[1] ?
  • {svg_checkmark}H265
  • : ""} +
    : ""}
  • {svg_checkmark}{translate('Show remote cursor')}
  • {svg_checkmark}{translate('Show quality monitor')}
  • @@ -311,7 +321,7 @@ class Header: Reactor.Component { } } - event click $(menu#display-options>li) (_, me) { + event click $(menu#display-options li) (_, me) { if (me.id == "custom") { handle_custom_image_quality(); } else if (me.id == "privacy-mode") { @@ -328,6 +338,9 @@ class Header: Reactor.Component { } else if (type == "view-style") { handler.save_view_style(me.id); adaptDisplay(); + } else if (type == "codec-preference") { + handler.set_option("codec-preference", me.id); + handler.change_prefer_codec(); } toggleMenuState(); } @@ -355,7 +368,10 @@ function toggleMenuState() { var s = handler.get_view_style(); if (!s) s = "original"; values.push(s); - for (var el in $$(menu#display-options>li)) { + var c = handler.get_option("codec-preference"); + if (!c) c = "auto"; + values.push(c); + for (var el in $$(menu#display-options li)) { el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); } for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 4d941e1f8..a738d7f8b 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -231,6 +231,9 @@ impl sciter::EventHandler for Handler { fn get_remember(); fn peer_platform(); fn set_write_override(i32, i32, bool, bool, bool); + fn has_hwcodec(); + fn supported_hwcodec(); + fn change_prefer_codec(); } } @@ -595,6 +598,42 @@ impl Handler { true } + fn has_hwcodec(&self) -> bool { + #[cfg(not(feature = "hwcodec"))] + return false; + #[cfg(feature = "hwcodec")] + return true; + } + + fn supported_hwcodec(&self) -> Value { + #[cfg(feature = "hwcodec")] + { + let mut v = Value::array(0); + let decoder = scrap::codec::Decoder::video_codec_state(&self.id); + let mut h264 = decoder.score_h264 > 0; + let mut h265 = decoder.score_h265 > 0; + if let Some((encoding_264, encoding_265)) = self.lc.read().unwrap().supported_encoding { + h264 = h264 && encoding_264; + h265 = h265 && encoding_265; + } + v.push(h264); + v.push(h265); + v + } + #[cfg(not(feature = "hwcodec"))] + { + let mut v = Value::array(0); + v.push(false); + v.push(false); + v + } + } + + fn change_prefer_codec(&self) { + let msg = self.lc.write().unwrap().change_prefer_codec(); + self.send(Data::Message(msg)); + } + fn t(&self, name: String) -> String { crate::client::translate(name) }