use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::bitstream::{AvcConfig, SequenceParameterSet};
use crate::color::{ColorSpace, Matrix, Primaries, Range, Transfer};
use crate::error::{Error, Result};
use crate::identify::Container;
use crate::media::{MediaInfo, Packet, Rational, Rotation, TrackInfo, TrackKind};
use super::{color_from, demux_error, scaled, video_track};
struct Cursor {
track_id: u32,
next_sample: u32,
pending: Option<Packet>,
keyframes: Vec<(u32, Duration)>,
scanned_to: u32,
}
pub(super) struct Mp4Demuxer {
path: PathBuf,
reader: ::mp4::Mp4Reader<BufReader<File>>,
info: MediaInfo,
cursors: Vec<Cursor>,
}
impl Mp4Demuxer {
pub(super) fn open(path: &Path, container: Container) -> Result<Self> {
let file = File::open(path).map_err(|error| Error::io(path, error))?;
let size = file
.metadata()
.map_err(|error| Error::io(path, error))?
.len();
let reader = ::mp4::Mp4Reader::read_header(BufReader::new(file), size)
.map_err(|error| demux_error(&path.to_path_buf(), error))?;
let mut tracks = Vec::new();
let mut ids: Vec<u32> = reader.tracks().keys().copied().collect();
ids.sort_unstable();
for id in ids {
let track = &reader.tracks()[&id];
let kind = match track.track_type() {
Ok(::mp4::TrackType::Video) => TrackKind::Video,
Ok(::mp4::TrackType::Audio) => TrackKind::Audio,
Ok(::mp4::TrackType::Subtitle) => TrackKind::Subtitle,
Err(_) => TrackKind::Other,
};
let duration = track.duration();
let codec_id = track
.box_type()
.map(|fourcc| fourcc.to_string())
.unwrap_or_default();
if kind != TrackKind::Video {
tracks.push(TrackInfo {
id,
kind,
encoding: None,
codec_id,
width: 0,
height: 0,
frame_rate: None,
bit_depth: 0,
color: ColorSpace::default(),
rotation: Rotation::None,
duration,
extra_data: Vec::new(),
});
continue;
}
let width = u32::from(track.width());
let height = u32::from(track.height());
let frame_rate = rational_from(track.frame_rate());
let extra_data = parameter_sets(track);
let (color, bit_depth) = stated_color(&extra_data, width, height);
tracks.push(video_track(
id,
codec_id,
width,
height,
frame_rate,
bit_depth,
color,
Rotation::None,
duration,
extra_data,
));
}
let duration = reader.duration();
let cursors = tracks
.iter()
.map(|track| Cursor {
track_id: track.id,
next_sample: 1,
pending: None,
keyframes: Vec::new(),
scanned_to: 0,
})
.collect();
Ok(Mp4Demuxer {
path: path.to_path_buf(),
reader,
info: MediaInfo {
container,
tracks,
duration,
seekable: true,
},
cursors,
})
}
}
fn parameter_sets(track: &::mp4::Mp4Track) -> Vec<u8> {
let (Ok(sps), Ok(pps)) = (
track.sequence_parameter_set(),
track.picture_parameter_set(),
) else {
return Vec::new();
};
AvcConfig::new(sps.to_vec(), pps.to_vec()).to_record()
}
fn stated_color(extra_data: &[u8], width: u32, height: u32) -> (ColorSpace, u32) {
let guess = ColorSpace::guess_for(width, height);
let Some(sps) = AvcConfig::parse(extra_data)
.ok()
.and_then(|avc| avc.sequence_parameter_sets.into_iter().next())
.and_then(|sps| SequenceParameterSet::parse(&sps).ok())
else {
return (guess, 8);
};
let mut color = color_from(
sps.matrix.and_then(Matrix::from_h273),
sps.full_range
.map(|full| if full { Range::Full } else { Range::Limited }),
width,
height,
);
if let Some(primaries) = sps.primaries.and_then(Primaries::from_h273) {
color.primaries = primaries;
}
if let Some(transfer) = sps.transfer.and_then(Transfer::from_h273) {
color.transfer = transfer;
}
(color, sps.bit_depth)
}
fn rational_from(rate: f64) -> Option<Rational> {
if !rate.is_finite() || rate <= 0.0 {
return None;
}
for numerator in [24_000_u32, 30_000, 60_000, 48_000, 120_000] {
let exact = f64::from(numerator) / 1001.0;
if (rate - exact).abs() < 0.01 {
return Some(Rational::new(numerator, 1001));
}
}
if (rate - rate.round()).abs() < 0.001 {
return Some(Rational::new(rate.round() as u32, 1));
}
Some(Rational::new((rate * 1000.0).round() as u32, 1000))
}
impl Mp4Demuxer {
fn read(&mut self, track_id: u32, sample_number: u32) -> Result<Option<Packet>> {
let Some(track) = self.reader.tracks().get(&track_id) else {
return Ok(None);
};
if sample_number == 0 || sample_number > track.sample_count() {
return Ok(None);
}
let timescale = u64::from(track.timescale());
let Some(sample) = self
.reader
.read_sample(track_id, sample_number)
.map_err(|error| demux_error(&self.path, error))?
else {
return Ok(None);
};
let dts = scaled(sample.start_time, timescale);
let pts = if sample.rendering_offset >= 0 {
dts + scaled(sample.rendering_offset as u64, timescale)
} else {
dts.saturating_sub(scaled(sample.rendering_offset.unsigned_abs() as u64, timescale))
};
Ok(Some(Packet {
track_id,
data: sample.bytes.to_vec(),
pts,
dts,
is_keyframe: sample.is_sync,
}))
}
fn fill(&mut self, index: usize) -> Result<()> {
if self.cursors[index].pending.is_some() {
return Ok(());
}
let track_id = self.cursors[index].track_id;
let sample_number = self.cursors[index].next_sample;
if let Some(packet) = self.read(track_id, sample_number)? {
let cursor = &mut self.cursors[index];
cursor.next_sample += 1;
if packet.is_keyframe {
cursor.keyframes.push((sample_number, packet.pts));
}
cursor.scanned_to = cursor.scanned_to.max(sample_number);
cursor.pending = Some(packet);
}
Ok(())
}
fn keyframe_before(&mut self, index: usize, position: Duration) -> Result<u32> {
let track_id = self.cursors[index].track_id;
let count = self
.reader
.tracks()
.get(&track_id)
.map(|track| track.sample_count())
.unwrap_or(0);
let mut sample_number = self.cursors[index].scanned_to + 1;
while sample_number <= count {
let Some(packet) = self.read(track_id, sample_number)? else {
break;
};
let cursor = &mut self.cursors[index];
cursor.scanned_to = sample_number;
if packet.is_keyframe {
cursor.keyframes.push((sample_number, packet.pts));
}
if packet.pts > position {
break;
}
sample_number += 1;
}
Ok(self.cursors[index]
.keyframes
.iter()
.filter(|(_, pts)| *pts <= position)
.map(|(sample, _)| *sample)
.next_back()
.unwrap_or(1))
}
}
impl super::Demuxer for Mp4Demuxer {
fn info(&self) -> &MediaInfo {
&self.info
}
fn next_packet(&mut self) -> Result<Option<Packet>> {
for index in 0..self.cursors.len() {
self.fill(index)?;
}
let earliest = self
.cursors
.iter()
.enumerate()
.filter_map(|(index, cursor)| cursor.pending.as_ref().map(|packet| (index, packet.dts)))
.min_by_key(|(_, dts)| *dts)
.map(|(index, _)| index);
Ok(earliest.and_then(|index| self.cursors[index].pending.take()))
}
fn seek(&mut self, position: Duration) -> Result<()> {
for index in 0..self.cursors.len() {
let landing = self.keyframe_before(index, position)?;
self.cursors[index].next_sample = landing;
self.cursors[index].pending = None;
}
Ok(())
}
}