fix live view

This broke with the media vs wall duration split, part of #34.
This commit is contained in:
Scott Lamb 2020-08-07 10:16:06 -07:00
parent 036e8427e6
commit b9c08b18a4
6 changed files with 104 additions and 99 deletions

View File

@ -521,7 +521,7 @@ pub struct LiveSegment {
/// The pts, relative to the start of the recording, of the start and end of this live segment, /// The pts, relative to the start of the recording, of the start and end of this live segment,
/// in 90kHz units. /// in 90kHz units.
pub off_90k: Range<i32>, pub media_off_90k: Range<i32>,
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]

View File

@ -43,25 +43,24 @@ pub const MAX_RECORDING_WALL_DURATION: i64 = 5 * 60 * TIME_UNITS_PER_SEC;
pub use base::time::Time; pub use base::time::Time;
pub use base::time::Duration; pub use base::time::Duration;
/// Converts from a wall time offset into a recording to a media time offset. /// Converts from a wall time offset into a recording to a media time offset or vice versa.
pub fn wall_to_media(wall_off_90k: i32, wall_duration_90k: i32, media_duration_90k: i32) -> i32 { pub fn rescale(from_off_90k: i32, from_duration_90k: i32, to_duration_90k: i32) -> i32 {
debug_assert!(wall_off_90k <= wall_duration_90k, debug_assert!(from_off_90k <= from_duration_90k,
"wall_off_90k={} wall_duration_90k={} media_duration_90k={}", "from_off_90k={} from_duration_90k={} to_duration_90k={}",
wall_off_90k, wall_duration_90k, media_duration_90k); from_off_90k, from_duration_90k, to_duration_90k);
if wall_duration_90k == 0 { if from_duration_90k == 0 {
return 0; return 0; // avoid a divide by zero.
} }
// The intermediate values here may overflow i32, so use an i64 instead. The max wall // The intermediate values here may overflow i32, so use an i64 instead. The max wall
// time is recording::MAX_RECORDING_WALL_DURATION; the max media duration should be // time is recording::MAX_RECORDING_WALL_DURATION; the max media duration should be
// roughly the same (design limit of 500 ppm correction). The final result should fit // roughly the same (design limit of 500 ppm correction). The final result should fit
// within i32. // within i32.
i32::try_from(i64::from(wall_off_90k) * i32::try_from(i64::from(from_off_90k) *
i64::from(media_duration_90k) / i64::from(to_duration_90k) /
i64::from(wall_duration_90k)) i64::from(from_duration_90k))
.map_err(|_| format!("wall_to_media overflow: {} * {} / {} > i32::max_value()", .map_err(|_| format!("rescale overflow: {} * {} / {} > i32::max_value()",
wall_off_90k, media_duration_90k, from_off_90k, to_duration_90k, from_duration_90k))
wall_duration_90k))
.unwrap() .unwrap()
} }

View File

@ -560,7 +560,7 @@ struct InnerWriter<F: FileWriter> {
/// The pts, relative to the start of this segment and in 90kHz units, up until which live /// The pts, relative to the start of this segment and in 90kHz units, up until which live
/// segments have been sent out. Initially 0. /// segments have been sent out. Initially 0.
completed_live_segment_off_90k: i32, completed_live_segment_media_off_90k: i32,
hasher: blake3::Hasher, hasher: blake3::Hasher,
@ -634,7 +634,7 @@ impl<'a, C: Clocks + Clone, D: DirWriter> Writer<'a, C, D> {
r, r,
e: recording::SampleIndexEncoder::new(), e: recording::SampleIndexEncoder::new(),
id, id,
completed_live_segment_off_90k: 0, completed_live_segment_media_off_90k: 0,
hasher: blake3::Hasher::new(), hasher: blake3::Hasher::new(),
local_start: recording::Time(i64::max_value()), local_start: recording::Time(i64::max_value()),
unindexed_sample: None, unindexed_sample: None,
@ -686,9 +686,9 @@ impl<'a, C: Clocks + Clone, D: DirWriter> Writer<'a, C, D> {
if is_key { if is_key {
self.db.lock().send_live_segment(self.stream_id, db::LiveSegment { self.db.lock().send_live_segment(self.stream_id, db::LiveSegment {
recording: w.id.recording(), recording: w.id.recording(),
off_90k: w.completed_live_segment_off_90k .. d, media_off_90k: w.completed_live_segment_media_off_90k .. d,
}).unwrap(); }).unwrap();
w.completed_live_segment_off_90k = d; w.completed_live_segment_media_off_90k = d;
} }
} }
let mut remaining = pkt; let mut remaining = pkt;
@ -726,7 +726,7 @@ fn clamp(v: i64, min: i64, max: i64) -> i64 {
} }
impl<F: FileWriter> InnerWriter<F> { impl<F: FileWriter> InnerWriter<F> {
/// Returns the total duration of the `RecordingToInsert` (needed for live view path). /// Returns the total media duration of the `RecordingToInsert` (needed for live view path).
fn add_sample(&mut self, duration_90k: i32, bytes: i32, is_key: bool, fn add_sample(&mut self, duration_90k: i32, bytes: i32, is_key: bool,
pkt_local_time: recording::Time) -> Result<i32, Error> { pkt_local_time: recording::Time) -> Result<i32, Error> {
let mut l = self.r.lock(); let mut l = self.r.lock();
@ -772,7 +772,7 @@ impl<F: FileWriter> InnerWriter<F> {
// This always ends a live segment. // This always ends a live segment.
db.lock().send_live_segment(stream_id, db::LiveSegment { db.lock().send_live_segment(stream_id, db::LiveSegment {
recording: self.id.recording(), recording: self.id.recording(),
off_90k: self.completed_live_segment_off_90k .. d, media_off_90k: self.completed_live_segment_media_off_90k .. d,
}).unwrap(); }).unwrap();
let wall_duration; let wall_duration;
{ {

View File

@ -359,17 +359,17 @@ Expected query parameters:
`START_ID[-END_ID][@OPEN_ID][.[REL_START_TIME]-[REL_END_TIME]]`. This `START_ID[-END_ID][@OPEN_ID][.[REL_START_TIME]-[REL_END_TIME]]`. This
specifies *segments* to include. The produced `.mp4` file will be a specifies *segments* to include. The produced `.mp4` file will be a
concatenation of the segments indicated by all `s` parameters. The ids to concatenation of the segments indicated by all `s` parameters. The ids to
retrieve are as returned by the `/recordings` URL. The *open id* (see retrieve are as returned by the `/recordings` URL. The *open id* is
[glossary](glossary.md)) is optional and will be enforced if present; it's optional and will be enforced if present; it's recommended for
recommended for disambiguation when the requested range includes uncommitted disambiguation when the requested range includes uncommitted recordings.
recordings. The optional start and end times are in 90k units of wall time The optional start and end times are in 90k units of wall time and relative
and relative to the start of the first specified id. These can be used to to the start of the first specified id. These can be used to clip the
clip the returned segments. Note they can be used to skip over some ids returned segments. Note they can be used to skip over some ids entirely;
entirely; this is allowed so that the caller doesn't need to know the start this is allowed so that the caller doesn't need to know the start time of
time of each interior id. If there is no key frame at the desired relative each interior id. If there is no key frame at the desired relative start
start time, frames back to the last key frame will be included in the time, frames back to the last key frame will be included in the returned
returned data, and an edit list will instruct the viewer to skip to the data, and an edit list will instruct the viewer to skip to the desired
desired start time. start time.
* `ts` (optional): should be set to `true` to request a subtitle track be * `ts` (optional): should be set to `true` to request a subtitle track be
added with human-readable recording timestamps. added with human-readable recording timestamps.
@ -449,8 +449,8 @@ this fundamental reason Moonfire NVR makes no effort to make multiple-segment
* There's currently no way to generate an initialization segment for more * There's currently no way to generate an initialization segment for more
than one video sample entry, so a `.m4s` that uses more than one video than one video sample entry, so a `.m4s` that uses more than one video
sample entry can't be used. sample entry can't be used.
* The `X-Prev-Media-Duration` and `X-Leading-Duration` headers only describe * The `X-Prev-Media-Duration` and `X-Leading-Media-Duration` headers only
the first segment. describe the first segment.
Timestamp tracks (see the `ts` parameter to `.mp4` URIs) aren't supported Timestamp tracks (see the `ts` parameter to `.mp4` URIs) aren't supported
today. Most likely browser clients will implement timestamp subtitles via today. Most likely browser clients will implement timestamp subtitles via
@ -468,9 +468,9 @@ WebSocket headers as described in [RFC 6455][rfc-6455] and (if authentication
is required) the `s` cookie. is required) the `s` cookie.
The server will send a sequence of binary messages. Each message corresponds The server will send a sequence of binary messages. Each message corresponds
to one run (GOP) of video: a key (IDR) frame and all other frames which depend to one GOP of video: a key (IDR) frame and all other frames which depend on it.
on it. These are encoded as a `.mp4` media segment. The following headers will These are encoded as a `.mp4` media segment. The following headers will be
be included: included:
* `X-Recording-Id`: the open id, a period, and the recording id of the * `X-Recording-Id`: the open id, a period, and the recording id of the
recording these frames belong to. recording these frames belong to.
@ -478,11 +478,10 @@ be included:
of a second) of the start of the recording. Note that if the recording of a second) of the start of the recording. Note that if the recording
is "unanchored" (as described in `GET /api/.../recordings`), the is "unanchored" (as described in `GET /api/.../recordings`), the
recording's start time may change before it is completed. recording's start time may change before it is completed.
* `X-Prev-Duration`: as in `/.../view.m4s`. * `X-Prev-Media-Duration`: as in `/.../view.m4s`.
* `X-Runs`: as in `/.../view.m4s`. * `X-Runs`: as in `/.../view.m4s`.
* `X-Time-Range`: the relative start and end times of these frames within * `X-Media-Time-Range`: the relative media start and end times of these
the recording, in the same format as `REL_START_TIME` and `REL_END_TIME` frames within the recording, as a half-open interval.
above.
Cameras are typically configured to have about one key frame per second, so Cameras are typically configured to have about one key frame per second, so
there will be one part per second when the stream is working. If the stream is there will be one part per second when the stream is working. If the stream is
@ -501,7 +500,8 @@ Example binary message sequence:
Content-Type: video/mp4; codecs="avc1.640028" Content-Type: video/mp4; codecs="avc1.640028"
X-Recording-Id: 42.5680 X-Recording-Id: 42.5680
X-Recording-Start: 130985461191810 X-Recording-Start: 130985461191810
X-Time-Range: 5220058-5400061 X-Prev-Media-Duration: 10000000
X-Media-Time-Range: 5220058-5400061
X-Video-Sample-Entry-Id: 4 X-Video-Sample-Entry-Id: 4
binary mp4 data binary mp4 data
@ -511,7 +511,8 @@ binary mp4 data
Content-Type: video/mp4; codecs="avc1.640028" Content-Type: video/mp4; codecs="avc1.640028"
X-Recording-Id: 42.5681 X-Recording-Id: 42.5681
X-Recording-Start: 130985461191822 X-Recording-Start: 130985461191822
X-Time-Range: 0-180002 X-Prev-Media-Duration: 10180003
X-Media-Time-Range: 0-180002
X-Video-Sample-Entry-Id: 4 X-Video-Sample-Entry-Id: 4
binary mp4 data binary mp4 data
@ -521,13 +522,15 @@ binary mp4 data
Content-Type: video/mp4; codecs="avc1.640028" Content-Type: video/mp4; codecs="avc1.640028"
X-Recording-Id: 42.5681 X-Recording-Id: 42.5681
X-Recording-Start: 130985461191822 X-Recording-Start: 130985461191822
X-Time-Range: 180002-360004 X-Prev-Media-Duration: 10360005
X-Media-Time-Range: 180002-360004
X-Video-Sample-Entry-Id: 4 X-Video-Sample-Entry-Id: 4
binary mp4 data binary mp4 data
``` ```
These segments are exactly the same as ones that can be retrieved at the If the wall duration and media duration for these recordings are equal, these
segments are exactly the same as the ones that can be retrieved at the
following URLs, respectively: following URLs, respectively:
* `/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.m4s?s=5680@42.5220058-5400061` * `/api/cameras/fd20f7a2-9d69-4cb3-94ed-d51a20c3edfe/main/view.m4s?s=5680@42.5220058-5400061`

View File

@ -81,7 +81,7 @@ use bytes::{Buf, BytesMut};
use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use crate::body::{Chunk, BoxedError, wrap_error}; use crate::body::{Chunk, BoxedError, wrap_error};
use db::dir; use db::dir;
use db::recording::{self, TIME_UNITS_PER_SEC, wall_to_media}; use db::recording::{self, TIME_UNITS_PER_SEC, rescale};
use futures::Stream; use futures::Stream;
use futures::stream; use futures::stream;
use http; use http;
@ -347,13 +347,12 @@ struct Segment {
recording_wall_duration_90k: i32, recording_wall_duration_90k: i32,
recording_media_duration_90k: i32, recording_media_duration_90k: i32,
/// The _desired_, _relative_, _wall_ time range covered by this recording. /// The _desired_, _relative_, _media_ time range covered by this recording.
/// * _desired_: as noted in `recording::Segment`, the _actual_ time range may be somewhat /// * _desired_: as noted in `recording::Segment`, the _actual_ time range may be somewhat
/// more if there's no key frame at the desired start. /// more if there's no key frame at the desired start.
/// * _relative_: relative to `recording_start` rather than absolute timestamps. /// * _relative_: relative to `recording_start` rather than absolute timestamps.
/// * _wall_ time: the media time units are in terms of the cameras' clocks. Wall time units /// * _media_ time: as described in design/glossary.md and design/time.md.
/// differ slightly. rel_media_range_90k: Range<i32>,
rel_wall_range_90k: Range<i32>,
/// If generated, the `.mp4`-format sample indexes, accessed only through `get_index`: /// If generated, the `.mp4`-format sample indexes, accessed only through `get_index`:
/// 1. stts: `slice[.. stsz_start]` /// 1. stts: `slice[.. stsz_start]`
@ -382,18 +381,15 @@ impl fmt::Debug for Segment {
unsafe impl Sync for Segment {} unsafe impl Sync for Segment {}
impl Segment { impl Segment {
fn new(db: &db::LockedDatabase, row: &db::ListRecordingsRow, rel_wall_range_90k: Range<i32>, fn new(db: &db::LockedDatabase, row: &db::ListRecordingsRow, rel_media_range_90k: Range<i32>,
first_frame_num: u32) -> Result<Self, Error> { first_frame_num: u32) -> Result<Self, Error> {
let rel_media_range_90k =
wall_to_media(rel_wall_range_90k.start, row.wall_duration_90k, row.media_duration_90k)
..
wall_to_media(rel_wall_range_90k.end, row.wall_duration_90k, row.media_duration_90k);
Ok(Segment { Ok(Segment {
s: recording::Segment::new(db, row, rel_media_range_90k).err_kind(ErrorKind::Unknown)?, s: recording::Segment::new(db, row, rel_media_range_90k.clone())
.err_kind(ErrorKind::Unknown)?,
recording_start: row.start, recording_start: row.start,
recording_wall_duration_90k: row.wall_duration_90k, recording_wall_duration_90k: row.wall_duration_90k,
recording_media_duration_90k: row.media_duration_90k, recording_media_duration_90k: row.media_duration_90k,
rel_wall_range_90k, rel_media_range_90k,
index: UnsafeCell::new(Err(())), index: UnsafeCell::new(Err(())),
index_once: Once::new(), index_once: Once::new(),
first_frame_num, first_frame_num,
@ -401,9 +397,12 @@ impl Segment {
}) })
} }
fn wall(&self, rel_media_90k: i32) -> i32 {
rescale(rel_media_90k, self.recording_media_duration_90k, self.recording_wall_duration_90k)
}
fn media(&self, rel_wall_90k: i32) -> i32 { fn media(&self, rel_wall_90k: i32) -> i32 {
db::recording::wall_to_media(rel_wall_90k, self.recording_wall_duration_90k, rescale(rel_wall_90k, self.recording_wall_duration_90k, self.recording_media_duration_90k)
self.recording_media_duration_90k)
} }
fn get_index<'a, F>(&'a self, db: &db::Database, f: F) -> Result<&'a [u8], Error> fn get_index<'a, F>(&'a self, db: &db::Database, f: F) -> Result<&'a [u8], Error>
@ -467,7 +466,7 @@ impl Segment {
// Doing this after the fact is more efficient than having a condition on every // Doing this after the fact is more efficient than having a condition on every
// iteration. // iteration.
if let Some((last_start, dur)) = last_start_and_dur { if let Some((last_start, dur)) = last_start_and_dur {
let min = cmp::min(self.media(self.rel_wall_range_90k.end) - last_start, dur); let min = cmp::min(self.rel_media_range_90k.end - last_start, dur);
BigEndian::write_u32(&mut stts[8*frame-4 ..], u32::try_from(min).unwrap()); BigEndian::write_u32(&mut stts[8*frame-4 ..], u32::try_from(min).unwrap());
} }
} }
@ -558,9 +557,10 @@ impl Segment {
// One more thing to do in the terminal case: fix up the final frame's duration. // One more thing to do in the terminal case: fix up the final frame's duration.
// Doing this after the fact is more efficient than having a condition on every // Doing this after the fact is more efficient than having a condition on every
// iteration. // iteration.
BigEndian::write_u32(&mut v[p-8 .. p-4], BigEndian::write_u32(
cmp::min(self.media(self.rel_wall_range_90k.end) - r.last_start, &mut v[p-8 .. p-4],
r.last_dur) as u32); u32::try_from(cmp::min(self.rel_media_range_90k.end - r.last_start, r.last_dur))
.unwrap());
} }
Ok(v) Ok(v)
@ -794,10 +794,10 @@ impl FileBuilder {
} }
/// Appends a segment for (a subset of) the given recording. /// Appends a segment for (a subset of) the given recording.
/// `rel_wall_range_90k` is the wall time range within the recording. /// `rel_media_range_90k` is the media time range within the recording.
/// Eg `0 .. row.wall_duration_90k` means the full recording. /// Eg `0 .. row.media_duration_90k` means the full recording.
pub fn append(&mut self, db: &db::LockedDatabase, row: db::ListRecordingsRow, pub fn append(&mut self, db: &db::LockedDatabase, row: db::ListRecordingsRow,
rel_wall_range_90k: Range<i32>) -> Result<(), Error> { rel_media_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() {
bail_t!(InvalidArgument, bail_t!(InvalidArgument,
@ -810,7 +810,7 @@ impl FileBuilder {
self.prev_media_duration_and_cur_runs = row.prev_media_duration_and_runs self.prev_media_duration_and_cur_runs = row.prev_media_duration_and_runs
.map(|(d, r)| (d, r + if row.open_id == 0 { 1 } else { 0 })); .map(|(d, r)| (d, r + if row.open_id == 0 { 1 } else { 0 }));
} }
let s = Segment::new(db, &row, rel_wall_range_90k, self.next_frame_num)?; let s = Segment::new(db, &row, rel_media_range_90k, self.next_frame_num)?;
self.next_frame_num += s.s.frames as u32; self.next_frame_num += s.s.frames as u32;
self.segments.push(s); self.segments.push(s);
@ -848,8 +848,7 @@ impl FileBuilder {
Type::MediaSegment => { etag.update(b":media:"); }, Type::MediaSegment => { etag.update(b":media:"); },
}; };
for s in &mut self.segments { for s in &mut self.segments {
let wd = &s.rel_wall_range_90k; let md = &s.rel_media_range_90k;
let md = s.media(wd.start) .. s.media(wd.end);
// Add the media time for this segment. If edit lists are supported (not media // Add the media time for this segment. If edit lists are supported (not media
// segments), this shouldn't include the portion they skip. // segments), this shouldn't include the portion they skip.
@ -859,8 +858,8 @@ impl FileBuilder {
}; };
self.media_duration_90k += u64::try_from(md.end - start).unwrap(); self.media_duration_90k += u64::try_from(md.end - start).unwrap();
let wall = let wall =
s.recording_start + recording::Duration(i64::from(s.rel_wall_range_90k.start)) .. s.recording_start + recording::Duration(i64::from(s.wall(md.start))) ..
s.recording_start + recording::Duration(i64::from(s.rel_wall_range_90k.end)); s.recording_start + recording::Duration(i64::from(s.wall(md.end)));
max_end = match max_end { max_end = match max_end {
None => Some(wall.end), None => Some(wall.end),
Some(v) => Some(cmp::max(v, wall.end)), Some(v) => Some(cmp::max(v, wall.end)),
@ -881,8 +880,8 @@ impl FileBuilder {
cursor.write_i64::<BigEndian>(s.s.id.0).err_kind(ErrorKind::Internal)?; cursor.write_i64::<BigEndian>(s.s.id.0).err_kind(ErrorKind::Internal)?;
cursor.write_i64::<BigEndian>(s.recording_start.0).err_kind(ErrorKind::Internal)?; cursor.write_i64::<BigEndian>(s.recording_start.0).err_kind(ErrorKind::Internal)?;
cursor.write_u32::<BigEndian>(s.s.open_id).err_kind(ErrorKind::Internal)?; cursor.write_u32::<BigEndian>(s.s.open_id).err_kind(ErrorKind::Internal)?;
cursor.write_i32::<BigEndian>(wd.start).err_kind(ErrorKind::Internal)?; cursor.write_i32::<BigEndian>(md.start).err_kind(ErrorKind::Internal)?;
cursor.write_i32::<BigEndian>(wd.end).err_kind(ErrorKind::Internal)?; cursor.write_i32::<BigEndian>(md.end).err_kind(ErrorKind::Internal)?;
etag.update(cursor.into_inner()); etag.update(cursor.into_inner());
} }
let max_end = match max_end { let max_end = match max_end {
@ -1162,7 +1161,7 @@ impl FileBuilder {
// key frame. This relationship should hold true: // key frame. This relationship should hold true:
// actual start <= desired start <= desired end // actual start <= desired start <= desired end
let actual_start_90k = s.s.actual_start_90k(); let actual_start_90k = s.s.actual_start_90k();
let md = s.media(s.rel_wall_range_90k.start) .. s.media(s.rel_wall_range_90k.end); let md = &s.rel_media_range_90k;
let skip = md.start - actual_start_90k; let skip = md.start - actual_start_90k;
let keep = md.end - md.start; let keep = md.end - md.start;
if skip < 0 || keep < 0 { if skip < 0 || keep < 0 {
@ -1325,13 +1324,12 @@ impl FileBuilder {
for s in &self.segments { for s in &self.segments {
// Note desired media range = actual media range for the subtitle track. // Note desired media range = actual media range for the subtitle track.
// We still need to consider media time vs wall time. // We still need to consider media time vs wall time.
let wr = &s.rel_wall_range_90k; let mr = &s.rel_media_range_90k;
let start = s.recording_start + recording::Duration(i64::from(wr.start)); let start = s.recording_start + recording::Duration(i64::from(s.wall(mr.start)));
let end = s.recording_start + recording::Duration(i64::from(wr.end)); let end = s.recording_start + recording::Duration(i64::from(s.wall(mr.end)));
let start_next_sec = recording::Time( let start_next_sec = recording::Time(
start.0 + TIME_UNITS_PER_SEC - (start.0 % TIME_UNITS_PER_SEC)); start.0 + TIME_UNITS_PER_SEC - (start.0 % TIME_UNITS_PER_SEC));
let mr = s.media(wr.start) .. s.media(wr.end);
if end <= start_next_sec { if end <= start_next_sec {
// Segment doesn't last past the next second. Just write one entry. // Segment doesn't last past the next second. Just write one entry.
entry_count += 1; entry_count += 1;
@ -1346,7 +1344,7 @@ impl FileBuilder {
self.body.append_u32(u32::try_from(media_pos - mr.start).unwrap()); self.body.append_u32(u32::try_from(media_pos - mr.start).unwrap());
// Then there are zero or more "interior" subtitles, one second each. That's // Then there are zero or more "interior" subtitles, one second each. That's
// one second converted from wall to media duration. wall_to_media rounds down, // one second converted from wall to media duration. rescale rounds down,
// and these errors accumulate, so the final subtitle can be too early by as // and these errors accumulate, so the final subtitle can be too early by as
// much as (MAX_RECORDING_WALL_DURATION/TIME_UNITS_PER_SEC) time units, or // much as (MAX_RECORDING_WALL_DURATION/TIME_UNITS_PER_SEC) time units, or
// roughly 3 ms. We could avoid that by writing a separate entry for each // roughly 3 ms. We could avoid that by writing a separate entry for each
@ -1563,11 +1561,12 @@ impl FileInner {
fn get_subtitle_sample_data(&self, i: usize, r: Range<u64>, l: u64) -> Result<Chunk, Error> { fn get_subtitle_sample_data(&self, i: usize, r: Range<u64>, l: u64) -> Result<Chunk, Error> {
let s = &self.segments[i]; let s = &self.segments[i];
let d = &s.rel_wall_range_90k; let md = &s.rel_media_range_90k;
let wd = s.wall(md.start) .. s.wall(md.end);
let start_sec = let start_sec =
(s.recording_start + recording::Duration(i64::from(d.start))).unix_seconds(); (s.recording_start + recording::Duration(i64::from(wd.start))).unix_seconds();
let end_sec = let end_sec =
(s.recording_start + recording::Duration(i64::from(d.end) + TIME_UNITS_PER_SEC - 1)) (s.recording_start + recording::Duration(i64::from(wd.end) + TIME_UNITS_PER_SEC - 1))
.unix_seconds(); .unix_seconds();
let l = usize::try_from(l).unwrap(); let l = usize::try_from(l).unwrap();
let mut v = Vec::with_capacity(l); let mut v = Vec::with_capacity(l);
@ -1642,7 +1641,7 @@ impl http_serve::Entity for File {
HeaderValue::try_from(r.to_string()).expect("ints are valid headers")); HeaderValue::try_from(r.to_string()).expect("ints are valid headers"));
} }
if let Some(s) = self.0.segments.first() { if let Some(s) = self.0.segments.first() {
let skip = s.media(s.rel_wall_range_90k.start) - s.s.actual_start_90k(); let skip = s.rel_media_range_90k.start - s.s.actual_start_90k();
if skip > 0 { if skip > 0 {
hdrs.insert( hdrs.insert(
"X-Leading-Media-Duration", "X-Leading-Media-Duration",

View File

@ -448,7 +448,7 @@ impl Service {
db.list_recordings_by_id(stream_id, live.recording .. live.recording+1, &mut |r| { db.list_recordings_by_id(stream_id, live.recording .. live.recording+1, &mut |r| {
rows += 1; rows += 1;
row = Some(r); row = Some(r);
builder.append(&db, r, live.off_90k.clone())?; builder.append(&db, r, live.media_off_90k.clone())?;
Ok(()) Ok(())
})?; })?;
if rows != 1 { if rows != 1 {
@ -466,7 +466,7 @@ impl Service {
"Content-Type: {}\r\n\ "Content-Type: {}\r\n\
X-Recording-Start: {}\r\n\ X-Recording-Start: {}\r\n\
X-Recording-Id: {}.{}\r\n\ X-Recording-Id: {}.{}\r\n\
X-Time-Range: {}-{}\r\n\ X-Media-Time-Range: {}-{}\r\n\
X-Prev-Media-Duration: {}\r\n\ X-Prev-Media-Duration: {}\r\n\
X-Runs: {}\r\n\ X-Runs: {}\r\n\
X-Video-Sample-Entry-Id: {}\r\n\r\n", X-Video-Sample-Entry-Id: {}\r\n\r\n",
@ -474,8 +474,8 @@ impl Service {
row.start.0, row.start.0,
open_id, open_id,
live.recording, live.recording,
live.off_90k.start, live.media_off_90k.start,
live.off_90k.end, live.media_off_90k.end,
prev_media_duration.0, prev_media_duration.0,
prev_runs + if row.run_offset == 0 { 1 } else { 0 }, prev_runs + if row.run_offset == 0 { 1 } else { 0 },
&row.video_sample_entry_id); &row.video_sample_entry_id);
@ -737,23 +737,27 @@ impl Service {
// Add a segment for the relevant part of the recording, if any. // Add a segment for the relevant part of the recording, if any.
// Note all calculations here are in wall times / wall durations. // Note all calculations here are in wall times / wall durations.
let end_time = s.end_time.unwrap_or(i64::max_value()); let end_time = s.end_time.unwrap_or(i64::max_value());
let d = i64::from(r.wall_duration_90k); let wd = i64::from(r.wall_duration_90k);
if s.start_time <= cur_off + d && cur_off < end_time { if s.start_time <= cur_off + wd && cur_off < end_time {
let start = cmp::max(0, s.start_time - cur_off); let start = cmp::max(0, s.start_time - cur_off);
let end = cmp::min(d, end_time - cur_off); let end = cmp::min(wd, end_time - cur_off);
let times = i32::try_from(start).unwrap() .. let wr = i32::try_from(start).unwrap() ..
i32::try_from(end).unwrap(); i32::try_from(end).unwrap();
debug!("...appending recording {} with times {:?} \ debug!("...appending recording {} with wall duration {:?} \
(out of dur {})", r.id, times, d); (out of total {})", r.id, wr, wd);
if start_time_for_filename.is_none() { if start_time_for_filename.is_none() {
start_time_for_filename = start_time_for_filename =
Some(r.start + recording::Duration(start)); Some(r.start + recording::Duration(start));
} }
builder.append(&db, r, times)?; use recording::rescale;
let mr =
rescale(wr.start, r.wall_duration_90k, r.media_duration_90k) ..
rescale(wr.end, r.wall_duration_90k, r.media_duration_90k);
builder.append(&db, r, mr)?;
} else { } else {
debug!("...skipping recording {} dur {}", r.id, d); debug!("...skipping recording {} wall dur {}", r.id, wd);
} }
cur_off += d; cur_off += wd;
Ok(()) Ok(())
}).map_err(internal_server_err)?; }).map_err(internal_server_err)?;