commit
e80a2d8999
@ -1258,9 +1258,6 @@ class _DisplayState extends State<_Display> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget codec(BuildContext context) {
|
Widget codec(BuildContext context) {
|
||||||
if (!bind.mainHasHwcodec()) {
|
|
||||||
return Offstage();
|
|
||||||
}
|
|
||||||
final key = 'codec-preference';
|
final key = 'codec-preference';
|
||||||
onChanged(String value) async {
|
onChanged(String value) async {
|
||||||
await bind.mainSetUserDefaultOption(key: key, value: value);
|
await bind.mainSetUserDefaultOption(key: key, value: value);
|
||||||
@ -1268,28 +1265,45 @@ class _DisplayState extends State<_Display> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
||||||
|
var hwRadios = [];
|
||||||
|
try {
|
||||||
|
final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings());
|
||||||
|
final h264 = codecsJson['h264'] ?? false;
|
||||||
|
final h265 = codecsJson['h265'] ?? false;
|
||||||
|
if (h264) {
|
||||||
|
hwRadios.add(_Radio(context,
|
||||||
|
value: 'h264',
|
||||||
|
groupValue: groupValue,
|
||||||
|
label: 'H264',
|
||||||
|
onChanged: onChanged));
|
||||||
|
}
|
||||||
|
if (h265) {
|
||||||
|
hwRadios.add(_Radio(context,
|
||||||
|
value: 'h265',
|
||||||
|
groupValue: groupValue,
|
||||||
|
label: 'H265',
|
||||||
|
onChanged: onChanged));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("failed to parse supported hwdecodings, err=$e");
|
||||||
|
}
|
||||||
return _Card(title: 'Default Codec', children: [
|
return _Card(title: 'Default Codec', children: [
|
||||||
_Radio(context,
|
_Radio(context,
|
||||||
value: 'auto',
|
value: 'auto',
|
||||||
groupValue: groupValue,
|
groupValue: groupValue,
|
||||||
label: 'Auto',
|
label: 'Auto',
|
||||||
onChanged: onChanged),
|
onChanged: onChanged),
|
||||||
|
_Radio(context,
|
||||||
|
value: 'vp8',
|
||||||
|
groupValue: groupValue,
|
||||||
|
label: 'VP8',
|
||||||
|
onChanged: onChanged),
|
||||||
_Radio(context,
|
_Radio(context,
|
||||||
value: 'vp9',
|
value: 'vp9',
|
||||||
groupValue: groupValue,
|
groupValue: groupValue,
|
||||||
label: 'VP9',
|
label: 'VP9',
|
||||||
onChanged: onChanged),
|
onChanged: onChanged),
|
||||||
_Radio(context,
|
...hwRadios,
|
||||||
value: 'h264',
|
|
||||||
groupValue: groupValue,
|
|
||||||
label: 'H264',
|
|
||||||
onChanged: onChanged),
|
|
||||||
_Radio(context,
|
|
||||||
value: 'h265',
|
|
||||||
groupValue: groupValue,
|
|
||||||
label: 'H265',
|
|
||||||
onChanged: onChanged),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1351,29 +1351,30 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
|
|
||||||
codec() {
|
codec() {
|
||||||
return futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
final supportedHwcodec =
|
final alternativeCodecs =
|
||||||
await bind.sessionSupportedHwcodec(id: widget.id);
|
await bind.sessionAlternativeCodecs(id: widget.id);
|
||||||
final codecPreference =
|
final codecPreference =
|
||||||
await bind.sessionGetOption(id: widget.id, arg: 'codec-preference') ??
|
await bind.sessionGetOption(id: widget.id, arg: 'codec-preference') ??
|
||||||
'';
|
'';
|
||||||
return {
|
return {
|
||||||
'supportedHwcodec': supportedHwcodec,
|
'alternativeCodecs': alternativeCodecs,
|
||||||
'codecPreference': codecPreference
|
'codecPreference': codecPreference
|
||||||
};
|
};
|
||||||
}(), hasData: (data) {
|
}(), hasData: (data) {
|
||||||
final List<bool> codecs = [];
|
final List<bool> codecs = [];
|
||||||
try {
|
try {
|
||||||
final Map codecsJson = jsonDecode(data['supportedHwcodec']);
|
final Map codecsJson = jsonDecode(data['alternativeCodecs']);
|
||||||
|
final vp8 = codecsJson['vp8'] ?? false;
|
||||||
final h264 = codecsJson['h264'] ?? false;
|
final h264 = codecsJson['h264'] ?? false;
|
||||||
final h265 = codecsJson['h265'] ?? false;
|
final h265 = codecsJson['h265'] ?? false;
|
||||||
|
codecs.add(vp8);
|
||||||
codecs.add(h264);
|
codecs.add(h264);
|
||||||
codecs.add(h265);
|
codecs.add(h265);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Show Codec Preference err=$e");
|
debugPrint("Show Codec Preference err=$e");
|
||||||
}
|
}
|
||||||
final visible = bind.mainHasHwcodec() &&
|
final visible =
|
||||||
codecs.length == 2 &&
|
codecs.length == 3 && (codecs[0] || codecs[1] || codecs[2]);
|
||||||
(codecs[0] || codecs[1]);
|
|
||||||
if (!visible) return Offstage();
|
if (!visible) return Offstage();
|
||||||
final groupValue = data['codecPreference'] as String;
|
final groupValue = data['codecPreference'] as String;
|
||||||
onChanged(String? value) async {
|
onChanged(String? value) async {
|
||||||
@ -1394,6 +1395,13 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
),
|
),
|
||||||
|
_RadioMenuButton<String>(
|
||||||
|
child: Text(translate('VP8')),
|
||||||
|
value: 'vp8',
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: codecs[0] ? onChanged : null,
|
||||||
|
ffi: widget.ffi,
|
||||||
|
),
|
||||||
_RadioMenuButton<String>(
|
_RadioMenuButton<String>(
|
||||||
child: Text(translate('VP9')),
|
child: Text(translate('VP9')),
|
||||||
value: 'vp9',
|
value: 'vp9',
|
||||||
@ -1405,14 +1413,14 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
child: Text(translate('H264')),
|
child: Text(translate('H264')),
|
||||||
value: 'h264',
|
value: 'h264',
|
||||||
groupValue: groupValue,
|
groupValue: groupValue,
|
||||||
onChanged: codecs[0] ? onChanged : null,
|
onChanged: codecs[1] ? onChanged : null,
|
||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
),
|
),
|
||||||
_RadioMenuButton<String>(
|
_RadioMenuButton<String>(
|
||||||
child: Text(translate('H265')),
|
child: Text(translate('H265')),
|
||||||
value: 'h265',
|
value: 'h265',
|
||||||
groupValue: groupValue,
|
groupValue: groupValue,
|
||||||
onChanged: codecs[1] ? onChanged : null,
|
onChanged: codecs[2] ? onChanged : null,
|
||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
@ -970,17 +970,17 @@ void showOptions(
|
|||||||
final perms = gFFI.ffiModel.permissions;
|
final perms = gFFI.ffiModel.permissions;
|
||||||
final hasHwcodec = bind.mainHasHwcodec();
|
final hasHwcodec = bind.mainHasHwcodec();
|
||||||
final List<bool> codecs = [];
|
final List<bool> codecs = [];
|
||||||
if (hasHwcodec) {
|
try {
|
||||||
try {
|
final Map codecsJson =
|
||||||
final Map codecsJson =
|
jsonDecode(await bind.sessionAlternativeCodecs(id: id));
|
||||||
jsonDecode(await bind.sessionSupportedHwcodec(id: id));
|
final vp8 = codecsJson['vp8'] ?? false;
|
||||||
final h264 = codecsJson['h264'] ?? false;
|
final h264 = codecsJson['h264'] ?? false;
|
||||||
final h265 = codecsJson['h265'] ?? false;
|
final h265 = codecsJson['h265'] ?? false;
|
||||||
codecs.add(h264);
|
codecs.add(vp8);
|
||||||
codecs.add(h265);
|
codecs.add(h264);
|
||||||
} catch (e) {
|
codecs.add(h265);
|
||||||
debugPrint("Show Codec Preference err=$e");
|
} catch (e) {
|
||||||
}
|
debugPrint("Show Codec Preference err=$e");
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogManager.show((setState, close) {
|
dialogManager.show((setState, close) {
|
||||||
@ -1041,15 +1041,16 @@ void showOptions(
|
|||||||
const Divider(color: MyTheme.border)
|
const Divider(color: MyTheme.border)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (hasHwcodec && codecs.length == 2 && (codecs[0] || codecs[1])) {
|
if (codecs.length == 3 && (codecs[0] || codecs[1] || codecs[2])) {
|
||||||
radios.addAll([
|
radios.add(getRadio(translate('Auto'), 'auto', codec, setCodec));
|
||||||
getRadio(translate('Auto'), 'auto', codec, setCodec),
|
|
||||||
getRadio('VP9', 'vp9', codec, setCodec),
|
|
||||||
]);
|
|
||||||
if (codecs[0]) {
|
if (codecs[0]) {
|
||||||
|
radios.add(getRadio('VP8', 'vp8', codec, setCodec));
|
||||||
|
}
|
||||||
|
radios.add(getRadio('VP9', 'vp9', codec, setCodec));
|
||||||
|
if (codecs[1]) {
|
||||||
radios.add(getRadio('H264', 'h264', codec, setCodec));
|
radios.add(getRadio('H264', 'h264', codec, setCodec));
|
||||||
}
|
}
|
||||||
if (codecs[1]) {
|
if (codecs[2]) {
|
||||||
radios.add(getRadio('H265', 'h265', codec, setCodec));
|
radios.add(getRadio('H265', 'h265', codec, setCodec));
|
||||||
}
|
}
|
||||||
radios.add(const Divider(color: MyTheme.border));
|
radios.add(const Divider(color: MyTheme.border));
|
||||||
|
@ -24,6 +24,7 @@ message VideoFrame {
|
|||||||
YUV yuv = 8;
|
YUV yuv = 8;
|
||||||
EncodedVideoFrames h264s = 10;
|
EncodedVideoFrames h264s = 10;
|
||||||
EncodedVideoFrames h265s = 11;
|
EncodedVideoFrames h265s = 11;
|
||||||
|
EncodedVideoFrames vp8s = 12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +77,7 @@ message Features {
|
|||||||
message SupportedEncoding {
|
message SupportedEncoding {
|
||||||
bool h264 = 1;
|
bool h264 = 1;
|
||||||
bool h265 = 2;
|
bool h265 = 2;
|
||||||
|
bool vp8 = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PeerInfo {
|
message PeerInfo {
|
||||||
@ -459,18 +461,20 @@ enum ImageQuality {
|
|||||||
Best = 4;
|
Best = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message VideoCodecState {
|
message SupportedDecoding {
|
||||||
enum PreferCodec {
|
enum PreferCodec {
|
||||||
Auto = 0;
|
Auto = 0;
|
||||||
VPX = 1;
|
VP9 = 1;
|
||||||
H264 = 2;
|
H264 = 2;
|
||||||
H265 = 3;
|
H265 = 3;
|
||||||
|
VP8 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 score_vpx = 1;
|
int32 ability_vp9 = 1;
|
||||||
int32 score_h264 = 2;
|
int32 ability_h264 = 2;
|
||||||
int32 score_h265 = 3;
|
int32 ability_h265 = 3;
|
||||||
PreferCodec prefer = 4;
|
PreferCodec prefer = 4;
|
||||||
|
int32 ability_vp8 = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message OptionMessage {
|
message OptionMessage {
|
||||||
@ -488,7 +492,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;
|
SupportedDecoding supported_decoding = 10;
|
||||||
int32 custom_fps = 11;
|
int32 custom_fps = 11;
|
||||||
BoolOption disable_keyboard = 12;
|
BoolOption disable_keyboard = 12;
|
||||||
}
|
}
|
||||||
|
@ -917,7 +917,8 @@ impl PeerConfig {
|
|||||||
store = store || store2;
|
store = store || store2;
|
||||||
for opt in ["rdp_password", "os-password"] {
|
for opt in ["rdp_password", "os-password"] {
|
||||||
if let Some(v) = config.options.get_mut(opt) {
|
if let Some(v) = config.options.get_mut(opt) {
|
||||||
let (encrypted, _, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
let (encrypted, _, store2) =
|
||||||
|
decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||||
*v = encrypted;
|
*v = encrypted;
|
||||||
store = store || store2;
|
store = store || store2;
|
||||||
}
|
}
|
||||||
@ -1356,7 +1357,7 @@ impl UserDefaultConfig {
|
|||||||
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
|
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
|
||||||
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
|
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
|
||||||
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
|
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
|
||||||
"codec-preference" => self.get_string(key, "auto", vec!["vp9", "h264", "h265"]),
|
"codec-preference" => self.get_string(key, "auto", vec!["vp8", "vp9", "h264", "h265"]),
|
||||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
|
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
|
||||||
"custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0),
|
"custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0),
|
||||||
_ => self
|
_ => self
|
||||||
|
@ -3,7 +3,8 @@ use hbb_common::env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV};
|
|||||||
use scrap::{
|
use scrap::{
|
||||||
codec::{EncoderApi, EncoderCfg},
|
codec::{EncoderApi, EncoderCfg},
|
||||||
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
|
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
|
||||||
VpxVideoCodecId, STRIDE_ALIGN,
|
VpxVideoCodecId::{self, *},
|
||||||
|
STRIDE_ALIGN,
|
||||||
};
|
};
|
||||||
use std::{io::Write, time::Instant};
|
use std::{io::Write, time::Instant};
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ fn main() {
|
|||||||
"benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}",
|
"benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}",
|
||||||
width, height, bitrate_k, args.flag_hw_pixfmt
|
width, height, bitrate_k, args.flag_hw_pixfmt
|
||||||
);
|
);
|
||||||
test_vp9(&yuvs, width, height, bitrate_k, yuv_count);
|
[VP8, VP9].map(|c| test_vpx(c, &yuvs, width, height, bitrate_k, yuv_count));
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
{
|
{
|
||||||
use hwcodec::AVPixelFormat;
|
use hwcodec::AVPixelFormat;
|
||||||
@ -57,7 +58,7 @@ fn main() {
|
|||||||
Pixfmt::I420 => AVPixelFormat::AV_PIX_FMT_YUV420P,
|
Pixfmt::I420 => AVPixelFormat::AV_PIX_FMT_YUV420P,
|
||||||
Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
|
Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
|
||||||
};
|
};
|
||||||
let yuvs = hw::vp9_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
|
let yuvs = hw::vpx_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
|
||||||
hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt);
|
hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,13 +88,20 @@ fn capture_yuv(yuv_count: usize) -> (Vec<Vec<u8>>, usize, usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize, yuv_count: usize) {
|
fn test_vpx(
|
||||||
|
codec_id: VpxVideoCodecId,
|
||||||
|
yuvs: &Vec<Vec<u8>>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
bitrate_k: usize,
|
||||||
|
yuv_count: usize,
|
||||||
|
) {
|
||||||
let config = EncoderCfg::VPX(VpxEncoderConfig {
|
let config = EncoderCfg::VPX(VpxEncoderConfig {
|
||||||
width: width as _,
|
width: width as _,
|
||||||
height: height as _,
|
height: height as _,
|
||||||
timebase: [1, 1000],
|
timebase: [1, 1000],
|
||||||
bitrate: bitrate_k as _,
|
bitrate: bitrate_k as _,
|
||||||
codec: VpxVideoCodecId::VP9,
|
codec: codec_id,
|
||||||
num_threads: (num_cpus::get() / 2) as _,
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
});
|
});
|
||||||
let mut encoder = VpxEncoder::new(config).unwrap();
|
let mut encoder = VpxEncoder::new(config).unwrap();
|
||||||
@ -104,35 +112,43 @@ fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize,
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let _ = encoder.flush().unwrap();
|
let _ = encoder.flush().unwrap();
|
||||||
}
|
}
|
||||||
println!("vp9 encode: {:?}", start.elapsed() / yuv_count as _);
|
println!(
|
||||||
|
"{:?} encode: {:?}",
|
||||||
|
codec_id,
|
||||||
|
start.elapsed() / yuv_count as _
|
||||||
|
);
|
||||||
|
|
||||||
// prepare data separately
|
// prepare data separately
|
||||||
let mut vp9s = vec![];
|
let mut vpxs = vec![];
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for yuv in yuvs {
|
for yuv in yuvs {
|
||||||
for ref frame in encoder
|
for ref frame in encoder
|
||||||
.encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN)
|
.encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
{
|
{
|
||||||
vp9s.push(frame.data.to_vec());
|
vpxs.push(frame.data.to_vec());
|
||||||
}
|
}
|
||||||
for ref frame in encoder.flush().unwrap() {
|
for ref frame in encoder.flush().unwrap() {
|
||||||
vp9s.push(frame.data.to_vec());
|
vpxs.push(frame.data.to_vec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(vp9s.len(), yuv_count);
|
assert_eq!(vpxs.len(), yuv_count);
|
||||||
|
|
||||||
let mut decoder = VpxDecoder::new(VpxDecoderConfig {
|
let mut decoder = VpxDecoder::new(VpxDecoderConfig {
|
||||||
codec: VpxVideoCodecId::VP9,
|
codec: codec_id,
|
||||||
num_threads: (num_cpus::get() / 2) as _,
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for vp9 in vp9s {
|
for vpx in vpxs {
|
||||||
let _ = decoder.decode(&vp9);
|
let _ = decoder.decode(&vpx);
|
||||||
let _ = decoder.flush();
|
let _ = decoder.flush();
|
||||||
}
|
}
|
||||||
println!("vp9 decode: {:?}", start.elapsed() / yuv_count as _);
|
println!(
|
||||||
|
"{:?} decode: {:?}",
|
||||||
|
codec_id,
|
||||||
|
start.elapsed() / yuv_count as _
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
@ -267,7 +283,7 @@ mod hw {
|
|||||||
Some(info.clone()) == best.h264 || Some(info.clone()) == best.h265
|
Some(info.clone()) == best.h264 || Some(info.clone()) == best.h265
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vp9_yuv_to_hw_yuv(
|
pub fn vpx_yuv_to_hw_yuv(
|
||||||
yuvs: Vec<Vec<u8>>,
|
yuvs: Vec<Vec<u8>>,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
|
@ -50,6 +50,7 @@ impl crate::TraitCapturer for Capturer {
|
|||||||
|
|
||||||
pub enum Frame<'a> {
|
pub enum Frame<'a> {
|
||||||
RAW(&'a [u8]),
|
RAW(&'a [u8]),
|
||||||
|
VP8(&'a [u8]),
|
||||||
VP9(&'a [u8]),
|
VP9(&'a [u8]),
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
#[cfg(feature = "hwcodec")]
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11,30 +10,31 @@ use crate::hwcodec::*;
|
|||||||
use crate::mediacodec::{
|
use crate::mediacodec::{
|
||||||
MediaCodecDecoder, MediaCodecDecoders, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT,
|
MediaCodecDecoder, MediaCodecDecoders, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT,
|
||||||
};
|
};
|
||||||
use crate::{vpxcodec::*, ImageFormat};
|
use crate::{vpxcodec::*, CodecName, ImageFormat};
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
use hbb_common::sysinfo::{System, SystemExt};
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
anyhow::anyhow,
|
anyhow::anyhow,
|
||||||
|
config::PeerConfig,
|
||||||
log,
|
log,
|
||||||
message_proto::{video_frame, EncodedVideoFrames, Message, VideoCodecState},
|
message_proto::{
|
||||||
|
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message,
|
||||||
|
SupportedDecoding, SupportedEncoding,
|
||||||
|
},
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||||
use hbb_common::{
|
use hbb_common::{config::Config2, lazy_static};
|
||||||
config::{Config2, PeerConfig},
|
|
||||||
lazy_static,
|
|
||||||
message_proto::video_codec_state::PreferCodec,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "hwcodec")]
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref PEER_DECODER_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default();
|
static ref PEER_DECODINGS: Arc<Mutex<HashMap<i32, SupportedDecoding>>> = Default::default();
|
||||||
|
static ref CODEC_NAME: Arc<Mutex<CodecName>> = Arc::new(Mutex::new(CodecName::VP9));
|
||||||
}
|
}
|
||||||
const SCORE_VPX: i32 = 90;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HwEncoderConfig {
|
pub struct HwEncoderConfig {
|
||||||
pub codec_name: String,
|
pub name: String,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub bitrate: i32,
|
pub bitrate: i32,
|
||||||
@ -58,10 +58,6 @@ pub trait EncoderApi {
|
|||||||
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
|
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DecoderCfg {
|
|
||||||
pub vpx: VpxDecoderConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Encoder {
|
pub struct Encoder {
|
||||||
pub codec: Box<dyn EncoderApi>,
|
pub codec: Box<dyn EncoderApi>,
|
||||||
}
|
}
|
||||||
@ -81,7 +77,8 @@ impl DerefMut for Encoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Decoder {
|
pub struct Decoder {
|
||||||
vpx: VpxDecoder,
|
vp8: VpxDecoder,
|
||||||
|
vp9: VpxDecoder,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
hw: HwDecoders,
|
hw: HwDecoders,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
@ -91,10 +88,10 @@ pub struct Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum EncoderUpdate {
|
pub enum EncodingUpdate {
|
||||||
State(VideoCodecState),
|
New(SupportedDecoding),
|
||||||
Remove,
|
Remove,
|
||||||
DisableHwIfNotExist,
|
NewOnlyVP9,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder {
|
impl Encoder {
|
||||||
@ -120,172 +117,156 @@ impl Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
pub fn update(id: i32, update: EncodingUpdate) {
|
||||||
pub fn update_video_encoder(id: i32, update: EncoderUpdate) {
|
let mut decodings = PEER_DECODINGS.lock().unwrap();
|
||||||
|
match update {
|
||||||
|
EncodingUpdate::New(decoding) => {
|
||||||
|
decodings.insert(id, decoding);
|
||||||
|
}
|
||||||
|
EncodingUpdate::Remove => {
|
||||||
|
decodings.remove(&id);
|
||||||
|
}
|
||||||
|
EncodingUpdate::NewOnlyVP9 => {
|
||||||
|
decodings.insert(
|
||||||
|
id,
|
||||||
|
SupportedDecoding {
|
||||||
|
ability_vp9: 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let vp8_useable = decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_vp8 > 0);
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut h264_name = None;
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut h265_name = None;
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
{
|
{
|
||||||
let mut states = PEER_DECODER_STATES.lock().unwrap();
|
let best = HwEncoder::best();
|
||||||
match update {
|
let h264_useable =
|
||||||
EncoderUpdate::State(state) => {
|
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h264 > 0);
|
||||||
states.insert(id, state);
|
let h265_useable =
|
||||||
}
|
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
|
||||||
EncoderUpdate::Remove => {
|
if h264_useable {
|
||||||
states.remove(&id);
|
h264_name = best.h264.map_or(None, |c| Some(c.name));
|
||||||
}
|
|
||||||
EncoderUpdate::DisableHwIfNotExist => {
|
|
||||||
if !states.contains_key(&id) {
|
|
||||||
states.insert(id, VideoCodecState::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let name = HwEncoder::current_name();
|
if h265_useable {
|
||||||
if states.len() > 0 {
|
h265_name = best.h265.map_or(None, |c| Some(c.name));
|
||||||
let best = HwEncoder::best();
|
|
||||||
let enabled_h264 = best.h264.is_some()
|
|
||||||
&& states.len() > 0
|
|
||||||
&& states.iter().all(|(_, s)| s.score_h264 > 0);
|
|
||||||
let enabled_h265 = best.h265.is_some()
|
|
||||||
&& states.len() > 0
|
|
||||||
&& states.iter().all(|(_, s)| s.score_h265 > 0);
|
|
||||||
|
|
||||||
// Preference first
|
|
||||||
let mut preference = PreferCodec::Auto;
|
|
||||||
let preferences: Vec<_> = states
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, s)| {
|
|
||||||
s.prefer == PreferCodec::VPX.into()
|
|
||||||
|| s.prefer == PreferCodec::H264.into() && enabled_h264
|
|
||||||
|| s.prefer == PreferCodec::H265.into() && enabled_h265
|
|
||||||
})
|
|
||||||
.map(|(_, s)| s.prefer)
|
|
||||||
.collect();
|
|
||||||
if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
|
|
||||||
preference = preferences[0].enum_value_or(PreferCodec::Auto);
|
|
||||||
}
|
|
||||||
|
|
||||||
match preference {
|
|
||||||
PreferCodec::VPX => *name.lock().unwrap() = None,
|
|
||||||
PreferCodec::H264 => {
|
|
||||||
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name))
|
|
||||||
}
|
|
||||||
PreferCodec::H265 => {
|
|
||||||
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name))
|
|
||||||
}
|
|
||||||
PreferCodec::Auto => {
|
|
||||||
// score encoder
|
|
||||||
let mut score_vpx = SCORE_VPX;
|
|
||||||
let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score);
|
|
||||||
let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score);
|
|
||||||
|
|
||||||
// score decoder
|
|
||||||
score_vpx += states.iter().map(|s| s.1.score_vpx).sum::<i32>();
|
|
||||||
if enabled_h264 {
|
|
||||||
score_h264 += states.iter().map(|s| s.1.score_h264).sum::<i32>();
|
|
||||||
}
|
|
||||||
if enabled_h265 {
|
|
||||||
score_h265 += states.iter().map(|s| s.1.score_h265).sum::<i32>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 {
|
|
||||||
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name));
|
|
||||||
} else if enabled_h264
|
|
||||||
&& score_h264 >= score_vpx
|
|
||||||
&& score_h264 >= score_h265
|
|
||||||
{
|
|
||||||
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name));
|
|
||||||
} else {
|
|
||||||
*name.lock().unwrap() = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!(
|
|
||||||
"connection count:{}, used preference:{:?}, encoder:{:?}",
|
|
||||||
states.len(),
|
|
||||||
preference,
|
|
||||||
name.lock().unwrap()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
*name.lock().unwrap() = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
|
||||||
{
|
let mut name = CODEC_NAME.lock().unwrap();
|
||||||
let _ = id;
|
let mut preference = PreferCodec::Auto;
|
||||||
let _ = update;
|
let preferences: Vec<_> = decodings
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, s)| {
|
||||||
|
s.prefer == PreferCodec::VP9.into()
|
||||||
|
|| s.prefer == PreferCodec::VP8.into() && vp8_useable
|
||||||
|
|| s.prefer == PreferCodec::H264.into() && h264_name.is_some()
|
||||||
|
|| s.prefer == PreferCodec::H265.into() && h265_name.is_some()
|
||||||
|
})
|
||||||
|
.map(|(_, s)| s.prefer)
|
||||||
|
.collect();
|
||||||
|
if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
|
||||||
|
preference = preferences[0].enum_value_or(PreferCodec::Auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut auto_codec = CodecName::VP9;
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
if vp8_useable && System::new_all().total_memory() <= 4 * 1024 * 1024 * 1024 {
|
||||||
|
// 4 Gb
|
||||||
|
auto_codec = CodecName::VP8
|
||||||
|
}
|
||||||
|
|
||||||
|
match preference {
|
||||||
|
PreferCodec::VP8 => *name = CodecName::VP8,
|
||||||
|
PreferCodec::VP9 => *name = CodecName::VP9,
|
||||||
|
PreferCodec::H264 => *name = h264_name.map_or(auto_codec, |c| CodecName::H264(c)),
|
||||||
|
PreferCodec::H265 => *name = h265_name.map_or(auto_codec, |c| CodecName::H265(c)),
|
||||||
|
PreferCodec::Auto => *name = auto_codec,
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"connection count:{}, used preference:{:?}, encoder:{:?}",
|
||||||
|
decodings.len(),
|
||||||
|
preference,
|
||||||
|
*name
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current_hw_encoder_name() -> Option<String> {
|
pub fn negotiated_codec() -> CodecName {
|
||||||
#[cfg(feature = "hwcodec")]
|
CODEC_NAME.lock().unwrap().clone()
|
||||||
if enable_hwcodec_option() {
|
|
||||||
return HwEncoder::current_name().lock().unwrap().clone();
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_encoding() -> (bool, bool) {
|
pub fn supported_encoding() -> SupportedEncoding {
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut encoding = SupportedEncoding {
|
||||||
|
vp8: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let best = HwEncoder::best();
|
let best = HwEncoder::best();
|
||||||
(
|
encoding.h264 = best.h264.is_some();
|
||||||
best.h264.as_ref().map_or(false, |c| c.score > 0),
|
encoding.h265 = best.h265.is_some();
|
||||||
best.h265.as_ref().map_or(false, |c| c.score > 0),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(false, false)
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
encoding
|
||||||
(false, false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decoder {
|
impl Decoder {
|
||||||
pub fn video_codec_state(_id: &str) -> VideoCodecState {
|
pub fn supported_decodings(id_for_perfer: Option<&str>) -> SupportedDecoding {
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut decoding = SupportedDecoding {
|
||||||
|
ability_vp8: 1,
|
||||||
|
ability_vp9: 1,
|
||||||
|
prefer: id_for_perfer
|
||||||
|
.map_or(PreferCodec::Auto, |id| Self::codec_preference(id))
|
||||||
|
.into(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let best = HwDecoder::best();
|
let best = HwDecoder::best();
|
||||||
return VideoCodecState {
|
decoding.ability_h264 = if best.h264.is_some() { 1 } else { 0 };
|
||||||
score_vpx: SCORE_VPX,
|
decoding.ability_h265 = if best.h265.is_some() { 1 } else { 0 };
|
||||||
score_h264: best.h264.map_or(0, |c| c.score),
|
|
||||||
score_h265: best.h265.map_or(0, |c| c.score),
|
|
||||||
prefer: Self::codec_preference(_id).into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let score_h264 = if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
decoding.ability_h264 =
|
||||||
92
|
if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
} else {
|
1
|
||||||
0
|
} else {
|
||||||
};
|
0
|
||||||
let score_h265 = if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
};
|
||||||
94
|
decoding.ability_h265 =
|
||||||
} else {
|
if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
0
|
1
|
||||||
};
|
} else {
|
||||||
return VideoCodecState {
|
0
|
||||||
score_vpx: SCORE_VPX,
|
};
|
||||||
score_h264,
|
|
||||||
score_h265,
|
|
||||||
prefer: Self::codec_preference(_id).into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
VideoCodecState {
|
|
||||||
score_vpx: SCORE_VPX,
|
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
|
decoding
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(config: DecoderCfg) -> Decoder {
|
pub fn new() -> Decoder {
|
||||||
let vpx = VpxDecoder::new(config.vpx).unwrap();
|
let vp8 = VpxDecoder::new(VpxDecoderConfig {
|
||||||
|
codec: VpxVideoCodecId::VP8,
|
||||||
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let vp9 = VpxDecoder::new(VpxDecoderConfig {
|
||||||
|
codec: VpxVideoCodecId::VP9,
|
||||||
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
Decoder {
|
Decoder {
|
||||||
vpx,
|
vp8,
|
||||||
|
vp9,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
hw: if enable_hwcodec_option() {
|
hw: if enable_hwcodec_option() {
|
||||||
HwDecoder::new_decoders()
|
HwDecoder::new_decoders()
|
||||||
@ -310,8 +291,11 @@ impl Decoder {
|
|||||||
rgb: &mut Vec<u8>,
|
rgb: &mut Vec<u8>,
|
||||||
) -> ResultType<bool> {
|
) -> ResultType<bool> {
|
||||||
match frame {
|
match frame {
|
||||||
|
video_frame::Union::Vp8s(vp8s) => {
|
||||||
|
Decoder::handle_vpxs_video_frame(&mut self.vp8, vp8s, fmt, rgb)
|
||||||
|
}
|
||||||
video_frame::Union::Vp9s(vp9s) => {
|
video_frame::Union::Vp9s(vp9s) => {
|
||||||
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, fmt, rgb)
|
Decoder::handle_vpxs_video_frame(&mut self.vp9, vp9s, fmt, rgb)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H264s(h264s) => {
|
video_frame::Union::H264s(h264s) => {
|
||||||
@ -349,15 +333,15 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_vp9s_video_frame(
|
fn handle_vpxs_video_frame(
|
||||||
decoder: &mut VpxDecoder,
|
decoder: &mut VpxDecoder,
|
||||||
vp9s: &EncodedVideoFrames,
|
vpxs: &EncodedVideoFrames,
|
||||||
fmt: (ImageFormat, usize),
|
fmt: (ImageFormat, usize),
|
||||||
rgb: &mut Vec<u8>,
|
rgb: &mut Vec<u8>,
|
||||||
) -> ResultType<bool> {
|
) -> ResultType<bool> {
|
||||||
let mut last_frame = Image::new();
|
let mut last_frame = Image::new();
|
||||||
for vp9 in vp9s.frames.iter() {
|
for vpx in vpxs.frames.iter() {
|
||||||
for frame in decoder.decode(&vp9.data)? {
|
for frame in decoder.decode(&vpx.data)? {
|
||||||
drop(last_frame);
|
drop(last_frame);
|
||||||
last_frame = frame;
|
last_frame = frame;
|
||||||
}
|
}
|
||||||
@ -408,14 +392,15 @@ impl Decoder {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
|
||||||
fn codec_preference(id: &str) -> PreferCodec {
|
fn codec_preference(id: &str) -> PreferCodec {
|
||||||
let codec = PeerConfig::load(id)
|
let codec = PeerConfig::load(id)
|
||||||
.options
|
.options
|
||||||
.get("codec-preference")
|
.get("codec-preference")
|
||||||
.map_or("".to_owned(), |c| c.to_owned());
|
.map_or("".to_owned(), |c| c.to_owned());
|
||||||
if codec == "vp9" {
|
if codec == "vp8" {
|
||||||
PreferCodec::VPX
|
PreferCodec::VP8
|
||||||
|
} else if codec == "vp9" {
|
||||||
|
PreferCodec::VP9
|
||||||
} else if codec == "h264" {
|
} else if codec == "h264" {
|
||||||
PreferCodec::H264
|
PreferCodec::H264
|
||||||
} else if codec == "h265" {
|
} else if codec == "h265" {
|
||||||
|
@ -7,7 +7,7 @@ use hbb_common::{
|
|||||||
anyhow::{anyhow, Context},
|
anyhow::{anyhow, Context},
|
||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
config::HwCodecConfig,
|
config::HwCodecConfig,
|
||||||
lazy_static, log,
|
log,
|
||||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
@ -19,11 +19,6 @@ use hwcodec::{
|
|||||||
Quality::{self, *},
|
Quality::{self, *},
|
||||||
RateControl::{self, *},
|
RateControl::{self, *},
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref HW_ENCODER_NAME: Arc<Mutex<Option<String>>> = Default::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
||||||
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
||||||
@ -49,7 +44,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
match cfg {
|
match cfg {
|
||||||
EncoderCfg::HW(config) => {
|
EncoderCfg::HW(config) => {
|
||||||
let ctx = EncodeContext {
|
let ctx = EncodeContext {
|
||||||
name: config.codec_name.clone(),
|
name: config.name.clone(),
|
||||||
width: config.width as _,
|
width: config.width as _,
|
||||||
height: config.height as _,
|
height: config.height as _,
|
||||||
pixfmt: DEFAULT_PIXFMT,
|
pixfmt: DEFAULT_PIXFMT,
|
||||||
@ -60,12 +55,12 @@ impl EncoderApi for HwEncoder {
|
|||||||
quality: DEFAULT_HW_QUALITY,
|
quality: DEFAULT_HW_QUALITY,
|
||||||
rc: DEFAULT_RC,
|
rc: DEFAULT_RC,
|
||||||
};
|
};
|
||||||
let format = match Encoder::format_from_name(config.codec_name.clone()) {
|
let format = match Encoder::format_from_name(config.name.clone()) {
|
||||||
Ok(format) => format,
|
Ok(format) => format,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(anyhow!(format!(
|
return Err(anyhow!(format!(
|
||||||
"failed to get format from name:{}",
|
"failed to get format from name:{}",
|
||||||
config.codec_name
|
config.name
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -133,10 +128,6 @@ impl HwEncoder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_name() -> Arc<Mutex<Option<String>>> {
|
|
||||||
HW_ENCODER_NAME.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(&mut self, bgra: &[u8]) -> ResultType<Vec<EncodeFrame>> {
|
pub fn encode(&mut self, bgra: &[u8]) -> ResultType<Vec<EncodeFrame>> {
|
||||||
match self.pixfmt {
|
match self.pixfmt {
|
||||||
AVPixelFormat::AV_PIX_FMT_YUV420P => hw::hw_bgra_to_i420(
|
AVPixelFormat::AV_PIX_FMT_YUV420P => hw::hw_bgra_to_i420(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub use self::vpxcodec::*;
|
pub use self::vpxcodec::*;
|
||||||
|
use hbb_common::message_proto::{video_frame, VideoFrame};
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(quartz)] {
|
if #[cfg(quartz)] {
|
||||||
@ -92,3 +93,55 @@ pub fn is_cursor_embedded() -> bool {
|
|||||||
pub fn is_cursor_embedded() -> bool {
|
pub fn is_cursor_embedded() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum CodecName {
|
||||||
|
VP8,
|
||||||
|
VP9,
|
||||||
|
H264(String),
|
||||||
|
H265(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
pub enum CodecFormat {
|
||||||
|
VP8,
|
||||||
|
VP9,
|
||||||
|
H264,
|
||||||
|
H265,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&VideoFrame> for CodecFormat {
|
||||||
|
fn from(it: &VideoFrame) -> Self {
|
||||||
|
match it.union {
|
||||||
|
Some(video_frame::Union::Vp8s(_)) => CodecFormat::VP8,
|
||||||
|
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
|
||||||
|
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
|
||||||
|
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
|
||||||
|
_ => CodecFormat::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&CodecName> for CodecFormat {
|
||||||
|
fn from(value: &CodecName) -> Self {
|
||||||
|
match value {
|
||||||
|
CodecName::VP8 => Self::VP8,
|
||||||
|
CodecName::VP9 => Self::VP9,
|
||||||
|
CodecName::H264(_) => Self::H264,
|
||||||
|
CodecName::H265(_) => Self::H265,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for CodecFormat {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
CodecFormat::VP8 => "VP8".into(),
|
||||||
|
CodecFormat::VP9 => "VP9".into(),
|
||||||
|
CodecFormat::H264 => "H264".into(),
|
||||||
|
CodecFormat::H265 => "H265".into(),
|
||||||
|
CodecFormat::Unknown => "Unknow".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::CodecFormat;
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
use hbb_common::anyhow::anyhow;
|
use hbb_common::anyhow::anyhow;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
@ -21,13 +22,6 @@ use webm::mux::{self, Segment, Track, VideoTrack, Writer};
|
|||||||
|
|
||||||
const MIN_SECS: u64 = 1;
|
const MIN_SECS: u64 = 1;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum RecordCodecID {
|
|
||||||
VP9,
|
|
||||||
H264,
|
|
||||||
H265,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RecorderContext {
|
pub struct RecorderContext {
|
||||||
pub server: bool,
|
pub server: bool,
|
||||||
@ -36,7 +30,7 @@ pub struct RecorderContext {
|
|||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub codec_id: RecordCodecID,
|
pub format: CodecFormat,
|
||||||
pub tx: Option<Sender<RecordState>>,
|
pub tx: Option<Sender<RecordState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +49,9 @@ impl RecorderContext {
|
|||||||
}
|
}
|
||||||
let file = if self.server { "s" } else { "c" }.to_string()
|
let file = if self.server { "s" } else { "c" }.to_string()
|
||||||
+ &self.id.clone()
|
+ &self.id.clone()
|
||||||
+ &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string()
|
+ &chrono::Local::now().format("_%Y%m%d%H%M%S_").to_string()
|
||||||
+ if self.codec_id == RecordCodecID::VP9 {
|
+ &self.format.to_string()
|
||||||
|
+ if self.format == CodecFormat::VP9 || self.format == CodecFormat::VP8 {
|
||||||
".webm"
|
".webm"
|
||||||
} else {
|
} else {
|
||||||
".mp4"
|
".mp4"
|
||||||
@ -107,8 +102,8 @@ impl DerefMut for Recorder {
|
|||||||
impl Recorder {
|
impl Recorder {
|
||||||
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
let recorder = match ctx.codec_id {
|
let recorder = match ctx.format {
|
||||||
RecordCodecID::VP9 => Recorder {
|
CodecFormat::VP8 | CodecFormat::VP9 => Recorder {
|
||||||
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
ctx,
|
ctx,
|
||||||
},
|
},
|
||||||
@ -126,8 +121,8 @@ impl Recorder {
|
|||||||
|
|
||||||
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
self.inner = match ctx.codec_id {
|
self.inner = match ctx.format {
|
||||||
RecordCodecID::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
CodecFormat::VP8 | CodecFormat::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
@ -148,10 +143,19 @@ impl Recorder {
|
|||||||
|
|
||||||
pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> {
|
pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> {
|
||||||
match frame {
|
match frame {
|
||||||
video_frame::Union::Vp9s(vp9s) => {
|
video_frame::Union::Vp8s(vp8s) => {
|
||||||
if self.ctx.codec_id != RecordCodecID::VP9 {
|
if self.ctx.format != CodecFormat::VP8 {
|
||||||
self.change(RecorderContext {
|
self.change(RecorderContext {
|
||||||
codec_id: RecordCodecID::VP9,
|
format: CodecFormat::VP8,
|
||||||
|
..self.ctx.clone()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
vp8s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
|
}
|
||||||
|
video_frame::Union::Vp9s(vp9s) => {
|
||||||
|
if self.ctx.format != CodecFormat::VP9 {
|
||||||
|
self.change(RecorderContext {
|
||||||
|
format: CodecFormat::VP9,
|
||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@ -159,25 +163,25 @@ impl Recorder {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H264s(h264s) => {
|
video_frame::Union::H264s(h264s) => {
|
||||||
if self.ctx.codec_id != RecordCodecID::H264 {
|
if self.ctx.format != CodecFormat::H264 {
|
||||||
self.change(RecorderContext {
|
self.change(RecorderContext {
|
||||||
codec_id: RecordCodecID::H264,
|
format: CodecFormat::H264,
|
||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.codec_id == RecordCodecID::H264 {
|
if self.ctx.format == CodecFormat::H264 {
|
||||||
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H265s(h265s) => {
|
video_frame::Union::H265s(h265s) => {
|
||||||
if self.ctx.codec_id != RecordCodecID::H265 {
|
if self.ctx.format != CodecFormat::H265 {
|
||||||
self.change(RecorderContext {
|
self.change(RecorderContext {
|
||||||
codec_id: RecordCodecID::H265,
|
format: CodecFormat::H265,
|
||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.codec_id == RecordCodecID::H265 {
|
if self.ctx.format == CodecFormat::H265 {
|
||||||
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,7 +225,11 @@ impl RecorderApi for WebmRecorder {
|
|||||||
ctx.width as _,
|
ctx.width as _,
|
||||||
ctx.height as _,
|
ctx.height as _,
|
||||||
None,
|
None,
|
||||||
mux::VideoCodecId::VP9,
|
if ctx.format == CodecFormat::VP9 {
|
||||||
|
mux::VideoCodecId::VP9
|
||||||
|
} else {
|
||||||
|
mux::VideoCodecId::VP8
|
||||||
|
},
|
||||||
);
|
);
|
||||||
Ok(WebmRecorder {
|
Ok(WebmRecorder {
|
||||||
vt,
|
vt,
|
||||||
@ -279,7 +287,7 @@ impl RecorderApi for HwRecorder {
|
|||||||
filename: ctx.filename.clone(),
|
filename: ctx.filename.clone(),
|
||||||
width: ctx.width,
|
width: ctx.width,
|
||||||
height: ctx.height,
|
height: ctx.height,
|
||||||
is265: ctx.codec_id == RecordCodecID::H265,
|
is265: ctx.format == CodecFormat::H265,
|
||||||
framerate: crate::hwcodec::DEFAULT_TIME_BASE[1] as _,
|
framerate: crate::hwcodec::DEFAULT_TIME_BASE[1] as _,
|
||||||
})
|
})
|
||||||
.map_err(|_| anyhow!("Failed to create hardware muxer"))?;
|
.map_err(|_| anyhow!("Failed to create hardware muxer"))?;
|
||||||
|
@ -30,6 +30,7 @@ pub struct VpxEncoder {
|
|||||||
ctx: vpx_codec_ctx_t,
|
ctx: vpx_codec_ctx_t,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
|
id: VpxVideoCodecId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VpxDecoder {
|
pub struct VpxDecoder {
|
||||||
@ -97,15 +98,10 @@ impl EncoderApi for VpxEncoder {
|
|||||||
{
|
{
|
||||||
match cfg {
|
match cfg {
|
||||||
crate::codec::EncoderCfg::VPX(config) => {
|
crate::codec::EncoderCfg::VPX(config) => {
|
||||||
let i;
|
let i = match config.codec {
|
||||||
if cfg!(feature = "VP8") {
|
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
||||||
i = match config.codec {
|
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
||||||
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() };
|
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||||
|
|
||||||
@ -187,12 +183,17 @@ impl EncoderApi for VpxEncoder {
|
|||||||
VP9E_SET_TILE_COLUMNS as _,
|
VP9E_SET_TILE_COLUMNS as _,
|
||||||
4 as c_int
|
4 as c_int
|
||||||
));
|
));
|
||||||
|
} else if config.codec == VpxVideoCodecId::VP8 {
|
||||||
|
// https://github.com/webmproject/libvpx/blob/972149cafeb71d6f08df89e91a0130d6a38c4b15/vpx/vp8cx.h#L172
|
||||||
|
// https://groups.google.com/a/webmproject.org/g/webm-discuss/c/DJhSrmfQ61M
|
||||||
|
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 12,));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ctx,
|
ctx,
|
||||||
width: config.width as _,
|
width: config.width as _,
|
||||||
height: config.height as _,
|
height: config.height as _,
|
||||||
|
id: config.codec,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("encoder type mismatch")),
|
_ => Err(anyhow!("encoder type mismatch")),
|
||||||
@ -213,7 +214,7 @@ impl EncoderApi for VpxEncoder {
|
|||||||
|
|
||||||
// to-do: flush periodically, e.g. 1 second
|
// to-do: flush periodically, e.g. 1 second
|
||||||
if frames.len() > 0 {
|
if frames.len() > 0 {
|
||||||
Ok(VpxEncoder::create_msg(frames))
|
Ok(VpxEncoder::create_msg(self.id, frames))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no valid frame"))
|
Err(anyhow!("no valid frame"))
|
||||||
}
|
}
|
||||||
@ -280,13 +281,17 @@ impl VpxEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
|
pub fn create_msg(codec_id: VpxVideoCodecId, frames: Vec<EncodedVideoFrame>) -> Message {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let mut vf = VideoFrame::new();
|
let mut vf = VideoFrame::new();
|
||||||
vf.set_vp9s(EncodedVideoFrames {
|
let vpxs = EncodedVideoFrames {
|
||||||
frames: vp9s.into(),
|
frames: frames.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
};
|
||||||
|
match codec_id {
|
||||||
|
VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs),
|
||||||
|
VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs),
|
||||||
|
}
|
||||||
msg_out.set_video_frame(vf);
|
msg_out.set_video_frame(vf);
|
||||||
msg_out
|
msg_out
|
||||||
}
|
}
|
||||||
@ -382,15 +387,10 @@ impl VpxDecoder {
|
|||||||
pub fn new(config: VpxDecoderConfig) -> 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 = match config.codec {
|
||||||
if cfg!(feature = "VP8") {
|
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
||||||
i = match config.codec {
|
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
||||||
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 mut ctx = Default::default();
|
||||||
let cfg = vpx_codec_dec_cfg_t {
|
let cfg = vpx_codec_dec_cfg_t {
|
||||||
threads: if config.num_threads == 0 {
|
threads: if config.num_threads == 0 {
|
||||||
|
@ -44,9 +44,9 @@ use hbb_common::{
|
|||||||
};
|
};
|
||||||
pub use helper::*;
|
pub use helper::*;
|
||||||
use scrap::{
|
use scrap::{
|
||||||
codec::{Decoder, DecoderCfg},
|
codec::Decoder,
|
||||||
record::{Recorder, RecorderContext},
|
record::{Recorder, RecorderContext},
|
||||||
ImageFormat, VpxDecoderConfig, VpxVideoCodecId,
|
ImageFormat,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -917,12 +917,7 @@ impl VideoHandler {
|
|||||||
/// Create a new video handler.
|
/// Create a new video handler.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
VideoHandler {
|
VideoHandler {
|
||||||
decoder: Decoder::new(DecoderCfg {
|
decoder: Decoder::new(),
|
||||||
vpx: VpxDecoderConfig {
|
|
||||||
codec: VpxVideoCodecId::VP9,
|
|
||||||
num_threads: (num_cpus::get() / 2) as _,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
rgb: Default::default(),
|
rgb: Default::default(),
|
||||||
recorder: Default::default(),
|
recorder: Default::default(),
|
||||||
record: false,
|
record: false,
|
||||||
@ -954,12 +949,7 @@ impl VideoHandler {
|
|||||||
|
|
||||||
/// Reset the decoder.
|
/// Reset the decoder.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.decoder = Decoder::new(DecoderCfg {
|
self.decoder = Decoder::new();
|
||||||
vpx: VpxDecoderConfig {
|
|
||||||
codec: VpxVideoCodecId::VP9,
|
|
||||||
num_threads: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start or stop screen record.
|
/// Start or stop screen record.
|
||||||
@ -973,7 +963,7 @@ impl VideoHandler {
|
|||||||
filename: "".to_owned(),
|
filename: "".to_owned(),
|
||||||
width: w as _,
|
width: w as _,
|
||||||
height: h as _,
|
height: h as _,
|
||||||
codec_id: scrap::record::RecordCodecID::VP9,
|
format: scrap::CodecFormat::VP9,
|
||||||
tx: None,
|
tx: None,
|
||||||
})
|
})
|
||||||
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
|
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))));
|
||||||
@ -999,7 +989,7 @@ pub struct LoginConfigHandler {
|
|||||||
pub conn_id: i32,
|
pub conn_id: i32,
|
||||||
features: Option<Features>,
|
features: Option<Features>,
|
||||||
session_id: u64,
|
session_id: u64,
|
||||||
pub supported_encoding: Option<(bool, bool)>,
|
pub supported_encoding: SupportedEncoding,
|
||||||
pub restarting_remote_device: bool,
|
pub restarting_remote_device: bool,
|
||||||
pub force_relay: bool,
|
pub force_relay: bool,
|
||||||
pub direct: Option<bool>,
|
pub direct: Option<bool>,
|
||||||
@ -1047,7 +1037,7 @@ impl LoginConfigHandler {
|
|||||||
self.remember = !config.password.is_empty();
|
self.remember = !config.password.is_empty();
|
||||||
self.config = config;
|
self.config = config;
|
||||||
self.session_id = rand::random();
|
self.session_id = rand::random();
|
||||||
self.supported_encoding = None;
|
self.supported_encoding = Default::default();
|
||||||
self.restarting_remote_device = false;
|
self.restarting_remote_device = false;
|
||||||
self.force_relay = !self.get_option("force-always-relay").is_empty() || force_relay;
|
self.force_relay = !self.get_option("force-always-relay").is_empty() || force_relay;
|
||||||
self.direct = None;
|
self.direct = None;
|
||||||
@ -1331,8 +1321,8 @@ impl LoginConfigHandler {
|
|||||||
msg.disable_clipboard = BoolOption::Yes.into();
|
msg.disable_clipboard = BoolOption::Yes.into();
|
||||||
n += 1;
|
n += 1;
|
||||||
}
|
}
|
||||||
let state = Decoder::video_codec_state(&self.id);
|
msg.supported_decoding =
|
||||||
msg.video_codec_state = hbb_common::protobuf::MessageField::some(state);
|
hbb_common::protobuf::MessageField::some(Decoder::supported_decodings(Some(&self.id)));
|
||||||
n += 1;
|
n += 1;
|
||||||
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
@ -1565,10 +1555,7 @@ impl LoginConfigHandler {
|
|||||||
self.conn_id = pi.conn_id;
|
self.conn_id = pi.conn_id;
|
||||||
// no matter if change, for update file time
|
// no matter if change, for update file time
|
||||||
self.save_config(config);
|
self.save_config(config);
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
self.supported_encoding = pi.encoding.clone().unwrap_or_default();
|
||||||
{
|
|
||||||
self.supported_encoding = Some((pi.encoding.h264, pi.encoding.h265));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_remote_dir(&self) -> String {
|
pub fn get_remote_dir(&self) -> String {
|
||||||
@ -1626,10 +1613,10 @@ impl LoginConfigHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_prefer_codec(&self) -> Message {
|
pub fn change_prefer_codec(&self) -> Message {
|
||||||
let state = scrap::codec::Decoder::video_codec_state(&self.id);
|
let decoding = scrap::codec::Decoder::supported_decodings(Some(&self.id));
|
||||||
let mut misc = Misc::new();
|
let mut misc = Misc::new();
|
||||||
misc.set_option(OptionMessage {
|
misc.set_option(OptionMessage {
|
||||||
video_codec_state: hbb_common::protobuf::MessageField::some(state),
|
supported_decoding: hbb_common::protobuf::MessageField::some(decoding),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
|
@ -1,37 +1,8 @@
|
|||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
get_time,
|
get_time,
|
||||||
message_proto::{video_frame, Message, VideoFrame, VoiceCallRequest, VoiceCallResponse},
|
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
|
||||||
};
|
};
|
||||||
|
use scrap::CodecFormat;
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
|
||||||
pub enum CodecFormat {
|
|
||||||
VP9,
|
|
||||||
H264,
|
|
||||||
H265,
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&VideoFrame> for CodecFormat {
|
|
||||||
fn from(it: &VideoFrame) -> Self {
|
|
||||||
match it.union {
|
|
||||||
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
|
|
||||||
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
|
|
||||||
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
|
|
||||||
_ => CodecFormat::Unknown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for CodecFormat {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
CodecFormat::VP9 => "VP9".into(),
|
|
||||||
CodecFormat::H264 => "H264".into(),
|
|
||||||
CodecFormat::H265 => "H265".into(),
|
|
||||||
CodecFormat::Unknown => "Unknow".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct QualityStatus {
|
pub struct QualityStatus {
|
||||||
|
@ -29,10 +29,10 @@ use hbb_common::tokio::{
|
|||||||
time::{self, Duration, Instant, Interval},
|
time::{self, Duration, Instant, Interval},
|
||||||
};
|
};
|
||||||
use hbb_common::{allow_err, fs, get_time, log, message_proto::*, Stream};
|
use hbb_common::{allow_err, fs, get_time, log, message_proto::*, Stream};
|
||||||
|
use scrap::CodecFormat;
|
||||||
|
|
||||||
use crate::client::{
|
use crate::client::{
|
||||||
new_voice_call_request, Client, CodecFormat, MediaData, MediaSender, QualityStatus, MILLI1,
|
new_voice_call_request, Client, MediaData, MediaSender, QualityStatus, MILLI1, SEC30,
|
||||||
SEC30,
|
|
||||||
};
|
};
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
use crate::common::{self, update_clipboard};
|
use crate::common::{self, update_clipboard};
|
||||||
@ -817,11 +817,10 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn contains_key_frame(vf: &VideoFrame) -> bool {
|
fn contains_key_frame(vf: &VideoFrame) -> bool {
|
||||||
|
use video_frame::Union::*;
|
||||||
match &vf.union {
|
match &vf.union {
|
||||||
Some(vf) => match vf {
|
Some(vf) => match vf {
|
||||||
video_frame::Union::Vp9s(f) => f.frames.iter().any(|e| e.key),
|
Vp8s(f) | Vp9s(f) | H264s(f) | H265s(f) => f.frames.iter().any(|e| e.key),
|
||||||
video_frame::Union::H264s(f) => f.frames.iter().any(|e| e.key),
|
|
||||||
video_frame::Union::H265s(f) => f.frames.iter().any(|e| e.key),
|
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
None => false,
|
None => false,
|
||||||
|
@ -969,6 +969,13 @@ pub fn main_has_hwcodec() -> SyncReturn<bool> {
|
|||||||
SyncReturn(has_hwcodec())
|
SyncReturn(has_hwcodec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_supported_hwdecodings() -> SyncReturn<String> {
|
||||||
|
let decoding = supported_hwdecodings();
|
||||||
|
let msg = HashMap::from([("h264", decoding.0), ("h265", decoding.1)]);
|
||||||
|
|
||||||
|
SyncReturn(serde_json::ser::to_string(&msg).unwrap_or("".to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main_is_root() -> bool {
|
pub fn main_is_root() -> bool {
|
||||||
is_root()
|
is_root()
|
||||||
}
|
}
|
||||||
@ -1054,10 +1061,10 @@ pub fn session_send_note(id: String, note: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_supported_hwcodec(id: String) -> String {
|
pub fn session_alternative_codecs(id: String) -> String {
|
||||||
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
|
||||||
let (h264, h265) = session.supported_hwcodec();
|
let (vp8, h264, h265) = session.alternative_codecs();
|
||||||
let msg = HashMap::from([("h264", h264), ("h265", h265)]);
|
let msg = HashMap::from([("vp8", vp8), ("h264", h264), ("h265", h265)]);
|
||||||
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
|
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
|
@ -536,7 +536,7 @@ impl Connection {
|
|||||||
let _ = privacy_mode::turn_off_privacy(0);
|
let _ = privacy_mode::turn_off_privacy(0);
|
||||||
}
|
}
|
||||||
video_service::notify_video_frame_fetched(id, None);
|
video_service::notify_video_frame_fetched(id, None);
|
||||||
scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
|
scrap::codec::Encoder::update(id, scrap::codec::EncodingUpdate::Remove);
|
||||||
video_service::VIDEO_QOS.lock().unwrap().reset();
|
video_service::VIDEO_QOS.lock().unwrap().reset();
|
||||||
if conn.authorized {
|
if conn.authorized {
|
||||||
password::update_temporary_password();
|
password::update_temporary_password();
|
||||||
@ -862,16 +862,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "hwcodec")]
|
pi.encoding = Some(scrap::codec::Encoder::supported_encoding()).into();
|
||||||
{
|
|
||||||
let (h264, h265) = scrap::codec::Encoder::supported_encoding();
|
|
||||||
pi.encoding = Some(SupportedEncoding {
|
|
||||||
h264,
|
|
||||||
h265,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.port_forward_socket.is_some() {
|
if self.port_forward_socket.is_some() {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
@ -1147,21 +1138,21 @@ impl Connection {
|
|||||||
self.lr = lr.clone();
|
self.lr = lr.clone();
|
||||||
if let Some(o) = lr.option.as_ref() {
|
if let Some(o) = lr.option.as_ref() {
|
||||||
self.options_in_login = Some(o.clone());
|
self.options_in_login = Some(o.clone());
|
||||||
if let Some(q) = o.video_codec_state.clone().take() {
|
if let Some(q) = o.supported_decoding.clone().take() {
|
||||||
scrap::codec::Encoder::update_video_encoder(
|
scrap::codec::Encoder::update(
|
||||||
self.inner.id(),
|
self.inner.id(),
|
||||||
scrap::codec::EncoderUpdate::State(q),
|
scrap::codec::EncodingUpdate::New(q),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
scrap::codec::Encoder::update_video_encoder(
|
scrap::codec::Encoder::update(
|
||||||
self.inner.id(),
|
self.inner.id(),
|
||||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
scrap::codec::EncodingUpdate::NewOnlyVP9,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scrap::codec::Encoder::update_video_encoder(
|
scrap::codec::Encoder::update(
|
||||||
self.inner.id(),
|
self.inner.id(),
|
||||||
scrap::codec::EncoderUpdate::DisableHwIfNotExist,
|
scrap::codec::EncodingUpdate::NewOnlyVP9,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.video_ack_required = lr.video_ack_required;
|
self.video_ack_required = lr.video_ack_required;
|
||||||
@ -1784,11 +1775,8 @@ impl Connection {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.update_user_fps(o.custom_fps as _);
|
.update_user_fps(o.custom_fps as _);
|
||||||
}
|
}
|
||||||
if let Some(q) = o.video_codec_state.clone().take() {
|
if let Some(q) = o.supported_decoding.clone().take() {
|
||||||
scrap::codec::Encoder::update_video_encoder(
|
scrap::codec::Encoder::update(self.inner.id(), scrap::codec::EncodingUpdate::New(q));
|
||||||
self.inner.id(),
|
|
||||||
scrap::codec::EncoderUpdate::State(q),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if let Ok(q) = o.lock_after_session_end.enum_value() {
|
if let Ok(q) = o.lock_after_session_end.enum_value() {
|
||||||
if q != BoolOption::NotSet {
|
if q != BoolOption::NotSet {
|
||||||
|
@ -31,7 +31,7 @@ use scrap::{
|
|||||||
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
codec::{Encoder, EncoderCfg, HwEncoderConfig},
|
||||||
record::{Recorder, RecorderContext},
|
record::{Recorder, RecorderContext},
|
||||||
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
vpxcodec::{VpxEncoderConfig, VpxVideoCodecId},
|
||||||
Display, TraitCapturer,
|
CodecName, Display, TraitCapturer,
|
||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
@ -468,21 +468,29 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
drop(video_qos);
|
drop(video_qos);
|
||||||
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
log::info!("init bitrate={}, abr enabled:{}", bitrate, abr);
|
||||||
|
|
||||||
let encoder_cfg = match Encoder::current_hw_encoder_name() {
|
let encoder_cfg = match Encoder::negotiated_codec() {
|
||||||
Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
|
scrap::CodecName::H264(name) | scrap::CodecName::H265(name) => {
|
||||||
codec_name,
|
EncoderCfg::HW(HwEncoderConfig {
|
||||||
width: c.width,
|
name,
|
||||||
height: c.height,
|
width: c.width,
|
||||||
bitrate: bitrate as _,
|
height: c.height,
|
||||||
}),
|
bitrate: bitrate as _,
|
||||||
None => EncoderCfg::VPX(VpxEncoderConfig {
|
})
|
||||||
width: c.width as _,
|
}
|
||||||
height: c.height as _,
|
name @ (scrap::CodecName::VP8 | scrap::CodecName::VP9) => {
|
||||||
timebase: [1, 1000], // Output timestamp precision
|
EncoderCfg::VPX(VpxEncoderConfig {
|
||||||
bitrate,
|
width: c.width as _,
|
||||||
codec: VpxVideoCodecId::VP9,
|
height: c.height as _,
|
||||||
num_threads: (num_cpus::get() / 2) as _,
|
timebase: [1, 1000], // Output timestamp precision
|
||||||
}),
|
bitrate,
|
||||||
|
codec: if name == scrap::CodecName::VP8 {
|
||||||
|
VpxVideoCodecId::VP8
|
||||||
|
} else {
|
||||||
|
VpxVideoCodecId::VP9
|
||||||
|
},
|
||||||
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
|
})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut encoder;
|
let mut encoder;
|
||||||
@ -526,7 +534,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
let mut try_gdi = 1;
|
let mut try_gdi = 1;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
log::info!("gdi: {}", c.is_gdi());
|
log::info!("gdi: {}", c.is_gdi());
|
||||||
let codec_name = Encoder::current_hw_encoder_name();
|
let codec_name = Encoder::negotiated_codec();
|
||||||
let recorder = get_recorder(c.width, c.height, &codec_name);
|
let recorder = get_recorder(c.width, c.height, &codec_name);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
start_uac_elevation_check();
|
start_uac_elevation_check();
|
||||||
@ -557,7 +565,7 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
*SWITCH.lock().unwrap() = true;
|
*SWITCH.lock().unwrap() = true;
|
||||||
bail!("SWITCH");
|
bail!("SWITCH");
|
||||||
}
|
}
|
||||||
if codec_name != Encoder::current_hw_encoder_name() {
|
if codec_name != Encoder::negotiated_codec() {
|
||||||
bail!("SWITCH");
|
bail!("SWITCH");
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -603,8 +611,14 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
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;
|
||||||
match frame {
|
match frame {
|
||||||
|
scrap::Frame::VP8(data) => {
|
||||||
|
let send_conn_ids =
|
||||||
|
handle_one_frame_encoded(VpxVideoCodecId::VP8, &sp, data, ms)?;
|
||||||
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
|
}
|
||||||
scrap::Frame::VP9(data) => {
|
scrap::Frame::VP9(data) => {
|
||||||
let send_conn_ids = handle_one_frame_encoded(&sp, data, ms)?;
|
let send_conn_ids =
|
||||||
|
handle_one_frame_encoded(VpxVideoCodecId::VP9, &sp, data, ms)?;
|
||||||
frame_controller.set_send(now, send_conn_ids);
|
frame_controller.set_send(now, send_conn_ids);
|
||||||
}
|
}
|
||||||
scrap::Frame::RAW(data) => {
|
scrap::Frame::RAW(data) => {
|
||||||
@ -717,12 +731,11 @@ fn run(sp: GenericService) -> ResultType<()> {
|
|||||||
fn get_recorder(
|
fn get_recorder(
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
codec_name: &Option<String>,
|
codec_name: &CodecName,
|
||||||
) -> Arc<Mutex<Option<Recorder>>> {
|
) -> Arc<Mutex<Option<Recorder>>> {
|
||||||
#[cfg(not(target_os = "ios"))]
|
#[cfg(not(target_os = "ios"))]
|
||||||
let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() {
|
let recorder = if !Config::get_option("allow-auto-record-incoming").is_empty() {
|
||||||
use crate::hbbs_http::record_upload;
|
use crate::hbbs_http::record_upload;
|
||||||
use scrap::record::RecordCodecID::*;
|
|
||||||
|
|
||||||
let tx = if record_upload::is_enable() {
|
let tx = if record_upload::is_enable() {
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
@ -731,16 +744,6 @@ fn get_recorder(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let codec_id = match codec_name {
|
|
||||||
Some(name) => {
|
|
||||||
if name.contains("264") {
|
|
||||||
H264
|
|
||||||
} else {
|
|
||||||
H265
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => VP9,
|
|
||||||
};
|
|
||||||
Recorder::new(RecorderContext {
|
Recorder::new(RecorderContext {
|
||||||
server: true,
|
server: true,
|
||||||
id: Config::get_id(),
|
id: Config::get_id(),
|
||||||
@ -748,7 +751,7 @@ fn get_recorder(
|
|||||||
filename: "".to_owned(),
|
filename: "".to_owned(),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
codec_id,
|
format: codec_name.into(),
|
||||||
tx,
|
tx,
|
||||||
})
|
})
|
||||||
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
|
.map_or(Default::default(), |r| Arc::new(Mutex::new(Some(r))))
|
||||||
@ -775,19 +778,6 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
|
||||||
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
|
|
||||||
let mut msg_out = Message::new();
|
|
||||||
let mut vf = VideoFrame::new();
|
|
||||||
vf.set_vp9s(EncodedVideoFrames {
|
|
||||||
frames: vp9s.into(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
msg_out.set_video_frame(vf);
|
|
||||||
msg_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn handle_one_frame(
|
fn handle_one_frame(
|
||||||
sp: &GenericService,
|
sp: &GenericService,
|
||||||
@ -820,6 +810,7 @@ fn handle_one_frame(
|
|||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
pub fn handle_one_frame_encoded(
|
pub fn handle_one_frame_encoded(
|
||||||
|
codec: VpxVideoCodecId,
|
||||||
sp: &GenericService,
|
sp: &GenericService,
|
||||||
frame: &[u8],
|
frame: &[u8],
|
||||||
ms: i64,
|
ms: i64,
|
||||||
@ -831,13 +822,13 @@ pub fn handle_one_frame_encoded(
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
let vp9_frame = EncodedVideoFrame {
|
let vpx_frame = EncodedVideoFrame {
|
||||||
data: frame.to_vec().into(),
|
data: frame.to_vec().into(),
|
||||||
key: true,
|
key: true,
|
||||||
pts: ms,
|
pts: ms,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let send_conn_ids = sp.send_video_frame(create_msg(vec![vp9_frame]));
|
let send_conn_ids = sp.send_video_frame(scrap::VpxEncoder::create_msg(codec, vec![vpx_frame]));
|
||||||
Ok(send_conn_ids)
|
Ok(send_conn_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,8 +161,8 @@ class Header: Reactor.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderDisplayPop() {
|
function renderDisplayPop() {
|
||||||
var codecs = handler.supported_hwcodec();
|
var codecs = handler.alternative_codecs();
|
||||||
var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]);
|
var show_codec = codecs[0] || codecs[1] || codecs[2];
|
||||||
|
|
||||||
var cursor_embedded = false;
|
var cursor_embedded = false;
|
||||||
if ((pi.displays || []).length > 0) {
|
if ((pi.displays || []).length > 0) {
|
||||||
@ -186,9 +186,10 @@ class Header: Reactor.Component {
|
|||||||
{show_codec ? <div>
|
{show_codec ? <div>
|
||||||
<div .separator />
|
<div .separator />
|
||||||
<li #auto type="codec-preference"><span>{svg_checkmark}</span>Auto</li>
|
<li #auto type="codec-preference"><span>{svg_checkmark}</span>Auto</li>
|
||||||
|
{codecs[0] ? <li #vp8 type="codec-preference"><span>{svg_checkmark}</span>VP8</li> : ""}
|
||||||
<li #vp9 type="codec-preference"><span>{svg_checkmark}</span>VP9</li>
|
<li #vp9 type="codec-preference"><span>{svg_checkmark}</span>VP9</li>
|
||||||
{codecs[0] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
|
{codecs[1] ? <li #h264 type="codec-preference"><span>{svg_checkmark}</span>H264</li> : ""}
|
||||||
{codecs[1] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
|
{codecs[2] ? <li #h265 type="codec-preference"><span>{svg_checkmark}</span>H265</li> : ""}
|
||||||
</div> : ""}
|
</div> : ""}
|
||||||
<div .separator />
|
<div .separator />
|
||||||
{!cursor_embedded && <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>}
|
{!cursor_embedded && <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>}
|
||||||
|
@ -20,7 +20,6 @@ use hbb_common::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::*,
|
client::*,
|
||||||
ui_interface::has_hwcodec,
|
|
||||||
ui_session_interface::{InvokeUiSession, Session},
|
ui_session_interface::{InvokeUiSession, Session},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -197,7 +196,14 @@ impl InvokeUiSession for SciterHandler {
|
|||||||
self.call("confirmDeleteFiles", &make_args!(id, i, name));
|
self.call("confirmDeleteFiles", &make_args!(id, i, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool, is_identical: bool) {
|
fn override_file_confirm(
|
||||||
|
&self,
|
||||||
|
id: i32,
|
||||||
|
file_num: i32,
|
||||||
|
to: String,
|
||||||
|
is_upload: bool,
|
||||||
|
is_identical: bool,
|
||||||
|
) {
|
||||||
self.call(
|
self.call(
|
||||||
"overrideFileConfirm",
|
"overrideFileConfirm",
|
||||||
&make_args!(id, file_num, to, is_upload, is_identical),
|
&make_args!(id, file_num, to, is_upload, is_identical),
|
||||||
@ -451,8 +457,7 @@ impl sciter::EventHandler for SciterSession {
|
|||||||
fn set_write_override(i32, i32, bool, bool, bool);
|
fn set_write_override(i32, i32, bool, bool, bool);
|
||||||
fn get_keyboard_mode();
|
fn get_keyboard_mode();
|
||||||
fn save_keyboard_mode(String);
|
fn save_keyboard_mode(String);
|
||||||
fn has_hwcodec();
|
fn alternative_codecs();
|
||||||
fn supported_hwcodec();
|
|
||||||
fn change_prefer_codec();
|
fn change_prefer_codec();
|
||||||
fn restart_remote_device();
|
fn restart_remote_device();
|
||||||
fn request_voice_call();
|
fn request_voice_call();
|
||||||
@ -504,10 +509,6 @@ impl SciterSession {
|
|||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_hwcodec(&self) -> bool {
|
|
||||||
has_hwcodec()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn t(&self, name: String) -> String {
|
pub fn t(&self, name: String) -> String {
|
||||||
crate::client::translate(name)
|
crate::client::translate(name)
|
||||||
}
|
}
|
||||||
@ -516,9 +517,10 @@ impl SciterSession {
|
|||||||
super::get_icon()
|
super::get_icon()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supported_hwcodec(&self) -> Value {
|
fn alternative_codecs(&self) -> Value {
|
||||||
let (h264, h265) = self.0.supported_hwcodec();
|
let (vp8, h264, h265) = self.0.alternative_codecs();
|
||||||
let mut v = Value::array(0);
|
let mut v = Value::array(0);
|
||||||
|
v.push(vp8);
|
||||||
v.push(h264);
|
v.push(h264);
|
||||||
v.push(h265);
|
v.push(h265);
|
||||||
v
|
v
|
||||||
|
@ -752,6 +752,13 @@ pub fn has_hwcodec() -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
#[inline]
|
||||||
|
pub fn supported_hwdecodings() -> (bool, bool) {
|
||||||
|
let decoding = scrap::codec::Decoder::supported_decodings(None);
|
||||||
|
(decoding.ability_h264 > 0, decoding.ability_h265 > 0)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_root() -> bool {
|
pub fn is_root() -> bool {
|
||||||
|
@ -216,24 +216,16 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_hwcodec(&self) -> (bool, bool) {
|
pub fn alternative_codecs(&self) -> (bool, bool, bool) {
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
let decoder = scrap::codec::Decoder::supported_decodings(None);
|
||||||
{
|
let mut vp8 = decoder.ability_vp8 > 0;
|
||||||
let decoder = scrap::codec::Decoder::video_codec_state(&self.id);
|
let mut h264 = decoder.ability_h264 > 0;
|
||||||
let mut h264 = decoder.score_h264 > 0;
|
let mut h265 = decoder.ability_h265 > 0;
|
||||||
let mut h265 = decoder.score_h265 > 0;
|
let enc = &self.lc.read().unwrap().supported_encoding;
|
||||||
let (encoding_264, encoding_265) = self
|
vp8 = vp8 && enc.vp8;
|
||||||
.lc
|
h264 = h264 && enc.h264;
|
||||||
.read()
|
h265 = h265 && enc.h265;
|
||||||
.unwrap()
|
(vp8, h264, h265)
|
||||||
.supported_encoding
|
|
||||||
.unwrap_or_default();
|
|
||||||
h264 = h264 && encoding_264;
|
|
||||||
h265 = h265 && encoding_265;
|
|
||||||
return (h264, h265);
|
|
||||||
}
|
|
||||||
#[allow(unreachable_code)]
|
|
||||||
(false, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_prefer_codec(&self) {
|
pub fn change_prefer_codec(&self) {
|
||||||
|
Loading…
Reference in New Issue
Block a user