use std::{sync::Arc, time::Duration};
use crate::{Error, Event, Image, Result, Texture, runtime::NativeDecodedFrame};
use super::VideoPixelFormat;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VideoColorMatrix {
Unspecified,
Bt601,
Bt709,
Bt2020,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VideoColorRange {
Unspecified,
Limited,
Full,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VideoColorInfo {
matrix: VideoColorMatrix,
range: VideoColorRange,
}
impl VideoColorInfo {
pub const fn new(matrix: VideoColorMatrix, range: VideoColorRange) -> Self {
Self { matrix, range }
}
pub const fn unspecified() -> Self {
Self::new(VideoColorMatrix::Unspecified, VideoColorRange::Unspecified)
}
pub const fn matrix(self) -> VideoColorMatrix {
self.matrix
}
pub const fn range(self) -> VideoColorRange {
self.range
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VideoFrameTiming {
presentation_timestamp: Duration,
duration: Option<Duration>,
}
impl VideoFrameTiming {
pub fn new(presentation_timestamp: Duration, duration: Option<Duration>) -> Result<Self> {
if duration == Some(Duration::ZERO) {
return Err(Error::invalid_argument(
"video frame duration must be non-zero when specified",
));
}
Ok(Self {
presentation_timestamp,
duration,
})
}
pub fn from_microseconds(
presentation_timestamp_microseconds: u64,
duration_microseconds: u64,
) -> Self {
Self {
presentation_timestamp: Duration::from_micros(presentation_timestamp_microseconds),
duration: (duration_microseconds != 0)
.then_some(Duration::from_micros(duration_microseconds)),
}
}
pub const fn presentation_timestamp(self) -> Duration {
self.presentation_timestamp
}
pub const fn duration(self) -> Option<Duration> {
self.duration
}
}
#[derive(Clone)]
pub struct VideoFrame {
backing: VideoFrameBacking,
timing: VideoFrameTiming,
color: VideoColorInfo,
}
impl VideoFrame {
pub(crate) const fn native_backing(&self) -> Option<&NativeDecodedFrame> {
match &self.backing {
VideoFrameBacking::Native(frame) => Some(frame),
_ => None,
}
}
pub(crate) fn from_native(
frame: NativeDecodedFrame,
timing: VideoFrameTiming,
color: VideoColorInfo,
) -> Self {
Self {
backing: VideoFrameBacking::Native(frame),
timing,
color,
}
}
pub(crate) fn from_texture(
texture: Texture,
timing: VideoFrameTiming,
color: VideoColorInfo,
ready: Option<Event>,
) -> Self {
Self {
backing: VideoFrameBacking::Texture { texture, ready },
timing,
color,
}
}
pub fn from_image(image: Image, timing: VideoFrameTiming, color: VideoColorInfo) -> Result<Self> {
if image.batch_size() != 1 {
return Err(Error::invalid_argument(format!(
"video frame requires one image, but the batch extent is {}",
image.batch_size()
)));
}
if image.width() == 0 || image.height() == 0 {
return Err(Error::invalid_argument(
"video frame width and height must be non-zero",
));
}
Ok(Self {
backing: VideoFrameBacking::PackedImage(image),
timing,
color,
})
}
pub fn from_yuv420p(
bytes: Vec<u8>,
width: usize,
height: usize,
timing: VideoFrameTiming,
color: VideoColorInfo,
) -> Result<Self> {
if width == 0 || height == 0 || !width.is_multiple_of(2) || !height.is_multiple_of(2) {
return Err(Error::invalid_argument(
"planar YUV420 video frames require non-zero even dimensions",
));
}
let expected = width
.checked_mul(height)
.and_then(|luma| luma.checked_mul(3))
.and_then(|samples| samples.checked_div(2))
.ok_or_else(|| Error::out_of_range("planar YUV420 frame size overflows usize"))?;
if bytes.len() != expected {
return Err(Error::invalid_argument(format!(
"planar YUV420 frame requires {expected} bytes, but received {}",
bytes.len()
)));
}
Ok(Self {
backing: VideoFrameBacking::PlanarYuv420 {
bytes: Arc::new(bytes),
width,
height,
},
timing,
color,
})
}
pub fn width(&self) -> usize {
self.backing.width()
}
pub fn height(&self) -> usize {
self.backing.height()
}
pub const fn timing(&self) -> VideoFrameTiming {
self.timing
}
pub const fn color_info(&self) -> VideoColorInfo {
self.color
}
pub const fn as_image(&self) -> Option<&Image> {
match &self.backing {
VideoFrameBacking::PackedImage(image) => Some(image),
VideoFrameBacking::Texture { .. }
| VideoFrameBacking::Native(_)
| VideoFrameBacking::PlanarYuv420 { .. } => None,
}
}
pub const fn as_texture(&self) -> Option<&Texture> {
match &self.backing {
VideoFrameBacking::Texture { texture, .. } => Some(texture),
VideoFrameBacking::PackedImage(_)
| VideoFrameBacking::Native(_)
| VideoFrameBacking::PlanarYuv420 { .. } => None,
}
}
pub fn as_yuv420p(&self) -> Option<&[u8]> {
match &self.backing {
VideoFrameBacking::PlanarYuv420 { bytes, .. } => Some(bytes.as_slice()),
VideoFrameBacking::PackedImage(_)
| VideoFrameBacking::Texture { .. }
| VideoFrameBacking::Native(_) => None,
}
}
pub const fn native_format(&self) -> Option<VideoPixelFormat> {
match &self.backing {
VideoFrameBacking::Native(frame) => Some(frame.format()),
_ => None,
}
}
pub const fn ready_event(&self) -> Option<&Event> {
match &self.backing {
VideoFrameBacking::Native(frame) => Some(frame.ready()),
VideoFrameBacking::Texture { ready, .. } => ready.as_ref(),
_ => None,
}
}
pub fn mark_consumed(&self, event: &Event) -> Result<()> {
match &self.backing {
VideoFrameBacking::Native(frame) => frame.mark_consumed(event),
VideoFrameBacking::Texture { texture, .. } => texture.mark_consumed(event),
_ => Err(Error::failed_precondition(
"consumer completion applies only to native decoded or render-target video frames",
)),
}
}
pub fn into_image(self) -> Option<Image> {
match self.backing {
VideoFrameBacking::PackedImage(image) => Some(image),
VideoFrameBacking::Texture { .. }
| VideoFrameBacking::Native(_)
| VideoFrameBacking::PlanarYuv420 { .. } => None,
}
}
pub fn into_texture(self) -> Option<Texture> {
match self.backing {
VideoFrameBacking::Texture { texture, .. } => Some(texture),
VideoFrameBacking::PackedImage(_)
| VideoFrameBacking::Native(_)
| VideoFrameBacking::PlanarYuv420 { .. } => None,
}
}
}
#[derive(Clone)]
enum VideoFrameBacking {
PackedImage(Image),
Texture {
texture: Texture,
ready: Option<Event>,
},
Native(NativeDecodedFrame),
PlanarYuv420 {
bytes: Arc<Vec<u8>>,
width: usize,
height: usize,
},
}
impl VideoFrameBacking {
fn width(&self) -> usize {
match self {
Self::PackedImage(image) => image.width(),
Self::Texture { texture, .. } => texture.width(),
Self::Native(frame) => frame.extent().width as usize,
Self::PlanarYuv420 { width, .. } => *width,
}
}
fn height(&self) -> usize {
match self {
Self::PackedImage(image) => image.height(),
Self::Texture { texture, .. } => texture.height(),
Self::Native(frame) => frame.extent().height as usize,
Self::PlanarYuv420 { height, .. } => *height,
}
}
}