diff options
author | Torne (Richard Coles) <torne@google.com> | 2014-08-19 13:00:08 +0100 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2014-08-19 13:00:08 +0100 |
commit | 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38 (patch) | |
tree | 8d824ad26fac42e008142b86aa9631b2be7e4705 /media/cast | |
parent | 4f7316adb45db5ec3c9c1181ba9510c004566df8 (diff) | |
download | chromium_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')
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(); |