#![allow(dead_code)]
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SampleEntryCodec {
Avc,
Hevc,
Av1,
Vp9,
Aac,
Opus,
Flac,
Unknown([u8; 4]),
}
impl SampleEntryCodec {
#[must_use]
pub fn from_fourcc(cc: [u8; 4]) -> Self {
match &cc {
b"avc1" | b"avc3" => Self::Avc,
b"hev1" | b"hvc1" => Self::Hevc,
b"av01" => Self::Av1,
b"vp09" => Self::Vp9,
b"mp4a" => Self::Aac,
b"Opus" => Self::Opus,
b"fLaC" => Self::Flac,
_ => Self::Unknown(cc),
}
}
#[must_use]
pub fn is_video(self) -> bool {
matches!(self, Self::Avc | Self::Hevc | Self::Av1 | Self::Vp9)
}
#[must_use]
pub fn is_audio(self) -> bool {
matches!(self, Self::Aac | Self::Opus | Self::Flac)
}
}
impl fmt::Display for SampleEntryCodec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = match self {
Self::Avc => "AVC/H.264",
Self::Hevc => "HEVC/H.265",
Self::Av1 => "AV1",
Self::Vp9 => "VP9",
Self::Aac => "AAC",
Self::Opus => "Opus",
Self::Flac => "FLAC",
Self::Unknown(_) => "Unknown",
};
f.write_str(label)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VideoSampleEntry {
pub codec: SampleEntryCodec,
pub width: u32,
pub height: u32,
pub bit_depth: u8,
pub config_data: Vec<u8>,
}
impl VideoSampleEntry {
#[must_use]
pub fn new(codec: SampleEntryCodec, width: u32, height: u32, bit_depth: u8) -> Self {
Self {
codec,
width,
height,
bit_depth,
config_data: Vec::new(),
}
}
#[must_use]
pub fn pixel_count(&self) -> u64 {
u64::from(self.width) * u64::from(self.height)
}
#[must_use]
pub fn with_config(mut self, data: Vec<u8>) -> Self {
self.config_data = data;
self
}
#[must_use]
pub fn has_config(&self) -> bool {
!self.config_data.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AudioSampleEntry {
pub codec: SampleEntryCodec,
pub sample_rate: u32,
pub channels: u16,
pub bits_per_sample: u16,
pub config_data: Vec<u8>,
}
impl AudioSampleEntry {
#[must_use]
pub fn new(
codec: SampleEntryCodec,
sample_rate: u32,
channels: u16,
bits_per_sample: u16,
) -> Self {
Self {
codec,
sample_rate,
channels,
bits_per_sample,
config_data: Vec::new(),
}
}
#[must_use]
pub fn with_config(mut self, data: Vec<u8>) -> Self {
self.config_data = data;
self
}
#[must_use]
pub fn has_config(&self) -> bool {
!self.config_data.is_empty()
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn pcm_bitrate_kbps(&self) -> f64 {
(u64::from(self.sample_rate) * u64::from(self.channels) * u64::from(self.bits_per_sample))
as f64
/ 1000.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SampleEntry {
Video(VideoSampleEntry),
Audio(AudioSampleEntry),
}
impl SampleEntry {
#[must_use]
pub fn codec(&self) -> SampleEntryCodec {
match self {
Self::Video(v) => v.codec,
Self::Audio(a) => a.codec,
}
}
#[must_use]
pub fn is_video(&self) -> bool {
matches!(self, Self::Video(_))
}
#[must_use]
pub fn is_audio(&self) -> bool {
matches!(self, Self::Audio(_))
}
}
#[must_use]
pub fn parse_sample_entry(fourcc: [u8; 4], data: &[u8]) -> Option<SampleEntry> {
let codec = SampleEntryCodec::from_fourcc(fourcc);
if codec.is_video() {
if data.len() < 28 {
return None;
}
let width = u16::from_be_bytes([data[24], data[25]]);
let height = u16::from_be_bytes([data[26], data[27]]);
let entry = VideoSampleEntry::new(codec, u32::from(width), u32::from(height), 8);
Some(SampleEntry::Video(entry))
} else if codec.is_audio() {
if data.len() < 24 {
return None;
}
let channels = u16::from_be_bytes([data[16], data[17]]);
let bps = u16::from_be_bytes([data[18], data[19]]);
let sr_fixed = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
let sample_rate = sr_fixed >> 16; let entry = AudioSampleEntry::new(codec, sample_rate, channels, bps);
Some(SampleEntry::Audio(entry))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_codec_from_fourcc_avc() {
assert_eq!(
SampleEntryCodec::from_fourcc(*b"avc1"),
SampleEntryCodec::Avc
);
}
#[test]
fn test_codec_from_fourcc_unknown() {
let cc = *b"xxxx";
assert_eq!(
SampleEntryCodec::from_fourcc(cc),
SampleEntryCodec::Unknown(cc)
);
}
#[test]
fn test_is_video() {
assert!(SampleEntryCodec::Avc.is_video());
assert!(SampleEntryCodec::Av1.is_video());
assert!(!SampleEntryCodec::Aac.is_video());
}
#[test]
fn test_is_audio() {
assert!(SampleEntryCodec::Aac.is_audio());
assert!(SampleEntryCodec::Opus.is_audio());
assert!(!SampleEntryCodec::Hevc.is_audio());
}
#[test]
fn test_video_pixel_count() {
let v = VideoSampleEntry::new(SampleEntryCodec::Av1, 1920, 1080, 10);
assert_eq!(v.pixel_count(), 1920 * 1080);
}
#[test]
fn test_video_with_config() {
let v = VideoSampleEntry::new(SampleEntryCodec::Avc, 640, 480, 8)
.with_config(vec![0x67, 0x42, 0x00, 0x1e]);
assert!(v.has_config());
assert_eq!(v.config_data.len(), 4);
}
#[test]
fn test_audio_pcm_bitrate() {
let a = AudioSampleEntry::new(SampleEntryCodec::Flac, 48000, 2, 16);
assert!((a.pcm_bitrate_kbps() - 1536.0).abs() < f64::EPSILON);
}
#[test]
fn test_audio_no_config() {
let a = AudioSampleEntry::new(SampleEntryCodec::Opus, 48000, 2, 16);
assert!(!a.has_config());
}
#[test]
fn test_sample_entry_codec() {
let se = SampleEntry::Video(VideoSampleEntry::new(
SampleEntryCodec::Hevc,
3840,
2160,
10,
));
assert_eq!(se.codec(), SampleEntryCodec::Hevc);
}
#[test]
fn test_sample_entry_variant() {
let v = SampleEntry::Video(VideoSampleEntry::new(SampleEntryCodec::Av1, 1280, 720, 8));
assert!(v.is_video());
assert!(!v.is_audio());
let a = SampleEntry::Audio(AudioSampleEntry::new(SampleEntryCodec::Aac, 44100, 2, 16));
assert!(a.is_audio());
}
#[test]
fn test_parse_video_entry() {
let mut data = vec![0u8; 30];
data[24] = 0x07;
data[25] = 0x80; data[26] = 0x04;
data[27] = 0x38; let result = parse_sample_entry(*b"avc1", &data).expect("operation should succeed");
assert!(result.is_video());
if let SampleEntry::Video(v) = result {
assert_eq!(v.width, 1920);
assert_eq!(v.height, 1080);
}
}
#[test]
fn test_parse_audio_entry() {
let mut data = vec![0u8; 24];
data[16] = 0x00;
data[17] = 0x02; data[18] = 0x00;
data[19] = 0x10; let sr_fixed = 48000u32 << 16;
data[20..24].copy_from_slice(&sr_fixed.to_be_bytes());
let result = parse_sample_entry(*b"mp4a", &data).expect("operation should succeed");
assert!(result.is_audio());
if let SampleEntry::Audio(a) = result {
assert_eq!(a.sample_rate, 48000);
assert_eq!(a.channels, 2);
}
}
#[test]
fn test_parse_too_short() {
assert!(parse_sample_entry(*b"avc1", &[0; 10]).is_none());
}
#[test]
fn test_parse_unknown_codec() {
assert!(parse_sample_entry(*b"xxxx", &[0; 30]).is_none());
}
#[test]
fn test_codec_display() {
assert_eq!(format!("{}", SampleEntryCodec::Av1), "AV1");
assert_eq!(format!("{}", SampleEntryCodec::Opus), "Opus");
}
#[test]
fn test_hevc_alternate_fourcc() {
assert_eq!(
SampleEntryCodec::from_fourcc(*b"hvc1"),
SampleEntryCodec::Hevc
);
}
}