Network up/down signaling in Call.

BUG=2429
R=mflodman@webrtc.org, stefan@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/13109005

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7044 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/media/webrtc/webrtcvideoengine2.cc b/talk/media/webrtc/webrtcvideoengine2.cc
index 97d274d..44c1bf8 100644
--- a/talk/media/webrtc/webrtcvideoengine2.cc
+++ b/talk/media/webrtc/webrtcvideoengine2.cc
@@ -1153,7 +1153,9 @@
 }
 
 void WebRtcVideoChannel2::OnReadyToSend(bool ready) {
-  LOG(LS_VERBOSE) << "OnReadySend: " << (ready ? "Ready." : "Not ready.");
+  LOG(LS_VERBOSE) << "OnReadyToSend: " << (ready ? "Ready." : "Not ready.");
+  call_->SignalNetworkState(ready ? webrtc::Call::kNetworkUp
+                                  : webrtc::Call::kNetworkDown);
 }
 
 bool WebRtcVideoChannel2::MuteStream(uint32 ssrc, bool mute) {
diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.cc b/talk/media/webrtc/webrtcvideoengine2_unittest.cc
index 4f69f6d..2178a68 100644
--- a/talk/media/webrtc/webrtcvideoengine2_unittest.cc
+++ b/talk/media/webrtc/webrtcvideoengine2_unittest.cc
@@ -157,7 +157,9 @@
 void FakeVideoReceiveStream::GetCurrentReceiveCodec(webrtc::VideoCodec* codec) {
 }
 
-FakeCall::FakeCall() { SetVideoCodecs(GetDefaultVideoCodecs()); }
+FakeCall::FakeCall() : network_state_(kNetworkUp) {
+  SetVideoCodecs(GetDefaultVideoCodecs());
+}
 
 FakeCall::~FakeCall() {
   EXPECT_EQ(0u, video_send_streams_.size());
@@ -218,6 +220,10 @@
   return codecs;
 }
 
+webrtc::Call::NetworkState FakeCall::GetNetworkState() const {
+  return network_state_;
+}
+
 webrtc::VideoSendStream* FakeCall::CreateVideoSendStream(
     const webrtc::VideoSendStream::Config& config,
     const std::vector<webrtc::VideoStream>& video_streams,
@@ -274,6 +280,10 @@
   return 0;
 }
 
+void FakeCall::SignalNetworkState(webrtc::Call::NetworkState state) {
+  network_state_ = state;
+}
+
 FakeWebRtcVideoChannel2::FakeWebRtcVideoChannel2(
     FakeCall* call,
     WebRtcVideoEngine2* engine,
@@ -289,6 +299,7 @@
 VoiceMediaChannel* FakeWebRtcVideoChannel2::GetVoiceChannel() {
   return voice_channel_;
 }
+
 FakeCall* FakeWebRtcVideoChannel2::GetFakeCall() {
   return fake_call_;
 }
@@ -1614,8 +1625,17 @@
   FAIL() << "Not implemented.";  // TODO(pbos): Implement.
 }
 
-TEST_F(WebRtcVideoChannel2Test, DISABLED_OnReadyToSend) {
-  FAIL() << "Not implemented.";  // TODO(pbos): Implement.
+TEST_F(WebRtcVideoChannel2Test, OnReadyToSendSignalsNetworkState) {
+  EXPECT_EQ(webrtc::Call::kNetworkUp,
+            fake_channel_->GetFakeCall()->GetNetworkState());
+
+  channel_->OnReadyToSend(false);
+  EXPECT_EQ(webrtc::Call::kNetworkDown,
+            fake_channel_->GetFakeCall()->GetNetworkState());
+
+  channel_->OnReadyToSend(true);
+  EXPECT_EQ(webrtc::Call::kNetworkUp,
+            fake_channel_->GetFakeCall()->GetNetworkState());
 }
 
 TEST_F(WebRtcVideoChannel2Test, DISABLED_CaptureFrameTimestampToNtpTimestamp) {
diff --git a/talk/media/webrtc/webrtcvideoengine2_unittest.h b/talk/media/webrtc/webrtcvideoengine2_unittest.h
index 54e6f06..5aaa3e3 100644
--- a/talk/media/webrtc/webrtcvideoengine2_unittest.h
+++ b/talk/media/webrtc/webrtcvideoengine2_unittest.h
@@ -103,6 +103,8 @@
 
   std::vector<webrtc::VideoCodec> GetDefaultVideoCodecs();
 
+  webrtc::Call::NetworkState GetNetworkState() const;
+
  private:
   virtual webrtc::VideoSendStream* CreateVideoSendStream(
       const webrtc::VideoSendStream::Config& config,
@@ -122,6 +124,9 @@
   virtual uint32_t SendBitrateEstimate() OVERRIDE;
   virtual uint32_t ReceiveBitrateEstimate() OVERRIDE;
 
+  virtual void SignalNetworkState(webrtc::Call::NetworkState state) OVERRIDE;
+
+  webrtc::Call::NetworkState network_state_;
   std::vector<webrtc::VideoCodec> codecs_;
   std::vector<FakeVideoSendStream*> video_send_streams_;
   std::vector<FakeVideoReceiveStream*> video_receive_streams_;
diff --git a/webrtc/call.h b/webrtc/call.h
index 86cf1c6..3030074 100644
--- a/webrtc/call.h
+++ b/webrtc/call.h
@@ -56,6 +56,10 @@
 // etc.
 class Call {
  public:
+  enum NetworkState {
+    kNetworkUp,
+    kNetworkDown,
+  };
   struct Config {
     explicit Config(newapi::Transport* send_transport)
         : webrtc_config(NULL),
@@ -111,6 +115,8 @@
   // differ from the actual receive bitrate.
   virtual uint32_t ReceiveBitrateEstimate() = 0;
 
+  virtual void SignalNetworkState(NetworkState state) = 0;
+
   virtual ~Call() {}
 };
 }  // namespace webrtc
diff --git a/webrtc/video/call.cc b/webrtc/video/call.cc
index cd29d62..8b71acf 100644
--- a/webrtc/video/call.cc
+++ b/webrtc/video/call.cc
@@ -28,6 +28,8 @@
 #include "webrtc/video_engine/include/vie_base.h"
 #include "webrtc/video_engine/include/vie_codec.h"
 #include "webrtc/video_engine/include/vie_rtp_rtcp.h"
+#include "webrtc/video_engine/include/vie_network.h"
+#include "webrtc/video_engine/include/vie_rtp_rtcp.h"
 
 namespace webrtc {
 const char* RtpExtension::kTOffset = "urn:ietf:params:rtp-hdrext:toffset";
@@ -93,18 +95,26 @@
   virtual DeliveryStatus DeliverPacket(const uint8_t* packet,
                                        size_t length) OVERRIDE;
 
+  virtual void SignalNetworkState(NetworkState state) OVERRIDE;
+
  private:
   DeliveryStatus DeliverRtcp(const uint8_t* packet, size_t length);
   DeliveryStatus DeliverRtp(const uint8_t* packet, size_t length);
 
   Call::Config config_;
 
-  std::map<uint32_t, VideoReceiveStream*> receive_ssrcs_
-      GUARDED_BY(receive_lock_);
-  scoped_ptr<RWLockWrapper> receive_lock_;
+  // Needs to be held while write-locking |receive_crit_| or |send_crit_|. This
+  // ensures that we have a consistent network state signalled to all senders
+  // and receivers.
+  scoped_ptr<CriticalSectionWrapper> network_enabled_crit_;
+  bool network_enabled_ GUARDED_BY(network_enabled_crit_);
 
-  std::map<uint32_t, VideoSendStream*> send_ssrcs_ GUARDED_BY(send_lock_);
-  scoped_ptr<RWLockWrapper> send_lock_;
+  scoped_ptr<RWLockWrapper> receive_crit_;
+  std::map<uint32_t, VideoReceiveStream*> receive_ssrcs_
+      GUARDED_BY(receive_crit_);
+
+  scoped_ptr<RWLockWrapper> send_crit_;
+  std::map<uint32_t, VideoSendStream*> send_ssrcs_ GUARDED_BY(send_crit_);
 
   scoped_ptr<CpuOveruseObserverProxy> overuse_observer_proxy_;
 
@@ -135,8 +145,10 @@
 
 Call::Call(webrtc::VideoEngine* video_engine, const Call::Config& config)
     : config_(config),
-      receive_lock_(RWLockWrapper::CreateRWLock()),
-      send_lock_(RWLockWrapper::CreateRWLock()),
+      network_enabled_crit_(CriticalSectionWrapper::CreateCriticalSection()),
+      network_enabled_(true),
+      receive_crit_(RWLockWrapper::CreateRWLock()),
+      send_crit_(RWLockWrapper::CreateRWLock()),
       video_engine_(video_engine),
       base_channel_id_(-1) {
   assert(video_engine != NULL);
@@ -192,11 +204,16 @@
       config_.start_bitrate_bps != -1 ? config_.start_bitrate_bps
                                       : kDefaultVideoStreamBitrateBps);
 
-  WriteLockScoped write_lock(*send_lock_);
+  // This needs to be taken before send_crit_ as both locks need to be held
+  // while changing network state.
+  CriticalSectionScoped lock(network_enabled_crit_.get());
+  WriteLockScoped write_lock(*send_crit_);
   for (size_t i = 0; i < config.rtp.ssrcs.size(); ++i) {
     assert(send_ssrcs_.find(config.rtp.ssrcs[i]) == send_ssrcs_.end());
     send_ssrcs_[config.rtp.ssrcs[i]] = send_stream;
   }
+  if (!network_enabled_)
+    send_stream->SignalNetworkState(kNetworkDown);
   return send_stream;
 }
 
@@ -207,7 +224,7 @@
 
   VideoSendStream* send_stream_impl = NULL;
   {
-    WriteLockScoped write_lock(*send_lock_);
+    WriteLockScoped write_lock(*send_crit_);
     std::map<uint32_t, VideoSendStream*>::iterator it = send_ssrcs_.begin();
     while (it != send_ssrcs_.end()) {
       if (it->second == static_cast<VideoSendStream*>(send_stream)) {
@@ -240,7 +257,10 @@
                              config_.voice_engine,
                              base_channel_id_);
 
-  WriteLockScoped write_lock(*receive_lock_);
+  // This needs to be taken before receive_crit_ as both locks need to be held
+  // while changing network state.
+  CriticalSectionScoped lock(network_enabled_crit_.get());
+  WriteLockScoped write_lock(*receive_crit_);
   assert(receive_ssrcs_.find(config.rtp.remote_ssrc) == receive_ssrcs_.end());
   receive_ssrcs_[config.rtp.remote_ssrc] = receive_stream;
   // TODO(pbos): Configure different RTX payloads per receive payload.
@@ -249,6 +269,8 @@
   if (it != config.rtp.rtx.end())
     receive_ssrcs_[it->second.ssrc] = receive_stream;
 
+  if (!network_enabled_)
+    receive_stream->SignalNetworkState(kNetworkDown);
   return receive_stream;
 }
 
@@ -258,7 +280,7 @@
 
   VideoReceiveStream* receive_stream_impl = NULL;
   {
-    WriteLockScoped write_lock(*receive_lock_);
+    WriteLockScoped write_lock(*receive_crit_);
     // Remove all ssrcs pointing to a receive stream. As RTX retransmits on a
     // separate SSRC there can be either one or two.
     std::map<uint32_t, VideoReceiveStream*>::iterator it =
@@ -289,6 +311,31 @@
   return 0;
 }
 
+void Call::SignalNetworkState(NetworkState state) {
+  // Take crit for entire function, it needs to be held while updating streams
+  // to guarantee a consistent state across streams.
+  CriticalSectionScoped lock(network_enabled_crit_.get());
+  network_enabled_ = state == kNetworkUp;
+  {
+    ReadLockScoped write_lock(*send_crit_);
+    for (std::map<uint32_t, VideoSendStream*>::iterator it =
+             send_ssrcs_.begin();
+         it != send_ssrcs_.end();
+         ++it) {
+      it->second->SignalNetworkState(state);
+    }
+  }
+  {
+    ReadLockScoped write_lock(*receive_crit_);
+    for (std::map<uint32_t, VideoReceiveStream*>::iterator it =
+             receive_ssrcs_.begin();
+         it != receive_ssrcs_.end();
+         ++it) {
+      it->second->SignalNetworkState(state);
+    }
+  }
+}
+
 PacketReceiver::DeliveryStatus Call::DeliverRtcp(const uint8_t* packet,
                                                        size_t length) {
   // TODO(pbos): Figure out what channel needs it actually.
@@ -297,7 +344,7 @@
   //             there's no receiver of the packet.
   bool rtcp_delivered = false;
   {
-    ReadLockScoped read_lock(*receive_lock_);
+    ReadLockScoped read_lock(*receive_crit_);
     for (std::map<uint32_t, VideoReceiveStream*>::iterator it =
              receive_ssrcs_.begin();
          it != receive_ssrcs_.end();
@@ -308,7 +355,7 @@
   }
 
   {
-    ReadLockScoped read_lock(*send_lock_);
+    ReadLockScoped read_lock(*send_crit_);
     for (std::map<uint32_t, VideoSendStream*>::iterator it =
              send_ssrcs_.begin();
          it != send_ssrcs_.end();
@@ -329,7 +376,7 @@
   const uint8_t* ptr = &packet[8];
   uint32_t ssrc = ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
 
-  ReadLockScoped read_lock(*receive_lock_);
+  ReadLockScoped read_lock(*receive_crit_);
   std::map<uint32_t, VideoReceiveStream*>::iterator it =
       receive_ssrcs_.find(ssrc);
 
diff --git a/webrtc/video/end_to_end_tests.cc b/webrtc/video/end_to_end_tests.cc
index 739944a..11ce642 100644
--- a/webrtc/video/end_to_end_tests.cc
+++ b/webrtc/video/end_to_end_tests.cc
@@ -43,6 +43,7 @@
 
 namespace webrtc {
 
+static const unsigned long kSilenceTimeoutMs = 2000;
 static const int kRedPayloadType = 118;
 static const int kUlpfecPayloadType = 119;
 
@@ -56,6 +57,19 @@
   }
 
  protected:
+  class UnusedTransport : public newapi::Transport {
+   private:
+    virtual bool SendRtp(const uint8_t* packet, size_t length) OVERRIDE {
+      ADD_FAILURE() << "Unexpected RTP sent.";
+      return false;
+    }
+
+    virtual bool SendRtcp(const uint8_t* packet, size_t length) OVERRIDE {
+      ADD_FAILURE() << "Unexpected RTCP sent.";
+      return false;
+    }
+  };
+
   void DecodesRetransmittedFrame(bool retransmit_over_rtx);
   void ReceivesPliAndRecovers(int rtp_history_ms);
   void RespectsRtcpMode(newapi::RtcpMode rtcp_mode);
@@ -1840,6 +1854,228 @@
   TestRtpStatePreservation(true);
 }
 
+TEST_F(EndToEndTest, RespectsNetworkState) {
+  // TODO(pbos): Remove accepted downtime packets etc. when signaling network
+  // down blocks until no more packets will be sent.
+
+  // Pacer will send from its packet list and then send required padding before
+  // checking paused_ again. This should be enough for one round of pacing,
+  // otherwise increase.
+  static const int kNumAcceptedDowntimeRtp = 5;
+  // A single RTCP may be in the pipeline.
+  static const int kNumAcceptedDowntimeRtcp = 1;
+  class NetworkStateTest : public test::EndToEndTest, public test::FakeEncoder {
+   public:
+    NetworkStateTest()
+        : EndToEndTest(kDefaultTimeoutMs),
+          FakeEncoder(Clock::GetRealTimeClock()),
+          test_crit_(CriticalSectionWrapper::CreateCriticalSection()),
+          encoded_frames_(EventWrapper::Create()),
+          sender_packets_(EventWrapper::Create()),
+          receiver_packets_(EventWrapper::Create()),
+          sender_state_(Call::kNetworkUp),
+          down_sender_rtp_(0),
+          down_sender_rtcp_(0),
+          receiver_state_(Call::kNetworkUp),
+          down_receiver_rtcp_(0),
+          down_frames_(0) {}
+
+    virtual Action OnSendRtp(const uint8_t* packet, size_t length) OVERRIDE {
+      CriticalSectionScoped lock(test_crit_.get());
+      if (sender_state_ == Call::kNetworkDown) {
+        ++down_sender_rtp_;
+        EXPECT_LE(down_sender_rtp_, kNumAcceptedDowntimeRtp)
+            << "RTP sent during sender-side downtime.";
+        if (down_sender_rtp_> kNumAcceptedDowntimeRtp)
+          sender_packets_->Set();
+      } else {
+        sender_packets_->Set();
+      }
+      return SEND_PACKET;
+    }
+
+    virtual Action OnSendRtcp(const uint8_t* packet, size_t length) OVERRIDE {
+      CriticalSectionScoped lock(test_crit_.get());
+      if (sender_state_ == Call::kNetworkDown) {
+        ++down_sender_rtcp_;
+        EXPECT_LE(down_sender_rtcp_, kNumAcceptedDowntimeRtcp)
+            << "RTCP sent during sender-side downtime.";
+        if (down_sender_rtcp_ > kNumAcceptedDowntimeRtcp)
+          sender_packets_->Set();
+      } else {
+        sender_packets_->Set();
+      }
+      return SEND_PACKET;
+    }
+
+    virtual Action OnReceiveRtp(const uint8_t* packet, size_t length) OVERRIDE {
+      ADD_FAILURE() << "Unexpected receiver RTP, should not be sending.";
+      return SEND_PACKET;
+    }
+
+    virtual Action OnReceiveRtcp(const uint8_t* packet,
+                                 size_t length) OVERRIDE {
+      CriticalSectionScoped lock(test_crit_.get());
+      if (receiver_state_ == Call::kNetworkDown) {
+        ++down_receiver_rtcp_;
+        EXPECT_LE(down_receiver_rtcp_, kNumAcceptedDowntimeRtcp)
+            << "RTCP sent during receiver-side downtime.";
+        if (down_receiver_rtcp_ > kNumAcceptedDowntimeRtcp)
+          receiver_packets_->Set();
+      } else {
+        receiver_packets_->Set();
+      }
+      return SEND_PACKET;
+    }
+
+    virtual void OnCallsCreated(Call* sender_call,
+                                Call* receiver_call) OVERRIDE {
+      sender_call_ = sender_call;
+      receiver_call_ = receiver_call;
+    }
+
+    virtual void ModifyConfigs(
+        VideoSendStream::Config* send_config,
+        std::vector<VideoReceiveStream::Config>* receive_configs,
+        std::vector<VideoStream>* video_streams) OVERRIDE {
+      send_config->encoder_settings.encoder = this;
+    }
+
+    virtual void PerformTest() OVERRIDE {
+      EXPECT_EQ(kEventSignaled, encoded_frames_->Wait(kDefaultTimeoutMs))
+          << "No frames received by the encoder.";
+      EXPECT_EQ(kEventSignaled, sender_packets_->Wait(kDefaultTimeoutMs))
+          << "Timed out waiting for send-side packets.";
+      EXPECT_EQ(kEventSignaled, receiver_packets_->Wait(kDefaultTimeoutMs))
+          << "Timed out waiting for receiver-side packets.";
+
+      // Sender-side network down.
+      sender_call_->SignalNetworkState(Call::kNetworkDown);
+      {
+        CriticalSectionScoped lock(test_crit_.get());
+        sender_packets_->Reset();  // Earlier packets should not count.
+        sender_state_ = Call::kNetworkDown;
+      }
+      EXPECT_EQ(kEventTimeout, sender_packets_->Wait(kSilenceTimeoutMs))
+          << "Packets sent during sender-network downtime.";
+      EXPECT_EQ(kEventSignaled, receiver_packets_->Wait(kDefaultTimeoutMs))
+          << "Timed out waiting for receiver-side packets.";
+      // Receiver-side network down.
+      receiver_call_->SignalNetworkState(Call::kNetworkDown);
+      {
+        CriticalSectionScoped lock(test_crit_.get());
+        receiver_packets_->Reset();  // Earlier packets should not count.
+        receiver_state_ = Call::kNetworkDown;
+      }
+      EXPECT_EQ(kEventTimeout, receiver_packets_->Wait(kSilenceTimeoutMs))
+          << "Packets sent during receiver-network downtime.";
+
+      // Network back up again for both.
+      {
+        CriticalSectionScoped lock(test_crit_.get());
+        sender_packets_->Reset();  // Earlier packets should not count.
+        receiver_packets_->Reset();  // Earlier packets should not count.
+        sender_state_ = receiver_state_ = Call::kNetworkUp;
+      }
+      sender_call_->SignalNetworkState(Call::kNetworkUp);
+      receiver_call_->SignalNetworkState(Call::kNetworkUp);
+      EXPECT_EQ(kEventSignaled, sender_packets_->Wait(kDefaultTimeoutMs))
+          << "Timed out waiting for send-side packets.";
+      EXPECT_EQ(kEventSignaled, receiver_packets_->Wait(kDefaultTimeoutMs))
+          << "Timed out waiting for receiver-side packets.";
+    }
+
+    virtual int32_t Encode(const I420VideoFrame& input_image,
+                           const CodecSpecificInfo* codec_specific_info,
+                           const std::vector<VideoFrameType>* frame_types)
+        OVERRIDE {
+      {
+        CriticalSectionScoped lock(test_crit_.get());
+        if (sender_state_ == Call::kNetworkDown) {
+          ++down_frames_;
+          EXPECT_LE(down_frames_, 1)
+              << "Encoding more than one frame while network is down.";
+          if (down_frames_ > 1)
+            encoded_frames_->Set();
+        } else {
+          encoded_frames_->Set();
+        }
+      }
+      return test::FakeEncoder::Encode(
+          input_image, codec_specific_info, frame_types);
+    }
+
+   private:
+    const scoped_ptr<CriticalSectionWrapper> test_crit_;
+    scoped_ptr<EventWrapper> encoded_frames_;
+    scoped_ptr<EventWrapper> sender_packets_;
+    scoped_ptr<EventWrapper> receiver_packets_;
+    Call* sender_call_;
+    Call* receiver_call_;
+    Call::NetworkState sender_state_ GUARDED_BY(test_crit_);
+    int down_sender_rtp_ GUARDED_BY(test_crit_);
+    int down_sender_rtcp_ GUARDED_BY(test_crit_);
+    Call::NetworkState receiver_state_ GUARDED_BY(test_crit_);
+    int down_receiver_rtcp_ GUARDED_BY(test_crit_);
+    int down_frames_ GUARDED_BY(test_crit_);
+  } test;
+
+  RunBaseTest(&test);
+}
+
+TEST_F(EndToEndTest, NewSendStreamsRespectNetworkDown) {
+  class UnusedEncoder : public test::FakeEncoder {
+    public:
+     UnusedEncoder() : FakeEncoder(Clock::GetRealTimeClock()) {}
+    virtual int32_t Encode(const I420VideoFrame& input_image,
+                           const CodecSpecificInfo* codec_specific_info,
+                           const std::vector<VideoFrameType>* frame_types)
+        OVERRIDE {
+      ADD_FAILURE() << "Unexpected frame encode.";
+      return test::FakeEncoder::Encode(
+          input_image, codec_specific_info, frame_types);
+    }
+  };
+
+  UnusedTransport transport;
+  CreateSenderCall(Call::Config(&transport));
+  sender_call_->SignalNetworkState(Call::kNetworkDown);
+
+  CreateSendConfig(1);
+  UnusedEncoder unused_encoder;
+  send_config_.encoder_settings.encoder = &unused_encoder;
+  CreateStreams();
+  CreateFrameGeneratorCapturer();
+
+  Start();
+  SleepMs(kSilenceTimeoutMs);
+  Stop();
+
+  DestroyStreams();
+}
+
+TEST_F(EndToEndTest, NewReceiveStreamsRespectNetworkDown) {
+  test::DirectTransport sender_transport;
+  CreateSenderCall(Call::Config(&sender_transport));
+  UnusedTransport transport;
+  CreateReceiverCall(Call::Config(&transport));
+  sender_transport.SetReceiver(receiver_call_->Receiver());
+
+  receiver_call_->SignalNetworkState(Call::kNetworkDown);
+
+  CreateSendConfig(1);
+  CreateMatchingReceiveConfigs();
+  CreateStreams();
+  CreateFrameGeneratorCapturer();
+
+  Start();
+  SleepMs(kSilenceTimeoutMs);
+  Stop();
+
+  sender_transport.StopSending();
+
+  DestroyStreams();
+}
 }  // namespace webrtc
 
 #endif // !WEBRTC_ANDROID
diff --git a/webrtc/video/video_receive_stream.cc b/webrtc/video/video_receive_stream.cc
index 9c3298c..41a800f 100644
--- a/webrtc/video/video_receive_stream.cc
+++ b/webrtc/video/video_receive_stream.cc
@@ -52,14 +52,7 @@
   // TODO(pbos): This is not fine grained enough...
   rtp_rtcp_->SetNACKStatus(channel_, config_.rtp.nack.rtp_history_ms > 0);
   rtp_rtcp_->SetKeyFrameRequestMethod(channel_, kViEKeyFrameRequestPliRtcp);
-  switch (config_.rtp.rtcp_mode) {
-    case newapi::kRtcpCompound:
-      rtp_rtcp_->SetRTCPStatus(channel_, kRtcpCompound_RFC4585);
-      break;
-    case newapi::kRtcpReducedSize:
-      rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNonCompound_RFC5506);
-      break;
-  }
+  SetRtcpMode(config_.rtp.rtcp_mode);
 
   assert(config_.rtp.remote_ssrc != 0);
   // TODO(pbos): What's an appropriate local_ssrc for receive-only streams?
@@ -264,5 +257,24 @@
 
   return 0;
 }
+
+void VideoReceiveStream::SignalNetworkState(Call::NetworkState state) {
+  if (state == Call::kNetworkUp)
+    SetRtcpMode(config_.rtp.rtcp_mode);
+  network_->SetNetworkTransmissionState(channel_, state == Call::kNetworkUp);
+  if (state == Call::kNetworkDown)
+    rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNone);
+}
+
+void VideoReceiveStream::SetRtcpMode(newapi::RtcpMode mode) {
+  switch (mode) {
+    case newapi::kRtcpCompound:
+      rtp_rtcp_->SetRTCPStatus(channel_, kRtcpCompound_RFC4585);
+      break;
+    case newapi::kRtcpReducedSize:
+      rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNonCompound_RFC5506);
+      break;
+  }
+}
 }  // namespace internal
 }  // namespace webrtc
diff --git a/webrtc/video/video_receive_stream.h b/webrtc/video/video_receive_stream.h
index c45ebac..6894828 100644
--- a/webrtc/video/video_receive_stream.h
+++ b/webrtc/video/video_receive_stream.h
@@ -13,6 +13,7 @@
 
 #include <vector>
 
+#include "webrtc/call.h"
 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
 #include "webrtc/modules/video_render/include/video_render_defines.h"
 #include "webrtc/system_wrappers/interface/clock.h"
@@ -61,11 +62,14 @@
   virtual int32_t RenderFrame(const uint32_t stream_id,
                               I420VideoFrame& video_frame) OVERRIDE;
 
- public:
+  void SignalNetworkState(Call::NetworkState state);
+
   virtual bool DeliverRtcp(const uint8_t* packet, size_t length);
   virtual bool DeliverRtp(const uint8_t* packet, size_t length);
 
  private:
+  void SetRtcpMode(newapi::RtcpMode mode);
+
   TransportAdapter transport_adapter_;
   EncodedFrameCallbackAdapter encoded_frame_proxy_;
   const VideoReceiveStream::Config config_;
diff --git a/webrtc/video/video_send_stream.cc b/webrtc/video/video_send_stream.cc
index 6597029..624aca8 100644
--- a/webrtc/video/video_send_stream.cc
+++ b/webrtc/video/video_send_stream.cc
@@ -460,5 +460,16 @@
   return rtp_states;
 }
 
+void VideoSendStream::SignalNetworkState(Call::NetworkState state) {
+  // When network goes up, enable RTCP status before setting transmission state.
+  // When it goes down, disable RTCP afterwards. This ensures that any packets
+  // sent due to the network state changed will not be dropped.
+  if (state == Call::kNetworkUp)
+    rtp_rtcp_->SetRTCPStatus(channel_, kRtcpCompound_RFC4585);
+  network_->SetNetworkTransmissionState(channel_, state == Call::kNetworkUp);
+  if (state == Call::kNetworkDown)
+    rtp_rtcp_->SetRTCPStatus(channel_, kRtcpNone);
+}
+
 }  // namespace internal
 }  // namespace webrtc
diff --git a/webrtc/video/video_send_stream.h b/webrtc/video/video_send_stream.h
index f1f8f7d..130c1c5 100644
--- a/webrtc/video/video_send_stream.h
+++ b/webrtc/video/video_send_stream.h
@@ -14,6 +14,7 @@
 #include <map>
 #include <vector>
 
+#include "webrtc/call.h"
 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
 #include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h"
 #include "webrtc/video/encoded_frame_callback_adapter.h"
@@ -72,6 +73,8 @@
   typedef std::map<uint32_t, RtpState> RtpStateMap;
   RtpStateMap GetRtpStates() const;
 
+  void SignalNetworkState(Call::NetworkState state);
+
  private:
   void ConfigureSsrcs();
   TransportAdapter transport_adapter_;