Initial commit: RustDesk 1.4.7 macOS desktop port
- Filled empty libs/hbb_common/ submodule (cloned from rustdesk/hbb_common) - Patched Flutter 3.44 / Dart 3.12 compatibility: * flutter/lib/generated_bridge.dart: asTypedList with cast<>, DartPort=Int64 * flutter/lib/common.dart: DialogTheme->DialogThemeData, TabBarTheme->TabBarThemeData * flutter/pubspec.yaml: extended_text 14.0.0->15.0.2, google_fonts override 5.0.0 * flutter/macos/Runner/Configs/Release.xcconfig: EXCLUDED_ARCHS=x86_64 - Build verified: cargo check + cargo build --features flutter + cargo build --release --features flutter - Verified flutter build macos --debug and --release both produce working .app - Verified .dmg installer (27MB arm64) created via hdiutil - Build deps: Xcode 26.5, CocoaPods 1.16.2, Flutter 3.44, VCPKG arm64-osx
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
use docopt::Docopt;
|
||||
use hbb_common::{
|
||||
env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV},
|
||||
log,
|
||||
};
|
||||
use scrap::{
|
||||
aom::{AomDecoder, AomEncoder, AomEncoderConfig},
|
||||
codec::{EncoderApi, EncoderCfg},
|
||||
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
|
||||
VpxVideoCodecId::{self, *},
|
||||
STRIDE_ALIGN,
|
||||
};
|
||||
use std::{
|
||||
io::Write,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
// cargo run --package scrap --example benchmark --release --features hwcodec
|
||||
|
||||
const USAGE: &'static str = "
|
||||
Codec benchmark.
|
||||
|
||||
Usage:
|
||||
benchmark [--count=COUNT] [--quality=QUALITY] [--i444]
|
||||
benchmark (-h | --help)
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
--count=COUNT Capture frame count [default: 100].
|
||||
--quality=QUALITY Video quality [default: 1.0].
|
||||
--i444 I444.
|
||||
";
|
||||
|
||||
#[derive(Debug, serde::Deserialize, Clone, Copy)]
|
||||
struct Args {
|
||||
flag_count: usize,
|
||||
flag_quality: f32,
|
||||
flag_i444: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.deserialize())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
let quality = args.flag_quality;
|
||||
let yuv_count = args.flag_count;
|
||||
let mut index = 0;
|
||||
let mut displays = Display::all().unwrap();
|
||||
for i in 0..displays.len() {
|
||||
if displays[i].is_primary() {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let d = displays.remove(index);
|
||||
let mut c = Capturer::new(d).unwrap();
|
||||
let width = c.width();
|
||||
let height = c.height();
|
||||
|
||||
println!(
|
||||
"benchmark {}x{} quality:{:?}, i444:{:?}",
|
||||
width, height, quality, args.flag_i444
|
||||
);
|
||||
[VP8, VP9].map(|codec| {
|
||||
test_vpx(
|
||||
&mut c,
|
||||
codec,
|
||||
width,
|
||||
height,
|
||||
quality,
|
||||
yuv_count,
|
||||
if codec == VP8 { false } else { args.flag_i444 },
|
||||
)
|
||||
});
|
||||
test_av1(&mut c, width, height, quality, yuv_count, args.flag_i444);
|
||||
#[cfg(feature = "hwcodec")]
|
||||
{
|
||||
hw::test(&mut c, width, height, quality, yuv_count);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_vpx(
|
||||
c: &mut Capturer,
|
||||
codec_id: VpxVideoCodecId,
|
||||
width: usize,
|
||||
height: usize,
|
||||
quality: f32,
|
||||
yuv_count: usize,
|
||||
i444: bool,
|
||||
) {
|
||||
let config = EncoderCfg::VPX(VpxEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
quality,
|
||||
codec: codec_id,
|
||||
keyframe_interval: None,
|
||||
});
|
||||
let mut encoder = VpxEncoder::new(config, i444).unwrap();
|
||||
let mut vpxs = vec![];
|
||||
let start = Instant::now();
|
||||
let mut size = 0;
|
||||
let mut yuv = Vec::new();
|
||||
let mut mid_data = Vec::new();
|
||||
let mut counter = 0;
|
||||
let mut time_sum = Duration::ZERO;
|
||||
loop {
|
||||
match c.frame(std::time::Duration::from_millis(30)) {
|
||||
Ok(frame) => {
|
||||
let tmp_timer = Instant::now();
|
||||
let frame = frame.to(encoder.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
|
||||
let yuv = frame.yuv().unwrap();
|
||||
for ref frame in encoder
|
||||
.encode(start.elapsed().as_millis() as _, &yuv, STRIDE_ALIGN)
|
||||
.unwrap()
|
||||
{
|
||||
size += frame.data.len();
|
||||
vpxs.push(frame.data.to_vec());
|
||||
counter += 1;
|
||||
print!("\r{codec_id:?} {}/{}", counter, yuv_count);
|
||||
std::io::stdout().flush().ok();
|
||||
}
|
||||
for ref frame in encoder.flush().unwrap() {
|
||||
size += frame.data.len();
|
||||
vpxs.push(frame.data.to_vec());
|
||||
counter += 1;
|
||||
print!("\r{codec_id:?} {}/{}", counter, yuv_count);
|
||||
std::io::stdout().flush().ok();
|
||||
}
|
||||
time_sum += tmp_timer.elapsed();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
}
|
||||
}
|
||||
if counter >= yuv_count {
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(vpxs.len(), yuv_count);
|
||||
println!(
|
||||
"{:?} encode: {:?}, {} byte",
|
||||
codec_id,
|
||||
time_sum / yuv_count as _,
|
||||
size / yuv_count
|
||||
);
|
||||
|
||||
let mut decoder = VpxDecoder::new(VpxDecoderConfig { codec: codec_id }).unwrap();
|
||||
let start = Instant::now();
|
||||
for vpx in vpxs {
|
||||
let _ = decoder.decode(&vpx);
|
||||
let _ = decoder.flush();
|
||||
}
|
||||
println!(
|
||||
"{:?} decode: {:?}",
|
||||
codec_id,
|
||||
start.elapsed() / yuv_count as _
|
||||
);
|
||||
}
|
||||
|
||||
fn test_av1(
|
||||
c: &mut Capturer,
|
||||
width: usize,
|
||||
height: usize,
|
||||
quality: f32,
|
||||
yuv_count: usize,
|
||||
i444: bool,
|
||||
) {
|
||||
let config = EncoderCfg::AOM(AomEncoderConfig {
|
||||
width: width as _,
|
||||
height: height as _,
|
||||
quality,
|
||||
keyframe_interval: None,
|
||||
});
|
||||
let mut encoder = AomEncoder::new(config, i444).unwrap();
|
||||
let start = Instant::now();
|
||||
let mut size = 0;
|
||||
let mut av1s: Vec<Vec<u8>> = vec![];
|
||||
let mut yuv = Vec::new();
|
||||
let mut mid_data = Vec::new();
|
||||
let mut counter = 0;
|
||||
let mut time_sum = Duration::ZERO;
|
||||
loop {
|
||||
match c.frame(std::time::Duration::from_millis(30)) {
|
||||
Ok(frame) => {
|
||||
let tmp_timer = Instant::now();
|
||||
let frame = frame.to(encoder.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
|
||||
let yuv = frame.yuv().unwrap();
|
||||
for ref frame in encoder
|
||||
.encode(start.elapsed().as_millis() as _, &yuv, STRIDE_ALIGN)
|
||||
.unwrap()
|
||||
{
|
||||
size += frame.data.len();
|
||||
av1s.push(frame.data.to_vec());
|
||||
counter += 1;
|
||||
print!("\rAV1 {}/{}", counter, yuv_count);
|
||||
std::io::stdout().flush().ok();
|
||||
}
|
||||
time_sum += tmp_timer.elapsed();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
}
|
||||
}
|
||||
if counter >= yuv_count {
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(av1s.len(), yuv_count);
|
||||
println!(
|
||||
"AV1 encode: {:?}, {} byte",
|
||||
time_sum / yuv_count as _,
|
||||
size / yuv_count
|
||||
);
|
||||
let mut decoder = AomDecoder::new().unwrap();
|
||||
let start = Instant::now();
|
||||
for av1 in av1s {
|
||||
let _ = decoder.decode(&av1);
|
||||
let _ = decoder.flush();
|
||||
}
|
||||
println!("AV1 decode: {:?}", start.elapsed() / yuv_count as _);
|
||||
}
|
||||
|
||||
#[cfg(feature = "hwcodec")]
|
||||
mod hw {
|
||||
use hwcodec::ffmpeg_ram::CodecInfo;
|
||||
use scrap::{
|
||||
hwcodec::{HwRamDecoder, HwRamEncoder, HwRamEncoderConfig},
|
||||
CodecFormat,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn test(c: &mut Capturer, width: usize, height: usize, quality: f32, yuv_count: usize) {
|
||||
let mut h264s = Vec::new();
|
||||
let mut h265s = Vec::new();
|
||||
if let Some(info) = HwRamEncoder::try_get(CodecFormat::H264) {
|
||||
test_encoder(width, height, quality, info, c, yuv_count, &mut h264s);
|
||||
}
|
||||
if let Some(info) = HwRamEncoder::try_get(CodecFormat::H265) {
|
||||
test_encoder(width, height, quality, info, c, yuv_count, &mut h265s);
|
||||
}
|
||||
test_decoder(CodecFormat::H264, &h264s);
|
||||
test_decoder(CodecFormat::H265, &h265s);
|
||||
}
|
||||
|
||||
fn test_encoder(
|
||||
width: usize,
|
||||
height: usize,
|
||||
quality: f32,
|
||||
info: CodecInfo,
|
||||
c: &mut Capturer,
|
||||
yuv_count: usize,
|
||||
h26xs: &mut Vec<Vec<u8>>,
|
||||
) {
|
||||
let mut encoder = HwRamEncoder::new(
|
||||
EncoderCfg::HWRAM(HwRamEncoderConfig {
|
||||
name: info.name.clone(),
|
||||
mc_name: None,
|
||||
width,
|
||||
height,
|
||||
quality,
|
||||
keyframe_interval: None,
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let mut size = 0;
|
||||
|
||||
let mut yuv = Vec::new();
|
||||
let mut mid_data = Vec::new();
|
||||
let mut counter = 0;
|
||||
let mut time_sum = Duration::ZERO;
|
||||
let start = std::time::Instant::now();
|
||||
loop {
|
||||
match c.frame(std::time::Duration::from_millis(30)) {
|
||||
Ok(frame) => {
|
||||
let tmp_timer = Instant::now();
|
||||
let frame = frame.to(encoder.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
|
||||
let yuv = frame.yuv().unwrap();
|
||||
for ref frame in encoder
|
||||
.encode(&yuv, start.elapsed().as_millis() as _)
|
||||
.unwrap()
|
||||
{
|
||||
size += frame.data.len();
|
||||
|
||||
h26xs.push(frame.data.to_vec());
|
||||
counter += 1;
|
||||
print!("\r{:?} {}/{}", info.name, counter, yuv_count);
|
||||
std::io::stdout().flush().ok();
|
||||
}
|
||||
time_sum += tmp_timer.elapsed();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
}
|
||||
}
|
||||
if counter >= yuv_count {
|
||||
println!();
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"{}: {:?}, {} byte",
|
||||
info.name,
|
||||
time_sum / yuv_count as u32,
|
||||
size / yuv_count,
|
||||
);
|
||||
}
|
||||
|
||||
fn test_decoder(format: CodecFormat, h26xs: &Vec<Vec<u8>>) {
|
||||
let mut decoder = HwRamDecoder::new(format).unwrap();
|
||||
let start = Instant::now();
|
||||
let mut cnt = 0;
|
||||
for h26x in h26xs {
|
||||
let _ = decoder.decode(h26x).unwrap();
|
||||
cnt += 1;
|
||||
}
|
||||
let device = format!("{:?}", decoder.info.hwdevice).to_lowercase();
|
||||
let device = device.split("_").last().unwrap();
|
||||
println!(
|
||||
"{} {}: {:?}",
|
||||
decoder.info.name,
|
||||
device,
|
||||
start.elapsed() / cnt
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
extern crate repng;
|
||||
extern crate scrap;
|
||||
|
||||
use scrap::Display;
|
||||
#[cfg(windows)]
|
||||
use scrap::{CapturerMag, TraitCapturer};
|
||||
#[cfg(windows)]
|
||||
use std::fs::File;
|
||||
|
||||
fn main() {
|
||||
let n = Display::all().unwrap().len();
|
||||
for _i in 0..n {
|
||||
#[cfg(windows)]
|
||||
record(_i);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_display(i: usize) -> Display {
|
||||
Display::all().unwrap().remove(i)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn record(i: usize) {
|
||||
use std::time::Duration;
|
||||
|
||||
use scrap::{Frame, TraitPixelBuffer};
|
||||
|
||||
for d in Display::all().unwrap() {
|
||||
println!("{:?} {} {}", d.origin(), d.width(), d.height());
|
||||
}
|
||||
|
||||
let display = get_display(i);
|
||||
let (w, h) = (display.width(), display.height());
|
||||
|
||||
{
|
||||
let mut capture_mag = CapturerMag::new(display.origin(), display.width(), display.height())
|
||||
.expect("Couldn't begin capture.");
|
||||
let wnd_cls = "";
|
||||
let wnd_name = "RustDeskPrivacyWindow";
|
||||
if false == capture_mag.exclude(wnd_cls, wnd_name).unwrap() {
|
||||
println!("No window found for cls {} name {}", wnd_cls, wnd_name);
|
||||
} else {
|
||||
println!("Filter window for cls {} name {}", wnd_cls, wnd_name);
|
||||
}
|
||||
|
||||
let frame = capture_mag.frame(Duration::from_millis(0)).unwrap();
|
||||
let Frame::PixelBuffer(frame) = frame else {
|
||||
return;
|
||||
};
|
||||
let frame = frame.data();
|
||||
println!("Capture data len: {}, Saving...", frame.len());
|
||||
|
||||
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 + 4 * x;
|
||||
bitflipped.extend_from_slice(&[frame[i + 2], frame[i + 1], frame[i], 255]);
|
||||
}
|
||||
}
|
||||
// Save the image.
|
||||
let name = format!("capture_mag_{}_1.png", i);
|
||||
repng::encode(
|
||||
File::create(name.clone()).unwrap(),
|
||||
w as u32,
|
||||
h as u32,
|
||||
&bitflipped,
|
||||
)
|
||||
.unwrap();
|
||||
println!("Image saved to `{}`.", name);
|
||||
}
|
||||
|
||||
{
|
||||
let mut capture_mag = CapturerMag::new(display.origin(), display.width(), display.height())
|
||||
.expect("Couldn't begin capture.");
|
||||
let wnd_cls = "";
|
||||
let wnd_title = "RustDeskPrivacyWindow";
|
||||
if false == capture_mag.exclude(wnd_cls, wnd_title).unwrap() {
|
||||
println!("No window found for cls {} title {}", wnd_cls, wnd_title);
|
||||
} else {
|
||||
println!("Filter window for cls {} title {}", wnd_cls, wnd_title);
|
||||
}
|
||||
|
||||
let frame = capture_mag.frame(Duration::from_millis(0)).unwrap();
|
||||
let Frame::PixelBuffer(frame) = frame else {
|
||||
return;
|
||||
};
|
||||
println!("Capture data len: {}, Saving...", frame.data().len());
|
||||
|
||||
let mut raw = Vec::new();
|
||||
unsafe {
|
||||
scrap::ARGBToRAW(
|
||||
frame.data().as_ptr(),
|
||||
frame.stride()[0] as _,
|
||||
(&mut raw).as_mut_ptr(),
|
||||
(w * 3) as _,
|
||||
w as _,
|
||||
h as _,
|
||||
)
|
||||
};
|
||||
|
||||
let mut bitflipped = Vec::with_capacity(w * h * 4);
|
||||
let stride = raw.len() / h;
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let i = stride * y + 3 * x;
|
||||
bitflipped.extend_from_slice(&[raw[i], raw[i + 1], raw[i + 2], 255]);
|
||||
}
|
||||
}
|
||||
let name = format!("capture_mag_{}_2.png", i);
|
||||
repng::encode(
|
||||
File::create(name.clone()).unwrap(),
|
||||
w as u32,
|
||||
h as u32,
|
||||
&bitflipped,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!("Image saved to `{}`.", name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use scrap::{Frame, TraitPixelBuffer};
|
||||
|
||||
extern crate scrap;
|
||||
|
||||
fn main() {
|
||||
use scrap::{Capturer, Display, TraitCapturer};
|
||||
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).unwrap();
|
||||
let mut out = child.stdin.unwrap();
|
||||
|
||||
loop {
|
||||
match capturer.frame(Duration::from_millis(0)) {
|
||||
Ok(frame) => {
|
||||
// Write the frame, removing end-of-row padding.
|
||||
let Frame::PixelBuffer(frame) = frame else {
|
||||
return;
|
||||
};
|
||||
let stride = frame.stride()[0];
|
||||
let rowlen = 4 * w;
|
||||
for row in frame.data().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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
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 scrap::codec::{EncoderApi, EncoderCfg};
|
||||
use webm::mux;
|
||||
use webm::mux::Track;
|
||||
|
||||
use scrap::vpxcodec as vpx_encode;
|
||||
use scrap::{Capturer, Display, TraitCapturer, STRIDE_ALIGN};
|
||||
|
||||
const USAGE: &'static str = "
|
||||
Simple WebM screen capture.
|
||||
|
||||
Usage:
|
||||
record-screen <path> [--time=<s>] [--fps=<fps>] [--quality=<quality>] [--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].
|
||||
--quality=<quality> Video quality [default: 1.0].
|
||||
--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_quality: f32,
|
||||
}
|
||||
|
||||
#[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::VpxVideoCodecId::VP8, mux::VideoCodecId::VP8),
|
||||
Codec::Vp9 => (vpx_encode::VpxVideoCodecId::VP9, mux::VideoCodecId::VP9),
|
||||
};
|
||||
|
||||
let mut vt = webm.add_video_track(width, height, None, mux_codec);
|
||||
|
||||
// Setup the encoder.
|
||||
let quality = args.flag_quality;
|
||||
let mut vpx = vpx_encode::VpxEncoder::new(
|
||||
EncoderCfg::VPX(vpx_encode::VpxEncoderConfig {
|
||||
width,
|
||||
height,
|
||||
quality,
|
||||
codec: vpx_codec,
|
||||
keyframe_interval: None,
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.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).unwrap();
|
||||
let mut yuv = Vec::new();
|
||||
let mut mid_data = Vec::new();
|
||||
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(Duration::from_millis(0)) {
|
||||
let ms = time.as_secs() * 1000 + time.subsec_millis() as u64;
|
||||
frame.to(vpx.yuvfmt(), &mut yuv, &mut mid_data).unwrap();
|
||||
for frame in vpx.encode(ms as i64, &yuv, 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(())
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
extern crate repng;
|
||||
extern crate scrap;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind::WouldBlock;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use scrap::{Capturer, Display, Frame, TraitCapturer, TraitPixelBuffer};
|
||||
|
||||
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;
|
||||
|
||||
for d in Display::all().unwrap() {
|
||||
println!("{:?} {} {}", d.origin(), d.width(), d.height());
|
||||
}
|
||||
|
||||
let display = get_display(i);
|
||||
let mut capturer = Capturer::new(display).expect("Couldn't begin capture.");
|
||||
let (w, h) = (capturer.width(), capturer.height());
|
||||
|
||||
loop {
|
||||
// Wait until there's a frame.
|
||||
|
||||
let frame = match capturer.frame(Duration::from_millis(0)) {
|
||||
Ok(frame) => frame,
|
||||
Err(error) => {
|
||||
if error.kind() == WouldBlock {
|
||||
// Keep spinning.
|
||||
thread::sleep(one_frame);
|
||||
continue;
|
||||
} else {
|
||||
panic!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
let Frame::PixelBuffer(frame) = frame else {
|
||||
return;
|
||||
};
|
||||
let buffer = frame.data();
|
||||
println!("Captured data len: {}, Saving...", buffer.len());
|
||||
|
||||
// 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).expect("Couldn't begin capture.");
|
||||
let (w, h) = (capturer.width(), capturer.height());
|
||||
|
||||
loop {
|
||||
// Wait until there's a frame.
|
||||
|
||||
let frame = match capturer.frame(Duration::from_millis(0)) {
|
||||
Ok(frame) => frame,
|
||||
Err(error) => {
|
||||
if error.kind() == WouldBlock {
|
||||
// Keep spinning.
|
||||
thread::sleep(one_frame);
|
||||
continue;
|
||||
} else {
|
||||
panic!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
let Frame::PixelBuffer(frame) = frame else {
|
||||
return;
|
||||
};
|
||||
let buffer = frame.data();
|
||||
println!("Captured data len: {}, Saving...", buffer.len());
|
||||
|
||||
let mut raw = Vec::new();
|
||||
unsafe {
|
||||
scrap::ARGBToRAW(
|
||||
buffer.as_ptr(),
|
||||
frame.stride()[0] as _,
|
||||
(&mut raw).as_mut_ptr(),
|
||||
(w * 3) as _,
|
||||
w as _,
|
||||
h as _,
|
||||
)
|
||||
};
|
||||
|
||||
let mut bitflipped = Vec::with_capacity(w * h * 4);
|
||||
let stride = raw.len() / h;
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let i = stride * y + 3 * x;
|
||||
bitflipped.extend_from_slice(&[raw[i], raw[i + 1], raw[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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user