trim 16 bytes from each recording::Segment

This reduces the working set by another 960 bytes for a typical one-hour recording, improving cache efficiency a bit more.

8 bytes from SampleIndexIterator:
   * reduce the three "bytes" fields to two. Doing so as "bytes_key" vs
     "bytes_nonkey" slowed it down a bit, perhaps because the "bytes" is
     needed right away and requires a branch. But "bytes" vs "bytes_other"
     seems fine. Looks like it can do this with cmovs in parallel with other
     stuff.
   * stuff "is_key" into the "i" field.

8 bytes from recording::Segment itself:
   * make "frames" and "key_frame" u16s
   * stuff "trailing_zero" into "video_sample_entry_id"
This commit is contained in:
Scott Lamb 2017-02-27 21:14:06 -08:00
parent 15609ddb8e
commit ce363162f4
4 changed files with 114 additions and 86 deletions

View File

@ -79,7 +79,7 @@ fn summarize_index(video_index: &[u8]) -> Result<RecordingSummary, Error> {
bytes += it.bytes as u64; bytes += it.bytes as u64;
duration += it.duration_90k; duration += it.duration_90k;
video_samples += 1; video_samples += 1;
video_sync_samples += if it.is_key { 1 } else { 0 }; video_sync_samples += it.is_key() as i32;
} }
Ok(RecordingSummary{ Ok(RecordingSummary{
bytes: bytes, bytes: bytes,

View File

@ -400,7 +400,7 @@ impl Segment {
BigEndian::write_u32(&mut stts[8*frame .. 8*frame+4], 1); BigEndian::write_u32(&mut stts[8*frame .. 8*frame+4], 1);
BigEndian::write_u32(&mut stts[8*frame+4 .. 8*frame+8], it.duration_90k as u32); BigEndian::write_u32(&mut stts[8*frame+4 .. 8*frame+8], it.duration_90k as u32);
BigEndian::write_u32(&mut stsz[4*frame .. 4*frame+4], it.bytes as u32); BigEndian::write_u32(&mut stsz[4*frame .. 4*frame+4], it.bytes as u32);
if it.is_key { if it.is_key() {
BigEndian::write_u32(&mut stss[4*key_frame .. 4*key_frame+4], BigEndian::write_u32(&mut stss[4*key_frame .. 4*key_frame+4],
self.first_frame_num + (frame as u32)); self.first_frame_num + (frame as u32));
key_frame += 1; key_frame += 1;
@ -589,7 +589,7 @@ impl FileBuilder {
pub fn append(&mut self, db: &db::LockedDatabase, row: db::ListRecordingsRow, pub fn append(&mut self, db: &db::LockedDatabase, row: db::ListRecordingsRow,
rel_range_90k: Range<i32>) -> Result<(), Error> { rel_range_90k: Range<i32>) -> Result<(), Error> {
if let Some(prev) = self.segments.last() { if let Some(prev) = self.segments.last() {
if prev.s.have_trailing_zero { if prev.s.have_trailing_zero() {
return Err(Error::new(format!( return Err(Error::new(format!(
"unable to append recording {}/{} after recording {}/{} with trailing zero", "unable to append recording {}/{} after recording {}/{} with trailing zero",
row.camera_id, row.id, prev.s.camera_id, prev.s.recording_id))); row.camera_id, row.id, prev.s.camera_id, prev.s.recording_id)));
@ -1005,7 +1005,7 @@ impl FileBuilder {
// Write sample_description_index. // Write sample_description_index.
let i = self.video_sample_entries.iter().position( let i = self.video_sample_entries.iter().position(
|e| e.id == s.s.video_sample_entry_id).unwrap(); |e| e.id == s.s.video_sample_entry_id()).unwrap();
self.body.append_u32((i + 1) as u32); self.body.append_u32((i + 1) as u32);
} }
}) })

View File

@ -208,16 +208,95 @@ impl ops::SubAssign for Duration {
fn sub_assign(&mut self, rhs: Duration) { self.0 -= rhs.0 } fn sub_assign(&mut self, rhs: Duration) { self.0 -= rhs.0 }
} }
/// An iterator through a sample index.
/// Initially invalid; call `next()` before each read.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct SampleIndexIterator { pub struct SampleIndexIterator {
i: u32, /// The index byte position of the next sample to read (low 31 bits) and if the current
/// same is a key frame (high bit).
i_and_is_key: u32,
/// The starting data byte position of this sample within the segment.
pub pos: i32, pub pos: i32,
/// The starting time of this sample within the segment (in 90 kHz units).
pub start_90k: i32, pub start_90k: i32,
/// The duration of this sample (in 90 kHz units).
pub duration_90k: i32, pub duration_90k: i32,
/// The byte length of this frame.
pub bytes: i32, pub bytes: i32,
bytes_key: i32,
bytes_nonkey: i32, /// The byte length of the last frame of the "other" type: if this one is key, the last
pub is_key: bool /// non-key; if this one is non-key, the last key.
bytes_other: i32,
}
impl SampleIndexIterator {
pub fn new() -> SampleIndexIterator {
SampleIndexIterator{i_and_is_key: 0,
pos: 0,
start_90k: 0,
duration_90k: 0,
bytes: 0,
bytes_other: 0}
}
pub fn next(&mut self, data: &[u8]) -> Result<bool, Error> {
self.pos += self.bytes;
self.start_90k += self.duration_90k;
let i = (self.i_and_is_key & 0x7FFF_FFFF) as usize;
if i == data.len() {
return Ok(false)
}
let (raw1, i1) = match decode_varint32(data, i) {
Ok(tuple) => tuple,
Err(()) => return Err(Error::new(format!("bad varint 1 at offset {}", i))),
};
let (raw2, i2) = match decode_varint32(data, i1) {
Ok(tuple) => tuple,
Err(()) => return Err(Error::new(format!("bad varint 2 at offset {}", i1))),
};
let duration_90k_delta = unzigzag32(raw1 >> 1);
self.duration_90k += duration_90k_delta;
if self.duration_90k < 0 {
return Err(Error{
description: format!("negative duration {} after applying delta {}",
self.duration_90k, duration_90k_delta),
cause: None});
}
if self.duration_90k == 0 && data.len() > i2 {
return Err(Error{
description: format!("zero duration only allowed at end; have {} bytes left",
data.len() - i2),
cause: None});
}
let (prev_bytes_key, prev_bytes_nonkey) = match self.is_key() {
true => (self.bytes, self.bytes_other),
false => (self.bytes_other, self.bytes),
};
self.i_and_is_key = (i2 as u32) | (((raw1 & 1) as u32) << 31);
let bytes_delta = unzigzag32(raw2);
if self.is_key() {
self.bytes = prev_bytes_key + bytes_delta;
self.bytes_other = prev_bytes_nonkey;
} else {
self.bytes = prev_bytes_nonkey + bytes_delta;
self.bytes_other = prev_bytes_key;
}
if self.bytes <= 0 {
return Err(Error{
description: format!("non-positive bytes {} after applying delta {} to key={} \
frame at ts {}", self.bytes, bytes_delta, self.is_key(),
self.start_90k),
cause: None});
}
Ok(true)
}
pub fn uninitialized(&self) -> bool { self.i_and_is_key == 0 }
pub fn is_key(&self) -> bool { (self.i_and_is_key & 0x8000_0000) != 0 }
} }
#[derive(Debug)] #[derive(Debug)]
@ -236,67 +315,6 @@ pub struct SampleIndexEncoder {
pub video_index: Vec<u8>, pub video_index: Vec<u8>,
} }
impl SampleIndexIterator {
pub fn new() -> SampleIndexIterator {
SampleIndexIterator{i: 0,
pos: 0,
start_90k: 0,
duration_90k: 0,
bytes: 0,
bytes_key: 0,
bytes_nonkey: 0,
is_key: false}
}
pub fn next(&mut self, data: &[u8]) -> Result<bool, Error> {
self.pos += self.bytes;
self.start_90k += self.duration_90k;
if self.i as usize == data.len() {
return Ok(false)
}
let (raw1, i1) = match decode_varint32(data, self.i as usize) {
Ok(tuple) => tuple,
Err(()) => return Err(Error::new(format!("bad varint 1 at offset {}", self.i))),
};
let (raw2, i2) = match decode_varint32(data, i1) {
Ok(tuple) => tuple,
Err(()) => return Err(Error::new(format!("bad varint 2 at offset {}", i1))),
};
self.i = i2 as u32;
let duration_90k_delta = unzigzag32(raw1 >> 1);
self.duration_90k += duration_90k_delta;
if self.duration_90k < 0 {
return Err(Error{
description: format!("negative duration {} after applying delta {}",
self.duration_90k, duration_90k_delta),
cause: None});
}
if self.duration_90k == 0 && data.len() > self.i as usize {
return Err(Error{
description: format!("zero duration only allowed at end; have {} bytes left",
data.len() - self.i as usize),
cause: None});
}
self.is_key = (raw1 & 1) == 1;
let bytes_delta = unzigzag32(raw2);
self.bytes = if self.is_key {
self.bytes_key += bytes_delta;
self.bytes_key
} else {
self.bytes_nonkey += bytes_delta;
self.bytes_nonkey
};
if self.bytes <= 0 {
return Err(Error{
description: format!("non-positive bytes {} after applying delta {} to key={} frame at ts {}",
self.bytes, bytes_delta, self.is_key,
self.start_90k),
cause: None});
}
Ok(true)
}
}
impl SampleIndexEncoder { impl SampleIndexEncoder {
pub fn new() -> Self { pub fn new() -> Self {
SampleIndexEncoder{ SampleIndexEncoder{
@ -344,10 +362,9 @@ pub struct Segment {
pub file_end: i32, pub file_end: i32,
pub desired_range_90k: Range<i32>, pub desired_range_90k: Range<i32>,
actual_end_90k: i32, actual_end_90k: i32,
pub frames: i32, pub frames: u16,
pub key_frames: i32, pub key_frames: u16,
pub video_sample_entry_id: i32, video_sample_entry_id_and_trailing_zero: i32,
pub have_trailing_zero: bool,
} }
impl Segment { impl Segment {
@ -368,10 +385,11 @@ impl Segment {
file_end: recording.sample_file_bytes, file_end: recording.sample_file_bytes,
desired_range_90k: desired_range_90k, desired_range_90k: desired_range_90k,
actual_end_90k: recording.duration_90k, actual_end_90k: recording.duration_90k,
frames: recording.video_samples, frames: recording.video_samples as u16,
key_frames: recording.video_sync_samples, key_frames: recording.video_sync_samples as u16,
video_sample_entry_id: recording.video_sample_entry.id, video_sample_entry_id_and_trailing_zero:
have_trailing_zero: (recording.flags & db::RecordingFlags::TrailingZero as i32) != 0, recording.video_sample_entry.id |
((((recording.flags & db::RecordingFlags::TrailingZero as i32) != 0) as i32) << 31),
}; };
if self_.desired_range_90k.start > self_.desired_range_90k.end || if self_.desired_range_90k.start > self_.desired_range_90k.end ||
@ -395,7 +413,7 @@ impl Segment {
return Err(Error{description: String::from("no index"), return Err(Error{description: String::from("no index"),
cause: None}); cause: None});
} }
if !it.is_key { if !it.is_key() {
return Err(Error{description: String::from("not key frame"), return Err(Error{description: String::from("not key frame"),
cause: None}); cause: None});
} }
@ -412,7 +430,7 @@ impl Segment {
}; };
loop { loop {
if it.start_90k <= self_.desired_range_90k.start && it.is_key { if it.start_90k <= self_.desired_range_90k.start && it.is_key() {
// new start candidate. // new start candidate.
self_.begin = it; self_.begin = it;
self_.frames = 0; self_.frames = 0;
@ -422,17 +440,25 @@ impl Segment {
break; break;
} }
self_.frames += 1; self_.frames += 1;
self_.key_frames += it.is_key as i32; self_.key_frames += it.is_key() as u16;
if !it.next(data)? { if !it.next(data)? {
break; break;
} }
} }
self_.file_end = it.pos; self_.file_end = it.pos;
self_.actual_end_90k = it.start_90k; self_.actual_end_90k = it.start_90k;
self_.have_trailing_zero = it.duration_90k == 0; self_.video_sample_entry_id_and_trailing_zero =
recording.video_sample_entry.id |
(((it.duration_90k == 0) as i32) << 31);
Ok(self_) Ok(self_)
} }
pub fn video_sample_entry_id(&self) -> i32 {
self.video_sample_entry_id_and_trailing_zero & 0x7FFFFFFF
}
pub fn have_trailing_zero(&self) -> bool { self.video_sample_entry_id_and_trailing_zero < 0 }
/// Returns the byte range within the sample file of data associated with this segment. /// Returns the byte range within the sample file of data associated with this segment.
pub fn sample_file_range(&self) -> Range<u64> { self.begin.pos as u64 .. self.file_end as u64 } pub fn sample_file_range(&self) -> Range<u64> { self.begin.pos as u64 .. self.file_end as u64 }
@ -448,12 +474,12 @@ impl Segment {
let playback = db.lock().get_recording_playback(self.camera_id, self.recording_id)?; let playback = db.lock().get_recording_playback(self.camera_id, self.recording_id)?;
let data = &(&playback).video_index; let data = &(&playback).video_index;
let mut it = self.begin; let mut it = self.begin;
if it.i == 0 { if it.uninitialized() {
if !it.next(data)? { if !it.next(data)? {
return Err(Error::new(format!("recording {}/{}: no frames", return Err(Error::new(format!("recording {}/{}: no frames",
self.camera_id, self.recording_id))); self.camera_id, self.recording_id)));
} }
if !it.is_key { if !it.is_key() {
return Err(Error::new(format!("recording {}/{}: doesn't start with key frame", return Err(Error::new(format!("recording {}/{}: doesn't start with key frame",
self.camera_id, self.recording_id))); self.camera_id, self.recording_id)));
} }
@ -466,7 +492,7 @@ impl Segment {
self.camera_id, self.recording_id, self.frames, self.camera_id, self.recording_id, self.frames,
i+1))); i+1)));
} }
if it.is_key { if it.is_key() {
key_frame += 1; key_frame += 1;
if key_frame > self.key_frames { if key_frame > self.key_frames {
return Err(Error::new(format!( return Err(Error::new(format!(
@ -577,7 +603,9 @@ mod tests {
for sample in &samples { for sample in &samples {
assert!(it.next(&e.video_index).unwrap()); assert!(it.next(&e.video_index).unwrap());
assert_eq!(sample, assert_eq!(sample,
&Sample{duration_90k: it.duration_90k, bytes: it.bytes, is_key: it.is_key}); &Sample{duration_90k: it.duration_90k,
bytes: it.bytes,
is_key: it.is_key()});
} }
assert!(!it.next(&e.video_index).unwrap()); assert!(!it.next(&e.video_index).unwrap());
} }

View File

@ -313,7 +313,7 @@ mod tests {
frames.push(Frame{ frames.push(Frame{
start_90k: it.start_90k, start_90k: it.start_90k,
duration_90k: it.duration_90k, duration_90k: it.duration_90k,
is_key: it.is_key, is_key: it.is_key(),
}); });
} }
frames frames