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}
This commit is contained in:
parent
9caef2a8b8
commit
f0820ffd88
@ -80,7 +80,11 @@ std::string VideoReceiveStreamInterface::Stats::ToString(
|
||||
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>()
|
||||
<< ", ";
|
||||
|
||||
@ -98,8 +98,12 @@ class VideoReceiveStreamInterface : public MediaReceiveStreamInterface {
|
||||
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;
|
||||
|
||||
@ -442,15 +442,11 @@ struct MediaReceiverInfo {
|
||||
// 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;
|
||||
|
||||
@ -3394,7 +3394,11 @@ WebRtcVideoChannel::WebRtcVideoReceiveStream::GetVideoReceiverInfo(
|
||||
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 =
|
||||
|
||||
@ -6557,7 +6557,9 @@ TEST_F(WebRtcVideoChannelTest, GetStatsTranslatesDecodeStatsCorrectly) {
|
||||
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 @@ TEST_F(WebRtcVideoChannelTest, GetStatsTranslatesDecodeStatsCorrectly) {
|
||||
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);
|
||||
|
||||
@ -252,6 +252,13 @@ TimeDelta VCMTiming::TargetDelayInternal() const {
|
||||
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 @@ VCMTiming::VideoDelayTimings VCMTiming::GetTimings() const {
|
||||
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_};
|
||||
}
|
||||
|
||||
|
||||
@ -31,13 +31,15 @@ class VCMTiming {
|
||||
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 @@ class VCMTiming {
|
||||
// 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 @@ class VCMTiming {
|
||||
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_;
|
||||
|
||||
@ -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 @@ namespace {
|
||||
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 @@ TEST(ReceiverTimingTest, JitterDelay) {
|
||||
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 @@ TEST(ReceiverTimingTest, TimestampWrapAround) {
|
||||
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 @@ TEST(ReceiverTimingTest, UseLowLatencyRenderer) {
|
||||
// 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 @@ TEST(ReceiverTimingTest, MaxWaitingTimeIsZeroForZeroRenderTime) {
|
||||
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 @@ TEST(ReceiverTimingTest, MaxWaitingTimeZeroDelayPacingExperiment) {
|
||||
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 @@ TEST(ReceiverTimingTest, DefaultMaxWaitingTimeUnaffectedByPacingExperiment) {
|
||||
/*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 @@ TEST(ReceiverTimingTest, MaxWaitingTimeReturnsZeroIfTooManyFramesQueuedIsTrue) {
|
||||
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 @@ TEST(ReceiverTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) {
|
||||
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
|
||||
|
||||
@ -416,14 +416,10 @@ void SetInboundRTPStreamStatsFromMediaReceiverInfo(
|
||||
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()) {
|
||||
|
||||
@ -634,6 +634,10 @@ class RTCStatsReportVerifier {
|
||||
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 @@ class RTCStatsReportVerifier {
|
||||
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);
|
||||
|
||||
@ -129,8 +129,12 @@ TEST_F(StatsEndToEndTest, GetStats) {
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@ -531,12 +531,15 @@ void ReceiveStatisticsProxy::OnDecoderInfo(
|
||||
}));
|
||||
}
|
||||
|
||||
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(
|
||||
|
||||
@ -89,7 +89,9 @@ class ReceiveStatisticsProxy : public VideoStreamBufferControllerStatsObserver,
|
||||
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,
|
||||
|
||||
@ -560,35 +560,45 @@ TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsOnDroppedFrame) {
|
||||
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) {
|
||||
|
||||
@ -336,7 +336,7 @@ void VideoStreamBufferController::UpdateFrameBufferTimings(
|
||||
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 @@ void VideoStreamBufferController::UpdateFrameBufferTimings(
|
||||
// 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() {
|
||||
|
||||
@ -45,8 +45,12 @@ class VideoStreamBufferControllerStatsObserver {
|
||||
|
||||
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,
|
||||
|
||||
@ -107,7 +107,9 @@ class VideoStreamBufferControllerStatsObserverMock
|
||||
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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user