blob: 41388e0ade05f5d68f65f50d780414f006535d56 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/rand_util.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/tick_clock.h"
#include "media/cast/cast_environment.h"
#include "media/cast/logging/logging_defines.h"
#include "media/cast/logging/stats_event_subscriber.h"
#include "media/cast/test/fake_receiver_time_offset_estimator.h"
#include "media/cast/test/fake_single_thread_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const int kReceiverOffsetSecs = 100;
}
namespace media {
namespace cast {
class StatsEventSubscriberTest : public ::testing::Test {
protected:
StatsEventSubscriberTest()
: sender_clock_(new base::SimpleTestTickClock()),
task_runner_(new test::FakeSingleThreadTaskRunner(sender_clock_)),
cast_environment_(new CastEnvironment(
scoped_ptr<base::TickClock>(sender_clock_).Pass(),
task_runner_,
task_runner_,
task_runner_)),
fake_offset_estimator_(
base::TimeDelta::FromSeconds(kReceiverOffsetSecs)) {
receiver_clock_.Advance(base::TimeDelta::FromSeconds(kReceiverOffsetSecs));
cast_environment_->Logging()->AddRawEventSubscriber(
&fake_offset_estimator_);
}
~StatsEventSubscriberTest() override {
if (subscriber_.get())
cast_environment_->Logging()->RemoveRawEventSubscriber(subscriber_.get());
cast_environment_->Logging()->RemoveRawEventSubscriber(
&fake_offset_estimator_);
}
void AdvanceClocks(base::TimeDelta delta) {
sender_clock_->Advance(delta);
receiver_clock_.Advance(delta);
}
void Init(EventMediaType event_media_type) {
DCHECK(!subscriber_.get());
subscriber_.reset(new StatsEventSubscriber(
event_media_type, cast_environment_->Clock(), &fake_offset_estimator_));
cast_environment_->Logging()->AddRawEventSubscriber(subscriber_.get());
}
base::SimpleTestTickClock* sender_clock_; // Owned by CastEnvironment.
base::SimpleTestTickClock receiver_clock_;
scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner_;
scoped_refptr<CastEnvironment> cast_environment_;
test::FakeReceiverTimeOffsetEstimator fake_offset_estimator_;
scoped_ptr<StatsEventSubscriber> subscriber_;
};
TEST_F(StatsEventSubscriberTest, CaptureEncode) {
Init(VIDEO_EVENT);
uint32 rtp_timestamp = 0;
uint32 frame_id = 0;
int extra_frames = 50;
// Only the first |extra_frames| frames logged will be taken into account
// when computing dropped frames.
int num_frames = StatsEventSubscriber::kMaxFrameInfoMapSize + 50;
int dropped_frames = 0;
base::TimeTicks start_time = sender_clock_->NowTicks();
// Drop half the frames during the encode step.
for (int i = 0; i < num_frames; i++) {
cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(),
FRAME_CAPTURE_BEGIN,
VIDEO_EVENT,
rtp_timestamp,
frame_id);
AdvanceClocks(base::TimeDelta::FromMicroseconds(10));
cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(),
FRAME_CAPTURE_END,
VIDEO_EVENT,
rtp_timestamp,
frame_id);
if (i % 2 == 0) {
AdvanceClocks(base::TimeDelta::FromMicroseconds(10));
cast_environment_->Logging()->InsertEncodedFrameEvent(
sender_clock_->NowTicks(),
FRAME_ENCODED,
VIDEO_EVENT,
rtp_timestamp,
frame_id,
1024,
true,
5678);
} else if (i < extra_frames) {
dropped_frames++;
}
AdvanceClocks(base::TimeDelta::FromMicroseconds(34567));
rtp_timestamp += 90;
frame_id++;
}
base::TimeTicks end_time = sender_clock_->NowTicks();
StatsEventSubscriber::StatsMap stats_map;
subscriber_->GetStatsInternal(&stats_map);
StatsEventSubscriber::StatsMap::iterator it =
stats_map.find(StatsEventSubscriber::CAPTURE_FPS);
ASSERT_TRUE(it != stats_map.end());
base::TimeDelta duration = end_time - start_time;
EXPECT_DOUBLE_EQ(
it->second,
static_cast<double>(num_frames) / duration.InMillisecondsF() * 1000);
it = stats_map.find(StatsEventSubscriber::NUM_FRAMES_CAPTURED);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second, static_cast<double>(num_frames));
it = stats_map.find(StatsEventSubscriber::NUM_FRAMES_DROPPED_BY_ENCODER);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second, static_cast<double>(dropped_frames));
}
TEST_F(StatsEventSubscriberTest, Encode) {
Init(VIDEO_EVENT);
uint32 rtp_timestamp = 0;
uint32 frame_id = 0;
int num_frames = 10;
base::TimeTicks start_time = sender_clock_->NowTicks();
AdvanceClocks(base::TimeDelta::FromMicroseconds(35678));
base::TimeTicks first_event_time = sender_clock_->NowTicks();
base::TimeTicks last_event_time;
int total_size = 0;
for (int i = 0; i < num_frames; i++) {
int size = 1000 + base::RandInt(-100, 100);
total_size += size;
cast_environment_->Logging()->InsertEncodedFrameEvent(
sender_clock_->NowTicks(),
FRAME_ENCODED, VIDEO_EVENT,
rtp_timestamp,
frame_id,
size,
true,
5678);
last_event_time = sender_clock_->NowTicks();
AdvanceClocks(base::TimeDelta::FromMicroseconds(35678));
rtp_timestamp += 90;
frame_id++;
}
base::TimeTicks end_time = sender_clock_->NowTicks();
StatsEventSubscriber::StatsMap stats_map;
subscriber_->GetStatsInternal(&stats_map);
StatsEventSubscriber::StatsMap::iterator it =
stats_map.find(StatsEventSubscriber::ENCODE_FPS);
ASSERT_TRUE(it != stats_map.end());
base::TimeDelta duration = end_time - start_time;
EXPECT_DOUBLE_EQ(
it->second,
static_cast<double>(num_frames) / duration.InMillisecondsF() * 1000);
it = stats_map.find(StatsEventSubscriber::ENCODE_KBPS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second,
static_cast<double>(total_size) / duration.InMillisecondsF() * 8);
it = stats_map.find(StatsEventSubscriber::FIRST_EVENT_TIME_MS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(
it->second,
(first_event_time - base::TimeTicks::UnixEpoch()).InMillisecondsF());
it = stats_map.find(StatsEventSubscriber::LAST_EVENT_TIME_MS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(
it->second,
(last_event_time - base::TimeTicks::UnixEpoch()).InMillisecondsF());
}
TEST_F(StatsEventSubscriberTest, Decode) {
Init(VIDEO_EVENT);
uint32 rtp_timestamp = 0;
uint32 frame_id = 0;
int num_frames = 10;
base::TimeTicks start_time = sender_clock_->NowTicks();
for (int i = 0; i < num_frames; i++) {
cast_environment_->Logging()->InsertFrameEvent(receiver_clock_.NowTicks(),
FRAME_DECODED, VIDEO_EVENT,
rtp_timestamp,
frame_id);
AdvanceClocks(base::TimeDelta::FromMicroseconds(36789));
rtp_timestamp += 90;
frame_id++;
}
base::TimeTicks end_time = sender_clock_->NowTicks();
StatsEventSubscriber::StatsMap stats_map;
subscriber_->GetStatsInternal(&stats_map);
StatsEventSubscriber::StatsMap::iterator it =
stats_map.find(StatsEventSubscriber::DECODE_FPS);
ASSERT_TRUE(it != stats_map.end());
base::TimeDelta duration = end_time - start_time;
EXPECT_DOUBLE_EQ(
it->second,
static_cast<double>(num_frames) / duration.InMillisecondsF() * 1000);
}
TEST_F(StatsEventSubscriberTest, PlayoutDelay) {
Init(VIDEO_EVENT);
uint32 rtp_timestamp = 0;
uint32 frame_id = 0;
int num_frames = 10;
int total_delay_ms = 0;
int late_frames = 0;
for (int i = 0, delay_ms = -50; i < num_frames; i++, delay_ms += 10) {
base::TimeDelta delay = base::TimeDelta::FromMilliseconds(delay_ms);
total_delay_ms += delay_ms;
if (delay_ms <= 0)
late_frames++;
cast_environment_->Logging()->InsertFrameEventWithDelay(
receiver_clock_.NowTicks(),
FRAME_PLAYOUT,
VIDEO_EVENT,
rtp_timestamp,
frame_id,
delay);
AdvanceClocks(base::TimeDelta::FromMicroseconds(37890));
rtp_timestamp += 90;
frame_id++;
}
StatsEventSubscriber::StatsMap stats_map;
subscriber_->GetStatsInternal(&stats_map);
StatsEventSubscriber::StatsMap::iterator it =
stats_map.find(StatsEventSubscriber::AVG_PLAYOUT_DELAY_MS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(
it->second, static_cast<double>(total_delay_ms) / num_frames);
it = stats_map.find(StatsEventSubscriber::NUM_FRAMES_LATE);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second, late_frames);
}
TEST_F(StatsEventSubscriberTest, E2ELatency) {
Init(VIDEO_EVENT);
uint32 rtp_timestamp = 0;
uint32 frame_id = 0;
int num_frames = 10;
base::TimeDelta total_latency;
for (int i = 0; i < num_frames; i++) {
cast_environment_->Logging()->InsertFrameEvent(sender_clock_->NowTicks(),
FRAME_CAPTURE_BEGIN,
VIDEO_EVENT,
rtp_timestamp,
frame_id);
int latency_micros = 100000 + base::RandInt(-5000, 50000);
base::TimeDelta latency = base::TimeDelta::FromMicroseconds(latency_micros);
AdvanceClocks(latency);
int delay_micros = base::RandInt(-50000, 50000);
base::TimeDelta delay = base::TimeDelta::FromMilliseconds(delay_micros);
total_latency += latency + delay;
cast_environment_->Logging()->InsertFrameEventWithDelay(
receiver_clock_.NowTicks(),
FRAME_PLAYOUT,
VIDEO_EVENT,
rtp_timestamp,
frame_id,
delay);
rtp_timestamp += 90;
frame_id++;
}
StatsEventSubscriber::StatsMap stats_map;
subscriber_->GetStatsInternal(&stats_map);
StatsEventSubscriber::StatsMap::iterator it =
stats_map.find(StatsEventSubscriber::AVG_E2E_LATENCY_MS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(
it->second, total_latency.InMillisecondsF() / num_frames);
}
TEST_F(StatsEventSubscriberTest, Packets) {
Init(VIDEO_EVENT);
uint32 rtp_timestamp = 0;
int num_packets = 10;
int num_latency_recorded_packets = 0;
base::TimeTicks start_time = sender_clock_->NowTicks();
int total_size = 0;
int retransmit_total_size = 0;
base::TimeDelta total_latency;
int num_packets_transmitted = 0;
int num_packets_retransmitted = 0;
int num_packets_rtx_rejected = 0;
// Every 2nd packet will be retransmitted once.
// Every 4th packet will be retransmitted twice.
// Every 8th packet will be retransmitted 3 times + 1 rejected retransmission.
for (int i = 0; i < num_packets; i++) {
int size = 1000 + base::RandInt(-100, 100);
total_size += size;
cast_environment_->Logging()->InsertPacketEvent(sender_clock_->NowTicks(),
PACKET_SENT_TO_NETWORK,
VIDEO_EVENT,
rtp_timestamp,
0,
i,
num_packets - 1,
size);
num_packets_transmitted++;
int latency_micros = 20000 + base::RandInt(-10000, 10000);
base::TimeDelta latency = base::TimeDelta::FromMicroseconds(latency_micros);
// Latency is only recorded for packets that aren't retransmitted.
if (i % 2 != 0) {
total_latency += latency;
num_latency_recorded_packets++;
}
AdvanceClocks(latency);
base::TimeTicks received_time = receiver_clock_.NowTicks();
// Retransmission 1.
AdvanceClocks(base::TimeDelta::FromMicroseconds(12345));
if (i % 2 == 0) {
cast_environment_->Logging()->InsertPacketEvent(
receiver_clock_.NowTicks(),
PACKET_RETRANSMITTED,
VIDEO_EVENT,
rtp_timestamp,
0,
i,
num_packets - 1,
size);
retransmit_total_size += size;
num_packets_transmitted++;
num_packets_retransmitted++;
}
// Retransmission 2.
AdvanceClocks(base::TimeDelta::FromMicroseconds(13456));
if (i % 4 == 0) {
cast_environment_->Logging()->InsertPacketEvent(
receiver_clock_.NowTicks(),
PACKET_RETRANSMITTED,
VIDEO_EVENT,
rtp_timestamp,
0,
i,
num_packets - 1,
size);
retransmit_total_size += size;
num_packets_transmitted++;
num_packets_retransmitted++;
}
// Retransmission 3.
AdvanceClocks(base::TimeDelta::FromMicroseconds(14567));
if (i % 8 == 0) {
cast_environment_->Logging()->InsertPacketEvent(
receiver_clock_.NowTicks(),
PACKET_RETRANSMITTED,
VIDEO_EVENT,
rtp_timestamp,
0,
i,
num_packets - 1,
size);
cast_environment_->Logging()->InsertPacketEvent(
receiver_clock_.NowTicks(),
PACKET_RTX_REJECTED,
VIDEO_EVENT,
rtp_timestamp,
0,
i,
num_packets - 1,
size);
retransmit_total_size += size;
num_packets_transmitted++;
num_packets_retransmitted++;
num_packets_rtx_rejected++;
}
cast_environment_->Logging()->InsertPacketEvent(received_time,
PACKET_RECEIVED,
VIDEO_EVENT,
rtp_timestamp,
0,
i,
num_packets - 1,
size);
}
base::TimeTicks end_time = sender_clock_->NowTicks();
base::TimeDelta duration = end_time - start_time;
StatsEventSubscriber::StatsMap stats_map;
subscriber_->GetStatsInternal(&stats_map);
// Measure AVG_NETWORK_LATENCY_MS, TRANSMISSION_KBPS, RETRANSMISSION_KBPS,
// and PACKET_LOSS_FRACTION.
StatsEventSubscriber::StatsMap::iterator it =
stats_map.find(StatsEventSubscriber::AVG_NETWORK_LATENCY_MS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(
it->second,
total_latency.InMillisecondsF() / num_latency_recorded_packets);
it = stats_map.find(StatsEventSubscriber::TRANSMISSION_KBPS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second,
static_cast<double>(total_size) / duration.InMillisecondsF() * 8);
it = stats_map.find(StatsEventSubscriber::RETRANSMISSION_KBPS);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second,
static_cast<double>(retransmit_total_size) /
duration.InMillisecondsF() * 8);
it = stats_map.find(StatsEventSubscriber::PACKET_LOSS_FRACTION);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(
it->second,
static_cast<double>(num_packets_retransmitted) / num_packets_transmitted);
it = stats_map.find(StatsEventSubscriber::NUM_PACKETS_SENT);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second, static_cast<double>(num_packets));
it = stats_map.find(StatsEventSubscriber::NUM_PACKETS_RETRANSMITTED);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second, static_cast<double>(num_packets_retransmitted));
it = stats_map.find(StatsEventSubscriber::NUM_PACKETS_RTX_REJECTED);
ASSERT_TRUE(it != stats_map.end());
EXPECT_DOUBLE_EQ(it->second, static_cast<double>(num_packets_rtx_rejected));
}
} // namespace cast
} // namespace media