use crate::error::OxiResult;
use crate::types::{CodecId, PixelFormat, SampleFormat, Timestamp};
#[derive(Debug)]
pub struct VideoFrame {
pub format: PixelFormat,
pub width: u32,
pub height: u32,
pub timestamp: Timestamp,
pub planes: Vec<Vec<u8>>,
pub strides: Vec<usize>,
pub is_keyframe: bool,
}
impl VideoFrame {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
format: PixelFormat,
width: u32,
height: u32,
timestamp: Timestamp,
planes: Vec<Vec<u8>>,
strides: Vec<usize>,
is_keyframe: bool,
) -> Self {
Self {
format,
width,
height,
timestamp,
planes,
strides,
is_keyframe,
}
}
}
#[derive(Debug)]
pub struct AudioFrame {
pub format: SampleFormat,
pub sample_rate: u32,
pub channels: u16,
pub samples: usize,
pub timestamp: Timestamp,
pub data: Vec<Vec<u8>>,
}
impl AudioFrame {
#[must_use]
pub fn new(
format: SampleFormat,
sample_rate: u32,
channels: u16,
samples: usize,
timestamp: Timestamp,
data: Vec<Vec<u8>>,
) -> Self {
Self {
format,
sample_rate,
channels,
samples,
timestamp,
data,
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn duration_seconds(&self) -> f64 {
self.samples as f64 / f64::from(self.sample_rate)
}
}
pub trait VideoDecoder {
fn codec_id(&self) -> CodecId;
fn output_format(&self) -> PixelFormat;
fn output_dimensions(&self) -> (u32, u32);
fn send_packet(&mut self, data: &[u8]) -> OxiResult<()>;
fn receive_frame(&mut self) -> OxiResult<Option<VideoFrame>>;
fn flush(&mut self) -> OxiResult<()>;
fn reset(&mut self) -> OxiResult<()>;
}
pub trait AudioDecoder {
fn codec_id(&self) -> CodecId;
fn output_format(&self) -> SampleFormat;
fn sample_rate(&self) -> u32;
fn channels(&self) -> u16;
fn send_packet(&mut self, data: &[u8]) -> OxiResult<()>;
fn receive_frame(&mut self) -> OxiResult<Option<AudioFrame>>;
fn flush(&mut self) -> OxiResult<()>;
fn reset(&mut self) -> OxiResult<()>;
}
#[derive(Debug, Clone)]
pub struct SubtitleFrame {
pub start: Timestamp,
pub end: Timestamp,
pub text: String,
pub layer: u32,
pub settings: Option<SubtitleSettings>,
}
impl SubtitleFrame {
#[must_use]
pub fn new(start: Timestamp, end: Timestamp, text: impl Into<String>) -> Self {
Self {
start,
end,
text: text.into(),
layer: 0,
settings: None,
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn duration_seconds(&self) -> f64 {
let start_sec = self.start.to_seconds();
let end_sec = self.end.to_seconds();
end_sec - start_sec
}
#[must_use]
pub const fn with_layer(mut self, layer: u32) -> Self {
self.layer = layer;
self
}
#[must_use]
pub fn with_settings(mut self, settings: SubtitleSettings) -> Self {
self.settings = Some(settings);
self
}
}
#[derive(Debug, Clone, Default)]
pub struct SubtitleSettings {
pub align_h: Option<HorizontalAlign>,
pub align_v: Option<VerticalAlign>,
pub position: Option<(f32, f32)>,
pub size: Option<(f32, f32)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HorizontalAlign {
Left,
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerticalAlign {
Top,
Middle,
Bottom,
}
pub trait SubtitleDecoder {
fn codec_id(&self) -> CodecId;
fn send_packet(&mut self, data: &[u8]) -> OxiResult<()>;
fn receive_frame(&mut self) -> OxiResult<Option<SubtitleFrame>>;
fn flush(&mut self) -> OxiResult<()>;
fn reset(&mut self) -> OxiResult<()>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Rational;
#[test]
fn test_video_frame_new() {
let timestamp = Timestamp::new(0, Rational::new(1, 1000));
let frame = VideoFrame::new(
PixelFormat::Yuv420p,
1920,
1080,
timestamp,
vec![
vec![0u8; 1920 * 1080],
vec![0u8; 960 * 540],
vec![0u8; 960 * 540],
],
vec![1920, 960, 960],
true,
);
assert_eq!(frame.format, PixelFormat::Yuv420p);
assert_eq!(frame.width, 1920);
assert_eq!(frame.height, 1080);
assert!(frame.is_keyframe);
}
#[test]
fn test_audio_frame_new() {
let timestamp = Timestamp::new(0, Rational::new(1, 48000));
let frame = AudioFrame::new(
SampleFormat::F32,
48000,
2,
1024,
timestamp,
vec![vec![0u8; 1024 * 2 * 4]],
);
assert_eq!(frame.format, SampleFormat::F32);
assert_eq!(frame.sample_rate, 48000);
assert_eq!(frame.channels, 2);
assert_eq!(frame.samples, 1024);
}
#[test]
fn test_audio_frame_duration() {
let timestamp = Timestamp::new(0, Rational::new(1, 48000));
let frame = AudioFrame::new(
SampleFormat::F32,
48000,
2,
48000, timestamp,
vec![vec![0u8; 48000 * 2 * 4]],
);
assert!((frame.duration_seconds() - 1.0).abs() < f64::EPSILON);
}
}