421 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			421 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::{
 | |
|     collections::{HashMap, HashSet},
 | |
|     ffi::c_void,
 | |
|     sync::{Arc, Mutex},
 | |
| };
 | |
| 
 | |
| use crate::{
 | |
|     codec::{base_bitrate, enable_vram_option, EncoderApi, EncoderCfg, Quality},
 | |
|     hwcodec::HwCodecConfig,
 | |
|     AdapterDevice, CodecFormat, EncodeInput, EncodeYuvFormat, Pixfmt,
 | |
| };
 | |
| use hbb_common::{
 | |
|     anyhow::{anyhow, bail, Context},
 | |
|     bytes::Bytes,
 | |
|     log,
 | |
|     message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame},
 | |
|     ResultType,
 | |
| };
 | |
| use hwcodec::{
 | |
|     common::{AdapterVendor::*, DataFormat, Driver, MAX_GOP},
 | |
|     vram::{
 | |
|         decode::{self, DecodeFrame, Decoder},
 | |
|         encode::{self, EncodeFrame, Encoder},
 | |
|         Available, DecodeContext, DynamicContext, EncodeContext, FeatureContext,
 | |
|     },
 | |
| };
 | |
| 
 | |
| // https://www.reddit.com/r/buildapc/comments/d2m4ny/two_graphics_cards_two_monitors/
 | |
| // https://www.reddit.com/r/techsupport/comments/t2v9u6/dual_monitor_setup_with_dual_gpu/
 | |
| // https://cybersided.com/two-monitors-two-gpus/
 | |
| // https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-getadapterluid#remarks
 | |
| lazy_static::lazy_static! {
 | |
|     static ref ENOCDE_NOT_USE: Arc<Mutex<HashMap<usize, bool>>> = Default::default();
 | |
|     static ref FALLBACK_GDI_DISPLAYS: Arc<Mutex<HashSet<usize>>> = Default::default();
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Clone)]
 | |
| pub struct VRamEncoderConfig {
 | |
|     pub device: AdapterDevice,
 | |
|     pub width: usize,
 | |
|     pub height: usize,
 | |
|     pub quality: Quality,
 | |
|     pub feature: FeatureContext,
 | |
|     pub keyframe_interval: Option<usize>,
 | |
| }
 | |
| 
 | |
| pub struct VRamEncoder {
 | |
|     encoder: Encoder,
 | |
|     pub format: DataFormat,
 | |
|     ctx: EncodeContext,
 | |
|     bitrate: u32,
 | |
|     last_frame_len: usize,
 | |
|     same_bad_len_counter: usize,
 | |
|     config: VRamEncoderConfig,
 | |
| }
 | |
| 
 | |
| impl EncoderApi for VRamEncoder {
 | |
|     fn new(cfg: EncoderCfg, _i444: bool) -> ResultType<Self>
 | |
|     where
 | |
|         Self: Sized,
 | |
|     {
 | |
|         match cfg {
 | |
|             EncoderCfg::VRAM(config) => {
 | |
|                 let b = Self::convert_quality(config.quality, &config.feature);
 | |
|                 let base_bitrate = base_bitrate(config.width as _, config.height as _);
 | |
|                 let mut bitrate = base_bitrate * b / 100;
 | |
|                 if base_bitrate <= 0 {
 | |
|                     bitrate = base_bitrate;
 | |
|                 }
 | |
|                 let gop = config.keyframe_interval.unwrap_or(MAX_GOP as _) as i32;
 | |
|                 let ctx = EncodeContext {
 | |
|                     f: config.feature.clone(),
 | |
|                     d: DynamicContext {
 | |
|                         device: Some(config.device.device),
 | |
|                         width: config.width as _,
 | |
|                         height: config.height as _,
 | |
|                         kbitrate: bitrate as _,
 | |
|                         framerate: 30,
 | |
|                         gop,
 | |
|                     },
 | |
|                 };
 | |
|                 match Encoder::new(ctx.clone()) {
 | |
|                     Ok(encoder) => Ok(VRamEncoder {
 | |
|                         encoder,
 | |
|                         ctx,
 | |
|                         format: config.feature.data_format,
 | |
|                         bitrate,
 | |
|                         last_frame_len: 0,
 | |
|                         same_bad_len_counter: 0,
 | |
|                         config,
 | |
|                     }),
 | |
|                     Err(_) => Err(anyhow!(format!("Failed to create encoder"))),
 | |
|                 }
 | |
|             }
 | |
|             _ => Err(anyhow!("encoder type mismatch")),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn encode_to_message(
 | |
|         &mut self,
 | |
|         frame: EncodeInput,
 | |
|         ms: i64,
 | |
|     ) -> ResultType<hbb_common::message_proto::VideoFrame> {
 | |
|         let (texture, rotation) = frame.texture()?;
 | |
|         if rotation != 0 {
 | |
|             // to-do: support rotation
 | |
|             // Both the encoder and display(w,h) information need to be changed.
 | |
|             bail!("rotation not supported");
 | |
|         }
 | |
|         let mut vf = VideoFrame::new();
 | |
|         let mut frames = Vec::new();
 | |
|         for frame in self
 | |
|             .encode(texture, ms)
 | |
|             .with_context(|| "Failed to encode")?
 | |
|         {
 | |
|             frames.push(EncodedVideoFrame {
 | |
|                 data: Bytes::from(frame.data),
 | |
|                 pts: frame.pts,
 | |
|                 key: frame.key == 1,
 | |
|                 ..Default::default()
 | |
|             });
 | |
|         }
 | |
|         if frames.len() > 0 {
 | |
|             // This kind of problem is occurred after a period of time when using AMD encoding,
 | |
|             // the encoding length is fixed at about 40, and the picture is still
 | |
|             const MIN_BAD_LEN: usize = 100;
 | |
|             const MAX_BAD_COUNTER: usize = 30;
 | |
|             let this_frame_len = frames[0].data.len();
 | |
|             if this_frame_len < MIN_BAD_LEN && this_frame_len == self.last_frame_len {
 | |
|                 self.same_bad_len_counter += 1;
 | |
|                 if self.same_bad_len_counter >= MAX_BAD_COUNTER {
 | |
|                     log::info!(
 | |
|                         "{} times encoding len is {}, switch",
 | |
|                         self.same_bad_len_counter,
 | |
|                         self.last_frame_len
 | |
|                     );
 | |
|                     bail!(crate::codec::ENCODE_NEED_SWITCH);
 | |
|                 }
 | |
|             } else {
 | |
|                 self.same_bad_len_counter = 0;
 | |
|             }
 | |
|             self.last_frame_len = this_frame_len;
 | |
|             let frames = EncodedVideoFrames {
 | |
|                 frames: frames.into(),
 | |
|                 ..Default::default()
 | |
|             };
 | |
|             match self.format {
 | |
|                 DataFormat::H264 => vf.set_h264s(frames),
 | |
|                 DataFormat::H265 => vf.set_h265s(frames),
 | |
|                 _ => bail!("{:?} not supported", self.format),
 | |
|             }
 | |
|             Ok(vf)
 | |
|         } else {
 | |
|             Err(anyhow!("no valid frame"))
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn yuvfmt(&self) -> EncodeYuvFormat {
 | |
|         // useless
 | |
|         EncodeYuvFormat {
 | |
|             pixfmt: Pixfmt::BGRA,
 | |
|             w: self.ctx.d.width as _,
 | |
|             h: self.ctx.d.height as _,
 | |
|             stride: Vec::new(),
 | |
|             u: 0,
 | |
|             v: 0,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(feature = "vram")]
 | |
|     fn input_texture(&self) -> bool {
 | |
|         true
 | |
|     }
 | |
| 
 | |
|     fn set_quality(&mut self, quality: Quality) -> ResultType<()> {
 | |
|         let b = Self::convert_quality(quality, &self.ctx.f);
 | |
|         let bitrate = base_bitrate(self.ctx.d.width as _, self.ctx.d.height as _) * b / 100;
 | |
|         if bitrate > 0 {
 | |
|             if self.encoder.set_bitrate((bitrate) as _).is_ok() {
 | |
|                 self.bitrate = bitrate;
 | |
|             }
 | |
|         }
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     fn bitrate(&self) -> u32 {
 | |
|         self.bitrate
 | |
|     }
 | |
| 
 | |
|     fn support_abr(&self) -> bool {
 | |
|         self.config.device.vendor_id != ADAPTER_VENDOR_INTEL as u32
 | |
|     }
 | |
| 
 | |
|     fn support_changing_quality(&self) -> bool {
 | |
|         true
 | |
|     }
 | |
| 
 | |
|     fn latency_free(&self) -> bool {
 | |
|         true
 | |
|     }
 | |
| 
 | |
|     fn is_hardware(&self) -> bool {
 | |
|         true
 | |
|     }
 | |
| 
 | |
|     fn disable(&self) {
 | |
|         HwCodecConfig::clear(true, true);
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl VRamEncoder {
 | |
|     pub fn try_get(device: &AdapterDevice, format: CodecFormat) -> Option<FeatureContext> {
 | |
|         let v: Vec<_> = Self::available(format)
 | |
|             .drain(..)
 | |
|             .filter(|e| e.luid == device.luid)
 | |
|             .collect();
 | |
|         if v.len() > 0 {
 | |
|             // prefer ffmpeg
 | |
|             if let Some(ctx) = v.iter().find(|c| c.driver == Driver::FFMPEG) {
 | |
|                 return Some(ctx.clone());
 | |
|             }
 | |
|             Some(v[0].clone())
 | |
|         } else {
 | |
|             None
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn available(format: CodecFormat) -> Vec<FeatureContext> {
 | |
|         let fallbacks = FALLBACK_GDI_DISPLAYS.lock().unwrap().clone();
 | |
|         if !fallbacks.is_empty() {
 | |
|             log::info!("fallback gdi displays not empty: {fallbacks:?}");
 | |
|             return vec![];
 | |
|         }
 | |
|         let not_use = ENOCDE_NOT_USE.lock().unwrap().clone();
 | |
|         if not_use.values().any(|not_use| *not_use) {
 | |
|             log::info!("currently not use vram encoders: {not_use:?}");
 | |
|             return vec![];
 | |
|         }
 | |
|         let data_format = match format {
 | |
|             CodecFormat::H264 => DataFormat::H264,
 | |
|             CodecFormat::H265 => DataFormat::H265,
 | |
|             _ => return vec![],
 | |
|         };
 | |
|         let v: Vec<_> = crate::hwcodec::HwCodecConfig::get()
 | |
|             .vram_encode
 | |
|             .drain(..)
 | |
|             .filter(|c| c.data_format == data_format)
 | |
|             .collect();
 | |
|         if crate::hwcodec::HwRamEncoder::try_get(format).is_some() {
 | |
|             // has fallback, no need to require all adapters support
 | |
|             v
 | |
|         } else {
 | |
|             let Ok(displays) = crate::Display::all() else {
 | |
|                 log::error!("failed to get displays");
 | |
|                 return vec![];
 | |
|             };
 | |
|             if displays.is_empty() {
 | |
|                 log::error!("no display found");
 | |
|                 return vec![];
 | |
|             }
 | |
|             let luids = displays
 | |
|                 .iter()
 | |
|                 .map(|d| d.adapter_luid())
 | |
|                 .collect::<Vec<_>>();
 | |
|             if luids
 | |
|                 .iter()
 | |
|                 .all(|luid| v.iter().any(|f| Some(f.luid) == *luid))
 | |
|             {
 | |
|                 v
 | |
|             } else {
 | |
|                 log::info!("not all adapters support {data_format:?}, luids = {luids:?}");
 | |
|                 vec![]
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn encode(&mut self, texture: *mut c_void, ms: i64) -> ResultType<Vec<EncodeFrame>> {
 | |
|         match self.encoder.encode(texture, ms) {
 | |
|             Ok(v) => {
 | |
|                 let mut data = Vec::<EncodeFrame>::new();
 | |
|                 data.append(v);
 | |
|                 Ok(data)
 | |
|             }
 | |
|             Err(_) => Ok(Vec::<EncodeFrame>::new()),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn convert_quality(quality: Quality, f: &FeatureContext) -> u32 {
 | |
|         match quality {
 | |
|             Quality::Best => {
 | |
|                 if f.driver == Driver::MFX && f.data_format == DataFormat::H264 {
 | |
|                     200
 | |
|                 } else {
 | |
|                     150
 | |
|                 }
 | |
|             }
 | |
|             Quality::Balanced => {
 | |
|                 if f.driver == Driver::MFX && f.data_format == DataFormat::H264 {
 | |
|                     150
 | |
|                 } else {
 | |
|                     100
 | |
|                 }
 | |
|             }
 | |
|             Quality::Low => {
 | |
|                 if f.driver == Driver::MFX && f.data_format == DataFormat::H264 {
 | |
|                     75
 | |
|                 } else {
 | |
|                     50
 | |
|                 }
 | |
|             }
 | |
|             Quality::Custom(b) => b,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn set_not_use(display: usize, not_use: bool) {
 | |
|         log::info!("set display#{display} not use vram encode to {not_use}");
 | |
|         ENOCDE_NOT_USE.lock().unwrap().insert(display, not_use);
 | |
|     }
 | |
| 
 | |
|     pub fn set_fallback_gdi(display: usize, fallback: bool) {
 | |
|         if fallback {
 | |
|             FALLBACK_GDI_DISPLAYS.lock().unwrap().insert(display);
 | |
|         } else {
 | |
|             FALLBACK_GDI_DISPLAYS.lock().unwrap().remove(&display);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub struct VRamDecoder {
 | |
|     decoder: Decoder,
 | |
| }
 | |
| 
 | |
| impl VRamDecoder {
 | |
|     pub fn try_get(format: CodecFormat, luid: Option<i64>) -> Option<DecodeContext> {
 | |
|         let v: Vec<_> = Self::available(format, luid);
 | |
|         if v.len() > 0 {
 | |
|             // prefer ffmpeg
 | |
|             if let Some(ctx) = v.iter().find(|c| c.driver == Driver::FFMPEG) {
 | |
|                 return Some(ctx.clone());
 | |
|             }
 | |
|             Some(v[0].clone())
 | |
|         } else {
 | |
|             None
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn available(format: CodecFormat, luid: Option<i64>) -> Vec<DecodeContext> {
 | |
|         let luid = luid.unwrap_or_default();
 | |
|         let data_format = match format {
 | |
|             CodecFormat::H264 => DataFormat::H264,
 | |
|             CodecFormat::H265 => DataFormat::H265,
 | |
|             _ => return vec![],
 | |
|         };
 | |
|         crate::hwcodec::HwCodecConfig::get()
 | |
|             .vram_decode
 | |
|             .drain(..)
 | |
|             .filter(|c| c.data_format == data_format && c.luid == luid && luid != 0)
 | |
|             .collect()
 | |
|     }
 | |
| 
 | |
|     pub fn possible_available_without_check() -> (bool, bool) {
 | |
|         if !enable_vram_option(false) {
 | |
|             return (false, false);
 | |
|         }
 | |
|         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),
 | |
|         )
 | |
|     }
 | |
| 
 | |
|     pub fn new(format: CodecFormat, luid: Option<i64>) -> ResultType<Self> {
 | |
|         let ctx = Self::try_get(format, luid).ok_or(anyhow!("Failed to get decode context"))?;
 | |
|         log::info!("try create vram decoder: {ctx:?}");
 | |
|         match Decoder::new(ctx) {
 | |
|             Ok(decoder) => Ok(Self { decoder }),
 | |
|             Err(_) => {
 | |
|                 HwCodecConfig::clear(true, false);
 | |
|                 Err(anyhow!(format!(
 | |
|                     "Failed to create decoder, format: {:?}",
 | |
|                     format
 | |
|                 )))
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<VRamDecoderImage>> {
 | |
|         match self.decoder.decode(data) {
 | |
|             Ok(v) => Ok(v.iter().map(|f| VRamDecoderImage { frame: f }).collect()),
 | |
|             Err(e) => Err(anyhow!(e)),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub struct VRamDecoderImage<'a> {
 | |
|     pub frame: &'a DecodeFrame,
 | |
| }
 | |
| 
 | |
| impl VRamDecoderImage<'_> {}
 | |
| 
 | |
| pub(crate) fn check_available_vram() -> (Vec<FeatureContext>, Vec<DecodeContext>, String) {
 | |
|     let d = DynamicContext {
 | |
|         device: None,
 | |
|         width: 1280,
 | |
|         height: 720,
 | |
|         kbitrate: 5000,
 | |
|         framerate: 60,
 | |
|         gop: MAX_GOP as _,
 | |
|     };
 | |
|     let encoders = encode::available(d);
 | |
|     let decoders = decode::available();
 | |
|     let available = Available {
 | |
|         e: encoders.clone(),
 | |
|         d: decoders.clone(),
 | |
|     };
 | |
|     (
 | |
|         encoders,
 | |
|         decoders,
 | |
|         available.serialize().unwrap_or_default(),
 | |
|     )
 | |
| }
 |