Shrink mp4 file slices from 16 to 8 bytes each

For a one-hour recording, this is about 2 KiB, so a decent chunk of a
Raspberry Pi 2's L1 cache. Generating the Slices and searching/scanning
it should be a bit faster and pollute the cache less.

This is a pretty small optimization now when transferring a decent chunk
of the moov or mdat, but it's easy enough to do. It will be much more
noticeable if I end up interleaving the captions between each key frame.
This commit is contained in:
Scott Lamb 2017-02-21 19:37:36 -08:00
parent c6813bd886
commit 0a683b0846
3 changed files with 246 additions and 227 deletions

View File

@ -72,8 +72,8 @@ mod h264;
mod json;
mod mmapfile;
mod mp4;
mod pieces;
mod recording;
mod slices;
mod stream;
mod streamer;
mod strutil;

View File

@ -82,15 +82,13 @@ extern crate time;
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use db;
use dir;
use error::{Error, Result};
use error::Error;
use http_entity;
use hyper::header;
use mmapfile;
use openssl::hash;
use pieces;
use pieces::ContextWriter;
use pieces::Slices;
use recording::{self, TIME_UNITS_PER_SEC};
use slices::{self, Slices};
use smallvec::SmallVec;
use std::cell::RefCell;
use std::cmp;
@ -295,7 +293,7 @@ const STATIC_BYTESTRINGS: [&'static [u8]; 8] = [
/// Enumeration of the static bytestrings. The order here must match the `STATIC_BYTESTRINGS`
/// array. The advantage of this enum over direct pointers to the relevant strings is that it
/// fits into a u32 on 64-bit platforms, allowing an `Mp4FileSlice` to fit into 8 bytes.
/// fits into `Slice`'s 20-bit `p`.
#[derive(Copy, Clone, Debug)]
enum StaticBytestring {
FtypBox,
@ -347,8 +345,8 @@ struct Mp4Segment {
}
impl Mp4Segment {
fn with_index<F, R>(&self, db: &db::Database, f: F) -> Result<R>
where F: FnOnce(&Mp4SegmentIndex) -> Result<R> {
fn with_index<F, R>(&self, db: &db::Database, f: F) -> Result<R, Error>
where F: FnOnce(&Mp4SegmentIndex) -> Result<R, Error> {
let mut i = self.index.borrow_mut();
if let Some(ref i) = *i {
return f(i);
@ -359,7 +357,7 @@ impl Mp4Segment {
r
}
fn build_index(&self, db: &db::Database) -> Result<Mp4SegmentIndex> {
fn build_index(&self, db: &db::Database) -> Result<Mp4SegmentIndex, Error> {
let s = &self.s;
let stts_len = mem::size_of::<u32>() * 2 * (s.frames as usize);
let stsz_len = mem::size_of::<u32>() * s.frames as usize;
@ -423,7 +421,7 @@ pub struct Mp4FileBuilder {
/// This is separated out from the rest so that it can be borrowed in a loop over
/// `Mp4FileBuilder::segments`; otherwise this would cause a double-self-borrow.
struct BodyState {
slices: Slices<Mp4FileSlice, Mp4File>,
slices: Slices<Slice, Mp4File>,
/// `self.buf[unflushed_buf_pos .. self.buf.len()]` holds bytes that should be
/// appended to `slices` before any other slice. See `flush_buf()`.
@ -435,64 +433,89 @@ struct BodyState {
/// some portion of the generated `.mp4` file. The box headers and such are generally in `Static`
/// or `Buf` slices; the others generally represent a single segment's contribution to the
/// like-named box.
#[derive(Debug)]
enum Mp4FileSlice {
Static(StaticBytestring), // param is index into STATIC_BYTESTRINGS
Buf(u32), // param is index into m.buf
VideoSampleEntry(u32), // param is index into m.video_sample_entries
Stts(u32), // param is index into m.segments
Stsz(u32), // param is index into m.segments
Co64,
Stss(u32), // param is index into m.segments
VideoSampleData(u32), // param is index into m.segments
SubtitleSampleData(u32), // param is index into m.segments
///
/// This is stored in a packed representation to be more cache-efficient:
///
/// * low 40 bits: end() (maximum 1 TiB).
/// * next 4 bits: t(), the SliceType.
/// * top 20 bits: p(), a parameter specified by the SliceType (maximum 1 Mi).
struct Slice(u64);
/// The type of a `Slice`.
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
enum SliceType {
Static = 0, // param is index into STATIC_BYTESTRINGS
Buf = 1, // param is index into m.buf
VideoSampleEntry = 2, // param is index into m.video_sample_entries
Stts = 3, // param is index into m.segments
Stsz = 4, // param is index into m.segments
Co64 = 5, // param is unused
Stss = 6, // param is index into m.segments
VideoSampleData = 7, // param is index into m.segments
SubtitleSampleData = 8, // param is index into m.segments
// There must be no value > 15, as this is packed into 4 bits in Slice.
}
impl ContextWriter<Mp4File> for Mp4FileSlice {
impl Slice {
fn new(end: u64, t: SliceType, p: usize) -> Result<Self, Error> {
if end >= (1<<40) || p >= (1<<20) {
return Err(Error::new(format!("end={} p={} too large for Slice", end, p)));
}
Ok(Slice(end | ((t as u64) << 40) | ((p as u64) << 44)))
}
}
impl Slice {
fn t(&self) -> SliceType {
// This value is guaranteed to be a valid SliceType because it was copied from a SliceType
// in Slice::new.
unsafe { ::std::mem::transmute(((self.0 >> 40) & 0xF) as u8) }
}
fn p(&self) -> usize { (self.0 >> 44) as usize }
}
impl slices::Slice<Mp4File> for Slice {
fn end(&self) -> u64 { return self.0 & 0xFF_FF_FF_FF_FF }
fn write_to(&self, f: &Mp4File, r: Range<u64>, l: u64, out: &mut io::Write)
-> Result<()> {
-> Result<(), Error> {
let t = self.t();
let p = self.p();
trace!("write {:?}, range {:?} out of len {}", self, r, l);
match *self {
Mp4FileSlice::Static(off) => {
let s = STATIC_BYTESTRINGS[off as usize];
match t {
SliceType::Static => {
let s = STATIC_BYTESTRINGS[p];
let part = &s[r.start as usize .. r.end as usize];
out.write_all(part)?;
Ok(())
},
Mp4FileSlice::Buf(off) => {
let off = off as usize;
out.write_all(
&f.buf[off+r.start as usize .. off+r.end as usize])?;
SliceType::Buf => {
out.write_all(&f.buf[p+r.start as usize .. p+r.end as usize])?;
Ok(())
},
Mp4FileSlice::VideoSampleEntry(off) => {
let e = &f.video_sample_entries[off as usize];
let part = &e.data[r.start as usize .. r.end as usize];
out.write_all(part)?;
SliceType::VideoSampleEntry => {
out.write_all(&f.video_sample_entries[p].data[r.start as usize .. r.end as usize])?;
Ok(())
},
Mp4FileSlice::Stts(index) => {
f.write_stts(index as usize, r, l, out)
},
Mp4FileSlice::Stsz(index) => {
f.write_stsz(index as usize, r, l, out)
},
Mp4FileSlice::Co64 => {
f.write_co64(r, l, out)
},
Mp4FileSlice::Stss(index) => {
f.write_stss(index as usize, r, l, out)
},
Mp4FileSlice::VideoSampleData(index) => {
f.write_video_sample_data(index as usize, r, out)
},
Mp4FileSlice::SubtitleSampleData(index) => {
f.write_subtitle_sample_data(index as usize, r, l, out)
}
SliceType::Stts => f.write_stts(p, r, l, out),
SliceType::Stsz => f.write_stsz(p, r, l, out),
SliceType::Co64 => f.write_co64(r, l, out),
SliceType::Stss => f.write_stss(p, r, l, out),
SliceType::VideoSampleData => f.write_video_sample_data(p, r, out),
SliceType::SubtitleSampleData => f.write_subtitle_sample_data(p, r, l, out),
}
}
}
impl ::std::fmt::Debug for Slice {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
// Write an unpacked representation. Omit end(); Slices writes that part.
write!(f, "{:?} {}", self.t(), self.p())
}
}
/// Converts from 90kHz units since Unix epoch (1970-01-01 00:00:00 UTC) to seconds since
/// ISO-14496 epoch (1904-01-01 00:00:00 UTC).
fn to_iso14496_timestamp(t: recording::Time) -> u32 { (t.unix_seconds() + 24107 * 86400) as u32 }
@ -510,6 +533,7 @@ macro_rules! write_length {
$_self.body.unflushed_buf_pos as u64;
BigEndian::write_u32(&mut $_self.body.buf[len_pos .. len_pos + 4],
(len_end - len_start) as u32);
Ok::<_, Error>(())
}}
}
@ -544,7 +568,7 @@ impl Mp4FileBuilder {
/// Appends a segment for (a subset of) the given recording.
pub fn append(&mut self, db: &MutexGuard<db::LockedDatabase>, row: db::ListRecordingsRow,
rel_range_90k: Range<i32>) -> Result<()> {
rel_range_90k: Range<i32>) -> Result<(), Error> {
if let Some(prev) = self.segments.last() {
if prev.s.have_trailing_zero {
return Err(Error::new(format!(
@ -566,7 +590,8 @@ impl Mp4FileBuilder {
}
/// Builds the `Mp4File`, consuming the builder.
pub fn build(mut self, db: Arc<db::Database>, dir: Arc<dir::SampleFileDir>) -> Result<Mp4File> {
pub fn build(mut self, db: Arc<db::Database>, dir: Arc<dir::SampleFileDir>)
-> Result<Mp4File, Error> {
let mut max_end = None;
let mut etag = hash::Hasher::new(hash::MessageDigest::sha1())?;
etag.update(&FORMAT_VERSION[..])?;
@ -614,7 +639,7 @@ impl Mp4FileBuilder {
self.body.slices.reserve(est_slices);
const EST_BUF_LEN: usize = 2048;
self.body.buf.reserve(EST_BUF_LEN);
self.body.append_static(StaticBytestring::FtypBox);
self.body.append_static(StaticBytestring::FtypBox)?;
self.append_moov(creation_ts)?;
// Write the mdat header. Use the large format to support files over 2^32-1 bytes long.
@ -622,19 +647,19 @@ impl Mp4FileBuilder {
// It'd be nice to use the until-EOF form, but QuickTime Player doesn't support it.
self.body.buf.extend_from_slice(b"\x00\x00\x00\x01mdat\x00\x00\x00\x00\x00\x00\x00\x00");
let mdat_len_pos = self.body.buf.len() - 8;
self.body.flush_buf();
self.body.flush_buf()?;
let initial_sample_byte_pos = self.body.slices.len();
for (i, s) in self.segments.iter().enumerate() {
let r = s.s.sample_file_range();
self.body.slices.append(r.end - r.start, Mp4FileSlice::VideoSampleData(i as u32));
self.body.append_slice(r.end - r.start, SliceType::VideoSampleData, i)?;
}
if let Some(p) = self.subtitle_co64_pos {
BigEndian::write_u64(&mut self.body.buf[p .. p + 8], self.body.slices.len());
for (i, s) in self.segments.iter().enumerate() {
self.body.slices.append(
self.body.append_slice(
s.num_subtitle_samples as u64 *
(mem::size_of::<u16>() + SUBTITLE_LENGTH) as u64,
Mp4FileSlice::SubtitleSampleData(i as u32));
SliceType::SubtitleSampleData, i)?;
}
}
// Fill in the length left as a placeholder above. Note the 16 here is the length
@ -668,20 +693,19 @@ impl Mp4FileBuilder {
}
/// Appends a `MovieBox` (ISO/IEC 14496-12 section 8.2.1).
fn append_moov(&mut self, creation_ts: u32) -> Result<()> {
fn append_moov(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"moov");
self.append_mvhd(creation_ts);
self.append_mvhd(creation_ts)?;
self.append_video_trak(creation_ts)?;
if self.include_timestamp_subtitle_track {
self.append_subtitle_trak(creation_ts);
self.append_subtitle_trak(creation_ts)?;
}
});
Ok(())
})
}
/// Appends a `MovieHeaderBox` version 0 (ISO/IEC 14496-12 section 8.2.2).
fn append_mvhd(&mut self, creation_ts: u32) {
fn append_mvhd(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"mvhd\x00\x00\x00\x00");
self.body.append_u32(creation_ts);
@ -689,34 +713,33 @@ impl Mp4FileBuilder {
self.body.append_u32(TIME_UNITS_PER_SEC as u32);
let d = self.duration_90k;
self.body.append_u32(d);
self.body.append_static(StaticBytestring::MvhdJunk);
self.body.append_static(StaticBytestring::MvhdJunk)?;
let next_track_id = if self.include_timestamp_subtitle_track { 3 } else { 2 };
self.body.append_u32(next_track_id);
});
})
}
/// Appends a `TrackBox` (ISO/IEC 14496-12 section 8.3.1) suitable for video.
fn append_video_trak(&mut self, creation_ts: u32) -> Result<()> {
fn append_video_trak(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"trak");
self.append_video_tkhd(creation_ts);
self.append_video_tkhd(creation_ts)?;
self.maybe_append_video_edts()?;
self.append_video_mdia(creation_ts);
});
Ok(())
self.append_video_mdia(creation_ts)?;
})
}
/// Appends a `TrackBox` (ISO/IEC 14496-12 section 8.3.1) suitable for subtitles.
fn append_subtitle_trak(&mut self, creation_ts: u32) {
fn append_subtitle_trak(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"trak");
self.append_subtitle_tkhd(creation_ts);
self.append_subtitle_mdia(creation_ts);
});
self.append_subtitle_tkhd(creation_ts)?;
self.append_subtitle_mdia(creation_ts)?;
})
}
/// Appends a `TrackHeaderBox` (ISO/IEC 14496-12 section 8.3.2) suitable for video.
fn append_video_tkhd(&mut self, creation_ts: u32) {
fn append_video_tkhd(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
// flags 7: track_enabled | track_in_movie | track_in_preview
self.body.buf.extend_from_slice(b"tkhd\x00\x00\x00\x07");
@ -725,16 +748,16 @@ impl Mp4FileBuilder {
self.body.append_u32(1); // track_id
self.body.append_u32(0); // reserved
self.body.append_u32(self.duration_90k);
self.body.append_static(StaticBytestring::TkhdJunk);
self.body.append_static(StaticBytestring::TkhdJunk)?;
let width = self.video_sample_entries.iter().map(|e| e.width).max().unwrap();
let height = self.video_sample_entries.iter().map(|e| e.height).max().unwrap();
self.body.append_u32((width as u32) << 16);
self.body.append_u32((height as u32) << 16);
});
})
}
/// Appends a `TrackHeaderBox` (ISO/IEC 14496-12 section 8.3.2) suitable for subtitles.
fn append_subtitle_tkhd(&mut self, creation_ts: u32) {
fn append_subtitle_tkhd(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
// flags 7: track_enabled | track_in_movie | track_in_preview
self.body.buf.extend_from_slice(b"tkhd\x00\x00\x00\x07");
@ -743,14 +766,14 @@ impl Mp4FileBuilder {
self.body.append_u32(2); // track_id
self.body.append_u32(0); // reserved
self.body.append_u32(self.duration_90k);
self.body.append_static(StaticBytestring::TkhdJunk);
self.body.append_static(StaticBytestring::TkhdJunk)?;
self.body.append_u32(0); // width, unused.
self.body.append_u32(0); // height, unused.
});
})
}
/// Appends an `EditBox` (ISO/IEC 14496-12 section 8.6.5) suitable for video, if necessary.
fn maybe_append_video_edts(&mut self) -> Result<()> {
fn maybe_append_video_edts(&mut self) -> Result<(), Error> {
#[derive(Debug, Default)]
struct Entry {
segment_duration: u64,
@ -804,34 +827,33 @@ impl Mp4FileBuilder {
// media_rate_integer + media_rate_fraction: both fixed at 1
self.body.buf.extend_from_slice(b"\x00\x01\x00\x01");
}
});
});
Ok(())
})?;
})
}
/// Appends a `MediaBox` (ISO/IEC 14496-12 section 8.4.1) suitable for video.
fn append_video_mdia(&mut self, creation_ts: u32) {
fn append_video_mdia(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"mdia");
self.append_mdhd(creation_ts);
self.body.append_static(StaticBytestring::VideoHdlrBox);
self.append_video_minf();
});
self.append_mdhd(creation_ts)?;
self.body.append_static(StaticBytestring::VideoHdlrBox)?;
self.append_video_minf()?;
})
}
/// Appends a `MediaBox` (ISO/IEC 14496-12 section 8.4.1) suitable for subtitles.
fn append_subtitle_mdia(&mut self, creation_ts: u32) {
fn append_subtitle_mdia(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"mdia");
self.append_mdhd(creation_ts);
self.body.append_static(StaticBytestring::SubtitleHdlrBox);
self.append_subtitle_minf();
});
self.append_mdhd(creation_ts)?;
self.body.append_static(StaticBytestring::SubtitleHdlrBox)?;
self.append_subtitle_minf()?;
})
}
/// Appends a `MediaHeaderBox` (ISO/IEC 14496-12 section 8.4.2.) suitable for either the video
/// or subtitle track.
fn append_mdhd(&mut self, creation_ts: u32) {
fn append_mdhd(&mut self, creation_ts: u32) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"mdhd\x00\x00\x00\x00");
self.body.append_u32(creation_ts);
@ -839,65 +861,64 @@ impl Mp4FileBuilder {
self.body.append_u32(TIME_UNITS_PER_SEC as u32);
self.body.append_u32(self.duration_90k);
self.body.append_u32(0x55c40000); // language=und + pre_defined
});
})
}
/// Appends a `MediaInformationBox` (ISO/IEC 14496-12 section 8.4.4) suitable for video.
fn append_video_minf(&mut self) {
fn append_video_minf(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.append_static(StaticBytestring::VideoMinfJunk);
self.append_video_stbl();
});
self.body.append_static(StaticBytestring::VideoMinfJunk)?;
self.append_video_stbl()?;
})
}
/// Appends a `MediaInformationBox` (ISO/IEC 14496-12 section 8.4.4) suitable for subtitles.
fn append_subtitle_minf(&mut self) {
fn append_subtitle_minf(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.append_static(StaticBytestring::SubtitleMinfJunk);
self.append_subtitle_stbl();
});
self.body.append_static(StaticBytestring::SubtitleMinfJunk)?;
self.append_subtitle_stbl()?;
})
}
/// Appends a `SampleTableBox` (ISO/IEC 14496-12 section 8.5.1) suitable for video.
fn append_video_stbl(&mut self) {
fn append_video_stbl(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stbl");
self.append_video_stsd();
self.append_video_stts();
self.append_video_stsc();
self.append_video_stsz();
self.append_video_co64();
self.append_video_stss();
});
self.append_video_stsd()?;
self.append_video_stts()?;
self.append_video_stsc()?;
self.append_video_stsz()?;
self.append_video_co64()?;
self.append_video_stss()?;
})
}
/// Appends a `SampleTableBox` (ISO/IEC 14496-12 section 8.5.1) suitable for subtitles.
fn append_subtitle_stbl(&mut self) {
fn append_subtitle_stbl(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.append_static(StaticBytestring::SubtitleStblJunk);
self.append_subtitle_stts();
self.append_subtitle_stsc();
self.append_subtitle_stsz();
self.append_subtitle_co64();
});
self.body.append_static(StaticBytestring::SubtitleStblJunk)?;
self.append_subtitle_stts()?;
self.append_subtitle_stsc()?;
self.append_subtitle_stsz()?;
self.append_subtitle_co64()?;
})
}
/// Appends a `SampleDescriptionBox` (ISO/IEC 14496-12 section 8.5.2) suitable for video.
fn append_video_stsd(&mut self) {
fn append_video_stsd(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stsd\x00\x00\x00\x00");
let n_entries = self.video_sample_entries.len() as u32;
self.body.append_u32(n_entries);
self.body.flush_buf();
self.body.flush_buf()?;
for (i, e) in self.video_sample_entries.iter().enumerate() {
self.body.slices.append(e.data.len() as u64,
Mp4FileSlice::VideoSampleEntry(i as u32));
self.body.append_slice(e.data.len() as u64, SliceType::VideoSampleEntry, i)?;
}
});
})
}
/// Appends a `TimeToSampleBox` (ISO/IEC 14496-12 section 8.6.1) suitable for video.
fn append_video_stts(&mut self) {
fn append_video_stts(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stts\x00\x00\x00\x00");
let mut entry_count = 0;
@ -905,17 +926,16 @@ impl Mp4FileBuilder {
entry_count += s.s.frames as u32;
}
self.body.append_u32(entry_count);
self.body.flush_buf();
self.body.flush_buf()?;
for (i, s) in self.segments.iter().enumerate() {
self.body.slices.append(
2 * (mem::size_of::<u32>() as u64) * (s.s.frames as u64),
Mp4FileSlice::Stts(i as u32));
self.body.append_slice(
2 * (mem::size_of::<u32>() as u64) * (s.s.frames as u64), SliceType::Stts, i)?;
}
});
})
}
/// Appends a `TimeToSampleBox` (ISO/IEC 14496-12 section 8.6.1) suitable for subtitles.
fn append_subtitle_stts(&mut self) {
fn append_subtitle_stts(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stts\x00\x00\x00\x00");
@ -957,11 +977,11 @@ impl Mp4FileBuilder {
}
BigEndian::write_u32(&mut self.body.buf[entry_count_pos .. entry_count_pos + 4],
entry_count);
});
})
}
/// Appends a `SampleToChunkBox` (ISO/IEC 14496-12 section 8.7.4) suitable for video.
fn append_video_stsc(&mut self) {
fn append_video_stsc(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stsc\x00\x00\x00\x00");
self.body.append_u32(self.segments.len() as u32);
@ -974,21 +994,21 @@ impl Mp4FileBuilder {
|e| e.id == s.s.video_sample_entry_id).unwrap();
self.body.append_u32((i + 1) as u32);
}
});
})
}
/// Appends a `SampleToChunkBox` (ISO/IEC 14496-12 section 8.7.4) suitable for subtitles.
fn append_subtitle_stsc(&mut self) {
fn append_subtitle_stsc(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(
b"stsc\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01");
self.body.append_u32(self.num_subtitle_samples);
self.body.append_u32(1);
});
})
}
/// Appends a `SampleSizeBox` (ISO/IEC 14496-12 section 8.7.3) suitable for video.
fn append_video_stsz(&mut self) {
fn append_video_stsz(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stsz\x00\x00\x00\x00\x00\x00\x00\x00");
let mut entry_count = 0;
@ -996,48 +1016,47 @@ impl Mp4FileBuilder {
entry_count += s.s.frames as u32;
}
self.body.append_u32(entry_count);
self.body.flush_buf();
self.body.flush_buf()?;
for (i, s) in self.segments.iter().enumerate() {
self.body.slices.append(
(mem::size_of::<u32>()) as u64 * (s.s.frames as u64),
Mp4FileSlice::Stsz(i as u32));
self.body.append_slice(
(mem::size_of::<u32>()) as u64 * (s.s.frames as u64), SliceType::Stsz, i)?;
}
});
})
}
/// Appends a `SampleSizeBox` (ISO/IEC 14496-12 section 8.7.3) suitable for subtitles.
fn append_subtitle_stsz(&mut self) {
fn append_subtitle_stsz(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stsz\x00\x00\x00\x00");
self.body.append_u32((mem::size_of::<u16>() + SUBTITLE_LENGTH) as u32);
self.body.append_u32(self.num_subtitle_samples);
});
})
}
/// Appends a `ChunkLargeOffsetBox` (ISO/IEC 14496-12 section 8.7.5) suitable for video.
fn append_video_co64(&mut self) {
fn append_video_co64(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"co64\x00\x00\x00\x00");
self.body.append_u32(self.segments.len() as u32);
self.body.flush_buf();
self.body.slices.append(
self.body.flush_buf()?;
self.body.append_slice(
(mem::size_of::<u64>()) as u64 * (self.segments.len() as u64),
Mp4FileSlice::Co64);
});
SliceType::Co64, 0)?;
})
}
/// Appends a `ChunkLargeOffsetBox` (ISO/IEC 14496-12 section 8.7.5) suitable for subtitles.
fn append_subtitle_co64(&mut self) {
fn append_subtitle_co64(&mut self) -> Result<(), Error> {
write_length!(self, {
// Write a placeholder; the actual value will be filled in later.
self.body.buf.extend_from_slice(
b"co64\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00");
self.subtitle_co64_pos = Some(self.body.buf.len() - 8);
});
})
}
/// Appends a `SyncSampleBox` (ISO/IEC 14496-12 section 8.6.2) suitable for video.
fn append_video_stss(&mut self) {
fn append_video_stss(&mut self) -> Result<(), Error> {
write_length!(self, {
self.body.buf.extend_from_slice(b"stss\x00\x00\x00\x00");
let mut entry_count = 0;
@ -1045,13 +1064,13 @@ impl Mp4FileBuilder {
entry_count += s.s.key_frames as u32;
}
self.body.append_u32(entry_count);
self.body.flush_buf();
self.body.flush_buf()?;
for (i, s) in self.segments.iter().enumerate() {
self.body.slices.append(
self.body.append_slice(
(mem::size_of::<u32>() as u64) * (s.s.key_frames as u64),
Mp4FileSlice::Stss(i as u32));
SliceType::Stss, i)?;
}
});
})
}
}
@ -1067,20 +1086,27 @@ impl BodyState {
/// Flushes the buffer: appends a slice for everything written into the buffer so far,
/// noting the position which has been flushed. Call this method prior to adding any non-buffer
/// slice.
fn flush_buf(&mut self) {
fn flush_buf(&mut self) -> Result<(), Error> {
let len = self.buf.len();
if self.unflushed_buf_pos < len {
self.slices.append((len - self.unflushed_buf_pos) as u64,
Mp4FileSlice::Buf(self.unflushed_buf_pos as u32));
let p = self.unflushed_buf_pos;
self.append_slice((len - p) as u64, SliceType::Buf, p)?;
self.unflushed_buf_pos = len;
}
Ok(())
}
fn append_slice(&mut self, len: u64, t: SliceType, p: usize) -> Result<(), Error> {
let l = self.slices.len();
self.slices.append(Slice::new(l + len, t, p)?);
Ok(())
}
/// Appends a static bytestring, flushing the buffer if necessary.
fn append_static(&mut self, which: StaticBytestring) {
self.flush_buf();
fn append_static(&mut self, which: StaticBytestring) -> Result<(), Error> {
self.flush_buf()?;
let s = STATIC_BYTESTRINGS[which as usize];
self.slices.append(s.len() as u64, Mp4FileSlice::Static(which));
self.append_slice(s.len() as u64, SliceType::Static, which as usize)
}
}
@ -1088,7 +1114,7 @@ pub struct Mp4File {
db: Arc<db::Database>,
dir: Arc<dir::SampleFileDir>,
segments: Vec<Mp4Segment>,
slices: Slices<Mp4FileSlice, Mp4File>,
slices: Slices<Slice, Mp4File>,
buf: Vec<u8>,
video_sample_entries: SmallVec<[Arc<db::VideoSampleEntry>; 1]>,
initial_sample_byte_pos: u64,
@ -1098,7 +1124,7 @@ pub struct Mp4File {
impl Mp4File {
fn write_stts(&self, i: usize, r: Range<u64>, _l: u64, out: &mut io::Write)
-> Result<()> {
-> Result<(), Error> {
self.segments[i].with_index(&self.db, |i| {
out.write_all(&i.stts()[r.start as usize .. r.end as usize])?;
Ok(())
@ -1106,15 +1132,15 @@ impl Mp4File {
}
fn write_stsz(&self, i: usize, r: Range<u64>, _l: u64, out: &mut io::Write)
-> Result<()> {
-> Result<(), Error> {
self.segments[i].with_index(&self.db, |i| {
out.write_all(&i.stsz()[r.start as usize .. r.end as usize])?;
Ok(())
})
}
fn write_co64(&self, r: Range<u64>, l: u64, out: &mut io::Write) -> Result<()> {
pieces::clip_to_range(r, l, out, |w| {
fn write_co64(&self, r: Range<u64>, l: u64, out: &mut io::Write) -> Result<(), Error> {
slices::clip_to_range(r, l, out, |w| {
let mut pos = self.initial_sample_byte_pos;
for s in &self.segments {
w.write_u64::<BigEndian>(pos)?;
@ -1125,14 +1151,16 @@ impl Mp4File {
})
}
fn write_stss(&self, i: usize, r: Range<u64>, _l: u64, out: &mut io::Write) -> Result<()> {
fn write_stss(&self, i: usize, r: Range<u64>, _l: u64, out: &mut io::Write)
-> Result<(), Error> {
self.segments[i].with_index(&self.db, |i| {
out.write_all(&i.stss()[r.start as usize .. r.end as usize])?;
Ok(())
})
}
fn write_video_sample_data(&self, i: usize, r: Range<u64>, out: &mut io::Write) -> Result<()> {
fn write_video_sample_data(&self, i: usize, r: Range<u64>, out: &mut io::Write)
-> Result<(), Error> {
let s = &self.segments[i];
let rec = self.db.lock().get_recording_playback(s.s.camera_id, s.s.recording_id)?;
let f = self.dir.open_sample_file(rec.sample_file_uuid)?;
@ -1140,13 +1168,13 @@ impl Mp4File {
}
fn write_subtitle_sample_data(&self, i: usize, r: Range<u64>, l: u64, out: &mut io::Write)
-> Result<()> {
-> Result<(), Error> {
let s = &self.segments[i];
let d = &s.s.desired_range_90k;
let start_sec = (s.s.start + recording::Duration(d.start as i64)).unix_seconds();
let end_sec = (s.s.start + recording::Duration(d.end as i64 + TIME_UNITS_PER_SEC - 1))
.unix_seconds();
pieces::clip_to_range(r, l, out, |w| {
slices::clip_to_range(r, l, out, |w| {
for ts in start_sec .. end_sec {
w.write_u16::<BigEndian>(SUBTITLE_LENGTH as u16)?;
let tm = time::at(time::Timespec{sec: ts, nsec: 0});
@ -1166,7 +1194,7 @@ impl http_entity::Entity<Error> for Mp4File {
fn last_modified(&self) -> Option<header::HttpDate> { Some(self.last_modified) }
fn etag(&self) -> Option<header::EntityTag> { Some(self.etag.clone()) }
fn len(&self) -> u64 { self.slices.len() }
fn write_to(&self, range: Range<u64>, out: &mut io::Write) -> Result<()> {
fn write_to(&self, range: Range<u64>, out: &mut io::Write) -> Result<(), Error> {
self.slices.write_to(self, range, out)
}
}
@ -1194,7 +1222,6 @@ mod tests {
use http_entity::{self, Entity};
use std::fs;
use std::io;
use std::mem;
use std::ops::Range;
use std::path::Path;
use std::sync::Arc;
@ -1493,8 +1520,8 @@ mod tests {
}
}
/// Makes a `.mp4` file which is only good for exercising the `Mp4FileSlice` logic for
/// producing sample tables that match the supplied encoder.
/// Makes a `.mp4` file which is only good for exercising the `Slice` logic for producing
/// sample tables that match the supplied encoder.
fn make_mp4_from_encoder(db: &TestDb, encoder: recording::SampleIndexEncoder,
desired_range_90k: Range<i32>) -> Mp4File {
let row = db.create_recording_from_encoder(encoder);
@ -1698,11 +1725,6 @@ mod tests {
drop(db.syncer_channel);
db.syncer_join.join().unwrap();
}
#[test]
fn mp4_file_slice_size() {
assert_eq!(8, mem::size_of::<super::Mp4FileSlice>());
}
}
#[cfg(all(test, feature="nightly"))]

View File

@ -36,20 +36,13 @@ use std::io;
use std::marker::PhantomData;
use std::ops::Range;
/// Information needed by `Slices` about a single slice.
#[derive(Debug)]
struct SliceInfo<W> {
/// The byte position (relative to the start of the `Slices`) beyond the end of this slice.
/// Note the starting position (and thus length) are inferred from the previous slice.
end: u64,
/// Should be an implementation of `ContextWriter<Ctx>` for some `Ctx`.
writer: W,
}
/// Writes a byte range to the given `io::Write` given a context argument; meant for use with
/// `Slices`.
pub trait ContextWriter<Ctx> {
pub trait Slice<Ctx> {
/// The byte position (relative to the start of the `Slices`) beyond the end of this slice.
/// Note the starting position (and thus length) are inferred from the previous slice.
fn end(&self) -> u64;
/// Writes `r` to `out`, as in `http_entity::Entity::write_to`.
/// The additional argument `ctx` is as supplied to the `Slices`.
/// The additional argument `l` is the length of this slice, as determined by the `Slices`.
@ -72,37 +65,36 @@ where F: FnMut(&mut Vec<u8>) -> Result<()> {
}
/// Helper to serve byte ranges from a body which is broken down into many "slices".
/// This is used to implement `.mp4` serving in `mp4::Mp4File` from `mp4::Mp4FileSlice` enums.
pub struct Slices<W, C> where W: ContextWriter<C> {
/// This is used to implement `.mp4` serving in `mp4::Mp4File` from `mp4::Slice` enums.
pub struct Slices<S, C> where S: Slice<C> {
/// The total byte length of the `Slices`.
/// Equivalent to `self.slices.back().map(|s| s.end).unwrap_or(0)`; kept for convenience and to
/// avoid a branch.
/// Equivalent to `self.slices.back().map(|s| s.end()).unwrap_or(0)`; kept for convenience and
/// to avoid a branch.
len: u64,
/// 0 or more slices of this file.
slices: Vec<SliceInfo<W>>,
slices: Vec<S>,
/// Marker so that `C` is part of the type.
phantom: PhantomData<C>,
}
impl<W, C> fmt::Debug for Slices<W, C> where W: fmt::Debug + ContextWriter<C> {
impl<S, C> fmt::Debug for Slices<S, C> where S: fmt::Debug + Slice<C> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} slices with overall length {}:", self.slices.len(), self.len)?;
let mut start = 0;
for (i, s) in self.slices.iter().enumerate() {
let end = s.end();
write!(f, "\ni {:7}: range [{:12}, {:12}) len {:12}: {:?}",
i, start, s.end, s.end - start, s.writer)?;
start = s.end;
i, start, end, end - start, s)?;
start = end;
}
Ok(())
}
}
impl<W, C> Slices<W, C> where W: ContextWriter<C> {
pub fn new() -> Slices<W, C> {
Slices{len: 0, slices: Vec::new(), phantom: PhantomData}
}
impl<S, C> Slices<S, C> where S: Slice<C> {
pub fn new() -> Self { Slices{len: 0, slices: Vec::new(), phantom: PhantomData} }
/// Reserves space for at least `additional` more slices to be appended.
pub fn reserve(&mut self, additional: usize) {
@ -110,9 +102,10 @@ impl<W, C> Slices<W, C> where W: ContextWriter<C> {
}
/// Appends the given slice.
pub fn append(&mut self, len: u64, writer: W) {
self.len += len;
self.slices.push(SliceInfo{end: self.len, writer: writer});
pub fn append(&mut self, slice: S) {
assert!(slice.end() > self.len);
self.len = slice.end();
self.slices.push(slice);
}
/// Returns the total byte length of all slices.
@ -133,11 +126,11 @@ impl<W, C> Slices<W, C> where W: ContextWriter<C> {
// Binary search for the first slice of the range to write, determining its index and
// (from the preceding slice) the start of its range.
let (mut i, mut slice_start) = match self.slices.binary_search_by_key(&range.start,
|s| s.end) {
|s| s.end()) {
Ok(i) if i == self.slices.len() - 1 => return Ok(()), // at end.
Ok(i) => (i+1, self.slices[i].end), // desired start == slice i's end; first is i+1!
Err(i) if i == 0 => (i, 0), // desired start < slice 0's end; first is 0.
Err(i) => (i, self.slices[i-1].end), // desired start < slice i's end; first is i.
Ok(i) => (i+1, self.slices[i].end()), // desired start == slice i's end; first is i+1!
Err(i) if i == 0 => (0, 0), // desired start < slice 0's end; first is 0.
Err(i) => (i, self.slices[i-1].end()), // desired start < slice i's end; first is i.
};
// There is at least one slice to write.
@ -145,15 +138,16 @@ impl<W, C> Slices<W, C> where W: ContextWriter<C> {
let mut start_pos = range.start - slice_start;
loop {
let s = &self.slices[i];
let l = s.end - slice_start;
if range.end <= s.end { // last slice.
return s.writer.write_to(ctx, start_pos .. range.end - slice_start, l, out);
let end = s.end();
let l = end - slice_start;
if range.end <= end { // last slice.
return s.write_to(ctx, start_pos .. range.end - slice_start, l, out);
}
s.writer.write_to(ctx, start_pos .. s.end - slice_start, l, out)?;
s.write_to(ctx, start_pos .. end - slice_start, l, out)?;
// Setup next iteration.
start_pos = 0;
slice_start = s.end;
slice_start = end;
i += 1;
}
}
@ -167,7 +161,7 @@ mod tests {
use std::io::Write;
use std::ops::Range;
use std::vec::Vec;
use super::{ContextWriter, Slices, clip_to_range};
use super::{Slice, Slices, clip_to_range};
#[derive(Debug, Eq, PartialEq)]
pub struct FakeWrite {
@ -175,11 +169,14 @@ mod tests {
range: Range<u64>,
}
pub struct FakeWriter {
pub struct FakeSlice {
end: u64,
name: &'static str,
}
impl ContextWriter<RefCell<Vec<FakeWrite>>> for FakeWriter {
impl Slice<RefCell<Vec<FakeWrite>>> for FakeSlice {
fn end(&self) -> u64 { self.end }
fn write_to(&self, ctx: &RefCell<Vec<FakeWrite>>, r: Range<u64>, _l: u64, _out: &mut Write)
-> Result<()> {
ctx.borrow_mut().push(FakeWrite{writer: self.name, range: r});
@ -187,13 +184,13 @@ mod tests {
}
}
pub fn new_slices() -> Slices<FakeWriter, RefCell<Vec<FakeWrite>>> {
pub fn new_slices() -> Slices<FakeSlice, RefCell<Vec<FakeWrite>>> {
let mut s = Slices::new();
s.append(5, FakeWriter{name: "a"});
s.append(13, FakeWriter{name: "b"});
s.append(7, FakeWriter{name: "c"});
s.append(17, FakeWriter{name: "d"});
s.append(19, FakeWriter{name: "e"});
s.append(FakeSlice{end: 5, name: "a"});
s.append(FakeSlice{end: 5+13, name: "b"});
s.append(FakeSlice{end: 5+13+7, name: "c"});
s.append(FakeSlice{end: 5+13+7+17, name: "d"});
s.append(FakeSlice{end: 5+13+7+17+19, name: "e"});
s
}