diff --git a/Cargo.toml b/Cargo.toml index 81c3c23..f0514d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "xcap" -version = "0.0.15" +version = "0.1.0" edition = "2021" -description = "XCap is a cross-platform screen capture library written in Rust. It supports Linux (X11, Wayland), MacOS, and Windows. XCap supports screenshot and video recording (to be implemented)." +description = "XCap is a cross-platform screen capture library written in Rust. It supports Linux (X11, Wayland), MacOS, and Windows. XCap supports screenshot and video recording (WIP)." license = "Apache-2.0" documentation = "https://docs.rs/xcap" homepage = "https://github.com/nashaofu/xcap" @@ -15,10 +15,10 @@ keywords = ["screen", "monitor", "window", "capture", "image"] vendored = ["dbus/vendored"] [dependencies] -image = "0.24" +image = "0.25" log = "0.4" -sysinfo = "0.32" -thiserror = "1.0" +sysinfo = "0.33" +thiserror = "2.0" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.10" @@ -34,11 +34,15 @@ windows = { version = "0.58", features = [ "Win32_System_Threading", "Win32_System_ProcessStatus", "Win32_Storage_FileSystem", + "Win32_Graphics_Dxgi", + "Win32_Graphics_Direct3D", + "Win32_Graphics_Direct3D11", + "Win32_Graphics_Dxgi_Common", ] } [target.'cfg(target_os="linux")'.dependencies] percent-encoding = "2.3" -xcb = { version = "1.4", features = ["randr"] } +xcb = { version = "1.5", features = ["randr"] } dbus = { version = "0.9" } [dev-dependencies] diff --git a/README-zh_CN.md b/README-zh_CN.md index f600195..88ac0b1 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -2,22 +2,22 @@ [English](README.md) | 简体中文 -XCap 是一个使用 Rust 编写的跨平台的屏幕捕获库,它支持 Linux(X11,Wayland)、MacOS 与 Windows。XCap 支持截图与视频录制(待实现)。 +XCap 是一个使用 Rust 编写的跨平台的屏幕捕获库,它支持 Linux(X11,Wayland)、MacOS 与 Windows。XCap 支持截图与视频录制(实现中)。 ## 功能 - 跨平台: 支持 Linux(X11,Wayland)、MacOS 与 Windows。 - 支持多种截图模式: 可以对屏幕与窗口进行截图。 -- 支持视频录制:支持对屏幕或窗口进行录制(待实现)。 +- 支持视频录制:支持对屏幕或窗口进行录制(实现中)。 ### 实现状态 -| 功能 | Linux(X11) | Linux(Wayland) | MacOS | Windows | -| -------- | ---------- | -------------- | ----- | ------- | -| 屏幕截图 | ✅ | ⛔ | ✅ | ✅ | -| 窗口截图 | ✅ | ⛔ | ✅ | ✅ | -| 屏幕录制 | 🛠️ | 🛠️ | 🛠️ | 🛠️ | -| 窗口录制 | 🛠️ | 🛠️ | 🛠️ | 🛠️ | +| 功能 | Linux(X11) | Linux(Wayland) | MacOS | Windows(>=Windows 8.1) | +| -------- | ---------- | -------------- | ----- | ---------------------- | +| 屏幕截图 | ✅ | ⛔ | ✅ | ✅ | +| 窗口截图 | ✅ | ⛔ | ✅ | ✅ | +| 屏幕录制 | 🛠️ | 🛠️ | 🛠️ | ✅ | +| 窗口录制 | 🛠️ | 🛠️ | 🛠️ | 🛠️ | - ✅: 功能可用 - ⛔: 功能可用,但在一些特殊场景下未完全支持 @@ -55,6 +55,42 @@ fn main() { } ``` +- 屏幕录制 + +```rust +use std::{sync::Arc, thread, time::Duration}; +use xcap::Monitor; + +fn main() { + let monitor = Monitor::from_point(100, 100).unwrap(); + + let video_recorder = Arc::new(monitor.video_recorder().unwrap()); + + let video_recorder_clone = video_recorder.clone(); + thread::spawn(move || { + video_recorder_clone + .on_frame(|frame| { + println!("frame: {:?}", frame.width); + Ok(()) + }) + .unwrap(); + }); + + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); +} + +``` + - 窗口截图 ```rust @@ -104,6 +140,8 @@ fn main() { } ``` +更多例子可以在 [examples](./examples) 目录中找到。 + ## Linux 系统要求 在 Linux 上,需要安装 `libxcb`, `libxrandr`与 `dbus`. diff --git a/README.md b/README.md index 6e515eb..7a4144c 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,22 @@ English | [简体中文](README-zh_CN.md) -XCap is a cross-platform screen capture library written in Rust. It supports Linux (X11, Wayland), MacOS, and Windows. XCap supports screenshot and video recording (to be implemented). +XCap is a cross-platform screen capture library written in Rust. It supports Linux (X11, Wayland), MacOS, and Windows. XCap supports screenshot and video recording (WIP). ## Features - Cross-platform: Supports Linux (X11, Wayland), MacOS, and Windows. - Supports multiple screenshot modes: Can take screenshots of the screen and windows. -- Supports video recording: Supports recording of the screen or window (to be implemented). +- Supports video recording: Supports recording of the screen or window (WIP). ### Implementation Status -| Feature | Linux(X11) | Linux(Wayland) | MacOS | Windows | -| ---------------- | ---------- | -------------- | ----- | ------- | -| Screen Capture | ✅ | ⛔ | ✅ | ✅ | -| Window Capture | ✅ | ⛔ | ✅ | ✅ | -| Screen Recording | 🛠️ | 🛠️ | 🛠️ | 🛠️ | -| Window Recording | 🛠️ | 🛠️ | 🛠️ | 🛠️ | +| Feature | Linux(X11) | Linux(Wayland) | MacOS | Windows(>=Windows 8.1) | +| ---------------- | ---------- | -------------- | ----- | ---------------------- | +| Screen Capture | ✅ | ⛔ | ✅ | ✅ | +| Window Capture | ✅ | ⛔ | ✅ | ✅ | +| Screen Recording | 🛠️ | 🛠️ | 🛠️ | ✅ | +| Window Recording | 🛠️ | 🛠️ | 🛠️ | 🛠️ | - ✅: Feature available - ⛔: Feature available, but not fully supported in some special scenarios @@ -55,6 +55,42 @@ fn main() { } ``` +- Screen Record + +```rust +use std::{sync::Arc, thread, time::Duration}; +use xcap::Monitor; + +fn main() { + let monitor = Monitor::from_point(100, 100).unwrap(); + + let video_recorder = Arc::new(monitor.video_recorder().unwrap()); + + let video_recorder_clone = video_recorder.clone(); + thread::spawn(move || { + video_recorder_clone + .on_frame(|frame| { + println!("frame: {:?}", frame.width); + Ok(()) + }) + .unwrap(); + }); + + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); +} + +``` + - Window Capture ```rust @@ -104,6 +140,8 @@ fn main() { } ``` +More examples in [examples](./examples) + ## Linux System Requirements On Linux, you need to install `libxcb`, `libxrandr`, and `dbus`. diff --git a/examples/monitor_record.rs b/examples/monitor_record.rs new file mode 100644 index 0000000..46b0b78 --- /dev/null +++ b/examples/monitor_record.rs @@ -0,0 +1,47 @@ +use fs_extra::dir; +use std::{ + thread, + time::{Duration, Instant}, +}; +use xcap::Monitor; + +fn main() { + let monitors = Monitor::all().unwrap(); + + dir::create_all("target/monitors", true).unwrap(); + + let monitor = monitors.get(0).unwrap().clone(); + + let mut i = 0; + let frame = 20; + let start = Instant::now(); + let fps = 1000 / frame; + + loop { + i += 1; + let time = Instant::now(); + let image = monitor.capture_image().unwrap(); + image + .save(format!("target/monitors/monitor-{}.png", i,)) + .unwrap(); + let sleep_time = fps * i - start.elapsed().as_millis() as i128; + println!( + "sleep_time: {:?} current_step_time: {:?}", + sleep_time, + time.elapsed() + ); + if sleep_time > 0 { + thread::sleep(Duration::from_millis(sleep_time as u64)); + } + + if i >= 900 { + break; + } + } + + println!("time {:?}", start.elapsed()); + let actual_fps = 900 / start.elapsed().as_secs(); + println!("actual fps: {}", actual_fps); + + // ffmpeg -framerate {actual_fps} -i monitor-%d.png -c:v libx264 -pix_fmt yuv420p output.mp4 +} diff --git a/examples/windows_monitor_record.rs b/examples/windows_monitor_record.rs new file mode 100644 index 0000000..c5a24b5 --- /dev/null +++ b/examples/windows_monitor_record.rs @@ -0,0 +1,30 @@ +use std::{sync::Arc, thread, time::Duration}; +use xcap::Monitor; + +fn main() { + let monitor = Monitor::from_point(100, 100).unwrap(); + + let video_recorder = Arc::new(monitor.video_recorder().unwrap()); + + let video_recorder_clone = video_recorder.clone(); + thread::spawn(move || { + video_recorder_clone + .on_frame(|frame| { + println!("frame: {:?}", frame.width); + Ok(()) + }) + .unwrap(); + }); + + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("start"); + video_recorder.start().unwrap(); + thread::sleep(Duration::from_secs(2)); + println!("stop"); + video_recorder.stop().unwrap(); +} diff --git a/src/error.rs b/src/error.rs index e93880c..cc565f1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,13 @@ +use std::sync::PoisonError; + use thiserror::Error; #[derive(Debug, Error)] pub enum XCapError { #[error("{0}")] Error(String), + #[error("StdSyncPoisonError {0}")] + StdSyncPoisonError(String), #[cfg(target_os = "linux")] #[error(transparent)] @@ -53,3 +57,9 @@ impl From for XCapError { } pub type XCapResult = Result; + +impl From> for XCapError { + fn from(value: PoisonError) -> Self { + XCapError::StdSyncPoisonError(value.to_string()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 84db7f3..a9786e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod error; mod monitor; +mod video_recorder; mod window; #[cfg(target_os = "macos")] @@ -19,3 +20,5 @@ pub use image; pub use error::{XCapError, XCapResult}; pub use monitor::Monitor; pub use window::Window; + +pub use video_recorder::VideoRecorder; diff --git a/src/linux/impl_monitor.rs b/src/linux/impl_monitor.rs index 0a06bdb..74196ba 100644 --- a/src/linux/impl_monitor.rs +++ b/src/linux/impl_monitor.rs @@ -11,7 +11,7 @@ use xcb::{ use crate::error::{XCapError, XCapResult}; -use super::capture::capture_monitor; +use super::{capture::capture_monitor, impl_video_recorder::ImplVideoRecorder}; #[derive(Debug, Clone)] pub(crate) struct ImplMonitor { @@ -236,4 +236,8 @@ impl ImplMonitor { pub fn capture_image(&self) -> XCapResult { capture_monitor(self) } + + pub fn video_recorder(&self) -> XCapResult { + ImplVideoRecorder::new() + } } diff --git a/src/linux/impl_video_recorder.rs b/src/linux/impl_video_recorder.rs new file mode 100644 index 0000000..cea5571 --- /dev/null +++ b/src/linux/impl_video_recorder.rs @@ -0,0 +1,25 @@ +#![allow(unused)] + +use crate::{video_recorder::Frame, XCapResult}; + +#[derive(Debug, Clone)] +pub struct ImplVideoRecorder {} + +impl ImplVideoRecorder { + pub fn new() -> XCapResult { + unimplemented!() + } + + pub fn on_frame(&self, on_frame: F) -> XCapResult<()> + where + F: Fn(Frame) -> XCapResult<()> + Send + 'static, + { + unimplemented!() + } + pub fn start(&self) -> XCapResult<()> { + unimplemented!() + } + pub fn stop(&self) -> XCapResult<()> { + unimplemented!() + } +} diff --git a/src/linux/mod.rs b/src/linux/mod.rs index e28c939..68de716 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -4,4 +4,5 @@ mod wayland_capture; mod xorg_capture; pub mod impl_monitor; +pub mod impl_video_recorder; pub mod impl_window; diff --git a/src/linux/wayland_capture.rs b/src/linux/wayland_capture.rs index 41c5acb..9fb01fe 100644 --- a/src/linux/wayland_capture.rs +++ b/src/linux/wayland_capture.rs @@ -68,7 +68,7 @@ fn org_gnome_shell_screenshot( let filename = path.to_string_lossy().to_string(); - proxy.method_call( + proxy.method_call::<(), (i32, i32, i32, i32, bool, &String), &str, &str>( "org.gnome.Shell.Screenshot", "ScreenshotArea", (x, y, width, height, false, &filename), @@ -124,7 +124,7 @@ fn org_freedesktop_portal_screenshot( options.insert(String::from("modal"), Variant(Box::new(true))); options.insert(String::from("interactive"), Variant(Box::new(false))); - proxy.method_call( + proxy.method_call::<(), (&str, PropMap), &str, &str>( "org.freedesktop.portal.Screenshot", "Screenshot", ("", options), diff --git a/src/macos/impl_monitor.rs b/src/macos/impl_monitor.rs index eb94a82..595bf94 100644 --- a/src/macos/impl_monitor.rs +++ b/src/macos/impl_monitor.rs @@ -6,7 +6,7 @@ use image::RgbaImage; use crate::error::{XCapError, XCapResult}; -use super::capture::capture; +use super::{capture::capture, impl_video_recorder::ImplVideoRecorder}; #[derive(Debug, Clone)] pub(crate) struct ImplMonitor { @@ -132,4 +132,8 @@ impl ImplMonitor { kCGNullWindowID, ) } + + pub fn video_recorder(&self) -> XCapResult { + ImplVideoRecorder::new() + } } diff --git a/src/macos/impl_video_recorder.rs b/src/macos/impl_video_recorder.rs new file mode 100644 index 0000000..cea5571 --- /dev/null +++ b/src/macos/impl_video_recorder.rs @@ -0,0 +1,25 @@ +#![allow(unused)] + +use crate::{video_recorder::Frame, XCapResult}; + +#[derive(Debug, Clone)] +pub struct ImplVideoRecorder {} + +impl ImplVideoRecorder { + pub fn new() -> XCapResult { + unimplemented!() + } + + pub fn on_frame(&self, on_frame: F) -> XCapResult<()> + where + F: Fn(Frame) -> XCapResult<()> + Send + 'static, + { + unimplemented!() + } + pub fn start(&self) -> XCapResult<()> { + unimplemented!() + } + pub fn stop(&self) -> XCapResult<()> { + unimplemented!() + } +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 262de57..ff876af 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -2,4 +2,5 @@ mod boxed; mod capture; pub mod impl_monitor; +pub mod impl_video_recorder; pub mod impl_window; diff --git a/src/monitor.rs b/src/monitor.rs index 8d089f7..89458e5 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,6 +1,6 @@ use image::RgbaImage; -use crate::{error::XCapResult, platform::impl_monitor::ImplMonitor}; +use crate::{error::XCapResult, platform::impl_monitor::ImplMonitor, VideoRecorder}; #[derive(Debug, Clone)] pub struct Monitor { @@ -78,4 +78,10 @@ impl Monitor { pub fn capture_image(&self) -> XCapResult { self.impl_monitor.capture_image() } + + pub fn video_recorder(&self) -> XCapResult { + let impl_video_recorder = self.impl_monitor.video_recorder()?; + + Ok(VideoRecorder::new(impl_video_recorder)) + } } diff --git a/src/video_recorder.rs b/src/video_recorder.rs new file mode 100644 index 0000000..57ff761 --- /dev/null +++ b/src/video_recorder.rs @@ -0,0 +1,85 @@ +use std::sync::{Condvar, Mutex}; + +use crate::{platform::impl_video_recorder::ImplVideoRecorder, XCapResult}; + +#[derive(Debug, Clone)] +pub struct Frame { + pub width: u32, + pub height: u32, + pub raw: Vec, +} + +impl Frame { + pub fn new(width: u32, height: u32, raw: Vec) -> Self { + Self { width, height, raw } + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) struct RecorderWaker { + parking: Mutex, + condvar: Condvar, +} + +impl RecorderWaker { + #[allow(dead_code)] + pub fn new() -> Self { + Self { + parking: Mutex::new(true), + condvar: Condvar::new(), + } + } + #[allow(dead_code)] + pub fn wake(&self) -> XCapResult<()> { + let mut parking = self.parking.lock()?; + *parking = false; + self.condvar.notify_one(); + + Ok(()) + } + #[allow(dead_code)] + pub fn sleep(&self) -> XCapResult<()> { + let mut parking = self.parking.lock()?; + *parking = true; + + Ok(()) + } + #[allow(dead_code)] + pub fn wait(&self) -> XCapResult<()> { + let mut parking = self.parking.lock()?; + while *parking { + parking = self.condvar.wait(parking)?; + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct VideoRecorder { + impl_video_recorder: ImplVideoRecorder, +} + +impl VideoRecorder { + pub(crate) fn new(impl_video_recorder: ImplVideoRecorder) -> VideoRecorder { + VideoRecorder { + impl_video_recorder, + } + } +} + +impl VideoRecorder { + pub fn on_frame(&self, on_frame: F) -> XCapResult<()> + where + F: Fn(Frame) -> XCapResult<()> + Send + 'static, + { + self.impl_video_recorder.on_frame(on_frame) + } + pub fn start(&self) -> XCapResult<()> { + self.impl_video_recorder.start() + } + pub fn stop(&self) -> XCapResult<()> { + self.impl_video_recorder.stop() + } +} diff --git a/src/windows/capture.rs b/src/windows/capture.rs index 398e451..9740556 100644 --- a/src/windows/capture.rs +++ b/src/windows/capture.rs @@ -18,7 +18,7 @@ use crate::error::{XCapError, XCapResult}; use super::{ boxed::{BoxHBITMAP, BoxHDC}, - utils::get_os_major_version, + utils::{bgra_to_rgba_image, get_os_major_version}, }; fn to_rgba_image( @@ -61,17 +61,7 @@ fn to_rgba_image( } }; - let is_old_version = get_os_major_version() < 8; - for src in buffer.chunks_exact_mut(4) { - src.swap(0, 2); - // fix https://github.com/nashaofu/xcap/issues/92#issuecomment-1910014951 - if src[3] == 0 && is_old_version { - src[3] = 255; - } - } - - RgbaImage::from_raw(width as u32, height as u32, buffer) - .ok_or_else(|| XCapError::new("RgbaImage::from_raw failed")) + bgra_to_rgba_image(width as u32, height as u32, buffer) } #[allow(unused)] diff --git a/src/windows/impl_monitor.rs b/src/windows/impl_monitor.rs index dc2d9a5..cd7ae70 100644 --- a/src/windows/impl_monitor.rs +++ b/src/windows/impl_monitor.rs @@ -16,7 +16,10 @@ use windows::{ use crate::error::{XCapError, XCapResult}; -use super::{boxed::BoxHDC, capture::capture_monitor, utils::wide_string_to_string}; +use super::{ + boxed::BoxHDC, capture::capture_monitor, impl_video_recorder::ImplVideoRecorder, + utils::wide_string_to_string, +}; // A 函数与 W 函数区别 // https://learn.microsoft.com/zh-cn/windows/win32/learnwin32/working-with-strings @@ -161,4 +164,8 @@ impl ImplMonitor { pub fn capture_image(&self) -> XCapResult { capture_monitor(self.x, self.y, self.width as i32, self.height as i32) } + + pub fn video_recorder(&self) -> XCapResult { + ImplVideoRecorder::new(self.hmonitor) + } } diff --git a/src/windows/impl_video_recorder.rs b/src/windows/impl_video_recorder.rs new file mode 100644 index 0000000..727abb3 --- /dev/null +++ b/src/windows/impl_video_recorder.rs @@ -0,0 +1,175 @@ +use std::{slice, sync::Arc}; + +use windows::{ + core::Interface, + Win32::Graphics::{ + Direct3D::D3D_DRIVER_TYPE_HARDWARE, + Direct3D11::{ + D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Resource, ID3D11Texture2D, + D3D11_CPU_ACCESS_READ, D3D11_CREATE_DEVICE_BGRA_SUPPORT, + D3D11_CREATE_DEVICE_SINGLETHREADED, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ, + D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, + }, + Dxgi::{ + IDXGIDevice, IDXGIOutput1, IDXGIOutputDuplication, IDXGIResource, + DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_FRAME_INFO, + }, + Gdi::HMONITOR, + }, +}; + +use crate::{ + video_recorder::{Frame, RecorderWaker}, + XCapError, XCapResult, +}; + +use super::utils::bgra_to_rgba; + +pub fn texture_to_frame( + d3d_device: &ID3D11Device, + d3d_context: &ID3D11DeviceContext, + source_texture: ID3D11Texture2D, +) -> XCapResult { + unsafe { + let mut source_desc = D3D11_TEXTURE2D_DESC::default(); + source_texture.GetDesc(&mut source_desc); + source_desc.BindFlags = 0; + source_desc.MiscFlags = 0; + source_desc.Usage = D3D11_USAGE_STAGING; + source_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32; + + let copy_texture = { + let mut texture = None; + d3d_device.CreateTexture2D(&source_desc, None, Some(&mut texture))?; + texture.ok_or(XCapError::new("CreateTexture2D failed"))? + }; + + d3d_context.CopyResource(Some(©_texture.cast()?), Some(&source_texture.cast()?)); + + let resource: ID3D11Resource = copy_texture.cast()?; + let mut mapped = D3D11_MAPPED_SUBRESOURCE::default(); + d3d_context.Map( + Some(&resource.clone()), + 0, + D3D11_MAP_READ, + 0, + Some(&mut mapped), + )?; + + // Get a slice of bytes + let bgra = slice::from_raw_parts( + mapped.pData.cast(), + (source_desc.Height * mapped.RowPitch) as usize, + ); + + d3d_context.Unmap(Some(&resource), 0); + + Ok(Frame::new( + source_desc.Width, + source_desc.Height, + bgra_to_rgba(bgra.to_owned()), + )) + } +} + +#[derive(Debug, Clone)] +pub struct ImplVideoRecorder { + d3d_device: ID3D11Device, + d3d_context: ID3D11DeviceContext, + duplication: IDXGIOutputDuplication, + recorder_waker: Arc, +} + +impl ImplVideoRecorder { + pub fn new(hmonitor: HMONITOR) -> XCapResult { + unsafe { + let mut d3d_device = None; + D3D11CreateDevice( + None, + D3D_DRIVER_TYPE_HARDWARE, + None, + D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_SINGLETHREADED, + None, + D3D11_SDK_VERSION, + Some(&mut d3d_device), + None, + None, + )?; + + let d3d_device = d3d_device.ok_or(XCapError::new("Call D3D11CreateDevice failed"))?; + let dxgi_device = d3d_device.cast::()?; + let d3d_context = d3d_device.GetImmediateContext()?; + + let adapter = dxgi_device.GetAdapter()?; + + let mut output_index = 0; + loop { + let output = adapter.EnumOutputs(output_index)?; + output_index += 1; + let output_desc = output.GetDesc()?; + + let output1 = output.cast::()?; + let duplication = output1.DuplicateOutput(&dxgi_device)?; + + if output_desc.Monitor == hmonitor { + return Ok(Self { + d3d_device, + d3d_context, + duplication, + recorder_waker: Arc::new(RecorderWaker::new()), + }); + } + } + } + } + + pub fn on_frame(&self, on_frame: F) -> XCapResult<()> + where + F: Fn(Frame) -> XCapResult<()> + Send + 'static, + { + let duplication = self.duplication.clone(); + let d3d_device = self.d3d_device.clone(); + let d3d_context = self.d3d_context.clone(); + let recorder_waker = self.recorder_waker.clone(); + + loop { + recorder_waker.wait()?; + + let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default(); + let mut resource: Option = None; + + unsafe { + if let Err(err) = duplication.AcquireNextFrame(200, &mut frame_info, &mut resource) + { + // 尝试释放当前帧,不然不能获取到下一帧数据 + let _ = duplication.ReleaseFrame(); + if err.code() != DXGI_ERROR_WAIT_TIMEOUT { + break Err::<(), XCapError>(XCapError::new("DXGI_ERROR_UNSUPPORTED")); + } + } else { + // 如何确定 AcquireNextFrame 执行成功 + if frame_info.LastPresentTime != 0 { + let resource = resource.ok_or(XCapError::new("AcquireNextFrame failed"))?; + let source_texture = resource.cast::()?; + let frame = texture_to_frame(&d3d_device, &d3d_context, source_texture)?; + + on_frame(frame)?; + } + + // 最后释放帧,不然获取不到当前帧的数据 + duplication.ReleaseFrame()?; + } + } + } + } + pub fn start(&self) -> XCapResult<()> { + self.recorder_waker.wake()?; + + Ok(()) + } + pub fn stop(&self) -> XCapResult<()> { + self.recorder_waker.sleep()?; + + Ok(()) + } +} diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 115329e..992b377 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -3,4 +3,5 @@ mod capture; mod utils; pub mod impl_monitor; +pub mod impl_video_recorder; pub mod impl_window; diff --git a/src/windows/utils.rs b/src/windows/utils.rs index 96fceab..ad968cd 100644 --- a/src/windows/utils.rs +++ b/src/windows/utils.rs @@ -1,7 +1,8 @@ +use image::RgbaImage; use sysinfo::System; use windows::Win32::Foundation::GetLastError; -use crate::error::XCapResult; +use crate::{error::XCapResult, XCapError}; pub(super) fn wide_string_to_string(wide_string: &[u16]) -> XCapResult { let string = if let Some(null_pos) = wide_string.iter().position(|pos| *pos == 0) { @@ -28,3 +29,25 @@ pub(super) fn log_last_error(label: T) { log::error!("{} error: {:?}", label.to_string(), err); } } + +pub(super) fn bgra_to_rgba(mut buffer: Vec) -> Vec { + let is_old_version = get_os_major_version() < 8; + for src in buffer.chunks_exact_mut(4) { + src.swap(0, 2); + // fix https://github.com/nashaofu/xcap/issues/92#issuecomment-1910014951 + if src[3] == 0 && is_old_version { + src[3] = 255; + } + } + + buffer +} + +pub(super) fn bgra_to_rgba_image( + width: u32, + height: u32, + buffer: Vec, +) -> XCapResult { + RgbaImage::from_raw(width, height, bgra_to_rgba(buffer)) + .ok_or_else(|| XCapError::new("RgbaImage::from_raw failed")) +}