Implement video versions of RTCInboundRtpStreamStats.jitterBuffer{Target,Minimum}Delay
* https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbuffertargetdelay
* https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferminimumdelay
Tested: https://jsfiddle.net/pfgzj0yo/17/
Bug: webrtc:14244
Change-Id: I3d949ba63c8339b3881f5d00356559d5789d283d
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/304404
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40157}
diff --git a/call/video_receive_stream.cc b/call/video_receive_stream.cc
index 0e0ad44..3e2a513 100644
--- a/call/video_receive_stream.cc
+++ b/call/video_receive_stream.cc
@@ -80,7 +80,11 @@
ss << "jitter_delay_ms: " << jitter_buffer_ms << ", ";
ss << "totalAssemblyTime: " << total_assembly_time.seconds<double>() << ", ";
ss << "jitterBufferDelay: " << jitter_buffer_delay.seconds<double>() << ", ";
+ ss << "jitterBufferTargetDelay: "
+ << jitter_buffer_target_delay.seconds<double>() << ", ";
ss << "jitterBufferEmittedCount: " << jitter_buffer_emitted_count << ", ";
+ ss << "jitterBufferMinimumDelay: "
+ << jitter_buffer_minimum_delay.seconds<double>();
ss << "totalDecodeTime: " << total_decode_time.seconds<double>() << ", ";
ss << "totalProcessingDelay: " << total_processing_delay.seconds<double>()
<< ", ";
diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h
index b25a126..48a1ad0 100644
--- a/call/video_receive_stream.h
+++ b/call/video_receive_stream.h
@@ -98,8 +98,12 @@
int jitter_buffer_ms = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay
TimeDelta jitter_buffer_delay = TimeDelta::Zero();
+ // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbuffertargetdelay
+ TimeDelta jitter_buffer_target_delay = TimeDelta::Zero();
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferemittedcount
uint64_t jitter_buffer_emitted_count = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferminimumdelay
+ TimeDelta jitter_buffer_minimum_delay = TimeDelta::Zero();
int min_playout_delay_ms = 0;
int render_delay_ms = 10;
int64_t interframe_delay_max_ms = -1;
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index dbcb30c..02dc693 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -442,15 +442,11 @@
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay
double jitter_buffer_delay_seconds = 0.0;
// Target delay for the jitter buffer (cumulative).
- // TODO(crbug.com/webrtc/14244): This metric is only implemented for
- // audio, it should be implemented for video as well.
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbuffertargetdelay
- absl::optional<double> jitter_buffer_target_delay_seconds;
+ double jitter_buffer_target_delay_seconds = 0.0;
// Minimum obtainable delay for the jitter buffer (cumulative).
- // TODO(crbug.com/webrtc/14244): This metric is only implemented for
- // audio, it should be implemented for video as well.
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferminimumdelay
- absl::optional<double> jitter_buffer_minimum_delay_seconds;
+ double jitter_buffer_minimum_delay_seconds = 0.0;
// Number of observations for cumulative jitter latency.
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferemittedcount
uint64_t jitter_buffer_emitted_count = 0;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index dd384b2..3828f00 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -3394,7 +3394,11 @@
info.jitter_buffer_ms = stats.jitter_buffer_ms;
info.jitter_buffer_delay_seconds =
stats.jitter_buffer_delay.seconds<double>();
+ info.jitter_buffer_target_delay_seconds =
+ stats.jitter_buffer_target_delay.seconds<double>();
info.jitter_buffer_emitted_count = stats.jitter_buffer_emitted_count;
+ info.jitter_buffer_minimum_delay_seconds =
+ stats.jitter_buffer_minimum_delay.seconds<double>();
info.min_playout_delay_ms = stats.min_playout_delay_ms;
info.render_delay_ms = stats.render_delay_ms;
info.frames_received =
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index e63199d..1c613a0 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -6557,7 +6557,9 @@
stats.target_delay_ms = 5;
stats.jitter_buffer_ms = 6;
stats.jitter_buffer_delay = TimeDelta::Seconds(60);
+ stats.jitter_buffer_target_delay = TimeDelta::Seconds(55);
stats.jitter_buffer_emitted_count = 6;
+ stats.jitter_buffer_minimum_delay = TimeDelta::Seconds(50);
stats.min_playout_delay_ms = 7;
stats.render_delay_ms = 8;
stats.width = 9;
@@ -6591,8 +6593,12 @@
EXPECT_EQ(stats.jitter_buffer_ms, receive_info.receivers[0].jitter_buffer_ms);
EXPECT_EQ(stats.jitter_buffer_delay.seconds<double>(),
receive_info.receivers[0].jitter_buffer_delay_seconds);
+ EXPECT_EQ(stats.jitter_buffer_target_delay.seconds<double>(),
+ receive_info.receivers[0].jitter_buffer_target_delay_seconds);
EXPECT_EQ(stats.jitter_buffer_emitted_count,
receive_info.receivers[0].jitter_buffer_emitted_count);
+ EXPECT_EQ(stats.jitter_buffer_minimum_delay.seconds<double>(),
+ receive_info.receivers[0].jitter_buffer_minimum_delay_seconds);
EXPECT_EQ(stats.min_playout_delay_ms,
receive_info.receivers[0].min_playout_delay_ms);
EXPECT_EQ(stats.render_delay_ms, receive_info.receivers[0].render_delay_ms);
diff --git a/modules/video_coding/timing/timing.cc b/modules/video_coding/timing/timing.cc
index 1035d6f..735f632 100644
--- a/modules/video_coding/timing/timing.cc
+++ b/modules/video_coding/timing/timing.cc
@@ -252,6 +252,13 @@
jitter_delay_ + EstimatedMaxDecodeTime() + render_delay_);
}
+// TODO(crbug.com/webrtc/15197): Centralize delay arithmetic.
+TimeDelta VCMTiming::StatsTargetDelayInternal() const {
+ TimeDelta stats_target_delay =
+ TargetDelayInternal() - (EstimatedMaxDecodeTime() + render_delay_);
+ return std::max(TimeDelta::Zero(), stats_target_delay);
+}
+
VideoFrame::RenderParameters VCMTiming::RenderParameters() const {
MutexLock lock(&mutex_);
return {.use_low_latency_rendering = UseLowLatencyRendering(),
@@ -271,12 +278,12 @@
MutexLock lock(&mutex_);
return VideoDelayTimings{
.num_decoded_frames = num_decoded_frames_,
- .jitter_delay = jitter_delay_,
+ .minimum_delay = jitter_delay_,
.estimated_max_decode_time = EstimatedMaxDecodeTime(),
.render_delay = render_delay_,
.min_playout_delay = min_playout_delay_,
.max_playout_delay = max_playout_delay_,
- .target_delay = TargetDelayInternal(),
+ .target_delay = StatsTargetDelayInternal(),
.current_delay = current_delay_};
}
diff --git a/modules/video_coding/timing/timing.h b/modules/video_coding/timing/timing.h
index dbac40d..9e7fb87 100644
--- a/modules/video_coding/timing/timing.h
+++ b/modules/video_coding/timing/timing.h
@@ -31,13 +31,15 @@
public:
struct VideoDelayTimings {
size_t num_decoded_frames;
- // Delay added to smooth out frame delay variation ("jitter") caused by
- // the network.
- TimeDelta jitter_delay;
+ // Pre-decode delay added to smooth out frame delay variation ("jitter")
+ // caused by the network. The target delay will be no smaller than this
+ // delay, thus it is called `minimum_delay`.
+ TimeDelta minimum_delay;
// Estimated time needed to decode a video frame. Obtained as the 95th
// percentile decode time over a recent time window.
TimeDelta estimated_max_decode_time;
- // Estimated time needed to render a frame. Set to a constant.
+ // Post-decode delay added to smooth out frame delay variation caused by
+ // decoding and rendering. Set to a constant.
TimeDelta render_delay;
// Minimum total delay used when determining render time for a frame.
// Obtained from API, `playout-delay` RTP header extension, or A/V sync.
@@ -45,9 +47,9 @@
// Maximum total delay used when determining render time for a frame.
// Obtained from `playout-delay` RTP header extension.
TimeDelta max_playout_delay;
- // Target delay. Obtained from all the elements above.
+ // Target total delay. Obtained from all the elements above.
TimeDelta target_delay;
- // Current delay. Obtained by smoothing out the target delay.
+ // Current total delay. Obtained by smoothening the `target_delay`.
TimeDelta current_delay;
};
@@ -133,6 +135,8 @@
Timestamp RenderTimeInternal(uint32_t frame_timestamp, Timestamp now) const
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
TimeDelta TargetDelayInternal() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
+ TimeDelta StatsTargetDelayInternal() const
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
bool UseLowLatencyRendering() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
mutable Mutex mutex_;
diff --git a/modules/video_coding/timing/timing_unittest.cc b/modules/video_coding/timing/timing_unittest.cc
index 8633c0d..4ba8c4d 100644
--- a/modules/video_coding/timing/timing_unittest.cc
+++ b/modules/video_coding/timing/timing_unittest.cc
@@ -13,6 +13,7 @@
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "system_wrappers/include/clock.h"
+#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
@@ -22,9 +23,54 @@
constexpr Frequency k25Fps = Frequency::Hertz(25);
constexpr Frequency k90kHz = Frequency::KiloHertz(90);
+MATCHER(HasConsistentVideoDelayTimings, "") {
+ // Delays should be non-negative.
+ bool p1 = arg.minimum_delay >= TimeDelta::Zero();
+ bool p2 = arg.estimated_max_decode_time >= TimeDelta::Zero();
+ bool p3 = arg.render_delay >= TimeDelta::Zero();
+ bool p4 = arg.min_playout_delay >= TimeDelta::Zero();
+ bool p5 = arg.max_playout_delay >= TimeDelta::Zero();
+ bool p6 = arg.target_delay >= TimeDelta::Zero();
+ bool p7 = arg.current_delay >= TimeDelta::Zero();
+ *result_listener << "\np: " << p1 << p2 << p3 << p4 << p5 << p6 << p7;
+ bool p = p1 && p2 && p3 && p4 && p5 && p6 && p7;
+
+ // Delays should be internally consistent.
+ bool m1 = arg.minimum_delay <= arg.target_delay;
+ if (!m1) {
+ *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", "
+ << "target_delay: " << arg.target_delay << "\n";
+ }
+ bool m2 = arg.minimum_delay <= arg.current_delay;
+ if (!m2) {
+ *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", "
+ << "current_delay: " << arg.current_delay;
+ }
+ bool m3 = arg.target_delay >= arg.min_playout_delay;
+ if (!m3) {
+ *result_listener << "\ntarget_delay: " << arg.target_delay << ", "
+ << "min_playout_delay: " << arg.min_playout_delay << "\n";
+ }
+ // TODO(crbug.com/webrtc/15197): Uncomment when this is guaranteed.
+ // bool m4 = arg.target_delay <= arg.max_playout_delay;
+ bool m5 = arg.current_delay >= arg.min_playout_delay;
+ if (!m5) {
+ *result_listener << "\ncurrent_delay: " << arg.current_delay << ", "
+ << "min_playout_delay: " << arg.min_playout_delay << "\n";
+ }
+ bool m6 = arg.current_delay <= arg.max_playout_delay;
+ if (!m6) {
+ *result_listener << "\ncurrent_delay: " << arg.current_delay << ", "
+ << "max_playout_delay: " << arg.max_playout_delay << "\n";
+ }
+ bool m = m1 && m2 && m3 && m5 && m6;
+
+ return p && m;
+}
+
} // namespace
-TEST(ReceiverTimingTest, JitterDelay) {
+TEST(VCMTimingTest, JitterDelay) {
test::ScopedKeyValueConfig field_trials;
SimulatedClock clock(0);
VCMTiming timing(&clock, field_trials);
@@ -115,9 +161,11 @@
clock.AdvanceTimeMilliseconds(5000);
timestamp += 5 * 90000;
timing.UpdateCurrentDelay(timestamp);
+
+ EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}
-TEST(ReceiverTimingTest, TimestampWrapAround) {
+TEST(VCMTimingTest, TimestampWrapAround) {
constexpr auto kStartTime = Timestamp::Millis(1337);
test::ScopedKeyValueConfig field_trials;
SimulatedClock clock(kStartTime);
@@ -136,9 +184,11 @@
EXPECT_EQ(kStartTime + 3 / k25Fps + TimeDelta::Millis(1),
timing.RenderTime(89u, clock.CurrentTime()));
}
+
+ EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}
-TEST(ReceiverTimingTest, UseLowLatencyRenderer) {
+TEST(VCMTimingTest, UseLowLatencyRenderer) {
test::ScopedKeyValueConfig field_trials;
SimulatedClock clock(0);
VCMTiming timing(&clock, field_trials);
@@ -161,9 +211,11 @@
// False if max playout delay > 500 ms.
timing.set_max_playout_delay(TimeDelta::Millis(501));
EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering);
+
+ EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}
-TEST(ReceiverTimingTest, MaxWaitingTimeIsZeroForZeroRenderTime) {
+TEST(VCMTimingTest, MaxWaitingTimeIsZeroForZeroRenderTime) {
// This is the default path when the RTP playout delay header extension is set
// to min==0 and max==0.
constexpr int64_t kStartTimeUs = 3.15e13; // About one year in us.
@@ -197,9 +249,11 @@
EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now,
/*too_many_frames_queued=*/false),
TimeDelta::Zero());
+
+ EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}
-TEST(ReceiverTimingTest, MaxWaitingTimeZeroDelayPacingExperiment) {
+TEST(VCMTimingTest, MaxWaitingTimeZeroDelayPacingExperiment) {
// The minimum pacing is enabled by a field trial and active if the RTP
// playout delay header extension is set to min==0.
constexpr TimeDelta kMinPacing = TimeDelta::Millis(3);
@@ -247,9 +301,11 @@
EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now,
/*too_many_frames_queued=*/false),
kMinPacing);
+
+ EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}
-TEST(ReceiverTimingTest, DefaultMaxWaitingTimeUnaffectedByPacingExperiment) {
+TEST(VCMTimingTest, DefaultMaxWaitingTimeUnaffectedByPacingExperiment) {
// The minimum pacing is enabled by a field trial but should not have any
// effect if render_time_ms is greater than 0;
test::ScopedKeyValueConfig field_trials(
@@ -277,9 +333,11 @@
/*too_many_frames_queued=*/false),
render_time - now - estimated_processing_delay);
}
+
+ EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}
-TEST(ReceiverTimingTest, MaxWaitingTimeReturnsZeroIfTooManyFramesQueuedIsTrue) {
+TEST(VCMTimingTest, MaxWaitingTimeReturnsZeroIfTooManyFramesQueuedIsTrue) {
// The minimum pacing is enabled by a field trial and active if the RTP
// playout delay header extension is set to min==0.
constexpr TimeDelta kMinPacing = TimeDelta::Millis(3);
@@ -314,9 +372,11 @@
EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms,
/*too_many_frames_queued=*/true),
TimeDelta::Zero());
+
+ EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
}
-TEST(ReceiverTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) {
+TEST(VCMTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) {
test::ScopedKeyValueConfig field_trials;
SimulatedClock clock(0);
VCMTiming timing(&clock, field_trials);
@@ -334,6 +394,52 @@
decode_time + TimeDelta::Millis(10) + TimeDelta::Micros(37);
timing.UpdateCurrentDelay(render_time, decode_time);
EXPECT_EQ(timing.GetTimings().current_delay, timing.TargetVideoDelay());
+
+ // TODO(crbug.com/webrtc/15197): Fix this.
+ // EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings());
+}
+
+TEST(VCMTimingTest, GetTimings) {
+ test::ScopedKeyValueConfig field_trials;
+ SimulatedClock clock(33);
+ VCMTiming timing(&clock, field_trials);
+ timing.Reset();
+
+ // Setup.
+ TimeDelta render_delay = TimeDelta::Millis(11);
+ timing.set_render_delay(render_delay);
+ TimeDelta min_playout_delay = TimeDelta::Millis(50);
+ timing.set_min_playout_delay(min_playout_delay);
+ TimeDelta max_playout_delay = TimeDelta::Millis(500);
+ timing.set_max_playout_delay(max_playout_delay);
+
+ // On complete.
+ timing.IncomingTimestamp(3000, clock.CurrentTime());
+ clock.AdvanceTimeMilliseconds(1);
+
+ // On decodable.
+ Timestamp render_time =
+ timing.RenderTime(/*next_temporal_unit_rtp=*/3000, clock.CurrentTime());
+ TimeDelta minimum_delay = TimeDelta::Millis(123);
+ timing.SetJitterDelay(minimum_delay);
+ timing.UpdateCurrentDelay(render_time, clock.CurrentTime());
+ clock.AdvanceTimeMilliseconds(100);
+
+ // On decoded.
+ TimeDelta decode_time = TimeDelta::Millis(4);
+ timing.StopDecodeTimer(decode_time, clock.CurrentTime());
+
+ VCMTiming::VideoDelayTimings timings = timing.GetTimings();
+ EXPECT_EQ(timings.num_decoded_frames, 1u);
+ EXPECT_EQ(timings.minimum_delay, minimum_delay);
+ // A single decoded frame is not enough to calculate p95.
+ EXPECT_EQ(timings.estimated_max_decode_time, TimeDelta::Zero());
+ EXPECT_EQ(timings.render_delay, render_delay);
+ EXPECT_EQ(timings.min_playout_delay, min_playout_delay);
+ EXPECT_EQ(timings.max_playout_delay, max_playout_delay);
+ EXPECT_EQ(timings.target_delay, minimum_delay);
+ EXPECT_EQ(timings.current_delay, minimum_delay);
+ EXPECT_THAT(timings, HasConsistentVideoDelayTimings());
}
} // namespace webrtc
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 97456a5..eeee3b8 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -416,14 +416,10 @@
static_cast<int32_t>(media_receiver_info.packets_lost);
inbound_stats->jitter_buffer_delay =
media_receiver_info.jitter_buffer_delay_seconds;
- if (media_receiver_info.jitter_buffer_target_delay_seconds.has_value()) {
- inbound_stats->jitter_buffer_target_delay =
- *media_receiver_info.jitter_buffer_target_delay_seconds;
- }
- if (media_receiver_info.jitter_buffer_minimum_delay_seconds.has_value()) {
- inbound_stats->jitter_buffer_minimum_delay =
- *media_receiver_info.jitter_buffer_minimum_delay_seconds;
- }
+ inbound_stats->jitter_buffer_target_delay =
+ media_receiver_info.jitter_buffer_target_delay_seconds;
+ inbound_stats->jitter_buffer_minimum_delay =
+ media_receiver_info.jitter_buffer_minimum_delay_seconds;
inbound_stats->jitter_buffer_emitted_count =
media_receiver_info.jitter_buffer_emitted_count;
if (media_receiver_info.nacks_sent.has_value()) {
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index fa9f226..12a0063 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -634,6 +634,10 @@
inbound_stream.jitter_buffer_delay);
verifier.TestMemberIsNonNegative<uint64_t>(
inbound_stream.jitter_buffer_emitted_count);
+ verifier.TestMemberIsNonNegative<double>(
+ inbound_stream.jitter_buffer_target_delay);
+ verifier.TestMemberIsNonNegative<double>(
+ inbound_stream.jitter_buffer_minimum_delay);
if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "video") {
verifier.TestMemberIsUndefined(inbound_stream.total_samples_received);
verifier.TestMemberIsUndefined(inbound_stream.concealed_samples);
@@ -643,9 +647,6 @@
inbound_stream.inserted_samples_for_deceleration);
verifier.TestMemberIsUndefined(
inbound_stream.removed_samples_for_acceleration);
- verifier.TestMemberIsUndefined(inbound_stream.jitter_buffer_target_delay);
- verifier.TestMemberIsUndefined(
- inbound_stream.jitter_buffer_minimum_delay);
verifier.TestMemberIsUndefined(inbound_stream.audio_level);
verifier.TestMemberIsUndefined(inbound_stream.total_audio_energy);
verifier.TestMemberIsUndefined(inbound_stream.total_samples_duration);
diff --git a/video/end_to_end_tests/stats_tests.cc b/video/end_to_end_tests/stats_tests.cc
index 3c3799f..967357f 100644
--- a/video/end_to_end_tests/stats_tests.cc
+++ b/video/end_to_end_tests/stats_tests.cc
@@ -129,8 +129,12 @@
receive_stats_filled_["JitterBufferDelay"] =
stats.jitter_buffer_delay > TimeDelta::Zero();
+ receive_stats_filled_["JitterBufferTargetDelay"] =
+ stats.jitter_buffer_target_delay > TimeDelta::Zero();
receive_stats_filled_["JitterBufferEmittedCount"] =
stats.jitter_buffer_emitted_count != 0;
+ receive_stats_filled_["JitterBufferMinimumDelay"] =
+ stats.jitter_buffer_minimum_delay > TimeDelta::Zero();
receive_stats_filled_["CName"] |= !stats.c_name.empty();
diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc
index faa0ea9..049f212 100644
--- a/video/receive_statistics_proxy.cc
+++ b/video/receive_statistics_proxy.cc
@@ -531,12 +531,15 @@
}));
}
-void ReceiveStatisticsProxy::OnDecodableFrame(TimeDelta jitter_buffer_delay) {
+void ReceiveStatisticsProxy::OnDecodableFrame(TimeDelta jitter_buffer_delay,
+ TimeDelta target_delay,
+ TimeDelta minimum_delay) {
RTC_DCHECK_RUN_ON(&main_thread_);
// Cumulative stats exposed through standardized GetStats.
- // TODO(crbug.com/webrtc/14244): Implement targetDelay and minimumDelay here.
stats_.jitter_buffer_delay += jitter_buffer_delay;
+ stats_.jitter_buffer_target_delay += target_delay;
++stats_.jitter_buffer_emitted_count;
+ stats_.jitter_buffer_minimum_delay += minimum_delay;
}
void ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated(
diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h
index 425eb1a..d8da306 100644
--- a/video/receive_statistics_proxy.h
+++ b/video/receive_statistics_proxy.h
@@ -89,7 +89,9 @@
size_t size_bytes,
VideoContentType content_type) override;
void OnDroppedFrames(uint32_t frames_dropped) override;
- void OnDecodableFrame(TimeDelta jitter_buffer_delay) override;
+ void OnDecodableFrame(TimeDelta jitter_buffer_delay,
+ TimeDelta target_delay,
+ TimeDelta minimum_delay) override;
void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms,
int current_delay_ms,
int target_delay_ms,
diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc
index 7854c79..ec6dd0c 100644
--- a/video/receive_statistics_proxy_unittest.cc
+++ b/video/receive_statistics_proxy_unittest.cc
@@ -560,35 +560,45 @@
TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsDecodeTimingStats) {
const int kMaxDecodeMs = 2;
const int kCurrentDelayMs = 3;
- const int kTargetDelayMs = 4;
+ const TimeDelta kTargetDelay = TimeDelta::Millis(4);
const int kJitterDelayMs = 5;
const int kMinPlayoutDelayMs = 6;
const int kRenderDelayMs = 7;
const int64_t kRttMs = 8;
- const int kJitterBufferDelayMs = 9;
+ const TimeDelta kJitterBufferDelay = TimeDelta::Millis(9);
+ const TimeDelta kMinimumDelay = TimeDelta::Millis(1);
statistics_proxy_->OnRttUpdate(kRttMs);
statistics_proxy_->OnFrameBufferTimingsUpdated(
- kMaxDecodeMs, kCurrentDelayMs, kTargetDelayMs, kJitterDelayMs,
+ kMaxDecodeMs, kCurrentDelayMs, kTargetDelay.ms(), kJitterDelayMs,
kMinPlayoutDelayMs, kRenderDelayMs);
- statistics_proxy_->OnDecodableFrame(TimeDelta::Millis(kJitterBufferDelayMs));
+ statistics_proxy_->OnDecodableFrame(kJitterBufferDelay, kTargetDelay,
+ kMinimumDelay);
VideoReceiveStreamInterface::Stats stats = FlushAndGetStats();
EXPECT_EQ(kMaxDecodeMs, stats.max_decode_ms);
EXPECT_EQ(kCurrentDelayMs, stats.current_delay_ms);
- EXPECT_EQ(kTargetDelayMs, stats.target_delay_ms);
+ EXPECT_EQ(kTargetDelay.ms(), stats.target_delay_ms);
EXPECT_EQ(kJitterDelayMs, stats.jitter_buffer_ms);
EXPECT_EQ(kMinPlayoutDelayMs, stats.min_playout_delay_ms);
EXPECT_EQ(kRenderDelayMs, stats.render_delay_ms);
- EXPECT_EQ(kJitterBufferDelayMs, stats.jitter_buffer_delay.ms());
+ EXPECT_EQ(kJitterBufferDelay, stats.jitter_buffer_delay);
+ EXPECT_EQ(kTargetDelay, stats.jitter_buffer_target_delay);
EXPECT_EQ(1u, stats.jitter_buffer_emitted_count);
+ EXPECT_EQ(kMinimumDelay, stats.jitter_buffer_minimum_delay);
}
TEST_F(ReceiveStatisticsProxyTest, CumulativeDecodeGetStatsAccumulate) {
- const int kJitterBufferDelayMs = 3;
- statistics_proxy_->OnDecodableFrame(TimeDelta::Millis(kJitterBufferDelayMs));
- statistics_proxy_->OnDecodableFrame(TimeDelta::Millis(kJitterBufferDelayMs));
+ const TimeDelta kJitterBufferDelay = TimeDelta::Millis(3);
+ const TimeDelta kTargetDelay = TimeDelta::Millis(2);
+ const TimeDelta kMinimumDelay = TimeDelta::Millis(1);
+ statistics_proxy_->OnDecodableFrame(kJitterBufferDelay, kTargetDelay,
+ kMinimumDelay);
+ statistics_proxy_->OnDecodableFrame(kJitterBufferDelay, kTargetDelay,
+ kMinimumDelay);
VideoReceiveStreamInterface::Stats stats = FlushAndGetStats();
- EXPECT_EQ(2 * kJitterBufferDelayMs, stats.jitter_buffer_delay.ms());
+ EXPECT_EQ(2 * kJitterBufferDelay, stats.jitter_buffer_delay);
+ EXPECT_EQ(2 * kTargetDelay, stats.jitter_buffer_target_delay);
EXPECT_EQ(2u, stats.jitter_buffer_emitted_count);
+ EXPECT_EQ(2 * kMinimumDelay, stats.jitter_buffer_minimum_delay);
}
TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsRtcpPacketTypeCounts) {
diff --git a/video/video_stream_buffer_controller.cc b/video/video_stream_buffer_controller.cc
index 870c32d..455f064 100644
--- a/video/video_stream_buffer_controller.cc
+++ b/video/video_stream_buffer_controller.cc
@@ -336,7 +336,7 @@
if (timings.num_decoded_frames) {
stats_proxy_->OnFrameBufferTimingsUpdated(
timings.estimated_max_decode_time.ms(), timings.current_delay.ms(),
- timings.target_delay.ms(), timings.jitter_delay.ms(),
+ timings.target_delay.ms(), timings.minimum_delay.ms(),
timings.min_playout_delay.ms(), timings.render_delay.ms());
}
@@ -351,7 +351,8 @@
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay
TimeDelta jitter_buffer_delay =
std::max(TimeDelta::Zero(), now - min_receive_time);
- stats_proxy_->OnDecodableFrame(jitter_buffer_delay);
+ stats_proxy_->OnDecodableFrame(jitter_buffer_delay, timings.target_delay,
+ timings.minimum_delay);
}
void VideoStreamBufferController::UpdateTimingFrameInfo() {
diff --git a/video/video_stream_buffer_controller.h b/video/video_stream_buffer_controller.h
index 96e867a..bb67304 100644
--- a/video/video_stream_buffer_controller.h
+++ b/video/video_stream_buffer_controller.h
@@ -45,8 +45,12 @@
virtual void OnDroppedFrames(uint32_t frames_dropped) = 0;
- // Actual delay experienced by a single frame.
- virtual void OnDecodableFrame(TimeDelta jitter_buffer_delay) = 0;
+ // `jitter_buffer_delay` is the delay experienced by a single frame,
+ // whereas `target_delay` and `minimum_delay` are the current delays
+ // applied by the jitter buffer.
+ virtual void OnDecodableFrame(TimeDelta jitter_buffer_delay,
+ TimeDelta target_delay,
+ TimeDelta minimum_delay) = 0;
// Various jitter buffer delays determined by VCMTiming.
virtual void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms,
diff --git a/video/video_stream_buffer_controller_unittest.cc b/video/video_stream_buffer_controller_unittest.cc
index 4156581..be779ea 100644
--- a/video/video_stream_buffer_controller_unittest.cc
+++ b/video/video_stream_buffer_controller_unittest.cc
@@ -107,7 +107,9 @@
MOCK_METHOD(void, OnDroppedFrames, (uint32_t num_dropped), (override));
MOCK_METHOD(void,
OnDecodableFrame,
- (TimeDelta jitter_buffer_delay),
+ (TimeDelta jitter_buffer_delay,
+ TimeDelta target_delay,
+ TimeDelta minimum_delay),
(override));
MOCK_METHOD(void,
OnFrameBufferTimingsUpdated,