diff --git a/Cargo.lock b/Cargo.lock index 74db665..6125e44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,31 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "ffmpeg-next" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6380599799e175191eb7ffe82c97f36a2a90a36cbc54c738a903e5287d7f516a" +dependencies = [ + "bitflags 2.13.1", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b939bf79dd5949412a4b81cfe21a07f48ea21b47fcbb5f57816c8c2de5ae30b" +dependencies = [ + "bindgen", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + [[package]] name = "find-msvc-tools" version = "0.1.11" @@ -413,6 +438,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + [[package]] name = "hex" version = "0.4.3" @@ -644,6 +675,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1165,6 +1206,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.1.1" @@ -1182,6 +1229,8 @@ name = "vrec" version = "0.1.0" dependencies = [ "ashpd", + "ffmpeg-next", + "ffmpeg-sys-next", "libc", "memmap2", "minifb", diff --git a/Cargo.toml b/Cargo.toml index 583cc90..baf8669 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ edition = "2024" [dependencies] ashpd = { version = "0.13.13", features = ["screencast"] } +ffmpeg-next = "9.0.0" +ffmpeg-sys-next = "9.0.0" libc = "0.2.189" memmap2 = "0.9.11" minifb = "0.28.0" diff --git a/src/encoder.rs b/src/encoder.rs new file mode 100644 index 0000000..0b94deb --- /dev/null +++ b/src/encoder.rs @@ -0,0 +1,108 @@ +use ffmpeg_next::ffi::*; +use std::ptr; +use crate::capture::Frame; + +pub struct VaapiEncoder { + codec_ctx: *mut AVCodecContext, + hw_device_ctx: *mut AVBufferRef, + hw_frames_ctx: *mut AVBufferRef, +} + +impl VaapiEncoder { + pub fn new(width: u32, height: u32) -> Result { + unsafe { + // Find VAAPI encoder (h264_vaapi) + let codec = avcodec_find_encoder_by_name(b"h264_vaapi\0".as_ptr() as *const i8); + if codec.is_null() { + return Err("h264_vaapi encoder not found".into()); + } + + let codec_ctx = avcodec_alloc_context3(codec); + if codec_ctx.is_null() { + return Err("Failed to allocate codec context".into()); + } + + (*codec_ctx).width = width as i32; + (*codec_ctx).height = height as i32; + (*codec_ctx).time_base = AVRational { num: 1, den: 60 }; + (*codec_ctx).framerate = AVRational { num: 60, den: 1 }; + (*codec_ctx).pix_fmt = AVPixelFormat::AV_PIX_FMT_VAAPI; + + // Init hardware device + let mut hw_device_ctx: *mut AVBufferRef = ptr::null_mut(); + let ret = av_hwdevice_ctx_create( + &mut hw_device_ctx, + AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI, + ptr::null(), // default device + ptr::null_mut(), + 0, + ); + if ret < 0 { + return Err("Failed to create VAAPI hardware device".into()); + } + + // Allocate hw frames context + let hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx); + if hw_frames_ref.is_null() { + return Err("Failed to allocate hw frames context".into()); + } + let frames_ctx = (*hw_frames_ref).data as *mut AVHWFramesContext; + (*frames_ctx).format = AVPixelFormat::AV_PIX_FMT_VAAPI; + (*frames_ctx).sw_format = AVPixelFormat::AV_PIX_FMT_NV12; + (*frames_ctx).width = width as i32; + (*frames_ctx).height = height as i32; + (*frames_ctx).initial_pool_size = 20; + + let ret = av_hwframe_ctx_init(hw_frames_ref); + if ret < 0 { + return Err("Failed to init hw frames context".into()); + } + + (*codec_ctx).hw_frames_ctx = av_buffer_ref(hw_frames_ref); + + let ret = avcodec_open2(codec_ctx, codec, ptr::null_mut()); + if ret < 0 { + return Err("Failed to open h264_vaapi encoder".into()); + } + + Ok(Self { + codec_ctx, + hw_device_ctx, + hw_frames_ctx: hw_frames_ref, + }) + } + } + + pub fn encode_frame(&mut self, frame: &Frame) -> Result<(), String> { + unsafe { + match frame { + Frame::Raw { width, height, stride, data } => { + // For Phase 2 X11 fallback: + // 1. Convert BGRA to NV12 (omitted/mocked for brevity if not using swscale yet) + // 2. Upload NV12 to a VAAPI AVFrame via av_hwframe_get_buffer and av_hwframe_transfer_data + // 3. Send to avcodec_send_frame + println!("VAAPI: Mock encoding Raw CPU frame (X11 fallback). Needs BGRA->NV12 swscale."); + } + Frame::DmaBuf { width, height, format, fd, stride, offset } => { + // For Phase 2 Wayland zero-copy: + // 1. Populate AVDRMFrameDescriptor with the FD and modifier. + // 2. Create an AVFrame with format AV_PIX_FMT_DRM_PRIME. + // 3. Use av_hwframe_map to map it to AV_PIX_FMT_VAAPI. + // 4. Send to avcodec_send_frame. + println!("VAAPI: Mock encoding DMA-BUF frame (Wayland zero-copy). FD: {}", fd); + } + } + } + Ok(()) + } +} + +impl Drop for VaapiEncoder { + fn drop(&mut self) { + unsafe { + avcodec_free_context(&mut self.codec_ctx); + av_buffer_unref(&mut self.hw_device_ctx); + av_buffer_unref(&mut self.hw_frames_ctx); + } + } +} diff --git a/src/main.rs b/src/main.rs index 120d76e..03434a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ pub mod capture; +pub mod encoder; pub mod debug; use capture::{Frame, FrameSource};