frame_source/
mp4_source.rs1use std::path::Path;
5
6use crate::{
7 Result,
8 h264_source::{H264Preparser, H264Source, SeekRead, SeekableH264Source},
9};
10use mp4::MediaType;
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct Mp4NalLocation {
14 track_id: u32,
15 sample_id: u32,
16}
17
18#[derive(thiserror::Error, Debug)]
19pub enum Mp4SourceError {
20 #[error("sample is empty")]
21 SampleEmpty,
22 #[error("sample in track disappeared")]
23 SampleDisappeared,
24 #[error("only MP4 files with a single H264 video track are supported")]
25 SingleH264TrackOnly,
26 #[error("No H264 video track found in MP4 file.")]
27 NoH264Track,
28 #[error("sample buffer is too short for NAL unit header")]
29 SampleBufferTooShort,
30 #[error("AVCC buffer length: {sz}+4 but buffer {cur_len}")]
31 LengthMismatch { sz: usize, cur_len: usize },
32}
33
34pub struct Mp4Source {
35 mp4_reader: mp4::Mp4Reader<Box<dyn SeekRead + Send>>,
36 nal_locations: Vec<Mp4NalLocation>,
37 first_sps: Vec<u8>,
38 first_pps: Vec<u8>,
39}
40
41impl SeekableH264Source for Mp4Source {
42 type NalLocation = Mp4NalLocation;
43 fn nal_boundaries(&mut self) -> &[Self::NalLocation] {
44 &self.nal_locations
45 }
46 fn read_nal_units_at_location(&mut self, location: &Self::NalLocation) -> Result<Vec<Vec<u8>>> {
47 match self
48 .mp4_reader
49 .read_sample(location.track_id, location.sample_id)?
50 {
51 Some(sample) => {
52 if !sample.bytes.is_empty() {
53 let sample_nal_units = avcc_to_nalu_ebsp(sample.bytes.as_ref())?;
54 Ok(sample_nal_units.iter().map(|x| x.to_vec()).collect())
55 } else {
56 Err(Mp4SourceError::SampleEmpty.into())
57 }
58 }
59 _ => Err(Mp4SourceError::SampleDisappeared.into()),
60 }
61 }
62 fn first_sps(&self) -> Option<Vec<u8>> {
63 Some(self.first_sps.clone())
64 }
65 fn first_pps(&self) -> Option<Vec<u8>> {
66 Some(self.first_pps.clone())
67 }
68}
69
70pub fn from_reader_with_timestamp_source(
72 mut mp4_reader: mp4::Mp4Reader<Box<dyn SeekRead + Send>>,
73 do_decode_h264: bool,
74 timestamp_source: crate::TimestampSource,
75 srt_file_path: Option<std::path::PathBuf>,
76 show_progress: bool,
77 preparser: Option<Box<dyn H264Preparser>>,
78) -> Result<H264Source<Mp4Source>> {
79 let mut video_track = None;
80 for (track_id, track) in mp4_reader.tracks().iter() {
81 if let Ok(MediaType::H264) = track.media_type() {
83 if video_track.is_some() {
84 return Err(Mp4SourceError::SingleH264TrackOnly.into());
85 }
86 video_track = Some((track_id, track));
87 }
88 }
89
90 let (track_id, track) = if let Some(vt) = video_track {
91 vt
92 } else {
93 return Err(Mp4SourceError::NoH264Track.into());
94 };
95
96 let track_id = *track_id;
97
98 let mut nal_locations = Vec::new();
103 let mut mp4_pts = Vec::new();
104 let mut sample_timing = Vec::new();
105 let data_from_mp4_track = crate::h264_source::FromMp4Track {
106 sequence_parameter_set: track.sequence_parameter_set()?.to_vec(),
107 picture_parameter_set: track.picture_parameter_set()?.to_vec(),
108 };
109
110 let comp_offsets = composition_offsets(track);
115 let media_timescale = track.timescale();
118
119 let num_samples = mp4_reader.sample_count(track_id)?;
120
121 for sample_id in 1..=num_samples {
123 let (decode_time, duration) = mp4_reader.sample_time_duration(track_id, sample_id)?;
129 let offset = comp_offsets
130 .get((sample_id - 1) as usize)
131 .copied()
132 .unwrap_or(0);
133 let pts_raw = (decode_time as i64 + offset as i64).max(0) as u64;
134 mp4_pts.push(raw2dur(pts_raw, media_timescale));
135 sample_timing.push(crate::h264_source::Mp4SampleTiming {
136 decode_duration: raw2dur(duration as u64, media_timescale),
137 composition_offset: raw2signed_dur(offset, media_timescale),
138 });
139 nal_locations.push(Mp4NalLocation {
140 track_id,
141 sample_id,
142 });
143 }
144 assert_eq!(mp4_pts.len(), num_samples as usize);
145
146 let seekable_h264_source = Mp4Source {
147 mp4_reader,
148 nal_locations,
149 first_sps: data_from_mp4_track.sequence_parameter_set.clone(),
150 first_pps: data_from_mp4_track.picture_parameter_set.clone(),
151 };
152
153 let h264_source = H264Source::from_seekable_h264_source_with_timestamp_source(
154 seekable_h264_source,
155 do_decode_h264,
156 Some(mp4_pts),
157 Some(sample_timing),
158 Some(data_from_mp4_track),
159 timestamp_source,
160 srt_file_path,
161 show_progress,
162 preparser,
163 )?;
164 Ok(h264_source)
165}
166
167pub(crate) fn open_h264_in_mp4<P: AsRef<Path>>(
168 path: P,
169 do_decode_h264: bool,
170 timestamp_source: crate::TimestampSource,
171 srt_file_path: Option<std::path::PathBuf>,
172 show_progress: bool,
173 preparser: Option<Box<dyn H264Preparser>>,
174) -> Result<H264Source<Mp4Source>> {
175 let rdr = std::fs::File::open(path.as_ref())?;
176 let size = rdr.metadata()?.len();
177 let buf_reader: Box<dyn SeekRead + Send + 'static> = Box::new(std::io::BufReader::new(rdr));
178 let mp4_reader = mp4::Mp4Reader::read_header(buf_reader, size)?;
179
180 let result = from_reader_with_timestamp_source(
181 mp4_reader,
182 do_decode_h264,
183 timestamp_source,
184 srt_file_path,
185 show_progress,
186 preparser,
187 )?;
188 Ok(result)
189}
190
191fn avcc_to_nalu_ebsp(mp4_sample_buffer: &[u8]) -> Result<Vec<&[u8]>> {
199 let mut result = vec![];
200 let mut cur_buf = mp4_sample_buffer;
201 let mut total_nal_sizes = 0;
202 while !cur_buf.is_empty() {
203 if cur_buf.len() < 4 {
204 return Err(Mp4SourceError::SampleBufferTooShort.into());
205 }
206 let header = [cur_buf[0], cur_buf[1], cur_buf[2], cur_buf[3]];
207 let sz: usize = u32::from_be_bytes(header).try_into().unwrap();
208 let used = sz + 4;
209 if cur_buf.len() < used {
210 return Err(Mp4SourceError::LengthMismatch {
211 sz,
212 cur_len: cur_buf.len(),
213 }
214 .into());
215 }
216 total_nal_sizes += used;
217 result.push(&cur_buf[4..used]);
218 cur_buf = &cur_buf[used..];
219 }
220 if total_nal_sizes != mp4_sample_buffer.len() {
221 tracing::warn!(
222 "MP4 sample was {} bytes, but H264 NAL units totaled {} bytes.",
223 mp4_sample_buffer.len(),
224 total_nal_sizes
225 );
226 }
227 Ok(result)
228}
229
230fn raw2dur(raw: u64, timescale: u32) -> std::time::Duration {
231 std::time::Duration::from_secs_f64(raw as f64 / timescale as f64)
232}
233
234fn raw2signed_dur(raw: i32, timescale: u32) -> chrono::Duration {
237 let nanos = (raw as i64 * 1_000_000_000i64) / timescale as i64;
238 chrono::Duration::nanoseconds(nanos)
239}
240
241fn composition_offsets(track: &mp4::Mp4Track) -> Vec<i32> {
246 let mut offsets = Vec::new();
247 if let Some(ctts) = track.trak.mdia.minf.stbl.ctts.as_ref() {
248 for entry in &ctts.entries {
249 for _ in 0..entry.sample_count {
250 offsets.push(entry.sample_offset);
251 }
252 }
253 }
254 offsets
255}
256
257#[test]
258fn test_raw_duration() {
259 const TIMESCALE: u32 = 90_000;
260 fn dur2raw(dur: &std::time::Duration) -> u64 {
261 (dur.as_secs_f64() * TIMESCALE as f64).round() as u64
262 }
263
264 fn roundtrip(orig: u64) {
265 let actual = dur2raw(&raw2dur(orig, TIMESCALE));
266 assert_eq!(orig, actual);
267 }
268 roundtrip(0);
269 roundtrip(100);
270 roundtrip(1_000_000);
271 roundtrip(1_000_000_000);
272 roundtrip(1_000_000_000_000);
273}