use anyhow::{Context, Result};
use codec::frame::{ColorMetadata, ColorSpace, PixelFormat, StreamInfo};
use mp4::Mp4Reader;
use std::io::Cursor;
use crate::annexb::{NaluCodec, ParamSetTracker, length_prefixed_to_annexb_tracked};
use crate::mp4_sanitize::sanitize_isobmff_box_sizes;
use super::DemuxResult;
mod sample_entry;
mod streaming;
pub use streaming::Mp4StreamingDemuxer;
#[allow(unused_imports)] pub(super) use streaming::FragSample;
pub(crate) use streaming::demux_mp4_streaming_init;
pub(crate) use sample_entry::{has_av01_sample_entry, prores_sample_entry_fourcc};
#[allow(unused_imports)] pub(crate) use sample_entry::parse_avcc_param_sets;
pub(crate) use streaming::build_fragmented_sample_table;
use sample_entry::{extract_avc_config, extract_hevc_config, hevc_sample_entry_fourcc};
pub fn demux_mp4(data: &[u8]) -> Result<DemuxResult> {
let sanitized = sanitize_isobmff_box_sizes(data);
let data: &[u8] = &sanitized;
let size = data.len() as u64;
let cursor = Cursor::new(data);
let reader = Mp4Reader::read_header(cursor, size).context("reading MP4 header")?;
let video_track = reader
.tracks()
.values()
.find(|t| t.track_type().ok() == Some(mp4::TrackType::Video))
.context("no video track in MP4")?;
let track_id = video_track.track_id();
let codec_from_mp4 = format_codec(video_track);
let codec = if codec_from_mp4 == "unknown" && has_av01_sample_entry(data) {
"av1".to_string()
} else if codec_from_mp4 == "unknown" && hevc_sample_entry_fourcc(data).is_some() {
"h265".to_string()
} else if codec_from_mp4 == "unknown" && prores_sample_entry_fourcc(data).is_some() {
"prores".to_string()
} else {
codec_from_mp4
};
let width = video_track.width() as u32;
let height = video_track.height() as u32;
let sample_count = video_track.sample_count();
let duration = video_track.duration().as_secs_f64();
let frame_rate = mp4_frame_rate(video_track, duration);
let bitrate = video_track.bitrate() as u64;
let mp4_color = super::hdr::extract_mp4_visual_color_metadata(data);
let initial_color_metadata = ColorMetadata {
mastering_display: mp4_color.mastering_display,
content_light_level: mp4_color.content_light_level,
..Default::default()
};
let info = StreamInfo {
codec: codec.clone(),
width,
height,
frame_rate,
duration,
pixel_format: PixelFormat::Yuv420p,
color_space: ColorSpace::Bt709,
total_frames: sample_count as u64,
bitrate,
color_metadata: initial_color_metadata,
};
let cursor = Cursor::new(data);
let mut reader = Mp4Reader::read_header(cursor, size).context("re-reading MP4 for samples")?;
let mut samples = Vec::with_capacity(sample_count as usize);
let needs_annexb = matches!(codec.as_str(), "h264" | "h265");
let (sps_pps, length_size) = if needs_annexb {
if codec == "h264" {
match extract_avc_config(data) {
Some(cfg) => (cfg.parameter_sets, cfg.length_size),
None => (extract_sps_pps(&reader, track_id), 4u8),
}
} else {
match extract_hevc_config(data) {
Some(cfg) => (cfg.parameter_sets, cfg.length_size),
None => (Vec::new(), 4u8),
}
}
} else {
(Vec::new(), 4u8)
};
let mut avc_tracker = if needs_annexb {
Some(ParamSetTracker::new(if codec == "h264" {
NaluCodec::Avc
} else {
NaluCodec::Hevc
}))
} else {
None
};
for sample_idx in 1..=sample_count {
let sample = reader
.read_sample(track_id, sample_idx)
.context("reading sample")?;
if let Some(sample) = sample {
let sample_data = sample.bytes.to_vec();
if let Some(tracker) = avc_tracker.as_mut() {
let annexb =
length_prefixed_to_annexb_tracked(&sample_data, length_size, tracker, &sps_pps);
samples.push(annexb);
} else {
samples.push(sample_data);
}
}
}
let detected_pf = codec::pixel_format::detect(&codec, &samples);
let info = StreamInfo {
pixel_format: detected_pf,
..info
};
let audio = super::audio::extract_mp4_audio(data);
Ok(DemuxResult {
codec,
info,
samples,
audio,
})
}
fn format_codec(track: &mp4::Mp4Track) -> String {
match track.media_type() {
Ok(mp4::MediaType::H264) => "h264".into(),
Ok(mp4::MediaType::H265) => "h265".into(),
Ok(mp4::MediaType::VP9) => "vp9".into(),
_ => "unknown".into(),
}
}
fn mp4_frame_rate(track: &mp4::Mp4Track, duration: f64) -> f64 {
let stts = &track.trak.mdia.minf.stbl.stts;
if stts.entries.len() == 1 && stts.entries[0].sample_delta > 0 {
return track.timescale() as f64 / stts.entries[0].sample_delta as f64;
}
if duration > 0.0 {
track.sample_count() as f64 / duration
} else {
30.0
}
}
fn extract_sps_pps(reader: &Mp4Reader<Cursor<&[u8]>>, track_id: u32) -> Vec<Vec<u8>> {
let mut nalus = Vec::new();
if let Some(track) = reader.tracks().get(&track_id)
&& let Some(ref avc1) = track.trak.mdia.minf.stbl.stsd.avc1
{
for sps in &avc1.avcc.sequence_parameter_sets {
nalus.push(sps.bytes.to_vec());
}
for pps in &avc1.avcc.picture_parameter_sets {
nalus.push(pps.bytes.to_vec());
}
}
nalus
}