fix: resolve SHM hang, DRM device mismatch, and duplicate VAAPI context
BUG-2 (HIGH): SHM Buffer event caused permanent hang In the ZwlrScreencopyFrameV1 dispatcher, receiving a SHM Buffer event left in_flight_surface stuck at AllocQueued forever, preventing queue_alloc_frame() from requesting new frames. Fix: treat Buffer as a metadata offer (v3 protocol), wait for BufferDone to decide failure, and add AllocQueued state guard to LinuxDmabuf handler. BUG-3 (MEDIUM): Portal backend picked wrong GPU on multi-GPU systems state_portal.rs hardcoded /dev/dri/renderD128 then renderD129, which selects the wrong GPU when PipeWire uses a different device. Fix: extract find_drm_render_nodes() as shared utility; defer DRM device selection to first PipeWire frame; test each candidate with av_hwframe_transfer_data to find the GPU that can actually import the DMA-BUF frame. BUG-4 (LOW): VAAPI device context created twice unnecessarily try_finalize_output() created an AvHwDevCtx stored in EverythingButFmt, but negotiate_format() discarded it (_hw_device_ctx) and EncState::new created a new one. Fix: thread the existing hw_device_ctx through negotiate_format() and create_encoder() to EncState::new() which reuses it when provided.
This commit is contained in:
83
src/avhw.rs
83
src/avhw.rs
@@ -1,4 +1,6 @@
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
use std::mem;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
@@ -7,6 +9,7 @@ use ffmpeg_next as ff;
|
|||||||
use ffmpeg_next::ffi;
|
use ffmpeg_next::ffi;
|
||||||
use ffmpeg_next::packet::Mut as _;
|
use ffmpeg_next::packet::Mut as _;
|
||||||
|
|
||||||
|
use crate::cap_portal::PwDmaBufFrame;
|
||||||
use crate::transform::{transpose_if_transform_transposed, Transform};
|
use crate::transform::{transpose_if_transform_transposed, Transform};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -123,6 +126,74 @@ impl Drop for AvHwFrameCtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test whether `drm_device` can import the PipeWire DMA-BUF frame via VAAPI.
|
||||||
|
pub fn test_dma_buf_import(drm_device: &Path, frame: &PwDmaBufFrame) -> Result<()> {
|
||||||
|
let hw_dev = AvHwDevCtx::new_vaapi(drm_device)?;
|
||||||
|
let frames = AvHwFrameCtx::for_capture(
|
||||||
|
&hw_dev,
|
||||||
|
frame.width,
|
||||||
|
frame.height,
|
||||||
|
ff::format::Pixel::RGBZ,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// SAFETY: AVDRMFrameDescriptor is a C POD struct. Zero-initialization is the
|
||||||
|
// expected FFmpeg setup before filling the fields used below.
|
||||||
|
let mut desc: ffi::AVDRMFrameDescriptor = unsafe { mem::zeroed() };
|
||||||
|
desc.nb_objects = 1;
|
||||||
|
desc.objects[0].fd = frame.fd.as_raw_fd();
|
||||||
|
desc.objects[0].size = 0;
|
||||||
|
desc.objects[0].format_modifier = frame.modifier;
|
||||||
|
desc.nb_layers = 1;
|
||||||
|
desc.layers[0].format = frame.format;
|
||||||
|
desc.layers[0].nb_planes = 1;
|
||||||
|
desc.layers[0].planes[0].object_index = 0;
|
||||||
|
desc.layers[0].planes[0].offset = frame.offset as isize;
|
||||||
|
desc.layers[0].planes[0].pitch = frame.stride as isize;
|
||||||
|
|
||||||
|
let desc_box = Box::new(desc);
|
||||||
|
let mut raw_frame = ff::frame::Video::empty();
|
||||||
|
// SAFETY: raw_frame owns a valid AVFrame. data[0] is used by FFmpeg's
|
||||||
|
// DRM_PRIME frame convention to point at an AVDRMFrameDescriptor. The Box is
|
||||||
|
// recovered before every return path below.
|
||||||
|
unsafe {
|
||||||
|
let raw_ptr = raw_frame.as_mut_ptr();
|
||||||
|
(*raw_ptr).data[0] = Box::into_raw(desc_box) as *mut u8;
|
||||||
|
(*raw_ptr).format = ffi::AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32;
|
||||||
|
(*raw_ptr).width = frame.width as i32;
|
||||||
|
(*raw_ptr).height = frame.height as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hw_frame = ff::frame::Video::empty();
|
||||||
|
// SAFETY: frames is an initialized AVHWFramesContext and hw_frame is a valid
|
||||||
|
// writable AVFrame wrapper.
|
||||||
|
let ret = unsafe { ffi::av_hwframe_get_buffer(frames.as_ptr(), hw_frame.as_mut_ptr(), 0) };
|
||||||
|
if ret < 0 {
|
||||||
|
// SAFETY: data[0] still contains the Box pointer installed above.
|
||||||
|
unsafe {
|
||||||
|
let _ = Box::from_raw((*raw_frame.as_ptr()).data[0] as *mut ffi::AVDRMFrameDescriptor);
|
||||||
|
(*raw_frame.as_mut_ptr()).data[0] = ptr::null_mut();
|
||||||
|
}
|
||||||
|
bail!("av_hwframe_get_buffer failed: error {ret}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: hw_frame is a valid VAAPI frame allocated from `frames`; raw_frame
|
||||||
|
// is a DRM_PRIME source frame whose descriptor describes `frame`'s DMA-BUF.
|
||||||
|
let ret = unsafe { ffi::av_hwframe_transfer_data(hw_frame.as_mut_ptr(), raw_frame.as_ptr(), 0) };
|
||||||
|
|
||||||
|
// SAFETY: data[0] still contains the Box pointer installed above. Recover it
|
||||||
|
// before checking the transfer result so all paths clean up the descriptor.
|
||||||
|
unsafe {
|
||||||
|
let _ = Box::from_raw((*raw_frame.as_ptr()).data[0] as *mut ffi::AVDRMFrameDescriptor);
|
||||||
|
(*raw_frame.as_mut_ptr()).data[0] = ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
bail!("av_hwframe_transfer_data failed: error {ret}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// EncState
|
// EncState
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -139,8 +210,8 @@ pub struct EncState {
|
|||||||
|
|
||||||
unsafe impl Send for EncState {}
|
unsafe impl Send for EncState {}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
impl EncState {
|
impl EncState {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
drm_device: &Path,
|
drm_device: &Path,
|
||||||
output_path: &Path,
|
output_path: &Path,
|
||||||
@@ -152,12 +223,16 @@ impl EncState {
|
|||||||
gop_size: u32,
|
gop_size: u32,
|
||||||
fps: u32,
|
fps: u32,
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
|
existing_hw_ctx: Option<AvHwDevCtx>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"EncState::new: {width}x{height} enc={enc_width}x{enc_height} transform={transform:?}"
|
"EncState::new: {width}x{height} enc={enc_width}x{enc_height} transform={transform:?}"
|
||||||
);
|
);
|
||||||
// 1. VAAPI device
|
// 1. VAAPI device — reuse existing context if provided
|
||||||
let hw_device_ctx = AvHwDevCtx::new_vaapi(drm_device)?;
|
let hw_device_ctx = match existing_hw_ctx {
|
||||||
|
Some(ctx) => ctx,
|
||||||
|
None => AvHwDevCtx::new_vaapi(drm_device)?,
|
||||||
|
};
|
||||||
|
|
||||||
// 2. Frame context for capture (XRGB/RGBZ)
|
// 2. Frame context for capture (XRGB/RGBZ)
|
||||||
let frames_rgb =
|
let frames_rgb =
|
||||||
@@ -482,6 +557,7 @@ pub fn create_encoder(
|
|||||||
transform: Transform,
|
transform: Transform,
|
||||||
bitrate: Option<u64>,
|
bitrate: Option<u64>,
|
||||||
gop_size: Option<u32>,
|
gop_size: Option<u32>,
|
||||||
|
existing_hw_ctx: Option<AvHwDevCtx>,
|
||||||
) -> Result<EncState> {
|
) -> Result<EncState> {
|
||||||
let (enc_w, enc_h) =
|
let (enc_w, enc_h) =
|
||||||
transpose_if_transform_transposed(transform, width as i32, height as i32);
|
transpose_if_transform_transposed(transform, width as i32, height as i32);
|
||||||
@@ -500,6 +576,7 @@ pub fn create_encoder(
|
|||||||
actual_gop_size,
|
actual_gop_size,
|
||||||
fps,
|
fps,
|
||||||
transform,
|
transform,
|
||||||
|
existing_hw_ctx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
71
src/state.rs
71
src/state.rs
@@ -188,23 +188,29 @@ pub struct State<S: CaptureSource> {
|
|||||||
// Helpers
|
// Helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Scan /dev/dri for all available DRM render nodes (renderD*), sorted by node number.
|
||||||
|
pub(crate) fn find_drm_render_nodes() -> Vec<PathBuf> {
|
||||||
|
let Ok(entries) = std::fs::read_dir("/dev/dri") else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut nodes: Vec<(u32, PathBuf)> = entries
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.filter_map(|entry| {
|
||||||
|
let path = entry.path();
|
||||||
|
let name = path.file_name()?.to_str()?;
|
||||||
|
let number = name.strip_prefix("renderD")?.parse::<u32>().ok()?;
|
||||||
|
std::fs::metadata(&path).ok()?;
|
||||||
|
Some((number, path))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
nodes.sort_by_key(|(number, _)| *number);
|
||||||
|
nodes.into_iter().map(|(_, path)| path).collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Scan /dev/dri for the first available DRM render node (renderD*).
|
/// Scan /dev/dri for the first available DRM render node (renderD*).
|
||||||
fn find_drm_render_node() -> Option<PathBuf> {
|
fn find_drm_render_node() -> Option<PathBuf> {
|
||||||
std::fs::read_dir("/dev/dri")
|
find_drm_render_nodes().into_iter().next()
|
||||||
.ok()?
|
|
||||||
.filter_map(|e| e.ok())
|
|
||||||
.filter(|e| {
|
|
||||||
e.file_name()
|
|
||||||
.to_str()
|
|
||||||
.map(|s| s.starts_with("renderD"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
.filter_map(|e| {
|
|
||||||
let path = e.path();
|
|
||||||
std::fs::metadata(&path).ok()?;
|
|
||||||
Some(path)
|
|
||||||
})
|
|
||||||
.min_by_key(|e| e.to_path_buf())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: CaptureSource> State<S> {
|
impl<S: CaptureSource> State<S> {
|
||||||
@@ -584,18 +590,18 @@ impl<S: CaptureSource> State<S> {
|
|||||||
EncConstructionStage::EverythingButFmt {
|
EncConstructionStage::EverythingButFmt {
|
||||||
output_info,
|
output_info,
|
||||||
output,
|
output,
|
||||||
hw_device_ctx: _hw_device_ctx,
|
hw_device_ctx,
|
||||||
cap,
|
cap,
|
||||||
screencopy_manager,
|
screencopy_manager,
|
||||||
dmabuf,
|
dmabuf,
|
||||||
} => (output_info, output, cap, screencopy_manager, dmabuf),
|
} => (output_info, output, hw_device_ctx, cap, screencopy_manager, dmabuf),
|
||||||
other => {
|
other => {
|
||||||
tracing::warn!("negotiate_format: not in EverythingButFmt stage");
|
tracing::warn!("negotiate_format: not in EverythingButFmt stage");
|
||||||
self.stage = other;
|
self.stage = other;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (output_info, output, cap, screencopy_manager, dmabuf) = stage_data;
|
let (output_info, output, hw_device_ctx, cap, screencopy_manager, dmabuf) = stage_data;
|
||||||
let drm_path = self.resolve_drm_path();
|
let drm_path = self.resolve_drm_path();
|
||||||
let fps = self.args.fps;
|
let fps = self.args.fps;
|
||||||
let bitrate = self.args.bitrate.unwrap_or_else(|| {
|
let bitrate = self.args.bitrate.unwrap_or_else(|| {
|
||||||
@@ -610,6 +616,7 @@ impl<S: CaptureSource> State<S> {
|
|||||||
output_info.transform,
|
output_info.transform,
|
||||||
self.args.bitrate,
|
self.args.bitrate,
|
||||||
self.args.gop_size,
|
self.args.gop_size,
|
||||||
|
Some(hw_device_ctx),
|
||||||
) {
|
) {
|
||||||
Ok(enc) => enc,
|
Ok(enc) => enc,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -1228,11 +1235,13 @@ impl Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
|
|||||||
_qhandle: &QueueHandle<State<CapWlrScreencopy>>,
|
_qhandle: &QueueHandle<State<CapWlrScreencopy>>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
|
// SHM buffer offer — in v3 the compositor enumerates supported buffer
|
||||||
|
// types (buffer and/or linux_dmabuf) before buffer_done. We only
|
||||||
|
// support DMA-BUF, so just log and wait for linux_dmabuf / buffer_done.
|
||||||
ScreencopyFrameEvent::Buffer { .. } => {
|
ScreencopyFrameEvent::Buffer { .. } => {
|
||||||
tracing::warn!(
|
tracing::debug!(
|
||||||
"Received SHM Buffer event — only DMA-BUF capture is supported. Ignoring."
|
"Received SHM Buffer offer — only DMA-BUF capture is supported"
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
ScreencopyFrameEvent::LinuxDmabuf {
|
ScreencopyFrameEvent::LinuxDmabuf {
|
||||||
format,
|
format,
|
||||||
@@ -1240,6 +1249,12 @@ impl Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
|
|||||||
height,
|
height,
|
||||||
} => {
|
} => {
|
||||||
tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}");
|
tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}");
|
||||||
|
|
||||||
|
if !matches!(state.in_flight_surface, InFlightSurface::AllocQueued) {
|
||||||
|
tracing::warn!("Received LinuxDmabuf while no frame allocation was queued");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) {
|
if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) {
|
||||||
state.negotiate_format(format, width, height);
|
state.negotiate_format(format, width, height);
|
||||||
if state.errored {
|
if state.errored {
|
||||||
@@ -1251,6 +1266,20 @@ impl Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
|
|||||||
}
|
}
|
||||||
state.on_frame_allocd((), format, width, height);
|
state.on_frame_allocd((), format, width, height);
|
||||||
}
|
}
|
||||||
|
// v3 terminal event: all buffer offers have been enumerated.
|
||||||
|
// If still AllocQueued, the compositor never sent linux_dmabuf —
|
||||||
|
// DMA-BUF screencopy is unsupported, so we must error out.
|
||||||
|
ScreencopyFrameEvent::BufferDone => {
|
||||||
|
if matches!(state.in_flight_surface, InFlightSurface::AllocQueued) {
|
||||||
|
tracing::error!(
|
||||||
|
"Compositor did not offer DMA-BUF screencopy (only SHM); \
|
||||||
|
DMA-BUF capture is required"
|
||||||
|
);
|
||||||
|
state.in_flight_surface = InFlightSurface::None;
|
||||||
|
proxy.destroy();
|
||||||
|
state.errored = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
ScreencopyFrameEvent::Ready {
|
ScreencopyFrameEvent::Ready {
|
||||||
tv_sec_hi,
|
tv_sec_hi,
|
||||||
tv_sec_lo,
|
tv_sec_lo,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// 采集门户状态模块 —— 通过 PipeWire/DMA-BUF 进行屏幕采集并编码
|
// 采集门户状态模块 —— 通过 PipeWire/DMA-BUF 进行屏幕采集并编码
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
@@ -39,8 +40,8 @@ pub struct StatePortal {
|
|||||||
errored: bool,
|
errored: bool,
|
||||||
/// 是否为第一帧(首帧跳过帧率限制)
|
/// 是否为第一帧(首帧跳过帧率限制)
|
||||||
first_frame: bool,
|
first_frame: bool,
|
||||||
/// DRM 渲染设备路径(如 /dev/dri/renderD128)
|
/// DRM 渲染设备路径(如 /dev/dri/renderD128);None 表示首帧自动检测
|
||||||
drm_device: PathBuf,
|
drm_device: Option<PathBuf>,
|
||||||
/// 第一帧的时间戳(纳秒),用于计算相对 PTS
|
/// 第一帧的时间戳(纳秒),用于计算相对 PTS
|
||||||
first_pts_ns: Option<i64>,
|
first_pts_ns: Option<i64>,
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,11 @@ impl StatePortal {
|
|||||||
/// 初始化 DRM 设备路径和 PipeWire 采集端点,编码器延迟到第一帧到达时创建。
|
/// 初始化 DRM 设备路径和 PipeWire 采集端点,编码器延迟到第一帧到达时创建。
|
||||||
pub fn new(args: Args) -> Result<Self> {
|
pub fn new(args: Args) -> Result<Self> {
|
||||||
let drm_device = resolve_drm_device(&args)?;
|
let drm_device = resolve_drm_device(&args)?;
|
||||||
tracing::info!("Using DRM device: {}", drm_device.display());
|
if let Some(ref drm_device) = drm_device {
|
||||||
|
tracing::info!("Using DRM device: {}", drm_device.display());
|
||||||
|
} else {
|
||||||
|
tracing::info!("DRM device auto-detection enabled");
|
||||||
|
}
|
||||||
|
|
||||||
let cap = CapPortal::new(&args)?;
|
let cap = CapPortal::new(&args)?;
|
||||||
|
|
||||||
@@ -92,8 +97,9 @@ impl StatePortal {
|
|||||||
frame.modifier
|
frame.modifier
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let drm_path = self.resolve_drm_device_for_frame(&frame)?;
|
||||||
let enc = avhw::create_encoder(
|
let enc = avhw::create_encoder(
|
||||||
&self.drm_device,
|
&drm_path,
|
||||||
self.args.output.as_ref(),
|
self.args.output.as_ref(),
|
||||||
frame.width,
|
frame.width,
|
||||||
frame.height,
|
frame.height,
|
||||||
@@ -101,6 +107,7 @@ impl StatePortal {
|
|||||||
Transform::Normal,
|
Transform::Normal,
|
||||||
self.args.bitrate,
|
self.args.bitrate,
|
||||||
self.args.gop_size,
|
self.args.gop_size,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.enc = Some(enc);
|
self.enc = Some(enc);
|
||||||
@@ -128,6 +135,44 @@ impl StatePortal {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_drm_device_for_frame(&mut self, frame: &PwDmaBufFrame) -> Result<PathBuf> {
|
||||||
|
if let Some(ref drm) = self.drm_device {
|
||||||
|
return Ok(drm.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let candidates = crate::state::find_drm_render_nodes();
|
||||||
|
if candidates.is_empty() {
|
||||||
|
bail!("No DRM render device found. Specify --drm-device.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut failures = Vec::new();
|
||||||
|
for candidate in &candidates {
|
||||||
|
match crate::avhw::test_dma_buf_import(candidate, frame) {
|
||||||
|
Ok(()) => {
|
||||||
|
tracing::info!(
|
||||||
|
"Auto-selected DRM device: {} (can import PipeWire DMA-BUF)",
|
||||||
|
candidate.display()
|
||||||
|
);
|
||||||
|
self.drm_device = Some(candidate.clone());
|
||||||
|
return Ok(candidate.clone());
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"DRM device {} cannot import frame: {err:#}",
|
||||||
|
candidate.display()
|
||||||
|
);
|
||||||
|
failures.push(format!("{}: {err:#}", candidate.display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!(
|
||||||
|
"No DRM render device can import the PipeWire DMA-BUF frame. \
|
||||||
|
Specify --drm-device. Tried: {}",
|
||||||
|
failures.join("; ")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// 处理单帧 DMA-BUF 数据
|
/// 处理单帧 DMA-BUF 数据
|
||||||
///
|
///
|
||||||
/// 完整的帧处理流水线:
|
/// 完整的帧处理流水线:
|
||||||
@@ -311,25 +356,14 @@ fn build_drm_descriptor(frame: &PwDmaBufFrame) -> ffi::AVDRMFrameDescriptor {
|
|||||||
desc
|
desc
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::os::fd::AsRawFd;
|
|
||||||
|
|
||||||
/// 解析 DRM 渲染设备路径
|
/// 解析 DRM 渲染设备路径
|
||||||
///
|
///
|
||||||
/// 优先使用命令行指定的设备路径,否则依次尝试
|
/// 仅使用命令行指定的设备路径;未指定则在首帧到达时自动检测。
|
||||||
/// `/dev/dri/renderD128` 和 `/dev/dri/renderD129`。
|
fn resolve_drm_device(args: &Args) -> Result<Option<PathBuf>> {
|
||||||
fn resolve_drm_device(args: &Args) -> Result<PathBuf> {
|
|
||||||
if let Some(ref drm) = args.drm_device {
|
if let Some(ref drm) = args.drm_device {
|
||||||
return Ok(PathBuf::from(drm));
|
return Ok(Some(PathBuf::from(drm)));
|
||||||
}
|
}
|
||||||
|
Ok(None)
|
||||||
for render in &["/dev/dri/renderD128", "/dev/dri/renderD129"] {
|
|
||||||
let path = PathBuf::from(render);
|
|
||||||
if path.exists() {
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("No DRM render device found. Specify --drm-device.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user