mod mp4;
use std::collections::VecDeque;
use std::future::Future;
use std::io::Write;
use mp4::Muxer;
#[cfg(all(feature = "native", windows))]
pub(crate) use mp4::nals;
use crate::gpu::readback::Readback;
use crate::host::{Encoder, Host, TIMESCALE, Unit};
use crate::shot::{Borrowed, Drawing, Step, Views};
use crate::{Error, Gpu, Result, Shot, View};
const IN_FLIGHT: usize = 3;
const KEYFRAMES: f64 = 2.0;
const BITS_PER_PIXEL: f64 = 0.15;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Pixels {
Rgba,
Nv12,
}
pub trait Sink {
const PIXELS: Pixels;
fn begin(&mut self, encoding: &Encoding, host: Option<&dyn Host>) -> Result<()> {
let _ = (encoding, host);
Ok(())
}
fn frame(&mut self, index: u64, pixels: Option<Vec<u8>>) -> impl Future<Output = Result<()>>;
fn finish(&mut self, end: u64) -> impl Future<Output = Result<()>>;
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Encoding {
pub size: [u32; 2],
pub fps: f64,
pub bitrate: u32,
}
impl Encoding {
pub fn new(size: [u32; 2], fps: f64) -> Encoding {
let bits = f64::from(size[0]) * f64::from(size[1]) * fps * BITS_PER_PIXEL;
Encoding { size, fps, bitrate: bits.clamp(1e5, 4e8) as u32 }
}
pub fn rate(&self) -> [u32; 2] {
for den in [1_u32, 1001, 1000] {
let num = (self.fps * f64::from(den)).round();
if (num / f64::from(den) - self.fps).abs() < 1e-6 * self.fps {
return [num as u32, den];
}
}
[(self.fps * 1000.0).round() as u32, 1000]
}
}
pub struct Mp4<W: Write> {
out: W,
bitrate: Option<u32>,
began: Option<(Box<dyn Encoder>, Muxer, f64)>,
keyed: Option<u64>,
}
impl<W: Write> Mp4<W> {
pub fn new(out: W) -> Mp4<W> {
Mp4 { out, bitrate: None, began: None, keyed: None }
}
#[must_use]
pub fn bitrate(mut self, bitrate: u32) -> Mp4<W> {
self.bitrate = Some(bitrate);
self
}
pub fn into_inner(self) -> W {
self.out
}
fn write(&mut self, units: Vec<Unit>) -> Result<()> {
let Some((_, muxer, _)) = &mut self.began else { return Ok(()) };
for unit in units {
let bytes = muxer.push(&unit).map_err(|reason| Error::Unsupported(reason.into()))?;
self.out.write_all(&bytes).map_err(|e| written(&e))?;
}
Ok(())
}
}
impl<W: Write> Sink for Mp4<W> {
const PIXELS: Pixels = Pixels::Nv12;
fn begin(&mut self, encoding: &Encoding, host: Option<&dyn Host>) -> Result<()> {
let mut encoding = *encoding;
if let Some(bitrate) = self.bitrate {
if bitrate == 0 {
return Err(Error::invalid("an MP4 takes a positive bitrate").at("bitrate"));
}
encoding.bitrate = bitrate;
}
let host = host.ok_or_else(|| Error::Unsupported("an MP4 is encoded by the Gpu's host; it has none".into()))?;
self.began = Some((host.encoder(&encoding)?, Muxer::new(encoding.size, TIMESCALE), encoding.fps));
Ok(())
}
async fn frame(&mut self, index: u64, pixels: Option<Vec<u8>>) -> Result<()> {
let Some(nv12) = pixels else { return Ok(()) };
let (encoder, _, fps) = self.began.as_mut().ok_or_else(|| Error::invalid("an MP4 begins before its frames"))?;
let time = ticks(index, *fps);
let key = self.keyed.is_none_or(|keyed| time - keyed >= (KEYFRAMES * f64::from(TIMESCALE)) as u64);
if key {
self.keyed = Some(time);
}
let units = encoder.encode(nv12, time, key).await?;
self.write(units)
}
async fn finish(&mut self, end: u64) -> Result<()> {
let (encoder, _, _) = self.began.as_mut().ok_or_else(|| Error::invalid("an MP4 begins before it ends"))?;
let units = encoder.finish().await?;
self.write(units)?;
let (_, muxer, fps) = self.began.as_mut().expect("begun");
let last = muxer.finish(ticks(end.max(1), *fps));
self.out.write_all(&last).map_err(|e| written(&e))?;
self.out.flush().map_err(|e| written(&e))
}
}
pub struct Video<S: Sink> {
drawing: Drawing,
sink: S,
fps: f64,
index: u64,
flight: VecDeque<(u64, Option<Readback>)>,
drawn: bool,
}
impl Shot {
pub fn video<S: Sink>(&self, gpu: &Gpu, fps: f64, mut sink: S) -> Result<Video<S>> {
if !(fps.is_finite() && (1.0..=240.0).contains(&fps)) {
return Err(Error::invalid("a video shows 1 to 240 frames a second").at("fps"));
}
self.check(S::PIXELS)?;
let host = gpu.0.lock().host.clone();
sink.begin(&Encoding::new(self.size(), fps), host.as_deref())?;
Ok(Video {
drawing: Drawing::later(self.clone(), S::PIXELS),
sink,
fps,
index: 0,
flight: VecDeque::with_capacity(IN_FLIGHT * 2),
drawn: false,
})
}
}
impl<S: Sink> Video<S> {
pub fn time(&self) -> f64 {
self.index as f64 / self.fps
}
pub fn frames(&self) -> u64 {
self.index
}
pub async fn frame(&mut self, views: &mut [&mut View]) -> Result<()> {
self.frame_of(&mut Borrowed(views)).await
}
pub(crate) async fn frame_of(&mut self, views: &mut impl Views) -> Result<()> {
let held = self.drawn && !views.lend(|views| views.iter().any(|v| v.needs_render()));
if held {
self.flight.push_back((self.index, None));
} else {
while self.flight.iter().filter(|(_, readback)| readback.is_some()).count() >= IN_FLIGHT {
self.hand_on().await?;
}
let time = self.time();
views.lend(|views| self.drawing.begin(views))?;
let readback = loop {
match views.lend(|views| self.drawing.step(views, time, false))? {
Step::Done(readback) => break readback,
Step::Easing => {}
Step::Waiting => self.drawing.wait().await,
}
};
self.flight.push_back((self.index, Some(readback)));
self.drawn = true;
}
self.index += 1;
while matches!(self.flight.front(), Some((_, None))) {
self.hand_on().await?;
}
Ok(())
}
pub async fn finish(mut self) -> Result<S> {
while !self.flight.is_empty() {
self.hand_on().await?;
}
self.sink.finish(self.index).await?;
Ok(self.sink)
}
async fn hand_on(&mut self) -> Result<()> {
let Some((index, readback)) = self.flight.pop_front() else { return Ok(()) };
let pixels = match readback {
Some(readback) => Some(self.drawing.read(&readback).await?),
None => None,
};
self.sink.frame(index, pixels).await
}
}
fn ticks(index: u64, fps: f64) -> u64 {
(index as f64 * f64::from(TIMESCALE) / fps).round() as u64
}
fn written(error: &std::io::Error) -> Error {
Error::Io(format!("a video could not be written: {error}").into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_rates_are_ratios() {
let rate = |fps| Encoding::new([16, 16], fps).rate();
assert_eq!(rate(60.0), [60, 1]);
assert_eq!(rate(30_000.0 / 1001.0), [30_000, 1001]);
assert_eq!(rate(12.5), [12_500, 1000]);
}
}