#![allow(unsafe_code)]
use mp4_track::{Mp4Reader, TrackId};
use std::alloc::{GlobalAlloc, Layout, System};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
struct CountingAllocator;
static CURRENT: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
fn add(size: usize) {
let current = CURRENT
.fetch_add(size, Ordering::Relaxed)
.saturating_add(size);
PEAK.fetch_max(current, Ordering::Relaxed);
}
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let pointer = unsafe { System.alloc(layout) };
if !pointer.is_null() {
add(layout.size());
}
pointer
}
unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
CURRENT.fetch_sub(layout.size(), Ordering::Relaxed);
unsafe { System.dealloc(pointer, layout) };
}
unsafe fn realloc(&self, pointer: *mut u8, old: Layout, new_size: usize) -> *mut u8 {
let resized = unsafe { System.realloc(pointer, old, new_size) };
if !resized.is_null() {
if new_size >= old.size() {
add(new_size - old.size());
} else {
CURRENT.fetch_sub(old.size() - new_size, Ordering::Relaxed);
}
}
resized
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
struct CountedReader {
file: File,
bytes_read: usize,
}
impl Read for CountedReader {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
let count = self.file.read(buffer)?;
self.bytes_read = self.bytes_read.saturating_add(count);
Ok(count)
}
}
impl Seek for CountedReader {
fn seek(&mut self, position: SeekFrom) -> std::io::Result<u64> {
self.file.seek(position)
}
}
#[test]
fn open_many_samples_stays_streaming_and_bounded() {
let path =
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/v09_perf_many_samples.mp4");
let file = File::open(path).expect("fixture");
let counted = CountedReader {
file,
bytes_read: 0,
};
let baseline = CURRENT.load(Ordering::Relaxed);
PEAK.store(baseline, Ordering::Relaxed);
let started = Instant::now();
let reader = Mp4Reader::open(counted).expect("large fixture must parse");
let open_elapsed = started.elapsed();
let peak = PEAK.load(Ordering::Relaxed).saturating_sub(baseline);
let sample_started = Instant::now();
assert_eq!(reader.samples(TrackId(1)).expect("samples").count(), 72_000);
let sample_elapsed = sample_started.elapsed();
let counted = reader.into_inner();
eprintln!(
"open={open_elapsed:?} sample_count={sample_elapsed:?} bytes_read={} peak_bytes={peak}",
counted.bytes_read
);
assert!(
counted.bytes_read <= 289_996 + 4_096,
"open read {} bytes",
counted.bytes_read
);
assert!(
open_elapsed < Duration::from_millis(200),
"open took {open_elapsed:?}"
);
assert!(
sample_elapsed < Duration::from_millis(10),
"sample count took {sample_elapsed:?}"
);
assert!(
peak < 16 * 1024 * 1024,
"open allocated {peak} bytes at peak"
);
}