mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
source code
This commit is contained in:
4
libs/scrap/.gitignore
vendored
Normal file
4
libs/scrap/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
generated/
|
||||
32
libs/scrap/Cargo.toml
Normal file
32
libs/scrap/Cargo.toml
Normal 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
60
libs/scrap/README.md
Normal 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
118
libs/scrap/build.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
51
libs/scrap/examples/ffplay.rs
Normal file
51
libs/scrap/examples/ffplay.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
libs/scrap/examples/list.rs
Normal file
16
libs/scrap/examples/list.rs
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
161
libs/scrap/examples/record-screen.rs
Normal file
161
libs/scrap/examples/record-screen.rs
Normal 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(())
|
||||
}
|
||||
122
libs/scrap/examples/screenshot.rs
Normal file
122
libs/scrap/examples/screenshot.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
536
libs/scrap/src/common/codec.rs
Normal file
536
libs/scrap/src/common/codec.rs
Normal 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 {}
|
||||
188
libs/scrap/src/common/convert.rs
Normal file
188
libs/scrap/src/common/convert.rs
Normal 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 _,
|
||||
);
|
||||
}
|
||||
104
libs/scrap/src/common/dxgi.rs
Normal file
104
libs/scrap/src/common/dxgi.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
23
libs/scrap/src/common/mod.rs
Normal file
23
libs/scrap/src/common/mod.rs
Normal 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;
|
||||
125
libs/scrap/src/common/quartz.rs
Normal file
125
libs/scrap/src/common/quartz.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
25
libs/scrap/src/common/vpx.rs
Normal file
25
libs/scrap/src/common/vpx.rs
Normal 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"));
|
||||
87
libs/scrap/src/common/x11.rs
Normal file
87
libs/scrap/src/common/x11.rs
Normal 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
212
libs/scrap/src/dxgi/gdi.rs
Normal 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
539
libs/scrap/src/dxgi/mod.rs
Normal 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
20
libs/scrap/src/lib.rs
Normal 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;
|
||||
111
libs/scrap/src/quartz/capturer.rs
Normal file
111
libs/scrap/src/quartz/capturer.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
libs/scrap/src/quartz/config.rs
Normal file
75
libs/scrap/src/quartz/config.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
63
libs/scrap/src/quartz/display.rs
Normal file
63
libs/scrap/src/quartz/display.rs
Normal 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) }
|
||||
}
|
||||
}
|
||||
240
libs/scrap/src/quartz/ffi.rs
Normal file
240
libs/scrap/src/quartz/ffi.rs
Normal 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);
|
||||
}
|
||||
79
libs/scrap/src/quartz/frame.rs
Normal file
79
libs/scrap/src/quartz/frame.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
libs/scrap/src/quartz/mod.rs
Normal file
11
libs/scrap/src/quartz/mod.rs
Normal 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;
|
||||
123
libs/scrap/src/x11/capturer.rs
Normal file
123
libs/scrap/src/x11/capturer.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
55
libs/scrap/src/x11/display.rs
Normal file
55
libs/scrap/src/x11/display.rs
Normal 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
205
libs/scrap/src/x11/ffi.rs
Normal 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],
|
||||
}
|
||||
93
libs/scrap/src/x11/iter.rs
Normal file
93
libs/scrap/src/x11/iter.rs
Normal 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
10
libs/scrap/src/x11/mod.rs
Normal 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;
|
||||
122
libs/scrap/src/x11/server.rs
Normal file
122
libs/scrap/src/x11/server.rs
Normal 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
9
libs/scrap/vpx_ffi.h
Normal 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>
|
||||
Reference in New Issue
Block a user