source code

This commit is contained in:
rustdesk
2021-03-29 15:59:14 +08:00
parent 002fce136c
commit d1013487e2
175 changed files with 35074 additions and 2 deletions

4
libs/scrap/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock
generated/

32
libs/scrap/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "scrap"
description = "Screen capture made easy."
version = "0.5.0"
repository = "https://github.com/quadrupleslap/scrap"
documentation = "https://docs.rs/scrap"
keywords = ["screen", "capture", "record"]
license = "MIT"
authors = ["Ram <quadrupleslap@gmail.com>"]
edition = "2018"
[dependencies]
block = "0.1"
cfg-if = "1.0"
libc = "0.2"
num_cpus = "1.13"
[dependencies.winapi]
version = "0.3"
default-features = true
features = ["dxgi", "dxgi1_2", "dxgi1_5", "d3d11"]
[dev-dependencies]
repng = "0.2"
docopt = "1.1"
webm = "1.0"
serde = {version="1.0", features=["derive"]}
quest = "0.3"
[build-dependencies]
target_build_utils = "0.3"
bindgen = "0.53"

60
libs/scrap/README.md Normal file
View File

@@ -0,0 +1,60 @@
# scrap
Scrap records your screen! At least it does if you're on Windows, macOS, or Linux.
## Usage
```toml
[dependencies]
scrap = "0.5"
```
Its API is as simple as it gets!
```rust
struct Display; /// A screen.
struct Frame; /// An array of the pixels that were on-screen.
struct Capturer; /// A recording instance.
impl Capturer {
/// Begin recording.
pub fn new(display: Display) -> io::Result<Capturer>;
/// Try to get a frame.
/// Returns WouldBlock if it's not ready yet.
pub fn frame<'a>(&'a mut self) -> io::Result<Frame<'a>>;
pub fn width(&self) -> usize;
pub fn height(&self) -> usize;
}
impl Display {
/// The primary screen.
pub fn primary() -> io::Result<Display>;
/// All the screens.
pub fn all() -> io::Result<Vec<Display>>;
pub fn width(&self) -> usize;
pub fn height(&self) -> usize;
}
impl<'a> ops::Deref for Frame<'a> {
/// A frame is just an array of bytes.
type Target = [u8];
}
```
## The Frame Format
- The frame format is guaranteed to be **packed BGRA**.
- The width and height are guaranteed to remain constant.
- The stride might be greater than the width, and it may also vary between frames.
## System Requirements
OS | Minimum Requirements
--------|---------------------
macOS | macOS 10.8
Linux | XCB + SHM + RandR
Windows | DirectX 11.1

118
libs/scrap/build.rs Normal file
View File

@@ -0,0 +1,118 @@
use std::{
env, fs,
path::{Path, PathBuf},
};
fn find_package(name: &str) -> Vec<PathBuf> {
let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap();
let mut path: PathBuf = vcpkg_root.into();
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let mut target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if target_arch == "x86_64" {
target_arch = "x64".to_owned();
} else if target_arch == "aarch64" {
target_arch = "arm64".to_owned();
} else {
target_arch = "arm".to_owned();
}
let target = if target_os == "macos" {
"x64-osx".to_owned()
} else if target_os == "windows" {
"x64-windows-static".to_owned()
} else if target_os == "android" {
format!("{}-android-static", target_arch)
} else {
"x64-linux".to_owned()
};
println!("cargo:info={}", target);
path.push("installed");
path.push(target);
let mut lib = name.trim_start_matches("lib").to_string();
if lib == "vpx" && target_os == "windows" {
lib = format!("{}mt", lib);
}
println!("{}", format!("cargo:rustc-link-lib={}", lib));
println!(
"{}",
format!(
"cargo:rustc-link-search={}",
path.join("lib").to_str().unwrap()
)
);
let include = path.join("include");
println!("{}", format!("cargo:include={}", include.to_str().unwrap()));
vec![include]
}
fn generate_bindings(
ffi_header: &Path,
include_paths: &[PathBuf],
ffi_rs: &Path,
exact_file: &Path,
) {
let mut b = bindgen::builder()
.header(ffi_header.to_str().unwrap())
.whitelist_type("^[vV].*")
.whitelist_var("^[vV].*")
.whitelist_function("^[vV].*")
.rustified_enum("^v.*")
.trust_clang_mangling(false)
.layout_tests(false) // breaks 32/64-bit compat
.generate_comments(false); // vpx comments have prefix /*!\
for dir in include_paths {
b = b.clang_arg(format!("-I{}", dir.display()));
}
b.generate().unwrap().write_to_file(ffi_rs).unwrap();
fs::copy(ffi_rs, exact_file).ok(); // ignore failure
}
fn gen_vpx() {
let includes = find_package("libvpx");
let src_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
let src_dir = Path::new(&src_dir);
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let ffi_header = src_dir.join("vpx_ffi.h");
println!("rerun-if-changed={}", ffi_header.display());
for dir in &includes {
println!("rerun-if-changed={}", dir.display());
}
let ffi_rs = out_dir.join("vpx_ffi.rs");
let exact_file = src_dir.join("generated").join("vpx_ffi.rs");
generate_bindings(&ffi_header, &includes, &ffi_rs, &exact_file);
}
fn main() {
// note: all link symbol names in x86 (32-bit) are prefixed wth "_".
// run "rustup show" to show current default toolchain, if it is stable-x86-pc-windows-msvc,
// please install x64 toolchain by "rustup toolchain install stable-x86_64-pc-windows-msvc",
// then set x64 to default by "rustup default stable-x86_64-pc-windows-msvc"
let target = target_build_utils::TargetInfo::new();
if target.unwrap().target_pointer_width() != "64" {
panic!("Only support 64bit system");
}
env::remove_var("CARGO_CFG_TARGET_FEATURE");
env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
find_package("libyuv");
gen_vpx();
// there is problem with cfg(target_os) in build.rs, so use our workaround
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "android" || target_os == "ios" {
// nothing
} else if cfg!(windows) {
// The first choice is Windows because DXGI is amazing.
println!("cargo:rustc-cfg=dxgi");
} else if cfg!(target_os = "macos") {
// Quartz is second because macOS is the (annoying) exception.
println!("cargo:rustc-cfg=quartz");
} else if cfg!(unix) {
// On UNIX we pray that X11 (with XCB) is available.
println!("cargo:rustc-cfg=x11");
}
}

View File

@@ -0,0 +1,51 @@
extern crate scrap;
fn main() {
use scrap::{Capturer, Display};
use std::io::ErrorKind::WouldBlock;
use std::io::Write;
use std::process::{Command, Stdio};
let d = Display::primary().unwrap();
let (w, h) = (d.width(), d.height());
let child = Command::new("ffplay")
.args(&[
"-f",
"rawvideo",
"-pixel_format",
"bgr0",
"-video_size",
&format!("{}x{}", w, h),
"-framerate",
"60",
"-",
])
.stdin(Stdio::piped())
.spawn()
.expect("This example requires ffplay.");
let mut capturer = Capturer::new(d, false).unwrap();
let mut out = child.stdin.unwrap();
loop {
match capturer.frame(0) {
Ok(frame) => {
// Write the frame, removing end-of-row padding.
let stride = frame.len() / h;
let rowlen = 4 * w;
for row in frame.chunks(stride) {
let row = &row[..rowlen];
out.write_all(row).unwrap();
}
}
Err(ref e) if e.kind() == WouldBlock => {
// Wait for the frame.
}
Err(_) => {
// We're done here.
break;
}
}
}
}

View File

@@ -0,0 +1,16 @@
extern crate scrap;
use scrap::Display;
fn main() {
let displays = Display::all().unwrap();
for (i, display) in displays.iter().enumerate() {
println!(
"Display {} [{}x{}]",
i + 1,
display.width(),
display.height()
);
}
}

View File

@@ -0,0 +1,161 @@
extern crate docopt;
extern crate quest;
extern crate repng;
extern crate scrap;
extern crate serde;
extern crate webm;
use std::fs::{File, OpenOptions};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::{io, thread};
use docopt::Docopt;
use webm::mux;
use webm::mux::Track;
use scrap::codec as vpx_encode;
use scrap::{Capturer, Display, STRIDE_ALIGN};
const USAGE: &'static str = "
Simple WebM screen capture.
Usage:
record-screen <path> [--time=<s>] [--fps=<fps>] [--bv=<kbps>] [--ba=<kbps>] [--codec CODEC]
record-screen (-h | --help)
Options:
-h --help Show this screen.
--time=<s> Recording duration in seconds.
--fps=<fps> Frames per second [default: 30].
--bv=<kbps> Video bitrate in kilobits per second [default: 5000].
--ba=<kbps> Audio bitrate in kilobits per second [default: 96].
--codec CODEC Configure the codec used. [default: vp9]
Valid values: vp8, vp9.
";
#[derive(Debug, serde::Deserialize)]
struct Args {
arg_path: PathBuf,
flag_codec: Codec,
flag_time: Option<u64>,
flag_fps: u64,
flag_bv: u32,
flag_ba: u32,
}
#[derive(Debug, serde::Deserialize)]
enum Codec {
Vp8,
Vp9,
}
fn main() -> io::Result<()> {
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit());
let duration = args.flag_time.map(Duration::from_secs);
let d = Display::primary().unwrap();
let (width, height) = (d.width() as u32, d.height() as u32);
// Setup the multiplexer.
let out = match {
OpenOptions::new()
.write(true)
.create_new(true)
.open(&args.arg_path)
} {
Ok(file) => file,
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
if loop {
quest::ask("Overwrite the existing file? [y/N] ");
if let Some(b) = quest::yesno(false)? {
break b;
}
} {
File::create(&args.arg_path)?
} else {
return Ok(());
}
}
Err(e) => return Err(e.into()),
};
let mut webm =
mux::Segment::new(mux::Writer::new(out)).expect("Could not initialize the multiplexer.");
let (vpx_codec, mux_codec) = match args.flag_codec {
Codec::Vp8 => (vpx_encode::VideoCodecId::VP8, mux::VideoCodecId::VP8),
Codec::Vp9 => (vpx_encode::VideoCodecId::VP9, mux::VideoCodecId::VP9),
};
let mut vt = webm.add_video_track(width, height, None, mux_codec);
// Setup the encoder.
let mut vpx = vpx_encode::Encoder::new(
&vpx_encode::Config {
width,
height,
timebase: [1, 1000],
bitrate: args.flag_bv,
codec: vpx_codec,
rc_min_quantizer: 0,
rc_max_quantizer: 0,
speed: 6,
},
0,
)
.unwrap();
// Start recording.
let start = Instant::now();
let stop = Arc::new(AtomicBool::new(false));
thread::spawn({
let stop = stop.clone();
move || {
let _ = quest::ask("Recording! Press ⏎ to stop.");
let _ = quest::text();
stop.store(true, Ordering::Release);
}
});
let spf = Duration::from_nanos(1_000_000_000 / args.flag_fps);
// Capturer object is expensive, avoiding to create it frequently.
let mut c = Capturer::new(d, true).unwrap();
while !stop.load(Ordering::Acquire) {
let now = Instant::now();
let time = now - start;
if Some(true) == duration.map(|d| time > d) {
break;
}
if let Ok(frame) = c.frame(0) {
let ms = time.as_secs() * 1000 + time.subsec_millis() as u64;
for frame in vpx.encode(ms as i64, &frame, STRIDE_ALIGN).unwrap() {
vt.add_frame(frame.data, frame.pts as u64 * 1_000_000, frame.key);
}
}
let dt = now.elapsed();
if dt < spf {
thread::sleep(spf - dt);
}
}
// End things.
let _ = webm.finalize(None);
Ok(())
}

View File

@@ -0,0 +1,122 @@
extern crate repng;
extern crate scrap;
use std::fs::File;
use std::io::ErrorKind::WouldBlock;
use std::thread;
use std::time::Duration;
use scrap::{i420_to_rgb, Capturer, Display};
fn main() {
let n = Display::all().unwrap().len();
for i in 0..n {
record(i);
}
}
fn get_display(i: usize) -> Display {
Display::all().unwrap().remove(i)
}
fn record(i: usize) {
let one_second = Duration::new(1, 0);
let one_frame = one_second / 60;
let display = get_display(i);
let mut capturer = Capturer::new(display, false).expect("Couldn't begin capture.");
let (w, h) = (capturer.width(), capturer.height());
loop {
// Wait until there's a frame.
let buffer = match capturer.frame(0) {
Ok(buffer) => buffer,
Err(error) => {
if error.kind() == WouldBlock {
// Keep spinning.
thread::sleep(one_frame);
continue;
} else {
panic!("Error: {}", error);
}
}
};
println!("Captured! Saving...");
// Flip the BGRA image into a RGBA image.
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = buffer.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 4 * x;
bitflipped.extend_from_slice(&[buffer[i + 2], buffer[i + 1], buffer[i], 255]);
}
}
// Save the image.
let name = format!("screenshot{}_1.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
break;
}
drop(capturer);
let display = get_display(i);
let mut capturer = Capturer::new(display, true).expect("Couldn't begin capture.");
let (w, h) = (capturer.width(), capturer.height());
loop {
// Wait until there's a frame.
let buffer = match capturer.frame(0) {
Ok(buffer) => buffer,
Err(error) => {
if error.kind() == WouldBlock {
// Keep spinning.
thread::sleep(one_frame);
continue;
} else {
panic!("Error: {}", error);
}
}
};
println!("Captured! Saving...");
let mut frame = Default::default();
i420_to_rgb(w, h, &buffer, &mut frame);
let mut bitflipped = Vec::with_capacity(w * h * 4);
let stride = frame.len() / h;
for y in 0..h {
for x in 0..w {
let i = stride * y + 3 * x;
bitflipped.extend_from_slice(&[frame[i], frame[i + 1], frame[i + 2], 255]);
}
}
let name = format!("screenshot{}_2.png", i);
repng::encode(
File::create(name.clone()).unwrap(),
w as u32,
h as u32,
&bitflipped,
)
.unwrap();
println!("Image saved to `{}`.", name);
break;
}
}

View File

@@ -0,0 +1,536 @@
// https://github.com/astraw/vpx-encode
// https://github.com/astraw/env-libvpx-sys
// https://github.com/rust-av/vpx-rs/blob/master/src/decoder.rs
use super::vpx::{vp8e_enc_control_id::*, vpx_codec_err_t::*, *};
use std::os::raw::{c_int, c_uint};
use std::{ptr, slice};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum VideoCodecId {
VP8,
VP9,
}
impl Default for VideoCodecId {
fn default() -> VideoCodecId {
VideoCodecId::VP9
}
}
pub struct Encoder {
ctx: vpx_codec_ctx_t,
width: usize,
height: usize,
}
pub struct Decoder {
ctx: vpx_codec_ctx_t,
}
#[derive(Debug)]
pub enum Error {
FailedCall(String),
BadPtr(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{:?}", self)
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
macro_rules! call_vpx {
($x:expr) => {{
let result = unsafe { $x }; // original expression
let result_int = unsafe { std::mem::transmute::<_, i32>(result) };
if result_int != 0 {
return Err(Error::FailedCall(format!(
"errcode={} {}:{}:{}:{}",
result_int,
module_path!(),
file!(),
line!(),
column!()
))
.into());
}
result
}};
}
macro_rules! call_vpx_ptr {
($x:expr) => {{
let result = unsafe { $x }; // original expression
let result_int = unsafe { std::mem::transmute::<_, i64>(result) };
if result_int == 0 {
return Err(Error::BadPtr(format!(
"errcode={} {}:{}:{}:{}",
result_int,
module_path!(),
file!(),
line!(),
column!()
))
.into());
}
result
}};
}
impl Encoder {
pub fn new(config: &Config, num_threads: u32) -> Result<Self> {
let i;
if cfg!(feature = "VP8") {
i = match config.codec {
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
};
} else {
i = call_vpx_ptr!(vpx_codec_vp9_cx());
}
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
// https://www.webmproject.org/docs/encoder-parameters/
// default: c.rc_min_quantizer = 0, c.rc_max_quantizer = 63
// try rc_resize_allowed later
c.g_w = config.width;
c.g_h = config.height;
c.g_timebase.num = config.timebase[0];
c.g_timebase.den = config.timebase[1];
c.rc_target_bitrate = config.bitrate;
c.rc_undershoot_pct = 95;
c.rc_dropframe_thresh = 25;
if config.rc_min_quantizer > 0 {
c.rc_min_quantizer = config.rc_min_quantizer;
}
if config.rc_max_quantizer > 0 {
c.rc_max_quantizer = config.rc_max_quantizer;
}
let mut speed = config.speed;
if speed <= 0 {
speed = 6;
}
c.g_threads = if num_threads == 0 {
num_cpus::get() as _
} else {
num_threads
};
c.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
// https://developers.google.com/media/vp9/bitrate-modes/
// Constant Bitrate mode (CBR) is recommended for live streaming with VP9.
c.rc_end_usage = vpx_rc_mode::VPX_CBR;
// c.kf_min_dist = 0;
// c.kf_max_dist = 999999;
c.kf_mode = vpx_kf_mode::VPX_KF_DISABLED; // reduce bandwidth a lot
/*
VPX encoder支持two-pass encode这是为了rate control的。
对于两遍编码,就是需要整个编码过程做两次,第一次会得到一些新的控制参数来进行第二遍的编码,
这样可以在相同的bitrate下得到最好的PSNR
*/
let mut ctx = Default::default();
call_vpx!(vpx_codec_enc_init_ver(
&mut ctx,
i,
&c,
0,
VPX_ENCODER_ABI_VERSION as _
));
if config.codec == VideoCodecId::VP9 {
// set encoder internal speed settings
// in ffmpeg, it is --speed option
/*
set to 0 or a positive value 1-16, the codec will try to adapt its
complexity depending on the time it spends encoding. Increasing this
number will make the speed go up and the quality go down.
Negative values mean strict enforcement of this
while positive values are adaptive
*/
/* https://developers.google.com/media/vp9/live-encoding
Speed 5 to 8 should be used for live / real-time encoding.
Lower numbers (5 or 6) are higher quality but require more CPU power.
Higher numbers (7 or 8) will be lower quality but more manageable for lower latency
use cases and also for lower CPU power devices such as mobile.
*/
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, speed,));
// set row level multi-threading
/*
as some people in comments and below have already commented,
more recent versions of libvpx support -row-mt 1 to enable tile row
multi-threading. This can increase the number of tiles by up to 4x in VP9
(since the max number of tile rows is 4, regardless of video height).
To enable this, use -tile-rows N where N is the number of tile rows in
log2 units (so -tile-rows 1 means 2 tile rows and -tile-rows 2 means 4 tile
rows). The total number of active threads will then be equal to
$tile_rows * $tile_columns
*/
call_vpx!(vpx_codec_control_(
&mut ctx,
VP9E_SET_ROW_MT as _,
1 as c_int
));
call_vpx!(vpx_codec_control_(
&mut ctx,
VP9E_SET_TILE_COLUMNS as _,
4 as c_int
));
}
Ok(Self {
ctx,
width: config.width as _,
height: config.height as _,
})
}
pub fn encode(&mut self, pts: i64, data: &[u8], stride_align: usize) -> Result<EncodeFrames> {
assert!(2 * data.len() >= 3 * self.width * self.height);
let mut image = Default::default();
call_vpx_ptr!(vpx_img_wrap(
&mut image,
vpx_img_fmt::VPX_IMG_FMT_I420,
self.width as _,
self.height as _,
stride_align as _,
data.as_ptr() as _,
));
call_vpx!(vpx_codec_encode(
&mut self.ctx,
&image,
pts as _,
1, // Duration
0, // Flags
VPX_DL_REALTIME as _,
));
Ok(EncodeFrames {
ctx: &mut self.ctx,
iter: ptr::null(),
})
}
/// Notify the encoder to return any pending packets
pub fn flush(&mut self) -> Result<EncodeFrames> {
call_vpx!(vpx_codec_encode(
&mut self.ctx,
ptr::null(),
-1, // PTS
1, // Duration
0, // Flags
VPX_DL_REALTIME as _,
));
Ok(EncodeFrames {
ctx: &mut self.ctx,
iter: ptr::null(),
})
}
}
impl Drop for Encoder {
fn drop(&mut self) {
unsafe {
let result = vpx_codec_destroy(&mut self.ctx);
if result != VPX_CODEC_OK {
panic!("failed to destroy vpx codec");
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct EncodeFrame<'a> {
/// Compressed data.
pub data: &'a [u8],
/// Whether the frame is a keyframe.
pub key: bool,
/// Presentation timestamp (in timebase units).
pub pts: i64,
}
#[derive(Clone, Copy, Debug)]
pub struct Config {
/// The width (in pixels).
pub width: c_uint,
/// The height (in pixels).
pub height: c_uint,
/// The timebase numerator and denominator (in seconds).
pub timebase: [c_int; 2],
/// The target bitrate (in kilobits per second).
pub bitrate: c_uint,
/// The codec
pub codec: VideoCodecId,
pub rc_min_quantizer: u32,
pub rc_max_quantizer: u32,
pub speed: i32,
}
pub struct EncodeFrames<'a> {
ctx: &'a mut vpx_codec_ctx_t,
iter: vpx_codec_iter_t,
}
impl<'a> Iterator for EncodeFrames<'a> {
type Item = EncodeFrame<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
unsafe {
let pkt = vpx_codec_get_cx_data(self.ctx, &mut self.iter);
if pkt.is_null() {
return None;
} else if (*pkt).kind == vpx_codec_cx_pkt_kind::VPX_CODEC_CX_FRAME_PKT {
let f = &(*pkt).data.frame;
return Some(Self::Item {
data: slice::from_raw_parts(f.buf as _, f.sz as _),
key: (f.flags & VPX_FRAME_IS_KEY) != 0,
pts: f.pts,
});
} else {
// Ignore the packet.
}
}
}
}
}
impl Decoder {
/// Create a new decoder
///
/// # Errors
///
/// The function may fail if the underlying libvpx does not provide
/// the VP9 decoder.
pub fn new(codec: VideoCodecId, num_threads: u32) -> Result<Self> {
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
// cause UB if uninitialized.
let i;
if cfg!(feature = "VP8") {
i = match codec {
VideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
VideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
};
} else {
i = call_vpx_ptr!(vpx_codec_vp9_dx());
}
let mut ctx = Default::default();
let cfg = vpx_codec_dec_cfg_t {
threads: if num_threads == 0 {
num_cpus::get() as _
} else {
num_threads
},
w: 0,
h: 0,
};
/*
unsafe {
println!("{}", vpx_codec_get_caps(i));
}
*/
call_vpx!(vpx_codec_dec_init_ver(
&mut ctx,
i,
&cfg,
0,
VPX_DECODER_ABI_VERSION as _,
));
Ok(Self { ctx })
}
pub fn decode2rgb(&mut self, data: &[u8], rgba: bool) -> Result<Vec<u8>> {
let mut img = Image::new();
for frame in self.decode(data)? {
drop(img);
img = frame;
}
for frame in self.flush()? {
drop(img);
img = frame;
}
if img.is_null() {
Ok(Vec::new())
} else {
let mut out = Default::default();
img.rgb(1, rgba, &mut out);
Ok(out)
}
}
/// Feed some compressed data to the encoder
///
/// The `data` slice is sent to the decoder
///
/// It matches a call to `vpx_codec_decode`.
pub fn decode(&mut self, data: &[u8]) -> Result<DecodeFrames> {
call_vpx!(vpx_codec_decode(
&mut self.ctx,
data.as_ptr(),
data.len() as _,
ptr::null_mut(),
0,
));
Ok(DecodeFrames {
ctx: &mut self.ctx,
iter: ptr::null(),
})
}
/// Notify the decoder to return any pending frame
pub fn flush(&mut self) -> Result<DecodeFrames> {
call_vpx!(vpx_codec_decode(
&mut self.ctx,
ptr::null(),
0,
ptr::null_mut(),
0
));
Ok(DecodeFrames {
ctx: &mut self.ctx,
iter: ptr::null(),
})
}
}
impl Drop for Decoder {
fn drop(&mut self) {
unsafe {
let result = vpx_codec_destroy(&mut self.ctx);
if result != VPX_CODEC_OK {
panic!("failed to destroy vpx codec");
}
}
}
}
pub struct DecodeFrames<'a> {
ctx: &'a mut vpx_codec_ctx_t,
iter: vpx_codec_iter_t,
}
impl<'a> Iterator for DecodeFrames<'a> {
type Item = Image;
fn next(&mut self) -> Option<Self::Item> {
let img = unsafe { vpx_codec_get_frame(self.ctx, &mut self.iter) };
if img.is_null() {
return None;
} else {
return Some(Image(img));
}
}
}
// https://chromium.googlesource.com/webm/libvpx/+/bali/vpx/src/vpx_image.c
pub struct Image(*mut vpx_image_t);
impl Image {
#[inline]
pub fn new() -> Self {
Self(std::ptr::null_mut())
}
#[inline]
pub fn is_null(&self) -> bool {
self.0.is_null()
}
#[inline]
pub fn width(&self) -> usize {
self.inner().d_w as _
}
#[inline]
pub fn height(&self) -> usize {
self.inner().d_h as _
}
#[inline]
pub fn format(&self) -> vpx_img_fmt_t {
// VPX_IMG_FMT_I420
self.inner().fmt
}
#[inline]
pub fn inner(&self) -> &vpx_image_t {
unsafe { &*self.0 }
}
#[inline]
pub fn stride(&self, iplane: usize) -> i32 {
self.inner().stride[iplane]
}
pub fn rgb(&self, stride_align: usize, rgba: bool, dst: &mut Vec<u8>) {
let h = self.height();
let mut w = self.width();
let bps = if rgba { 4 } else { 3 };
w = (w + stride_align - 1) & !(stride_align - 1);
dst.resize(h * w * bps, 0);
let img = self.inner();
unsafe {
if rgba {
super::I420ToARGB(
img.planes[0],
img.stride[0],
img.planes[1],
img.stride[1],
img.planes[2],
img.stride[2],
dst.as_mut_ptr(),
(w * bps) as _,
self.width() as _,
self.height() as _,
);
} else {
super::I420ToRAW(
img.planes[0],
img.stride[0],
img.planes[1],
img.stride[1],
img.planes[2],
img.stride[2],
dst.as_mut_ptr(),
(w * bps) as _,
self.width() as _,
self.height() as _,
);
}
}
}
#[inline]
pub fn data(&self) -> (&[u8], &[u8], &[u8]) {
unsafe {
let img = self.inner();
let h = (img.d_h as usize + 1) & !1;
let n = img.stride[0] as usize * h;
let y = slice::from_raw_parts(img.planes[0], n);
let n = img.stride[1] as usize * (h >> 1);
let u = slice::from_raw_parts(img.planes[1], n);
let v = slice::from_raw_parts(img.planes[2], n);
(y, u, v)
}
}
}
impl Drop for Image {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { vpx_img_free(self.0) };
}
}
}
unsafe impl Send for vpx_codec_ctx_t {}

View File

@@ -0,0 +1,188 @@
use super::vpx::*;
use std::os::raw::c_int;
extern "C" {
// seems libyuv uses reverse byte order compared with our view
pub fn ARGBRotate(
src_argb: *const u8,
src_stride_argb: c_int,
dst_argb: *mut u8,
dst_stride_argb: c_int,
width: c_int,
height: c_int,
mode: c_int,
) -> c_int;
pub fn ARGBMirror(
src_argb: *const u8,
src_stride_argb: c_int,
dst_argb: *mut u8,
dst_stride_argb: c_int,
width: c_int,
height: c_int,
) -> c_int;
pub fn ARGBToI420(
src_bgra: *const u8,
src_stride_bgra: c_int,
dst_y: *mut u8,
dst_stride_y: c_int,
dst_u: *mut u8,
dst_stride_u: c_int,
dst_v: *mut u8,
dst_stride_v: c_int,
width: c_int,
height: c_int,
) -> c_int;
pub fn NV12ToI420(
src_y: *const u8,
src_stride_y: c_int,
src_uv: *const u8,
src_stride_uv: c_int,
dst_y: *mut u8,
dst_stride_y: c_int,
dst_u: *mut u8,
dst_stride_u: c_int,
dst_v: *mut u8,
dst_stride_v: c_int,
width: c_int,
height: c_int,
) -> c_int;
// I420ToRGB24: RGB little endian (bgr in memory)
// I420ToRaw: RGB big endian (rgb in memory) to RGBA.
pub fn I420ToRAW(
src_y: *const u8,
src_stride_y: c_int,
src_u: *const u8,
src_stride_u: c_int,
src_v: *const u8,
src_stride_v: c_int,
dst_rgba: *mut u8,
dst_stride_raw: c_int,
width: c_int,
height: c_int,
) -> c_int;
pub fn I420ToARGB(
src_y: *const u8,
src_stride_y: c_int,
src_u: *const u8,
src_stride_u: c_int,
src_v: *const u8,
src_stride_v: c_int,
dst_rgba: *mut u8,
dst_stride_rgba: c_int,
width: c_int,
height: c_int,
) -> c_int;
}
// https://github.com/webmproject/libvpx/blob/master/vpx/src/vpx_image.c
#[inline]
fn get_vpx_i420_stride(
width: usize,
height: usize,
stride_align: usize,
) -> (usize, usize, usize, usize, usize, usize) {
let mut img = Default::default();
unsafe {
vpx_img_wrap(
&mut img,
vpx_img_fmt::VPX_IMG_FMT_I420,
width as _,
height as _,
stride_align as _,
0x1 as _,
);
}
(
img.w as _,
img.h as _,
img.stride[0] as _,
img.stride[1] as _,
img.planes[1] as usize - img.planes[0] as usize,
img.planes[2] as usize - img.planes[0] as usize,
)
}
pub fn i420_to_rgb(width: usize, height: usize, src: &[u8], dst: &mut Vec<u8>) {
let (_, _, src_stride_y, src_stride_uv, u, v) =
get_vpx_i420_stride(width, height, super::STRIDE_ALIGN);
let src_y = src.as_ptr();
let src_u = src[u..].as_ptr();
let src_v = src[v..].as_ptr();
dst.resize(width * height * 3, 0);
unsafe {
super::I420ToRAW(
src_y,
src_stride_y as _,
src_u,
src_stride_uv as _,
src_v,
src_stride_uv as _,
dst.as_mut_ptr(),
(width * 3) as _,
width as _,
height as _,
);
};
}
pub fn bgra_to_i420(width: usize, height: usize, src: &[u8], dst: &mut Vec<u8>) {
let (_, h, dst_stride_y, dst_stride_uv, u, v) =
get_vpx_i420_stride(width, height, super::STRIDE_ALIGN);
let bps = 12;
dst.resize(h * dst_stride_y * bps / 8, 0);
let dst_y = dst.as_mut_ptr();
let dst_u = dst[u..].as_mut_ptr();
let dst_v = dst[v..].as_mut_ptr();
unsafe {
ARGBToI420(
src.as_ptr(),
(src.len() / height) as _,
dst_y,
dst_stride_y as _,
dst_u,
dst_stride_uv as _,
dst_v,
dst_stride_uv as _,
width as _,
height as _,
);
}
}
pub unsafe fn nv12_to_i420(
src_y: *const u8,
src_stride_y: c_int,
src_uv: *const u8,
src_stride_uv: c_int,
width: usize,
height: usize,
dst: &mut Vec<u8>,
) {
let (w, h, dst_stride_y, dst_stride_uv, u, v) =
get_vpx_i420_stride(width, height, super::STRIDE_ALIGN);
let bps = 12;
dst.resize(h * w * bps / 8, 0);
let dst_y = dst.as_mut_ptr();
let dst_u = dst[u..].as_mut_ptr();
let dst_v = dst[v..].as_mut_ptr();
NV12ToI420(
src_y,
src_stride_y,
src_uv,
src_stride_uv,
dst_y,
dst_stride_y as _,
dst_u,
dst_stride_uv as _,
dst_v,
dst_stride_uv as _,
width as _,
height as _,
);
}

View File

@@ -0,0 +1,104 @@
use crate::dxgi;
use std::io::ErrorKind::{NotFound, TimedOut, WouldBlock};
use std::{io, ops};
pub struct Capturer {
inner: dxgi::Capturer,
width: usize,
height: usize,
}
impl Capturer {
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {
let width = display.width();
let height = display.height();
let inner = dxgi::Capturer::new(display.0, yuv)?;
Ok(Capturer {
inner,
width,
height,
})
}
pub fn is_gdi(&self) -> bool {
self.inner.is_gdi()
}
pub fn set_gdi(&mut self) -> bool {
self.inner.set_gdi()
}
pub fn cancel_gdi(&mut self) {
self.inner.cancel_gdi()
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn frame<'a>(&'a mut self, timeout_ms: u32) -> io::Result<Frame<'a>> {
match self.inner.frame(timeout_ms) {
Ok(frame) => Ok(Frame(frame)),
Err(ref error) if error.kind() == TimedOut => Err(WouldBlock.into()),
Err(error) => Err(error),
}
}
}
pub struct Frame<'a>(&'a [u8]);
impl<'a> ops::Deref for Frame<'a> {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.0
}
}
pub struct Display(dxgi::Display);
impl Display {
pub fn primary() -> io::Result<Display> {
match dxgi::Displays::new()?.next() {
Some(inner) => Ok(Display(inner)),
None => Err(NotFound.into()),
}
}
pub fn all() -> io::Result<Vec<Display>> {
Ok(dxgi::Displays::new()?.map(Display).collect::<Vec<_>>())
}
pub fn width(&self) -> usize {
self.0.width() as usize
}
pub fn height(&self) -> usize {
self.0.height() as usize
}
pub fn name(&self) -> String {
use std::ffi::OsString;
use std::os::windows::prelude::*;
OsString::from_wide(self.0.name())
.to_string_lossy()
.to_string()
}
pub fn is_online(&self) -> bool {
self.0.is_online()
}
pub fn origin(&self) -> (usize, usize) {
let o = self.0.origin();
(o.0 as usize, o.1 as usize)
}
// to-do: not found primary display api for dxgi
pub fn is_primary(&self) -> bool {
self.name() == Self::primary().unwrap().name()
}
}

View File

@@ -0,0 +1,23 @@
pub use self::codec::*;
cfg_if! {
if #[cfg(quartz)] {
mod quartz;
pub use self::quartz::*;
} else if #[cfg(x11)] {
mod x11;
pub use self::x11::*;
} else if #[cfg(dxgi)] {
mod dxgi;
pub use self::dxgi::*;
} else {
//TODO: Fallback implementation.
}
}
pub mod codec;
mod convert;
pub use self::convert::*;
pub const STRIDE_ALIGN: usize = 16; // commonly used in libvpx vpx_img_alloc caller
mod vpx;

View File

@@ -0,0 +1,125 @@
use crate::quartz;
use std::marker::PhantomData;
use std::sync::{Arc, Mutex, TryLockError};
use std::{io, mem, ops};
pub struct Capturer {
inner: quartz::Capturer,
frame: Arc<Mutex<Option<quartz::Frame>>>,
use_yuv: bool,
i420: Vec<u8>,
}
impl Capturer {
pub fn new(display: Display, use_yuv: bool) -> io::Result<Capturer> {
let frame = Arc::new(Mutex::new(None));
let f = frame.clone();
let inner = quartz::Capturer::new(
display.0,
display.width(),
display.height(),
if use_yuv {
quartz::PixelFormat::YCbCr420Full
} else {
quartz::PixelFormat::Argb8888
},
Default::default(),
move |inner| {
if let Ok(mut f) = f.lock() {
*f = Some(inner);
}
},
)
.map_err(|_| io::Error::from(io::ErrorKind::Other))?;
Ok(Capturer {
inner,
frame,
use_yuv,
i420: Vec::new(),
})
}
pub fn width(&self) -> usize {
self.inner.width()
}
pub fn height(&self) -> usize {
self.inner.height()
}
pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
match self.frame.try_lock() {
Ok(mut handle) => {
let mut frame = None;
mem::swap(&mut frame, &mut handle);
match frame {
Some(mut frame) => {
if self.use_yuv {
frame.nv12_to_i420(self.width(), self.height(), &mut self.i420);
}
Ok(Frame(frame, PhantomData))
}
None => Err(io::ErrorKind::WouldBlock.into()),
}
}
Err(TryLockError::WouldBlock) => Err(io::ErrorKind::WouldBlock.into()),
Err(TryLockError::Poisoned(..)) => Err(io::ErrorKind::Other.into()),
}
}
}
pub struct Frame<'a>(quartz::Frame, PhantomData<&'a [u8]>);
impl<'a> ops::Deref for Frame<'a> {
type Target = [u8];
fn deref(&self) -> &[u8] {
&*self.0
}
}
pub struct Display(quartz::Display);
impl Display {
pub fn primary() -> io::Result<Display> {
Ok(Display(quartz::Display::primary()))
}
pub fn all() -> io::Result<Vec<Display>> {
Ok(quartz::Display::online()
.map_err(|_| io::Error::from(io::ErrorKind::Other))?
.into_iter()
.map(Display)
.collect())
}
pub fn width(&self) -> usize {
self.0.width()
}
pub fn height(&self) -> usize {
self.0.height()
}
pub fn name(&self) -> String {
self.0.id().to_string()
}
pub fn is_online(&self) -> bool {
self.0.is_online()
}
pub fn origin(&self) -> (usize, usize) {
let o = self.0.bounds().origin;
(o.x as usize, o.y as usize)
}
pub fn is_primary(&self) -> bool {
self.0.is_primary()
}
}

View File

@@ -0,0 +1,25 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(improper_ctypes)]
#![allow(dead_code)]
impl Default for vpx_codec_enc_cfg {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
impl Default for vpx_codec_ctx {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
impl Default for vpx_image_t {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
include!(concat!(env!("OUT_DIR"), "/vpx_ffi.rs"));

View File

@@ -0,0 +1,87 @@
use crate::x11;
use std::{io, ops};
pub struct Capturer(x11::Capturer);
impl Capturer {
pub fn new(display: Display, yuv: bool) -> io::Result<Capturer> {
x11::Capturer::new(display.0, yuv).map(Capturer)
}
pub fn width(&self) -> usize {
self.0.display().rect().w as usize
}
pub fn height(&self) -> usize {
self.0.display().rect().h as usize
}
pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
Ok(Frame(self.0.frame()))
}
}
pub struct Frame<'a>(&'a [u8]);
impl<'a> ops::Deref for Frame<'a> {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.0
}
}
pub struct Display(x11::Display);
impl Display {
pub fn primary() -> io::Result<Display> {
let server = match x11::Server::default() {
Ok(server) => server,
Err(_) => return Err(io::ErrorKind::ConnectionRefused.into()),
};
let mut displays = x11::Server::displays(server);
let mut best = displays.next();
if best.as_ref().map(|x| x.is_default()) == Some(false) {
best = displays.find(|x| x.is_default()).or(best);
}
match best {
Some(best) => Ok(Display(best)),
None => Err(io::ErrorKind::NotFound.into()),
}
}
pub fn all() -> io::Result<Vec<Display>> {
let server = match x11::Server::default() {
Ok(server) => server,
Err(_) => return Err(io::ErrorKind::ConnectionRefused.into()),
};
Ok(x11::Server::displays(server).map(Display).collect())
}
pub fn width(&self) -> usize {
self.0.rect().w as usize
}
pub fn height(&self) -> usize {
self.0.rect().h as usize
}
pub fn origin(&self) -> (usize, usize) {
let r = self.0.rect();
(r.x as _, r.y as _)
}
pub fn is_online(&self) -> bool {
true
}
pub fn is_primary(&self) -> bool {
self.0.is_default()
}
pub fn name(&self) -> String {
"".to_owned()
}
}

212
libs/scrap/src/dxgi/gdi.rs Normal file
View File

@@ -0,0 +1,212 @@
use std::mem::size_of;
use winapi::{
shared::windef::{HBITMAP, HDC},
um::wingdi::{
BitBlt,
CreateCompatibleBitmap,
CreateCompatibleDC,
CreateDCW,
DeleteDC,
DeleteObject,
GetDIBits,
SelectObject,
BITMAPINFO,
BITMAPINFOHEADER,
BI_RGB,
DIB_RGB_COLORS, //CAPTUREBLT,
HGDI_ERROR,
RGBQUAD,
SRCCOPY,
},
};
const PIXEL_WIDTH: i32 = 4;
pub struct CapturerGDI {
screen_dc: HDC,
dc: HDC,
bmp: HBITMAP,
width: i32,
height: i32,
}
impl CapturerGDI {
pub fn new(name: &[u16], width: i32, height: i32) -> Result<Self, Box<dyn std::error::Error>> {
/* or Enumerate monitors with EnumDisplayMonitors,
https://stackoverflow.com/questions/34987695/how-can-i-get-an-hmonitor-handle-from-a-display-device-name
#[no_mangle]
pub extern "C" fn callback(m: HMONITOR, dc: HDC, rect: LPRECT, lp: LPARAM) -> BOOL {}
*/
/*
shared::windef::HMONITOR,
winuser::{GetMonitorInfoW, GetSystemMetrics, MONITORINFOEXW},
let mut mi: MONITORINFOEXW = std::mem::MaybeUninit::uninit().assume_init();
mi.cbSize = size_of::<MONITORINFOEXW>() as _;
if GetMonitorInfoW(m, &mut mi as *mut MONITORINFOEXW as _) == 0 {
return Err(format!("Failed to get monitor information of: {:?}", m).into());
}
*/
unsafe {
if name.is_empty() {
return Err("Empty display name".into());
}
let screen_dc = CreateDCW(&name[0], 0 as _, 0 as _, 0 as _);
if screen_dc.is_null() {
return Err("Failed to create dc from monitor name".into());
}
// Create a Windows Bitmap, and copy the bits into it
let dc = CreateCompatibleDC(screen_dc);
if dc.is_null() {
DeleteDC(screen_dc);
return Err("Can't get a Windows display".into());
}
let bmp = CreateCompatibleBitmap(screen_dc, width, height);
if bmp.is_null() {
DeleteDC(screen_dc);
DeleteDC(dc);
return Err("Can't create a Windows buffer".into());
}
let res = SelectObject(dc, bmp as _);
if res.is_null() || res == HGDI_ERROR {
DeleteDC(screen_dc);
DeleteDC(dc);
DeleteObject(bmp as _);
return Err("Can't select Windows buffer".into());
}
Ok(Self {
screen_dc,
dc,
bmp,
width,
height,
})
}
}
pub fn frame(&self, data: &mut Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
unsafe {
let res = BitBlt(
self.dc,
0,
0,
self.width,
self.height,
self.screen_dc,
0,
0,
SRCCOPY, // | CAPTUREBLT, // CAPTUREBLT enable layered window but also make cursor blinking
);
if res == 0 {
return Err("Failed to copy screen to Windows buffer".into());
}
let stride = self.width * PIXEL_WIDTH;
let size: usize = (stride * self.height) as usize;
let mut data1: Vec<u8> = Vec::with_capacity(size);
data1.set_len(size);
data.resize(size, 0);
let mut bmi = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: size_of::<BITMAPINFOHEADER>() as _,
biWidth: self.width as _,
biHeight: self.height as _,
biPlanes: 1,
biBitCount: (8 * PIXEL_WIDTH) as _,
biCompression: BI_RGB,
biSizeImage: (self.width * self.height * PIXEL_WIDTH) as _,
biXPelsPerMeter: 0,
biYPelsPerMeter: 0,
biClrUsed: 0,
biClrImportant: 0,
},
bmiColors: [RGBQUAD {
rgbBlue: 0,
rgbGreen: 0,
rgbRed: 0,
rgbReserved: 0,
}],
};
// copy bits into Vec
let res = GetDIBits(
self.dc,
self.bmp,
0,
self.height as _,
&mut data[0] as *mut u8 as _,
&mut bmi as _,
DIB_RGB_COLORS,
);
if res == 0 {
return Err("GetDIBits failed".into());
}
crate::common::ARGBMirror(
data.as_ptr(),
stride,
data1.as_mut_ptr(),
stride,
self.width,
self.height,
);
crate::common::ARGBRotate(
data1.as_ptr(),
stride,
data.as_mut_ptr(),
stride,
self.width,
self.height,
180,
);
Ok(())
}
}
}
impl Drop for CapturerGDI {
fn drop(&mut self) {
unsafe {
DeleteDC(self.screen_dc);
DeleteDC(self.dc);
DeleteObject(self.bmp as _);
}
}
}
#[cfg(test)]
mod tests {
use super::super::*;
use super::*;
#[test]
fn test() {
match Displays::new().unwrap().next() {
Some(d) => {
let w = d.width();
let h = d.height();
let c = CapturerGDI::new(d.name(), w, h).unwrap();
let mut data = Vec::new();
c.frame(&mut data).unwrap();
let mut bitflipped = Vec::with_capacity((w * h * 4) as usize);
for y in 0..h {
for x in 0..w {
let i = (w * 4 * y + 4 * x) as usize;
bitflipped.extend_from_slice(&[data[i + 2], data[i + 1], data[i], 255]);
}
}
repng::encode(
std::fs::File::create("gdi_screen.png").unwrap(),
d.width() as u32,
d.height() as u32,
&bitflipped,
)
.unwrap();
}
_ => {
assert!(false);
}
}
}
}

539
libs/scrap/src/dxgi/mod.rs Normal file
View File

@@ -0,0 +1,539 @@
use std::{io, mem, ptr, slice};
pub mod gdi;
pub use gdi::CapturerGDI;
use winapi::{
shared::dxgi::{
CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIResource, IDXGISurface,
IID_IDXGIFactory1, IID_IDXGISurface, DXGI_MAP_READ, DXGI_OUTPUT_DESC,
DXGI_RESOURCE_PRIORITY_MAXIMUM,
},
shared::dxgi1_2::IDXGIOutputDuplication,
// shared::dxgiformat::{DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE},
shared::dxgi1_2::{IDXGIOutput1, IID_IDXGIOutput1},
shared::dxgitype::DXGI_MODE_ROTATION,
shared::minwindef::{TRUE, UINT},
shared::ntdef::LONG,
shared::windef::HMONITOR,
shared::winerror::{
DXGI_ERROR_ACCESS_LOST, DXGI_ERROR_INVALID_CALL, DXGI_ERROR_NOT_CURRENTLY_AVAILABLE,
DXGI_ERROR_SESSION_DISCONNECTED, DXGI_ERROR_UNSUPPORTED, DXGI_ERROR_WAIT_TIMEOUT,
E_ACCESSDENIED, E_INVALIDARG, S_OK,
},
um::d3d11::{
D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, IID_ID3D11Texture2D,
D3D11_CPU_ACCESS_READ, D3D11_SDK_VERSION, D3D11_USAGE_STAGING,
},
um::d3dcommon::D3D_DRIVER_TYPE_UNKNOWN,
um::winnt::HRESULT,
};
//TODO: Split up into files.
pub struct Capturer {
device: *mut ID3D11Device,
display: Display,
context: *mut ID3D11DeviceContext,
duplication: *mut IDXGIOutputDuplication,
fastlane: bool,
surface: *mut IDXGISurface,
data: *const u8,
len: usize,
width: usize,
height: usize,
use_yuv: bool,
yuv: Vec<u8>,
gdi_capturer: Option<CapturerGDI>,
gdi_buffer: Vec<u8>,
}
impl Capturer {
pub fn new(display: Display, use_yuv: bool) -> io::Result<Capturer> {
let mut device = ptr::null_mut();
let mut context = ptr::null_mut();
let mut duplication = ptr::null_mut();
let mut desc = unsafe { mem::MaybeUninit::uninit().assume_init() };
let mut gdi_capturer = None;
let mut res = wrap_hresult(unsafe {
D3D11CreateDevice(
display.adapter as *mut _,
D3D_DRIVER_TYPE_UNKNOWN,
ptr::null_mut(), // No software rasterizer.
0, // No device flags.
ptr::null_mut(), // Feature levels.
0, // Feature levels' length.
D3D11_SDK_VERSION,
&mut device,
ptr::null_mut(),
&mut context,
)
});
if res.is_err() {
gdi_capturer = display.create_gdi();
println!("Fallback to GDI");
if gdi_capturer.is_some() {
res = Ok(());
}
} else {
res = wrap_hresult(unsafe {
let hres = (*display.inner).DuplicateOutput(device as *mut _, &mut duplication);
if hres != S_OK {
gdi_capturer = display.create_gdi();
println!("Fallback to GDI");
if gdi_capturer.is_some() {
S_OK
} else {
hres
}
} else {
hres
}
// NVFBC(NVIDIA Capture SDK) which xpra used already deprecated, https://developer.nvidia.com/capture-sdk
// also try high version DXGI for better performance, e.g.
// https://docs.microsoft.com/zh-cn/windows/win32/direct3ddxgi/dxgi-1-2-improvements
// dxgi-1-6 may too high, only support win10 (2018)
// https://docs.microsoft.com/zh-cn/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
// DXGI_FORMAT_420_OPAQUE
// IDXGIOutputDuplication::GetFrameDirtyRects and IDXGIOutputDuplication::GetFrameMoveRects
// can help us update screen incrementally
/* // not supported on my PC, try in the future
let format : Vec<DXGI_FORMAT> = vec![DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE];
(*display.inner).DuplicateOutput1(
device as *mut _,
0 as UINT,
2 as UINT,
format.as_ptr(),
&mut duplication
)
*/
// if above not work, I think below should not work either, try later
// https://developer.nvidia.com/capture-sdk deprecated
// examples using directx + nvideo sdk for GPU-accelerated video encoding/decoding
// https://github.com/NVIDIA/video-sdk-samples
});
}
if let Err(err) = res {
unsafe {
if !device.is_null() {
(*device).Release();
}
if !context.is_null() {
(*context).Release();
}
}
return Err(err);
}
if !duplication.is_null() {
unsafe {
(*duplication).GetDesc(&mut desc);
}
}
Ok(Capturer {
device,
context,
duplication,
fastlane: desc.DesktopImageInSystemMemory == TRUE,
surface: ptr::null_mut(),
width: display.width() as usize,
height: display.height() as usize,
display,
data: ptr::null(),
len: 0,
use_yuv,
yuv: Vec::new(),
gdi_capturer,
gdi_buffer: Vec::new(),
})
}
pub fn is_gdi(&self) -> bool {
self.gdi_capturer.is_some()
}
pub fn set_gdi(&mut self) -> bool {
self.gdi_capturer = self.display.create_gdi();
self.is_gdi()
}
pub fn cancel_gdi(&mut self) {
self.gdi_buffer = Vec::new();
self.gdi_capturer.take();
}
unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<()> {
let mut frame = ptr::null_mut();
let mut info = mem::MaybeUninit::uninit().assume_init();
self.data = ptr::null();
wrap_hresult((*self.duplication).AcquireNextFrame(timeout, &mut info, &mut frame))?;
if *info.LastPresentTime.QuadPart() == 0 {
return Err(std::io::ErrorKind::WouldBlock.into());
}
if self.fastlane {
let mut rect = mem::MaybeUninit::uninit().assume_init();
let res = wrap_hresult((*self.duplication).MapDesktopSurface(&mut rect));
(*frame).Release();
if let Err(err) = res {
Err(err)
} else {
self.data = rect.pBits;
self.len = self.height * rect.Pitch as usize;
Ok(())
}
} else {
self.surface = ptr::null_mut();
self.surface = self.ohgodwhat(frame)?;
let mut rect = mem::MaybeUninit::uninit().assume_init();
wrap_hresult((*self.surface).Map(&mut rect, DXGI_MAP_READ))?;
self.data = rect.pBits;
self.len = self.height * rect.Pitch as usize;
Ok(())
}
}
// copy from GPU memory to system memory
unsafe fn ohgodwhat(&mut self, frame: *mut IDXGIResource) -> io::Result<*mut IDXGISurface> {
let mut texture: *mut ID3D11Texture2D = ptr::null_mut();
(*frame).QueryInterface(
&IID_ID3D11Texture2D,
&mut texture as *mut *mut _ as *mut *mut _,
);
let mut texture_desc = mem::MaybeUninit::uninit().assume_init();
(*texture).GetDesc(&mut texture_desc);
texture_desc.Usage = D3D11_USAGE_STAGING;
texture_desc.BindFlags = 0;
texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
texture_desc.MiscFlags = 0;
let mut readable = ptr::null_mut();
let res = wrap_hresult((*self.device).CreateTexture2D(
&mut texture_desc,
ptr::null(),
&mut readable,
));
if let Err(err) = res {
(*frame).Release();
(*texture).Release();
(*readable).Release();
Err(err)
} else {
(*readable).SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
let mut surface = ptr::null_mut();
(*readable).QueryInterface(
&IID_IDXGISurface,
&mut surface as *mut *mut _ as *mut *mut _,
);
(*self.context).CopyResource(readable as *mut _, texture as *mut _);
(*frame).Release();
(*texture).Release();
(*readable).Release();
Ok(surface)
}
}
pub fn frame<'a>(&'a mut self, timeout: UINT) -> io::Result<&'a [u8]> {
unsafe {
// Release last frame.
// No error checking needed because we don't care.
// None of the errors crash anyway.
let result = {
if let Some(gdi_capturer) = &self.gdi_capturer {
match gdi_capturer.frame(&mut self.gdi_buffer) {
Ok(_) => &self.gdi_buffer,
Err(err) => {
return Err(io::Error::new(io::ErrorKind::Other, err.to_string()));
}
}
} else {
if self.fastlane {
(*self.duplication).UnMapDesktopSurface();
} else {
if !self.surface.is_null() {
(*self.surface).Unmap();
(*self.surface).Release();
self.surface = ptr::null_mut();
}
}
(*self.duplication).ReleaseFrame();
self.load_frame(timeout)?;
slice::from_raw_parts(self.data, self.len)
}
};
Ok({
if self.use_yuv {
crate::common::bgra_to_i420(
self.width as usize,
self.height as usize,
&result,
&mut self.yuv,
);
&self.yuv[..]
} else {
result
}
})
}
}
}
impl Drop for Capturer {
fn drop(&mut self) {
unsafe {
if !self.surface.is_null() {
(*self.surface).Unmap();
(*self.surface).Release();
}
if !self.duplication.is_null() {
(*self.duplication).Release();
}
if !self.device.is_null() {
(*self.device).Release();
}
if !self.context.is_null() {
(*self.context).Release();
}
}
}
}
pub struct Displays {
factory: *mut IDXGIFactory1,
adapter: *mut IDXGIAdapter1,
/// Index of the CURRENT adapter.
nadapter: UINT,
/// Index of the NEXT display to fetch.
ndisplay: UINT,
}
impl Displays {
pub fn new() -> io::Result<Displays> {
let mut factory = ptr::null_mut();
wrap_hresult(unsafe { CreateDXGIFactory1(&IID_IDXGIFactory1, &mut factory) })?;
let factory = factory as *mut IDXGIFactory1;
let mut adapter = ptr::null_mut();
unsafe {
// On error, our adapter is null, so it's fine.
(*factory).EnumAdapters1(0, &mut adapter);
};
Ok(Displays {
factory,
adapter,
nadapter: 0,
ndisplay: 0,
})
}
// No Adapter => Some(None)
// Non-Empty Adapter => Some(Some(OUTPUT))
// End of Adapter => None
fn read_and_invalidate(&mut self) -> Option<Option<Display>> {
// If there is no adapter, there is nothing left for us to do.
if self.adapter.is_null() {
return Some(None);
}
// Otherwise, we get the next output of the current adapter.
let output = unsafe {
let mut output = ptr::null_mut();
(*self.adapter).EnumOutputs(self.ndisplay, &mut output);
output
};
// If the current adapter is done, we free it.
// We return None so the caller gets the next adapter and tries again.
if output.is_null() {
unsafe {
(*self.adapter).Release();
self.adapter = ptr::null_mut();
}
return None;
}
// Advance to the next display.
self.ndisplay += 1;
// We get the display's details.
let desc = unsafe {
let mut desc = mem::MaybeUninit::uninit().assume_init();
(*output).GetDesc(&mut desc);
desc
};
// We cast it up to the version needed for desktop duplication.
let mut inner: *mut IDXGIOutput1 = ptr::null_mut();
unsafe {
(*output).QueryInterface(&IID_IDXGIOutput1, &mut inner as *mut *mut _ as *mut *mut _);
(*output).Release();
}
// If it's null, we have an error.
// So we act like the adapter is done.
if inner.is_null() {
unsafe {
(*self.adapter).Release();
self.adapter = ptr::null_mut();
}
return None;
}
unsafe {
(*self.adapter).AddRef();
}
Some(Some(Display {
inner,
adapter: self.adapter,
desc,
}))
}
}
impl Iterator for Displays {
type Item = Display;
fn next(&mut self) -> Option<Display> {
if let Some(res) = self.read_and_invalidate() {
res
} else {
// We need to replace the adapter.
self.ndisplay = 0;
self.nadapter += 1;
self.adapter = unsafe {
let mut adapter = ptr::null_mut();
(*self.factory).EnumAdapters1(self.nadapter, &mut adapter);
adapter
};
if let Some(res) = self.read_and_invalidate() {
res
} else {
// All subsequent adapters will also be empty.
None
}
}
}
}
impl Drop for Displays {
fn drop(&mut self) {
unsafe {
(*self.factory).Release();
if !self.adapter.is_null() {
(*self.adapter).Release();
}
}
}
}
pub struct Display {
inner: *mut IDXGIOutput1,
adapter: *mut IDXGIAdapter1,
desc: DXGI_OUTPUT_DESC,
}
impl Display {
pub fn width(&self) -> LONG {
self.desc.DesktopCoordinates.right - self.desc.DesktopCoordinates.left
}
pub fn height(&self) -> LONG {
self.desc.DesktopCoordinates.bottom - self.desc.DesktopCoordinates.top
}
pub fn attached_to_desktop(&self) -> bool {
self.desc.AttachedToDesktop != 0
}
pub fn rotation(&self) -> DXGI_MODE_ROTATION {
self.desc.Rotation
}
fn create_gdi(&self) -> Option<CapturerGDI> {
if let Ok(res) = CapturerGDI::new(self.name(), self.width(), self.height()) {
Some(res)
} else {
None
}
}
pub fn hmonitor(&self) -> HMONITOR {
self.desc.Monitor
}
pub fn name(&self) -> &[u16] {
let s = &self.desc.DeviceName;
let i = s.iter().position(|&x| x == 0).unwrap_or(s.len());
&s[..i]
}
pub fn is_online(&self) -> bool {
self.desc.AttachedToDesktop != 0
}
pub fn origin(&self) -> (LONG, LONG) {
(
self.desc.DesktopCoordinates.left,
self.desc.DesktopCoordinates.top,
)
}
}
impl Drop for Display {
fn drop(&mut self) {
unsafe {
(*self.inner).Release();
(*self.adapter).Release();
}
}
}
fn wrap_hresult(x: HRESULT) -> io::Result<()> {
use std::io::ErrorKind::*;
Err((match x {
S_OK => return Ok(()),
DXGI_ERROR_ACCESS_LOST => ConnectionReset,
DXGI_ERROR_WAIT_TIMEOUT => TimedOut,
DXGI_ERROR_INVALID_CALL => InvalidData,
E_ACCESSDENIED => PermissionDenied,
DXGI_ERROR_UNSUPPORTED => ConnectionRefused,
DXGI_ERROR_NOT_CURRENTLY_AVAILABLE => Interrupted,
DXGI_ERROR_SESSION_DISCONNECTED => ConnectionAborted,
E_INVALIDARG => InvalidInput,
_ => {
// 0x8000ffff https://www.auslogics.com/en/articles/windows-10-update-error-0x8000ffff-fixed/
return Err(io::Error::new(Other, format!("Error code: {:#X}", x)));
}
})
.into())
}

20
libs/scrap/src/lib.rs Normal file
View File

@@ -0,0 +1,20 @@
#[cfg(quartz)]
extern crate block;
#[macro_use]
extern crate cfg_if;
pub extern crate libc;
#[cfg(dxgi)]
extern crate winapi;
pub use common::*;
#[cfg(quartz)]
pub mod quartz;
#[cfg(x11)]
pub mod x11;
#[cfg(dxgi)]
pub mod dxgi;
mod common;

View File

@@ -0,0 +1,111 @@
use std::ptr;
use block::{Block, ConcreteBlock};
use libc::c_void;
use std::sync::{Arc, Mutex};
use super::config::Config;
use super::display::Display;
use super::ffi::*;
use super::frame::Frame;
pub struct Capturer {
stream: CGDisplayStreamRef,
queue: DispatchQueue,
width: usize,
height: usize,
format: PixelFormat,
display: Display,
stopped: Arc<Mutex<bool>>,
}
impl Capturer {
pub fn new<F: Fn(Frame) + 'static>(
display: Display,
width: usize,
height: usize,
format: PixelFormat,
config: Config,
handler: F,
) -> Result<Capturer, CGError> {
let stopped = Arc::new(Mutex::new(false));
let cloned_stopped = stopped.clone();
let handler: FrameAvailableHandler = ConcreteBlock::new(move |status, _, surface, _| {
use self::CGDisplayStreamFrameStatus::*;
if status == Stopped {
let mut lock = cloned_stopped.lock().unwrap();
*lock = true;
return;
}
if status == FrameComplete {
handler(unsafe { Frame::new(surface) });
}
})
.copy();
let queue = unsafe {
dispatch_queue_create(
b"quadrupleslap.scrap\0".as_ptr() as *const i8,
ptr::null_mut(),
)
};
let stream = unsafe {
let config = config.build();
let stream = CGDisplayStreamCreateWithDispatchQueue(
display.id(),
width,
height,
format,
config,
queue,
&*handler as *const Block<_, _> as *const c_void,
);
CFRelease(config);
stream
};
match unsafe { CGDisplayStreamStart(stream) } {
CGError::Success => Ok(Capturer {
stream,
queue,
width,
height,
format,
display,
stopped,
}),
x => Err(x),
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn format(&self) -> PixelFormat {
self.format
}
pub fn display(&self) -> Display {
self.display
}
}
impl Drop for Capturer {
fn drop(&mut self) {
unsafe {
let _ = CGDisplayStreamStop(self.stream);
loop {
if *self.stopped.lock().unwrap() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(30));
}
CFRelease(self.stream);
dispatch_release(self.queue);
}
}
}

View File

@@ -0,0 +1,75 @@
use std::ptr;
use libc::c_void;
use super::ffi::*;
//TODO: Color space, YCbCr matrix.
pub struct Config {
/// Whether the cursor is visible.
pub cursor: bool,
/// Whether it should letterbox or stretch.
pub letterbox: bool,
/// Minimum seconds per frame.
pub throttle: f64,
/// How many frames are allocated.
/// 3 is the recommended value.
/// 8 is the maximum value.
pub queue_length: i8,
}
impl Config {
/// Don't forget to CFRelease this!
pub fn build(self) -> CFDictionaryRef {
unsafe {
let throttle = CFNumberCreate(
ptr::null_mut(),
CFNumberType::Float64,
&self.throttle as *const _ as *const c_void,
);
let queue_length = CFNumberCreate(
ptr::null_mut(),
CFNumberType::SInt8,
&self.queue_length as *const _ as *const c_void,
);
let keys: [CFStringRef; 4] = [
kCGDisplayStreamShowCursor,
kCGDisplayStreamPreserveAspectRatio,
kCGDisplayStreamMinimumFrameTime,
kCGDisplayStreamQueueDepth,
];
let values: [*mut c_void; 4] = [
cfbool(self.cursor),
cfbool(self.letterbox),
throttle,
queue_length,
];
let res = CFDictionaryCreate(
ptr::null_mut(),
keys.as_ptr(),
values.as_ptr(),
4,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
);
CFRelease(throttle);
CFRelease(queue_length);
res
}
}
}
impl Default for Config {
fn default() -> Config {
Config {
cursor: false,
letterbox: true,
throttle: 0.0,
queue_length: 3,
}
}
}

View File

@@ -0,0 +1,63 @@
use std::mem;
use super::ffi::*;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
#[repr(C)]
pub struct Display(u32);
impl Display {
pub fn primary() -> Display {
Display(unsafe { CGMainDisplayID() })
}
pub fn online() -> Result<Vec<Display>, CGError> {
unsafe {
let mut arr: [u32; 16] = mem::MaybeUninit::uninit().assume_init();
let mut len: u32 = 0;
match CGGetOnlineDisplayList(16, arr.as_mut_ptr(), &mut len) {
CGError::Success => (),
x => return Err(x),
}
let mut res = Vec::with_capacity(16);
for i in 0..len as usize {
res.push(Display(*arr.get_unchecked(i)));
}
Ok(res)
}
}
pub fn id(self) -> u32 {
self.0
}
pub fn width(self) -> usize {
unsafe { CGDisplayPixelsWide(self.0) }
}
pub fn height(self) -> usize {
unsafe { CGDisplayPixelsHigh(self.0) }
}
pub fn is_builtin(self) -> bool {
unsafe { CGDisplayIsBuiltin(self.0) != 0 }
}
pub fn is_primary(self) -> bool {
unsafe { CGDisplayIsMain(self.0) != 0 }
}
pub fn is_active(self) -> bool {
unsafe { CGDisplayIsActive(self.0) != 0 }
}
pub fn is_online(self) -> bool {
unsafe { CGDisplayIsOnline(self.0) != 0 }
}
pub fn bounds(self) -> CGRect {
unsafe { CGDisplayBounds(self.0) }
}
}

View File

@@ -0,0 +1,240 @@
#![allow(dead_code)]
use block::RcBlock;
use libc::c_void;
pub type CGDisplayStreamRef = *mut c_void;
pub type CFDictionaryRef = *mut c_void;
pub type CFBooleanRef = *mut c_void;
pub type CFNumberRef = *mut c_void;
pub type CFStringRef = *mut c_void;
pub type CGDisplayStreamUpdateRef = *mut c_void;
pub type IOSurfaceRef = *mut c_void;
pub type DispatchQueue = *mut c_void;
pub type DispatchQueueAttr = *mut c_void;
pub type CFAllocatorRef = *mut c_void;
#[repr(C)]
pub struct CFDictionaryKeyCallBacks {
callbacks: [usize; 5],
version: i32,
}
#[repr(C)]
pub struct CFDictionaryValueCallBacks {
callbacks: [usize; 4],
version: i32,
}
macro_rules! pixel_format {
($a:expr, $b:expr, $c:expr, $d:expr) => {
($a as i32) << 24 | ($b as i32) << 16 | ($c as i32) << 8 | ($d as i32)
};
}
pub const SURFACE_LOCK_READ_ONLY: u32 = 0x0000_0001;
pub const SURFACE_LOCK_AVOID_SYNC: u32 = 0x0000_0002;
pub fn cfbool(x: bool) -> CFBooleanRef {
unsafe {
if x {
kCFBooleanTrue
} else {
kCFBooleanFalse
}
}
}
#[repr(i32)]
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum CGDisplayStreamFrameStatus {
/// A new frame was generated.
FrameComplete = 0,
/// A new frame was not generated because the display did not change.
FrameIdle = 1,
/// A new frame was not generated because the display has gone blank.
FrameBlank = 2,
/// The display stream was stopped.
Stopped = 3,
#[doc(hidden)]
__Nonexhaustive,
}
#[repr(i32)]
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum CFNumberType {
/* Fixed-width types */
SInt8 = 1,
SInt16 = 2,
SInt32 = 3,
SInt64 = 4,
Float32 = 5,
Float64 = 6,
/* 64-bit IEEE 754 */
/* Basic C types */
Char = 7,
Short = 8,
Int = 9,
Long = 10,
LongLong = 11,
Float = 12,
Double = 13,
/* Other */
CFIndex = 14,
NSInteger = 15,
CGFloat = 16,
}
#[repr(i32)]
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
#[must_use]
pub enum CGError {
Success = 0,
Failure = 1000,
IllegalArgument = 1001,
InvalidConnection = 1002,
InvalidContext = 1003,
CannotComplete = 1004,
NotImplemented = 1006,
RangeCheck = 1007,
TypeCheck = 1008,
InvalidOperation = 1010,
NoneAvailable = 1011,
#[doc(hidden)]
__Nonexhaustive,
}
#[repr(i32)]
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum PixelFormat {
/// Packed Little Endian ARGB8888
Argb8888 = pixel_format!('B', 'G', 'R', 'A'),
/// Packed Little Endian ARGB2101010
Argb2101010 = pixel_format!('l', '1', '0', 'r'),
/// 2-plane "video" range YCbCr 4:2:0
YCbCr420Video = pixel_format!('4', '2', '0', 'v'),
/// 2-plane "full" range YCbCr 4:2:0
YCbCr420Full = pixel_format!('4', '2', '0', 'f'),
#[doc(hidden)]
__Nonexhaustive,
}
pub type CGDisplayStreamFrameAvailableHandler = *const c_void;
pub type FrameAvailableHandler = RcBlock<
(
CGDisplayStreamFrameStatus, // status
u64, // displayTime
IOSurfaceRef, // frameSurface
CGDisplayStreamUpdateRef, // updateRef
),
(),
>;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
#[cfg(not(target_pointer_width = "64"))]
pub type CGFloat = f32;
#[repr(C)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
#[repr(C)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
}
#[repr(C)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
#[link(name = "System", kind = "dylib")]
#[link(name = "CoreGraphics", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")]
#[link(name = "IOSurface", kind = "framework")]
extern "C" {
// CoreGraphics
pub static kCGDisplayStreamShowCursor: CFStringRef;
pub static kCGDisplayStreamPreserveAspectRatio: CFStringRef;
pub static kCGDisplayStreamMinimumFrameTime: CFStringRef;
pub static kCGDisplayStreamQueueDepth: CFStringRef;
pub fn CGDisplayStreamCreateWithDispatchQueue(
display: u32,
output_width: usize,
output_height: usize,
pixel_format: PixelFormat,
properties: CFDictionaryRef,
queue: DispatchQueue,
handler: CGDisplayStreamFrameAvailableHandler,
) -> CGDisplayStreamRef;
pub fn CGDisplayStreamStart(displayStream: CGDisplayStreamRef) -> CGError;
pub fn CGDisplayStreamStop(displayStream: CGDisplayStreamRef) -> CGError;
pub fn CGMainDisplayID() -> u32;
pub fn CGDisplayPixelsWide(display: u32) -> usize;
pub fn CGDisplayPixelsHigh(display: u32) -> usize;
pub fn CGGetOnlineDisplayList(
max_displays: u32,
online_displays: *mut u32,
display_count: *mut u32,
) -> CGError;
pub fn CGDisplayIsBuiltin(display: u32) -> i32;
pub fn CGDisplayIsMain(display: u32) -> i32;
pub fn CGDisplayIsActive(display: u32) -> i32;
pub fn CGDisplayIsOnline(display: u32) -> i32;
pub fn CGDisplayBounds(display: u32) -> CGRect;
// IOSurface
pub fn IOSurfaceGetAllocSize(buffer: IOSurfaceRef) -> usize;
pub fn IOSurfaceGetBaseAddress(buffer: IOSurfaceRef) -> *mut c_void;
pub fn IOSurfaceIncrementUseCount(buffer: IOSurfaceRef);
pub fn IOSurfaceDecrementUseCount(buffer: IOSurfaceRef);
pub fn IOSurfaceLock(buffer: IOSurfaceRef, options: u32, seed: *mut u32) -> i32;
pub fn IOSurfaceUnlock(buffer: IOSurfaceRef, options: u32, seed: *mut u32) -> i32;
pub fn IOSurfaceGetBaseAddressOfPlane(buffer: IOSurfaceRef, index: usize) -> *mut c_void;
pub fn IOSurfaceGetBytesPerRowOfPlane(buffer: IOSurfaceRef, index: usize) -> usize;
// Dispatch
pub fn dispatch_queue_create(label: *const i8, attr: DispatchQueueAttr) -> DispatchQueue;
pub fn dispatch_release(object: DispatchQueue);
// Core Foundation
pub static kCFTypeDictionaryKeyCallBacks: CFDictionaryKeyCallBacks;
pub static kCFTypeDictionaryValueCallBacks: CFDictionaryValueCallBacks;
// EVEN THE BOOLEANS ARE REFERENCES.
pub static kCFBooleanTrue: CFBooleanRef;
pub static kCFBooleanFalse: CFBooleanRef;
pub fn CFNumberCreate(
allocator: CFAllocatorRef,
theType: CFNumberType,
valuePtr: *const c_void,
) -> CFNumberRef;
pub fn CFDictionaryCreate(
allocator: CFAllocatorRef,
keys: *const *mut c_void,
values: *const *mut c_void,
numValues: i64,
keyCallBacks: *const CFDictionaryKeyCallBacks,
valueCallBacks: *const CFDictionaryValueCallBacks,
) -> CFDictionaryRef;
pub fn CFRetain(cf: *const c_void);
pub fn CFRelease(cf: *const c_void);
}

View File

@@ -0,0 +1,79 @@
use std::{ops, ptr, slice};
use super::ffi::*;
pub struct Frame {
surface: IOSurfaceRef,
inner: &'static [u8],
i420: *mut u8,
i420_len: usize,
}
impl Frame {
pub unsafe fn new(surface: IOSurfaceRef) -> Frame {
CFRetain(surface);
IOSurfaceIncrementUseCount(surface);
IOSurfaceLock(surface, SURFACE_LOCK_READ_ONLY, ptr::null_mut());
let inner = slice::from_raw_parts(
IOSurfaceGetBaseAddress(surface) as *const u8,
IOSurfaceGetAllocSize(surface),
);
Frame {
surface,
inner,
i420: ptr::null_mut(),
i420_len: 0,
}
}
pub fn nv12_to_i420<'a>(&'a mut self, w: usize, h: usize, i420: &'a mut Vec<u8>) {
if self.inner.is_empty() {
return;
}
unsafe {
let plane0 = IOSurfaceGetBaseAddressOfPlane(self.surface, 0);
let stride0 = IOSurfaceGetBytesPerRowOfPlane(self.surface, 0);
let plane1 = IOSurfaceGetBaseAddressOfPlane(self.surface, 1);
let stride1 = IOSurfaceGetBytesPerRowOfPlane(self.surface, 1);
crate::common::nv12_to_i420(
plane0 as _,
stride0 as _,
plane1 as _,
stride1 as _,
w,
h,
i420,
);
self.i420 = i420.as_mut_ptr() as _;
self.i420_len = i420.len();
}
}
}
impl ops::Deref for Frame {
type Target = [u8];
fn deref<'a>(&'a self) -> &'a [u8] {
if self.i420.is_null() {
self.inner
} else {
unsafe {
let inner = slice::from_raw_parts(self.i420 as *const u8, self.i420_len);
inner
}
}
}
}
impl Drop for Frame {
fn drop(&mut self) {
unsafe {
IOSurfaceUnlock(self.surface, SURFACE_LOCK_READ_ONLY, ptr::null_mut());
IOSurfaceDecrementUseCount(self.surface);
CFRelease(self.surface);
}
}
}

View File

@@ -0,0 +1,11 @@
pub use self::capturer::Capturer;
pub use self::config::Config;
pub use self::display::Display;
pub use self::ffi::{CGError, PixelFormat};
pub use self::frame::Frame;
mod capturer;
mod config;
mod display;
pub mod ffi;
mod frame;

View File

@@ -0,0 +1,123 @@
use std::{io, ptr, slice};
use libc;
use super::ffi::*;
use super::Display;
pub struct Capturer {
display: Display,
shmid: i32,
xcbid: u32,
buffer: *const u8,
size: usize,
use_yuv: bool,
yuv: Vec<u8>,
}
impl Capturer {
pub fn new(display: Display, use_yuv: bool) -> io::Result<Capturer> {
// Calculate dimensions.
let pixel_width = 4;
let rect = display.rect();
let size = (rect.w as usize) * (rect.h as usize) * pixel_width;
// Create a shared memory segment.
let shmid = unsafe {
libc::shmget(
libc::IPC_PRIVATE,
size,
// Everyone can do anything.
libc::IPC_CREAT | 0o777,
)
};
if shmid == -1 {
return Err(io::Error::last_os_error());
}
// Attach the segment to a readable address.
let buffer = unsafe { libc::shmat(shmid, ptr::null(), libc::SHM_RDONLY) } as *mut u8;
if buffer as isize == -1 {
return Err(io::Error::last_os_error());
}
// Attach the segment to XCB.
let server = display.server().raw();
let xcbid = unsafe { xcb_generate_id(server) };
unsafe {
xcb_shm_attach(
server,
xcbid,
shmid as u32,
0, // False, i.e. not read-only.
);
}
let c = Capturer {
display,
shmid,
xcbid,
buffer,
size,
use_yuv,
yuv: Vec::new(),
};
Ok(c)
}
pub fn display(&self) -> &Display {
&self.display
}
fn get_image(&self) {
let rect = self.display.rect();
unsafe {
let request = xcb_shm_get_image_unchecked(
self.display.server().raw(),
self.display.root(),
rect.x,
rect.y,
rect.w,
rect.h,
!0,
XCB_IMAGE_FORMAT_Z_PIXMAP,
self.xcbid,
0,
);
let response =
xcb_shm_get_image_reply(self.display.server().raw(), request, ptr::null_mut());
libc::free(response as *mut _);
}
}
pub fn frame<'b>(&'b mut self) -> &'b [u8] {
self.get_image();
let result = unsafe { slice::from_raw_parts(self.buffer, self.size) };
if self.use_yuv {
crate::common::bgra_to_i420(self.display.w(), self.display.h(), &result, &mut self.yuv);
&self.yuv[..]
} else {
result
}
}
}
impl Drop for Capturer {
fn drop(&mut self) {
unsafe {
// Detach segment from XCB.
xcb_shm_detach(self.display.server().raw(), self.xcbid);
// Detach segment from our space.
libc::shmdt(self.buffer as *mut _);
// Destroy the shared memory segment.
libc::shmctl(self.shmid, libc::IPC_RMID, ptr::null_mut());
}
}
}

View File

@@ -0,0 +1,55 @@
use std::rc::Rc;
use super::ffi::*;
use super::Server;
#[derive(Debug)]
pub struct Display {
server: Rc<Server>,
default: bool,
rect: Rect,
root: xcb_window_t,
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Rect {
pub x: i16,
pub y: i16,
pub w: u16,
pub h: u16,
}
impl Display {
pub unsafe fn new(
server: Rc<Server>,
default: bool,
rect: Rect,
root: xcb_window_t,
) -> Display {
Display {
server,
default,
rect,
root,
}
}
pub fn server(&self) -> &Rc<Server> {
&self.server
}
pub fn is_default(&self) -> bool {
self.default
}
pub fn rect(&self) -> Rect {
self.rect
}
pub fn w(&self) -> usize {
self.rect.w as _
}
pub fn h(&self) -> usize {
self.rect.h as _
}
pub fn root(&self) -> xcb_window_t {
self.root
}
}

205
libs/scrap/src/x11/ffi.rs Normal file
View File

@@ -0,0 +1,205 @@
#![allow(non_camel_case_types)]
use libc::c_void;
#[link(name = "xcb")]
#[link(name = "xcb-shm")]
#[link(name = "xcb-randr")]
extern "C" {
pub fn xcb_connect(displayname: *const i8, screenp: *mut i32) -> *mut xcb_connection_t;
pub fn xcb_disconnect(c: *mut xcb_connection_t);
pub fn xcb_connection_has_error(c: *mut xcb_connection_t) -> i32;
pub fn xcb_get_setup(c: *mut xcb_connection_t) -> *const xcb_setup_t;
pub fn xcb_setup_roots_iterator(r: *const xcb_setup_t) -> xcb_screen_iterator_t;
pub fn xcb_screen_next(i: *mut xcb_screen_iterator_t);
pub fn xcb_generate_id(c: *mut xcb_connection_t) -> u32;
pub fn xcb_shm_attach(
c: *mut xcb_connection_t,
shmseg: xcb_shm_seg_t,
shmid: u32,
read_only: u8,
) -> xcb_void_cookie_t;
pub fn xcb_shm_detach(c: *mut xcb_connection_t, shmseg: xcb_shm_seg_t) -> xcb_void_cookie_t;
pub fn xcb_shm_get_image_unchecked(
c: *mut xcb_connection_t,
drawable: xcb_drawable_t,
x: i16,
y: i16,
width: u16,
height: u16,
plane_mask: u32,
format: u8,
shmseg: xcb_shm_seg_t,
offset: u32,
) -> xcb_shm_get_image_cookie_t;
pub fn xcb_shm_get_image_reply(
c: *mut xcb_connection_t,
cookie: xcb_shm_get_image_cookie_t,
e: *mut *mut xcb_generic_error_t,
) -> *mut xcb_shm_get_image_reply_t;
pub fn xcb_randr_get_monitors_unchecked(
c: *mut xcb_connection_t,
window: xcb_window_t,
get_active: u8,
) -> xcb_randr_get_monitors_cookie_t;
pub fn xcb_randr_get_monitors_reply(
c: *mut xcb_connection_t,
cookie: xcb_randr_get_monitors_cookie_t,
e: *mut *mut xcb_generic_error_t,
) -> *mut xcb_randr_get_monitors_reply_t;
pub fn xcb_randr_get_monitors_monitors_iterator(
r: *const xcb_randr_get_monitors_reply_t,
) -> xcb_randr_monitor_info_iterator_t;
pub fn xcb_randr_monitor_info_next(i: *mut xcb_randr_monitor_info_iterator_t);
}
pub const XCB_IMAGE_FORMAT_Z_PIXMAP: u8 = 2;
pub type xcb_atom_t = u32;
pub type xcb_connection_t = c_void;
pub type xcb_window_t = u32;
pub type xcb_keycode_t = u8;
pub type xcb_visualid_t = u32;
pub type xcb_timestamp_t = u32;
pub type xcb_colormap_t = u32;
pub type xcb_shm_seg_t = u32;
pub type xcb_drawable_t = u32;
#[repr(C)]
pub struct xcb_setup_t {
pub status: u8,
pub pad0: u8,
pub protocol_major_version: u16,
pub protocol_minor_version: u16,
pub length: u16,
pub release_number: u32,
pub resource_id_base: u32,
pub resource_id_mask: u32,
pub motion_buffer_size: u32,
pub vendor_len: u16,
pub maximum_request_length: u16,
pub roots_len: u8,
pub pixmap_formats_len: u8,
pub image_byte_order: u8,
pub bitmap_format_bit_order: u8,
pub bitmap_format_scanline_unit: u8,
pub bitmap_format_scanline_pad: u8,
pub min_keycode: xcb_keycode_t,
pub max_keycode: xcb_keycode_t,
pub pad1: [u8; 4],
}
#[repr(C)]
pub struct xcb_screen_iterator_t {
pub data: *mut xcb_screen_t,
pub rem: i32,
pub index: i32,
}
#[repr(C)]
pub struct xcb_screen_t {
pub root: xcb_window_t,
pub default_colormap: xcb_colormap_t,
pub white_pixel: u32,
pub black_pixel: u32,
pub current_input_masks: u32,
pub width_in_pixels: u16,
pub height_in_pixels: u16,
pub width_in_millimeters: u16,
pub height_in_millimeters: u16,
pub min_installed_maps: u16,
pub max_installed_maps: u16,
pub root_visual: xcb_visualid_t,
pub backing_stores: u8,
pub save_unders: u8,
pub root_depth: u8,
pub allowed_depths_len: u8,
}
#[repr(C)]
pub struct xcb_randr_monitor_info_iterator_t {
pub data: *mut xcb_randr_monitor_info_t,
pub rem: i32,
pub index: i32,
}
#[repr(C)]
pub struct xcb_randr_monitor_info_t {
pub name: xcb_atom_t,
pub primary: u8,
pub automatic: u8,
pub n_output: u16,
pub x: i16,
pub y: i16,
pub width: u16,
pub height: u16,
pub width_mm: u32,
pub height_mm: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct xcb_randr_get_monitors_cookie_t {
pub sequence: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct xcb_shm_get_image_cookie_t {
pub sequence: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct xcb_void_cookie_t {
pub sequence: u32,
}
#[repr(C)]
pub struct xcb_generic_error_t {
pub response_type: u8,
pub error_code: u8,
pub sequence: u16,
pub resource_id: u32,
pub minor_code: u16,
pub major_code: u8,
pub pad0: u8,
pub pad: [u32; 5],
pub full_sequence: u32,
}
#[repr(C)]
pub struct xcb_shm_get_image_reply_t {
pub response_type: u8,
pub depth: u8,
pub sequence: u16,
pub length: u32,
pub visual: xcb_visualid_t,
pub size: u32,
}
#[repr(C)]
pub struct xcb_randr_get_monitors_reply_t {
pub response_type: u8,
pub pad0: u8,
pub sequence: u16,
pub length: u32,
pub timestamp: xcb_timestamp_t,
pub n_monitors: u32,
pub n_outputs: u32,
pub pad1: [u8; 12],
}

View File

@@ -0,0 +1,93 @@
use std::ptr;
use std::rc::Rc;
use libc;
use super::ffi::*;
use super::{Display, Rect, Server};
//TODO: Do I have to free the displays?
pub struct DisplayIter {
outer: xcb_screen_iterator_t,
inner: Option<(xcb_randr_monitor_info_iterator_t, xcb_window_t)>,
server: Rc<Server>,
}
impl DisplayIter {
pub unsafe fn new(server: Rc<Server>) -> DisplayIter {
let mut outer = xcb_setup_roots_iterator(server.setup());
let inner = Self::next_screen(&mut outer, &server);
DisplayIter {
outer,
inner,
server,
}
}
fn next_screen(
outer: &mut xcb_screen_iterator_t,
server: &Server,
) -> Option<(xcb_randr_monitor_info_iterator_t, xcb_window_t)> {
if outer.rem == 0 {
return None;
}
unsafe {
let root = (*outer.data).root;
let cookie = xcb_randr_get_monitors_unchecked(
server.raw(),
root,
1, //TODO: I don't know if this should be true or false.
);
let response = xcb_randr_get_monitors_reply(server.raw(), cookie, ptr::null_mut());
let inner = xcb_randr_get_monitors_monitors_iterator(response);
libc::free(response as *mut _);
xcb_screen_next(outer);
Some((inner, root))
}
}
}
impl Iterator for DisplayIter {
type Item = Display;
fn next(&mut self) -> Option<Display> {
loop {
if let Some((ref mut inner, root)) = self.inner {
// If there is something in the current screen, return that.
if inner.rem != 0 {
unsafe {
let data = &*inner.data;
let display = Display::new(
self.server.clone(),
data.primary != 0,
Rect {
x: data.x,
y: data.y,
w: data.width,
h: data.height,
},
root,
);
xcb_randr_monitor_info_next(inner);
return Some(display);
}
}
} else {
// If there is no current screen, the screen iterator is empty.
return None;
}
// The current screen was empty, so try the next screen.
self.inner = Self::next_screen(&mut self.outer, &self.server);
}
}
}

10
libs/scrap/src/x11/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
pub use self::capturer::*;
pub use self::display::*;
pub use self::iter::*;
pub use self::server::*;
mod capturer;
mod display;
mod ffi;
mod iter;
mod server;

View File

@@ -0,0 +1,122 @@
use std::ptr;
use std::rc::Rc;
use super::ffi::*;
use super::DisplayIter;
#[derive(Debug)]
pub struct Server {
raw: *mut xcb_connection_t,
screenp: i32,
setup: *const xcb_setup_t,
}
/*
use std::cell::RefCell;
thread_local! {
static SERVER: RefCell<Option<Rc<Server>>> = RefCell::new(None);
}
*/
impl Server {
pub fn displays(slf: Rc<Server>) -> DisplayIter {
unsafe { DisplayIter::new(slf) }
}
pub fn default() -> Result<Rc<Server>, Error> {
Ok(Rc::new(Server::connect(ptr::null())?))
/*
let mut res = Err(Error::from(0));
SERVER.with(|xdo| {
if let Ok(mut server) = xdo.try_borrow_mut() {
if server.is_some() {
unsafe {
if 0 != xcb_connection_has_error(server.as_ref().unwrap().raw) {
*server = None;
println!("Reset x11 connection");
}
}
}
if server.is_none() {
println!("New x11 connection");
match Server::connect(ptr::null()) {
Ok(s) => {
let s = Rc::new(s);
res = Ok(s.clone());
*server = Some(s);
}
Err(err) => {
res = Err(err);
}
}
} else {
res = Ok(server.as_ref().map(|x| x.clone()).unwrap());
}
}
});
res
*/
}
pub fn connect(addr: *const i8) -> Result<Server, Error> {
unsafe {
let mut screenp = 0;
let raw = xcb_connect(addr, &mut screenp);
let error = xcb_connection_has_error(raw);
if error != 0 {
xcb_disconnect(raw);
Err(Error::from(error))
} else {
let setup = xcb_get_setup(raw);
Ok(Server {
raw,
screenp,
setup,
})
}
}
}
pub fn raw(&self) -> *mut xcb_connection_t {
self.raw
}
pub fn screenp(&self) -> i32 {
self.screenp
}
pub fn setup(&self) -> *const xcb_setup_t {
self.setup
}
}
impl Drop for Server {
fn drop(&mut self) {
unsafe {
xcb_disconnect(self.raw);
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Error {
Generic,
UnsupportedExtension,
InsufficientMemory,
RequestTooLong,
ParseError,
InvalidScreen,
}
impl From<i32> for Error {
fn from(x: i32) -> Error {
use self::Error::*;
match x {
2 => UnsupportedExtension,
3 => InsufficientMemory,
4 => RequestTooLong,
5 => ParseError,
6 => InvalidScreen,
_ => Generic,
}
}
}

9
libs/scrap/vpx_ffi.h Normal file
View File

@@ -0,0 +1,9 @@
#include <vpx/vp8.h>
#include <vpx/vp8cx.h>
#include <vpx/vp8dx.h>
#include <vpx/vpx_codec.h>
#include <vpx/vpx_decoder.h>
#include <vpx/vpx_encoder.h>
#include <vpx/vpx_frame_buffer.h>
#include <vpx/vpx_image.h>
#include <vpx/vpx_integer.h>