feat: GPU-downscale + software H.264 encode pipeline (WIP)
Add SwEncState in avhw.rs: GPU pipeline using scale_vaapi to downscale 4K BGRA -> 2K NV12 on AMD iGPU, then software encode with libopenh264. - import_dma_buf_to_vaapi: av_hwframe_map based DMA-BUF import - SwEncState: GPU filter graph (scale_vaapi) + NV12->YUV420P + libopenh264 - state_portal.rs: integrated SwEncState, auto DRM device detection - vaapi_import_bench.rs: CPU vs GPU pipeline benchmark - sw_encode_bench.rs: software encode benchmark Benchmark results: GPU pipeline ~91 FPS theoretical (10.95ms/frame) vs CPU pipeline ~33 FPS (30.21ms/frame). Known issue: only 1 frame encoded in production recording, diagnostic STATS logging added to debug frame flow.
This commit is contained in:
+61
-43
@@ -16,7 +16,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::thread::{self, JoinHandle};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam_channel::{Receiver, Sender, bounded};
|
||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::args::Args;
|
||||
@@ -105,9 +105,7 @@ impl CapPortal {
|
||||
|
||||
// 通过 Portal 获取 PipeWire 连接 fd 和节点 ID
|
||||
// block_on 在此处同步等待异步 Portal 调用完成
|
||||
let (pw_fd, node_id) = rt.block_on(async {
|
||||
Self::setup_portal().await
|
||||
})?;
|
||||
let (pw_fd, node_id) = rt.block_on(async { Self::setup_portal().await })?;
|
||||
|
||||
let (frame_tx, frame_rx) = bounded(3);
|
||||
let (event_tx, event_rx) = bounded(8);
|
||||
@@ -181,9 +179,9 @@ impl CapPortal {
|
||||
use ashpd::desktop::PersistMode;
|
||||
|
||||
// 创建 Screencast D-Bus 代理,与桌面环境的 Portal 服务通信
|
||||
let proxy = Screencast::new().await.map_err(|e| {
|
||||
anyhow::anyhow!("Failed to create Screencast proxy: {e}")
|
||||
})?;
|
||||
let proxy = Screencast::new()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create Screencast proxy: {e}"))?;
|
||||
|
||||
// 创建 ScreenCast 会话(每个会话对应一次屏幕录制请求)
|
||||
let session = proxy
|
||||
@@ -287,10 +285,10 @@ impl Drop for CapPortal {
|
||||
fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
use pipewire as pw;
|
||||
use pw::properties::properties;
|
||||
use pw::spa::param::video::VideoInfoRaw;
|
||||
use pw::stream::{StreamBox, StreamFlags};
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use pw::spa::param::video::VideoInfoRaw;
|
||||
|
||||
// 初始化 PipeWire 进程全局库。
|
||||
//
|
||||
@@ -329,9 +327,7 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
let core = match context.connect_fd(pw_fd, None) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let _ = event_tx.try_send(PwCtrlEvent::Error(format!(
|
||||
"connect_fd failed: {e}"
|
||||
)));
|
||||
let _ = event_tx.try_send(PwCtrlEvent::Error(format!("connect_fd failed: {e}")));
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -357,8 +353,7 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
}
|
||||
};
|
||||
|
||||
let format_info: Rc<Cell<Option<(u32, u32, u32, u64)>>> =
|
||||
Rc::new(Cell::new(None));
|
||||
let format_info: Rc<Cell<Option<(u32, u32, u32, u64)>>> = Rc::new(Cell::new(None));
|
||||
|
||||
let event_tx_state = event_tx.clone();
|
||||
let _listener = stream
|
||||
@@ -366,8 +361,7 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
.state_changed(move |_, _, old, new| {
|
||||
tracing::debug!("PipeWire stream state: {old:?} -> {new:?}");
|
||||
match new {
|
||||
pw::stream::StreamState::Error(_)
|
||||
| pw::stream::StreamState::Unconnected => {
|
||||
pw::stream::StreamState::Error(_) | pw::stream::StreamState::Unconnected => {
|
||||
let _ = event_tx_state.try_send(PwCtrlEvent::StreamEnded);
|
||||
}
|
||||
_ => {}
|
||||
@@ -436,7 +430,8 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
|
||||
// 从第一个数据项中获取 DMA-BUF 文件描述符
|
||||
// 通过 libspa 的 Data 包装类型安全地访问 SPA 数据结构
|
||||
let data_ref: &pw::spa::buffer::Data = unsafe { &*(datas_ptr as *const pw::spa::buffer::Data) };
|
||||
let data_ref: &pw::spa::buffer::Data =
|
||||
unsafe { &*(datas_ptr as *const pw::spa::buffer::Data) };
|
||||
let fd = data_ref.fd();
|
||||
if fd < 0 {
|
||||
unsafe { stream.queue_raw_buffer(raw_buf) };
|
||||
@@ -462,7 +457,8 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
for i in 0..n_metas {
|
||||
let meta = &*metas.add(i as usize);
|
||||
if meta.type_ == libspa::sys::SPA_META_Header
|
||||
&& meta.size as usize >= std::mem::size_of::<libspa::sys::spa_meta_header>()
|
||||
&& meta.size as usize
|
||||
>= std::mem::size_of::<libspa::sys::spa_meta_header>()
|
||||
&& !meta.data.is_null()
|
||||
{
|
||||
let header = &*(meta.data as *const libspa::sys::spa_meta_header);
|
||||
@@ -505,9 +501,7 @@ fn pipewire_thread(ctx: PwThreadCtx) {
|
||||
pts,
|
||||
};
|
||||
|
||||
if let Err(crossbeam_channel::TrySendError::Full(_)) =
|
||||
frame_tx.try_send(frame)
|
||||
{
|
||||
if let Err(crossbeam_channel::TrySendError::Full(_)) = frame_tx.try_send(frame) {
|
||||
let prev = dropped.fetch_add(1, Ordering::Relaxed);
|
||||
if prev > 0 && prev % 30 == 0 {
|
||||
tracing::warn!("dropped {prev} frames total: encoder backlog");
|
||||
@@ -593,35 +587,65 @@ const fn fourcc(a: u8, b: u8, c: u8, d: u8) -> u32 {
|
||||
/// 此函数建立了两者之间的映射关系。
|
||||
///
|
||||
/// 支持的格式:
|
||||
/// - BGRA/BGRx: 蓝绿红(Alpha/X) 32位格式
|
||||
/// - RGBA/RGBx: 红绿蓝(Alpha/X) 32位格式
|
||||
/// - ARGB/xRGB: Alpha/X-红绿蓝 32位格式 (映射为 AR24/XR24)
|
||||
/// - ABGR/xBGR: Alpha/X-蓝绿红 32位格式 (映射为 AB24/XB24)
|
||||
///
|
||||
/// 不支持的格式返回 0
|
||||
/// DRM 格式名描述像素值位布局(大端序),而非内存字节序。
|
||||
/// 例如 DRM_FORMAT_ARGB8888 在小端 x86 上内存为 [B,G,R,A] = PipeWire BGRA。
|
||||
fn spa_to_drm_fourcc(format: libspa::param::video::VideoFormat) -> u32 {
|
||||
use drm_fourcc::DrmFourcc;
|
||||
use libspa::param::video::VideoFormat;
|
||||
match format {
|
||||
VideoFormat::BGRA => fourcc(b'B', b'G', b'R', b'A'),
|
||||
VideoFormat::BGRx => fourcc(b'B', b'G', b'R', b'X'),
|
||||
VideoFormat::RGBA => fourcc(b'R', b'G', b'B', b'A'),
|
||||
VideoFormat::RGBx => fourcc(b'R', b'G', b'B', b'X'),
|
||||
VideoFormat::ARGB => fourcc(b'A', b'R', b'2', b'4'),
|
||||
VideoFormat::xRGB => fourcc(b'X', b'R', b'2', b'4'),
|
||||
VideoFormat::ABGR => fourcc(b'A', b'B', b'2', b'4'),
|
||||
VideoFormat::xBGR => fourcc(b'X', b'B', b'2', b'4'),
|
||||
// 不支持的格式返回 0,调用者应检查此值
|
||||
_ => 0, }
|
||||
VideoFormat::BGRA => DrmFourcc::Argb8888 as u32,
|
||||
VideoFormat::BGRx => DrmFourcc::Xrgb8888 as u32,
|
||||
VideoFormat::RGBA => DrmFourcc::Abgr8888 as u32,
|
||||
VideoFormat::RGBx => DrmFourcc::Xbgr8888 as u32,
|
||||
VideoFormat::ARGB => DrmFourcc::Bgra8888 as u32,
|
||||
VideoFormat::xRGB => DrmFourcc::Bgrx8888 as u32,
|
||||
VideoFormat::ABGR => DrmFourcc::Rgba8888 as u32,
|
||||
VideoFormat::xBGR => DrmFourcc::Rgbx8888 as u32,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use drm_fourcc::DrmFourcc;
|
||||
|
||||
#[test]
|
||||
fn spa_to_drm_fourcc_bgra() {
|
||||
fn spa_to_drm_fourcc_all_32bit() {
|
||||
use libspa::param::video::VideoFormat;
|
||||
assert_eq!(spa_to_drm_fourcc(VideoFormat::BGRA), fourcc(b'B', b'G', b'R', b'A'));
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::BGRA),
|
||||
DrmFourcc::Argb8888 as u32
|
||||
);
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::BGRx),
|
||||
DrmFourcc::Xrgb8888 as u32
|
||||
);
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::RGBA),
|
||||
DrmFourcc::Abgr8888 as u32
|
||||
);
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::RGBx),
|
||||
DrmFourcc::Xbgr8888 as u32
|
||||
);
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::ARGB),
|
||||
DrmFourcc::Bgra8888 as u32
|
||||
);
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::xRGB),
|
||||
DrmFourcc::Bgrx8888 as u32
|
||||
);
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::ABGR),
|
||||
DrmFourcc::Rgba8888 as u32
|
||||
);
|
||||
assert_eq!(
|
||||
spa_to_drm_fourcc(VideoFormat::xBGR),
|
||||
DrmFourcc::Rgbx8888 as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -629,10 +653,4 @@ mod tests {
|
||||
use libspa::param::video::VideoFormat;
|
||||
assert_eq!(spa_to_drm_fourcc(VideoFormat::NV12), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fourcc_values() {
|
||||
assert_eq!(fourcc(b'B', b'G', b'R', b'A'), 0x41524742);
|
||||
assert_eq!(fourcc(b'R', b'G', b'B', b'A'), 0x41424752);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user