use std::sync::Arc;
use std::thread;
use std::time::Instant;
#[cfg(feature = "hooks")]
use image::DynamicImage;
#[cfg(feature = "hooks")]
use styx_codec::decoder::frame_to_dynamic_image;
#[cfg(feature = "hooks")]
use styx_codec::image_utils::dynamic_image_to_frame;
use styx_codec::prelude::*;
#[cfg(feature = "hooks")]
type HookFn = Box<dyn FnMut(DynamicImage) -> DynamicImage + Send>;
#[cfg(feature = "hooks")]
type FrameHookFn = Box<dyn FnMut(FrameLease) -> FrameLease + Send>;
#[cfg(feature = "hooks")]
enum HookStore<T> {
Local(Option<T>),
}
#[cfg(feature = "hooks")]
impl<T> HookStore<T> {
fn take(&mut self) -> T {
match self {
HookStore::Local(h) => h.take().expect("hook missing"),
}
}
}
use crate::capture_api::{CaptureHandle, CaptureRequest};
pub struct MediaPipelineBuilder<'a> {
capture: CaptureRequest<'a>,
decoder: Option<Arc<dyn Codec>>,
encoder: Option<Arc<dyn Codec>>,
#[cfg(feature = "hooks")]
hook: Option<HookStore<HookFn>>,
#[cfg(feature = "hooks")]
frame_hook: Option<HookStore<FrameHookFn>>,
decode_enabled: bool,
encode_enabled: bool,
}
impl<'a> MediaPipelineBuilder<'a> {
pub fn new(capture: CaptureRequest<'a>) -> Self {
Self {
capture,
decoder: None,
encoder: None,
#[cfg(feature = "hooks")]
hook: None,
#[cfg(feature = "hooks")]
frame_hook: None,
decode_enabled: true,
encode_enabled: true,
}
}
pub fn decoder(mut self, codec: Arc<dyn Codec>) -> Self {
self.decoder = Some(codec);
self
}
pub fn encoder(mut self, codec: Arc<dyn Codec>) -> Self {
self.encoder = Some(codec);
self
}
pub fn decode_enabled(mut self, enabled: bool) -> Self {
self.decode_enabled = enabled;
self
}
pub fn encode_enabled(mut self, enabled: bool) -> Self {
self.encode_enabled = enabled;
self
}
pub fn decoder_from_registry(
mut self,
registry: &CodecRegistryHandle,
fourcc: FourCc,
impl_name: Option<&str>,
prefer_hardware: bool,
) -> Result<Self, RegistryError> {
let decoder = lookup_codec(registry, fourcc, impl_name, prefer_hardware)?;
self.decoder = Some(decoder);
Ok(self)
}
pub fn encoder_from_registry(
mut self,
registry: &CodecRegistryHandle,
fourcc: FourCc,
impl_name: Option<&str>,
prefer_hardware: bool,
) -> Result<Self, RegistryError> {
let encoder = lookup_codec(registry, fourcc, impl_name, prefer_hardware)?;
self.encoder = Some(encoder);
Ok(self)
}
#[cfg(feature = "hooks")]
pub fn hook<F>(mut self, hook: F) -> Self
where
F: FnMut(DynamicImage) -> DynamicImage + Send + 'static,
{
self.hook = Some(HookStore::Local(Some(Box::new(hook))));
self
}
#[cfg(feature = "hooks")]
pub fn frame_hook<F>(mut self, hook: F) -> Self
where
F: FnMut(FrameLease) -> FrameLease + Send + 'static,
{
self.frame_hook = Some(HookStore::Local(Some(Box::new(hook))));
self
}
pub fn start(self) -> Result<MediaPipeline, crate::capture_api::CaptureError> {
let capture = self.capture.start()?;
Ok(MediaPipeline {
capture,
decoder: self.decoder,
encoder: self.encoder,
#[cfg(feature = "hooks")]
hook: self.hook,
#[cfg(feature = "hooks")]
frame_hook: self.frame_hook,
metrics: crate::metrics::PipelineMetrics::default(),
decode_enabled: self.decode_enabled,
encode_enabled: self.encode_enabled,
})
}
}
pub struct MediaPipeline {
capture: CaptureHandle,
decoder: Option<Arc<dyn Codec>>,
encoder: Option<Arc<dyn Codec>>,
#[cfg(feature = "hooks")]
hook: Option<HookStore<HookFn>>,
#[cfg(feature = "hooks")]
frame_hook: Option<HookStore<FrameHookFn>>,
metrics: crate::metrics::PipelineMetrics,
decode_enabled: bool,
encode_enabled: bool,
}
impl MediaPipeline {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> RecvOutcome<FrameLease> {
let capture_start = Instant::now();
match self.capture.recv() {
RecvOutcome::Data(frame) => {
self.metrics.capture.record(capture_start.elapsed());
self.process_frame(frame)
}
RecvOutcome::Empty => {
std::thread::yield_now();
RecvOutcome::Empty
}
RecvOutcome::Closed => RecvOutcome::Closed,
}
}
pub fn next_blocking(&mut self, wait: std::time::Duration) -> RecvOutcome<FrameLease> {
loop {
match self.next() {
RecvOutcome::Empty => {
if !wait.is_zero() {
std::thread::sleep(wait);
} else {
std::thread::yield_now();
}
}
other => return other,
}
}
}
#[cfg(feature = "async")]
pub async fn next_async(&mut self) -> RecvOutcome<FrameLease> {
let capture_start = Instant::now();
match self.capture.recv_async().await {
RecvOutcome::Data(frame) => {
self.metrics.capture.record(capture_start.elapsed());
tokio::task::block_in_place(|| self.process_frame(frame))
}
RecvOutcome::Empty => {
tokio::task::yield_now().await;
RecvOutcome::Empty
}
RecvOutcome::Closed => RecvOutcome::Closed,
}
}
#[cfg(feature = "async")]
pub fn spawn_async_worker(mut self) -> tokio::task::JoinHandle<()> {
tokio::task::spawn(async move {
loop {
match self.next_async().await {
RecvOutcome::Data(_) => {}
RecvOutcome::Empty => tokio::task::yield_now().await,
RecvOutcome::Closed => {
self.capture.stop();
break;
}
}
}
})
}
pub fn capture(&self) -> &CaptureHandle {
&self.capture
}
pub fn set_decoder(&mut self, decoder: Option<Arc<dyn Codec>>) {
self.decoder = decoder;
}
pub fn set_decoder_from_registry(
&mut self,
registry: &CodecRegistryHandle,
fourcc: FourCc,
impl_name: Option<&str>,
prefer_hardware: bool,
) -> Result<(), RegistryError> {
let decoder = lookup_codec(registry, fourcc, impl_name, prefer_hardware)?;
self.decoder = Some(decoder);
Ok(())
}
pub fn set_encoder(&mut self, encoder: Option<Arc<dyn Codec>>) {
self.encoder = encoder;
}
pub fn set_encoder_from_registry(
&mut self,
registry: &CodecRegistryHandle,
fourcc: FourCc,
impl_name: Option<&str>,
prefer_hardware: bool,
) -> Result<(), RegistryError> {
let encoder = lookup_codec(registry, fourcc, impl_name, prefer_hardware)?;
self.encoder = Some(encoder);
Ok(())
}
#[cfg(feature = "hooks")]
pub fn set_hook<F>(&mut self, hook: Option<F>)
where
F: FnMut(DynamicImage) -> DynamicImage + Send + 'static,
{
self.hook = hook.map(|h| HookStore::Local(Some(Box::new(h) as HookFn)));
}
#[cfg(feature = "hooks")]
pub fn set_frame_hook<F>(&mut self, hook: Option<F>)
where
F: FnMut(FrameLease) -> FrameLease + Send + 'static,
{
self.frame_hook = hook.map(|h| HookStore::Local(Some(Box::new(h) as FrameHookFn)));
}
pub fn reconfigure_capture(
&mut self,
request: CaptureRequest<'_>,
) -> Result<(), crate::capture_api::CaptureError> {
self.capture.reconfigure_in_place(request)
}
pub fn stop(self) {
self.capture.stop();
}
pub fn set_capture(&mut self, capture: CaptureHandle) {
let old = std::mem::replace(&mut self.capture, capture);
old.stop();
}
pub fn enable_decode(&mut self, enabled: bool) {
self.decode_enabled = enabled;
}
pub fn enable_encode(&mut self, enabled: bool) {
self.encode_enabled = enabled;
}
pub fn metrics(&self) -> crate::metrics::PipelineMetrics {
self.metrics.clone()
}
pub fn spawn_worker(mut self) -> thread::JoinHandle<()> {
thread::spawn(move || {
loop {
match self.next() {
RecvOutcome::Data(_) => {}
RecvOutcome::Empty => thread::yield_now(),
RecvOutcome::Closed => {
self.capture.stop();
break;
}
}
}
})
}
fn process_frame(&mut self, frame: FrameLease) -> RecvOutcome<FrameLease> {
let mut cur = frame;
if self.decode_enabled
&& let Some(dec) = &self.decoder
{
let t = Instant::now();
match dec.process(cur) {
Ok(f) => {
self.metrics.decode.record(t.elapsed());
cur = f;
}
Err(_) => return RecvOutcome::Closed,
}
}
#[cfg(feature = "hooks")]
if let Some(hook) = &mut self.frame_hook {
let mut h = hook.take();
cur = (h)(cur);
}
#[cfg(feature = "hooks")]
if let Some(hook) = &mut self.hook {
let ts = cur.meta().timestamp;
match frame_to_dynamic_image(&cur) {
Some(img) => {
let mut h = hook.take();
let img = (h)(img);
if let Some(f) = dynamic_image_to_frame(img, ts) {
cur = f;
} else {
return RecvOutcome::Closed;
}
}
None => return RecvOutcome::Closed,
}
}
if let Some(enc) = &self.encoder
&& self.encode_enabled
{
let t = Instant::now();
match enc.process(cur) {
Ok(f) => {
self.metrics.encode.record(t.elapsed());
cur = f;
}
Err(_) => return RecvOutcome::Closed,
}
}
RecvOutcome::Data(cur)
}
}
fn lookup_codec(
registry: &CodecRegistryHandle,
fourcc: FourCc,
impl_name: Option<&str>,
prefer_hardware: bool,
) -> Result<Arc<dyn Codec>, RegistryError> {
if let Some(name) = impl_name {
registry.lookup_named(fourcc, name)
} else if prefer_hardware {
registry.lookup_preferred(fourcc, &[], true)
} else {
registry.lookup_auto(fourcc)
}
}
impl Iterator for MediaPipeline {
type Item = FrameLease;
fn next(&mut self) -> Option<Self::Item> {
loop {
match MediaPipeline::next(self) {
RecvOutcome::Data(f) => return Some(f),
RecvOutcome::Empty => continue,
RecvOutcome::Closed => return None,
}
}
}
}