Skip to content

Commit

Permalink
remove minimum timestamp again
Browse files Browse the repository at this point in the history
  • Loading branch information
Wumpf committed Nov 13, 2024
1 parent af599d3 commit 4fabd1b
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 119 deletions.
30 changes: 6 additions & 24 deletions crates/store/re_video/src/decode/webcodecs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ impl std::ops::Deref for WebVideoFrame {
pub struct WebVideoDecoder {
video_config: Config,
timescale: Timescale,
minimum_presentation_timestamp: Time,
decoder: web_sys::VideoDecoder,
hw_acceleration: DecodeHardwareAcceleration,
on_output: Arc<OutputCallback>,
Expand Down Expand Up @@ -108,16 +107,11 @@ impl WebVideoDecoder {
on_output: impl Fn(Result<Frame>) + Send + Sync + 'static,
) -> Result<Self, Error> {
let on_output = Arc::new(on_output);
let decoder = init_video_decoder(
on_output.clone(),
video.timescale,
video.samples_statistics.minimum_presentation_timestamp,
)?;
let decoder = init_video_decoder(on_output.clone(), video.timescale)?;

Ok(Self {
video_config: video.config.clone(),
timescale: video.timescale,
minimum_presentation_timestamp: video.samples_statistics.minimum_presentation_timestamp,
decoder,
hw_acceleration,
on_output,
Expand All @@ -138,7 +132,7 @@ impl AsyncDecoder for WebVideoDecoder {
&data,
video_chunk
.presentation_timestamp
.into_micros_since_start(self.timescale, self.minimum_presentation_timestamp),
.into_micros(self.timescale),
type_,
);

Expand All @@ -162,11 +156,7 @@ impl AsyncDecoder for WebVideoDecoder {
// At least on Firefox, it can happen that reset on a previous error fails.
// In that case, start over completely and try again!
re_log::debug!("Video decoder reset failed, recreating decoder.");
self.decoder = init_video_decoder(
self.on_output.clone(),
self.timescale,
self.minimum_presentation_timestamp,
)?;
self.decoder = init_video_decoder(self.on_output.clone(), self.timescale)?;
};

self.decoder
Expand All @@ -183,23 +173,15 @@ impl AsyncDecoder for WebVideoDecoder {
fn init_video_decoder(
on_output_callback: Arc<OutputCallback>,
timescale: Timescale,
minimum_presentation_timestamp: Time,
) -> Result<web_sys::VideoDecoder, Error> {
let on_output = {
let on_output = on_output_callback.clone();
Closure::wrap(Box::new(move |frame: web_sys::VideoFrame| {
// We assume that the timestamp returned by the decoder is in time since start,
// and does not represent demuxed "raw" presentation timestamps.
let presentation_timestamp = Time::from_micros_since_start(
frame.timestamp().unwrap_or(0.0),
timescale,
minimum_presentation_timestamp,
);
let duration = Time::from_micros_since_start(
frame.duration().unwrap_or(0.0),
timescale,
minimum_presentation_timestamp,
);
let presentation_timestamp =
Time::from_micros(frame.timestamp().unwrap_or(0.0), timescale);
let duration = Time::from_micros(frame.duration().unwrap_or(0.0), timescale);

on_output(Ok(Frame {
content: WebVideoFrame(frame),
Expand Down
30 changes: 2 additions & 28 deletions crates/store/re_video/src/demux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,6 @@ pub struct VideoData {
/// Meta informationa about the video samples.
#[derive(Clone, Debug)]
pub struct SamplesStatistics {
/// The smallest presentation timestamp observed in this video.
///
/// This is typically 0, but in the presence of B-frames, it may be non-zero.
/// In fact, many formats don't require this to be zero, but video players typically
/// normalize the shown time to start at zero.
/// Note that timestamps in the [`Sample`]s are *not* automatically adjusted with this value.
// This is roughly equivalent to FFmpeg's internal `min_corrected_pts`
// https://github.com/FFmpeg/FFmpeg/blob/4047b887fc44b110bccb1da09bcb79d6e454b88b/libavformat/isom.h#L202
// (unlike us, this handles a bunch more edge cases but it fulfills the same role)
// To learn more about this I recommend reading the patch that introduced this in FFmpeg:
// https://patchwork.ffmpeg.org/project/ffmpeg/patch/[email protected]/#12592
pub minimum_presentation_timestamp: Time,

/// Whether all decode timestamps are equal to presentation timestamps.
///
/// If true, the video typically has no B-frames as those require frame reordering.
Expand All @@ -103,11 +90,6 @@ impl SamplesStatistics {
pub fn new(samples: &[Sample]) -> Self {
re_tracing::profile_function!();

let minimum_presentation_timestamp = samples
.iter()
.map(|s| s.presentation_timestamp)
.min()
.unwrap_or_default();
let dts_always_equal_pts = samples
.iter()
.all(|s| s.decode_timestamp == s.presentation_timestamp);
Expand All @@ -128,7 +110,6 @@ impl SamplesStatistics {
});

Self {
minimum_presentation_timestamp,
dts_always_equal_pts,
has_sample_highest_pts_so_far,
}
Expand Down Expand Up @@ -301,8 +282,6 @@ impl VideoData {
/// Determines the video timestamps of all frames inside a video, returning raw time values.
///
/// Returned timestamps are in nanoseconds since start and are guaranteed to be monotonically increasing.
/// These are *not* necessarily the same as the presentation timestamps, as the returned timestamps are
/// normalized respect to the start of the video, see [`SamplesStatistics::minimum_presentation_timestamp`].
pub fn frame_timestamps_ns(&self) -> impl Iterator<Item = i64> + '_ {
// Segments are guaranteed to be sorted among each other, but within a segment,
// presentation timestamps may not be sorted since this is sorted by decode timestamps.
Expand All @@ -311,12 +290,7 @@ impl VideoData {
.iter()
.map(|sample| sample.presentation_timestamp)
.sorted()
.map(|pts| {
pts.into_nanos_since_start(
self.timescale,
self.samples_statistics.minimum_presentation_timestamp,
)
})
.map(|pts| pts.into_nanos(self.timescale))
})
}

Expand Down Expand Up @@ -637,6 +611,7 @@ mod tests {
#[test]
fn test_latest_sample_index_at_presentation_timestamp() {
// This is a snippet of real world data!
// TODO:
let pts = [
512, 1536, 1024, 768, 1280, 2560, 2048, 1792, 2304, 3584, 3072, 2816, 3328, 4608, 4096,
3840, 4352, 5376, 4864, 5120, 6400, 5888, 5632, 6144, 7424, 6912, 6656, 7168, 8448,
Expand Down Expand Up @@ -671,7 +646,6 @@ mod tests {
.collect::<Vec<_>>();

let sample_statistics = SamplesStatistics::new(&samples);
assert_eq!(sample_statistics.minimum_presentation_timestamp, Time(512));
assert!(!sample_statistics.dts_always_equal_pts);

// Test queries on the samples.
Expand Down
51 changes: 17 additions & 34 deletions crates/store/re_video/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,67 +28,50 @@ impl Time {
Self(v)
}

/// `time_base` specifies the
#[inline]
pub fn from_secs_since_start(
secs_since_start: f64,
timescale: Timescale,
start_time: Self,
) -> Self {
Self((secs_since_start * timescale.0 as f64).round() as i64 + start_time.0)
pub fn from_secs(secs_since_start: f64, timescale: Timescale) -> Self {
Self((secs_since_start * timescale.0 as f64).round() as i64)
}

#[inline]
pub fn from_millis_since_start(
millis_since_start: f64,
timescale: Timescale,
start_time: Self,
) -> Self {
Self::from_secs_since_start(millis_since_start / 1e3, timescale, start_time)
pub fn from_millis(millis_since_start: f64, timescale: Timescale) -> Self {
Self::from_secs(millis_since_start / 1e3, timescale)
}

#[inline]
pub fn from_micros_since_start(
micros_since_start: f64,
timescale: Timescale,
start_time: Self,
) -> Self {
Self::from_secs_since_start(micros_since_start / 1e6, timescale, start_time)
pub fn from_micros(micros_since_start: f64, timescale: Timescale) -> Self {
Self::from_secs(micros_since_start / 1e6, timescale)
}

#[inline]
pub fn from_nanos_since_start(
nanos_since_start: i64,
timescale: Timescale,
start_time: Self,
) -> Self {
Self::from_secs_since_start(nanos_since_start as f64 / 1e9, timescale, start_time)
pub fn from_nanos(nanos_since_start: i64, timescale: Timescale) -> Self {
Self::from_secs(nanos_since_start as f64 / 1e9, timescale)
}

/// Convert to a duration
#[inline]
pub fn duration(self, timescale: Timescale) -> std::time::Duration {
std::time::Duration::from_nanos(self.into_nanos_since_start(timescale, Self(0)) as _)
std::time::Duration::from_nanos(self.into_nanos(timescale) as _)
}

#[inline]
pub fn into_secs_since_start(self, timescale: Timescale, start_time: Self) -> f64 {
(self.0 - start_time.0) as f64 / timescale.0 as f64
pub fn into_secs(self, timescale: Timescale) -> f64 {
self.0 as f64 / timescale.0 as f64
}

#[inline]
pub fn into_millis_since_start(self, timescale: Timescale, start_time: Self) -> f64 {
self.into_secs_since_start(timescale, start_time) * 1e3
pub fn into_millis(self, timescale: Timescale) -> f64 {
self.into_secs(timescale) * 1e3
}

#[inline]
pub fn into_micros_since_start(self, timescale: Timescale, start_time: Self) -> f64 {
self.into_secs_since_start(timescale, start_time) * 1e6
pub fn into_micros(self, timescale: Timescale) -> f64 {
self.into_secs(timescale) * 1e6
}

#[inline]
pub fn into_nanos_since_start(self, timescale: Timescale, start_time: Self) -> i64 {
(self.into_secs_since_start(timescale, start_time) * 1e9).round() as i64
pub fn into_nanos(self, timescale: Timescale) -> i64 {
(self.into_secs(timescale) * 1e9).round() as i64
}
}

Expand Down
33 changes: 9 additions & 24 deletions crates/viewer/re_data_ui/src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use re_renderer::{
};
use re_types::components::VideoTimestamp;
use re_ui::{list_item::PropertyContent, DesignTokens, UiExt};
use re_video::{decode::FrameInfo, demux::SamplesStatistics, VideoData};
use re_video::{decode::FrameInfo, VideoData};
use re_viewer_context::UiLayout;

pub fn video_result_ui(
Expand Down Expand Up @@ -113,7 +113,9 @@ fn video_data_ui(ui: &mut egui::Ui, ui_layout: UiLayout, video_data: &VideoData)
.value_text(video_data.gops.len().to_string()),
)
.on_hover_text("The total number of Group of Pictures (GOPs) in the video.");
samples_statistics_ui(ui, &video_data.samples_statistics);
ui.list_item_flat_noninteractive(
PropertyContent::new("All PTS equal DTS").value_bool(video_data.samples_statistics.dts_always_equal_pts)
).on_hover_text("Whether all decode timestamps are equal to presentation timestamps. If true, the video typically has no B-frames.");
});

ui.list_item_collapsible_noninteractive_label("Video samples", false, |ui| {
Expand Down Expand Up @@ -214,10 +216,7 @@ fn timestamp_ui(ui: &mut egui::Ui, video_data: &VideoData, timestamp: re_video::
ui.monospace(re_format::format_int(timestamp.0))
.on_hover_ui(|ui| {
ui.monospace(re_format::format_timestamp_seconds(
timestamp.into_secs_since_start(
video_data.timescale,
video_data.samples_statistics.minimum_presentation_timestamp,
),
timestamp.into_secs(video_data.timescale),
));
});
}
Expand Down Expand Up @@ -320,18 +319,6 @@ pub fn show_decoded_frame_info(
}
}

fn samples_statistics_ui(ui: &mut egui::Ui, samples_statistics: &SamplesStatistics) {
ui.list_item_flat_noninteractive(
PropertyContent::new("Minimum PTS").value_text(samples_statistics.minimum_presentation_timestamp.0.to_string())
).on_hover_text("The smallest presentation timestamp (PTS) observed in this video.\n\
A non-zero value indicates that there are B-frames in the video.\n\
Rerun will place the 0:00:00 time at this timestamp.");
ui.list_item_flat_noninteractive(
// `value_bool` doesn't look great for static values.
PropertyContent::new("All PTS equal DTS").value_text(samples_statistics.dts_always_equal_pts.to_string())
).on_hover_text("Whether all decode timestamps are equal to presentation timestamps. If true, the video typically has no B-frames.");
}

fn frame_info_ui(ui: &mut egui::Ui, frame_info: &FrameInfo, video_data: &re_video::VideoData) {
let FrameInfo {
is_sync,
Expand All @@ -352,13 +339,11 @@ fn frame_info_ui(ui: &mut egui::Ui, frame_info: &FrameInfo, video_data: &re_vide
let presentation_time_range = presentation_timestamp..presentation_timestamp + duration;
ui.list_item_flat_noninteractive(PropertyContent::new("Time range").value_text(format!(
"{} - {}",
re_format::format_timestamp_seconds(presentation_time_range.start.into_secs_since_start(
video_data.timescale,
video_data.samples_statistics.minimum_presentation_timestamp
re_format::format_timestamp_seconds(presentation_time_range.start.into_secs(
video_data.timescale
)),
re_format::format_timestamp_seconds(presentation_time_range.end.into_secs_since_start(
video_data.timescale,
video_data.samples_statistics.minimum_presentation_timestamp
re_format::format_timestamp_seconds(presentation_time_range.end.into_secs(
video_data.timescale
)),
)))
.on_hover_text("Time range in which this frame is valid.");
Expand Down
2 changes: 0 additions & 2 deletions crates/viewer/re_renderer/src/video/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ impl Video {
/// empty.
///
/// The time is specified in seconds since the start of the video.
/// This may not be equal to the presentation timestamp as it may have an offset applied,
/// see [`re_video::SamplesStatistics::minimum_presentation_timestamp`].
pub fn frame_at(
&self,
render_context: &RenderContext,
Expand Down
10 changes: 3 additions & 7 deletions crates/viewer/re_renderer/src/video/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,9 @@ impl VideoPlayer {
if time_since_video_start_in_seconds < 0.0 {
return Err(VideoPlayerError::NegativeTimestamp);
}
let presentation_timestamp = Time::from_secs_since_start(
time_since_video_start_in_seconds,
self.data.timescale,
self.data.samples_statistics.minimum_presentation_timestamp,
);
let presentation_timestamp = presentation_timestamp
.min(self.data.duration + self.data.samples_statistics.minimum_presentation_timestamp); // Don't seek past the end of the video.
let presentation_timestamp =
Time::from_secs(time_since_video_start_in_seconds, self.data.timescale);
let presentation_timestamp = presentation_timestamp.min(self.data.duration); // Don't seek past the end of the video.

let error_on_last_frame_at = self.last_error.is_some();
self.frame_at_internal(render_ctx, presentation_timestamp, video_data)?;
Expand Down

0 comments on commit 4fabd1b

Please sign in to comment.