use std::collections::VecDeque;
use std::sync::mpsc::{self, Receiver, TryRecvError};
use std::time::{Duration, Instant};
use crate::capture::FrameCapture;
use crate::error_message;
use crate::render_cache::BakedFrame;
use crate::trace::VideoFrameTrace;
use alpha2_media::AudioBlock;
use alpha2_native::Yuv420pFrameBuffer;
use alpha2_render::{PlaneTarget, ReadbackFailure, RenderError, Yuv420pReadback, Yuv420pTargets};
use alpha2_time::MediaTime;
pub enum PendingMedia {
Video {
index: u64,
time: MediaTime,
readback: PendingVideoReadback,
trace: Box<VideoFrameTrace>,
bake: Option<(String, bool)>,
},
Baked {
index: u64,
time: MediaTime,
frame: Box<BakedFrame>,
trace: Box<VideoFrameTrace>,
},
Audio(AudioBlock),
}
pub struct PendingVideoReadback {
pub readback: Box<Yuv420pReadback>,
pub mapping: Receiver<(Result<(), RenderError>, Duration)>,
pub mapping_ready: Option<(Result<(), RenderError>, Duration)>,
}
fn mapping_error(result: Result<(), RenderError>) -> Result<(), String> {
result.map_err(error_message)
}
const DROPPED: RenderError = RenderError::TextureReadback(ReadbackFailure::CallbackDropped);
impl PendingVideoReadback {
pub fn new(readback: Yuv420pReadback) -> Self {
let (sender, mapping) = mpsc::channel();
let mapping_started = Instant::now();
readback.map_async(move |mapped| {
let _ = sender.send((mapped, mapping_started.elapsed()));
});
Self {
readback: Box::new(readback),
mapping,
mapping_ready: None,
}
}
pub fn staging_bytes(&self) -> u64 {
self.readback.staging_bytes()
}
pub fn try_ready(&mut self) -> Result<bool, String> {
if let Some((mapped, _)) = self.mapping_ready {
return mapping_error(mapped).map(|()| true);
}
self.readback.poll();
match self.mapping.try_recv() {
Ok(mapping) => {
self.mapping_ready = Some(mapping);
mapping_error(mapping.0).map(|()| true)
}
Err(TryRecvError::Empty) => Ok(false),
Err(TryRecvError::Disconnected) => mapping_error(Err(DROPPED)).map(|()| false),
}
}
pub fn wait_ready(&mut self) -> Result<(), String> {
if self.try_ready()? {
return Ok(());
}
self.readback.wait_for_submission().map_err(error_message)?;
let mapping = self
.mapping
.recv()
.unwrap_or((Err(DROPPED), Duration::ZERO));
self.mapping_ready = Some(mapping);
mapping_error(mapping.0)
}
pub fn finish_into(
self,
frame: &mut Yuv420pFrameBuffer,
capture: Option<&mut FrameCapture>,
bake: Option<&mut BakedFrame>,
) -> Result<(), String> {
let (width, height) = frame.dimensions();
let planes = frame.begin_write().map_err(error_message)?;
let (y_stride, chroma_stride) = (planes.y_stride, planes.chroma_stride);
let mut targets = Yuv420pTargets {
y: PlaneTarget::new(planes.y, y_stride),
u: PlaneTarget::new(planes.u, chroma_stride),
v: PlaneTarget::new(planes.v, chroma_stride),
};
self.readback
.finish_into(&mut targets)
.map_err(error_message)?;
if let Some(capture) = capture {
capture.write(
(targets.y.bytes(), y_stride),
(targets.u.bytes(), chroma_stride),
(targets.v.bytes(), chroma_stride),
)?;
}
if let Some(bake) = bake {
let chroma = (width.div_ceil(2), height.div_ceil(2));
bake.y = pack_rows(targets.y.bytes(), y_stride, (width, height));
bake.u = pack_rows(targets.u.bytes(), chroma_stride, chroma);
bake.v = pack_rows(targets.v.bytes(), chroma_stride, chroma);
}
Ok(())
}
pub fn mapping_latency(&self) -> Duration {
self.mapping_ready
.map_or(Duration::ZERO, |(_, latency)| latency)
}
}
pub fn unpack_baked(
baked: &BakedFrame,
frame: &mut Yuv420pFrameBuffer,
capture: Option<&mut FrameCapture>,
) -> Result<(), String> {
let (width, height) = frame.dimensions();
let chroma = (width.div_ceil(2), height.div_ceil(2));
let planes = frame.begin_write().map_err(error_message)?;
let (y_stride, chroma_stride) = (planes.y_stride, planes.chroma_stride);
unpack_rows(&baked.y, planes.y, y_stride, (width, height));
unpack_rows(&baked.u, planes.u, chroma_stride, chroma);
unpack_rows(&baked.v, planes.v, chroma_stride, chroma);
if let Some(capture) = capture {
capture.write(
(&baked.y, width),
(&baked.u, chroma.0),
(&baked.v, chroma.0),
)?;
}
Ok(())
}
fn pack_rows(plane: &[u8], stride: usize, (width, height): (usize, usize)) -> Vec<u8> {
let mut packed = Vec::with_capacity(width * height);
if stride == width {
packed.extend_from_slice(&plane[..width * height]);
return packed;
}
for row in 0..height {
packed.extend_from_slice(&plane[row * stride..row * stride + width]);
}
packed
}
fn unpack_rows(packed: &[u8], plane: &mut [u8], stride: usize, (width, height): (usize, usize)) {
for row in 0..height {
plane[row * stride..row * stride + width]
.copy_from_slice(&packed[row * width..(row + 1) * width]);
}
}
pub fn ready_pending_media(pending: &mut VecDeque<PendingMedia>) -> Result<Option<bool>, String> {
match pending.front_mut() {
Some(PendingMedia::Audio(_)) => Ok(Some(false)),
Some(PendingMedia::Baked { .. }) => Ok(Some(true)),
Some(PendingMedia::Video {
readback, index, ..
}) => readback
.try_ready()
.map(|ready| ready.then_some(true))
.map_err(|error| {
crate::failure::tag_phase(
crate::failure::ExportPhase::Readback,
Some(*index),
error,
)
}),
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_plane_is_packed_into_one_allocation_of_its_visible_rows() {
let (width, height, stride) = (100, 7, 128);
let plane: Vec<u8> = (0..stride * height)
.map(|index| u8::try_from(index % 251).expect("fits"))
.collect();
let packed = pack_rows(&plane, stride, (width, height));
assert_eq!(packed.len(), width * height);
assert_eq!(packed.capacity(), width * height);
for row in 0..height {
assert_eq!(
packed[row * width..(row + 1) * width],
plane[row * stride..row * stride + width]
);
}
let unpadded = pack_rows(&plane, width, (width, height));
assert_eq!(unpadded, plane[..width * height]);
assert_eq!(unpadded.capacity(), width * height);
let mut round_trip = vec![0; stride * height];
unpack_rows(&packed, &mut round_trip, stride, (width, height));
assert_eq!(pack_rows(&round_trip, stride, (width, height)), packed);
}
}