mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-11 23:13:23 -05:00
fix timestamp duration calculation panic
The code was confused about whether this was relative to the start of the recording or the desired range of the recording. When the two differ (that is, when the beginning of the segment is skipped), the duration was incorrect. The thread would panic when skipping beyond the start of the next second. Also add some missing info to Segment's Debug impl.
This commit is contained in:
parent
f08732057e
commit
99ef052f80
41
src/mp4.rs
41
src/mp4.rs
@ -359,12 +359,11 @@ struct Segment {
|
||||
/// 2. stsz: `slice[stsz_start .. stss_start]`
|
||||
/// 3. stss: `slice[stss_start ..]`
|
||||
index: UnsafeCell<Result<Box<[u8]>, ()>>,
|
||||
index_once: Once,
|
||||
|
||||
/// The 1-indexed frame number in the `File` of the first frame in this segment.
|
||||
first_frame_num: u32,
|
||||
num_subtitle_samples: u16,
|
||||
|
||||
index_once: Once,
|
||||
}
|
||||
|
||||
// Manually implement Debug because `index` and `index_once` are not Debug.
|
||||
@ -372,6 +371,10 @@ impl fmt::Debug for Segment {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("mp4::Segment")
|
||||
.field("s", &self.s)
|
||||
.field("recording_start", &self.recording_start)
|
||||
.field("recording_wall_duration_90k", &self.recording_wall_duration_90k)
|
||||
.field("recording_media_duration_90k", &self.recording_media_duration_90k)
|
||||
.field("rel_media_range_90k", &self.rel_media_range_90k)
|
||||
.field("first_frame_num", &self.first_frame_num)
|
||||
.field("num_subtitle_samples", &self.num_subtitle_samples)
|
||||
.finish()
|
||||
@ -1368,11 +1371,12 @@ impl FileBuilder {
|
||||
self.body.append_u32(u32::try_from(mr.end - mr.start).unwrap());
|
||||
} else {
|
||||
// The first subtitle lasts until the next second.
|
||||
let mut media_pos =
|
||||
// media_off is relative to the start of the desired range.
|
||||
let mut media_off =
|
||||
s.media(i32::try_from((start_next_sec - start).0).unwrap());
|
||||
entry_count += 1;
|
||||
self.body.append_u32(1);
|
||||
self.body.append_u32(u32::try_from(media_pos - mr.start).unwrap());
|
||||
self.body.append_u32(u32::try_from(media_off).unwrap());
|
||||
|
||||
// Then there are zero or more "interior" subtitles, one second each. That's
|
||||
// one second converted from wall to media duration. rescale rounds down,
|
||||
@ -1388,13 +1392,13 @@ impl FileBuilder {
|
||||
entry_count += 1;
|
||||
self.body.append_u32(interior as u32); // count
|
||||
self.body.append_u32(u32::try_from(onesec_media_dur).unwrap());
|
||||
media_pos += onesec_media_dur * i32::try_from(interior).unwrap();
|
||||
media_off += onesec_media_dur * i32::try_from(interior).unwrap();
|
||||
}
|
||||
|
||||
// Then there's a final subtitle for the remaining fraction of a second.
|
||||
entry_count += 1;
|
||||
self.body.append_u32(1);
|
||||
self.body.append_u32(u32::try_from(mr.end - media_pos).unwrap());
|
||||
self.body.append_u32(u32::try_from(mr.end - mr.start - media_off).unwrap());
|
||||
}
|
||||
}
|
||||
BigEndian::write_u32(&mut self.body.buf[entry_count_pos .. entry_count_pos + 4],
|
||||
@ -2429,6 +2433,31 @@ mod tests {
|
||||
db.syncer_join.join().unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_round_trip_with_edit_list_and_subtitles() {
|
||||
testutil::init();
|
||||
let db = TestDb::new(RealClocks {});
|
||||
copy_mp4_to_db(&db);
|
||||
let off = 2*TIME_UNITS_PER_SEC;
|
||||
let mp4 = create_mp4_from_db(&db, i32::try_from(off).unwrap(), 0, true);
|
||||
traverse(mp4.clone()).await;
|
||||
let new_filename = write_mp4(&mp4, db.tmpdir.path()).await;
|
||||
compare_mp4s(&new_filename, off, 0);
|
||||
|
||||
// Test the metadata. This is brittle, which is the point. Any time the digest comparison
|
||||
// here fails, it can be updated, but the etag must change as well! Otherwise clients may
|
||||
// combine ranges from the new format with ranges from the old format.
|
||||
let hash = digest(&mp4).await;
|
||||
assert_eq!("7aef371e9ab5e871893fd9b1963cb71c1c9b093b5d4ff36cb1340b65155a8aa2",
|
||||
hash.to_hex().as_str());
|
||||
const EXPECTED_ETAG: &'static str =
|
||||
"\"84cafd9db7a5c0c32961d9848582d2dca436a58d25cbedfb02d7450bc6ce1229\"";
|
||||
assert_eq!(Some(HeaderValue::from_str(EXPECTED_ETAG).unwrap()), mp4.etag());
|
||||
drop(db.syncer_channel);
|
||||
db.db.lock().clear_on_flush();
|
||||
db.syncer_join.join().unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_round_trip_with_shorten() {
|
||||
testutil::init();
|
||||
|
Loading…
Reference in New Issue
Block a user