#![forbid(unsafe_code)]
use oximedia_core::{OxiError, OxiResult};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompositionTimeOffset {
pub dts: i64,
pub pts: i64,
}
impl CompositionTimeOffset {
#[must_use]
pub const fn new(dts: i64, pts: i64) -> Self {
Self { dts, pts }
}
#[must_use]
pub const fn offset(&self) -> i64 {
self.pts - self.dts
}
#[must_use]
pub const fn is_zero(&self) -> bool {
self.pts == self.dts
}
}
#[derive(Debug, Clone)]
pub struct CompositionTimeTable {
offsets: Vec<CompositionTimeOffset>,
sample_to_offset: HashMap<usize, usize>,
}
impl CompositionTimeTable {
#[must_use]
pub fn new() -> Self {
Self {
offsets: Vec::new(),
sample_to_offset: HashMap::new(),
}
}
pub fn add_offset(&mut self, sample_index: usize, offset: CompositionTimeOffset) {
let offset_index = self.offsets.len();
self.offsets.push(offset);
self.sample_to_offset.insert(sample_index, offset_index);
}
#[must_use]
pub fn get_offset(&self, sample_index: usize) -> Option<&CompositionTimeOffset> {
self.sample_to_offset
.get(&sample_index)
.and_then(|&offset_index| self.offsets.get(offset_index))
}
#[must_use]
pub fn offsets(&self) -> &[CompositionTimeOffset] {
&self.offsets
}
#[must_use]
pub fn all_zero(&self) -> bool {
self.offsets.iter().all(CompositionTimeOffset::is_zero)
}
pub fn clear(&mut self) {
self.offsets.clear();
self.sample_to_offset.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.offsets.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
}
impl Default for CompositionTimeTable {
fn default() -> Self {
Self::new()
}
}
pub struct CompositionTimeBuilder {
table: CompositionTimeTable,
current_sample: usize,
}
impl CompositionTimeBuilder {
#[must_use]
pub fn new() -> Self {
Self {
table: CompositionTimeTable::new(),
current_sample: 0,
}
}
pub fn add_frame(&mut self, dts: i64, pts: i64) -> &mut Self {
let offset = CompositionTimeOffset::new(dts, pts);
self.table.add_offset(self.current_sample, offset);
self.current_sample += 1;
self
}
pub fn add_frame_no_offset(&mut self, timestamp: i64) -> &mut Self {
self.add_frame(timestamp, timestamp)
}
#[must_use]
pub fn build(self) -> CompositionTimeTable {
self.table
}
}
impl Default for CompositionTimeBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct CompositionTimeUtils;
impl CompositionTimeUtils {
#[must_use]
pub fn max_offset(table: &CompositionTimeTable) -> Option<i64> {
table
.offsets()
.iter()
.map(CompositionTimeOffset::offset)
.max()
}
#[must_use]
pub fn min_offset(table: &CompositionTimeTable) -> Option<i64> {
table
.offsets()
.iter()
.map(CompositionTimeOffset::offset)
.min()
}
#[must_use]
pub fn needs_ctts(table: &CompositionTimeTable) -> bool {
!table.all_zero()
}
pub fn validate_monotonic(table: &CompositionTimeTable) -> OxiResult<()> {
let mut last_dts = None;
for offset in table.offsets() {
if let Some(prev_dts) = last_dts {
if offset.dts < prev_dts {
return Err(OxiError::InvalidData(
"Decode timestamps are not monotonically increasing".into(),
));
}
}
last_dts = Some(offset.dts);
}
Ok(())
}
pub fn from_arrays(dts: &[i64], pts: &[i64]) -> OxiResult<CompositionTimeTable> {
if dts.len() != pts.len() {
return Err(OxiError::InvalidData(
"DTS and PTS arrays must have the same length".into(),
));
}
let mut table = CompositionTimeTable::new();
for (i, (&dts_val, &pts_val)) in dts.iter().zip(pts.iter()).enumerate() {
table.add_offset(i, CompositionTimeOffset::new(dts_val, pts_val));
}
Ok(table)
}
#[must_use]
pub fn to_arrays(table: &CompositionTimeTable) -> (Vec<i64>, Vec<i64>) {
let dts: Vec<i64> = table.offsets().iter().map(|o| o.dts).collect();
let pts: Vec<i64> = table.offsets().iter().map(|o| o.pts).collect();
(dts, pts)
}
}
pub struct FrameReorderer {
reorder_buffer: Vec<(usize, i64, i64)>, max_reorder: usize,
}
impl FrameReorderer {
#[must_use]
pub const fn new(max_reorder: usize) -> Self {
Self {
reorder_buffer: Vec::new(),
max_reorder,
}
}
pub fn add_frame(&mut self, index: usize, dts: i64, pts: i64) {
self.reorder_buffer.push((index, dts, pts));
}
pub fn get_next_frame(&mut self) -> Option<(usize, i64, i64)> {
if self.reorder_buffer.len() < self.max_reorder {
return None;
}
self.reorder_buffer.sort_by_key(|(_, _, pts)| *pts);
Some(self.reorder_buffer.remove(0))
}
pub fn flush(&mut self) -> Vec<(usize, i64, i64)> {
self.reorder_buffer.sort_by_key(|(_, _, pts)| *pts);
std::mem::take(&mut self.reorder_buffer)
}
#[must_use]
pub fn is_full(&self) -> bool {
self.reorder_buffer.len() >= self.max_reorder
}
#[must_use]
pub fn buffer_size(&self) -> usize {
self.reorder_buffer.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_composition_time_offset() {
let offset = CompositionTimeOffset::new(1000, 1500);
assert_eq!(offset.dts, 1000);
assert_eq!(offset.pts, 1500);
assert_eq!(offset.offset(), 500);
assert!(!offset.is_zero());
let zero = CompositionTimeOffset::new(1000, 1000);
assert!(zero.is_zero());
}
#[test]
fn test_composition_time_table() {
let mut table = CompositionTimeTable::new();
table.add_offset(0, CompositionTimeOffset::new(0, 0));
table.add_offset(1, CompositionTimeOffset::new(1000, 2000));
assert_eq!(table.len(), 2);
assert!(!table.is_empty());
assert!(!table.all_zero());
let offset = table.get_offset(1).expect("operation should succeed");
assert_eq!(offset.dts, 1000);
assert_eq!(offset.pts, 2000);
}
#[test]
fn test_composition_time_builder() {
let mut builder = CompositionTimeBuilder::new();
builder.add_frame(0, 0);
builder.add_frame(1000, 3000);
builder.add_frame(2000, 1000);
builder.add_frame(3000, 2000);
let table = builder.build();
assert_eq!(table.len(), 4);
}
#[test]
fn test_composition_time_utils() {
let mut table = CompositionTimeTable::new();
table.add_offset(0, CompositionTimeOffset::new(0, 0));
table.add_offset(1, CompositionTimeOffset::new(1000, 3000));
table.add_offset(2, CompositionTimeOffset::new(2000, 1000));
assert_eq!(CompositionTimeUtils::max_offset(&table), Some(2000));
assert_eq!(CompositionTimeUtils::min_offset(&table), Some(-1000));
assert!(CompositionTimeUtils::needs_ctts(&table));
}
#[test]
fn test_validate_monotonic() {
let mut table = CompositionTimeTable::new();
table.add_offset(0, CompositionTimeOffset::new(0, 0));
table.add_offset(1, CompositionTimeOffset::new(1000, 3000));
table.add_offset(2, CompositionTimeOffset::new(2000, 1000));
assert!(CompositionTimeUtils::validate_monotonic(&table).is_ok());
let mut bad_table = CompositionTimeTable::new();
bad_table.add_offset(0, CompositionTimeOffset::new(1000, 1000));
bad_table.add_offset(1, CompositionTimeOffset::new(500, 500));
assert!(CompositionTimeUtils::validate_monotonic(&bad_table).is_err());
}
#[test]
fn test_from_to_arrays() {
let dts = vec![0, 1000, 2000];
let pts = vec![0, 3000, 1000];
let table =
CompositionTimeUtils::from_arrays(&dts, &pts).expect("operation should succeed");
assert_eq!(table.len(), 3);
let (dts_out, pts_out) = CompositionTimeUtils::to_arrays(&table);
assert_eq!(dts_out, dts);
assert_eq!(pts_out, pts);
}
#[test]
fn test_frame_reorderer() {
let mut reorderer = FrameReorderer::new(3);
reorderer.add_frame(0, 0, 0);
reorderer.add_frame(1, 1000, 3000);
reorderer.add_frame(2, 2000, 1000);
let next = reorderer.get_next_frame();
assert!(next.is_some());
let (index, _, pts) = next.expect("operation should succeed");
assert_eq!(index, 0);
assert_eq!(pts, 0);
let flushed = reorderer.flush();
assert_eq!(flushed.len(), 2);
assert_eq!(flushed[0].0, 2); assert_eq!(flushed[1].0, 1); }
}