mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
@@ -24,6 +24,7 @@ message VideoFrame {
|
||||
YUV yuv = 8;
|
||||
EncodedVideoFrames h264s = 10;
|
||||
EncodedVideoFrames h265s = 11;
|
||||
EncodedVideoFrames vp8s = 12;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +77,7 @@ message Features {
|
||||
message SupportedEncoding {
|
||||
bool h264 = 1;
|
||||
bool h265 = 2;
|
||||
bool vp8 = 3;
|
||||
}
|
||||
|
||||
message PeerInfo {
|
||||
@@ -457,18 +459,20 @@ enum ImageQuality {
|
||||
Best = 4;
|
||||
}
|
||||
|
||||
message VideoCodecState {
|
||||
message SupportedDecoding {
|
||||
enum PreferCodec {
|
||||
Auto = 0;
|
||||
VPX = 1;
|
||||
VP9 = 1;
|
||||
H264 = 2;
|
||||
H265 = 3;
|
||||
VP8 = 4;
|
||||
}
|
||||
|
||||
int32 score_vpx = 1;
|
||||
int32 score_h264 = 2;
|
||||
int32 score_h265 = 3;
|
||||
int32 ability_vp9 = 1;
|
||||
int32 ability_h264 = 2;
|
||||
int32 ability_h265 = 3;
|
||||
PreferCodec prefer = 4;
|
||||
int32 ability_vp8 = 5;
|
||||
}
|
||||
|
||||
message OptionMessage {
|
||||
@@ -486,7 +490,7 @@ message OptionMessage {
|
||||
BoolOption disable_audio = 7;
|
||||
BoolOption disable_clipboard = 8;
|
||||
BoolOption enable_file_transfer = 9;
|
||||
VideoCodecState video_codec_state = 10;
|
||||
SupportedDecoding supported_decoding = 10;
|
||||
int32 custom_fps = 11;
|
||||
BoolOption disable_keyboard = 12;
|
||||
}
|
||||
|
||||
@@ -917,7 +917,8 @@ impl PeerConfig {
|
||||
store = store || store2;
|
||||
for opt in ["rdp_password", "os-password"] {
|
||||
if let Some(v) = config.options.get_mut(opt) {
|
||||
let (encrypted, _, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||
let (encrypted, _, store2) =
|
||||
decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||
*v = encrypted;
|
||||
store = store || store2;
|
||||
}
|
||||
@@ -1356,7 +1357,7 @@ impl UserDefaultConfig {
|
||||
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
|
||||
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
|
||||
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
|
||||
"codec-preference" => self.get_string(key, "auto", vec!["vp9", "h264", "h265"]),
|
||||
"codec-preference" => self.get_string(key, "auto", vec!["vp8", "vp9", "h264", "h265"]),
|
||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
|
||||
"custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0),
|
||||
_ => self
|
||||
|
||||
@@ -3,7 +3,8 @@ use hbb_common::env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV};
|
||||
use scrap::{
|
||||
codec::{EncoderApi, EncoderCfg},
|
||||
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
|
||||
VpxVideoCodecId, STRIDE_ALIGN,
|
||||
VpxVideoCodecId::{self, *},
|
||||
STRIDE_ALIGN,
|
||||
};
|
||||
use std::{io::Write, time::Instant};
|
||||
|
||||
@@ -49,7 +50,7 @@ fn main() {
|
||||
"benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}",
|
||||
width, height, bitrate_k, args.flag_hw_pixfmt
|
||||
);
|
||||
test_vp9(&yuvs, width, height, bitrate_k, yuv_count);
|
||||
[VP8, VP9].map(|c| test_vpx(c, &yuvs, width, height, bitrate_k, yuv_count));
|
||||
#[cfg(feature = "hwcodec")]
|
||||
{
|
||||
use hwcodec::AVPixelFormat;
|
||||
@@ -57,7 +58,7 @@ fn main() {
|
||||
Pixfmt::I420 => AVPixelFormat::AV_PIX_FMT_YUV420P,
|
||||
Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
|
||||
};
|
||||
let yuvs = hw::vp9_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
|
||||
let yuvs = hw::vpx_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
|
||||
hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt);
|
||||
}
|
||||
}
|
||||
@@ -87,13 +88,20 @@ fn capture_yuv(yuv_count: usize) -> (Vec<Vec<u8>>, usize, usize) {
|
||||
}
|
||||
}
|
||||
|
||||
fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize, yuv_count: usize) {
|
||||
fn test_vpx(
|
||||
codec_id: VpxVideoCodecId,
|
||||
yuvs: &Vec<Vec<u8>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
bitrate_k: usize,
|
||||
yuv_count: usize,
|
||||
) {
|
||||
let config = EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
timebase: [1, 1000],
|
||||
bitrate: bitrate_k as _,
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
codec: codec_id,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
});
|
||||
let mut encoder = VpxEncoder::new(config).unwrap();
|
||||
@@ -104,35 +112,43 @@ fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize,
|
||||
.unwrap();
|
||||
let _ = encoder.flush().unwrap();
|
||||
}
|
||||
println!("vp9 encode: {:?}", start.elapsed() / yuv_count as _);
|
||||
println!(
|
||||
"{:?} encode: {:?}",
|
||||
codec_id,
|
||||
start.elapsed() / yuv_count as _
|
||||
);
|
||||
|
||||
// prepare data separately
|
||||
let mut vp9s = vec![];
|
||||
let mut vpxs = vec![];
|
||||
let start = Instant::now();
|
||||
for yuv in yuvs {
|
||||
for ref frame in encoder
|
||||
.encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN)
|
||||
.unwrap()
|
||||
{
|
||||
vp9s.push(frame.data.to_vec());
|
||||
vpxs.push(frame.data.to_vec());
|
||||
}
|
||||
for ref frame in encoder.flush().unwrap() {
|
||||
vp9s.push(frame.data.to_vec());
|
||||
vpxs.push(frame.data.to_vec());
|
||||
}
|
||||
}
|
||||
assert_eq!(vp9s.len(), yuv_count);
|
||||
assert_eq!(vpxs.len(), yuv_count);
|
||||
|
||||
let mut decoder = VpxDecoder::new(VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
codec: codec_id,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
let start = Instant::now();
|
||||
for vp9 in vp9s {
|
||||
let _ = decoder.decode(&vp9);
|
||||
for vpx in vpxs {
|
||||
let _ = decoder.decode(&vpx);
|
||||
let _ = decoder.flush();
|
||||
}
|
||||
println!("vp9 decode: {:?}", start.elapsed() / yuv_count as _);
|
||||
println!(
|
||||
"{:?} decode: {:?}",
|
||||
codec_id,
|
||||
start.elapsed() / yuv_count as _
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "hwcodec")]
|
||||
@@ -267,7 +283,7 @@ mod hw {
|
||||
Some(info.clone()) == best.h264 || Some(info.clone()) == best.h265
|
||||
}
|
||||
|
||||
pub fn vp9_yuv_to_hw_yuv(
|
||||
pub fn vpx_yuv_to_hw_yuv(
|
||||
yuvs: Vec<Vec<u8>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
|
||||
@@ -50,6 +50,7 @@ impl crate::TraitCapturer for Capturer {
|
||||
|
||||
pub enum Frame<'a> {
|
||||
RAW(&'a [u8]),
|
||||
VP8(&'a [u8]),
|
||||
VP9(&'a [u8]),
|
||||
Empty,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
#[cfg(feature = "hwcodec")]
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
@@ -11,30 +10,31 @@ use crate::hwcodec::*;
|
||||
use crate::mediacodec::{
|
||||
MediaCodecDecoder, MediaCodecDecoders, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT,
|
||||
};
|
||||
use crate::{vpxcodec::*, ImageFormat};
|
||||
use crate::{vpxcodec::*, CodecName, ImageFormat};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::sysinfo::{System, SystemExt};
|
||||
use hbb_common::{
|
||||
anyhow::anyhow,
|
||||
config::PeerConfig,
|
||||
log,
|
||||
message_proto::{video_frame, EncodedVideoFrames, Message, VideoCodecState},
|
||||
message_proto::{
|
||||
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message,
|
||||
SupportedDecoding, SupportedEncoding,
|
||||
},
|
||||
ResultType,
|
||||
};
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||
use hbb_common::{
|
||||
config::{Config2, PeerConfig},
|
||||
lazy_static,
|
||||
message_proto::video_codec_state::PreferCodec,
|
||||
};
|
||||
use hbb_common::{config::Config2, lazy_static};
|
||||
|
||||
#[cfg(feature = "hwcodec")]
|
||||
lazy_static::lazy_static! {
|
||||
static ref PEER_DECODER_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default();
|
||||
static ref PEER_DECODINGS: Arc<Mutex<HashMap<i32, SupportedDecoding>>> = Default::default();
|
||||
static ref CODEC_NAME: Arc<Mutex<CodecName>> = Arc::new(Mutex::new(CodecName::VP9));
|
||||
}
|
||||
const SCORE_VPX: i32 = 90;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HwEncoderConfig {
|
||||
pub codec_name: String,
|
||||
pub name: String,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub bitrate: i32,
|
||||
@@ -58,10 +58,6 @@ pub trait EncoderApi {
|
||||
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
|
||||
}
|
||||
|
||||
pub struct DecoderCfg {
|
||||
pub vpx: VpxDecoderConfig,
|
||||
}
|
||||
|
||||
pub struct Encoder {
|
||||
pub codec: Box<dyn EncoderApi>,
|
||||
}
|
||||
@@ -81,7 +77,8 @@ impl DerefMut for Encoder {
|
||||
}
|
||||
|
||||
pub struct Decoder {
|
||||
vpx: VpxDecoder,
|
||||
vp8: VpxDecoder,
|
||||
vp9: VpxDecoder,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
hw: HwDecoders,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
@@ -91,10 +88,10 @@ pub struct Decoder {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EncoderUpdate {
|
||||
State(VideoCodecState),
|
||||
pub enum EncodingUpdate {
|
||||
New(SupportedDecoding),
|
||||
Remove,
|
||||
DisableHwIfNotExist,
|
||||
NewOnlyVP9,
|
||||
}
|
||||
|
||||
impl Encoder {
|
||||
@@ -120,172 +117,156 @@ impl Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
pub fn update_video_encoder(id: i32, update: EncoderUpdate) {
|
||||
pub fn update(id: i32, update: EncodingUpdate) {
|
||||
let mut decodings = PEER_DECODINGS.lock().unwrap();
|
||||
match update {
|
||||
EncodingUpdate::New(decoding) => {
|
||||
decodings.insert(id, decoding);
|
||||
}
|
||||
EncodingUpdate::Remove => {
|
||||
decodings.remove(&id);
|
||||
}
|
||||
EncodingUpdate::NewOnlyVP9 => {
|
||||
decodings.insert(
|
||||
id,
|
||||
SupportedDecoding {
|
||||
ability_vp9: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let vp8_useable = decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_vp8 > 0);
|
||||
#[allow(unused_mut)]
|
||||
let mut h264_name = None;
|
||||
#[allow(unused_mut)]
|
||||
let mut h265_name = None;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
{
|
||||
let mut states = PEER_DECODER_STATES.lock().unwrap();
|
||||
match update {
|
||||
EncoderUpdate::State(state) => {
|
||||
states.insert(id, state);
|
||||
}
|
||||
EncoderUpdate::Remove => {
|
||||
states.remove(&id);
|
||||
}
|
||||
EncoderUpdate::DisableHwIfNotExist => {
|
||||
if !states.contains_key(&id) {
|
||||
states.insert(id, VideoCodecState::default());
|
||||
}
|
||||
}
|
||||
let best = HwEncoder::best();
|
||||
let h264_useable =
|
||||
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h264 > 0);
|
||||
let h265_useable =
|
||||
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
|
||||
if h264_useable {
|
||||
h264_name = best.h264.map_or(None, |c| Some(c.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.score_h264 > 0);
|
||||
let enabled_h265 = best.h265.is_some()
|
||||
&& states.len() > 0
|
||||
&& states.iter().all(|(_, s)| s.score_h265 > 0);
|
||||
|
||||
// Preference first
|
||||
let mut preference = PreferCodec::Auto;
|
||||
let preferences: Vec<_> = states
|
||||
.iter()
|
||||
.filter(|(_, s)| {
|
||||
s.prefer == PreferCodec::VPX.into()
|
||||
|| s.prefer == PreferCodec::H264.into() && enabled_h264
|
||||
|| s.prefer == PreferCodec::H265.into() && enabled_h265
|
||||
})
|
||||
.map(|(_, s)| s.prefer)
|
||||
.collect();
|
||||
if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
|
||||
preference = preferences[0].enum_value_or(PreferCodec::Auto);
|
||||
}
|
||||
|
||||
match preference {
|
||||
PreferCodec::VPX => *name.lock().unwrap() = None,
|
||||
PreferCodec::H264 => {
|
||||
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name))
|
||||
}
|
||||
PreferCodec::H265 => {
|
||||
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name))
|
||||
}
|
||||
PreferCodec::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::<i32>();
|
||||
if enabled_h264 {
|
||||
score_h264 += states.iter().map(|s| s.1.score_h264).sum::<i32>();
|
||||
}
|
||||
if enabled_h265 {
|
||||
score_h265 += states.iter().map(|s| s.1.score_h265).sum::<i32>();
|
||||
}
|
||||
|
||||
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:{}, used preference:{:?}, encoder:{:?}",
|
||||
states.len(),
|
||||
preference,
|
||||
name.lock().unwrap()
|
||||
)
|
||||
} else {
|
||||
*name.lock().unwrap() = None;
|
||||
if h265_useable {
|
||||
h265_name = best.h265.map_or(None, |c| Some(c.name));
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
{
|
||||
let _ = id;
|
||||
let _ = update;
|
||||
|
||||
let mut name = CODEC_NAME.lock().unwrap();
|
||||
let mut preference = PreferCodec::Auto;
|
||||
let preferences: Vec<_> = decodings
|
||||
.iter()
|
||||
.filter(|(_, s)| {
|
||||
s.prefer == PreferCodec::VP9.into()
|
||||
|| s.prefer == PreferCodec::VP8.into() && vp8_useable
|
||||
|| s.prefer == PreferCodec::H264.into() && h264_name.is_some()
|
||||
|| s.prefer == PreferCodec::H265.into() && h265_name.is_some()
|
||||
})
|
||||
.map(|(_, s)| s.prefer)
|
||||
.collect();
|
||||
if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
|
||||
preference = preferences[0].enum_value_or(PreferCodec::Auto);
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut auto_codec = CodecName::VP9;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if vp8_useable && System::new_all().total_memory() <= 4 * 1024 * 1024 * 1024 {
|
||||
// 4 Gb
|
||||
auto_codec = CodecName::VP8
|
||||
}
|
||||
|
||||
match preference {
|
||||
PreferCodec::VP8 => *name = CodecName::VP8,
|
||||
PreferCodec::VP9 => *name = CodecName::VP9,
|
||||
PreferCodec::H264 => *name = h264_name.map_or(auto_codec, |c| CodecName::H264(c)),
|
||||
PreferCodec::H265 => *name = h265_name.map_or(auto_codec, |c| CodecName::H265(c)),
|
||||
PreferCodec::Auto => *name = auto_codec,
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"connection count:{}, used preference:{:?}, encoder:{:?}",
|
||||
decodings.len(),
|
||||
preference,
|
||||
*name
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn current_hw_encoder_name() -> Option<String> {
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if enable_hwcodec_option() {
|
||||
return HwEncoder::current_name().lock().unwrap().clone();
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
return None;
|
||||
pub fn negotiated_codec() -> CodecName {
|
||||
CODEC_NAME.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn supported_encoding() -> (bool, bool) {
|
||||
pub fn supported_encoding() -> SupportedEncoding {
|
||||
#[allow(unused_mut)]
|
||||
let mut encoding = SupportedEncoding {
|
||||
vp8: true,
|
||||
..Default::default()
|
||||
};
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if enable_hwcodec_option() {
|
||||
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)
|
||||
encoding.h264 = best.h264.is_some();
|
||||
encoding.h265 = best.h265.is_some();
|
||||
}
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
(false, false)
|
||||
encoding
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
pub fn video_codec_state(_id: &str) -> VideoCodecState {
|
||||
pub fn supported_decodings(id_for_perfer: Option<&str>) -> SupportedDecoding {
|
||||
#[allow(unused_mut)]
|
||||
let mut decoding = SupportedDecoding {
|
||||
ability_vp8: 1,
|
||||
ability_vp9: 1,
|
||||
prefer: id_for_perfer
|
||||
.map_or(PreferCodec::Auto, |id| Self::codec_preference(id))
|
||||
.into(),
|
||||
..Default::default()
|
||||
};
|
||||
#[cfg(feature = "hwcodec")]
|
||||
if enable_hwcodec_option() {
|
||||
let best = HwDecoder::best();
|
||||
return VideoCodecState {
|
||||
score_vpx: SCORE_VPX,
|
||||
score_h264: best.h264.map_or(0, |c| c.score),
|
||||
score_h265: best.h265.map_or(0, |c| c.score),
|
||||
prefer: Self::codec_preference(_id).into(),
|
||||
..Default::default()
|
||||
};
|
||||
decoding.ability_h264 = if best.h264.is_some() { 1 } else { 0 };
|
||||
decoding.ability_h265 = if best.h265.is_some() { 1 } else { 0 };
|
||||
}
|
||||
#[cfg(feature = "mediacodec")]
|
||||
if enable_hwcodec_option() {
|
||||
let score_h264 = if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
92
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let score_h265 = if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
94
|
||||
} else {
|
||||
0
|
||||
};
|
||||
return VideoCodecState {
|
||||
score_vpx: SCORE_VPX,
|
||||
score_h264,
|
||||
score_h265,
|
||||
prefer: Self::codec_preference(_id).into(),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
VideoCodecState {
|
||||
score_vpx: SCORE_VPX,
|
||||
..Default::default()
|
||||
decoding.ability_h264 =
|
||||
if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
decoding.ability_h265 =
|
||||
if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
decoding
|
||||
}
|
||||
|
||||
pub fn new(config: DecoderCfg) -> Decoder {
|
||||
let vpx = VpxDecoder::new(config.vpx).unwrap();
|
||||
pub fn new() -> Decoder {
|
||||
let vp8 = VpxDecoder::new(VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP8,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
let vp9 = VpxDecoder::new(VpxDecoderConfig {
|
||||
codec: VpxVideoCodecId::VP9,
|
||||
num_threads: (num_cpus::get() / 2) as _,
|
||||
})
|
||||
.unwrap();
|
||||
Decoder {
|
||||
vpx,
|
||||
vp8,
|
||||
vp9,
|
||||
#[cfg(feature = "hwcodec")]
|
||||
hw: if enable_hwcodec_option() {
|
||||
HwDecoder::new_decoders()
|
||||
@@ -310,8 +291,11 @@ impl Decoder {
|
||||
rgb: &mut Vec<u8>,
|
||||
) -> ResultType<bool> {
|
||||
match frame {
|
||||
video_frame::Union::Vp8s(vp8s) => {
|
||||
Decoder::handle_vpxs_video_frame(&mut self.vp8, vp8s, fmt, rgb)
|
||||
}
|
||||
video_frame::Union::Vp9s(vp9s) => {
|
||||
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, fmt, rgb)
|
||||
Decoder::handle_vpxs_video_frame(&mut self.vp9, vp9s, fmt, rgb)
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::H264s(h264s) => {
|
||||
@@ -349,15 +333,15 @@ impl Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_vp9s_video_frame(
|
||||
fn handle_vpxs_video_frame(
|
||||
decoder: &mut VpxDecoder,
|
||||
vp9s: &EncodedVideoFrames,
|
||||
vpxs: &EncodedVideoFrames,
|
||||
fmt: (ImageFormat, usize),
|
||||
rgb: &mut Vec<u8>,
|
||||
) -> ResultType<bool> {
|
||||
let mut last_frame = Image::new();
|
||||
for vp9 in vp9s.frames.iter() {
|
||||
for frame in decoder.decode(&vp9.data)? {
|
||||
for vpx in vpxs.frames.iter() {
|
||||
for frame in decoder.decode(&vpx.data)? {
|
||||
drop(last_frame);
|
||||
last_frame = frame;
|
||||
}
|
||||
@@ -408,14 +392,15 @@ impl Decoder {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||
fn codec_preference(id: &str) -> PreferCodec {
|
||||
let codec = PeerConfig::load(id)
|
||||
.options
|
||||
.get("codec-preference")
|
||||
.map_or("".to_owned(), |c| c.to_owned());
|
||||
if codec == "vp9" {
|
||||
PreferCodec::VPX
|
||||
if codec == "vp8" {
|
||||
PreferCodec::VP8
|
||||
} else if codec == "vp9" {
|
||||
PreferCodec::VP9
|
||||
} else if codec == "h264" {
|
||||
PreferCodec::H264
|
||||
} else if codec == "h265" {
|
||||
|
||||
@@ -7,7 +7,7 @@ use hbb_common::{
|
||||
anyhow::{anyhow, Context},
|
||||
bytes::Bytes,
|
||||
config::HwCodecConfig,
|
||||
lazy_static, log,
|
||||
log,
|
||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
||||
ResultType,
|
||||
};
|
||||
@@ -19,11 +19,6 @@ use hwcodec::{
|
||||
Quality::{self, *},
|
||||
RateControl::{self, *},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref HW_ENCODER_NAME: Arc<Mutex<Option<String>>> = Default::default();
|
||||
}
|
||||
|
||||
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
||||
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
||||
@@ -49,7 +44,7 @@ impl EncoderApi for HwEncoder {
|
||||
match cfg {
|
||||
EncoderCfg::HW(config) => {
|
||||
let ctx = EncodeContext {
|
||||
name: config.codec_name.clone(),
|
||||
name: config.name.clone(),
|
||||
width: config.width as _,
|
||||
height: config.height as _,
|
||||
pixfmt: DEFAULT_PIXFMT,
|
||||
@@ -60,12 +55,12 @@ impl EncoderApi for HwEncoder {
|
||||
quality: DEFAULT_HW_QUALITY,
|
||||
rc: DEFAULT_RC,
|
||||
};
|
||||
let format = match Encoder::format_from_name(config.codec_name.clone()) {
|
||||
let format = match Encoder::format_from_name(config.name.clone()) {
|
||||
Ok(format) => format,
|
||||
Err(_) => {
|
||||
return Err(anyhow!(format!(
|
||||
"failed to get format from name:{}",
|
||||
config.codec_name
|
||||
config.name
|
||||
)))
|
||||
}
|
||||
};
|
||||
@@ -133,10 +128,6 @@ impl HwEncoder {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn current_name() -> Arc<Mutex<Option<String>>> {
|
||||
HW_ENCODER_NAME.clone()
|
||||
}
|
||||
|
||||
pub fn encode(&mut self, bgra: &[u8]) -> ResultType<Vec<EncodeFrame>> {
|
||||
match self.pixfmt {
|
||||
AVPixelFormat::AV_PIX_FMT_YUV420P => hw::hw_bgra_to_i420(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub use self::vpxcodec::*;
|
||||
use hbb_common::message_proto::{video_frame, VideoFrame};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(quartz)] {
|
||||
@@ -92,3 +93,55 @@ pub fn is_cursor_embedded() -> bool {
|
||||
pub fn is_cursor_embedded() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CodecName {
|
||||
VP8,
|
||||
VP9,
|
||||
H264(String),
|
||||
H265(String),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum CodecFormat {
|
||||
VP8,
|
||||
VP9,
|
||||
H264,
|
||||
H265,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<&VideoFrame> for CodecFormat {
|
||||
fn from(it: &VideoFrame) -> Self {
|
||||
match it.union {
|
||||
Some(video_frame::Union::Vp8s(_)) => CodecFormat::VP8,
|
||||
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
|
||||
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
|
||||
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
|
||||
_ => CodecFormat::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&CodecName> for CodecFormat {
|
||||
fn from(value: &CodecName) -> Self {
|
||||
match value {
|
||||
CodecName::VP8 => Self::VP8,
|
||||
CodecName::VP9 => Self::VP9,
|
||||
CodecName::H264(_) => Self::H264,
|
||||
CodecName::H265(_) => Self::H265,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for CodecFormat {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
CodecFormat::VP8 => "VP8".into(),
|
||||
CodecFormat::VP9 => "VP9".into(),
|
||||
CodecFormat::H264 => "H264".into(),
|
||||
CodecFormat::H265 => "H265".into(),
|
||||
CodecFormat::Unknown => "Unknow".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::CodecFormat;
|
||||
#[cfg(feature = "hwcodec")]
|
||||
use hbb_common::anyhow::anyhow;
|
||||
use hbb_common::{
|
||||
@@ -21,13 +22,6 @@ use webm::mux::{self, Segment, Track, VideoTrack, Writer};
|
||||
|
||||
const MIN_SECS: u64 = 1;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RecordCodecID {
|
||||
VP9,
|
||||
H264,
|
||||
H265,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RecorderContext {
|
||||
pub server: bool,
|
||||
@@ -36,7 +30,7 @@ pub struct RecorderContext {
|
||||
pub filename: String,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub codec_id: RecordCodecID,
|
||||
pub format: CodecFormat,
|
||||
pub tx: Option<Sender<RecordState>>,
|
||||
}
|
||||
|
||||
@@ -55,8 +49,9 @@ impl RecorderContext {
|
||||
}
|
||||
let file = if self.server { "s" } else { "c" }.to_string()
|
||||
+ &self.id.clone()
|
||||
+ &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string()
|
||||
+ if self.codec_id == RecordCodecID::VP9 {
|
||||
+ &chrono::Local::now().format("_%Y%m%d%H%M%S_").to_string()
|
||||
+ &self.format.to_string()
|
||||
+ if self.format == CodecFormat::VP9 || self.format == CodecFormat::VP8 {
|
||||
".webm"
|
||||
} else {
|
||||
".mp4"
|
||||
@@ -107,8 +102,8 @@ impl DerefMut for Recorder {
|
||||
impl Recorder {
|
||||
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
||||
ctx.set_filename()?;
|
||||
let recorder = match ctx.codec_id {
|
||||
RecordCodecID::VP9 => Recorder {
|
||||
let recorder = match ctx.format {
|
||||
CodecFormat::VP8 | CodecFormat::VP9 => Recorder {
|
||||
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
||||
ctx,
|
||||
},
|
||||
@@ -126,8 +121,8 @@ impl Recorder {
|
||||
|
||||
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
||||
ctx.set_filename()?;
|
||||
self.inner = match ctx.codec_id {
|
||||
RecordCodecID::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
||||
self.inner = match ctx.format {
|
||||
CodecFormat::VP8 | CodecFormat::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
||||
#[cfg(feature = "hwcodec")]
|
||||
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
||||
#[cfg(not(feature = "hwcodec"))]
|
||||
@@ -148,10 +143,19 @@ impl Recorder {
|
||||
|
||||
pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> {
|
||||
match frame {
|
||||
video_frame::Union::Vp9s(vp9s) => {
|
||||
if self.ctx.codec_id != RecordCodecID::VP9 {
|
||||
video_frame::Union::Vp8s(vp8s) => {
|
||||
if self.ctx.format != CodecFormat::VP8 {
|
||||
self.change(RecorderContext {
|
||||
codec_id: RecordCodecID::VP9,
|
||||
format: CodecFormat::VP8,
|
||||
..self.ctx.clone()
|
||||
})?;
|
||||
}
|
||||
vp8s.frames.iter().map(|f| self.write_video(f)).count();
|
||||
}
|
||||
video_frame::Union::Vp9s(vp9s) => {
|
||||
if self.ctx.format != CodecFormat::VP9 {
|
||||
self.change(RecorderContext {
|
||||
format: CodecFormat::VP9,
|
||||
..self.ctx.clone()
|
||||
})?;
|
||||
}
|
||||
@@ -159,25 +163,25 @@ impl Recorder {
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::H264s(h264s) => {
|
||||
if self.ctx.codec_id != RecordCodecID::H264 {
|
||||
if self.ctx.format != CodecFormat::H264 {
|
||||
self.change(RecorderContext {
|
||||
codec_id: RecordCodecID::H264,
|
||||
format: CodecFormat::H264,
|
||||
..self.ctx.clone()
|
||||
})?;
|
||||
}
|
||||
if self.ctx.codec_id == RecordCodecID::H264 {
|
||||
if self.ctx.format == CodecFormat::H264 {
|
||||
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "hwcodec")]
|
||||
video_frame::Union::H265s(h265s) => {
|
||||
if self.ctx.codec_id != RecordCodecID::H265 {
|
||||
if self.ctx.format != CodecFormat::H265 {
|
||||
self.change(RecorderContext {
|
||||
codec_id: RecordCodecID::H265,
|
||||
format: CodecFormat::H265,
|
||||
..self.ctx.clone()
|
||||
})?;
|
||||
}
|
||||
if self.ctx.codec_id == RecordCodecID::H265 {
|
||||
if self.ctx.format == CodecFormat::H265 {
|
||||
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
||||
}
|
||||
}
|
||||
@@ -221,7 +225,11 @@ impl RecorderApi for WebmRecorder {
|
||||
ctx.width as _,
|
||||
ctx.height as _,
|
||||
None,
|
||||
mux::VideoCodecId::VP9,
|
||||
if ctx.format == CodecFormat::VP9 {
|
||||
mux::VideoCodecId::VP9
|
||||
} else {
|
||||
mux::VideoCodecId::VP8
|
||||
},
|
||||
);
|
||||
Ok(WebmRecorder {
|
||||
vt,
|
||||
@@ -279,7 +287,7 @@ impl RecorderApi for HwRecorder {
|
||||
filename: ctx.filename.clone(),
|
||||
width: ctx.width,
|
||||
height: ctx.height,
|
||||
is265: ctx.codec_id == RecordCodecID::H265,
|
||||
is265: ctx.format == CodecFormat::H265,
|
||||
framerate: crate::hwcodec::DEFAULT_TIME_BASE[1] as _,
|
||||
})
|
||||
.map_err(|_| anyhow!("Failed to create hardware muxer"))?;
|
||||
|
||||
@@ -30,6 +30,7 @@ pub struct VpxEncoder {
|
||||
ctx: vpx_codec_ctx_t,
|
||||
width: usize,
|
||||
height: usize,
|
||||
id: VpxVideoCodecId,
|
||||
}
|
||||
|
||||
pub struct VpxDecoder {
|
||||
@@ -97,15 +98,10 @@ impl EncoderApi for VpxEncoder {
|
||||
{
|
||||
match cfg {
|
||||
crate::codec::EncoderCfg::VPX(config) => {
|
||||
let i;
|
||||
if cfg!(feature = "VP8") {
|
||||
i = match config.codec {
|
||||
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
||||
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
||||
};
|
||||
} else {
|
||||
i = call_vpx_ptr!(vpx_codec_vp9_cx());
|
||||
}
|
||||
let i = match config.codec {
|
||||
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
||||
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
||||
};
|
||||
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||
|
||||
@@ -187,12 +183,17 @@ impl EncoderApi for VpxEncoder {
|
||||
VP9E_SET_TILE_COLUMNS as _,
|
||||
4 as c_int
|
||||
));
|
||||
} else if config.codec == VpxVideoCodecId::VP8 {
|
||||
// https://github.com/webmproject/libvpx/blob/972149cafeb71d6f08df89e91a0130d6a38c4b15/vpx/vp8cx.h#L172
|
||||
// https://groups.google.com/a/webmproject.org/g/webm-discuss/c/DJhSrmfQ61M
|
||||
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 12,));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ctx,
|
||||
width: config.width as _,
|
||||
height: config.height as _,
|
||||
id: config.codec,
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!("encoder type mismatch")),
|
||||
@@ -213,7 +214,7 @@ impl EncoderApi for VpxEncoder {
|
||||
|
||||
// to-do: flush periodically, e.g. 1 second
|
||||
if frames.len() > 0 {
|
||||
Ok(VpxEncoder::create_msg(frames))
|
||||
Ok(VpxEncoder::create_msg(self.id, frames))
|
||||
} else {
|
||||
Err(anyhow!("no valid frame"))
|
||||
}
|
||||
@@ -280,13 +281,17 @@ impl VpxEncoder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
|
||||
pub fn create_msg(codec_id: VpxVideoCodecId, frames: Vec<EncodedVideoFrame>) -> Message {
|
||||
let mut msg_out = Message::new();
|
||||
let mut vf = VideoFrame::new();
|
||||
vf.set_vp9s(EncodedVideoFrames {
|
||||
frames: vp9s.into(),
|
||||
let vpxs = EncodedVideoFrames {
|
||||
frames: frames.into(),
|
||||
..Default::default()
|
||||
});
|
||||
};
|
||||
match codec_id {
|
||||
VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs),
|
||||
VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs),
|
||||
}
|
||||
msg_out.set_video_frame(vf);
|
||||
msg_out
|
||||
}
|
||||
@@ -382,15 +387,10 @@ impl VpxDecoder {
|
||||
pub fn new(config: VpxDecoderConfig) -> Result<Self> {
|
||||
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
|
||||
// cause UB if uninitialized.
|
||||
let i;
|
||||
if cfg!(feature = "VP8") {
|
||||
i = match config.codec {
|
||||
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
||||
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
||||
};
|
||||
} else {
|
||||
i = call_vpx_ptr!(vpx_codec_vp9_dx());
|
||||
}
|
||||
let i = match config.codec {
|
||||
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
||||
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
||||
};
|
||||
let mut ctx = Default::default();
|
||||
let cfg = vpx_codec_dec_cfg_t {
|
||||
threads: if config.num_threads == 0 {
|
||||
|
||||
Reference in New Issue
Block a user