mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-27 23:19:02 +08:00
Merge branch 'hwcodec' into master
This commit is contained in:
commit
847c4acb07
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -2231,6 +2231,19 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hwcodec"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/21pages/hwcodec#504363444b6fcd93f0a9b2f21d80744306f15819"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
|
"log",
|
||||||
|
"serde 1.0.137",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json 1.0.81",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.19"
|
version = "0.14.19"
|
||||||
@ -4255,6 +4268,8 @@ dependencies = [
|
|||||||
"gstreamer",
|
"gstreamer",
|
||||||
"gstreamer-app",
|
"gstreamer-app",
|
||||||
"gstreamer-video",
|
"gstreamer-video",
|
||||||
|
"hbb_common",
|
||||||
|
"hwcodec",
|
||||||
"jni",
|
"jni",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -24,6 +24,7 @@ appimage = []
|
|||||||
use_samplerate = ["samplerate"]
|
use_samplerate = ["samplerate"]
|
||||||
use_rubato = ["rubato"]
|
use_rubato = ["rubato"]
|
||||||
use_dasp = ["dasp"]
|
use_dasp = ["dasp"]
|
||||||
|
hwcodec = ["scrap/hwcodec"]
|
||||||
default = ["use_dasp"]
|
default = ["use_dasp"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -9,6 +9,26 @@ message VP9 {
|
|||||||
|
|
||||||
message VP9s { repeated VP9 frames = 1; }
|
message VP9s { repeated VP9 frames = 1; }
|
||||||
|
|
||||||
|
message H264 {
|
||||||
|
bytes data = 1;
|
||||||
|
bool key = 2;
|
||||||
|
int64 pts = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message H264s {
|
||||||
|
repeated H264 h264s = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message H265 {
|
||||||
|
bytes data = 1;
|
||||||
|
bool key = 2;
|
||||||
|
int64 pts = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message H265s {
|
||||||
|
repeated H265 h265s = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message RGB { bool compress = 1; }
|
message RGB { bool compress = 1; }
|
||||||
|
|
||||||
// planes data send directly in binary for better use arraybuffer on web
|
// planes data send directly in binary for better use arraybuffer on web
|
||||||
@ -22,6 +42,8 @@ message VideoFrame {
|
|||||||
VP9s vp9s = 6;
|
VP9s vp9s = 6;
|
||||||
RGB rgb = 7;
|
RGB rgb = 7;
|
||||||
YUV yuv = 8;
|
YUV yuv = 8;
|
||||||
|
H264s h264s = 10;
|
||||||
|
H265s h265s = 11;
|
||||||
}
|
}
|
||||||
int64 timestamp = 9;
|
int64 timestamp = 9;
|
||||||
}
|
}
|
||||||
@ -430,6 +452,14 @@ enum ImageQuality {
|
|||||||
Best = 4;
|
Best = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message VideoCodecState {
|
||||||
|
int32 ScoreVpx = 1;
|
||||||
|
bool H264 = 2;
|
||||||
|
int32 ScoreH264 = 3;
|
||||||
|
bool H265 = 4;
|
||||||
|
int32 ScoreH265 = 5;
|
||||||
|
}
|
||||||
|
|
||||||
message OptionMessage {
|
message OptionMessage {
|
||||||
enum BoolOption {
|
enum BoolOption {
|
||||||
NotSet = 0;
|
NotSet = 0;
|
||||||
@ -445,6 +475,7 @@ message OptionMessage {
|
|||||||
BoolOption disable_audio = 7;
|
BoolOption disable_audio = 7;
|
||||||
BoolOption disable_clipboard = 8;
|
BoolOption disable_clipboard = 8;
|
||||||
BoolOption enable_file_transfer = 9;
|
BoolOption enable_file_transfer = 9;
|
||||||
|
VideoCodecState video_codec_state = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TestDelay {
|
message TestDelay {
|
||||||
|
@ -35,6 +35,7 @@ lazy_static::lazy_static! {
|
|||||||
static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::load()));
|
static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::load()));
|
||||||
static ref CONFIG2: Arc<RwLock<Config2>> = Arc::new(RwLock::new(Config2::load()));
|
static ref CONFIG2: Arc<RwLock<Config2>> = Arc::new(RwLock::new(Config2::load()));
|
||||||
static ref LOCAL_CONFIG: Arc<RwLock<LocalConfig>> = Arc::new(RwLock::new(LocalConfig::load()));
|
static ref LOCAL_CONFIG: Arc<RwLock<LocalConfig>> = Arc::new(RwLock::new(LocalConfig::load()));
|
||||||
|
static ref HWCODEC_CONFIG: Arc<RwLock<HwCodecConfig>> = Arc::new(RwLock::new(HwCodecConfig::load()));
|
||||||
pub static ref ONLINE: Arc<Mutex<HashMap<String, i64>>> = Default::default();
|
pub static ref ONLINE: Arc<Mutex<HashMap<String, i64>>> = Default::default();
|
||||||
pub static ref PROD_RENDEZVOUS_SERVER: Arc<RwLock<String>> = Default::default();
|
pub static ref PROD_RENDEZVOUS_SERVER: Arc<RwLock<String>> = Default::default();
|
||||||
pub static ref APP_NAME: Arc<RwLock<String>> = Arc::new(RwLock::new("RustDesk".to_owned()));
|
pub static ref APP_NAME: Arc<RwLock<String>> = Arc::new(RwLock::new("RustDesk".to_owned()));
|
||||||
@ -881,6 +882,43 @@ impl LanPeers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct HwCodecConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
options: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HwCodecConfig {
|
||||||
|
fn load() -> HwCodecConfig {
|
||||||
|
Config::load_::<HwCodecConfig>("_hwcodec")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store(&self) {
|
||||||
|
Config::store_(self, "_hwcodec");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_option(k: &str) -> String {
|
||||||
|
if let Some(v) = HWCODEC_CONFIG.read().unwrap().options.get(k) {
|
||||||
|
v.clone()
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_option(k: String, v: String) {
|
||||||
|
let mut config = HWCODEC_CONFIG.write().unwrap();
|
||||||
|
let v2 = if v.is_empty() { None } else { Some(&v) };
|
||||||
|
if v2 != config.options.get(&k) {
|
||||||
|
if v2.is_none() {
|
||||||
|
config.options.remove(&k);
|
||||||
|
} else {
|
||||||
|
config.options.insert(k, v);
|
||||||
|
}
|
||||||
|
config.store();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -18,6 +18,7 @@ cfg-if = "1.0"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
hbb_common = { path = "../hbb_common" }
|
||||||
|
|
||||||
[dependencies.winapi]
|
[dependencies.winapi]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
@ -48,3 +49,6 @@ tracing = { version = "0.1", optional = true }
|
|||||||
gstreamer = { version = "0.16", optional = true }
|
gstreamer = { version = "0.16", optional = true }
|
||||||
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
|
gstreamer-app = { version = "0.16", features = ["v1_10"], optional = true }
|
||||||
gstreamer-video = { version = "0.16", optional = true }
|
gstreamer-video = { version = "0.16", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
hwcodec = { git = "https://github.com/21pages/hwcodec", optional = true }
|
||||||
|
@ -13,10 +13,11 @@ use std::time::{Duration, Instant};
|
|||||||
use std::{io, thread};
|
use std::{io, thread};
|
||||||
|
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
|
use scrap::codec::{EncoderApi, EncoderCfg};
|
||||||
use webm::mux;
|
use webm::mux;
|
||||||
use webm::mux::Track;
|
use webm::mux::Track;
|
||||||
|
|
||||||
use scrap::codec as vpx_encode;
|
use scrap::vpxcodec as vpx_encode;
|
||||||
use scrap::{Capturer, Display, STRIDE_ALIGN};
|
use scrap::{Capturer, Display, STRIDE_ALIGN};
|
||||||
|
|
||||||
const USAGE: &'static str = "
|
const USAGE: &'static str = "
|
||||||
@ -89,27 +90,25 @@ fn main() -> io::Result<()> {
|
|||||||
mux::Segment::new(mux::Writer::new(out)).expect("Could not initialize the multiplexer.");
|
mux::Segment::new(mux::Writer::new(out)).expect("Could not initialize the multiplexer.");
|
||||||
|
|
||||||
let (vpx_codec, mux_codec) = match args.flag_codec {
|
let (vpx_codec, mux_codec) = match args.flag_codec {
|
||||||
Codec::Vp8 => (vpx_encode::VideoCodecId::VP8, mux::VideoCodecId::VP8),
|
Codec::Vp8 => (vpx_encode::VpxVideoCodecId::VP8, mux::VideoCodecId::VP8),
|
||||||
Codec::Vp9 => (vpx_encode::VideoCodecId::VP9, mux::VideoCodecId::VP9),
|
Codec::Vp9 => (vpx_encode::VpxVideoCodecId::VP9, mux::VideoCodecId::VP9),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut vt = webm.add_video_track(width, height, None, mux_codec);
|
let mut vt = webm.add_video_track(width, height, None, mux_codec);
|
||||||
|
|
||||||
// Setup the encoder.
|
// Setup the encoder.
|
||||||
|
|
||||||
let mut vpx = vpx_encode::Encoder::new(
|
let mut vpx = vpx_encode::VpxEncoder::new(EncoderCfg::VPX(vpx_encode::VpxEncoderConfig {
|
||||||
&vpx_encode::Config {
|
width,
|
||||||
width,
|
height,
|
||||||
height,
|
timebase: [1, 1000],
|
||||||
timebase: [1, 1000],
|
bitrate: args.flag_bv,
|
||||||
bitrate: args.flag_bv,
|
codec: vpx_codec,
|
||||||
codec: vpx_codec,
|
rc_min_quantizer: 0,
|
||||||
rc_min_quantizer: 0,
|
rc_max_quantizer: 0,
|
||||||
rc_max_quantizer: 0,
|
speed: 6,
|
||||||
speed: 6,
|
num_threads: 0,
|
||||||
},
|
}))
|
||||||
0,
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Start recording.
|
// Start recording.
|
||||||
|
@ -1,536 +1,328 @@
|
|||||||
// https://github.com/astraw/vpx-encode
|
use std::ops::{Deref, DerefMut};
|
||||||
// https://github.com/astraw/env-libvpx-sys
|
#[cfg(feature = "hwcodec")]
|
||||||
// https://github.com/rust-av/vpx-rs/blob/master/src/decoder.rs
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
|
sync::{Arc, Mutex},
|
||||||
use std::os::raw::{c_int, c_uint};
|
};
|
||||||
use std::{ptr, slice};
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
use crate::hwcodec::*;
|
||||||
pub enum VideoCodecId {
|
use crate::vpxcodec::*;
|
||||||
VP8,
|
|
||||||
VP9,
|
use hbb_common::{
|
||||||
}
|
anyhow::anyhow,
|
||||||
|
log,
|
||||||
impl Default for VideoCodecId {
|
message_proto::{video_frame, Message, VP9s, VideoCodecState},
|
||||||
fn default() -> VideoCodecId {
|
ResultType,
|
||||||
VideoCodecId::VP9
|
};
|
||||||
}
|
#[cfg(feature = "hwcodec")]
|
||||||
}
|
use hbb_common::{
|
||||||
|
lazy_static,
|
||||||
pub struct Encoder {
|
message_proto::{H264s, H265s},
|
||||||
ctx: vpx_codec_ctx_t,
|
};
|
||||||
width: usize,
|
|
||||||
height: usize,
|
#[cfg(feature = "hwcodec")]
|
||||||
}
|
lazy_static::lazy_static! {
|
||||||
|
static ref PEER_DECODER_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default();
|
||||||
pub struct Decoder {
|
static ref MY_DECODER_STATE: Arc<Mutex<VideoCodecState>> = Default::default();
|
||||||
ctx: vpx_codec_ctx_t,
|
}
|
||||||
}
|
const SCORE_VPX: i32 = 90;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Error {
|
pub struct HwEncoderConfig {
|
||||||
FailedCall(String),
|
pub codec_name: String,
|
||||||
BadPtr(String),
|
pub width: usize,
|
||||||
}
|
pub height: usize,
|
||||||
|
pub bitrate_ratio: i32,
|
||||||
impl std::fmt::Display for Error {
|
}
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
write!(f, "{:?}", self)
|
#[derive(Debug, Clone)]
|
||||||
}
|
pub enum EncoderCfg {
|
||||||
}
|
VPX(VpxEncoderConfig),
|
||||||
|
HW(HwEncoderConfig),
|
||||||
impl std::error::Error for Error {}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub trait EncoderApi {
|
||||||
|
fn new(cfg: EncoderCfg) -> ResultType<Self>
|
||||||
macro_rules! call_vpx {
|
where
|
||||||
($x:expr) => {{
|
Self: Sized;
|
||||||
let result = unsafe { $x }; // original expression
|
|
||||||
let result_int = unsafe { std::mem::transmute::<_, i32>(result) };
|
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message>;
|
||||||
if result_int != 0 {
|
|
||||||
return Err(Error::FailedCall(format!(
|
fn use_yuv(&self) -> bool;
|
||||||
"errcode={} {}:{}:{}:{}",
|
}
|
||||||
result_int,
|
|
||||||
module_path!(),
|
pub struct DecoderCfg {
|
||||||
file!(),
|
pub vpx: VpxDecoderConfig,
|
||||||
line!(),
|
}
|
||||||
column!()
|
|
||||||
))
|
pub struct Encoder {
|
||||||
.into());
|
pub codec: Box<dyn EncoderApi>,
|
||||||
}
|
}
|
||||||
result
|
|
||||||
}};
|
impl Deref for Encoder {
|
||||||
}
|
type Target = Box<dyn EncoderApi>;
|
||||||
|
|
||||||
macro_rules! call_vpx_ptr {
|
fn deref(&self) -> &Self::Target {
|
||||||
($x:expr) => {{
|
&self.codec
|
||||||
let result = unsafe { $x }; // original expression
|
}
|
||||||
let result_int = unsafe { std::mem::transmute::<_, isize>(result) };
|
}
|
||||||
if result_int == 0 {
|
|
||||||
return Err(Error::BadPtr(format!(
|
impl DerefMut for Encoder {
|
||||||
"errcode={} {}:{}:{}:{}",
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
result_int,
|
&mut self.codec
|
||||||
module_path!(),
|
}
|
||||||
file!(),
|
}
|
||||||
line!(),
|
|
||||||
column!()
|
pub struct Decoder {
|
||||||
))
|
vpx: VpxDecoder,
|
||||||
.into());
|
#[cfg(feature = "hwcodec")]
|
||||||
}
|
hw: HwDecoders,
|
||||||
result
|
#[cfg(feature = "hwcodec")]
|
||||||
}};
|
i420: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder {
|
#[derive(Debug, Clone)]
|
||||||
pub fn new(config: &Config, num_threads: u32) -> Result<Self> {
|
pub enum EncoderUpdate {
|
||||||
let i;
|
State(VideoCodecState),
|
||||||
if cfg!(feature = "VP8") {
|
Remove,
|
||||||
i = match config.codec {
|
DisableHwIfNotExist,
|
||||||
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
}
|
||||||
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
|
||||||
};
|
impl Encoder {
|
||||||
} else {
|
pub fn new(config: EncoderCfg) -> ResultType<Encoder> {
|
||||||
i = call_vpx_ptr!(vpx_codec_vp9_cx());
|
log::info!("new encoder:{:?}", config);
|
||||||
}
|
match config {
|
||||||
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
EncoderCfg::VPX(_) => Ok(Encoder {
|
||||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
codec: Box::new(VpxEncoder::new(config)?),
|
||||||
|
}),
|
||||||
// https://www.webmproject.org/docs/encoder-parameters/
|
|
||||||
// default: c.rc_min_quantizer = 0, c.rc_max_quantizer = 63
|
#[cfg(feature = "hwcodec")]
|
||||||
// try rc_resize_allowed later
|
EncoderCfg::HW(_) => match HwEncoder::new(config) {
|
||||||
|
Ok(hw) => Ok(Encoder {
|
||||||
c.g_w = config.width;
|
codec: Box::new(hw),
|
||||||
c.g_h = config.height;
|
}),
|
||||||
c.g_timebase.num = config.timebase[0];
|
Err(e) => {
|
||||||
c.g_timebase.den = config.timebase[1];
|
HwEncoder::best(true, true);
|
||||||
c.rc_target_bitrate = config.bitrate;
|
Err(e)
|
||||||
c.rc_undershoot_pct = 95;
|
}
|
||||||
c.rc_dropframe_thresh = 25;
|
},
|
||||||
if config.rc_min_quantizer > 0 {
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
c.rc_min_quantizer = config.rc_min_quantizer;
|
_ => Err(anyhow!("unsupported encoder type")),
|
||||||
}
|
}
|
||||||
if config.rc_max_quantizer > 0 {
|
}
|
||||||
c.rc_max_quantizer = config.rc_max_quantizer;
|
|
||||||
}
|
// TODO
|
||||||
let mut speed = config.speed;
|
pub fn update_video_encoder(id: i32, update: EncoderUpdate) {
|
||||||
if speed <= 0 {
|
log::info!("encoder update: {:?}", update);
|
||||||
speed = 6;
|
#[cfg(feature = "hwcodec")]
|
||||||
}
|
{
|
||||||
|
let mut states = PEER_DECODER_STATES.lock().unwrap();
|
||||||
c.g_threads = if num_threads == 0 {
|
match update {
|
||||||
num_cpus::get() as _
|
EncoderUpdate::State(state) => {
|
||||||
} else {
|
states.insert(id, state);
|
||||||
num_threads
|
}
|
||||||
};
|
EncoderUpdate::Remove => {
|
||||||
c.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
|
states.remove(&id);
|
||||||
// https://developers.google.com/media/vp9/bitrate-modes/
|
}
|
||||||
// Constant Bitrate mode (CBR) is recommended for live streaming with VP9.
|
EncoderUpdate::DisableHwIfNotExist => {
|
||||||
c.rc_end_usage = vpx_rc_mode::VPX_CBR;
|
if !states.contains_key(&id) {
|
||||||
// c.kf_min_dist = 0;
|
states.insert(id, VideoCodecState::default());
|
||||||
// c.kf_max_dist = 999999;
|
}
|
||||||
c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot
|
}
|
||||||
|
}
|
||||||
/*
|
let current_encoder_name = HwEncoder::current_name();
|
||||||
VPX encoder支持two-pass encode,这是为了rate control的。
|
if states.len() > 0 {
|
||||||
对于两遍编码,就是需要整个编码过程做两次,第一次会得到一些新的控制参数来进行第二遍的编码,
|
let (best, _) = HwEncoder::best(false, true);
|
||||||
这样可以在相同的bitrate下得到最好的PSNR
|
let enabled_h264 =
|
||||||
*/
|
best.h264.is_some() && states.len() > 0 && states.iter().all(|(_, s)| s.H264);
|
||||||
|
let enabled_h265 =
|
||||||
let mut ctx = Default::default();
|
best.h265.is_some() && states.len() > 0 && states.iter().all(|(_, s)| s.H265);
|
||||||
call_vpx!(vpx_codec_enc_init_ver(
|
|
||||||
&mut ctx,
|
// score encoder
|
||||||
i,
|
let mut score_vpx = SCORE_VPX;
|
||||||
&c,
|
let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score);
|
||||||
0,
|
let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score);
|
||||||
VPX_ENCODER_ABI_VERSION as _
|
|
||||||
));
|
// score decoder
|
||||||
|
score_vpx += states.iter().map(|s| s.1.ScoreVpx).sum::<i32>();
|
||||||
if config.codec == VideoCodecId::VP9 {
|
if enabled_h264 {
|
||||||
// set encoder internal speed settings
|
score_h264 += states.iter().map(|s| s.1.ScoreH264).sum::<i32>();
|
||||||
// in ffmpeg, it is --speed option
|
}
|
||||||
/*
|
if enabled_h265 {
|
||||||
set to 0 or a positive value 1-16, the codec will try to adapt its
|
score_h265 += states.iter().map(|s| s.1.ScoreH265).sum::<i32>();
|
||||||
complexity depending on the time it spends encoding. Increasing this
|
}
|
||||||
number will make the speed go up and the quality go down.
|
|
||||||
Negative values mean strict enforcement of this
|
if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 {
|
||||||
while positive values are adaptive
|
*current_encoder_name.lock().unwrap() = Some(best.h265.unwrap().name);
|
||||||
*/
|
} else if enabled_h264 && score_h264 >= score_vpx && score_h264 >= score_h265 {
|
||||||
/* https://developers.google.com/media/vp9/live-encoding
|
*current_encoder_name.lock().unwrap() = Some(best.h264.unwrap().name);
|
||||||
Speed 5 to 8 should be used for live / real-time encoding.
|
} else {
|
||||||
Lower numbers (5 or 6) are higher quality but require more CPU power.
|
*current_encoder_name.lock().unwrap() = None;
|
||||||
Higher numbers (7 or 8) will be lower quality but more manageable for lower latency
|
}
|
||||||
use cases and also for lower CPU power devices such as mobile.
|
log::info!(
|
||||||
*/
|
"connection count:{}, h264:{}, h265:{}, score: vpx({}), h264({}), h265({}), set current encoder name {:?}",
|
||||||
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, speed,));
|
states.len(),
|
||||||
// set row level multi-threading
|
enabled_h264,
|
||||||
/*
|
enabled_h265,
|
||||||
as some people in comments and below have already commented,
|
score_vpx,
|
||||||
more recent versions of libvpx support -row-mt 1 to enable tile row
|
score_h264,
|
||||||
multi-threading. This can increase the number of tiles by up to 4x in VP9
|
score_h265,
|
||||||
(since the max number of tile rows is 4, regardless of video height).
|
current_encoder_name.lock().unwrap()
|
||||||
To enable this, use -tile-rows N where N is the number of tile rows in
|
)
|
||||||
log2 units (so -tile-rows 1 means 2 tile rows and -tile-rows 2 means 4 tile
|
} else {
|
||||||
rows). The total number of active threads will then be equal to
|
*current_encoder_name.lock().unwrap() = None;
|
||||||
$tile_rows * $tile_columns
|
}
|
||||||
*/
|
}
|
||||||
call_vpx!(vpx_codec_control_(
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
&mut ctx,
|
{
|
||||||
VP9E_SET_ROW_MT as _,
|
let _ = id;
|
||||||
1 as c_int
|
let _ = update;
|
||||||
));
|
}
|
||||||
|
}
|
||||||
call_vpx!(vpx_codec_control_(
|
#[inline]
|
||||||
&mut ctx,
|
pub fn current_hw_encoder_name() -> Option<String> {
|
||||||
VP9E_SET_TILE_COLUMNS as _,
|
#[cfg(feature = "hwcodec")]
|
||||||
4 as c_int
|
return HwEncoder::current_name().lock().unwrap().clone();
|
||||||
));
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
}
|
return None;
|
||||||
|
}
|
||||||
Ok(Self {
|
}
|
||||||
ctx,
|
|
||||||
width: config.width as _,
|
#[cfg(feature = "hwcodec")]
|
||||||
height: config.height as _,
|
impl Drop for Decoder {
|
||||||
})
|
fn drop(&mut self) {
|
||||||
}
|
*MY_DECODER_STATE.lock().unwrap() = VideoCodecState {
|
||||||
|
ScoreVpx: SCORE_VPX,
|
||||||
pub fn encode(&mut self, pts: i64, data: &[u8], stride_align: usize) -> Result<EncodeFrames> {
|
..Default::default()
|
||||||
assert!(2 * data.len() >= 3 * self.width * self.height);
|
};
|
||||||
|
}
|
||||||
let mut image = Default::default();
|
}
|
||||||
call_vpx_ptr!(vpx_img_wrap(
|
|
||||||
&mut image,
|
impl Decoder {
|
||||||
vpx_img_fmt::VPX_IMG_FMT_I420,
|
pub fn video_codec_state() -> VideoCodecState {
|
||||||
self.width as _,
|
// video_codec_state is mainted by creation and destruction of Decoder.
|
||||||
self.height as _,
|
// It has been ensured to use after Decoder's creation.
|
||||||
stride_align as _,
|
#[cfg(feature = "hwcodec")]
|
||||||
data.as_ptr() as _,
|
return MY_DECODER_STATE.lock().unwrap().clone();
|
||||||
));
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
|
VideoCodecState {
|
||||||
call_vpx!(vpx_codec_encode(
|
ScoreVpx: SCORE_VPX,
|
||||||
&mut self.ctx,
|
..Default::default()
|
||||||
&image,
|
}
|
||||||
pts as _,
|
}
|
||||||
1, // Duration
|
|
||||||
0, // Flags
|
pub fn new(config: DecoderCfg) -> Decoder {
|
||||||
VPX_DL_REALTIME as _,
|
let vpx = VpxDecoder::new(config.vpx).unwrap();
|
||||||
));
|
let decoder = Decoder {
|
||||||
|
vpx,
|
||||||
Ok(EncodeFrames {
|
#[cfg(feature = "hwcodec")]
|
||||||
ctx: &mut self.ctx,
|
hw: HwDecoder::new_decoders(),
|
||||||
iter: ptr::null(),
|
#[cfg(feature = "hwcodec")]
|
||||||
})
|
i420: vec![],
|
||||||
}
|
};
|
||||||
|
|
||||||
/// Notify the encoder to return any pending packets
|
#[cfg(feature = "hwcodec")]
|
||||||
pub fn flush(&mut self) -> Result<EncodeFrames> {
|
{
|
||||||
call_vpx!(vpx_codec_encode(
|
let mut state = MY_DECODER_STATE.lock().unwrap();
|
||||||
&mut self.ctx,
|
state.ScoreVpx = SCORE_VPX;
|
||||||
ptr::null(),
|
state.H264 = decoder.hw.h264.is_some();
|
||||||
-1, // PTS
|
state.ScoreH264 = decoder.hw.h264.as_ref().map_or(0, |d| d.info.score);
|
||||||
1, // Duration
|
state.H265 = decoder.hw.h265.is_some();
|
||||||
0, // Flags
|
state.ScoreH265 = decoder.hw.h265.as_ref().map_or(0, |d| d.info.score);
|
||||||
VPX_DL_REALTIME as _,
|
}
|
||||||
));
|
|
||||||
|
decoder
|
||||||
Ok(EncodeFrames {
|
}
|
||||||
ctx: &mut self.ctx,
|
|
||||||
iter: ptr::null(),
|
pub fn handle_video_frame(
|
||||||
})
|
&mut self,
|
||||||
}
|
frame: &video_frame::Union,
|
||||||
}
|
rgb: &mut Vec<u8>,
|
||||||
|
) -> ResultType<bool> {
|
||||||
impl Drop for Encoder {
|
match frame {
|
||||||
fn drop(&mut self) {
|
video_frame::Union::vp9s(vp9s) => {
|
||||||
unsafe {
|
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, rgb)
|
||||||
let result = vpx_codec_destroy(&mut self.ctx);
|
}
|
||||||
if result != VPX_CODEC_OK {
|
#[cfg(feature = "hwcodec")]
|
||||||
panic!("failed to destroy vpx codec");
|
video_frame::Union::h264s(h264s) => {
|
||||||
}
|
if let Some(decoder) = &mut self.hw.h264 {
|
||||||
}
|
Decoder::handle_h264s_video_frame(decoder, h264s, rgb, &mut self.i420)
|
||||||
}
|
} else {
|
||||||
}
|
Err(anyhow!("don't support h264!"))
|
||||||
|
}
|
||||||
#[derive(Clone, Copy, Debug)]
|
}
|
||||||
pub struct EncodeFrame<'a> {
|
#[cfg(feature = "hwcodec")]
|
||||||
/// Compressed data.
|
video_frame::Union::h265s(h265s) => {
|
||||||
pub data: &'a [u8],
|
if let Some(decoder) = &mut self.hw.h265 {
|
||||||
/// Whether the frame is a keyframe.
|
Decoder::handle_h265s_video_frame(decoder, h265s, rgb, &mut self.i420)
|
||||||
pub key: bool,
|
} else {
|
||||||
/// Presentation timestamp (in timebase units).
|
Err(anyhow!("don't support h265!"))
|
||||||
pub pts: i64,
|
}
|
||||||
}
|
}
|
||||||
|
_ => Err(anyhow!("unsupported video frame type!")),
|
||||||
#[derive(Clone, Copy, Debug)]
|
}
|
||||||
pub struct Config {
|
}
|
||||||
/// The width (in pixels).
|
|
||||||
pub width: c_uint,
|
fn handle_vp9s_video_frame(
|
||||||
/// The height (in pixels).
|
decoder: &mut VpxDecoder,
|
||||||
pub height: c_uint,
|
vp9s: &VP9s,
|
||||||
/// The timebase numerator and denominator (in seconds).
|
rgb: &mut Vec<u8>,
|
||||||
pub timebase: [c_int; 2],
|
) -> ResultType<bool> {
|
||||||
/// The target bitrate (in kilobits per second).
|
let mut last_frame = Image::new();
|
||||||
pub bitrate: c_uint,
|
for vp9 in vp9s.frames.iter() {
|
||||||
/// The codec
|
for frame in decoder.decode(&vp9.data)? {
|
||||||
pub codec: VideoCodecId,
|
drop(last_frame);
|
||||||
pub rc_min_quantizer: u32,
|
last_frame = frame;
|
||||||
pub rc_max_quantizer: u32,
|
}
|
||||||
pub speed: i32,
|
}
|
||||||
}
|
for frame in decoder.flush()? {
|
||||||
|
drop(last_frame);
|
||||||
pub struct EncodeFrames<'a> {
|
last_frame = frame;
|
||||||
ctx: &'a mut vpx_codec_ctx_t,
|
}
|
||||||
iter: vpx_codec_iter_t,
|
if last_frame.is_null() {
|
||||||
}
|
Ok(false)
|
||||||
|
} else {
|
||||||
impl<'a> Iterator for EncodeFrames<'a> {
|
last_frame.rgb(1, true, rgb);
|
||||||
type Item = EncodeFrame<'a>;
|
Ok(true)
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
}
|
||||||
loop {
|
}
|
||||||
unsafe {
|
|
||||||
let pkt = vpx_codec_get_cx_data(self.ctx, &mut self.iter);
|
#[cfg(feature = "hwcodec")]
|
||||||
if pkt.is_null() {
|
fn handle_h264s_video_frame(
|
||||||
return None;
|
decoder: &mut HwDecoder,
|
||||||
} else if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
|
h264s: &H264s,
|
||||||
let f = &(*pkt).data.frame;
|
rgb: &mut Vec<u8>,
|
||||||
return Some(Self::Item {
|
i420: &mut Vec<u8>,
|
||||||
data: slice::from_raw_parts(f.buf as _, f.sz as _),
|
) -> ResultType<bool> {
|
||||||
key: (f.flags & VPX_FRAME_IS_KEY) != 0,
|
let mut ret = false;
|
||||||
pts: f.pts,
|
for h264 in h264s.h264s.iter() {
|
||||||
});
|
for image in decoder.decode(&h264.data)? {
|
||||||
} else {
|
// TODO: just process the last frame
|
||||||
// Ignore the packet.
|
if image.bgra(rgb, i420).is_ok() {
|
||||||
}
|
ret = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return Ok(ret);
|
||||||
|
}
|
||||||
impl Decoder {
|
|
||||||
/// Create a new decoder
|
#[cfg(feature = "hwcodec")]
|
||||||
///
|
fn handle_h265s_video_frame(
|
||||||
/// # Errors
|
decoder: &mut HwDecoder,
|
||||||
///
|
h265s: &H265s,
|
||||||
/// The function may fail if the underlying libvpx does not provide
|
rgb: &mut Vec<u8>,
|
||||||
/// the VP9 decoder.
|
i420: &mut Vec<u8>,
|
||||||
pub fn new(codec: VideoCodecId, num_threads: u32) -> Result<Self> {
|
) -> ResultType<bool> {
|
||||||
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
|
let mut ret = false;
|
||||||
// cause UB if uninitialized.
|
for h265 in h265s.h265s.iter() {
|
||||||
let i;
|
for image in decoder.decode(&h265.data)? {
|
||||||
if cfg!(feature = "VP8") {
|
// TODO: just process the last frame
|
||||||
i = match codec {
|
if image.bgra(rgb, i420).is_ok() {
|
||||||
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
ret = true;
|
||||||
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
}
|
||||||
};
|
}
|
||||||
} else {
|
}
|
||||||
i = call_vpx_ptr!(vpx_codec_vp9_dx());
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
let mut ctx = Default::default();
|
}
|
||||||
let cfg = vpx_codec_dec_cfg_t {
|
|
||||||
threads: if num_threads == 0 {
|
|
||||||
num_cpus::get() as _
|
|
||||||
} else {
|
|
||||||
num_threads
|
|
||||||
},
|
|
||||||
w: 0,
|
|
||||||
h: 0,
|
|
||||||
};
|
|
||||||
/*
|
|
||||||
unsafe {
|
|
||||||
println!("{}", vpx_codec_get_caps(i));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
call_vpx!(vpx_codec_dec_init_ver(
|
|
||||||
&mut ctx,
|
|
||||||
i,
|
|
||||||
&cfg,
|
|
||||||
0,
|
|
||||||
VPX_DECODER_ABI_VERSION as _,
|
|
||||||
));
|
|
||||||
Ok(Self { ctx })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode2rgb(&mut self, data: &[u8], rgba: bool) -> Result<Vec<u8>> {
|
|
||||||
let mut img = Image::new();
|
|
||||||
for frame in self.decode(data)? {
|
|
||||||
drop(img);
|
|
||||||
img = frame;
|
|
||||||
}
|
|
||||||
for frame in self.flush()? {
|
|
||||||
drop(img);
|
|
||||||
img = frame;
|
|
||||||
}
|
|
||||||
if img.is_null() {
|
|
||||||
Ok(Vec::new())
|
|
||||||
} else {
|
|
||||||
let mut out = Default::default();
|
|
||||||
img.rgb(1, rgba, &mut out);
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Feed some compressed data to the encoder
|
|
||||||
///
|
|
||||||
/// The `data` slice is sent to the decoder
|
|
||||||
///
|
|
||||||
/// It matches a call to `vpx_codec_decode`.
|
|
||||||
pub fn decode(&mut self, data: &[u8]) -> Result<DecodeFrames> {
|
|
||||||
call_vpx!(vpx_codec_decode(
|
|
||||||
&mut self.ctx,
|
|
||||||
data.as_ptr(),
|
|
||||||
data.len() as _,
|
|
||||||
ptr::null_mut(),
|
|
||||||
0,
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(DecodeFrames {
|
|
||||||
ctx: &mut self.ctx,
|
|
||||||
iter: ptr::null(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notify the decoder to return any pending frame
|
|
||||||
pub fn flush(&mut self) -> Result<DecodeFrames> {
|
|
||||||
call_vpx!(vpx_codec_decode(
|
|
||||||
&mut self.ctx,
|
|
||||||
ptr::null(),
|
|
||||||
0,
|
|
||||||
ptr::null_mut(),
|
|
||||||
0
|
|
||||||
));
|
|
||||||
Ok(DecodeFrames {
|
|
||||||
ctx: &mut self.ctx,
|
|
||||||
iter: ptr::null(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Decoder {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
let result = vpx_codec_destroy(&mut self.ctx);
|
|
||||||
if result != VPX_CODEC_OK {
|
|
||||||
panic!("failed to destroy vpx codec");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DecodeFrames<'a> {
|
|
||||||
ctx: &'a mut vpx_codec_ctx_t,
|
|
||||||
iter: vpx_codec_iter_t,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for DecodeFrames<'a> {
|
|
||||||
type Item = Image;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let img = unsafe { vpx_codec_get_frame(self.ctx, &mut self.iter) };
|
|
||||||
if img.is_null() {
|
|
||||||
return None;
|
|
||||||
} else {
|
|
||||||
return Some(Image(img));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://chromium.googlesource.com/webm/libvpx/+/bali/vpx/src/vpx_image.c
|
|
||||||
pub struct Image(*mut vpx_image_t);
|
|
||||||
impl Image {
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(std::ptr::null_mut())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_null(&self) -> bool {
|
|
||||||
self.0.is_null()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn width(&self) -> usize {
|
|
||||||
self.inner().d_w as _
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn height(&self) -> usize {
|
|
||||||
self.inner().d_h as _
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn format(&self) -> vpx_img_fmt_t {
|
|
||||||
// VPX_IMG_FMT_I420
|
|
||||||
self.inner().fmt
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner(&self) -> &vpx_image_t {
|
|
||||||
unsafe { &*self.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn stride(&self, iplane: usize) -> i32 {
|
|
||||||
self.inner().stride[iplane]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb(&self, stride_align: usize, rgba: bool, dst: &mut Vec<u8>) {
|
|
||||||
let h = self.height();
|
|
||||||
let mut w = self.width();
|
|
||||||
let bps = if rgba { 4 } else { 3 };
|
|
||||||
w = (w + stride_align - 1) & !(stride_align - 1);
|
|
||||||
dst.resize(h * w * bps, 0);
|
|
||||||
let img = self.inner();
|
|
||||||
unsafe {
|
|
||||||
if rgba {
|
|
||||||
super::I420ToARGB(
|
|
||||||
img.planes[0],
|
|
||||||
img.stride[0],
|
|
||||||
img.planes[1],
|
|
||||||
img.stride[1],
|
|
||||||
img.planes[2],
|
|
||||||
img.stride[2],
|
|
||||||
dst.as_mut_ptr(),
|
|
||||||
(w * bps) as _,
|
|
||||||
self.width() as _,
|
|
||||||
self.height() as _,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
super::I420ToRAW(
|
|
||||||
img.planes[0],
|
|
||||||
img.stride[0],
|
|
||||||
img.planes[1],
|
|
||||||
img.stride[1],
|
|
||||||
img.planes[2],
|
|
||||||
img.stride[2],
|
|
||||||
dst.as_mut_ptr(),
|
|
||||||
(w * bps) as _,
|
|
||||||
self.width() as _,
|
|
||||||
self.height() as _,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn data(&self) -> (&[u8], &[u8], &[u8]) {
|
|
||||||
unsafe {
|
|
||||||
let img = self.inner();
|
|
||||||
let h = (img.d_h as usize + 1) & !1;
|
|
||||||
let n = img.stride[0] as usize * h;
|
|
||||||
let y = slice::from_raw_parts(img.planes[0], n);
|
|
||||||
let n = img.stride[1] as usize * (h >> 1);
|
|
||||||
let u = slice::from_raw_parts(img.planes[1], n);
|
|
||||||
let v = slice::from_raw_parts(img.planes[2], n);
|
|
||||||
(y, u, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Image {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if !self.0.is_null() {
|
|
||||||
unsafe { vpx_img_free(self.0) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for vpx_codec_ctx_t {}
|
|
||||||
|
@ -49,6 +49,17 @@ extern "C" {
|
|||||||
height: c_int,
|
height: c_int,
|
||||||
) -> c_int;
|
) -> c_int;
|
||||||
|
|
||||||
|
pub fn ARGBToNV12(
|
||||||
|
src_bgra: *const u8,
|
||||||
|
src_stride_bgra: c_int,
|
||||||
|
dst_y: *mut u8,
|
||||||
|
dst_stride_y: c_int,
|
||||||
|
dst_uv: *mut u8,
|
||||||
|
dst_stride_uv: c_int,
|
||||||
|
width: c_int,
|
||||||
|
height: c_int,
|
||||||
|
) -> c_int;
|
||||||
|
|
||||||
pub fn NV12ToI420(
|
pub fn NV12ToI420(
|
||||||
src_y: *const u8,
|
src_y: *const u8,
|
||||||
src_stride_y: c_int,
|
src_stride_y: c_int,
|
||||||
@ -91,6 +102,17 @@ extern "C" {
|
|||||||
width: c_int,
|
width: c_int,
|
||||||
height: c_int,
|
height: c_int,
|
||||||
) -> c_int;
|
) -> c_int;
|
||||||
|
|
||||||
|
pub fn NV12ToARGB(
|
||||||
|
src_y: *const u8,
|
||||||
|
src_stride_y: c_int,
|
||||||
|
src_uv: *const u8,
|
||||||
|
src_stride_uv: c_int,
|
||||||
|
dst_rgba: *mut u8,
|
||||||
|
dst_stride_rgba: c_int,
|
||||||
|
width: c_int,
|
||||||
|
height: c_int,
|
||||||
|
) -> c_int;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/webmproject/libvpx/blob/master/vpx/src/vpx_image.c
|
// https://github.com/webmproject/libvpx/blob/master/vpx/src/vpx_image.c
|
||||||
@ -220,3 +242,192 @@ pub unsafe fn nv12_to_i420(
|
|||||||
height as _,
|
height as _,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
pub mod hw {
|
||||||
|
use hbb_common::{anyhow::anyhow, ResultType};
|
||||||
|
use hwcodec::{ffmpeg::ffmpeg_linesize_offset_length, AVPixelFormat};
|
||||||
|
|
||||||
|
pub fn hw_bgra_to_i420(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
stride: &[i32],
|
||||||
|
offset: &[i32],
|
||||||
|
length: i32,
|
||||||
|
src: &[u8],
|
||||||
|
dst: &mut Vec<u8>,
|
||||||
|
) {
|
||||||
|
let stride_y = stride[0] as usize;
|
||||||
|
let stride_u = stride[1] as usize;
|
||||||
|
let stride_v = stride[2] as usize;
|
||||||
|
let offset_u = offset[0] as usize;
|
||||||
|
let offset_v = offset[1] as usize;
|
||||||
|
|
||||||
|
dst.resize(length as _, 0);
|
||||||
|
let dst_y = dst.as_mut_ptr();
|
||||||
|
let dst_u = dst[offset_u..].as_mut_ptr();
|
||||||
|
let dst_v = dst[offset_v..].as_mut_ptr();
|
||||||
|
unsafe {
|
||||||
|
super::ARGBToI420(
|
||||||
|
src.as_ptr(),
|
||||||
|
(src.len() / height) as _,
|
||||||
|
dst_y,
|
||||||
|
stride_y as _,
|
||||||
|
dst_u,
|
||||||
|
stride_u as _,
|
||||||
|
dst_v,
|
||||||
|
stride_v as _,
|
||||||
|
width as _,
|
||||||
|
height as _,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hw_bgra_to_nv12(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
stride: &[i32],
|
||||||
|
offset: &[i32],
|
||||||
|
length: i32,
|
||||||
|
src: &[u8],
|
||||||
|
dst: &mut Vec<u8>,
|
||||||
|
) {
|
||||||
|
let stride_y = stride[0] as usize;
|
||||||
|
let stride_uv = stride[1] as usize;
|
||||||
|
let offset_uv = offset[0] as usize;
|
||||||
|
|
||||||
|
dst.resize(length as _, 0);
|
||||||
|
let dst_y = dst.as_mut_ptr();
|
||||||
|
let dst_uv = dst[offset_uv..].as_mut_ptr();
|
||||||
|
unsafe {
|
||||||
|
super::ARGBToNV12(
|
||||||
|
src.as_ptr(),
|
||||||
|
(src.len() / height) as _,
|
||||||
|
dst_y,
|
||||||
|
stride_y as _,
|
||||||
|
dst_uv,
|
||||||
|
stride_uv as _,
|
||||||
|
width as _,
|
||||||
|
height as _,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub fn hw_nv12_to_bgra(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
src_y: &[u8],
|
||||||
|
src_uv: &[u8],
|
||||||
|
src_stride_y: usize,
|
||||||
|
src_stride_uv: usize,
|
||||||
|
dst: &mut Vec<u8>,
|
||||||
|
i420: &mut Vec<u8>,
|
||||||
|
align: usize,
|
||||||
|
) -> ResultType<()> {
|
||||||
|
let nv12_stride_y = src_stride_y;
|
||||||
|
let nv12_stride_uv = src_stride_uv;
|
||||||
|
if let Ok((linesize_i420, offset_i420, i420_len)) =
|
||||||
|
ffmpeg_linesize_offset_length(AVPixelFormat::AV_PIX_FMT_YUV420P, width, height, align)
|
||||||
|
{
|
||||||
|
dst.resize(width * height * 4, 0);
|
||||||
|
let i420_stride_y = linesize_i420[0];
|
||||||
|
let i420_stride_u = linesize_i420[1];
|
||||||
|
let i420_stride_v = linesize_i420[2];
|
||||||
|
i420.resize(i420_len as _, 0);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let i420_offset_y = i420.as_ptr().add(0) as _;
|
||||||
|
let i420_offset_u = i420.as_ptr().add(offset_i420[0] as _) as _;
|
||||||
|
let i420_offset_v = i420.as_ptr().add(offset_i420[1] as _) as _;
|
||||||
|
super::NV12ToI420(
|
||||||
|
src_y.as_ptr(),
|
||||||
|
nv12_stride_y as _,
|
||||||
|
src_uv.as_ptr(),
|
||||||
|
nv12_stride_uv as _,
|
||||||
|
i420_offset_y,
|
||||||
|
i420_stride_y,
|
||||||
|
i420_offset_u,
|
||||||
|
i420_stride_u,
|
||||||
|
i420_offset_v,
|
||||||
|
i420_stride_v,
|
||||||
|
width as _,
|
||||||
|
height as _,
|
||||||
|
);
|
||||||
|
super::I420ToARGB(
|
||||||
|
i420_offset_y,
|
||||||
|
i420_stride_y,
|
||||||
|
i420_offset_u,
|
||||||
|
i420_stride_u,
|
||||||
|
i420_offset_v,
|
||||||
|
i420_stride_v,
|
||||||
|
dst.as_mut_ptr(),
|
||||||
|
(width * 4) as _,
|
||||||
|
width as _,
|
||||||
|
height as _,
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Err(anyhow!("get linesize offset failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
pub fn hw_nv12_to_bgra(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
src_y: &[u8],
|
||||||
|
src_uv: &[u8],
|
||||||
|
src_stride_y: usize,
|
||||||
|
src_stride_uv: usize,
|
||||||
|
dst: &mut Vec<u8>,
|
||||||
|
) -> ResultType<()> {
|
||||||
|
dst.resize(width * height * 4, 0);
|
||||||
|
unsafe {
|
||||||
|
match super::NV12ToARGB(
|
||||||
|
src_y.as_ptr(),
|
||||||
|
src_stride_y as _,
|
||||||
|
src_uv.as_ptr(),
|
||||||
|
src_stride_uv as _,
|
||||||
|
dst.as_mut_ptr(),
|
||||||
|
(width * 4) as _,
|
||||||
|
width as _,
|
||||||
|
height as _,
|
||||||
|
) {
|
||||||
|
0 => Ok(()),
|
||||||
|
_ => Err(anyhow!("NV12ToARGB failed")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hw_i420_to_bgra(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
src_y: &[u8],
|
||||||
|
src_u: &[u8],
|
||||||
|
src_v: &[u8],
|
||||||
|
src_stride_y: usize,
|
||||||
|
src_stride_u: usize,
|
||||||
|
src_stride_v: usize,
|
||||||
|
dst: &mut Vec<u8>,
|
||||||
|
) {
|
||||||
|
let src_y = src_y.as_ptr();
|
||||||
|
let src_u = src_u.as_ptr();
|
||||||
|
let src_v = src_v.as_ptr();
|
||||||
|
dst.resize(width * height * 4, 0);
|
||||||
|
unsafe {
|
||||||
|
super::I420ToARGB(
|
||||||
|
src_y,
|
||||||
|
src_stride_y as _,
|
||||||
|
src_u,
|
||||||
|
src_stride_u as _,
|
||||||
|
src_v,
|
||||||
|
src_stride_v as _,
|
||||||
|
dst.as_mut_ptr(),
|
||||||
|
(width * 4) as _,
|
||||||
|
width as _,
|
||||||
|
height as _,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
380
libs/scrap/src/common/hwcodec.rs
Normal file
380
libs/scrap/src/common/hwcodec.rs
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
use crate::{
|
||||||
|
codec::{EncoderApi, EncoderCfg},
|
||||||
|
hw, HW_STRIDE_ALIGN,
|
||||||
|
};
|
||||||
|
use hbb_common::{
|
||||||
|
anyhow::{anyhow, Context},
|
||||||
|
config::HwCodecConfig,
|
||||||
|
lazy_static, log,
|
||||||
|
message_proto::{H264s, H265s, Message, VideoFrame, H264, H265},
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
use hwcodec::{
|
||||||
|
decode::{DecodeContext, DecodeFrame, Decoder},
|
||||||
|
encode::{EncodeContext, EncodeFrame, Encoder},
|
||||||
|
ffmpeg::{CodecInfo, CodecInfos, DataFormat},
|
||||||
|
AVPixelFormat,
|
||||||
|
Quality::{self, *},
|
||||||
|
RateContorl::{self, *},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
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";
|
||||||
|
|
||||||
|
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P;
|
||||||
|
const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
||||||
|
const DEFAULT_GOP: i32 = 60;
|
||||||
|
const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
||||||
|
const DEFAULT_RC: RateContorl = RC_DEFAULT;
|
||||||
|
|
||||||
|
pub struct HwEncoder {
|
||||||
|
encoder: Encoder,
|
||||||
|
yuv: Vec<u8>,
|
||||||
|
pub format: DataFormat,
|
||||||
|
pub pixfmt: AVPixelFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncoderApi for HwEncoder {
|
||||||
|
fn new(cfg: EncoderCfg) -> ResultType<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match cfg {
|
||||||
|
EncoderCfg::HW(config) => {
|
||||||
|
let (bitrate, timebase, gop, quality, rc) =
|
||||||
|
HwEncoder::convert_quality(&config.codec_name, config.bitrate_ratio);
|
||||||
|
let ctx = EncodeContext {
|
||||||
|
name: config.codec_name.clone(),
|
||||||
|
width: config.width as _,
|
||||||
|
height: config.height as _,
|
||||||
|
pixfmt: DEFAULT_PIXFMT,
|
||||||
|
align: HW_STRIDE_ALIGN as _,
|
||||||
|
bitrate,
|
||||||
|
timebase,
|
||||||
|
gop,
|
||||||
|
quality,
|
||||||
|
rc,
|
||||||
|
};
|
||||||
|
let format = match Encoder::format_from_name(config.codec_name.clone()) {
|
||||||
|
Ok(format) => format,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(anyhow!(format!(
|
||||||
|
"failed to get format from name:{}",
|
||||||
|
config.codec_name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match Encoder::new(ctx.clone()) {
|
||||||
|
Ok(encoder) => Ok(HwEncoder {
|
||||||
|
encoder,
|
||||||
|
yuv: vec![],
|
||||||
|
format,
|
||||||
|
pixfmt: ctx.pixfmt,
|
||||||
|
}),
|
||||||
|
Err(_) => Err(anyhow!(format!("Failed to create encoder"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("encoder type mismatch")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_to_message(
|
||||||
|
&mut self,
|
||||||
|
frame: &[u8],
|
||||||
|
_ms: i64,
|
||||||
|
) -> ResultType<hbb_common::message_proto::Message> {
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
let mut vf = VideoFrame::new();
|
||||||
|
match self.format {
|
||||||
|
DataFormat::H264 => {
|
||||||
|
let mut h264s = Vec::new();
|
||||||
|
for frame in self.encode(frame).with_context(|| "Failed to encode")? {
|
||||||
|
h264s.push(H264 {
|
||||||
|
data: frame.data,
|
||||||
|
pts: frame.pts as _,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if h264s.len() > 0 {
|
||||||
|
vf.set_h264s(H264s {
|
||||||
|
h264s: h264s.into(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
msg_out.set_video_frame(vf);
|
||||||
|
Ok(msg_out)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("no valid frame"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataFormat::H265 => {
|
||||||
|
let mut h265s = Vec::new();
|
||||||
|
for frame in self.encode(frame).with_context(|| "Failed to encode")? {
|
||||||
|
h265s.push(H265 {
|
||||||
|
data: frame.data,
|
||||||
|
pts: frame.pts,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if h265s.len() > 0 {
|
||||||
|
vf.set_h265s(H265s {
|
||||||
|
h265s,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
msg_out.set_video_frame(vf);
|
||||||
|
Ok(msg_out)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("no valid frame"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_yuv(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HwEncoder {
|
||||||
|
/// Get best encoders.
|
||||||
|
///
|
||||||
|
/// # Parameter
|
||||||
|
/// `force_reset`: force to refresh config.
|
||||||
|
/// `write`: write to config file.
|
||||||
|
///
|
||||||
|
/// # Return
|
||||||
|
/// `CodecInfos`: infos.
|
||||||
|
/// `bool`: whether the config is refreshed.
|
||||||
|
pub fn best(force_reset: bool, write: bool) -> (CodecInfos, bool) {
|
||||||
|
let config = get_config(CFG_KEY_ENCODER);
|
||||||
|
if !force_reset && config.is_ok() {
|
||||||
|
(config.unwrap(), false)
|
||||||
|
} else {
|
||||||
|
let ctx = EncodeContext {
|
||||||
|
name: String::from(""),
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
pixfmt: DEFAULT_PIXFMT,
|
||||||
|
align: HW_STRIDE_ALIGN as _,
|
||||||
|
bitrate: 0,
|
||||||
|
timebase: DEFAULT_TIME_BASE,
|
||||||
|
gop: DEFAULT_GOP,
|
||||||
|
quality: DEFAULT_HW_QUALITY,
|
||||||
|
rc: DEFAULT_RC,
|
||||||
|
};
|
||||||
|
let encoders = CodecInfo::score(Encoder::avaliable_encoders(ctx));
|
||||||
|
if write {
|
||||||
|
let _ = set_config(CFG_KEY_ENCODER, &encoders)
|
||||||
|
.map_err(|e| log::error!("{:?}", e))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
(encoders, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
self.encoder.ctx.width as _,
|
||||||
|
self.encoder.ctx.height as _,
|
||||||
|
&self.encoder.linesize,
|
||||||
|
&self.encoder.offset,
|
||||||
|
self.encoder.length,
|
||||||
|
bgra,
|
||||||
|
&mut self.yuv,
|
||||||
|
),
|
||||||
|
AVPixelFormat::AV_PIX_FMT_NV12 => hw::hw_bgra_to_nv12(
|
||||||
|
self.encoder.ctx.width as _,
|
||||||
|
self.encoder.ctx.height as _,
|
||||||
|
&self.encoder.linesize,
|
||||||
|
&self.encoder.offset,
|
||||||
|
self.encoder.length,
|
||||||
|
bgra,
|
||||||
|
&mut self.yuv,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.encoder.encode(&self.yuv) {
|
||||||
|
Ok(v) => {
|
||||||
|
let mut data = Vec::<EncodeFrame>::new();
|
||||||
|
data.append(v);
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
Err(_) => Ok(Vec::<EncodeFrame>::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_quality(
|
||||||
|
name: &str,
|
||||||
|
bitrate_ratio: i32,
|
||||||
|
) -> (i32, [i32; 2], i32, Quality, RateContorl) {
|
||||||
|
// TODO
|
||||||
|
let mut bitrate = if name.contains("qsv") {
|
||||||
|
1_000_000
|
||||||
|
} else {
|
||||||
|
2_000_000
|
||||||
|
};
|
||||||
|
if bitrate_ratio > 0 && bitrate_ratio <= 200 {
|
||||||
|
bitrate = bitrate * bitrate_ratio / 100;
|
||||||
|
};
|
||||||
|
(
|
||||||
|
bitrate,
|
||||||
|
DEFAULT_TIME_BASE,
|
||||||
|
DEFAULT_GOP,
|
||||||
|
DEFAULT_HW_QUALITY,
|
||||||
|
DEFAULT_RC,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HwDecoder {
|
||||||
|
decoder: Decoder,
|
||||||
|
pub info: CodecInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HwDecoders {
|
||||||
|
pub h264: Option<HwDecoder>,
|
||||||
|
pub h265: Option<HwDecoder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HwDecoder {
|
||||||
|
/// See HwEncoder::best
|
||||||
|
fn best(force_reset: bool, write: bool) -> (CodecInfos, bool) {
|
||||||
|
let config = get_config(CFG_KEY_DECODER);
|
||||||
|
if !force_reset && config.is_ok() {
|
||||||
|
(config.unwrap(), false)
|
||||||
|
} else {
|
||||||
|
let decoders = CodecInfo::score(Decoder::avaliable_decoders());
|
||||||
|
if write {
|
||||||
|
set_config(CFG_KEY_DECODER, &decoders)
|
||||||
|
.map_err(|e| log::error!("{:?}", e))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
(decoders, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_decoders() -> HwDecoders {
|
||||||
|
let (best, _) = HwDecoder::best(false, true);
|
||||||
|
let mut h264: Option<HwDecoder> = None;
|
||||||
|
let mut h265: Option<HwDecoder> = None;
|
||||||
|
let mut fail = false;
|
||||||
|
|
||||||
|
if let Some(info) = best.h264 {
|
||||||
|
h264 = HwDecoder::new(info).ok();
|
||||||
|
if h264.is_none() {
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(info) = best.h265 {
|
||||||
|
h265 = HwDecoder::new(info).ok();
|
||||||
|
if h265.is_none() {
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fail {
|
||||||
|
HwDecoder::best(true, true);
|
||||||
|
}
|
||||||
|
HwDecoders { h264, h265 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(info: CodecInfo) -> ResultType<Self> {
|
||||||
|
let ctx = DecodeContext {
|
||||||
|
name: info.name.clone(),
|
||||||
|
device_type: info.hwdevice.clone(),
|
||||||
|
};
|
||||||
|
match Decoder::new(ctx) {
|
||||||
|
Ok(decoder) => Ok(HwDecoder { decoder, info }),
|
||||||
|
Err(_) => Err(anyhow!(format!("Failed to create decoder"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn decode(&mut self, data: &[u8]) -> ResultType<Vec<HwDecoderImage>> {
|
||||||
|
match self.decoder.decode(data) {
|
||||||
|
Ok(v) => Ok(v.iter().map(|f| HwDecoderImage { frame: f }).collect()),
|
||||||
|
Err(_) => Ok(vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HwDecoderImage<'a> {
|
||||||
|
frame: &'a DecodeFrame,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HwDecoderImage<'_> {
|
||||||
|
pub fn bgra(&self, bgra: &mut Vec<u8>, i420: &mut Vec<u8>) -> ResultType<()> {
|
||||||
|
let frame = self.frame;
|
||||||
|
match frame.pixfmt {
|
||||||
|
AVPixelFormat::AV_PIX_FMT_NV12 => hw::hw_nv12_to_bgra(
|
||||||
|
frame.width as _,
|
||||||
|
frame.height as _,
|
||||||
|
&frame.data[0],
|
||||||
|
&frame.data[1],
|
||||||
|
frame.linesize[0] as _,
|
||||||
|
frame.linesize[1] as _,
|
||||||
|
bgra,
|
||||||
|
i420,
|
||||||
|
HW_STRIDE_ALIGN,
|
||||||
|
),
|
||||||
|
AVPixelFormat::AV_PIX_FMT_YUV420P => {
|
||||||
|
hw::hw_i420_to_bgra(
|
||||||
|
frame.width as _,
|
||||||
|
frame.height as _,
|
||||||
|
&frame.data[0],
|
||||||
|
&frame.data[1],
|
||||||
|
&frame.data[2],
|
||||||
|
frame.linesize[0] as _,
|
||||||
|
frame.linesize[1] as _,
|
||||||
|
frame.linesize[2] as _,
|
||||||
|
bgra,
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config(k: &str) -> ResultType<CodecInfos> {
|
||||||
|
let v = HwCodecConfig::get_option(k);
|
||||||
|
match CodecInfos::deserialize(&v) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(_) => Err(anyhow!("Failed to get config:{}", k)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_config(k: &str, v: &CodecInfos) -> ResultType<()> {
|
||||||
|
match v.serialize() {
|
||||||
|
Ok(v) => {
|
||||||
|
HwCodecConfig::set_option(k.to_owned(), v);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(_) => Err(anyhow!("Failed to set config:{}", k)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_config() -> Option<HashMap<String, String>> {
|
||||||
|
let (encoders, update_encoders) = HwEncoder::best(false, false);
|
||||||
|
let (decoders, update_decoders) = HwDecoder::best(false, false);
|
||||||
|
if update_encoders || update_decoders {
|
||||||
|
if let Ok(encoders) = encoders.serialize() {
|
||||||
|
if let Ok(decoders) = decoders.serialize() {
|
||||||
|
return Some(HashMap::from([
|
||||||
|
(CFG_KEY_ENCODER.to_owned(), encoders),
|
||||||
|
(CFG_KEY_DECODER.to_owned(), decoders),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::error!("Failed to serialize codec info");
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
pub use self::codec::*;
|
pub use self::vpxcodec::*;
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(quartz)] {
|
if #[cfg(quartz)] {
|
||||||
@ -29,8 +29,12 @@ cfg_if! {
|
|||||||
|
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
mod convert;
|
mod convert;
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
pub mod hwcodec;
|
||||||
|
pub mod vpxcodec;
|
||||||
pub use self::convert::*;
|
pub use self::convert::*;
|
||||||
pub const STRIDE_ALIGN: usize = 64; // commonly used in libvpx vpx_img_alloc caller
|
pub const STRIDE_ALIGN: usize = 64; // commonly used in libvpx vpx_img_alloc caller
|
||||||
|
pub const HW_STRIDE_ALIGN: usize = 0; // recommended by av_frame_get_buffer
|
||||||
|
|
||||||
mod vpx;
|
mod vpx;
|
||||||
|
|
||||||
|
606
libs/scrap/src/common/vpxcodec.rs
Normal file
606
libs/scrap/src/common/vpxcodec.rs
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
// https://github.com/astraw/vpx-encode
|
||||||
|
// https://github.com/astraw/env-libvpx-sys
|
||||||
|
// https://github.com/rust-av/vpx-rs/blob/master/src/decoder.rs
|
||||||
|
|
||||||
|
use hbb_common::anyhow::{anyhow, Context};
|
||||||
|
use hbb_common::message_proto::{Message, VP9s, VideoFrame, VP9};
|
||||||
|
use hbb_common::ResultType;
|
||||||
|
|
||||||
|
use crate::codec::EncoderApi;
|
||||||
|
use crate::STRIDE_ALIGN;
|
||||||
|
|
||||||
|
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
|
||||||
|
use std::os::raw::{c_int, c_uint};
|
||||||
|
use std::{ptr, slice};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum VpxVideoCodecId {
|
||||||
|
VP8,
|
||||||
|
VP9,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VpxVideoCodecId {
|
||||||
|
fn default() -> VpxVideoCodecId {
|
||||||
|
VpxVideoCodecId::VP9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VpxEncoder {
|
||||||
|
ctx: vpx_codec_ctx_t,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VpxDecoder {
|
||||||
|
ctx: vpx_codec_ctx_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
FailedCall(String),
|
||||||
|
BadPtr(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
macro_rules! call_vpx {
|
||||||
|
($x:expr) => {{
|
||||||
|
let result = unsafe { $x }; // original expression
|
||||||
|
let result_int = unsafe { std::mem::transmute::<_, i32>(result) };
|
||||||
|
if result_int != 0 {
|
||||||
|
return Err(Error::FailedCall(format!(
|
||||||
|
"errcode={} {}:{}:{}:{}",
|
||||||
|
result_int,
|
||||||
|
module_path!(),
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! call_vpx_ptr {
|
||||||
|
($x:expr) => {{
|
||||||
|
let result = unsafe { $x }; // original expression
|
||||||
|
let result_int = unsafe { std::mem::transmute::<_, isize>(result) };
|
||||||
|
if result_int == 0 {
|
||||||
|
return Err(Error::BadPtr(format!(
|
||||||
|
"errcode={} {}:{}:{}:{}",
|
||||||
|
result_int,
|
||||||
|
module_path!(),
|
||||||
|
file!(),
|
||||||
|
line!(),
|
||||||
|
column!()
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncoderApi for VpxEncoder {
|
||||||
|
fn new(cfg: crate::codec::EncoderCfg) -> ResultType<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
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 mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||||
|
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||||
|
|
||||||
|
// https://www.webmproject.org/docs/encoder-parameters/
|
||||||
|
// default: c.rc_min_quantizer = 0, c.rc_max_quantizer = 63
|
||||||
|
// try rc_resize_allowed later
|
||||||
|
|
||||||
|
c.g_w = config.width;
|
||||||
|
c.g_h = config.height;
|
||||||
|
c.g_timebase.num = config.timebase[0];
|
||||||
|
c.g_timebase.den = config.timebase[1];
|
||||||
|
c.rc_target_bitrate = config.bitrate;
|
||||||
|
c.rc_undershoot_pct = 95;
|
||||||
|
c.rc_dropframe_thresh = 25;
|
||||||
|
if config.rc_min_quantizer > 0 {
|
||||||
|
c.rc_min_quantizer = config.rc_min_quantizer;
|
||||||
|
}
|
||||||
|
if config.rc_max_quantizer > 0 {
|
||||||
|
c.rc_max_quantizer = config.rc_max_quantizer;
|
||||||
|
}
|
||||||
|
let mut speed = config.speed;
|
||||||
|
if speed <= 0 {
|
||||||
|
speed = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
c.g_threads = if config.num_threads == 0 {
|
||||||
|
num_cpus::get() as _
|
||||||
|
} else {
|
||||||
|
config.num_threads
|
||||||
|
};
|
||||||
|
c.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
|
||||||
|
// https://developers.google.com/media/vp9/bitrate-modes/
|
||||||
|
// Constant Bitrate mode (CBR) is recommended for live streaming with VP9.
|
||||||
|
c.rc_end_usage = vpx_rc_mode::VPX_CBR;
|
||||||
|
// c.kf_min_dist = 0;
|
||||||
|
// c.kf_max_dist = 999999;
|
||||||
|
c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot
|
||||||
|
|
||||||
|
/*
|
||||||
|
VPX encoder支持two-pass encode,这是为了rate control的。
|
||||||
|
对于两遍编码,就是需要整个编码过程做两次,第一次会得到一些新的控制参数来进行第二遍的编码,
|
||||||
|
这样可以在相同的bitrate下得到最好的PSNR
|
||||||
|
*/
|
||||||
|
|
||||||
|
let mut ctx = Default::default();
|
||||||
|
call_vpx!(vpx_codec_enc_init_ver(
|
||||||
|
&mut ctx,
|
||||||
|
i,
|
||||||
|
&c,
|
||||||
|
0,
|
||||||
|
VPX_ENCODER_ABI_VERSION as _
|
||||||
|
));
|
||||||
|
|
||||||
|
if config.codec == VpxVideoCodecId::VP9 {
|
||||||
|
// set encoder internal speed settings
|
||||||
|
// in ffmpeg, it is --speed option
|
||||||
|
/*
|
||||||
|
set to 0 or a positive value 1-16, the codec will try to adapt its
|
||||||
|
complexity depending on the time it spends encoding. Increasing this
|
||||||
|
number will make the speed go up and the quality go down.
|
||||||
|
Negative values mean strict enforcement of this
|
||||||
|
while positive values are adaptive
|
||||||
|
*/
|
||||||
|
/* https://developers.google.com/media/vp9/live-encoding
|
||||||
|
Speed 5 to 8 should be used for live / real-time encoding.
|
||||||
|
Lower numbers (5 or 6) are higher quality but require more CPU power.
|
||||||
|
Higher numbers (7 or 8) will be lower quality but more manageable for lower latency
|
||||||
|
use cases and also for lower CPU power devices such as mobile.
|
||||||
|
*/
|
||||||
|
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, speed,));
|
||||||
|
// set row level multi-threading
|
||||||
|
/*
|
||||||
|
as some people in comments and below have already commented,
|
||||||
|
more recent versions of libvpx support -row-mt 1 to enable tile row
|
||||||
|
multi-threading. This can increase the number of tiles by up to 4x in VP9
|
||||||
|
(since the max number of tile rows is 4, regardless of video height).
|
||||||
|
To enable this, use -tile-rows N where N is the number of tile rows in
|
||||||
|
log2 units (so -tile-rows 1 means 2 tile rows and -tile-rows 2 means 4 tile
|
||||||
|
rows). The total number of active threads will then be equal to
|
||||||
|
$tile_rows * $tile_columns
|
||||||
|
*/
|
||||||
|
call_vpx!(vpx_codec_control_(
|
||||||
|
&mut ctx,
|
||||||
|
VP9E_SET_ROW_MT as _,
|
||||||
|
1 as c_int
|
||||||
|
));
|
||||||
|
|
||||||
|
call_vpx!(vpx_codec_control_(
|
||||||
|
&mut ctx,
|
||||||
|
VP9E_SET_TILE_COLUMNS as _,
|
||||||
|
4 as c_int
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
ctx,
|
||||||
|
width: config.width as _,
|
||||||
|
height: config.height as _,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("encoder type mismatch")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message> {
|
||||||
|
let mut frames = Vec::new();
|
||||||
|
for ref frame in self
|
||||||
|
.encode(ms, frame, STRIDE_ALIGN)
|
||||||
|
.with_context(|| "Failed to encode")?
|
||||||
|
{
|
||||||
|
frames.push(VpxEncoder::create_frame(frame));
|
||||||
|
}
|
||||||
|
for ref frame in self.flush().with_context(|| "Failed to flush")? {
|
||||||
|
frames.push(VpxEncoder::create_frame(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
// to-do: flush periodically, e.g. 1 second
|
||||||
|
if frames.len() > 0 {
|
||||||
|
Ok(VpxEncoder::create_msg(frames))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("no valid frame"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_yuv(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VpxEncoder {
|
||||||
|
pub fn encode(&mut self, pts: i64, data: &[u8], stride_align: usize) -> Result<EncodeFrames> {
|
||||||
|
assert!(2 * data.len() >= 3 * self.width * self.height);
|
||||||
|
|
||||||
|
let mut image = Default::default();
|
||||||
|
call_vpx_ptr!(vpx_img_wrap(
|
||||||
|
&mut image,
|
||||||
|
vpx_img_fmt::VPX_IMG_FMT_I420,
|
||||||
|
self.width as _,
|
||||||
|
self.height as _,
|
||||||
|
stride_align as _,
|
||||||
|
data.as_ptr() as _,
|
||||||
|
));
|
||||||
|
|
||||||
|
call_vpx!(vpx_codec_encode(
|
||||||
|
&mut self.ctx,
|
||||||
|
&image,
|
||||||
|
pts as _,
|
||||||
|
1, // Duration
|
||||||
|
0, // Flags
|
||||||
|
VPX_DL_REALTIME as _,
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(EncodeFrames {
|
||||||
|
ctx: &mut self.ctx,
|
||||||
|
iter: ptr::null(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify the encoder to return any pending packets
|
||||||
|
pub fn flush(&mut self) -> Result<EncodeFrames> {
|
||||||
|
call_vpx!(vpx_codec_encode(
|
||||||
|
&mut self.ctx,
|
||||||
|
ptr::null(),
|
||||||
|
-1, // PTS
|
||||||
|
1, // Duration
|
||||||
|
0, // Flags
|
||||||
|
VPX_DL_REALTIME as _,
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(EncodeFrames {
|
||||||
|
ctx: &mut self.ctx,
|
||||||
|
iter: ptr::null(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn create_msg(vp9s: Vec<VP9>) -> Message {
|
||||||
|
let mut msg_out = Message::new();
|
||||||
|
let mut vf = VideoFrame::new();
|
||||||
|
vf.set_vp9s(VP9s {
|
||||||
|
frames: vp9s.into(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
msg_out.set_video_frame(vf);
|
||||||
|
msg_out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn create_frame(frame: &EncodeFrame) -> VP9 {
|
||||||
|
VP9 {
|
||||||
|
data: frame.data.to_vec(),
|
||||||
|
key: frame.key,
|
||||||
|
pts: frame.pts,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VpxEncoder {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let result = vpx_codec_destroy(&mut self.ctx);
|
||||||
|
if result != VPX_CODEC_OK {
|
||||||
|
panic!("failed to destroy vpx codec");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct EncodeFrame<'a> {
|
||||||
|
/// Compressed data.
|
||||||
|
pub data: &'a [u8],
|
||||||
|
/// Whether the frame is a keyframe.
|
||||||
|
pub key: bool,
|
||||||
|
/// Presentation timestamp (in timebase units).
|
||||||
|
pub pts: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct VpxEncoderConfig {
|
||||||
|
/// The width (in pixels).
|
||||||
|
pub width: c_uint,
|
||||||
|
/// The height (in pixels).
|
||||||
|
pub height: c_uint,
|
||||||
|
/// The timebase numerator and denominator (in seconds).
|
||||||
|
pub timebase: [c_int; 2],
|
||||||
|
/// The target bitrate (in kilobits per second).
|
||||||
|
pub bitrate: c_uint,
|
||||||
|
/// The codec
|
||||||
|
pub codec: VpxVideoCodecId,
|
||||||
|
pub rc_min_quantizer: u32,
|
||||||
|
pub rc_max_quantizer: u32,
|
||||||
|
pub speed: i32,
|
||||||
|
pub num_threads: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct VpxDecoderConfig {
|
||||||
|
pub codec: VpxVideoCodecId,
|
||||||
|
pub num_threads: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EncodeFrames<'a> {
|
||||||
|
ctx: &'a mut vpx_codec_ctx_t,
|
||||||
|
iter: vpx_codec_iter_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for EncodeFrames<'a> {
|
||||||
|
type Item = EncodeFrame<'a>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
unsafe {
|
||||||
|
let pkt = vpx_codec_get_cx_data(self.ctx, &mut self.iter);
|
||||||
|
if pkt.is_null() {
|
||||||
|
return None;
|
||||||
|
} else if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
|
||||||
|
let f = &(*pkt).data.frame;
|
||||||
|
return Some(Self::Item {
|
||||||
|
data: slice::from_raw_parts(f.buf as _, f.sz as _),
|
||||||
|
key: (f.flags & VPX_FRAME_IS_KEY) != 0,
|
||||||
|
pts: f.pts,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Ignore the packet.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VpxDecoder {
|
||||||
|
/// Create a new decoder
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// The function may fail if the underlying libvpx does not provide
|
||||||
|
/// the VP9 decoder.
|
||||||
|
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 mut ctx = Default::default();
|
||||||
|
let cfg = vpx_codec_dec_cfg_t {
|
||||||
|
threads: if config.num_threads == 0 {
|
||||||
|
num_cpus::get() as _
|
||||||
|
} else {
|
||||||
|
config.num_threads
|
||||||
|
},
|
||||||
|
w: 0,
|
||||||
|
h: 0,
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
unsafe {
|
||||||
|
println!("{}", vpx_codec_get_caps(i));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
call_vpx!(vpx_codec_dec_init_ver(
|
||||||
|
&mut ctx,
|
||||||
|
i,
|
||||||
|
&cfg,
|
||||||
|
0,
|
||||||
|
VPX_DECODER_ABI_VERSION as _,
|
||||||
|
));
|
||||||
|
Ok(Self { ctx })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode2rgb(&mut self, data: &[u8], rgba: bool) -> Result<Vec<u8>> {
|
||||||
|
let mut img = Image::new();
|
||||||
|
for frame in self.decode(data)? {
|
||||||
|
drop(img);
|
||||||
|
img = frame;
|
||||||
|
}
|
||||||
|
for frame in self.flush()? {
|
||||||
|
drop(img);
|
||||||
|
img = frame;
|
||||||
|
}
|
||||||
|
if img.is_null() {
|
||||||
|
Ok(Vec::new())
|
||||||
|
} else {
|
||||||
|
let mut out = Default::default();
|
||||||
|
img.rgb(1, rgba, &mut out);
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Feed some compressed data to the encoder
|
||||||
|
///
|
||||||
|
/// The `data` slice is sent to the decoder
|
||||||
|
///
|
||||||
|
/// It matches a call to `vpx_codec_decode`.
|
||||||
|
pub fn decode(&mut self, data: &[u8]) -> Result<DecodeFrames> {
|
||||||
|
call_vpx!(vpx_codec_decode(
|
||||||
|
&mut self.ctx,
|
||||||
|
data.as_ptr(),
|
||||||
|
data.len() as _,
|
||||||
|
ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(DecodeFrames {
|
||||||
|
ctx: &mut self.ctx,
|
||||||
|
iter: ptr::null(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify the decoder to return any pending frame
|
||||||
|
pub fn flush(&mut self) -> Result<DecodeFrames> {
|
||||||
|
call_vpx!(vpx_codec_decode(
|
||||||
|
&mut self.ctx,
|
||||||
|
ptr::null(),
|
||||||
|
0,
|
||||||
|
ptr::null_mut(),
|
||||||
|
0
|
||||||
|
));
|
||||||
|
Ok(DecodeFrames {
|
||||||
|
ctx: &mut self.ctx,
|
||||||
|
iter: ptr::null(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VpxDecoder {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let result = vpx_codec_destroy(&mut self.ctx);
|
||||||
|
if result != VPX_CODEC_OK {
|
||||||
|
panic!("failed to destroy vpx codec");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DecodeFrames<'a> {
|
||||||
|
ctx: &'a mut vpx_codec_ctx_t,
|
||||||
|
iter: vpx_codec_iter_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for DecodeFrames<'a> {
|
||||||
|
type Item = Image;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let img = unsafe { vpx_codec_get_frame(self.ctx, &mut self.iter) };
|
||||||
|
if img.is_null() {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
return Some(Image(img));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://chromium.googlesource.com/webm/libvpx/+/bali/vpx/src/vpx_image.c
|
||||||
|
pub struct Image(*mut vpx_image_t);
|
||||||
|
impl Image {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(std::ptr::null_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
self.0.is_null()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
self.inner().d_w as _
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
self.inner().d_h as _
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn format(&self) -> vpx_img_fmt_t {
|
||||||
|
// VPX_IMG_FMT_I420
|
||||||
|
self.inner().fmt
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn inner(&self) -> &vpx_image_t {
|
||||||
|
unsafe { &*self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn stride(&self, iplane: usize) -> i32 {
|
||||||
|
self.inner().stride[iplane]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rgb(&self, stride_align: usize, rgba: bool, dst: &mut Vec<u8>) {
|
||||||
|
let h = self.height();
|
||||||
|
let mut w = self.width();
|
||||||
|
let bps = if rgba { 4 } else { 3 };
|
||||||
|
w = (w + stride_align - 1) & !(stride_align - 1);
|
||||||
|
dst.resize(h * w * bps, 0);
|
||||||
|
let img = self.inner();
|
||||||
|
unsafe {
|
||||||
|
if rgba {
|
||||||
|
super::I420ToARGB(
|
||||||
|
img.planes[0],
|
||||||
|
img.stride[0],
|
||||||
|
img.planes[1],
|
||||||
|
img.stride[1],
|
||||||
|
img.planes[2],
|
||||||
|
img.stride[2],
|
||||||
|
dst.as_mut_ptr(),
|
||||||
|
(w * bps) as _,
|
||||||
|
self.width() as _,
|
||||||
|
self.height() as _,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
super::I420ToRAW(
|
||||||
|
img.planes[0],
|
||||||
|
img.stride[0],
|
||||||
|
img.planes[1],
|
||||||
|
img.stride[1],
|
||||||
|
img.planes[2],
|
||||||
|
img.stride[2],
|
||||||
|
dst.as_mut_ptr(),
|
||||||
|
(w * bps) as _,
|
||||||
|
self.width() as _,
|
||||||
|
self.height() as _,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn data(&self) -> (&[u8], &[u8], &[u8]) {
|
||||||
|
unsafe {
|
||||||
|
let img = self.inner();
|
||||||
|
let h = (img.d_h as usize + 1) & !1;
|
||||||
|
let n = img.stride[0] as usize * h;
|
||||||
|
let y = slice::from_raw_parts(img.planes[0], n);
|
||||||
|
let n = img.stride[1] as usize * (h >> 1);
|
||||||
|
let u = slice::from_raw_parts(img.planes[1], n);
|
||||||
|
let v = slice::from_raw_parts(img.planes[2], n);
|
||||||
|
(y, u, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Image {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.0.is_null() {
|
||||||
|
unsafe { vpx_img_free(self.0) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for vpx_codec_ctx_t {}
|
@ -282,7 +282,11 @@ impl CapturerMag {
|
|||||||
let y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
let y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||||
let w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
let w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||||
let h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
let h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
if !(origin.0 == x as _ && origin.1 == y as _ && width == w as _ && height == h as _) {
|
if !(origin.0 == x as i32
|
||||||
|
&& origin.1 == y as i32
|
||||||
|
&& width == w as usize
|
||||||
|
&& height == h as usize)
|
||||||
|
{
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
format!(
|
format!(
|
||||||
@ -510,10 +514,10 @@ impl CapturerMag {
|
|||||||
let y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
let y = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||||
let w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
let w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||||
let h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
let h = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
if !(self.rect.left == x as _
|
if !(self.rect.left == x as i32
|
||||||
&& self.rect.top == y as _
|
&& self.rect.top == y as i32
|
||||||
&& self.rect.right == (x + w) as _
|
&& self.rect.right == (x + w) as i32
|
||||||
&& self.rect.bottom == (y + h) as _)
|
&& self.rect.bottom == (y + h) as i32)
|
||||||
{
|
{
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
|
@ -12,6 +12,11 @@ use cpal::{
|
|||||||
Device, Host, StreamConfig,
|
Device, Host, StreamConfig,
|
||||||
};
|
};
|
||||||
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
||||||
|
use scrap::{
|
||||||
|
codec::{Decoder, DecoderCfg},
|
||||||
|
VpxDecoderConfig, VpxVideoCodecId,
|
||||||
|
};
|
||||||
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -30,7 +35,6 @@ use hbb_common::{
|
|||||||
tokio::time::Duration,
|
tokio::time::Duration,
|
||||||
AddrMangle, ResultType, Stream,
|
AddrMangle, ResultType, Stream,
|
||||||
};
|
};
|
||||||
use scrap::{Decoder, Image, VideoCodecId};
|
|
||||||
|
|
||||||
pub use super::lang::*;
|
pub use super::lang::*;
|
||||||
pub mod file_trait;
|
pub mod file_trait;
|
||||||
@ -717,7 +721,12 @@ pub struct VideoHandler {
|
|||||||
impl VideoHandler {
|
impl VideoHandler {
|
||||||
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
pub fn new(latency_controller: Arc<Mutex<LatencyController>>) -> Self {
|
||||||
VideoHandler {
|
VideoHandler {
|
||||||
decoder: Decoder::new(VideoCodecId::VP9, (num_cpus::get() / 2) as _).unwrap(),
|
decoder: Decoder::new(DecoderCfg {
|
||||||
|
vpx: VpxDecoderConfig {
|
||||||
|
codec: VpxVideoCodecId::VP9,
|
||||||
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
|
},
|
||||||
|
}),
|
||||||
latency_controller,
|
latency_controller,
|
||||||
rgb: Default::default(),
|
rgb: Default::default(),
|
||||||
}
|
}
|
||||||
@ -731,33 +740,18 @@ impl VideoHandler {
|
|||||||
.update_video(vf.timestamp);
|
.update_video(vf.timestamp);
|
||||||
}
|
}
|
||||||
match &vf.union {
|
match &vf.union {
|
||||||
Some(video_frame::Union::vp9s(vp9s)) => self.handle_vp9s(vp9s),
|
Some(frame) => self.decoder.handle_video_frame(frame, &mut self.rgb),
|
||||||
_ => Ok(false),
|
_ => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_vp9s(&mut self, vp9s: &VP9s) -> ResultType<bool> {
|
|
||||||
let mut last_frame = Image::new();
|
|
||||||
for vp9 in vp9s.frames.iter() {
|
|
||||||
for frame in self.decoder.decode(&vp9.data)? {
|
|
||||||
drop(last_frame);
|
|
||||||
last_frame = frame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for frame in self.decoder.flush()? {
|
|
||||||
drop(last_frame);
|
|
||||||
last_frame = frame;
|
|
||||||
}
|
|
||||||
if last_frame.is_null() {
|
|
||||||
Ok(false)
|
|
||||||
} else {
|
|
||||||
last_frame.rgb(1, true, &mut self.rgb);
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.decoder = Decoder::new(VideoCodecId::VP9, 1).unwrap();
|
self.decoder = Decoder::new(DecoderCfg {
|
||||||
|
vpx: VpxDecoderConfig {
|
||||||
|
codec: VpxVideoCodecId::VP9,
|
||||||
|
num_threads: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -952,6 +946,11 @@ impl LoginConfigHandler {
|
|||||||
msg.disable_clipboard = BoolOption::Yes.into();
|
msg.disable_clipboard = BoolOption::Yes.into();
|
||||||
n += 1;
|
n += 1;
|
||||||
}
|
}
|
||||||
|
// TODO: add option
|
||||||
|
let state = Decoder::video_codec_state();
|
||||||
|
msg.video_codec_state = hbb_common::protobuf::MessageField::some(state);
|
||||||
|
n += 1;
|
||||||
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
Some(msg)
|
Some(msg)
|
||||||
} else {
|
} else {
|
||||||
@ -1170,9 +1169,11 @@ where
|
|||||||
|
|
||||||
let latency_controller = LatencyController::new();
|
let latency_controller = LatencyController::new();
|
||||||
let latency_controller_cl = latency_controller.clone();
|
let latency_controller_cl = latency_controller.clone();
|
||||||
|
// Create video_handler out of the thread below to ensure that the handler exists before client start.
|
||||||
|
// It will take a few tenths of a second for the first time, and then tens of milliseconds.
|
||||||
|
let mut video_handler = VideoHandler::new(latency_controller);
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let mut video_handler = VideoHandler::new(latency_controller);
|
|
||||||
loop {
|
loop {
|
||||||
if let Ok(data) = video_receiver.recv() {
|
if let Ok(data) = video_receiver.recv() {
|
||||||
match data {
|
match data {
|
||||||
|
29
src/ipc.rs
29
src/ipc.rs
@ -1,6 +1,8 @@
|
|||||||
use crate::rendezvous_mediator::RendezvousMediator;
|
use crate::rendezvous_mediator::RendezvousMediator;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub use clipboard::ClipbaordFile;
|
pub use clipboard::ClipbaordFile;
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
use hbb_common::config::HwCodecConfig;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err, bail, bytes,
|
allow_err, bail, bytes,
|
||||||
bytes_codec::BytesCodec,
|
bytes_codec::BytesCodec,
|
||||||
@ -73,7 +75,7 @@ pub enum FS {
|
|||||||
WriteOffset {
|
WriteOffset {
|
||||||
id: i32,
|
id: i32,
|
||||||
file_num: i32,
|
file_num: i32,
|
||||||
offset_blk: u32
|
offset_blk: u32,
|
||||||
},
|
},
|
||||||
CheckDigest {
|
CheckDigest {
|
||||||
id: i32,
|
id: i32,
|
||||||
@ -127,6 +129,8 @@ pub enum Data {
|
|||||||
ClipbaordFile(ClipbaordFile),
|
ClipbaordFile(ClipbaordFile),
|
||||||
ClipboardFileEnabled(bool),
|
ClipboardFileEnabled(bool),
|
||||||
PrivacyModeState((i32, PrivacyModeState)),
|
PrivacyModeState((i32, PrivacyModeState)),
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
HwCodecConfig(Option<HashMap<String, String>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
@ -336,6 +340,12 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
.await
|
.await
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
Data::HwCodecConfig(Some(config)) => {
|
||||||
|
for (k, v) in config {
|
||||||
|
HwCodecConfig::set_option(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -635,3 +645,20 @@ pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn check_hwcodec_config() {
|
||||||
|
if let Some(config) = scrap::hwcodec::check_config() {
|
||||||
|
match connect(1000, "").await {
|
||||||
|
Ok(mut conn) => {
|
||||||
|
if conn.send(&Data::HwCodecConfig(Some(config))).await.is_err() {
|
||||||
|
log::error!("Failed to send hwcodec config by ipc");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::info!("Failed to connect ipc: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
23
src/main.rs
23
src/main.rs
@ -70,6 +70,15 @@ fn main() {
|
|||||||
}
|
}
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
std::thread::spawn(move || start_server(false));
|
std::thread::spawn(move || start_server(false));
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
if let Ok(exe) = std::env::current_exe() {
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
std::process::Command::new(exe)
|
||||||
|
.arg("--check-hwcodec-config")
|
||||||
|
.status()
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@ -109,6 +118,9 @@ fn main() {
|
|||||||
} else if args[0] == "--extract" {
|
} else if args[0] == "--extract" {
|
||||||
#[cfg(feature = "with_rc")]
|
#[cfg(feature = "with_rc")]
|
||||||
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
|
hbb_common::allow_err!(crate::rc::extract_resources(&args[1]));
|
||||||
|
} else if args[0] == "--check-hwcodec-config" {
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
ipc::check_hwcodec_config();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,7 +209,7 @@ fn main() {
|
|||||||
.about("RustDesk command line tool")
|
.about("RustDesk command line tool")
|
||||||
.args_from_usage(&args)
|
.args_from_usage(&args)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
use hbb_common::{env_logger::*, config::LocalConfig};
|
use hbb_common::{config::LocalConfig, env_logger::*};
|
||||||
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
|
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
|
||||||
if let Some(p) = matches.value_of("port-forward") {
|
if let Some(p) = matches.value_of("port-forward") {
|
||||||
let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect();
|
let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect();
|
||||||
@ -225,6 +237,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
let key = matches.value_of("key").unwrap_or("").to_owned();
|
let key = matches.value_of("key").unwrap_or("").to_owned();
|
||||||
let token = LocalConfig::get_option("access_token");
|
let token = LocalConfig::get_option("access_token");
|
||||||
cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token);
|
cli::start_one_port_forward(
|
||||||
|
options[0].clone(),
|
||||||
|
port,
|
||||||
|
remote_host,
|
||||||
|
remote_port,
|
||||||
|
key,
|
||||||
|
token,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,6 +396,7 @@ impl Connection {
|
|||||||
video_service::notify_video_frame_feched(id, None);
|
video_service::notify_video_frame_feched(id, None);
|
||||||
video_service::update_test_latency(id, 0);
|
video_service::update_test_latency(id, 0);
|
||||||
video_service::update_image_quality(id, None);
|
video_service::update_image_quality(id, None);
|
||||||
|
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
|
||||||
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
|
if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
|
||||||
conn.on_close(&err.to_string(), false);
|
conn.on_close(&err.to_string(), false);
|
||||||
}
|
}
|
||||||
@ -780,6 +781,22 @@ impl Connection {
|
|||||||
if let Some(message::Union::login_request(lr)) = msg.union {
|
if let Some(message::Union::login_request(lr)) = msg.union {
|
||||||
if let Some(o) = lr.option.as_ref() {
|
if let Some(o) = lr.option.as_ref() {
|
||||||
self.update_option(o).await;
|
self.update_option(o).await;
|
||||||
|
if let Some(q) = o.video_codec_state.clone().take() {
|
||||||
|
scrap::codec::Encoder::update_video_encoder(
|
||||||
|
self.inner.id(),
|
||||||
|
scrap::codec::EncoderUpdate::State(q),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
scrap::codec::Encoder::update_video_encoder(
|
||||||
|
self.inner.id(),
|
||||||
|
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrap::codec::Encoder::update_video_encoder(
|
||||||
|
self.inner.id(),
|
||||||
|
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
self.video_ack_required = lr.video_ack_required;
|
self.video_ack_required = lr.video_ack_required;
|
||||||
if self.authorized {
|
if self.authorized {
|
||||||
|
@ -23,7 +23,11 @@ use hbb_common::tokio::sync::{
|
|||||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||||
Mutex as TokioMutex,
|
Mutex as TokioMutex,
|
||||||
};
|
};
|
||||||
use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, Frame, VideoCodecId, STRIDE_ALIGN};
|
use scrap::{
|
||||||
|
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
||||||
|
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||||
|
Capturer, Display, Frame,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
io::{ErrorKind::WouldBlock, Result},
|
io::{ErrorKind::WouldBlock, Result},
|
||||||
@ -201,9 +205,11 @@ fn check_display_changed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Capturer object is expensive, avoiding to create it frequently.
|
// Capturer object is expensive, avoiding to create it frequently.
|
||||||
fn create_capturer(privacy_mode_id: i32, display: Display) -> ResultType<Box<dyn TraitCapturer>> {
|
fn create_capturer(
|
||||||
let use_yuv = true;
|
privacy_mode_id: i32,
|
||||||
|
display: Display,
|
||||||
|
use_yuv: bool,
|
||||||
|
) -> ResultType<Box<dyn TraitCapturer>> {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let c: Option<Box<dyn TraitCapturer>> = None;
|
let c: Option<Box<dyn TraitCapturer>> = None;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -292,7 +298,7 @@ pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool {
|
|||||||
let test_begin = Instant::now();
|
let test_begin = Instant::now();
|
||||||
while test_begin.elapsed().as_millis() < timeout_millis as _ {
|
while test_begin.elapsed().as_millis() < timeout_millis as _ {
|
||||||
if let Ok((_, _, display)) = get_current_display() {
|
if let Ok((_, _, display)) = get_current_display() {
|
||||||
if let Ok(_) = create_capturer(privacy_mode_id, display) {
|
if let Ok(_) = create_capturer(privacy_mode_id, display, true) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,6 +342,36 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
num_cpus::get(),
|
num_cpus::get(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let q = get_image_quality();
|
||||||
|
let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
|
||||||
|
log::info!("bitrate={}, rc_min_quantizer={}", bitrate, rc_min_quantizer);
|
||||||
|
|
||||||
|
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||||
|
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||||
|
codec_name,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bitrate_ratio: q >> 8,
|
||||||
|
}),
|
||||||
|
None => EncoderCfg::VPX(VpxEncoderConfig {
|
||||||
|
width: width as _,
|
||||||
|
height: height as _,
|
||||||
|
timebase: [1, 1000], // Output timestamp precision
|
||||||
|
bitrate,
|
||||||
|
codec: VpxVideoCodecId::VP9,
|
||||||
|
rc_min_quantizer,
|
||||||
|
rc_max_quantizer,
|
||||||
|
speed,
|
||||||
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut encoder;
|
||||||
|
match Encoder::new(encoder_cfg) {
|
||||||
|
Ok(x) => encoder = x,
|
||||||
|
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||||
|
}
|
||||||
|
|
||||||
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
let captuerer_privacy_mode_id = privacy_mode_id;
|
let captuerer_privacy_mode_id = privacy_mode_id;
|
||||||
@ -355,26 +391,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
} else {
|
} else {
|
||||||
log::info!("In privacy mode, the peer side cannot watch the screen");
|
log::info!("In privacy mode, the peer side cannot watch the screen");
|
||||||
}
|
}
|
||||||
let mut c = create_capturer(captuerer_privacy_mode_id, display)?;
|
let mut c = create_capturer(captuerer_privacy_mode_id, display, encoder.use_yuv())?;
|
||||||
|
|
||||||
let q = get_image_quality();
|
|
||||||
let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
|
|
||||||
log::info!("bitrate={}, rc_min_quantizer={}", bitrate, rc_min_quantizer);
|
|
||||||
let cfg = Config {
|
|
||||||
width: width as _,
|
|
||||||
height: height as _,
|
|
||||||
timebase: [1, 1000], // Output timestamp precision
|
|
||||||
bitrate,
|
|
||||||
codec: VideoCodecId::VP9,
|
|
||||||
rc_min_quantizer,
|
|
||||||
rc_max_quantizer,
|
|
||||||
speed,
|
|
||||||
};
|
|
||||||
let mut vpx;
|
|
||||||
match Encoder::new(&cfg, (num_cpus::get() / 2) as _) {
|
|
||||||
Ok(x) => vpx = x,
|
|
||||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
|
||||||
}
|
|
||||||
|
|
||||||
if *SWITCH.lock().unwrap() {
|
if *SWITCH.lock().unwrap() {
|
||||||
log::debug!("Broadcasting display switch");
|
log::debug!("Broadcasting display switch");
|
||||||
@ -464,7 +481,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let time = now - start;
|
let time = now - start;
|
||||||
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
|
||||||
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut vpx)?;
|
let send_conn_ids = handle_one_frame(&sp, &frame, ms, &mut encoder)?;
|
||||||
frame_controller.set_send(now, send_conn_ids);
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@ -531,7 +548,6 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> {
|
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> {
|
||||||
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap();
|
||||||
if privacy_mode_id != privacy_mode_id_2 {
|
if privacy_mode_id != privacy_mode_id_2 {
|
||||||
@ -547,6 +563,7 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
fn create_msg(vp9s: Vec<VP9>) -> Message {
|
fn create_msg(vp9s: Vec<VP9>) -> Message {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let mut vf = VideoFrame::new();
|
let mut vf = VideoFrame::new();
|
||||||
@ -559,22 +576,12 @@ fn create_msg(vp9s: Vec<VP9>) -> Message {
|
|||||||
msg_out
|
msg_out
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn create_frame(frame: &EncodeFrame) -> VP9 {
|
|
||||||
VP9 {
|
|
||||||
data: frame.data.to_vec(),
|
|
||||||
key: frame.key,
|
|
||||||
pts: frame.pts,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn handle_one_frame(
|
fn handle_one_frame(
|
||||||
sp: &GenericService,
|
sp: &GenericService,
|
||||||
frame: &[u8],
|
frame: &[u8],
|
||||||
ms: i64,
|
ms: i64,
|
||||||
vpx: &mut Encoder,
|
encoder: &mut Encoder,
|
||||||
) -> ResultType<HashSet<i32>> {
|
) -> ResultType<HashSet<i32>> {
|
||||||
sp.snapshot(|sps| {
|
sp.snapshot(|sps| {
|
||||||
// so that new sub and old sub share the same encoder after switch
|
// so that new sub and old sub share the same encoder after switch
|
||||||
@ -585,20 +592,8 @@ fn handle_one_frame(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut send_conn_ids: HashSet<i32> = Default::default();
|
let mut send_conn_ids: HashSet<i32> = Default::default();
|
||||||
let mut frames = Vec::new();
|
if let Ok(msg) = encoder.encode_to_message(frame, ms) {
|
||||||
for ref frame in vpx
|
send_conn_ids = sp.send_video_frame(msg);
|
||||||
.encode(ms, frame, STRIDE_ALIGN)
|
|
||||||
.with_context(|| "Failed to encode")?
|
|
||||||
{
|
|
||||||
frames.push(create_frame(frame));
|
|
||||||
}
|
|
||||||
for ref frame in vpx.flush().with_context(|| "Failed to flush")? {
|
|
||||||
frames.push(create_frame(frame));
|
|
||||||
}
|
|
||||||
|
|
||||||
// to-do: flush periodically, e.g. 1 second
|
|
||||||
if frames.len() > 0 {
|
|
||||||
send_conn_ids = sp.send_video_frame(create_msg(frames));
|
|
||||||
}
|
}
|
||||||
Ok(send_conn_ids)
|
Ok(send_conn_ids)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user