mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-24 04:12:20 +08:00
scrap: add hwcodec
Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
parent
f542a39329
commit
70968638bf
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -2233,6 +2233,16 @@ 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#373d55d38c23cc8a9ef9961b3b2979d5fc9d1bc4"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.18"
|
version = "0.14.18"
|
||||||
@ -4257,6 +4267,8 @@ dependencies = [
|
|||||||
"gstreamer",
|
"gstreamer",
|
||||||
"gstreamer-app",
|
"gstreamer-app",
|
||||||
"gstreamer-video",
|
"gstreamer-video",
|
||||||
|
"hbb_common",
|
||||||
|
"hwcodec",
|
||||||
"jni",
|
"jni",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
@ -425,6 +447,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;
|
||||||
@ -440,6 +470,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 OptionResponse {
|
message OptionResponse {
|
||||||
|
@ -17,6 +17,7 @@ block = "0.1"
|
|||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
|
hbb_common = { path = "../hbb_common" }
|
||||||
|
|
||||||
[dependencies.winapi]
|
[dependencies.winapi]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
@ -46,3 +47,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,6 +13,7 @@ use std::time::{Duration, Instant};
|
|||||||
use std::{io, thread};
|
use std::{io, thread};
|
||||||
|
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
|
use scrap::coder::{EncoderApi, EncoderCfg};
|
||||||
use webm::mux;
|
use webm::mux;
|
||||||
use webm::mux::Track;
|
use webm::mux::Track;
|
||||||
|
|
||||||
@ -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.
|
||||||
|
@ -2,29 +2,36 @@
|
|||||||
// https://github.com/astraw/env-libvpx-sys
|
// https://github.com/astraw/env-libvpx-sys
|
||||||
// https://github.com/rust-av/vpx-rs/blob/master/src/decoder.rs
|
// 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::coder::EncoderApi;
|
||||||
|
use crate::STRIDE_ALIGN;
|
||||||
|
|
||||||
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
|
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
|
||||||
use std::os::raw::{c_int, c_uint};
|
use std::os::raw::{c_int, c_uint};
|
||||||
use std::{ptr, slice};
|
use std::{ptr, slice};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum VideoCodecId {
|
pub enum VpxVideoCodecId {
|
||||||
VP8,
|
VP8,
|
||||||
VP9,
|
VP9,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VideoCodecId {
|
impl Default for VpxVideoCodecId {
|
||||||
fn default() -> VideoCodecId {
|
fn default() -> VpxVideoCodecId {
|
||||||
VideoCodecId::VP9
|
VpxVideoCodecId::VP9
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Encoder {
|
pub struct VpxEncoder {
|
||||||
ctx: vpx_codec_ctx_t,
|
ctx: vpx_codec_ctx_t,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Decoder {
|
pub struct VpxDecoder {
|
||||||
ctx: vpx_codec_ctx_t,
|
ctx: vpx_codec_ctx_t,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,118 +89,152 @@ macro_rules! call_vpx_ptr {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder {
|
impl EncoderApi for VpxEncoder {
|
||||||
pub fn new(config: &Config, num_threads: u32) -> Result<Self> {
|
fn new(cfg: crate::coder::EncoderCfg) -> ResultType<Self>
|
||||||
let i;
|
where
|
||||||
if cfg!(feature = "VP8") {
|
Self: Sized,
|
||||||
i = match config.codec {
|
{
|
||||||
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
match cfg {
|
||||||
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
crate::coder::EncoderCfg::VPX(config) => {
|
||||||
};
|
let i;
|
||||||
} else {
|
if cfg!(feature = "VP8") {
|
||||||
i = call_vpx_ptr!(vpx_codec_vp9_cx());
|
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")),
|
||||||
}
|
}
|
||||||
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 num_threads == 0 {
|
|
||||||
num_cpus::get() as _
|
|
||||||
} else {
|
|
||||||
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 == VideoCodecId::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 _,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
pub fn encode(&mut self, pts: i64, data: &[u8], stride_align: usize) -> Result<EncodeFrames> {
|
||||||
assert!(2 * data.len() >= 3 * self.width * self.height);
|
assert!(2 * data.len() >= 3 * self.width * self.height);
|
||||||
|
|
||||||
@ -238,9 +279,31 @@ impl Encoder {
|
|||||||
iter: ptr::null(),
|
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 Encoder {
|
impl Drop for VpxEncoder {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let result = vpx_codec_destroy(&mut self.ctx);
|
let result = vpx_codec_destroy(&mut self.ctx);
|
||||||
@ -262,7 +325,7 @@ pub struct EncodeFrame<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Config {
|
pub struct VpxEncoderConfig {
|
||||||
/// The width (in pixels).
|
/// The width (in pixels).
|
||||||
pub width: c_uint,
|
pub width: c_uint,
|
||||||
/// The height (in pixels).
|
/// The height (in pixels).
|
||||||
@ -272,10 +335,17 @@ pub struct Config {
|
|||||||
/// The target bitrate (in kilobits per second).
|
/// The target bitrate (in kilobits per second).
|
||||||
pub bitrate: c_uint,
|
pub bitrate: c_uint,
|
||||||
/// The codec
|
/// The codec
|
||||||
pub codec: VideoCodecId,
|
pub codec: VpxVideoCodecId,
|
||||||
pub rc_min_quantizer: u32,
|
pub rc_min_quantizer: u32,
|
||||||
pub rc_max_quantizer: u32,
|
pub rc_max_quantizer: u32,
|
||||||
pub speed: i32,
|
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> {
|
pub struct EncodeFrames<'a> {
|
||||||
@ -306,31 +376,31 @@ impl<'a> Iterator for EncodeFrames<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decoder {
|
impl VpxDecoder {
|
||||||
/// Create a new decoder
|
/// Create a new decoder
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// The function may fail if the underlying libvpx does not provide
|
/// The function may fail if the underlying libvpx does not provide
|
||||||
/// the VP9 decoder.
|
/// the VP9 decoder.
|
||||||
pub fn new(codec: VideoCodecId, num_threads: u32) -> Result<Self> {
|
pub fn new(config: VpxDecoderConfig) -> Result<Self> {
|
||||||
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
|
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
|
||||||
// cause UB if uninitialized.
|
// cause UB if uninitialized.
|
||||||
let i;
|
let i;
|
||||||
if cfg!(feature = "VP8") {
|
if cfg!(feature = "VP8") {
|
||||||
i = match codec {
|
i = match config.codec {
|
||||||
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
||||||
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
i = call_vpx_ptr!(vpx_codec_vp9_dx());
|
i = call_vpx_ptr!(vpx_codec_vp9_dx());
|
||||||
}
|
}
|
||||||
let mut ctx = Default::default();
|
let mut ctx = Default::default();
|
||||||
let cfg = vpx_codec_dec_cfg_t {
|
let cfg = vpx_codec_dec_cfg_t {
|
||||||
threads: if num_threads == 0 {
|
threads: if config.num_threads == 0 {
|
||||||
num_cpus::get() as _
|
num_cpus::get() as _
|
||||||
} else {
|
} else {
|
||||||
num_threads
|
config.num_threads
|
||||||
},
|
},
|
||||||
w: 0,
|
w: 0,
|
||||||
h: 0,
|
h: 0,
|
||||||
@ -405,7 +475,7 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Decoder {
|
impl Drop for VpxDecoder {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let result = vpx_codec_destroy(&mut self.ctx);
|
let result = vpx_codec_destroy(&mut self.ctx);
|
||||||
|
276
libs/scrap/src/common/coder.rs
Normal file
276
libs/scrap/src/common/coder.rs
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::codec::*;
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
use crate::hwcodec::*;
|
||||||
|
|
||||||
|
use hbb_common::{
|
||||||
|
anyhow::anyhow,
|
||||||
|
message_proto::{video_frame, Message, VP9s, VideoCodecState},
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
use hbb_common::{
|
||||||
|
lazy_static, log,
|
||||||
|
message_proto::{H264s, H265s},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref VIDEO_CODEC_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HwEncoderConfig {
|
||||||
|
pub codec_name: String,
|
||||||
|
pub fps: i32,
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EncoderCfg {
|
||||||
|
VPX(VpxEncoderConfig),
|
||||||
|
HW(HwEncoderConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait EncoderApi {
|
||||||
|
fn new(cfg: EncoderCfg) -> ResultType<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message>;
|
||||||
|
|
||||||
|
fn use_yuv(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DecoderCfg {
|
||||||
|
pub vpx: VpxDecoderConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Encoder {
|
||||||
|
pub codec: Box<dyn EncoderApi>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Encoder {
|
||||||
|
type Target = Box<dyn EncoderApi>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.codec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Encoder {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.codec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Decoder {
|
||||||
|
vpx: VpxDecoder,
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
hw: Arc<Mutex<HwDecoderInstance>>,
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
i420: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder {
|
||||||
|
pub fn new(config: EncoderCfg) -> ResultType<Encoder> {
|
||||||
|
match config {
|
||||||
|
EncoderCfg::VPX(_) => Ok(Encoder {
|
||||||
|
codec: Box::new(VpxEncoder::new(config)?),
|
||||||
|
}),
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
EncoderCfg::HW(_) => Ok(Encoder {
|
||||||
|
codec: Box::new(HwEncoder::new(config)?),
|
||||||
|
}),
|
||||||
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
|
_ => Err(anyhow!("unsupported encoder type")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub fn update_video_encoder(id: i32, decoder: Option<VideoCodecState>) {
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
{
|
||||||
|
let mut states = VIDEO_CODEC_STATES.lock().unwrap();
|
||||||
|
match decoder {
|
||||||
|
Some(decoder) => states.insert(id, decoder),
|
||||||
|
None => states.remove(&id),
|
||||||
|
};
|
||||||
|
let (encoder_h264, encoder_h265) = HwEncoder::best();
|
||||||
|
let mut enabled_h264 = encoder_h264.is_some();
|
||||||
|
let mut enabled_h265 = encoder_h265.is_some();
|
||||||
|
let mut score_vpx = 90;
|
||||||
|
let mut score_h264 = encoder_h264.as_ref().map_or(0, |c| c.score);
|
||||||
|
let mut score_h265 = encoder_h265.as_ref().map_or(0, |c| c.score);
|
||||||
|
|
||||||
|
for state in states.iter() {
|
||||||
|
enabled_h264 = enabled_h264 && state.1.H264;
|
||||||
|
enabled_h265 = enabled_h265 && state.1.H265;
|
||||||
|
score_vpx += state.1.ScoreVpx;
|
||||||
|
score_h264 += state.1.ScoreH264;
|
||||||
|
score_h265 += state.1.ScoreH265;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_encoder_name = HwEncoder::current_name();
|
||||||
|
if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 {
|
||||||
|
*current_encoder_name.lock().unwrap() = Some(encoder_h265.unwrap().name);
|
||||||
|
} else if enabled_h264 && score_h264 >= score_vpx && score_h264 >= score_h265 {
|
||||||
|
*current_encoder_name.lock().unwrap() = Some(encoder_h264.unwrap().name);
|
||||||
|
} else {
|
||||||
|
*current_encoder_name.lock().unwrap() = None;
|
||||||
|
}
|
||||||
|
if states.len() > 0 {
|
||||||
|
log::info!(
|
||||||
|
"connection count:{}, h264:{}, h265:{}, score: vpx({}), h264({}), h265({}), set current encoder name {:?}",
|
||||||
|
states.len(),
|
||||||
|
enabled_h264,
|
||||||
|
enabled_h265,
|
||||||
|
score_vpx,
|
||||||
|
score_h264,
|
||||||
|
score_h265,
|
||||||
|
current_encoder_name.lock().unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
|
{
|
||||||
|
let _ = id;
|
||||||
|
let _ = decoder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn current_hw_encoder_name() -> Option<String> {
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
return HwEncoder::current_name().lock().unwrap().clone();
|
||||||
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
// TODO
|
||||||
|
pub fn video_codec_state() -> VideoCodecState {
|
||||||
|
let mut state = VideoCodecState::default();
|
||||||
|
state.ScoreVpx = 90;
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
{
|
||||||
|
let hw = HwDecoder::instance();
|
||||||
|
state.H264 = hw.lock().unwrap().h264.is_some();
|
||||||
|
state.ScoreH264 = hw.lock().unwrap().h264.as_ref().map_or(0, |d| d.info.score);
|
||||||
|
state.H265 = hw.lock().unwrap().h265.is_some();
|
||||||
|
state.ScoreH265 = hw.lock().unwrap().h265.as_ref().map_or(0, |d| d.info.score);
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(config: DecoderCfg) -> Decoder {
|
||||||
|
let vpx = VpxDecoder::new(config.vpx).unwrap();
|
||||||
|
Decoder {
|
||||||
|
vpx,
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
hw: HwDecoder::instance(),
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
i420: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_video_frame(
|
||||||
|
&mut self,
|
||||||
|
frame: &video_frame::Union,
|
||||||
|
rgb: &mut Vec<u8>,
|
||||||
|
) -> ResultType<bool> {
|
||||||
|
match frame {
|
||||||
|
video_frame::Union::vp9s(vp9s) => {
|
||||||
|
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, rgb)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
video_frame::Union::h264s(h264s) => {
|
||||||
|
if let Some(decoder) = &mut self.hw.lock().unwrap().h264 {
|
||||||
|
Decoder::handle_h264s_video_frame(decoder, h264s, rgb, &mut self.i420)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("don't support h264!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
video_frame::Union::h265s(h265s) => {
|
||||||
|
if let Some(decoder) = &mut self.hw.lock().unwrap().h265 {
|
||||||
|
Decoder::handle_h265s_video_frame(decoder, h265s, rgb, &mut self.i420)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("don't support h265!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("unsupported video frame type!")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_vp9s_video_frame(
|
||||||
|
decoder: &mut VpxDecoder,
|
||||||
|
vp9s: &VP9s,
|
||||||
|
rgb: &mut Vec<u8>,
|
||||||
|
) -> ResultType<bool> {
|
||||||
|
let mut last_frame = Image::new();
|
||||||
|
for vp9 in vp9s.frames.iter() {
|
||||||
|
for frame in decoder.decode(&vp9.data)? {
|
||||||
|
drop(last_frame);
|
||||||
|
last_frame = frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for frame in decoder.flush()? {
|
||||||
|
drop(last_frame);
|
||||||
|
last_frame = frame;
|
||||||
|
}
|
||||||
|
if last_frame.is_null() {
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
last_frame.rgb(1, true, rgb);
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
fn handle_h264s_video_frame(
|
||||||
|
decoder: &mut HwDecoder,
|
||||||
|
h264s: &H264s,
|
||||||
|
rgb: &mut Vec<u8>,
|
||||||
|
i420: &mut Vec<u8>,
|
||||||
|
) -> ResultType<bool> {
|
||||||
|
let mut ret = false;
|
||||||
|
for h264 in h264s.h264s.iter() {
|
||||||
|
for image in decoder.decode(&h264.data)? {
|
||||||
|
// TODO: just process the last frame
|
||||||
|
if image.bgra(rgb, i420).is_ok() {
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
fn handle_h265s_video_frame(
|
||||||
|
decoder: &mut HwDecoder,
|
||||||
|
h265s: &H265s,
|
||||||
|
rgb: &mut Vec<u8>,
|
||||||
|
i420: &mut Vec<u8>,
|
||||||
|
) -> ResultType<bool> {
|
||||||
|
let mut ret = false;
|
||||||
|
for h265 in h265s.h265s.iter() {
|
||||||
|
for image in decoder.decode(&h265.data)? {
|
||||||
|
// TODO: just process the last frame
|
||||||
|
if image.bgra(rgb, i420).is_ok() {
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
}
|
@ -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 _,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
290
libs/scrap/src/common/hwcodec.rs
Normal file
290
libs/scrap/src/common/hwcodec.rs
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
use crate::{
|
||||||
|
coder::{EncoderApi, EncoderCfg},
|
||||||
|
hw, HW_STRIDE_ALIGN,
|
||||||
|
};
|
||||||
|
use hbb_common::{
|
||||||
|
anyhow::{anyhow, Context},
|
||||||
|
lazy_static, log,
|
||||||
|
message_proto::{H264s, H265s, Message, VideoFrame, H264, H265},
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
use hwcodec::{
|
||||||
|
decode::{DecodeContext, DecodeFrame, Decoder},
|
||||||
|
encode::{EncodeContext, EncodeFrame, Encoder},
|
||||||
|
ffmpeg::{CodecInfo, DataFormat},
|
||||||
|
AVPixelFormat,
|
||||||
|
};
|
||||||
|
use std::sync::{Arc, Mutex, Once};
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref HW_ENCODER_NAME: Arc<Mutex<Option<String>>> = Default::default();
|
||||||
|
static ref HW_DECODER_INSTANCE: Arc<Mutex<HwDecoderInstance>> = Arc::new(Mutex::new(HwDecoderInstance {
|
||||||
|
h264: None,
|
||||||
|
h265: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P;
|
||||||
|
|
||||||
|
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 ctx = EncodeContext {
|
||||||
|
name: config.codec_name.clone(),
|
||||||
|
fps: config.fps as _,
|
||||||
|
width: config.width as _,
|
||||||
|
height: config.height as _,
|
||||||
|
pixfmt: DEFAULT_PIXFMT,
|
||||||
|
align: HW_STRIDE_ALIGN as _,
|
||||||
|
};
|
||||||
|
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 {
|
||||||
|
pub fn best() -> (Option<CodecInfo>, Option<CodecInfo>) {
|
||||||
|
let ctx = EncodeContext {
|
||||||
|
name: String::from(""),
|
||||||
|
fps: 30,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
pixfmt: DEFAULT_PIXFMT,
|
||||||
|
align: HW_STRIDE_ALIGN as _,
|
||||||
|
};
|
||||||
|
CodecInfo::score(Encoder::avaliable_encoders(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HwDecoder {
|
||||||
|
decoder: Decoder,
|
||||||
|
pub info: CodecInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HwDecoderInstance {
|
||||||
|
pub h264: Option<HwDecoder>,
|
||||||
|
pub h265: Option<HwDecoder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HwDecoder {
|
||||||
|
pub fn instance() -> Arc<Mutex<HwDecoderInstance>> {
|
||||||
|
static ONCE: Once = Once::new();
|
||||||
|
// TODO: different process
|
||||||
|
ONCE.call_once(|| {
|
||||||
|
let avaliable = Decoder::avaliable_decoders();
|
||||||
|
let mut decoders = vec![];
|
||||||
|
for decoder in avaliable {
|
||||||
|
if let Ok(d) = HwDecoder::new(decoder) {
|
||||||
|
decoders.push(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut h264: Option<HwDecoder> = None;
|
||||||
|
let mut h265: Option<HwDecoder> = None;
|
||||||
|
for decoder in decoders {
|
||||||
|
match decoder.info.format {
|
||||||
|
DataFormat::H264 => match &h264 {
|
||||||
|
Some(old) => {
|
||||||
|
if decoder.info.score > old.info.score {
|
||||||
|
h264 = Some(decoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => h264 = Some(decoder),
|
||||||
|
},
|
||||||
|
DataFormat::H265 => match &h265 {
|
||||||
|
Some(old) => {
|
||||||
|
if decoder.info.score > old.info.score {
|
||||||
|
h265 = Some(decoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => h265 = Some(decoder),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if h264.is_some() {
|
||||||
|
log::info!("h264 decoder:{:?}", h264.as_ref().unwrap().info);
|
||||||
|
}
|
||||||
|
if h265.is_some() {
|
||||||
|
log::info!("h265 decoder:{:?}", h265.as_ref().unwrap().info);
|
||||||
|
}
|
||||||
|
HW_DECODER_INSTANCE.lock().unwrap().h264 = h264;
|
||||||
|
HW_DECODER_INSTANCE.lock().unwrap().h265 = h265;
|
||||||
|
});
|
||||||
|
HW_DECODER_INSTANCE.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
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(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,17 +28,19 @@ cfg_if! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
|
pub mod coder;
|
||||||
mod convert;
|
mod convert;
|
||||||
|
#[cfg(feature = "hwcodec")]
|
||||||
|
pub mod hwcodec;
|
||||||
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;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn would_block_if_equal(old: &mut Vec<u128>, b: &[u8]) -> std::io::Result<()> {
|
pub fn would_block_if_equal(old: &mut Vec<u128>, b: &[u8]) -> std::io::Result<()> {
|
||||||
let b = unsafe {
|
let b = unsafe { std::slice::from_raw_parts::<u128>(b.as_ptr() as _, b.len() / 16) };
|
||||||
std::slice::from_raw_parts::<u128>(b.as_ptr() as _, b.len() / 16)
|
|
||||||
};
|
|
||||||
if b == &old[..] {
|
if b == &old[..] {
|
||||||
return Err(std::io::ErrorKind::WouldBlock.into());
|
return Err(std::io::ErrorKind::WouldBlock.into());
|
||||||
}
|
}
|
||||||
|
@ -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::{
|
||||||
|
coder::{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,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -951,6 +945,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 {
|
||||||
|
@ -365,6 +365,7 @@ impl Connection {
|
|||||||
video_service::notify_video_frame_feched(id, None);
|
video_service::notify_video_frame_feched(id, None);
|
||||||
super::video_service::update_test_latency(id, 0);
|
super::video_service::update_test_latency(id, 0);
|
||||||
super::video_service::update_image_quality(id, None);
|
super::video_service::update_image_quality(id, None);
|
||||||
|
scrap::coder::Encoder::update_video_encoder(id, None);
|
||||||
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);
|
||||||
}
|
}
|
||||||
@ -1186,6 +1187,20 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add option
|
||||||
|
if let Some(q) = o.video_codec_state.clone().take() {
|
||||||
|
scrap::coder::Encoder::update_video_encoder(self.inner.id(), Some(q));
|
||||||
|
} else {
|
||||||
|
scrap::coder::Encoder::update_video_encoder(
|
||||||
|
self.inner.id(),
|
||||||
|
Some(VideoCodecState {
|
||||||
|
H264: false,
|
||||||
|
H265: false,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_close(&mut self, reason: &str, lock: bool) {
|
fn on_close(&mut self, reason: &str, lock: bool) {
|
||||||
|
@ -26,7 +26,11 @@ use hbb_common::tokio::{
|
|||||||
Mutex as TokioMutex,
|
Mutex as TokioMutex,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, VideoCodecId, STRIDE_ALIGN};
|
use scrap::{
|
||||||
|
codec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||||
|
coder::{Encoder, EncoderCfg, HwEncoderConfig},
|
||||||
|
Capturer, Display,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
io::ErrorKind::WouldBlock,
|
io::ErrorKind::WouldBlock,
|
||||||
@ -172,27 +176,39 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
num_cpus::get_physical(),
|
num_cpus::get_physical(),
|
||||||
num_cpus::get(),
|
num_cpus::get(),
|
||||||
);
|
);
|
||||||
// Capturer object is expensive, avoiding to create it frequently.
|
|
||||||
let mut c = Capturer::new(display, true).with_context(|| "Failed to create capturer")?;
|
|
||||||
|
|
||||||
let q = get_image_quality();
|
let q = get_image_quality();
|
||||||
let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
|
let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
|
||||||
log::info!("bitrate={}, rc_min_quantizer={}", bitrate, rc_min_quantizer);
|
log::info!("bitrate={}, rc_min_quantizer={}", bitrate, rc_min_quantizer);
|
||||||
let cfg = Config {
|
|
||||||
width: width as _,
|
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
||||||
height: height as _,
|
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
||||||
timebase: [1, 1000], // Output timestamp precision
|
codec_name,
|
||||||
bitrate,
|
fps,
|
||||||
codec: VideoCodecId::VP9,
|
width,
|
||||||
rc_min_quantizer,
|
height,
|
||||||
rc_max_quantizer,
|
}),
|
||||||
speed,
|
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 vpx;
|
|
||||||
match Encoder::new(&cfg, (num_cpus::get() / 2) as _) {
|
let mut encoder;
|
||||||
Ok(x) => vpx = x,
|
match Encoder::new(encoder_cfg) {
|
||||||
|
Ok(x) => encoder = x,
|
||||||
Err(err) => bail!("Failed to create encoder: {}", err),
|
Err(err) => bail!("Failed to create encoder: {}", err),
|
||||||
}
|
}
|
||||||
|
// Capturer object is expensive, avoiding to create it frequently.
|
||||||
|
let mut c =
|
||||||
|
Capturer::new(display, encoder.use_yuv()).with_context(|| "Failed to create capturer")?;
|
||||||
|
|
||||||
if *SWITCH.lock().unwrap() {
|
if *SWITCH.lock().unwrap() {
|
||||||
log::debug!("Broadcasting display switch");
|
log::debug!("Broadcasting display switch");
|
||||||
@ -277,7 +293,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)]
|
||||||
{
|
{
|
||||||
@ -333,35 +349,12 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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()
|
|
||||||
});
|
|
||||||
vf.timestamp = crate::common::get_time();
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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
|
||||||
@ -372,20 +365,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