#![forbid(unsafe_code)]
use crate::{CaptureError, Select};
use mediaway_common::{GpuDeviceHandle, Rational, StreamInfo, VideoFrame, VideoFrameStorage};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum CaptureOutputPreference {
#[default]
ZeroCopyGpu,
CpuFramesOk,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CameraCaptureConfig {
pub select: Select,
pub time_base: Rational,
pub output: CaptureOutputPreference,
pub gpu_device: Option<GpuDeviceHandle>,
}
impl CameraCaptureConfig {
#[must_use]
pub const fn default_camera(time_base: Rational) -> Self {
Self {
select: Select::Default,
time_base,
output: CaptureOutputPreference::ZeroCopyGpu,
gpu_device: None,
}
}
}
pub trait CameraCapture {
fn stream_info(&self) -> &StreamInfo;
fn poll_frame(&mut self) -> Result<Option<VideoFrame>, CaptureError>;
fn release_frame(&mut self) -> Result<(), CaptureError>;
fn close(&mut self) -> Result<(), CaptureError>;
fn capture_next_frame_blocking(
&mut self,
timeout: std::time::Duration,
) -> Result<VideoFrame, CaptureError> {
const RETRY_INTERVAL: std::time::Duration = std::time::Duration::from_millis(4);
let deadline = std::time::Instant::now() + timeout;
loop {
if let Some(frame) = self.poll_frame()? {
return Ok(frame);
}
if std::time::Instant::now() >= deadline {
return Err(CaptureError::Timeout);
}
std::thread::sleep(RETRY_INTERVAL.min(deadline - std::time::Instant::now()));
}
}
}
pub fn capture_camera_once<C: CameraCapture>(
open: impl FnOnce() -> Result<C, CaptureError>,
timeout: std::time::Duration,
) -> Result<VideoFrame, CaptureError> {
let mut session = open()?;
let result = session.capture_next_frame_blocking(timeout);
let _ = session.release_frame();
let _ = session.close();
match result {
Ok(frame) if matches!(frame.storage, VideoFrameStorage::Gpu(_)) => {
Err(CaptureError::Unsupported)
}
other => other,
}
}