summaryrefslogtreecommitdiff
path: root/media/cast
diff options
context:
space:
mode:
authorTorne (Richard Coles) <torne@google.com>2014-08-19 13:00:08 +0100
committerTorne (Richard Coles) <torne@google.com>2014-08-19 13:00:08 +0100
commit6e8cce623b6e4fe0c9e4af605d675dd9d0338c38 (patch)
tree8d824ad26fac42e008142b86aa9631b2be7e4705 /media/cast
parent4f7316adb45db5ec3c9c1181ba9510c004566df8 (diff)
downloadchromium_org-6e8cce623b6e4fe0c9e4af605d675dd9d0338c38.tar.gz
Merge from Chromium at DEPS revision 290040
This commit was generated by merge_to_master.py. Change-Id: I694ec52d1e0b553f163c2faf4373d63270ab1aac
Diffstat (limited to 'media/cast')
-rw-r--r--media/cast/BUILD.gn1
-rw-r--r--media/cast/cast_defines.h2
-rw-r--r--media/cast/cast_testing.gypi26
-rw-r--r--media/cast/logging/stats_event_subscriber.cc83
-rw-r--r--media/cast/logging/stats_event_subscriber.h47
-rw-r--r--media/cast/logging/stats_event_subscriber_unittest.cc91
-rw-r--r--media/cast/net/cast_transport_defines.h58
-rw-r--r--media/cast/net/frame_id_wrap_helper_test.cc30
-rw-r--r--media/cast/net/udp_transport.cc7
-rw-r--r--media/cast/sender/audio_sender.cc2
-rw-r--r--media/cast/sender/external_video_encoder.cc162
-rw-r--r--media/cast/sender/external_video_encoder.h7
-rw-r--r--media/cast/sender/external_video_encoder_unittest.cc71
-rw-r--r--media/cast/test/fake_video_encode_accelerator.cc2
-rw-r--r--media/cast/test/simulator.cc6
-rwxr-xr-xmedia/cast/test/utility/netload.py100
-rwxr-xr-xmedia/cast/test/utility/shadow.sh121
-rw-r--r--media/cast/test/utility/tap_proxy.cc318
-rw-r--r--media/cast/test/utility/udp_proxy.cc80
-rw-r--r--media/cast/test/utility/udp_proxy.h4
20 files changed, 1027 insertions, 191 deletions
diff --git a/media/cast/BUILD.gn b/media/cast/BUILD.gn
index 95d0077504..8d38089dae 100644
--- a/media/cast/BUILD.gn
+++ b/media/cast/BUILD.gn
@@ -220,6 +220,7 @@ source_set("test_support") {
"//third_party/libyuv",
"//third_party/mt19937ar",
"//ui/gfx",
+ "//ui/gfx/geometry",
]
}
diff --git a/media/cast/cast_defines.h b/media/cast/cast_defines.h
index 07be3b3f9e..c02fa2bfa7 100644
--- a/media/cast/cast_defines.h
+++ b/media/cast/cast_defines.h
@@ -27,7 +27,7 @@ const uint32 kStartFrameId = UINT32_C(0xffffffff);
// This is an important system-wide constant. This limits how much history the
// implementation must retain in order to process the acknowledgements of past
// frames.
-const int kMaxUnackedFrames = 255;
+const int kMaxUnackedFrames = 60;
const int kStartRttMs = 20;
const int64 kCastMessageUpdateIntervalMs = 33;
diff --git a/media/cast/cast_testing.gypi b/media/cast/cast_testing.gypi
index 7cfc50c049..21585c81ab 100644
--- a/media/cast/cast_testing.gypi
+++ b/media/cast/cast_testing.gypi
@@ -82,6 +82,7 @@
'logging/simple_event_subscriber_unittest.cc',
'logging/stats_event_subscriber_unittest.cc',
'net/cast_transport_sender_impl_unittest.cc',
+ 'net/frame_id_wrap_helper_test.cc',
'net/pacing/mock_paced_packet_sender.cc',
'net/pacing/mock_paced_packet_sender.h',
'net/pacing/paced_sender_unittest.cc',
@@ -318,6 +319,29 @@
'sources': [
'test/utility/udp_proxy_main.cc',
],
- }
+ },
+ ], # targets
+
+ 'conditions': [
+ ['OS=="linux"',
+ { 'targets': [
+ {
+ 'target_name': 'tap_proxy',
+ 'type': 'executable',
+ 'include_dirs': [
+ '<(DEPTH)/',
+ ],
+ 'dependencies': [
+ 'cast_test_utility',
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/media/media.gyp:media',
+ ],
+ 'sources': [
+ 'test/utility/tap_proxy.cc',
+ ],
+ }
+ ]
+ }
+ ]
], # targets
}
diff --git a/media/cast/logging/stats_event_subscriber.cc b/media/cast/logging/stats_event_subscriber.cc
index 9e3226a216..03c669cfcb 100644
--- a/media/cast/logging/stats_event_subscriber.cc
+++ b/media/cast/logging/stats_event_subscriber.cc
@@ -19,7 +19,6 @@ namespace {
using media::cast::CastLoggingEvent;
using media::cast::EventMediaType;
-const size_t kMaxFrameEventTimeMapSize = 100;
const size_t kMaxPacketEventTimeMapSize = 1000;
bool IsReceiverEvent(CastLoggingEvent event) {
@@ -39,7 +38,9 @@ StatsEventSubscriber::StatsEventSubscriber(
clock_(clock),
offset_estimator_(offset_estimator),
network_latency_datapoints_(0),
- e2e_latency_datapoints_(0) {
+ e2e_latency_datapoints_(0),
+ num_frames_dropped_by_encoder_(0),
+ num_frames_late_(0) {
DCHECK(event_media_type == AUDIO_EVENT || event_media_type == VIDEO_EVENT);
base::TimeTicks now = clock_->NowTicks();
start_time_ = now;
@@ -71,9 +72,13 @@ void StatsEventSubscriber::OnReceiveFrameEvent(const FrameEvent& frame_event) {
}
if (type == FRAME_CAPTURE_BEGIN) {
- RecordFrameCapturedTime(frame_event);
+ RecordFrameCaptureTime(frame_event);
+ } else if (type == FRAME_ENCODED) {
+ MarkAsEncoded(frame_event.rtp_timestamp);
} else if (type == FRAME_PLAYOUT) {
RecordE2ELatency(frame_event);
+ if (frame_event.delay_delta <= base::TimeDelta())
+ num_frames_late_++;
}
if (IsReceiverEvent(type))
@@ -138,7 +143,9 @@ void StatsEventSubscriber::Reset() {
network_latency_datapoints_ = 0;
total_e2e_latency_ = base::TimeDelta();
e2e_latency_datapoints_ = 0;
- frame_captured_times_.clear();
+ num_frames_dropped_by_encoder_ = 0;
+ num_frames_late_ = 0;
+ recent_captured_frames_.clear();
packet_sent_times_.clear();
start_time_ = clock_->NowTicks();
last_response_received_time_ = base::TimeTicks();
@@ -159,6 +166,12 @@ const char* StatsEventSubscriber::CastStatToString(CastStat stat) {
STAT_ENUM_TO_STRING(RETRANSMISSION_KBPS);
STAT_ENUM_TO_STRING(PACKET_LOSS_FRACTION);
STAT_ENUM_TO_STRING(MS_SINCE_LAST_RECEIVER_RESPONSE);
+ STAT_ENUM_TO_STRING(NUM_FRAMES_CAPTURED);
+ STAT_ENUM_TO_STRING(NUM_FRAMES_DROPPED_BY_ENCODER);
+ STAT_ENUM_TO_STRING(NUM_FRAMES_LATE);
+ STAT_ENUM_TO_STRING(NUM_PACKETS_SENT);
+ STAT_ENUM_TO_STRING(NUM_PACKETS_RETRANSMITTED);
+ STAT_ENUM_TO_STRING(NUM_PACKETS_RTX_REJECTED);
}
NOTREACHED();
return "";
@@ -188,6 +201,12 @@ void StatsEventSubscriber::GetStatsInternal(StatsMap* stats_map) const {
RETRANSMISSION_KBPS,
stats_map);
PopulatePacketLossPercentageStat(stats_map);
+ PopulateFrameCountStat(FRAME_CAPTURE_END, NUM_FRAMES_CAPTURED, stats_map);
+ PopulatePacketCountStat(PACKET_SENT_TO_NETWORK, NUM_PACKETS_SENT, stats_map);
+ PopulatePacketCountStat(
+ PACKET_RETRANSMITTED, NUM_PACKETS_RETRANSMITTED, stats_map);
+ PopulatePacketCountStat(
+ PACKET_RTX_REJECTED, NUM_PACKETS_RTX_REJECTED, stats_map);
if (network_latency_datapoints_ > 0) {
double avg_network_latency_ms =
@@ -208,6 +227,10 @@ void StatsEventSubscriber::GetStatsInternal(StatsMap* stats_map) const {
std::make_pair(MS_SINCE_LAST_RECEIVER_RESPONSE,
(end_time - last_response_received_time_).InMillisecondsF()));
}
+
+ stats_map->insert(std::make_pair(NUM_FRAMES_DROPPED_BY_ENCODER,
+ num_frames_dropped_by_encoder_));
+ stats_map->insert(std::make_pair(NUM_FRAMES_LATE, num_frames_late_));
}
bool StatsEventSubscriber::GetReceiverOffset(base::TimeDelta* offset) {
@@ -222,12 +245,22 @@ bool StatsEventSubscriber::GetReceiverOffset(base::TimeDelta* offset) {
return true;
}
-void StatsEventSubscriber::RecordFrameCapturedTime(
+void StatsEventSubscriber::RecordFrameCaptureTime(
const FrameEvent& frame_event) {
- frame_captured_times_.insert(
- std::make_pair(frame_event.rtp_timestamp, frame_event.timestamp));
- if (frame_captured_times_.size() > kMaxFrameEventTimeMapSize)
- frame_captured_times_.erase(frame_captured_times_.begin());
+ recent_captured_frames_.insert(std::make_pair(
+ frame_event.rtp_timestamp, FrameInfo(frame_event.timestamp)));
+ if (recent_captured_frames_.size() > kMaxFrameInfoMapSize) {
+ FrameInfoMap::iterator erase_it = recent_captured_frames_.begin();
+ if (!erase_it->second.encoded)
+ num_frames_dropped_by_encoder_++;
+ recent_captured_frames_.erase(erase_it);
+ }
+}
+
+void StatsEventSubscriber::MarkAsEncoded(RtpTimestamp rtp_timestamp) {
+ FrameInfoMap::iterator it = recent_captured_frames_.find(rtp_timestamp);
+ if (it != recent_captured_frames_.end())
+ it->second.encoded = true;
}
void StatsEventSubscriber::RecordE2ELatency(const FrameEvent& frame_event) {
@@ -235,15 +268,15 @@ void StatsEventSubscriber::RecordE2ELatency(const FrameEvent& frame_event) {
if (!GetReceiverOffset(&receiver_offset))
return;
- FrameEventTimeMap::iterator it =
- frame_captured_times_.find(frame_event.rtp_timestamp);
- if (it == frame_captured_times_.end())
+ FrameInfoMap::iterator it =
+ recent_captured_frames_.find(frame_event.rtp_timestamp);
+ if (it == recent_captured_frames_.end())
return;
// Playout time is event time + playout delay.
base::TimeTicks playout_time =
frame_event.timestamp + frame_event.delay_delta - receiver_offset;
- total_e2e_latency_ += playout_time - it->second;
+ total_e2e_latency_ += playout_time - it->second.capture_time;
e2e_latency_datapoints_++;
}
@@ -323,6 +356,24 @@ void StatsEventSubscriber::PopulateFpsStat(base::TimeTicks end_time,
}
}
+void StatsEventSubscriber::PopulateFrameCountStat(CastLoggingEvent event,
+ CastStat stat,
+ StatsMap* stats_map) const {
+ FrameStatsMap::const_iterator it = frame_stats_.find(event);
+ if (it != frame_stats_.end()) {
+ stats_map->insert(std::make_pair(stat, it->second.event_counter));
+ }
+}
+
+void StatsEventSubscriber::PopulatePacketCountStat(CastLoggingEvent event,
+ CastStat stat,
+ StatsMap* stats_map) const {
+ PacketStatsMap::const_iterator it = packet_stats_.find(event);
+ if (it != packet_stats_.end()) {
+ stats_map->insert(std::make_pair(stat, it->second.event_counter));
+ }
+}
+
void StatsEventSubscriber::PopulatePlayoutDelayStat(StatsMap* stats_map) const {
FrameStatsMap::const_iterator it = frame_stats_.find(FRAME_PLAYOUT);
if (it != frame_stats_.end()) {
@@ -396,5 +447,11 @@ StatsEventSubscriber::PacketLogStats::PacketLogStats()
: event_counter(0), sum_size(0) {}
StatsEventSubscriber::PacketLogStats::~PacketLogStats() {}
+StatsEventSubscriber::FrameInfo::FrameInfo(base::TimeTicks capture_time)
+ : capture_time(capture_time), encoded(false) {
+}
+StatsEventSubscriber::FrameInfo::~FrameInfo() {
+}
+
} // namespace cast
} // namespace media
diff --git a/media/cast/logging/stats_event_subscriber.h b/media/cast/logging/stats_event_subscriber.h
index 173378ab0b..06ceaca9ed 100644
--- a/media/cast/logging/stats_event_subscriber.h
+++ b/media/cast/logging/stats_event_subscriber.h
@@ -49,13 +49,15 @@ class StatsEventSubscriber : public RawEventSubscriber {
private:
friend class StatsEventSubscriberTest;
FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, EmptyStats);
- FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Capture);
+ FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, CaptureEncode);
FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Encode);
FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Decode);
FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, PlayoutDelay);
FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, E2ELatency);
FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, Packets);
+ static const size_t kMaxFrameInfoMapSize = 100;
+
// Generic statistics given the raw data. More specific data (e.g. frame rate
// and bit rate) can be computed given the basic metrics.
// Some of the metrics will only be set when applicable, e.g. delay and size.
@@ -85,8 +87,7 @@ class StatsEventSubscriber : public RawEventSubscriber {
// TODO(imcheng): This stat is not populated yet because we do not have
// the time when encode started. Record it in FRAME_ENCODED event.
AVG_ENCODE_TIME_MS,
- // Average playout delay in milliseconds, with target delay already
- // accounted for. Ideally, every frame should have a playout delay of 0.
+ // Average playout delay in milliseconds.
AVG_PLAYOUT_DELAY_MS,
// Duration from when a packet is transmitted to when it is received.
// This measures latency from sender to receiver.
@@ -102,11 +103,31 @@ class StatsEventSubscriber : public RawEventSubscriber {
// Fraction of packet loss.
PACKET_LOSS_FRACTION,
// Duration in milliseconds since last receiver response.
- MS_SINCE_LAST_RECEIVER_RESPONSE
+ MS_SINCE_LAST_RECEIVER_RESPONSE,
+ // Number of frames captured.
+ NUM_FRAMES_CAPTURED,
+ // Number of frames dropped by encoder.
+ NUM_FRAMES_DROPPED_BY_ENCODER,
+ // Number of late frames.
+ NUM_FRAMES_LATE,
+ // Number of packets that were sent (not retransmitted).
+ NUM_PACKETS_SENT,
+ // Number of packets that were retransmitted.
+ NUM_PACKETS_RETRANSMITTED,
+ // Number of packets that had their retransmission cancelled.
+ NUM_PACKETS_RTX_REJECTED,
+ };
+
+ struct FrameInfo {
+ explicit FrameInfo(base::TimeTicks capture_time);
+ ~FrameInfo();
+
+ base::TimeTicks capture_time;
+ bool encoded;
};
typedef std::map<CastStat, double> StatsMap;
- typedef std::map<RtpTimestamp, base::TimeTicks> FrameEventTimeMap;
+ typedef std::map<RtpTimestamp, FrameInfo> FrameInfoMap;
typedef std::map<
std::pair<RtpTimestamp, uint16>,
std::pair<base::TimeTicks, CastLoggingEvent> >
@@ -120,7 +141,8 @@ class StatsEventSubscriber : public RawEventSubscriber {
void GetStatsInternal(StatsMap* stats_map) const;
bool GetReceiverOffset(base::TimeDelta* offset);
- void RecordFrameCapturedTime(const FrameEvent& frame_event);
+ void RecordFrameCaptureTime(const FrameEvent& frame_event);
+ void MarkAsEncoded(RtpTimestamp rtp_timestamp);
void RecordE2ELatency(const FrameEvent& frame_event);
void RecordPacketSentTime(const PacketEvent& packet_event);
void ErasePacketSentTime(const PacketEvent& packet_event);
@@ -131,6 +153,12 @@ class StatsEventSubscriber : public RawEventSubscriber {
CastLoggingEvent event,
CastStat stat,
StatsMap* stats_map) const;
+ void PopulateFrameCountStat(CastLoggingEvent event,
+ CastStat stat,
+ StatsMap* stats_map) const;
+ void PopulatePacketCountStat(CastLoggingEvent event,
+ CastStat stat,
+ StatsMap* stats_map) const;
void PopulatePlayoutDelayStat(StatsMap* stats_map) const;
void PopulateFrameBitrateStat(base::TimeTicks now, StatsMap* stats_map) const;
void PopulatePacketBitrateStat(base::TimeTicks now,
@@ -157,8 +185,11 @@ class StatsEventSubscriber : public RawEventSubscriber {
base::TimeTicks last_response_received_time_;
- // Fixed size map to record when recent frames were captured.
- FrameEventTimeMap frame_captured_times_;
+ int num_frames_dropped_by_encoder_;
+ int num_frames_late_;
+
+ // Fixed size map to record when recent frames were captured and other info.
+ FrameInfoMap recent_captured_frames_;
// Fixed size map to record when recent packets were sent.
PacketEventTimeMap packet_sent_times_;
diff --git a/media/cast/logging/stats_event_subscriber_unittest.cc b/media/cast/logging/stats_event_subscriber_unittest.cc
index 09c418b425..fe03bc6247 100644
--- a/media/cast/logging/stats_event_subscriber_unittest.cc
+++ b/media/cast/logging/stats_event_subscriber_unittest.cc
@@ -65,20 +65,44 @@ class StatsEventSubscriberTest : public ::testing::Test {
scoped_ptr<StatsEventSubscriber> subscriber_;
};
-TEST_F(StatsEventSubscriberTest, Capture) {
+TEST_F(StatsEventSubscriberTest, CaptureEncode) {
Init(VIDEO_EVENT);
uint32 rtp_timestamp = 0;
uint32 frame_id = 0;
- int num_frames = 10;
+ 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++;
@@ -97,6 +121,16 @@ TEST_F(StatsEventSubscriberTest, Capture) {
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) {
@@ -185,10 +219,12 @@ TEST_F(StatsEventSubscriberTest, PlayoutDelay) {
uint32 frame_id = 0;
int num_frames = 10;
int total_delay_ms = 0;
- for (int i = 0; i < num_frames; i++) {
- int delay_ms = base::RandInt(-50, 50);
+ 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,
@@ -211,6 +247,11 @@ TEST_F(StatsEventSubscriberTest, PlayoutDelay) {
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) {
@@ -268,11 +309,12 @@ TEST_F(StatsEventSubscriberTest, Packets) {
int total_size = 0;
int retransmit_total_size = 0;
base::TimeDelta total_latency;
- int num_packets_sent = 0;
+ 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.
+ // 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;
@@ -285,7 +327,7 @@ TEST_F(StatsEventSubscriberTest, Packets) {
i,
num_packets - 1,
size);
- num_packets_sent++;
+ num_packets_transmitted++;
int latency_micros = 20000 + base::RandInt(-10000, 10000);
base::TimeDelta latency = base::TimeDelta::FromMicroseconds(latency_micros);
@@ -312,7 +354,7 @@ TEST_F(StatsEventSubscriberTest, Packets) {
num_packets - 1,
size);
retransmit_total_size += size;
- num_packets_sent++;
+ num_packets_transmitted++;
num_packets_retransmitted++;
}
@@ -329,7 +371,7 @@ TEST_F(StatsEventSubscriberTest, Packets) {
num_packets - 1,
size);
retransmit_total_size += size;
- num_packets_sent++;
+ num_packets_transmitted++;
num_packets_retransmitted++;
}
@@ -345,9 +387,19 @@ TEST_F(StatsEventSubscriberTest, Packets) {
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_sent++;
+ num_packets_transmitted++;
num_packets_retransmitted++;
+ num_packets_rtx_rejected++;
}
cast_environment_->Logging()->InsertPacketEvent(received_time,
@@ -394,7 +446,22 @@ TEST_F(StatsEventSubscriberTest, Packets) {
EXPECT_DOUBLE_EQ(
it->second,
- static_cast<double>(num_packets_retransmitted) / num_packets_sent);
+ 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
diff --git a/media/cast/net/cast_transport_defines.h b/media/cast/net/cast_transport_defines.h
index fe329a2f44..63407aa7ba 100644
--- a/media/cast/net/cast_transport_defines.h
+++ b/media/cast/net/cast_transport_defines.h
@@ -61,64 +61,34 @@ typedef std::set<uint16> PacketIdSet;
// Each uint8 represents one cast frame.
typedef std::map<uint8, PacketIdSet> MissingFramesAndPacketsMap;
+class FrameIdWrapHelperTest;
+
// TODO(miu): UGLY IN-LINE DEFINITION IN HEADER FILE! Move to appropriate
// location, separated into .h and .cc files.
class FrameIdWrapHelper {
public:
FrameIdWrapHelper()
- : first_(true), frame_id_wrap_count_(0), range_(kLowRange) {}
+ : largest_frame_id_seen_(kStartFrameId) {}
uint32 MapTo32bitsFrameId(const uint8 over_the_wire_frame_id) {
- if (first_) {
- first_ = false;
- if (over_the_wire_frame_id == 0xff) {
- // Special case for startup.
- return kStartFrameId;
- }
+ uint32 ret = (largest_frame_id_seen_ & ~0xff) | over_the_wire_frame_id;
+ // Add 1000 to both sides to avoid underflows.
+ if (1000 + ret - largest_frame_id_seen_ > 1000 + 127) {
+ ret -= 0x100;
+ } else if (1000 + ret - largest_frame_id_seen_ < 1000 - 128) {
+ ret += 0x100;
}
-
- uint32 wrap_count = frame_id_wrap_count_;
- switch (range_) {
- case kLowRange:
- if (over_the_wire_frame_id > kLowRangeThreshold &&
- over_the_wire_frame_id < kHighRangeThreshold) {
- range_ = kMiddleRange;
- }
- if (over_the_wire_frame_id >= kHighRangeThreshold) {
- // Wrap count was incremented in High->Low transition, but this frame
- // is 'old', actually from before the wrap count got incremented.
- --wrap_count;
- }
- break;
- case kMiddleRange:
- if (over_the_wire_frame_id >= kHighRangeThreshold) {
- range_ = kHighRange;
- }
- break;
- case kHighRange:
- if (over_the_wire_frame_id <= kLowRangeThreshold) {
- // Wrap-around detected.
- range_ = kLowRange;
- ++frame_id_wrap_count_;
- // Frame triggering wrap-around so wrap count should be incremented as
- // as well to match |frame_id_wrap_count_|.
- ++wrap_count;
- }
- break;
+ if (1000 + ret - largest_frame_id_seen_ > 1000) {
+ largest_frame_id_seen_ = ret;
}
- return (wrap_count << 8) + over_the_wire_frame_id;
+ return ret;
}
private:
- enum Range { kLowRange, kMiddleRange, kHighRange, };
-
- static const uint8 kLowRangeThreshold = 63;
- static const uint8 kHighRangeThreshold = 192;
+ friend class FrameIdWrapHelperTest;
static const uint32 kStartFrameId = UINT32_C(0xffffffff);
- bool first_;
- uint32 frame_id_wrap_count_;
- Range range_;
+ uint32 largest_frame_id_seen_;
DISALLOW_COPY_AND_ASSIGN(FrameIdWrapHelper);
};
diff --git a/media/cast/net/frame_id_wrap_helper_test.cc b/media/cast/net/frame_id_wrap_helper_test.cc
index 92a8443533..c40979d928 100644
--- a/media/cast/net/frame_id_wrap_helper_test.cc
+++ b/media/cast/net/frame_id_wrap_helper_test.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include <gtest/gtest.h>
+#include "media/cast/cast_defines.h"
#include "media/cast/net/cast_transport_defines.h"
namespace media {
@@ -13,6 +14,25 @@ class FrameIdWrapHelperTest : public ::testing::Test {
FrameIdWrapHelperTest() {}
virtual ~FrameIdWrapHelperTest() {}
+ void RunOneTest(uint32 starting_point, int iterations) {
+ const int window_size = 127;
+ uint32 window_base = starting_point;
+ frame_id_wrap_helper_.largest_frame_id_seen_ = starting_point;
+ for (int i = 0; i < iterations; i++) {
+ uint32 largest_frame_id_seen =
+ frame_id_wrap_helper_.largest_frame_id_seen_;
+ int offset = rand() % window_size;
+ uint32 frame_id = window_base + offset;
+ uint32 mapped_frame_id =
+ frame_id_wrap_helper_.MapTo32bitsFrameId(frame_id & 0xff);
+ EXPECT_EQ(frame_id, mapped_frame_id)
+ << " Largest ID seen: " << largest_frame_id_seen
+ << " Window base: " << window_base
+ << " Offset: " << offset;
+ window_base = frame_id;
+ }
+ }
+
FrameIdWrapHelper frame_id_wrap_helper_;
DISALLOW_COPY_AND_ASSIGN(FrameIdWrapHelperTest);
@@ -46,5 +66,15 @@ TEST_F(FrameIdWrapHelperTest, OutOfOrder) {
EXPECT_EQ(257u, new_frame_id);
}
+TEST_F(FrameIdWrapHelperTest, Windowed) {
+ srand(0);
+ for (int i = 0; i < 50000 && !HasFailure(); i++) {
+ RunOneTest(i * 4711, 20);
+ // Test wrap-around scenarios.
+ RunOneTest(0x7fffff00ul, 20);
+ RunOneTest(0xffffff00ul, 20);
+ }
+}
+
} // namespace cast
} // namespace media
diff --git a/media/cast/net/udp_transport.cc b/media/cast/net/udp_transport.cc
index 64bcb8fd20..00bd822a4f 100644
--- a/media/cast/net/udp_transport.cc
+++ b/media/cast/net/udp_transport.cc
@@ -172,8 +172,11 @@ bool UdpTransport::SendPacket(PacketRef packet, const base::Closure& cb) {
VLOG(1) << "Unable to set DSCP: " << next_dscp_value_
<< " to socket; Error: " << result;
}
- // Don't change DSCP in next send.
- next_dscp_value_ = net::DSCP_NO_CHANGE;
+
+ if (result != net::ERR_SOCKET_NOT_CONNECTED) {
+ // Don't change DSCP in next send.
+ next_dscp_value_ = net::DSCP_NO_CHANGE;
+ }
}
scoped_refptr<net::IOBuffer> buf =
diff --git a/media/cast/sender/audio_sender.cc b/media/cast/sender/audio_sender.cc
index 154710dbce..704621da38 100644
--- a/media/cast/sender/audio_sender.cc
+++ b/media/cast/sender/audio_sender.cc
@@ -30,7 +30,7 @@ int GetMaxUnackedFrames(base::TimeDelta target_delay) {
// receiver has the ability to drop any one of the packets.
// We send up to three times of the target delay of audio frames.
int frames =
- 1 + 3 * target_delay * kAudioFrameRate / base::TimeDelta::FromSeconds(1);
+ 1 + 2 * target_delay * kAudioFrameRate / base::TimeDelta::FromSeconds(1);
return std::min(kMaxUnackedFrames, frames);
}
} // namespace
diff --git a/media/cast/sender/external_video_encoder.cc b/media/cast/sender/external_video_encoder.cc
index e3abecd407..fbc24d82cb 100644
--- a/media/cast/sender/external_video_encoder.cc
+++ b/media/cast/sender/external_video_encoder.cc
@@ -9,6 +9,7 @@
#include "base/memory/scoped_vector.h"
#include "base/memory/shared_memory.h"
#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/cast/cast_defines.h"
@@ -34,25 +35,6 @@ void LogFrameEncodedEvent(
event_time, media::cast::FRAME_ENCODED, media::cast::VIDEO_EVENT,
rtp_timestamp, frame_id);
}
-
-// Proxy this call to ExternalVideoEncoder on the cast main thread.
-void ProxyCreateVideoEncodeAccelerator(
- const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
- const base::WeakPtr<media::cast::ExternalVideoEncoder>& weak_ptr,
- const media::cast::CreateVideoEncodeMemoryCallback&
- create_video_encode_mem_cb,
- scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
- scoped_ptr<media::VideoEncodeAccelerator> vea) {
- cast_environment->PostTask(
- media::cast::CastEnvironment::MAIN,
- FROM_HERE,
- base::Bind(
- &media::cast::ExternalVideoEncoder::OnCreateVideoEncodeAccelerator,
- weak_ptr,
- create_video_encode_mem_cb,
- encoder_task_runner,
- base::Passed(&vea)));
-}
} // namespace
namespace media {
@@ -76,20 +58,28 @@ class LocalVideoEncodeAcceleratorClient
: public VideoEncodeAccelerator::Client,
public base::RefCountedThreadSafe<LocalVideoEncodeAcceleratorClient> {
public:
- LocalVideoEncodeAcceleratorClient(
+ // Create an instance of this class and post a task to create
+ // video_encode_accelerator_. A ref to |this| will be kept, awaiting reply
+ // via ProxyCreateVideoEncodeAccelerator, which will provide us with the
+ // encoder task runner and vea instance. We cannot be destroyed until we
+ // receive the reply, otherwise the VEA object created may leak.
+ static scoped_refptr<LocalVideoEncodeAcceleratorClient> Create(
scoped_refptr<CastEnvironment> cast_environment,
- scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
- scoped_ptr<media::VideoEncodeAccelerator> vea,
+ const CreateVideoEncodeAcceleratorCallback& create_vea_cb,
const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb,
- const base::WeakPtr<ExternalVideoEncoder>& weak_owner)
- : cast_environment_(cast_environment),
- encoder_task_runner_(encoder_task_runner),
- video_encode_accelerator_(vea.Pass()),
- create_video_encode_memory_cb_(create_video_encode_mem_cb),
- weak_owner_(weak_owner),
- last_encoded_frame_id_(kStartFrameId),
- key_frame_encountered_(false) {
- DCHECK(encoder_task_runner_);
+ const base::WeakPtr<ExternalVideoEncoder>& weak_owner) {
+ scoped_refptr<LocalVideoEncodeAcceleratorClient> client(
+ new LocalVideoEncodeAcceleratorClient(
+ cast_environment, create_video_encode_mem_cb, weak_owner));
+
+ // This will keep a ref to |client|, if weak_owner is destroyed before
+ // ProxyCreateVideoEncodeAccelerator is called, we will stay alive until
+ // we can properly destroy the VEA.
+ create_vea_cb.Run(base::Bind(
+ &LocalVideoEncodeAcceleratorClient::OnCreateVideoEncodeAcceleratorProxy,
+ client));
+
+ return client;
}
// Initialize the real HW encoder.
@@ -100,7 +90,7 @@ class LocalVideoEncodeAcceleratorClient
VideoCodecProfile output_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
switch (video_config.codec) {
case CODEC_VIDEO_VP8:
- output_profile = media::VP8PROFILE_MAIN;
+ output_profile = media::VP8PROFILE_ANY;
break;
case CODEC_VIDEO_H264:
output_profile = media::H264PROFILE_MAIN;
@@ -114,12 +104,16 @@ class LocalVideoEncodeAcceleratorClient
}
max_frame_rate_ = video_config.max_frame_rate;
- if (!video_encode_accelerator_->Initialize(
- media::VideoFrame::I420,
- gfx::Size(video_config.width, video_config.height),
- output_profile,
- video_config.start_bitrate,
- this)) {
+ bool result = video_encode_accelerator_->Initialize(
+ media::VideoFrame::I420,
+ gfx::Size(video_config.width, video_config.height),
+ output_profile,
+ video_config.start_bitrate,
+ this);
+
+ UMA_HISTOGRAM_BOOLEAN("Cast.Sender.VideoEncodeAcceleratorInitializeSuccess",
+ result);
+ if (!result) {
NotifyError(VideoEncodeAccelerator::kInvalidArgumentError);
return;
}
@@ -128,12 +122,22 @@ class LocalVideoEncodeAcceleratorClient
// initialized.
}
- // Free the HW.
+ // Destroy the VEA on the correct thread.
void Destroy() {
DCHECK(encoder_task_runner_);
- DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
+ if (!video_encode_accelerator_)
+ return;
- video_encode_accelerator_.reset();
+ if (encoder_task_runner_->RunsTasksOnCurrentThread()) {
+ video_encode_accelerator_.reset();
+ } else {
+ // We do this instead of just reposting to encoder_task_runner_, because
+ // we are called from the destructor.
+ encoder_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&DestroyVideoEncodeAcceleratorOnEncoderThread,
+ base::Passed(&video_encode_accelerator_)));
+ }
}
void SetBitRate(uint32 bit_rate) {
@@ -165,7 +169,6 @@ class LocalVideoEncodeAcceleratorClient
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
VLOG(1) << "ExternalVideoEncoder NotifyError: " << error;
- video_encode_accelerator_.reset();
cast_environment_->PostTask(
CastEnvironment::MAIN,
FROM_HERE,
@@ -269,6 +272,46 @@ class LocalVideoEncodeAcceleratorClient
}
private:
+ LocalVideoEncodeAcceleratorClient(
+ scoped_refptr<CastEnvironment> cast_environment,
+ const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb,
+ const base::WeakPtr<ExternalVideoEncoder>& weak_owner)
+ : cast_environment_(cast_environment),
+ create_video_encode_memory_cb_(create_video_encode_mem_cb),
+ weak_owner_(weak_owner),
+ last_encoded_frame_id_(kStartFrameId),
+ key_frame_encountered_(false) {}
+
+ // Trampoline VEA creation callback to OnCreateVideoEncodeAccelerator()
+ // on encoder_task_runner. Normally we would just repost the same method to
+ // it, and would not need a separate proxy method, but we can't
+ // ThreadTaskRunnerHandle::Get() in unittests just yet.
+ void OnCreateVideoEncodeAcceleratorProxy(
+ scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
+ scoped_ptr<media::VideoEncodeAccelerator> vea) {
+ encoder_task_runner->PostTask(
+ FROM_HERE,
+ base::Bind(&media::cast::LocalVideoEncodeAcceleratorClient::
+ OnCreateVideoEncodeAccelerator,
+ this,
+ encoder_task_runner,
+ base::Passed(&vea)));
+ }
+
+ void OnCreateVideoEncodeAccelerator(
+ scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
+ scoped_ptr<media::VideoEncodeAccelerator> vea) {
+ encoder_task_runner_ = encoder_task_runner;
+ video_encode_accelerator_.reset(vea.release());
+
+ cast_environment_->PostTask(
+ CastEnvironment::MAIN,
+ FROM_HERE,
+ base::Bind(&ExternalVideoEncoder::OnCreateVideoEncodeAccelerator,
+ weak_owner_,
+ encoder_task_runner_));
+ }
+
// Note: This method can be called on any thread.
void OnCreateSharedMemory(scoped_ptr<base::SharedMemory> memory) {
encoder_task_runner_->PostTask(
@@ -302,9 +345,17 @@ class LocalVideoEncodeAcceleratorClient
base::Bind(&ExternalVideoEncoder::EncoderInitialized, weak_owner_));
}
+ static void DestroyVideoEncodeAcceleratorOnEncoderThread(
+ scoped_ptr<media::VideoEncodeAccelerator> vea) {
+ // VEA::~VEA specialization takes care of calling Destroy() on the VEA impl.
+ }
+
friend class base::RefCountedThreadSafe<LocalVideoEncodeAcceleratorClient>;
- virtual ~LocalVideoEncodeAcceleratorClient() {}
+ virtual ~LocalVideoEncodeAcceleratorClient() {
+ Destroy();
+ DCHECK(!video_encode_accelerator_);
+ }
const scoped_refptr<CastEnvironment> cast_environment_;
scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner_;
@@ -337,17 +388,15 @@ ExternalVideoEncoder::ExternalVideoEncoder(
weak_factory_(this) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
- create_vea_cb.Run(base::Bind(&ProxyCreateVideoEncodeAccelerator,
- cast_environment,
- weak_factory_.GetWeakPtr(),
- create_video_encode_mem_cb));
+ video_accelerator_client_ =
+ LocalVideoEncodeAcceleratorClient::Create(cast_environment_,
+ create_vea_cb,
+ create_video_encode_mem_cb,
+ weak_factory_.GetWeakPtr());
+ DCHECK(video_accelerator_client_);
}
ExternalVideoEncoder::~ExternalVideoEncoder() {
- encoder_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&LocalVideoEncodeAcceleratorClient::Destroy,
- video_accelerator_client_));
}
void ExternalVideoEncoder::EncoderInitialized() {
@@ -361,18 +410,10 @@ void ExternalVideoEncoder::EncoderError() {
}
void ExternalVideoEncoder::OnCreateVideoEncodeAccelerator(
- const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb,
- scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
- scoped_ptr<media::VideoEncodeAccelerator> vea) {
+ scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
encoder_task_runner_ = encoder_task_runner;
- video_accelerator_client_ =
- new LocalVideoEncodeAcceleratorClient(cast_environment_,
- encoder_task_runner,
- vea.Pass(),
- create_video_encode_mem_cb,
- weak_factory_.GetWeakPtr());
encoder_task_runner_->PostTask(
FROM_HERE,
base::Bind(&LocalVideoEncodeAcceleratorClient::Initialize,
@@ -429,6 +470,5 @@ void ExternalVideoEncoder::GenerateKeyFrame() {
void ExternalVideoEncoder::LatestFrameIdToReference(uint32 /*frame_id*/) {
// Do nothing not supported.
}
-
} // namespace cast
} // namespace media
diff --git a/media/cast/sender/external_video_encoder.h b/media/cast/sender/external_video_encoder.h
index 84de7f08f4..269fb3e7c8 100644
--- a/media/cast/sender/external_video_encoder.h
+++ b/media/cast/sender/external_video_encoder.h
@@ -50,11 +50,10 @@ class ExternalVideoEncoder : public VideoEncoder {
virtual void GenerateKeyFrame() OVERRIDE;
virtual void LatestFrameIdToReference(uint32 frame_id) OVERRIDE;
- // Called when a VEA is created.
+ // Called when video_accelerator_client_ has finished creating the VEA and
+ // is ready for use.
void OnCreateVideoEncodeAccelerator(
- const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb,
- scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
- scoped_ptr<media::VideoEncodeAccelerator> vea);
+ scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner);
protected:
void EncoderInitialized();
diff --git a/media/cast/sender/external_video_encoder_unittest.cc b/media/cast/sender/external_video_encoder_unittest.cc
index 9461a11f26..385b121695 100644
--- a/media/cast/sender/external_video_encoder_unittest.cc
+++ b/media/cast/sender/external_video_encoder_unittest.cc
@@ -23,12 +23,26 @@ using testing::_;
namespace {
-void CreateVideoEncodeAccelerator(
- const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
- scoped_ptr<VideoEncodeAccelerator> fake_vea,
- const ReceiveVideoEncodeAcceleratorCallback& callback) {
- callback.Run(task_runner, fake_vea.Pass());
-}
+class VEAFactory {
+ public:
+ VEAFactory(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ scoped_ptr<VideoEncodeAccelerator> vea)
+ : task_runner_(task_runner), vea_(vea.Pass()) {}
+
+ void CreateVideoEncodeAccelerator(
+ const ReceiveVideoEncodeAcceleratorCallback& callback) {
+ create_cb_ = callback;
+ }
+
+ void FinishCreatingVideoEncodeAccelerator() {
+ create_cb_.Run(task_runner_, vea_.Pass());
+ }
+
+ private:
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_ptr<VideoEncodeAccelerator> vea_;
+ ReceiveVideoEncodeAcceleratorCallback create_cb_;
+};
void CreateSharedMemory(
size_t size, const ReceiveVideoEncodeMemoryCallback& callback) {
@@ -116,13 +130,14 @@ class ExternalVideoEncoderTest : public ::testing::Test {
fake_vea_ = new test::FakeVideoEncodeAccelerator(task_runner_,
&stored_bitrates_);
scoped_ptr<VideoEncodeAccelerator> fake_vea(fake_vea_);
- video_encoder_.reset(
- new ExternalVideoEncoder(cast_environment_,
- video_config_,
- base::Bind(&CreateVideoEncodeAccelerator,
- task_runner_,
- base::Passed(&fake_vea)),
- base::Bind(&CreateSharedMemory)));
+ VEAFactory vea_factory(task_runner_, fake_vea.Pass());
+ video_encoder_.reset(new ExternalVideoEncoder(
+ cast_environment_,
+ video_config_,
+ base::Bind(&VEAFactory::CreateVideoEncodeAccelerator,
+ base::Unretained(&vea_factory)),
+ base::Bind(&CreateSharedMemory)));
+ vea_factory.FinishCreatingVideoEncodeAccelerator();
}
virtual ~ExternalVideoEncoderTest() {}
@@ -193,5 +208,35 @@ TEST_F(ExternalVideoEncoderTest, StreamHeader) {
task_runner_->RunTasks();
}
+// Verify that everything goes well even if ExternalVideoEncoder is destroyed
+// before it has a chance to receive the VEA creation callback.
+TEST(ExternalVideoEncoderEarlyDestroyTest, DestroyBeforeVEACreatedCallback) {
+ VideoSenderConfig video_config;
+ base::SimpleTestTickClock* testing_clock = new base::SimpleTestTickClock();
+ scoped_refptr<test::FakeSingleThreadTaskRunner> task_runner(
+ new test::FakeSingleThreadTaskRunner(testing_clock));
+ scoped_refptr<CastEnvironment> cast_environment(
+ new CastEnvironment(scoped_ptr<base::TickClock>(testing_clock).Pass(),
+ task_runner,
+ task_runner,
+ task_runner));
+
+ std::vector<uint32> stored_bitrates;
+ scoped_ptr<VideoEncodeAccelerator> fake_vea(
+ new test::FakeVideoEncodeAccelerator(task_runner, &stored_bitrates));
+ VEAFactory vea_factory(task_runner, fake_vea.Pass());
+
+ scoped_ptr<ExternalVideoEncoder> video_encoder(new ExternalVideoEncoder(
+ cast_environment,
+ video_config,
+ base::Bind(&VEAFactory::CreateVideoEncodeAccelerator,
+ base::Unretained(&vea_factory)),
+ base::Bind(&CreateSharedMemory)));
+
+ video_encoder.reset();
+ vea_factory.FinishCreatingVideoEncodeAccelerator();
+ task_runner->RunTasks();
+}
+
} // namespace cast
} // namespace media
diff --git a/media/cast/test/fake_video_encode_accelerator.cc b/media/cast/test/fake_video_encode_accelerator.cc
index a391a9a81c..0442c0c928 100644
--- a/media/cast/test/fake_video_encode_accelerator.cc
+++ b/media/cast/test/fake_video_encode_accelerator.cc
@@ -38,7 +38,7 @@ bool FakeVideoEncodeAccelerator::Initialize(
uint32 initial_bitrate,
Client* client) {
client_ = client;
- if (output_profile != media::VP8PROFILE_MAIN &&
+ if (output_profile != media::VP8PROFILE_ANY &&
output_profile != media::H264PROFILE_MAIN) {
return false;
}
diff --git a/media/cast/test/simulator.cc b/media/cast/test/simulator.cc
index 157a18fcc3..b3360c8722 100644
--- a/media/cast/test/simulator.cc
+++ b/media/cast/test/simulator.cc
@@ -283,10 +283,12 @@ void RunSimulation(const base::FilePath& source_path,
// Connect sender to receiver. This initializes the pipe.
receiver_to_sender.Initialize(
- ipp.NewBuffer(128 * 1024), transport_sender->PacketReceiverForTesting(),
+ ipp.NewBuffer(128 * 1024).Pass(),
+ transport_sender->PacketReceiverForTesting(),
task_runner, &testing_clock);
sender_to_receiver.Initialize(
- ipp.NewBuffer(128 * 1024), cast_receiver->packet_receiver(), task_runner,
+ ipp.NewBuffer(128 * 1024).Pass(),
+ cast_receiver->packet_receiver(), task_runner,
&testing_clock);
// Start receiver.
diff --git a/media/cast/test/utility/netload.py b/media/cast/test/utility/netload.py
new file mode 100755
index 0000000000..248eca2bbe
--- /dev/null
+++ b/media/cast/test/utility/netload.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+# 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.
+#
+# Simple client/server script for generating an unlimited TCP stream.
+# see shadow.sh for how it's intended to be used.
+
+import socket
+import sys
+import thread
+import time
+
+sent = 0
+received = 0
+
+def Sink(socket):
+ global received
+ while True:
+ tmp = socket.recv(4096)
+ received += len(tmp)
+ if not tmp:
+ break;
+
+def Spew(socket):
+ global sent
+ data = " " * 4096
+ while True:
+ tmp = socket.send(data)
+ if tmp <= 0:
+ break
+ sent += tmp;
+
+def PrintStats():
+ global sent
+ global received
+ last_report = time.time()
+ last_sent = 0
+ last_received = 0
+ while True:
+ time.sleep(5)
+ now = time.time();
+ sent_now = sent
+ received_now = received
+ delta = now - last_report
+ sent_mbps = ((sent_now - last_sent) * 8.0 / 1000000) / delta
+ received_mbps = ((received_now - last_received) * 8.0 / 1000000) / delta
+ print "Sent: %5.2f mbps Received: %5.2f mbps" % (sent_mbps, received_mbps)
+ last_report = now
+ last_sent = sent_now
+ last_received = received_now
+
+def Serve(socket, upload=True, download=True):
+ while True:
+ (s, addr) = socket.accept()
+ if upload:
+ thread.start_new_thread(Spew, (s,))
+ if download:
+ thread.start_new_thread(Sink, (s,))
+
+def Receiver(port, upload=True, download=True):
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.bind(('', port))
+ s.listen(5)
+ thread.start_new_thread(Serve, (s, upload, download))
+
+
+def Connect(to_hostport, upload=True, download=False):
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.connect(to_hostport)
+ if upload:
+ thread.start_new_thread(Spew, (s,))
+ if download:
+ thread.start_new_thread(Sink, (s,))
+
+
+def Usage():
+ print "One of:"
+ print "%s listen <port>" % sys.arv[0]
+ print "%s upload <host> <port>" % sys.arv[0]
+ print "%s download <host> <port>" % sys.arv[0]
+ print "%s updown <host> <port>" % sys.arv[0]
+ sys.exit(1)
+
+if len(sys.argv) < 2:
+ Usage()
+if sys.argv[1] == "listen":
+ Receiver(int(sys.argv[2]))
+elif sys.argv[1] == "download":
+ Connect( (sys.argv[2], int(sys.argv[3])), upload=False, download=True)
+elif sys.argv[1] == "upload":
+ Connect( (sys.argv[2], int(sys.argv[3])), upload=True, download=False)
+elif sys.argv[1] == "updown":
+ Connect( (sys.argv[2], int(sys.argv[3])), upload=True, download=True)
+else:
+ Usage()
+
+PrintStats()
diff --git a/media/cast/test/utility/shadow.sh b/media/cast/test/utility/shadow.sh
new file mode 100755
index 0000000000..4163fd0859
--- /dev/null
+++ b/media/cast/test/utility/shadow.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+# 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.
+#
+# The purpose of this script is to set up all the neccessary magic to
+# pipe network traffic through a user-space process. That user-space
+# process can then delay, reorder and drop packets as it pleases to
+# emulate various network environments.
+#
+# The script currently assumes that you communicate with your cast streaming
+# receiver through eth1. After running "shadow.sh start", your network will
+# look something like this:
+#
+# +--------------------------------------------------+
+# | Your linux machine |
+# | +---------------+ |
+# cast | |shadowbr bridge| +-------------+ |
+# streaming <--+-+---> eth1 | |routing table| |
+# receiver | | tap2 <---+-> tap_proxy <-+-> tap1 | |
+# | | +->veth | | eth0 <----+--+->internet
+# | +--+------------+ | lo | |
+# | | +-------------+ |
+# | | +------------------+ ^ |
+# | | |shadow container | | |
+# | +------+-->veth | chrome |
+# | | netload.py server| netload.py client|
+# | +------------------+ |
+# +--------------------------------------------------+
+#
+# The result should be that all traffic to/from the cast streaming receiver
+# will go through tap_proxy. All traffic to/from the shadow container
+# will also go through the tap_proxy. (A container is kind of like a
+# virtual machine, but more lightweight.) Running "shadow.sh start" does
+# not start the tap_proxy, so you'll have to start it manually with
+# the command "tap_proxy tap1 tap2 <network_profile>" where
+# <network_profile> is one of "perfect", "good", "wifi", "bad" or "evil".
+#
+# While testing mirroring, we can now generate TCP traffic through
+# the tap proxy by talking to the netload server inside the "shadow"
+# container by using the following command:
+#
+# $ netload.py upload IP PORT
+#
+# The IP and PORT are printed out by this script when you run
+# "shadow.sh start", but will generally be the *.*.*.253 address
+# of the eth1 network, so hopefully that's not already taken...
+
+set -x
+
+DEV=eth1
+TAP1=tap1
+TAP2=tap2
+
+IP="$(ifconfig $DEV | sed -n 's@.*inet addr:\([^ ]*\).*@\1@gp')"
+MASK="$(ifconfig $DEV | sed -n 's@.*Mask:\([^ ]*\).*@\1@gp')"
+BCAST="$(ifconfig $DEV | sed -n 's@.*Bcast:\([^ ]*\).*@\1@gp')"
+NET=$(route -n | grep $DEV | head -1 | awk '{print $1}')
+DIR=$(dirname "$0")
+
+case "$MASK" in
+ 255.255.255.0) MASK_BITS=24 ;;
+ 255.255.0.0) MASK_BITS=16 ;;
+ 255.0.0.0) MASK_BITS=8 ;;
+ *)
+ echo "Unknown network mask"
+ exit 1
+ ;;
+esac
+
+SHADOWIP="$(echo $IP | sed 's@[^.]*$@@g')253"
+SHADOWCONF="/tmp/shadowconf.$$"
+cat <<EOF >$SHADOWCONF
+lxc.utsname = shadow
+lxc.network.type = veth
+lxc.network.link = shadowbr
+lxc.network.flags = up
+lxc.network.ipv4 = $SHADOWIP/$MASK_BITS
+lxc.network.ipv4.gateway = $IP
+lxc.kmsg = 0
+EOF
+
+trap "rm $SHADOWCONF" SIGINT SIGTERM EXIT
+LXC_COMMON="-n shadow -f $SHADOWCONF"
+
+case "$1" in
+ start)
+ openvpn --mktun --dev $TAP1
+ openvpn --mktun --dev $TAP2
+ ifconfig $TAP1 $IP netmask $MASK broadcast $BCAST up
+ ifconfig $TAP2 up
+ route add -net $NET netmask $MASK $TAP1
+ brctl addbr shadowbr
+ brctl addif shadowbr $TAP2 $DEV
+ ifconfig shadowbr up
+ lxc-create $LXC_COMMON
+ lxc-execute $LXC_COMMON -- \
+ "$DIRNAME/netload.py listen 9999" >/dev/null </dev/null 2>&1 &
+ echo "Now run: tap_proxy $TAP1 $TAP2 wifi"
+ echo "Data sink/source is available on $SHADOWIP 9999"
+ ;;
+
+ stop)
+ lxc-kill -n shadow
+ sleep 1
+ lxc-destroy $LXC_COMMON
+ ifconfig $TAP1 down
+ ifconfig $TAP2 down
+ ifconfig shadowbr down
+ brctl delbr shadowbr
+ openvpn --rmtun --dev $TAP1
+ openvpn --rmtun --dev $TAP2
+ ;;
+
+ *)
+ echo "$0 start/stop"
+ echo "Read $0 for more information."
+ ;;
+esac
+
+
diff --git a/media/cast/test/utility/tap_proxy.cc b/media/cast/test/utility/tap_proxy.cc
new file mode 100644
index 0000000000..7827bf976f
--- /dev/null
+++ b/media/cast/test/utility/tap_proxy.cc
@@ -0,0 +1,318 @@
+// 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 <fcntl.h>
+#include <linux/if_tun.h>
+#include <linux/types.h>
+#include <math.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <deque>
+#include <map>
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/time/default_tick_clock.h"
+#include "media/cast/test/utility/udp_proxy.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/udp/udp_socket.h"
+
+namespace media {
+namespace cast {
+namespace test {
+
+const size_t kMaxPacketSize = 4096;
+
+class SendToFDPipe : public PacketPipe {
+ public:
+ explicit SendToFDPipe(int fd) : fd_(fd) {
+ }
+ virtual void Send(scoped_ptr<Packet> packet) OVERRIDE {
+ while (1) {
+ int written = write(
+ fd_,
+ reinterpret_cast<char*>(&packet->front()),
+ packet->size());
+ if (written < 0) {
+ if (errno == EINTR) continue;
+ perror("write");
+ exit(1);
+ }
+ if (written != static_cast<int>(packet->size())) {
+ fprintf(stderr, "Truncated write!\n");
+ exit(1);
+ }
+ break;
+ }
+ }
+ private:
+ int fd_;
+};
+
+class QueueManager : public base::MessageLoopForIO::Watcher {
+ public:
+ QueueManager(int input_fd,
+ int output_fd,
+ scoped_ptr<PacketPipe> pipe) :
+ input_fd_(input_fd),
+ packet_pipe_(pipe.Pass()) {
+
+ CHECK(base::MessageLoopForIO::current()->WatchFileDescriptor(
+ input_fd_, true, base::MessageLoopForIO::WATCH_READ,
+ &read_socket_watcher_, this));
+
+ scoped_ptr<PacketPipe> tmp(new SendToFDPipe(output_fd));
+ if (packet_pipe_) {
+ packet_pipe_->AppendToPipe(tmp.Pass());
+ } else {
+ packet_pipe_ = tmp.Pass();
+ }
+ packet_pipe_->InitOnIOThread(base::MessageLoopProxy::current(),
+ &tick_clock_);
+ }
+
+ virtual ~QueueManager() {
+ }
+
+ // MessageLoopForIO::Watcher methods
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
+ scoped_ptr<Packet> packet(new Packet(kMaxPacketSize));
+ int nread = read(input_fd_,
+ reinterpret_cast<char*>(&packet->front()),
+ kMaxPacketSize);
+ if (nread < 0) {
+ if (errno == EINTR) return;
+ perror("read");
+ exit(1);
+ }
+ if (nread == 0) return;
+ packet->resize(nread);
+ packet_pipe_->Send(packet.Pass());
+ }
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {
+ NOTREACHED();
+ }
+
+ private:
+ int input_fd_;
+ scoped_ptr<PacketPipe> packet_pipe_;
+ base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_;
+ base::DefaultTickClock tick_clock_;
+};
+
+} // namespace test
+} // namespace cast
+} // namespace media
+
+base::TimeTicks last_printout;
+
+class ByteCounter {
+ public:
+ ByteCounter() : bytes_(0), packets_(0) {
+ push(base::TimeTicks::Now());
+ }
+
+ base::TimeDelta time_range() {
+ return time_data_.back() - time_data_.front();
+ }
+
+ void push(base::TimeTicks now) {
+ byte_data_.push_back(bytes_);
+ packet_data_.push_back(packets_);
+ time_data_.push_back(now);
+ while (time_range().InSeconds() > 10) {
+ byte_data_.pop_front();
+ packet_data_.pop_front();
+ time_data_.pop_front();
+ }
+ }
+
+ double megabits_per_second() {
+ double megabits = (byte_data_.back() - byte_data_.front()) * 8 / 1E6;
+ return megabits / time_range().InSecondsF();
+ }
+
+ double packets_per_second() {
+ double packets = packet_data_.back()- packet_data_.front();
+ return packets / time_range().InSecondsF();
+ }
+
+ void Increment(uint64 x) {
+ bytes_ += x;
+ packets_ ++;
+ }
+
+ private:
+ uint64 bytes_;
+ uint64 packets_;
+ std::deque<uint64> byte_data_;
+ std::deque<uint64> packet_data_;
+ std::deque<base::TimeTicks> time_data_;
+};
+
+ByteCounter in_pipe_input_counter;
+ByteCounter in_pipe_output_counter;
+ByteCounter out_pipe_input_counter;
+ByteCounter out_pipe_output_counter;
+
+class ByteCounterPipe : public media::cast::test::PacketPipe {
+ public:
+ ByteCounterPipe(ByteCounter* counter) : counter_(counter) {}
+ virtual void Send(scoped_ptr<media::cast::Packet> packet)
+ OVERRIDE {
+ counter_->Increment(packet->size());
+ pipe_->Send(packet.Pass());
+ }
+ private:
+ ByteCounter* counter_;
+};
+
+void SetupByteCounters(scoped_ptr<media::cast::test::PacketPipe>* pipe,
+ ByteCounter* pipe_input_counter,
+ ByteCounter* pipe_output_counter) {
+ media::cast::test::PacketPipe* new_pipe =
+ new ByteCounterPipe(pipe_input_counter);
+ new_pipe->AppendToPipe(pipe->Pass());
+ new_pipe->AppendToPipe(
+ scoped_ptr<media::cast::test::PacketPipe>(
+ new ByteCounterPipe(pipe_output_counter)).Pass());
+ pipe->reset(new_pipe);
+}
+
+void CheckByteCounters() {
+ base::TimeTicks now = base::TimeTicks::Now();
+ in_pipe_input_counter.push(now);
+ in_pipe_output_counter.push(now);
+ out_pipe_input_counter.push(now);
+ out_pipe_output_counter.push(now);
+ if ((now - last_printout).InSeconds() >= 5) {
+ fprintf(stderr, "Sending : %5.2f / %5.2f mbps %6.2f / %6.2f packets / s\n",
+ in_pipe_output_counter.megabits_per_second(),
+ in_pipe_input_counter.megabits_per_second(),
+ in_pipe_output_counter.packets_per_second(),
+ in_pipe_input_counter.packets_per_second());
+ fprintf(stderr, "Receiving: %5.2f / %5.2f mbps %6.2f / %6.2f packets / s\n",
+ out_pipe_output_counter.megabits_per_second(),
+ out_pipe_input_counter.megabits_per_second(),
+ out_pipe_output_counter.packets_per_second(),
+ out_pipe_input_counter.packets_per_second());
+
+ last_printout = now;
+ }
+ base::MessageLoopProxy::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&CheckByteCounters),
+ base::TimeDelta::FromMilliseconds(100));
+}
+
+int tun_alloc(char *dev, int flags) {
+ struct ifreq ifr;
+ int fd, err;
+ const char *clonedev = "/dev/net/tun";
+
+ /* Arguments taken by the function:
+ *
+ * char *dev: the name of an interface (or '\0'). MUST have enough
+ * space to hold the interface name if '\0' is passed
+ * int flags: interface flags (eg, IFF_TUN etc.)
+ */
+
+ /* open the clone device */
+ if( (fd = open(clonedev, O_RDWR)) < 0 ) {
+ return fd;
+ }
+
+ /* preparation of the struct ifr, of type "struct ifreq" */
+ memset(&ifr, 0, sizeof(ifr));
+
+ ifr.ifr_flags = flags; /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */
+
+ if (*dev) {
+ /* if a device name was specified, put it in the structure; otherwise,
+ * the kernel will try to allocate the "next" device of the
+ * specified type */
+ strncpy(ifr.ifr_name, dev, IFNAMSIZ);
+ }
+
+ /* try to create the device */
+ if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) {
+ close(fd);
+ return err;
+ }
+
+ if (!*dev) {
+ /* if the operation was successful, write back the name of the
+ * interface to the variable "dev", so the caller can know
+ * it. Note that the caller MUST reserve space in *dev (see calling
+ * code below) */
+ strcpy(dev, ifr.ifr_name);
+ }
+
+ /* this is the special file descriptor that the caller will use to talk
+ * with the virtual interface */
+ return fd;
+}
+
+
+int main(int argc, char **argv) {
+ base::AtExitManager exit_manager;
+ CommandLine::Init(argc, argv);
+ InitLogging(logging::LoggingSettings());
+
+ if (argc < 4) {
+ fprintf(stderr, "Usage: tap_proxy tap1 tap2 type\n");
+ fprintf(stderr,
+ "Where 'type' is one of perfect, good, wifi, bad or evil\n");
+ exit(1);
+ }
+
+ scoped_ptr<media::cast::test::PacketPipe> in_pipe, out_pipe;
+ std::string network_type = argv[3];
+ if (network_type == "perfect") {
+ // No action needed.
+ } else if (network_type == "good") {
+ in_pipe = media::cast::test::GoodNetwork().Pass();
+ out_pipe = media::cast::test::GoodNetwork().Pass();
+ } else if (network_type == "wifi") {
+ in_pipe = media::cast::test::WifiNetwork().Pass();
+ out_pipe = media::cast::test::WifiNetwork().Pass();
+ } else if (network_type == "bad") {
+ in_pipe = media::cast::test::BadNetwork().Pass();
+ out_pipe = media::cast::test::BadNetwork().Pass();
+ } else if (network_type == "evil") {
+ in_pipe = media::cast::test::EvilNetwork().Pass();
+ out_pipe = media::cast::test::EvilNetwork().Pass();
+ } else {
+ fprintf(stderr, "Unknown network type.\n");
+ exit(1);
+ }
+
+ SetupByteCounters(&in_pipe, &in_pipe_input_counter, &in_pipe_output_counter);
+ SetupByteCounters(
+ &out_pipe, &out_pipe_input_counter, &out_pipe_output_counter);
+
+ int fd1 = tun_alloc(argv[1], IFF_TAP);
+ int fd2 = tun_alloc(argv[2], IFF_TAP);
+
+ base::MessageLoopForIO message_loop;
+ last_printout = base::TimeTicks::Now();
+ media::cast::test::QueueManager qm1(fd1, fd2, in_pipe.Pass());
+ media::cast::test::QueueManager qm2(fd2, fd1, out_pipe.Pass());
+ CheckByteCounters();
+ printf("Press Ctrl-C when done.\n");
+ message_loop.Run();
+}
diff --git a/media/cast/test/utility/udp_proxy.cc b/media/cast/test/utility/udp_proxy.cc
index 4714b7ed67..e71678c482 100644
--- a/media/cast/test/utility/udp_proxy.cc
+++ b/media/cast/test/utility/udp_proxy.cc
@@ -68,6 +68,7 @@ class Buffer : public PacketPipe {
private:
void Schedule() {
+ last_schedule_ = clock_->NowTicks();
double megabits = buffer_.front()->size() * 8 / 1000000.0;
double seconds = megabits / max_megabits_per_second_;
int64 microseconds = static_cast<int64>(seconds * 1E6);
@@ -78,17 +79,28 @@ class Buffer : public PacketPipe {
}
void ProcessBuffer() {
- CHECK(!buffer_.empty());
- scoped_ptr<Packet> packet(buffer_.front().release());
- buffer_size_ -= packet->size();
- buffer_.pop_front();
- pipe_->Send(packet.Pass());
+ int64 bytes_to_send = static_cast<int64>(
+ (clock_->NowTicks() - last_schedule_).InSecondsF() *
+ max_megabits_per_second_ * 1E6 / 8);
+ if (bytes_to_send < static_cast<int64>(buffer_.front()->size())) {
+ bytes_to_send = buffer_.front()->size();
+ }
+ while (!buffer_.empty() &&
+ static_cast<int64>(buffer_.front()->size()) <= bytes_to_send) {
+ CHECK(!buffer_.empty());
+ scoped_ptr<Packet> packet(buffer_.front().release());
+ bytes_to_send -= packet->size();
+ buffer_size_ -= packet->size();
+ buffer_.pop_front();
+ pipe_->Send(packet.Pass());
+ }
if (!buffer_.empty()) {
Schedule();
}
}
std::deque<linked_ptr<Packet> > buffer_;
+ base::TimeTicks last_schedule_;
size_t buffer_size_;
size_t max_buffer_size_;
double max_megabits_per_second_; // megabits per second
@@ -188,7 +200,11 @@ class RandomSortedDelay : public PacketPipe {
virtual void Send(scoped_ptr<Packet> packet) OVERRIDE {
buffer_.push_back(linked_ptr<Packet>(packet.release()));
if (buffer_.size() == 1) {
- Schedule();
+ next_send_ = std::max(
+ clock_->NowTicks() +
+ base::TimeDelta::FromSecondsD(base::RandDouble() * random_delay_),
+ next_send_);
+ ProcessBuffer();
}
}
virtual void InitOnIOThread(
@@ -212,38 +228,34 @@ class RandomSortedDelay : public PacketPipe {
}
void CauseExtraDelay() {
- block_until_ = clock_->NowTicks() +
+ next_send_ = std::max<base::TimeTicks>(
+ clock_->NowTicks() +
base::TimeDelta::FromMicroseconds(
- static_cast<int64>(extra_delay_ * 1E6));
+ static_cast<int64>(extra_delay_ * 1E6)),
+ next_send_);
// An extra delay just happened, wait up to seconds_between_extra_delay_*2
// before scheduling another one to make the average equal to
// seconds_between_extra_delay_.
ScheduleExtraDelay(2.0);
}
- void Schedule() {
- double seconds = base::RandDouble() * random_delay_;
- base::TimeDelta block_time = block_until_ - base::TimeTicks::Now();
- base::TimeDelta delay_time =
- base::TimeDelta::FromMicroseconds(
- static_cast<int64>(seconds * 1E6));
- if (block_time > delay_time) {
- block_time = delay_time;
- }
+ void ProcessBuffer() {
+ base::TimeTicks now = clock_->NowTicks();
+ while (!buffer_.empty() && next_send_ <= now) {
+ scoped_ptr<Packet> packet(buffer_.front().release());
+ pipe_->Send(packet.Pass());
+ buffer_.pop_front();
- task_runner_->PostDelayedTask(FROM_HERE,
- base::Bind(&RandomSortedDelay::ProcessBuffer,
- weak_factory_.GetWeakPtr()),
- delay_time);
- }
+ next_send_ += base::TimeDelta::FromSecondsD(
+ base::RandDouble() * random_delay_);
+ }
- void ProcessBuffer() {
- CHECK(!buffer_.empty());
- scoped_ptr<Packet> packet(buffer_.front().release());
- pipe_->Send(packet.Pass());
- buffer_.pop_front();
if (!buffer_.empty()) {
- Schedule();
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&RandomSortedDelay::ProcessBuffer,
+ weak_factory_.GetWeakPtr()),
+ next_send_ - now);
}
}
@@ -253,6 +265,7 @@ class RandomSortedDelay : public PacketPipe {
double extra_delay_;
double seconds_between_extra_delay_;
base::WeakPtrFactory<RandomSortedDelay> weak_factory_;
+ base::TimeTicks next_send_;
};
scoped_ptr<PacketPipe> NewRandomSortedDelay(
@@ -536,6 +549,17 @@ void BuildPipe(scoped_ptr<PacketPipe>* pipe, PacketPipe* next) {
}
} // namespace
+scoped_ptr<PacketPipe> GoodNetwork() {
+ // This represents the buffer on the sender.
+ scoped_ptr<PacketPipe> pipe;
+ BuildPipe(&pipe, new Buffer(2 << 20, 50));
+ BuildPipe(&pipe, new ConstantDelay(1E-3));
+ BuildPipe(&pipe, new RandomSortedDelay(1E-3, 2E-3, 3));
+ // This represents the buffer on the receiving device.
+ BuildPipe(&pipe, new Buffer(2 << 20, 50));
+ return pipe.Pass();
+}
+
scoped_ptr<PacketPipe> WifiNetwork() {
// This represents the buffer on the sender.
scoped_ptr<PacketPipe> pipe;
diff --git a/media/cast/test/utility/udp_proxy.h b/media/cast/test/utility/udp_proxy.h
index ea50a2c86a..6c72fb6576 100644
--- a/media/cast/test/utility/udp_proxy.h
+++ b/media/cast/test/utility/udp_proxy.h
@@ -156,6 +156,10 @@ scoped_ptr<PacketPipe> NewNetworkGlitchPipe(double average_work_time,
double average_outage_time);
// This method builds a stack of PacketPipes to emulate a reasonably
+// good network. ~50mbit, ~3ms latency, no packet loss unless saturated.
+scoped_ptr<PacketPipe> GoodNetwork();
+
+// This method builds a stack of PacketPipes to emulate a reasonably
// good wifi network. ~20mbit, 1% packet loss, ~3ms latency.
scoped_ptr<PacketPipe> WifiNetwork();