From 610009528bc651c5d80187e9462c89b9edc365bc Mon Sep 17 00:00:00 2001
From: 21pages <pages21@163.com>
Date: Wed, 12 Jun 2024 20:40:35 +0800
Subject: [PATCH] hwcodec, only process that start ipc server start check
 process (#8325)

check process send config to ipc server, other process get config from ipc server. Process will save config to toml, and the toml will be used if the config is none.

when start check process: ipc server process start or option changed
from disable to enable

when get config: main window start or option changed from disable to
enable, start_video_audio_threads.

Only windows implements signature, which is used to mark whether the gpu software and hardware information changes. After reboot, the signature doesn't change. https://asawicki.info/news_1773_how_to_programmatically_check_graphics_driver_version, use dxgi way to get software version, it's not consistent with the visible driver version, after updating intel driver with small version change, the signature doesn't change. Linux doesn't use toml file.

Signed-off-by: 21pages <sunboeasy@gmail.com>
---
 Cargo.lock                       |   4 +-
 libs/hbb_common/src/config.rs    |  46 ++--
 libs/scrap/src/common/codec.rs   |   7 +-
 libs/scrap/src/common/hwcodec.rs | 353 +++++++++++++++++++------------
 libs/scrap/src/common/vram.rs    |  35 ++-
 src/client.rs                    |  22 ++
 src/core_main.rs                 |   2 +-
 src/ipc.rs                       | 104 ++++++++-
 src/server.rs                    |   9 +-
 src/ui_interface.rs              |  21 +-
 10 files changed, 393 insertions(+), 210 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 06763278a..48d488151 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3037,8 +3037,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 [[package]]
 name = "hwcodec"
-version = "0.4.15"
-source = "git+https://github.com/21pages/hwcodec#1d504ee590c15472813fecc22cee4b8149b2b8cd"
+version = "0.4.16"
+source = "git+https://github.com/21pages/hwcodec#0973290faddc4e22936859dd10f05610eb8d1619"
 dependencies = [
  "bindgen 0.59.2",
  "cc",
diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs
index d2f568fd4..b9533407a 100644
--- a/libs/hbb_common/src/config.rs
+++ b/libs/hbb_common/src/config.rs
@@ -1553,40 +1553,6 @@ impl LanPeers {
     }
 }
 
-#[derive(Debug, Default, Serialize, Deserialize, Clone)]
-pub struct HwCodecConfig {
-    #[serde(default, deserialize_with = "deserialize_string")]
-    pub ram: String,
-    #[serde(default, deserialize_with = "deserialize_string")]
-    pub vram: String,
-}
-
-impl HwCodecConfig {
-    pub fn load() -> HwCodecConfig {
-        Config::load_::<HwCodecConfig>("_hwcodec")
-    }
-
-    pub fn store(&self) {
-        Config::store_(self, "_hwcodec");
-    }
-
-    pub fn clear() {
-        HwCodecConfig::default().store();
-    }
-
-    pub fn clear_ram() {
-        let mut c = Self::load();
-        c.ram = Default::default();
-        c.store();
-    }
-
-    pub fn clear_vram() {
-        let mut c = Self::load();
-        c.vram = Default::default();
-        c.store();
-    }
-}
-
 #[derive(Debug, Default, Serialize, Deserialize, Clone)]
 pub struct UserDefaultConfig {
     #[serde(default, deserialize_with = "deserialize_hashmap_string_string")]
@@ -2238,6 +2204,18 @@ pub mod keys {
     ];
 }
 
+pub fn common_load<
+    T: serde::Serialize + serde::de::DeserializeOwned + Default + std::fmt::Debug,
+>(
+    suffix: &str,
+) -> T {
+    Config::load_::<T>(suffix)
+}
+
+pub fn common_store<T: serde::Serialize>(config: &T, suffix: &str) {
+    Config::store_(config, suffix);
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs
index 098daa998..343b1997b 100644
--- a/libs/scrap/src/common/codec.rs
+++ b/libs/scrap/src/common/codec.rs
@@ -144,10 +144,7 @@ impl Encoder {
                 }),
                 Err(e) => {
                     log::error!("new hw encoder failed: {e:?}, clear config");
-                    #[cfg(target_os = "android")]
-                    crate::android::ffi::clear_codec_info();
-                    #[cfg(not(target_os = "android"))]
-                    hbb_common::config::HwCodecConfig::clear_ram();
+                    HwCodecConfig::clear(false, true);
                     Self::update(EncodingUpdate::Check);
                     *ENCODE_CODEC_FORMAT.lock().unwrap() = CodecFormat::VP9;
                     Err(e)
@@ -160,7 +157,7 @@ impl Encoder {
                 }),
                 Err(e) => {
                     log::error!("new vram encoder failed: {e:?}, clear config");
-                    hbb_common::config::HwCodecConfig::clear_vram();
+                    HwCodecConfig::clear(true, true);
                     Self::update(EncodingUpdate::Check);
                     *ENCODE_CODEC_FORMAT.lock().unwrap() = CodecFormat::VP9;
                     Err(e)
diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs
index 8c50d12c7..ed713887a 100644
--- a/libs/scrap/src/common/hwcodec.rs
+++ b/libs/scrap/src/common/hwcodec.rs
@@ -8,7 +8,6 @@ use crate::{
 use hbb_common::{
     anyhow::{anyhow, bail, Context},
     bytes::Bytes,
-    config::HwCodecConfig,
     log,
     message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
     serde_derive::{Deserialize, Serialize},
@@ -16,7 +15,7 @@ use hbb_common::{
 };
 use hwcodec::{
     common::{
-        DataFormat,
+        get_gpu_signature, DataFormat,
         Quality::{self, *},
         RateControl::{self, *},
     },
@@ -35,6 +34,12 @@ const DEFAULT_HW_QUALITY: Quality = Quality_Default;
 
 crate::generate_call_macro!(call_yuv, false);
 
+#[cfg(not(target_os = "android"))]
+lazy_static::lazy_static! {
+    static ref CONFIG: std::sync::Arc<std::sync::Mutex<Option<HwCodecConfig>>> = Default::default();
+    static ref CONFIG_SET_BY_IPC: std::sync::Arc<std::sync::Mutex<bool>> = Default::default();
+}
+
 #[derive(Debug, Clone)]
 pub struct HwRamEncoderConfig {
     pub name: String,
@@ -210,21 +215,19 @@ impl EncoderApi for HwRamEncoder {
 impl HwRamEncoder {
     pub fn try_get(format: CodecFormat) -> Option<CodecInfo> {
         let mut info = None;
-        if let Ok(hw) = get_config().map(|c| c.e) {
-            let best = CodecInfo::prioritized(hw);
-            match format {
-                CodecFormat::H264 => {
-                    if let Some(v) = best.h264 {
-                        info = Some(v);
-                    }
+        let best = CodecInfo::prioritized(HwCodecConfig::get().ram_encode);
+        match format {
+            CodecFormat::H264 => {
+                if let Some(v) = best.h264 {
+                    info = Some(v);
                 }
-                CodecFormat::H265 => {
-                    if let Some(v) = best.h265 {
-                        info = Some(v);
-                    }
-                }
-                _ => {}
             }
+            CodecFormat::H265 => {
+                if let Some(v) = best.h265 {
+                    info = Some(v);
+                }
+            }
+            _ => {}
         }
         info
     }
@@ -313,21 +316,19 @@ impl HwRamDecoder {
             _ => {}
         }
         if enable_hwcodec_option() {
-            if let Ok(hw) = get_config().map(|c| c.d) {
-                let best = CodecInfo::prioritized(hw);
-                match format {
-                    CodecFormat::H264 => {
-                        if let Some(v) = best.h264 {
-                            info = Some(v);
-                        }
+            let best = CodecInfo::prioritized(HwCodecConfig::get().ram_decode);
+            match format {
+                CodecFormat::H264 => {
+                    if let Some(v) = best.h264 {
+                        info = Some(v);
                     }
-                    CodecFormat::H265 => {
-                        if let Some(v) = best.h265 {
-                            info = Some(v);
-                        }
-                    }
-                    _ => {}
                 }
+                CodecFormat::H265 => {
+                    if let Some(v) = best.h265 {
+                        info = Some(v);
+                    }
+                }
+                _ => {}
             }
         }
         info
@@ -347,10 +348,7 @@ impl HwRamDecoder {
         match Decoder::new(ctx) {
             Ok(decoder) => Ok(HwRamDecoder { decoder, info }),
             Err(_) => {
-                #[cfg(target_os = "android")]
-                crate::android::ffi::clear_codec_info();
-                #[cfg(not(target_os = "android"))]
-                hbb_common::config::HwCodecConfig::clear_ram();
+                HwCodecConfig::clear(false, false);
                 Err(anyhow!(format!("Failed to create decoder")))
             }
         }
@@ -467,85 +465,6 @@ impl HwRamDecoderImage<'_> {
     }
 }
 
-#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
-struct Available {
-    e: Vec<CodecInfo>,
-    d: Vec<CodecInfo>,
-}
-
-fn get_config() -> ResultType<Available> {
-    #[cfg(target_os = "android")]
-    {
-        let info = crate::android::ffi::get_codec_info();
-        log::info!("all codec info: {info:?}");
-        struct T {
-            name_prefix: &'static str,
-            data_format: DataFormat,
-        }
-        let ts = vec![
-            T {
-                name_prefix: "h264",
-                data_format: DataFormat::H264,
-            },
-            T {
-                name_prefix: "hevc",
-                data_format: DataFormat::H265,
-            },
-        ];
-        let mut e = vec![];
-        if let Some(info) = info {
-            ts.iter().for_each(|t| {
-                let codecs: Vec<_> = info
-                    .codecs
-                    .iter()
-                    .filter(|c| {
-                        c.is_encoder
-                            && c.mime_type.as_str() == get_mime_type(t.data_format)
-                            && c.nv12
-                            && c.hw == Some(true) //only use hardware codec
-                    })
-                    .collect();
-                log::debug!("available {:?} encoders: {codecs:?}", t.data_format);
-                let screen_wh = std::cmp::max(info.w, info.h);
-                let mut best = None;
-                if let Some(codec) = codecs
-                    .iter()
-                    .find(|c| c.max_width >= screen_wh && c.max_height >= screen_wh)
-                {
-                    best = Some(codec.name.clone());
-                } else {
-                    // find the max resolution
-                    let mut max_area = 0;
-                    for codec in codecs.iter() {
-                        if codec.max_width * codec.max_height > max_area {
-                            best = Some(codec.name.clone());
-                            max_area = codec.max_width * codec.max_height;
-                        }
-                    }
-                }
-                if let Some(best) = best {
-                    e.push(CodecInfo {
-                        name: format!("{}_mediacodec", t.name_prefix),
-                        mc_name: Some(best),
-                        format: t.data_format,
-                        hwdevice: hwcodec::ffmpeg::AVHWDeviceType::AV_HWDEVICE_TYPE_NONE,
-                        priority: 0,
-                    });
-                }
-            });
-        }
-        log::debug!("e: {e:?}");
-        Ok(Available { e, d: vec![] })
-    }
-    #[cfg(not(target_os = "android"))]
-    {
-        match serde_json::from_str(&HwCodecConfig::load().ram) {
-            Ok(v) => Ok(v),
-            Err(e) => Err(anyhow!("Failed to get config:{e:?}")),
-        }
-    }
-}
-
 #[cfg(target_os = "android")]
 fn get_mime_type(codec: DataFormat) -> &'static str {
     match codec {
@@ -557,7 +476,181 @@ fn get_mime_type(codec: DataFormat) -> &'static str {
     }
 }
 
-pub fn check_available_hwcodec() {
+#[derive(Debug, Default, Serialize, Deserialize, Clone)]
+pub struct HwCodecConfig {
+    #[serde(default)]
+    pub signature: u64,
+    #[serde(default)]
+    pub ram_encode: Vec<CodecInfo>,
+    #[serde(default)]
+    pub ram_decode: Vec<CodecInfo>,
+    #[cfg(feature = "vram")]
+    #[serde(default)]
+    pub vram_encode: Vec<hwcodec::vram::FeatureContext>,
+    #[cfg(feature = "vram")]
+    #[serde(default)]
+    pub vram_decode: Vec<hwcodec::vram::DecodeContext>,
+}
+
+// ipc server process start check process once, other process get from ipc server once
+// install: --server start check process, check process send to --server,  ui get from --server
+// portable: ui start check process, check process send to ui
+// sciter and unilink: get from ipc server
+impl HwCodecConfig {
+    #[cfg(any(target_os = "windows", target_os = "linux"))]
+    pub fn set(config: String) {
+        let config = serde_json::from_str(&config).unwrap_or_default();
+        log::info!("set hwcodec config");
+        log::debug!("{config:?}");
+        #[cfg(windows)]
+        hbb_common::config::common_store(&config, "_hwcodec");
+        *CONFIG.lock().unwrap() = Some(config);
+        *CONFIG_SET_BY_IPC.lock().unwrap() = true;
+    }
+
+    pub fn get() -> HwCodecConfig {
+        #[cfg(target_os = "android")]
+        {
+            let info = crate::android::ffi::get_codec_info();
+            log::info!("all codec info: {info:?}");
+            struct T {
+                name_prefix: &'static str,
+                data_format: DataFormat,
+            }
+            let ts = vec![
+                T {
+                    name_prefix: "h264",
+                    data_format: DataFormat::H264,
+                },
+                T {
+                    name_prefix: "hevc",
+                    data_format: DataFormat::H265,
+                },
+            ];
+            let mut e = vec![];
+            if let Some(info) = info {
+                ts.iter().for_each(|t| {
+                    let codecs: Vec<_> = info
+                        .codecs
+                        .iter()
+                        .filter(|c| {
+                            c.is_encoder
+                                && c.mime_type.as_str() == get_mime_type(t.data_format)
+                                && c.nv12
+                                && c.hw == Some(true) //only use hardware codec
+                        })
+                        .collect();
+                    let screen_wh = std::cmp::max(info.w, info.h);
+                    let mut best = None;
+                    if let Some(codec) = codecs
+                        .iter()
+                        .find(|c| c.max_width >= screen_wh && c.max_height >= screen_wh)
+                    {
+                        best = Some(codec.name.clone());
+                    } else {
+                        // find the max resolution
+                        let mut max_area = 0;
+                        for codec in codecs.iter() {
+                            if codec.max_width * codec.max_height > max_area {
+                                best = Some(codec.name.clone());
+                                max_area = codec.max_width * codec.max_height;
+                            }
+                        }
+                    }
+                    if let Some(best) = best {
+                        e.push(CodecInfo {
+                            name: format!("{}_mediacodec", t.name_prefix),
+                            mc_name: Some(best),
+                            format: t.data_format,
+                            hwdevice: hwcodec::ffmpeg::AVHWDeviceType::AV_HWDEVICE_TYPE_NONE,
+                            priority: 0,
+                        });
+                    }
+                });
+            }
+            log::debug!("e: {e:?}");
+            HwCodecConfig {
+                ram_encode: e,
+                ..Default::default()
+            }
+        }
+        #[cfg(windows)]
+        {
+            let config = CONFIG.lock().unwrap().clone();
+            match config {
+                Some(c) => c,
+                None => {
+                    log::info!("try load cached hwcodec config");
+                    let c = hbb_common::config::common_load::<HwCodecConfig>("_hwcodec");
+                    let new_signature = get_gpu_signature();
+                    if c.signature == new_signature {
+                        log::debug!("load cached hwcodec config: {c:?}");
+                        *CONFIG.lock().unwrap() = Some(c.clone());
+                        c
+                    } else {
+                        log::info!(
+                            "gpu signature changed, {} -> {}",
+                            c.signature,
+                            new_signature
+                        );
+                        HwCodecConfig::default()
+                    }
+                }
+            }
+        }
+        #[cfg(target_os = "linux")]
+        {
+            CONFIG.lock().unwrap().clone().unwrap_or_default()
+        }
+        #[cfg(any(target_os = "macos", target_os = "ios"))]
+        {
+            HwCodecConfig::default()
+        }
+    }
+
+    #[cfg(any(target_os = "windows", target_os = "linux"))]
+    pub fn get_set_value() -> Option<HwCodecConfig> {
+        let set = CONFIG_SET_BY_IPC.lock().unwrap().clone();
+        if set {
+            CONFIG.lock().unwrap().clone()
+        } else {
+            None
+        }
+    }
+
+    #[cfg(any(target_os = "windows", target_os = "linux"))]
+    pub fn already_set() -> bool {
+        CONFIG_SET_BY_IPC.lock().unwrap().clone()
+    }
+
+    pub fn clear(vram: bool, encode: bool) {
+        log::info!("clear hwcodec config, vram: {vram}, encode: {encode}");
+        #[cfg(target_os = "android")]
+        crate::android::ffi::clear_codec_info();
+        #[cfg(not(target_os = "android"))]
+        {
+            let mut c = CONFIG.lock().unwrap();
+            if let Some(c) = c.as_mut() {
+                if vram {
+                    #[cfg(feature = "vram")]
+                    if encode {
+                        c.vram_encode = vec![];
+                    } else {
+                        c.vram_decode = vec![];
+                    }
+                } else {
+                    if encode {
+                        c.ram_encode = vec![];
+                    } else {
+                        c.ram_decode = vec![];
+                    }
+                }
+            }
+        }
+    }
+}
+
+pub fn check_available_hwcodec() -> String {
     let ctx = EncodeContext {
         name: String::from(""),
         mc_name: None,
@@ -575,29 +668,31 @@ pub fn check_available_hwcodec() {
     };
     #[cfg(feature = "vram")]
     let vram = crate::vram::check_available_vram();
+    #[cfg(feature = "vram")]
+    let vram_string = vram.2;
     #[cfg(not(feature = "vram"))]
-    let vram = "".to_owned();
-    let ram = Available {
-        e: Encoder::available_encoders(ctx, Some(vram.clone())),
-        d: Decoder::available_decoders(Some(vram.clone())),
+    let vram_string = "".to_owned();
+    let c = HwCodecConfig {
+        ram_encode: Encoder::available_encoders(ctx, Some(vram_string.clone())),
+        ram_decode: Decoder::available_decoders(Some(vram_string)),
+        #[cfg(feature = "vram")]
+        vram_encode: vram.0,
+        #[cfg(feature = "vram")]
+        vram_decode: vram.1,
+        signature: get_gpu_signature(),
     };
-    if let Ok(ram) = serde_json::to_string_pretty(&ram) {
-        HwCodecConfig { ram, vram }.store();
-    }
+    log::debug!("{c:?}");
+    serde_json::to_string(&c).unwrap_or_default()
 }
 
 #[cfg(any(target_os = "windows", target_os = "linux"))]
-pub fn start_check_process(force: bool) {
-    if !force && !enable_hwcodec_option() {
+pub fn start_check_process() {
+    if !enable_hwcodec_option() || HwCodecConfig::already_set() {
         return;
     }
     use hbb_common::allow_err;
     use std::sync::Once;
     let f = || {
-        // Clear to avoid checking process errors
-        // But when the program is just started, the configuration file has not been updated, and the new connection will read an empty configuration
-        // TODO: --server start multi times on windows startup, which will clear the last config and cause concurrent file writing
-        HwCodecConfig::clear();
         if let Ok(exe) = std::env::current_exe() {
             if let Some(_) = exe.file_name().to_owned() {
                 let arg = "--check-hwcodec-config";
@@ -631,11 +726,7 @@ pub fn start_check_process(force: bool) {
         };
     };
     static ONCE: Once = Once::new();
-    if force && ONCE.is_completed() {
+    ONCE.call_once(|| {
         std::thread::spawn(f);
-    } else {
-        ONCE.call_once(|| {
-            std::thread::spawn(f);
-        });
-    }
+    });
 }
diff --git a/libs/scrap/src/common/vram.rs b/libs/scrap/src/common/vram.rs
index d99a6bc59..8baf6ea69 100644
--- a/libs/scrap/src/common/vram.rs
+++ b/libs/scrap/src/common/vram.rs
@@ -6,6 +6,7 @@ use std::{
 
 use crate::{
     codec::{base_bitrate, enable_vram_option, EncoderApi, EncoderCfg, Quality},
+    hwcodec::HwCodecConfig,
     AdapterDevice, CodecFormat, EncodeInput, EncodeYuvFormat, Pixfmt,
 };
 use hbb_common::{
@@ -228,9 +229,8 @@ impl VRamEncoder {
             CodecFormat::H265 => DataFormat::H265,
             _ => return vec![],
         };
-        let v: Vec<_> = get_available_config()
-            .map(|c| c.e)
-            .unwrap_or_default()
+        let v: Vec<_> = crate::hwcodec::HwCodecConfig::get()
+            .vram_encode
             .drain(..)
             .filter(|c| c.data_format == data_format)
             .collect();
@@ -339,9 +339,8 @@ impl VRamDecoder {
             CodecFormat::H265 => DataFormat::H265,
             _ => return vec![],
         };
-        get_available_config()
-            .map(|c| c.d)
-            .unwrap_or_default()
+        crate::hwcodec::HwCodecConfig::get()
+            .vram_decode
             .drain(..)
             .filter(|c| c.data_format == data_format && c.luid == luid && luid != 0)
             .collect()
@@ -351,7 +350,7 @@ impl VRamDecoder {
         if !enable_vram_option() {
             return (false, false);
         }
-        let v = get_available_config().map(|c| c.d).unwrap_or_default();
+        let v = crate::hwcodec::HwCodecConfig::get().vram_decode;
         (
             v.iter().any(|d| d.data_format == DataFormat::H264),
             v.iter().any(|d| d.data_format == DataFormat::H265),
@@ -364,7 +363,7 @@ impl VRamDecoder {
         match Decoder::new(ctx) {
             Ok(decoder) => Ok(Self { decoder }),
             Err(_) => {
-                hbb_common::config::HwCodecConfig::clear_vram();
+                HwCodecConfig::clear(true, false);
                 Err(anyhow!(format!(
                     "Failed to create decoder, format: {:?}",
                     format
@@ -386,15 +385,7 @@ pub struct VRamDecoderImage<'a> {
 
 impl VRamDecoderImage<'_> {}
 
-fn get_available_config() -> ResultType<Available> {
-    let available = hbb_common::config::HwCodecConfig::load().vram;
-    match Available::deserialize(&available) {
-        Ok(v) => Ok(v),
-        Err(_) => Err(anyhow!("Failed to deserialize:{}", available)),
-    }
-}
-
-pub(crate) fn check_available_vram() -> String {
+pub(crate) fn check_available_vram() -> (Vec<FeatureContext>, Vec<DecodeContext>, String) {
     let d = DynamicContext {
         device: None,
         width: 1280,
@@ -406,8 +397,12 @@ pub(crate) fn check_available_vram() -> String {
     let encoders = encode::available(d);
     let decoders = decode::available();
     let available = Available {
-        e: encoders,
-        d: decoders,
+        e: encoders.clone(),
+        d: decoders.clone(),
     };
-    available.serialize().unwrap_or_default()
+    (
+        encoders,
+        decoders,
+        available.serialize().unwrap_or_default(),
+    )
 }
diff --git a/src/client.rs b/src/client.rs
index b3f790d67..41f290bf0 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -2132,6 +2132,7 @@ where
     std::thread::spawn(move || {
         #[cfg(windows)]
         sync_cpu_usage();
+        get_hwcodec_config();
         let mut handler_controller_map = HashMap::new();
         // let mut count = Vec::new();
         // let mut duration = std::time::Duration::ZERO;
@@ -2333,6 +2334,27 @@ pub fn start_audio_thread() -> MediaSender {
     audio_sender
 }
 
+fn get_hwcodec_config() {
+    // for sciter and unilink
+    #[cfg(feature = "hwcodec")]
+    #[cfg(any(target_os = "windows", target_os = "linux"))]
+    {
+        use std::sync::Once;
+        static ONCE: Once = Once::new();
+        ONCE.call_once(|| {
+            let start = std::time::Instant::now();
+            if let Err(e) = crate::ipc::get_hwcodec_config_from_server() {
+                log::error!(
+                    "failed to get hwcodec config: {e:?}, elapsed: {:?}",
+                    start.elapsed()
+                );
+            } else {
+                log::info!("{:?} used to get hwcodec config", start.elapsed());
+            }
+        });
+    }
+}
+
 #[cfg(windows)]
 fn sync_cpu_usage() {
     use std::sync::Once;
diff --git a/src/core_main.rs b/src/core_main.rs
index e91b55710..bf7e536f6 100644
--- a/src/core_main.rs
+++ b/src/core_main.rs
@@ -412,7 +412,7 @@ pub fn core_main() -> Option<Vec<String>> {
             return None;
         } else if args[0] == "--check-hwcodec-config" {
             #[cfg(feature = "hwcodec")]
-            scrap::hwcodec::check_available_hwcodec();
+            crate::ipc::hwcodec_process();
             return None;
         } else if args[0] == "--cm" {
             // call connection manager to establish connections
diff --git a/src/ipc.rs b/src/ipc.rs
index a10a8f955..47cac05a6 100644
--- a/src/ipc.rs
+++ b/src/ipc.rs
@@ -239,6 +239,7 @@ pub enum Data {
     VideoConnCount(Option<usize>),
     // Although the key is not neccessary, it is used to avoid hardcoding the key.
     WaylandScreencastRestoreToken((String, String)),
+    HwCodecConfig(Option<String>),
 }
 
 #[tokio::main(flavor = "current_thread")]
@@ -523,12 +524,26 @@ async fn handle(data: Data, stream: &mut Connection) {
                     .await
             );
         }
-        Data::CheckHwcodec =>
-        {
-            #[cfg(feature = "hwcodec")]
-            #[cfg(any(target_os = "windows", target_os = "linux"))]
-            if crate::platform::is_root() {
-                scrap::hwcodec::start_check_process(true);
+        #[cfg(feature = "hwcodec")]
+        #[cfg(any(target_os = "windows", target_os = "linux"))]
+        Data::CheckHwcodec => {
+            scrap::hwcodec::start_check_process();
+        }
+        #[cfg(feature = "hwcodec")]
+        #[cfg(any(target_os = "windows", target_os = "linux"))]
+        Data::HwCodecConfig(c) => {
+            match c {
+                None => {
+                    let v = match scrap::hwcodec::HwCodecConfig::get_set_value() {
+                        Some(v) => Some(serde_json::to_string(&v).unwrap_or_default()),
+                        None => None,
+                    };
+                    allow_err!(stream.send(&Data::HwCodecConfig(v)).await);
+                }
+                Some(v) => {
+                    // --server and portable
+                    scrap::hwcodec::HwCodecConfig::set(v);
+                }
             }
         }
         Data::WaylandScreencastRestoreToken((key, value)) => {
@@ -1025,6 +1040,83 @@ pub async fn notify_server_to_check_hwcodec() -> ResultType<()> {
     Ok(())
 }
 
+#[cfg(feature = "hwcodec")]
+#[cfg(any(target_os = "windows", target_os = "linux"))]
+#[tokio::main(flavor = "current_thread")]
+pub async fn get_hwcodec_config_from_server() -> ResultType<()> {
+    if !scrap::codec::enable_hwcodec_option() || scrap::hwcodec::HwCodecConfig::already_set() {
+        return Ok(());
+    }
+    let mut c = connect(50, "").await?;
+    c.send(&Data::HwCodecConfig(None)).await?;
+    if let Some(Data::HwCodecConfig(v)) = c.next_timeout(50).await? {
+        match v {
+            Some(v) => {
+                scrap::hwcodec::HwCodecConfig::set(v);
+                return Ok(());
+            }
+            None => {
+                bail!("hwcodec config is none");
+            }
+        }
+    }
+    bail!("failed to get hwcodec config");
+}
+
+#[cfg(feature = "hwcodec")]
+#[cfg(any(target_os = "windows", target_os = "linux"))]
+pub fn client_get_hwcodec_config_thread(wait_sec: u64) {
+    static ONCE: std::sync::Once = std::sync::Once::new();
+    if !crate::platform::is_installed()
+        || !scrap::codec::enable_hwcodec_option()
+        || scrap::hwcodec::HwCodecConfig::already_set()
+    {
+        return;
+    }
+    ONCE.call_once(move || {
+        std::thread::spawn(move || {
+            std::thread::sleep(std::time::Duration::from_secs(1));
+            let mut intervals: Vec<u64> = vec![wait_sec, 3, 3, 6, 9];
+            for i in intervals.drain(..) {
+                if i > 0 {
+                    std::thread::sleep(std::time::Duration::from_secs(i));
+                }
+                if get_hwcodec_config_from_server().is_ok() {
+                    break;
+                }
+            }
+        });
+    });
+}
+
+#[cfg(feature = "hwcodec")]
+#[tokio::main(flavor = "current_thread")]
+pub async fn hwcodec_process() {
+    let s = scrap::hwcodec::check_available_hwcodec();
+    for _ in 0..5 {
+        match crate::ipc::connect(1000, "").await {
+            Ok(mut conn) => {
+                match conn
+                    .send(&crate::ipc::Data::HwCodecConfig(Some(s.clone())))
+                    .await
+                {
+                    Ok(()) => {
+                        log::info!("send ok");
+                        break;
+                    }
+                    Err(e) => {
+                        log::error!("send failed: {e:?}");
+                    }
+                }
+            }
+            Err(e) => {
+                log::error!("connect failed: {e:?}");
+            }
+        }
+        std::thread::sleep(std::time::Duration::from_secs(1));
+    }
+}
+
 #[tokio::main(flavor = "current_thread")]
 pub async fn get_wayland_screencast_restore_token(key: String) -> ResultType<String> {
     let v = handle_wayland_screencast_restore_token(key, "get".to_owned()).await?;
diff --git a/src/server.rs b/src/server.rs
index 81b509034..43edaea0e 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -459,9 +459,6 @@ pub async fn start_server(is_server: bool) {
         log::info!("DISPLAY={:?}", std::env::var("DISPLAY"));
         log::info!("XAUTHORITY={:?}", std::env::var("XAUTHORITY"));
     }
-    #[cfg(feature = "hwcodec")]
-    #[cfg(any(target_os = "windows", target_os = "linux"))]
-    scrap::hwcodec::start_check_process(false);
     #[cfg(windows)]
     hbb_common::platform::windows::start_cpu_performance_monitor();
 
@@ -489,6 +486,9 @@ pub async fn start_server(is_server: bool) {
         tokio::spawn(async { sync_and_watch_config_dir().await });
         #[cfg(target_os = "windows")]
         crate::platform::try_kill_broker();
+        #[cfg(feature = "hwcodec")]
+        #[cfg(any(target_os = "windows", target_os = "linux"))]
+        scrap::hwcodec::start_check_process();
         crate::RendezvousMediator::start_all().await;
     } else {
         match crate::ipc::connect(1000, "").await {
@@ -509,6 +509,9 @@ pub async fn start_server(is_server: bool) {
                         }
                     }
                 }
+                #[cfg(feature = "hwcodec")]
+                #[cfg(any(target_os = "windows", target_os = "linux"))]
+                crate::ipc::client_get_hwcodec_config_thread(0);
             }
             Err(err) => {
                 log::info!("server not started (will try to start): {}", err);
diff --git a/src/ui_interface.rs b/src/ui_interface.rs
index b58ed6bb3..6c95d4c4a 100644
--- a/src/ui_interface.rs
+++ b/src/ui_interface.rs
@@ -172,15 +172,13 @@ pub fn get_option<T: AsRef<str>>(key: T) -> String {
 #[inline]
 #[cfg(target_os = "macos")]
 pub fn use_texture_render() -> bool {
-    cfg!(feature = "flutter")
-        && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y"
+    cfg!(feature = "flutter") && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y"
 }
 
 #[inline]
 #[cfg(any(target_os = "windows", target_os = "linux"))]
 pub fn use_texture_render() -> bool {
-    cfg!(feature = "flutter")
-        && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
+    cfg!(feature = "flutter") && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
 }
 
 #[inline]
@@ -1398,9 +1396,16 @@ pub fn check_hwcodec() {
     #[cfg(feature = "hwcodec")]
     #[cfg(any(target_os = "windows", target_os = "linux"))]
     {
-        scrap::hwcodec::start_check_process(true);
-        if crate::platform::is_installed() {
-            ipc::notify_server_to_check_hwcodec().ok();
-        }
+        use std::sync::Once;
+        static ONCE: Once = Once::new();
+
+        ONCE.call_once(|| {
+            if crate::platform::is_installed() {
+                ipc::notify_server_to_check_hwcodec().ok();
+                ipc::client_get_hwcodec_config_thread(3);
+            } else {
+                scrap::hwcodec::start_check_process();
+            }
+        })
     }
 }