Files
rustdesk/libs/scrap/src/common/aom.rs
RustDesk b50fde6910 add the base crate and repoint the moved modules at it (#16107)
* add the base crate and repoint the moved modules at it

`libs/base` (crate `base`) takes the parts of hbb_common that only this app
uses: `fs`, `platform`, `keyboard`, `message.proto`, and 145 of the 177
`config::keys` constants. hbb_common keeps what the server names, and the 32
keys it reads itself are re-exported from `base::config::keys` so call sites
still see the full set through one path.

Sources move verbatim. The only edits inside them are `crate::` prefixes that
now have to say `hbb_common::`; `keyboard.rs` and `platform/windows.rs` are
byte-identical. The crate stays on edition 2018, the edition the moved code was
written under. `log`, `lazy_static` and `anyhow` become direct dependencies so
the bare paths in that code resolve exactly as before, and its winapi features
are spelled out rather than left to feature unification.

Two call sites outside Rust and Cargo had to follow the move: the Android
protobuf source dir, which still pointed at hbb_common/protos for message.proto,
and the three AGENTS.md entries that named hbb_common for options, protos and
file transfer.

`scrap`'s `drm` feature now forwards to `base/wayland_probe`. Left pointing at
hbb_common it would still have compiled, silently dropping the Wayland
socket-probe fallback, so that forward is verified by a build with and without
the feature.

`config::keys` carries a test asserting its names stay disjoint from the ones
hbb_common kept: the glob re-export and the local constants share a namespace,
and Rust prefers the local item silently, so a name added to both sides would
otherwise let client and server disagree with no diagnostic.

Verified: macOS and Linux, debug and release, `--all-targets`; the 177 key
constants diffed name-for-name and value-for-value; the generated protobuf types
compared before and after; every `#[cfg]` gate on a moved import checked against
its original; and every file that was `rustfmt`-clean before this change still
is, compared against master file by file. Windows is checked by inspection only
-- it cannot be compiled here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab

* one `use` per crate, and write the rule down

`fs.rs` came out of the move with two ungated `use hbb_common::` statements,
because the original single `use crate::{...}` had to give up `message_proto`
to the new crate and the rest was left in a second block. Fold it back into one.

A scan of the whole tree for the same shape finds nothing else: every other file
with more than one top-level `use base::` or `use hbb_common::` is split by a
`#[cfg]` that does not cover the whole block, or by `pub use` next to `use`.
Those are the cases that cannot merge, so AGENTS.md now states both the rule and
the exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:46:42 +08:00

601 lines
18 KiB
Rust

#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(improper_ctypes)]
#![allow(dead_code)]
include!(concat!(env!("OUT_DIR"), "/aom_ffi.rs"));
use crate::codec::{base_bitrate, codec_thread_num};
use crate::{codec::EncoderApi, EncodeFrame, STRIDE_ALIGN};
use crate::{common::GoogleImage, generate_call_macro, generate_call_ptr_macro, Error, Result};
use crate::{EncodeInput, EncodeYuvFormat, Pixfmt};
use hbb_common::{
anyhow::{anyhow, Context},
bytes::Bytes,
log, ResultType,
};
use base::message_proto::{Chroma, EncodedVideoFrame, EncodedVideoFrames, VideoFrame};
use std::{ptr, slice};
generate_call_macro!(call_aom, false);
generate_call_macro!(call_aom_allow_err, true);
generate_call_ptr_macro!(call_aom_ptr);
impl Default for aom_codec_enc_cfg_t {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
impl Default for aom_codec_ctx_t {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
impl Default for aom_image_t {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
#[derive(Clone, Copy, Debug)]
pub struct AomEncoderConfig {
pub width: u32,
pub height: u32,
pub quality: f32,
pub keyframe_interval: Option<usize>,
}
pub struct AomEncoder {
ctx: aom_codec_ctx_t,
width: usize,
height: usize,
i444: bool,
yuvfmt: EncodeYuvFormat,
}
// https://webrtc.googlesource.com/src/+/refs/heads/main/modules/video_coding/codecs/av1/libaom_av1_encoder.cc
mod webrtc {
use super::*;
const kUsageProfile: u32 = AOM_USAGE_REALTIME;
const kBitDepth: u32 = 8;
const kLagInFrames: u32 = 0; // No look ahead.
pub(super) const kTimeBaseDen: i64 = 1000;
// Only positive speeds, range for real-time coding currently is: 6 - 8.
// Lower means slower/better quality, higher means fastest/lower quality.
fn get_cpu_speed(width: u32, height: u32) -> u32 {
// aux_config_ = nullptr, kComplexityHigh
if width * height <= 320 * 180 {
8
} else if width * height <= 640 * 360 {
9
} else {
10
}
}
fn tile_log2(threads: u32) -> std::os::raw::c_uint {
(threads as f64).log2().ceil() as _
}
fn get_super_block_size(width: u32, height: u32, threads: u32) -> aom_superblock_size_t {
use aom_superblock_size::*;
let resolution = width * height;
if threads >= 4 && resolution >= 960 * 540 && resolution < 1920 * 1080 {
AOM_SUPERBLOCK_SIZE_64X64
} else {
AOM_SUPERBLOCK_SIZE_DYNAMIC
}
}
pub fn enc_cfg(
i: *const aom_codec_iface,
cfg: AomEncoderConfig,
i444: bool,
) -> ResultType<aom_codec_enc_cfg> {
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
call_aom!(aom_codec_enc_config_default(i, &mut c, kUsageProfile));
// Overwrite default config with input encoder settings & RTC-relevant values.
c.g_w = cfg.width;
c.g_h = cfg.height;
c.g_threads = codec_thread_num(64) as _;
c.g_timebase.num = 1;
c.g_timebase.den = kTimeBaseDen as _;
c.g_input_bit_depth = kBitDepth;
if let Some(keyframe_interval) = cfg.keyframe_interval {
c.kf_min_dist = 0;
c.kf_max_dist = keyframe_interval as _;
} else {
c.kf_mode = aom_kf_mode::AOM_KF_DISABLED;
}
let (q_min, q_max) = AomEncoder::calc_q_values(cfg.quality);
c.rc_min_quantizer = q_min;
c.rc_max_quantizer = q_max;
c.rc_target_bitrate = AomEncoder::bitrate(cfg.width as _, cfg.height as _, cfg.quality);
c.rc_undershoot_pct = 50;
c.rc_overshoot_pct = 50;
c.rc_buf_initial_sz = 600;
c.rc_buf_optimal_sz = 600;
c.rc_buf_sz = 1000;
c.g_usage = kUsageProfile;
c.g_error_resilient = 0;
// Low-latency settings.
c.rc_end_usage = aom_rc_mode::AOM_CBR; // Constant Bit Rate (CBR) mode
c.g_pass = aom_enc_pass::AOM_RC_ONE_PASS; // One-pass rate control
c.g_lag_in_frames = kLagInFrames; // No look ahead when lag equals 0.
// https://aomedia.googlesource.com/aom/+/refs/tags/v3.6.0/av1/common/enums.h#82
c.g_profile = if i444 { 1 } else { 0 };
Ok(c)
}
pub fn set_controls(ctx: *mut aom_codec_ctx_t, cfg: &aom_codec_enc_cfg) -> ResultType<()> {
use aom_tune_content::*;
use aome_enc_control_id::*;
macro_rules! call_ctl {
($ctx:expr, $av1e:expr, $arg:expr) => {{
call_aom_allow_err!(aom_codec_control($ctx, $av1e as i32, $arg));
}};
}
call_ctl!(ctx, AOME_SET_CPUUSED, get_cpu_speed(cfg.g_w, cfg.g_h));
call_ctl!(ctx, AV1E_SET_ENABLE_CDEF, 1);
call_ctl!(ctx, AV1E_SET_ENABLE_TPL_MODEL, 0);
call_ctl!(ctx, AV1E_SET_DELTAQ_MODE, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_ORDER_HINT, 0);
call_ctl!(ctx, AV1E_SET_AQ_MODE, 3);
call_ctl!(ctx, AOME_SET_MAX_INTRA_BITRATE_PCT, 300);
call_ctl!(ctx, AV1E_SET_COEFF_COST_UPD_FREQ, 3);
call_ctl!(ctx, AV1E_SET_MODE_COST_UPD_FREQ, 3);
call_ctl!(ctx, AV1E_SET_MV_COST_UPD_FREQ, 3);
// kScreensharing
call_ctl!(ctx, AV1E_SET_TUNE_CONTENT, AOM_CONTENT_SCREEN);
call_ctl!(ctx, AV1E_SET_ENABLE_PALETTE, 1);
let tile_set = if cfg.g_threads == 4 && cfg.g_w == 640 && (cfg.g_h == 360 || cfg.g_h == 480)
{
AV1E_SET_TILE_ROWS
} else {
AV1E_SET_TILE_COLUMNS
};
call_ctl!(ctx, tile_set, tile_log2(cfg.g_threads));
call_ctl!(ctx, AV1E_SET_ROW_MT, 1);
call_ctl!(ctx, AV1E_SET_ENABLE_OBMC, 0);
call_ctl!(ctx, AV1E_SET_NOISE_SENSITIVITY, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_WARPED_MOTION, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_GLOBAL_MOTION, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_REF_FRAME_MVS, 0);
call_ctl!(
ctx,
AV1E_SET_SUPERBLOCK_SIZE,
get_super_block_size(cfg.g_w, cfg.g_h, cfg.g_threads)
);
call_ctl!(ctx, AV1E_SET_ENABLE_CFL_INTRA, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_SMOOTH_INTRA, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_ANGLE_DELTA, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_FILTER_INTRA, 0);
call_ctl!(ctx, AV1E_SET_INTRA_DEFAULT_TX_ONLY, 1);
call_ctl!(ctx, AV1E_SET_DISABLE_TRELLIS_QUANT, 1);
call_ctl!(ctx, AV1E_SET_ENABLE_DIST_WTD_COMP, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_DIFF_WTD_COMP, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_DUAL_FILTER, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_INTERINTRA_COMP, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_INTERINTRA_WEDGE, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_INTRA_EDGE_FILTER, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_INTRABC, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_MASKED_COMP, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_PAETH_INTRA, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_QM, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_RECT_PARTITIONS, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_RESTORATION, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_SMOOTH_INTERINTRA, 0);
call_ctl!(ctx, AV1E_SET_ENABLE_TX64, 0);
call_ctl!(ctx, AV1E_SET_MAX_REFERENCE_FRAMES, 3);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::os::raw::c_uint;
#[test]
fn tile_log2_uses_c_uint_and_rounds_up() {
let one_thread: c_uint = tile_log2(1);
let three_threads: c_uint = tile_log2(3);
let max_threads: c_uint = tile_log2(64);
assert_eq!(one_thread, 0);
assert_eq!(three_threads, 2);
assert_eq!(max_threads, 6);
}
}
}
impl EncoderApi for AomEncoder {
fn new(cfg: crate::codec::EncoderCfg, i444: bool) -> ResultType<Self>
where
Self: Sized,
{
match cfg {
crate::codec::EncoderCfg::AOM(config) => {
let i = call_aom_ptr!(aom_codec_av1_cx());
let c = webrtc::enc_cfg(i, config, i444)?;
let mut ctx = Default::default();
// Flag options: AOM_CODEC_USE_PSNR and AOM_CODEC_USE_HIGHBITDEPTH
let flags: aom_codec_flags_t = 0;
call_aom!(aom_codec_enc_init_ver(
&mut ctx,
i,
&c,
flags,
AOM_ENCODER_ABI_VERSION as _
));
webrtc::set_controls(&mut ctx, &c)?;
Ok(Self {
ctx,
width: config.width as _,
height: config.height as _,
i444,
yuvfmt: Self::get_yuvfmt(config.width, config.height, i444),
})
}
_ => Err(anyhow!("encoder type mismatch")),
}
}
fn encode_to_message(&mut self, input: EncodeInput, ms: i64) -> ResultType<VideoFrame> {
let mut frames = Vec::new();
for ref frame in self
.encode(ms, input.yuv()?, STRIDE_ALIGN)
.with_context(|| "Failed to encode")?
{
frames.push(Self::create_frame(frame));
}
if frames.len() > 0 {
Ok(Self::create_video_frame(frames))
} else {
Err(anyhow!("no valid frame"))
}
}
fn yuvfmt(&self) -> crate::EncodeYuvFormat {
self.yuvfmt.clone()
}
#[cfg(feature = "vram")]
fn input_texture(&self) -> bool {
false
}
fn set_quality(&mut self, ratio: f32) -> ResultType<()> {
let mut c = unsafe { *self.ctx.config.enc.to_owned() };
let (q_min, q_max) = Self::calc_q_values(ratio);
c.rc_min_quantizer = q_min;
c.rc_max_quantizer = q_max;
c.rc_target_bitrate = Self::bitrate(self.width as _, self.height as _, ratio);
call_aom!(aom_codec_enc_config_set(&mut self.ctx, &c));
Ok(())
}
fn bitrate(&self) -> u32 {
let c = unsafe { *self.ctx.config.enc.to_owned() };
c.rc_target_bitrate
}
fn support_changing_quality(&self) -> bool {
true
}
fn latency_free(&self) -> bool {
true
}
fn is_hardware(&self) -> bool {
false
}
fn disable(&self) {}
}
impl AomEncoder {
pub fn encode<'a>(&'a mut self, ms: i64, data: &[u8], stride_align: usize) -> Result<EncodeFrames<'a>> {
let bpp = if self.i444 { 24 } else { 12 };
if data.len() < self.width * self.height * bpp / 8 {
return Err(Error::FailedCall("len not enough".to_string()));
}
let fmt = if self.i444 {
aom_img_fmt::AOM_IMG_FMT_I444
} else {
aom_img_fmt::AOM_IMG_FMT_I420
};
let mut image = Default::default();
call_aom_ptr!(aom_img_wrap(
&mut image,
fmt,
self.width as _,
self.height as _,
stride_align as _,
data.as_ptr() as _,
));
let pts = webrtc::kTimeBaseDen / 1000 * ms;
let duration = webrtc::kTimeBaseDen / 1000;
call_aom!(aom_codec_encode(
&mut self.ctx,
&image,
pts as _,
duration as _, // Duration
0, // Flags
));
Ok(EncodeFrames {
ctx: &mut self.ctx,
iter: ptr::null(),
})
}
#[inline]
pub fn create_video_frame(frames: Vec<EncodedVideoFrame>) -> VideoFrame {
let mut vf = VideoFrame::new();
let av1s = EncodedVideoFrames {
frames: frames.into(),
..Default::default()
};
vf.set_av1s(av1s);
vf
}
#[inline]
fn create_frame(frame: &EncodeFrame) -> EncodedVideoFrame {
EncodedVideoFrame {
data: Bytes::from(frame.data.to_vec()),
key: frame.key,
pts: frame.pts,
..Default::default()
}
}
fn bitrate(width: u32, height: u32, ratio: f32) -> u32 {
let bitrate = base_bitrate(width, height) as f32;
(bitrate * ratio) as u32
}
#[inline]
fn calc_q_values(ratio: f32) -> (u32, u32) {
let b = (ratio * 100.0) as u32;
let b = std::cmp::min(b, 200);
let q_min1 = 24;
let q_min2 = 5;
let q_max1 = 45;
let q_max2 = 25;
let t = b as f32 / 200.0;
let mut q_min: u32 = ((1.0 - t) * q_min1 as f32 + t * q_min2 as f32).round() as u32;
let mut q_max = ((1.0 - t) * q_max1 as f32 + t * q_max2 as f32).round() as u32;
q_min = q_min.clamp(q_min2, q_min1);
q_max = q_max.clamp(q_max2, q_max1);
(q_min, q_max)
}
fn get_yuvfmt(width: u32, height: u32, i444: bool) -> EncodeYuvFormat {
let mut img = Default::default();
let fmt = if i444 {
aom_img_fmt::AOM_IMG_FMT_I444
} else {
aom_img_fmt::AOM_IMG_FMT_I420
};
unsafe {
aom_img_wrap(
&mut img,
fmt,
width as _,
height as _,
crate::STRIDE_ALIGN as _,
0x1 as _,
);
}
let pixfmt = if i444 { Pixfmt::I444 } else { Pixfmt::I420 };
EncodeYuvFormat {
pixfmt,
w: img.w as _,
h: img.h as _,
stride: img.stride.map(|s| s as usize).to_vec(),
u: img.planes[1] as usize - img.planes[0] as usize,
v: img.planes[2] as usize - img.planes[0] as usize,
}
}
}
impl Drop for AomEncoder {
fn drop(&mut self) {
unsafe {
let result = aom_codec_destroy(&mut self.ctx);
if result != aom_codec_err_t::AOM_CODEC_OK {
panic!("failed to destroy aom codec");
}
}
}
}
pub struct EncodeFrames<'a> {
ctx: &'a mut aom_codec_ctx_t,
iter: aom_codec_iter_t,
}
impl<'a> Iterator for EncodeFrames<'a> {
type Item = EncodeFrame<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
unsafe {
let pkt = aom_codec_get_cx_data(self.ctx, &mut self.iter);
if pkt.is_null() {
return None;
} else if (*pkt).kind == aom_codec_cx_pkt_kind::AOM_CODEC_CX_FRAME_PKT {
let f = &(*pkt).data.frame;
return Some(Self::Item {
data: slice::from_raw_parts(f.buf as _, f.sz as _),
key: (f.flags & AOM_FRAME_IS_KEY) != 0,
pts: f.pts,
});
} else {
// Ignore the packet.
}
}
}
}
}
pub struct AomDecoder {
ctx: aom_codec_ctx_t,
}
impl AomDecoder {
pub fn new() -> Result<Self> {
let i = call_aom_ptr!(aom_codec_av1_dx());
let mut ctx = Default::default();
let cfg = aom_codec_dec_cfg_t {
threads: codec_thread_num(64) as _,
w: 0,
h: 0,
allow_lowbitdepth: 1,
};
call_aom!(aom_codec_dec_init_ver(
&mut ctx,
i,
&cfg,
0,
AOM_DECODER_ABI_VERSION as _,
));
Ok(Self { ctx })
}
pub fn decode<'a>(&'a mut self, data: &[u8]) -> Result<DecodeFrames<'a>> {
call_aom!(aom_codec_decode(
&mut self.ctx,
data.as_ptr(),
data.len() as _,
ptr::null_mut(),
));
Ok(DecodeFrames {
ctx: &mut self.ctx,
iter: ptr::null(),
})
}
/// Notify the decoder to return any pending frame
pub fn flush<'a>(&'a mut self) -> Result<DecodeFrames<'a>> {
call_aom!(aom_codec_decode(
&mut self.ctx,
ptr::null(),
0,
ptr::null_mut(),
));
Ok(DecodeFrames {
ctx: &mut self.ctx,
iter: ptr::null(),
})
}
}
impl Drop for AomDecoder {
fn drop(&mut self) {
unsafe {
let result = aom_codec_destroy(&mut self.ctx);
if result != aom_codec_err_t::AOM_CODEC_OK {
panic!("failed to destroy aom codec");
}
}
}
}
pub struct DecodeFrames<'a> {
ctx: &'a mut aom_codec_ctx_t,
iter: aom_codec_iter_t,
}
impl<'a> Iterator for DecodeFrames<'a> {
type Item = Image;
fn next(&mut self) -> Option<Self::Item> {
let img = unsafe { aom_codec_get_frame(self.ctx, &mut self.iter) };
if img.is_null() {
return None;
} else {
return Some(Image(img));
}
}
}
pub struct Image(*mut aom_image_t);
impl Image {
#[inline]
pub fn new() -> Self {
Self(std::ptr::null_mut())
}
#[inline]
pub fn is_null(&self) -> bool {
self.0.is_null()
}
#[inline]
pub fn format(&self) -> aom_img_fmt_t {
self.inner().fmt
}
#[inline]
pub fn inner(&self) -> &aom_image_t {
unsafe { &*self.0 }
}
}
impl GoogleImage for Image {
#[inline]
fn width(&self) -> usize {
self.inner().d_w as _
}
#[inline]
fn height(&self) -> usize {
self.inner().d_h as _
}
#[inline]
fn stride(&self) -> Vec<i32> {
self.inner().stride.iter().map(|x| *x as i32).collect()
}
#[inline]
fn planes(&self) -> Vec<*mut u8> {
self.inner().planes.iter().map(|p| *p as *mut u8).collect()
}
fn chroma(&self) -> Chroma {
match self.inner().fmt {
aom_img_fmt::AOM_IMG_FMT_I444 => Chroma::I444,
_ => Chroma::I420,
}
}
}
impl Drop for Image {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { aom_img_free(self.0) };
}
}
}
unsafe impl Send for aom_codec_ctx_t {}