(Auto)update libjingle 73927775-> 74032598

git-svn-id: http://webrtc.googlecode.com/svn/trunk@6965 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/mediaconstraintsinterface.cc b/talk/app/webrtc/mediaconstraintsinterface.cc
index d59716e..a7b88aa 100644
--- a/talk/app/webrtc/mediaconstraintsinterface.cc
+++ b/talk/app/webrtc/mediaconstraintsinterface.cc
@@ -95,6 +95,8 @@
     "googSuspendBelowMinBitrate";
 const char MediaConstraintsInterface::kNumUnsignalledRecvStreams[] =
     "googNumUnsignalledRecvStreams";
+const char MediaConstraintsInterface::kCombinedAudioVideoBwe[] =
+    "googCombinedAudioVideoBwe";
 const char MediaConstraintsInterface::kScreencastMinBitrate[] =
     "googScreencastMinBitrate";
 // TODO(ronghuawu): Remove once cpu overuse detection is stable.
diff --git a/talk/app/webrtc/mediaconstraintsinterface.h b/talk/app/webrtc/mediaconstraintsinterface.h
index d74ffab..045da79 100644
--- a/talk/app/webrtc/mediaconstraintsinterface.h
+++ b/talk/app/webrtc/mediaconstraintsinterface.h
@@ -117,6 +117,8 @@
       // googSuspendBelowMinBitrate
   static const char kNumUnsignalledRecvStreams[];
       // googNumUnsignalledRecvStreams
+  // Constraint to enable combined audio+video bandwidth estimation.
+  static const char kCombinedAudioVideoBwe[];  // googCombinedAudioVideoBwe
   static const char kScreencastMinBitrate[];  // googScreencastMinBitrate
   static const char kCpuOveruseDetection[];  // googCpuOveruseDetection
   static const char kCpuUnderuseThreshold[];  // googCpuUnderuseThreshold
diff --git a/talk/app/webrtc/peerconnection_unittest.cc b/talk/app/webrtc/peerconnection_unittest.cc
index ca022c6..a001d75 100644
--- a/talk/app/webrtc/peerconnection_unittest.cc
+++ b/talk/app/webrtc/peerconnection_unittest.cc
@@ -90,6 +90,8 @@
 // warnings.
 #if !defined(THREAD_SANITIZER)
 static const int kMaxWaitForStatsMs = 3000;
+static const int kMaxWaitForAudioDataMs = 10000;
+static const int kMaxWaitForRembMs = 5000;
 #endif
 static const int kMaxWaitForFramesMs = 10000;
 static const int kEndAudioFrameCount = 3;
@@ -154,11 +156,11 @@
   }
 
   void AddMediaStream(bool audio, bool video) {
-    std::string label = kStreamLabelBase +
+    std::string stream_label = kStreamLabelBase +
         rtc::ToString<int>(
             static_cast<int>(peer_connection_->local_streams()->count()));
     rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
-        peer_connection_factory_->CreateLocalMediaStream(label);
+        peer_connection_factory_->CreateLocalMediaStream(stream_label);
 
     if (audio && can_receive_audio()) {
       FakeConstraints constraints;
@@ -169,13 +171,13 @@
           peer_connection_factory_->CreateAudioSource(&constraints);
       // TODO(perkj): Test audio source when it is implemented. Currently audio
       // always use the default input.
+      std::string label = stream_label + kAudioTrackLabelBase;
       rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
-          peer_connection_factory_->CreateAudioTrack(kAudioTrackLabelBase,
-                                                     source));
+          peer_connection_factory_->CreateAudioTrack(label, source));
       stream->AddTrack(audio_track);
     }
     if (video && can_receive_video()) {
-      stream->AddTrack(CreateLocalVideoTrack(label));
+      stream->AddTrack(CreateLocalVideoTrack(stream_label));
     }
 
     EXPECT_TRUE(peer_connection_->AddStream(stream, NULL));
@@ -368,6 +370,17 @@
     return observer->BytesSent();
   }
 
+  int GetAvailableReceivedBandwidthStats() {
+    rtc::scoped_refptr<MockStatsObserver>
+        observer(new rtc::RefCountedObject<MockStatsObserver>());
+    EXPECT_TRUE(peer_connection_->GetStats(
+        observer, NULL, PeerConnectionInterface::kStatsOutputLevelStandard));
+    EXPECT_TRUE_WAIT(observer->called(), kMaxWaitMs);
+    int bw = observer->AvailableReceiveBandwidth();
+    LOG(INFO) << "Available Receive Bandwidth: " << bw;
+    return bw;
+  }
+
   int rendered_width() {
     EXPECT_FALSE(fake_video_renderers_.empty());
     return fake_video_renderers_.empty() ? 1 :
@@ -447,6 +460,12 @@
   webrtc::PeerConnectionInterface* pc() {
     return peer_connection_.get();
   }
+  void StopVideoCapturers() {
+    for (std::vector<cricket::VideoCapturer*>::iterator it =
+        video_capturers_.begin(); it != video_capturers_.end(); ++it) {
+      (*it)->Stop();
+    }
+  }
 
  protected:
   explicit PeerConnectionTestClientBase(const std::string& id)
@@ -529,10 +548,12 @@
     FakeConstraints source_constraints = video_constraints_;
     source_constraints.SetMandatoryMaxFrameRate(10);
 
+    cricket::FakeVideoCapturer* fake_capturer =
+        new webrtc::FakePeriodicVideoCapturer();
+    video_capturers_.push_back(fake_capturer);
     rtc::scoped_refptr<webrtc::VideoSourceInterface> source =
         peer_connection_factory_->CreateVideoSource(
-            new webrtc::FakePeriodicVideoCapturer(),
-            &source_constraints);
+            fake_capturer, &source_constraints);
     std::string label = stream_label + kVideoTrackLabelBase;
     return peer_connection_factory_->CreateVideoTrack(label, source);
   }
@@ -569,6 +590,10 @@
 
   // For remote peer communication.
   MessageReceiver* signaling_message_receiver_;
+
+  // Store references to the video capturers we've created, so that we can stop
+  // them, if required.
+  std::vector<cricket::VideoCapturer*> video_capturers_;
 };
 
 class JsepTestClient
@@ -1314,6 +1339,7 @@
 
   // Unregister the existing observer.
   receiving_client()->data_channel()->UnregisterObserver();
+
   std::string data = "hello world";
   SendRtpData(initializing_client()->data_channel(), data);
 
@@ -1437,4 +1463,80 @@
   EnableVideoDecoderFactory();
   LocalP2PTest();
 }
+
+// Test receive bandwidth stats with only audio enabled at receiver.
+TEST_F(JsepPeerConnectionP2PTestClient, ReceivedBweStatsAudio) {
+  ASSERT_TRUE(CreateTestClients());
+  receiving_client()->SetReceiveAudioVideo(true, false);
+  LocalP2PTest();
+
+  // Wait until we have received some audio data.
+  StreamCollectionInterface* local_streams =
+      initializing_client()->local_streams();
+  ASSERT_GT(local_streams->count(), 0u);
+  ASSERT_GT(local_streams->at(0)->GetAudioTracks().size(), 0u);
+  MediaStreamTrackInterface* local_audio_track =
+      local_streams->at(0)->GetAudioTracks()[0];
+  EXPECT_TRUE_WAIT(
+      receiving_client()->GetBytesReceivedStats(local_audio_track) > 10000,
+      kMaxWaitForAudioDataMs);
+
+  // Then wait for REMB.
+  EXPECT_EQ_WAIT(
+      receiving_client()->GetAvailableReceivedBandwidthStats(), 0,
+      kMaxWaitForRembMs);
+}
+
+// Test receive bandwidth stats with combined BWE.
+TEST_F(JsepPeerConnectionP2PTestClient, ReceivedBweStatsCombined) {
+  FakeConstraints setup_constraints;
+  setup_constraints.AddOptional(
+      MediaConstraintsInterface::kCombinedAudioVideoBwe, true);
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints));
+  initializing_client()->AddMediaStream(true, true);
+  initializing_client()->AddMediaStream(false, true);
+  initializing_client()->AddMediaStream(false, true);
+  initializing_client()->AddMediaStream(false, true);
+  LocalP2PTest();
+
+  // Run until a non-zero bw is reported.
+  EXPECT_TRUE_WAIT(
+      receiving_client()->GetAvailableReceivedBandwidthStats() > 40000,
+      kMaxWaitForRembMs);
+  int video_bw = receiving_client()->GetAvailableReceivedBandwidthStats();
+
+  // Halt video capturers, then run until we get a new non-zero bw which is
+  // lower than the previous value.
+  initializing_client()->StopVideoCapturers();
+  EXPECT_TRUE_WAIT(
+      receiving_client()->GetAvailableReceivedBandwidthStats() < video_bw,
+      kMaxWaitForRembMs);
+  EXPECT_GT(receiving_client()->GetAvailableReceivedBandwidthStats(), 0);
+}
+
+// Test receive bandwidth stats with 1 video, 3 audio streams but no combined
+// BWE.
+TEST_F(JsepPeerConnectionP2PTestClient, ReceivedBweStatsNotCombined) {
+  FakeConstraints setup_constraints;
+  setup_constraints.AddOptional(
+      MediaConstraintsInterface::kCombinedAudioVideoBwe, false);
+  ASSERT_TRUE(CreateTestClients(&setup_constraints, &setup_constraints));
+  initializing_client()->AddMediaStream(true, true);
+  initializing_client()->AddMediaStream(false, true);
+  initializing_client()->AddMediaStream(false, true);
+  initializing_client()->AddMediaStream(false, true);
+  LocalP2PTest();
+
+  // Run until a non-zero bw is reported.
+  EXPECT_TRUE_WAIT(
+      receiving_client()->GetAvailableReceivedBandwidthStats() > 40000,
+      kMaxWaitForRembMs);
+
+  // Halt video capturers, then run until we get a new bw which is zero.
+  initializing_client()->StopVideoCapturers();
+  EXPECT_EQ_WAIT(
+      receiving_client()->GetAvailableReceivedBandwidthStats(), 0,
+      kMaxWaitForRembMs);
+}
+
 #endif // if !defined(THREAD_SANITIZER)
diff --git a/talk/app/webrtc/test/mockpeerconnectionobservers.h b/talk/app/webrtc/test/mockpeerconnectionobservers.h
index 174b80b..0570d40 100644
--- a/talk/app/webrtc/test/mockpeerconnectionobservers.h
+++ b/talk/app/webrtc/test/mockpeerconnectionobservers.h
@@ -133,31 +133,37 @@
   size_t number_of_reports() const { return reports_.size(); }
 
   int AudioOutputLevel() {
-    return GetSsrcStatsValue(
-        webrtc::StatsReport::kStatsValueNameAudioOutputLevel);
+    return GetStatsValue(StatsReport::kStatsReportTypeSsrc,
+                         StatsReport::kStatsValueNameAudioOutputLevel);
   }
 
   int AudioInputLevel() {
-    return GetSsrcStatsValue(
-        webrtc::StatsReport::kStatsValueNameAudioInputLevel);
+    return GetStatsValue(StatsReport::kStatsReportTypeSsrc,
+                         StatsReport::kStatsValueNameAudioInputLevel);
   }
 
   int BytesReceived() {
-    return GetSsrcStatsValue(
-        webrtc::StatsReport::kStatsValueNameBytesReceived);
+    return GetStatsValue(StatsReport::kStatsReportTypeSsrc,
+                         StatsReport::kStatsValueNameBytesReceived);
   }
 
   int BytesSent() {
-    return GetSsrcStatsValue(webrtc::StatsReport::kStatsValueNameBytesSent);
+    return GetStatsValue(StatsReport::kStatsReportTypeSsrc,
+                         StatsReport::kStatsValueNameBytesSent);
+  }
+
+  int AvailableReceiveBandwidth() {
+    return GetStatsValue(StatsReport::kStatsReportTypeBwe,
+                         StatsReport::kStatsValueNameAvailableReceiveBandwidth);
   }
 
  private:
-  int GetSsrcStatsValue(StatsReport::StatsValueName name) {
+  int GetStatsValue(const std::string& type, StatsReport::StatsValueName name) {
     if (reports_.empty()) {
       return 0;
     }
     for (size_t i = 0; i < reports_.size(); ++i) {
-      if (reports_[i].type != StatsReport::kStatsReportTypeSsrc)
+      if (reports_[i].type != type)
         continue;
       webrtc::StatsReport::Values::const_iterator it =
           reports_[i].values.begin();
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index 6d57afb..b90bf2c 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -471,14 +471,16 @@
 }
 
 WebRtcSession::~WebRtcSession() {
-  if (voice_channel_.get()) {
-    SignalVoiceChannelDestroyed();
-    channel_manager_->DestroyVoiceChannel(voice_channel_.release());
-  }
+  // Destroy video_channel_ first since it may have a pointer to the
+  // voice_channel_.
   if (video_channel_.get()) {
     SignalVideoChannelDestroyed();
     channel_manager_->DestroyVideoChannel(video_channel_.release());
   }
+  if (voice_channel_.get()) {
+    SignalVoiceChannelDestroyed();
+    channel_manager_->DestroyVoiceChannel(voice_channel_.release());
+  }
   if (data_channel_.get()) {
     SignalDataChannelDestroyed();
     channel_manager_->DestroyDataChannel(data_channel_.release());
@@ -610,6 +612,10 @@
         cricket::VideoOptions::HIGH);
   }
 
+  SetOptionFromOptionalConstraint(constraints,
+      MediaConstraintsInterface::kCombinedAudioVideoBwe,
+      &audio_options_.combined_audio_video_bwe);
+
   const cricket::VideoCodec default_codec(
       JsepSessionDescription::kDefaultVideoCodecId,
       JsepSessionDescription::kDefaultVideoCodecName,
@@ -1425,16 +1431,8 @@
 
 void WebRtcSession::RemoveUnusedChannelsAndTransports(
     const SessionDescription* desc) {
-  const cricket::ContentInfo* voice_info =
-      cricket::GetFirstAudioContent(desc);
-  if ((!voice_info || voice_info->rejected) && voice_channel_) {
-    mediastream_signaling_->OnAudioChannelClose();
-    SignalVoiceChannelDestroyed();
-    const std::string content_name = voice_channel_->content_name();
-    channel_manager_->DestroyVoiceChannel(voice_channel_.release());
-    DestroyTransportProxy(content_name);
-  }
-
+  // Destroy video_channel_ first since it may have a pointer to the
+  // voice_channel_.
   const cricket::ContentInfo* video_info =
       cricket::GetFirstVideoContent(desc);
   if ((!video_info || video_info->rejected) && video_channel_) {
@@ -1445,6 +1443,16 @@
     DestroyTransportProxy(content_name);
   }
 
+  const cricket::ContentInfo* voice_info =
+      cricket::GetFirstAudioContent(desc);
+  if ((!voice_info || voice_info->rejected) && voice_channel_) {
+    mediastream_signaling_->OnAudioChannelClose();
+    SignalVoiceChannelDestroyed();
+    const std::string content_name = voice_channel_->content_name();
+    channel_manager_->DestroyVoiceChannel(voice_channel_.release());
+    DestroyTransportProxy(content_name);
+  }
+
   const cricket::ContentInfo* data_info =
       cricket::GetFirstDataContent(desc);
   if ((!data_info || data_info->rejected) && data_channel_) {
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
index 206f320..2f731a9 100644
--- a/talk/app/webrtc/webrtcsession_unittest.cc
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -3311,6 +3311,26 @@
   SetAndVerifyNumUnsignalledRecvStreams(-1, 0);
 }
 
+TEST_F(WebRtcSessionTest, TestCombinedAudioVideoBweConstraint) {
+  constraints_.reset(new FakeConstraints());
+  constraints_->AddOptional(
+      webrtc::MediaConstraintsInterface::kCombinedAudioVideoBwe,
+      true);
+  Init(NULL);
+  mediastream_signaling_.SendAudioVideoStream1();
+  SessionDescriptionInterface* offer = CreateOffer();
+
+  SetLocalDescriptionWithoutError(offer);
+
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  ASSERT_TRUE(voice_channel_ != NULL);
+  cricket::AudioOptions audio_options;
+  EXPECT_TRUE(voice_channel_->GetOptions(&audio_options));
+  EXPECT_TRUE(
+      audio_options.combined_audio_video_bwe.GetWithDefaultIfUnset(false));
+}
+
 // Tests that we can renegotiate new media content with ICE candidates in the
 // new remote SDP.
 TEST_F(WebRtcSessionTest, TestRenegotiateNewMediaWithCandidatesInSdp) {
diff --git a/talk/media/base/mediachannel.h b/talk/media/base/mediachannel.h
index 62d6b61..5232e5d 100644
--- a/talk/media/base/mediachannel.h
+++ b/talk/media/base/mediachannel.h
@@ -182,6 +182,7 @@
     recording_sample_rate.SetFrom(change.recording_sample_rate);
     playout_sample_rate.SetFrom(change.playout_sample_rate);
     dscp.SetFrom(change.dscp);
+    combined_audio_video_bwe.SetFrom(change.combined_audio_video_bwe);
   }
 
   bool operator==(const AudioOptions& o) const {
@@ -207,7 +208,8 @@
         rx_agc_limiter == o.rx_agc_limiter &&
         recording_sample_rate == o.recording_sample_rate &&
         playout_sample_rate == o.playout_sample_rate &&
-        dscp == o.dscp;
+        dscp == o.dscp &&
+        combined_audio_video_bwe == o.combined_audio_video_bwe;
   }
 
   std::string ToString() const {
@@ -238,6 +240,7 @@
     ost << ToStringIfSet("recording_sample_rate", recording_sample_rate);
     ost << ToStringIfSet("playout_sample_rate", playout_sample_rate);
     ost << ToStringIfSet("dscp", dscp);
+    ost << ToStringIfSet("combined_audio_video_bwe", combined_audio_video_bwe);
     ost << "}";
     return ost.str();
   }
@@ -275,6 +278,8 @@
   Settable<uint32> playout_sample_rate;
   // Set DSCP value for packet sent from audio channel.
   Settable<bool> dscp;
+  // Enable combined audio+bandwidth BWE.
+  Settable<bool> combined_audio_video_bwe;
 };
 
 // Options that can be applied to a VideoMediaChannel or a VideoMediaEngine.
diff --git a/talk/media/webrtc/fakewebrtcvideoengine.h b/talk/media/webrtc/fakewebrtcvideoengine.h
index 5cba380..ec4b9c6 100644
--- a/talk/media/webrtc/fakewebrtcvideoengine.h
+++ b/talk/media/webrtc/fakewebrtcvideoengine.h
@@ -335,6 +335,7 @@
     unsigned int send_nack_bitrate_;
     unsigned int send_bandwidth_;
     unsigned int receive_bandwidth_;
+    // Bandwidth to deduct from estimated uplink capacity.
     unsigned int reserved_transmit_bitrate_bps_;
     bool suspend_below_min_bitrate_;
     webrtc::CpuOveruseObserver* overuse_observer_;
diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h
index f731b8d..fe44390 100644
--- a/talk/media/webrtc/fakewebrtcvoiceengine.h
+++ b/talk/media/webrtc/fakewebrtcvoiceengine.h
@@ -43,10 +43,7 @@
 #ifdef USE_WEBRTC_DEV_BRANCH
 #include "webrtc/modules/audio_processing/include/audio_processing.h"
 #endif
-
-namespace webrtc {
-class ViENetwork;
-}
+#include "webrtc/video_engine/include/vie_network.h"
 
 namespace cricket {
 
@@ -316,6 +313,8 @@
   }
   webrtc::ViENetwork* GetViENetwork(int channel) {
     WEBRTC_ASSERT_CHANNEL(channel);
+    // WARNING: This pointer is for verification purposes only. Calling
+    // functions on it may result in undefined behavior!
     return channels_[channel]->vie_network;
   }
   int GetVideoChannel(int channel) {
@@ -999,6 +998,11 @@
     WEBRTC_CHECK_CHANNEL(channel);
     channels_[channel]->vie_network = vie_network;
     channels_[channel]->video_channel = video_channel;
+    if (vie_network) {
+      // The interface is released here to avoid leaks. A test should not
+      // attempt to call functions on the interface stored in the channel.
+      vie_network->Release();
+    }
     return 0;
   }
 
diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc
index 2462859..6d6ed25 100644
--- a/talk/media/webrtc/webrtcvideoengine.cc
+++ b/talk/media/webrtc/webrtcvideoengine.cc
@@ -1572,7 +1572,19 @@
 
 bool WebRtcVideoMediaChannel::Init() {
   const uint32 ssrc_key = 0;
-  return CreateChannel(ssrc_key, MD_SENDRECV, &default_channel_id_);
+  bool result = CreateChannel(ssrc_key, MD_SENDRECV, &default_channel_id_);
+  if (!result) {
+    return false;
+  }
+  if (voice_channel_) {
+    WebRtcVoiceMediaChannel* voice_channel =
+        static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
+    if (!voice_channel->SetupSharedBandwidthEstimation(
+        engine()->vie()->engine(), default_channel_id_)) {
+      return false;
+    }
+  }
+  return true;
 }
 
 WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() {
@@ -1581,6 +1593,12 @@
   const bool render = false;
   SetRender(render);
 
+  if (voice_channel_) {
+    WebRtcVoiceMediaChannel* voice_channel =
+        static_cast<WebRtcVoiceMediaChannel*>(voice_channel_);
+    voice_channel->SetupSharedBandwidthEstimation(NULL, -1);
+  }
+
   while (!send_channels_.empty()) {
     if (!DeleteSendChannel(send_channels_.begin()->first)) {
       LOG(LS_ERROR) << "Unable to delete channel with ssrc key "
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
index 5cf53e4..8cb85b6 100644
--- a/talk/media/webrtc/webrtcvoiceengine.cc
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -52,6 +52,7 @@
 #include "webrtc/base/stringutils.h"
 #include "webrtc/common.h"
 #include "webrtc/modules/audio_processing/include/audio_processing.h"
+#include "webrtc/video_engine/include/vie_network.h"
 
 #ifdef WIN32
 #include <objbase.h>  // NOLINT
@@ -1759,6 +1760,8 @@
       typing_noise_detected_(false),
       desired_send_(SEND_NOTHING),
       send_(SEND_NOTHING),
+      shared_bwe_vie_(NULL),
+      shared_bwe_vie_channel_(-1),
       default_receive_ssrc_(0) {
   engine->RegisterChannel(this);
   LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel "
@@ -1770,6 +1773,8 @@
 WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() {
   LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel "
                   << voe_channel();
+  ASSERT(shared_bwe_vie_ == NULL);
+  ASSERT(shared_bwe_vie_channel_ == -1);
 
   // Remove any remaining send streams, the default channel will be deleted
   // later.
@@ -1870,6 +1875,12 @@
     }
   }
 
+  // Force update of Video Engine BWE forwarding to reflect experiment setting.
+  if (!SetupSharedBandwidthEstimation(shared_bwe_vie_,
+                                      shared_bwe_vie_channel_)) {
+    return false;
+  }
+
   LOG(LS_INFO) << "Set voice channel options.  Current options: "
                << options_.ToString();
   return true;
@@ -2531,8 +2542,8 @@
   }
 
   if (engine()->voe()->rtp()->SetRTCP_CNAME(channel, sp.cname.c_str()) == -1) {
-     LOG_RTCERR2(SetRTCP_CNAME, channel, sp.cname);
-     return false;
+    LOG_RTCERR2(SetRTCP_CNAME, channel, sp.cname);
+    return false;
   }
 
   // Set the current codecs to be used for the new channel.
@@ -2604,6 +2615,9 @@
     receive_channels_.insert(std::make_pair(
         default_receive_ssrc_,
         new WebRtcVoiceChannelRenderer(voe_channel(), audio_transport)));
+    if (!SetupSharedBweOnChannel(voe_channel())) {
+      return false;
+    }
     return SetPlayout(voe_channel(), playout_);
   }
 
@@ -2691,6 +2705,11 @@
     return false;
   }
 
+  // Set up channel to be able to forward incoming packets to video engine BWE.
+  if (!SetupSharedBweOnChannel(channel)) {
+    return false;
+  }
+
   return SetPlayout(channel, playout_);
 }
 
@@ -3060,7 +3079,8 @@
   engine()->voe()->network()->ReceivedRTPPacket(
       which_channel,
       packet->data(),
-      static_cast<unsigned int>(packet->length()));
+      static_cast<unsigned int>(packet->length()),
+      webrtc::PacketTime(packet_time.timestamp, packet_time.not_before));
 }
 
 void WebRtcVoiceMediaChannel::OnRtcpReceived(
@@ -3454,6 +3474,23 @@
   return -1;
 }
 
+bool WebRtcVoiceMediaChannel::SetupSharedBandwidthEstimation(
+    webrtc::VideoEngine* vie, int vie_channel) {
+  shared_bwe_vie_ = vie;
+  shared_bwe_vie_channel_ = vie_channel;
+
+  if (!SetupSharedBweOnChannel(voe_channel())) {
+    return false;
+  }
+  for (ChannelMap::iterator it = receive_channels_.begin();
+      it != receive_channels_.end(); ++it) {
+    if (!SetupSharedBweOnChannel(it->second->channel())) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool WebRtcVoiceMediaChannel::GetRedSendCodec(const AudioCodec& red_codec,
     const std::vector<AudioCodec>& all_codecs, webrtc::CodecInst* send_codec) {
   // Get the RED encodings from the parameter with no name. This may
@@ -3603,6 +3640,25 @@
   return true;
 }
 
+bool WebRtcVoiceMediaChannel::SetupSharedBweOnChannel(int voe_channel) {
+  webrtc::ViENetwork* vie_network = NULL;
+  int vie_channel = -1;
+  if (options_.combined_audio_video_bwe.GetWithDefaultIfUnset(false) &&
+      shared_bwe_vie_ != NULL && shared_bwe_vie_channel_ != -1) {
+    vie_network = webrtc::ViENetwork::GetInterface(shared_bwe_vie_);
+    vie_channel = shared_bwe_vie_channel_;
+  }
+  if (engine()->voe()->rtp()->SetVideoEngineBWETarget(voe_channel, vie_network,
+      vie_channel) == -1) {
+    LOG_RTCERR3(SetVideoEngineBWETarget, voe_channel, vie_network, vie_channel);
+    if (vie_network != NULL) {
+      // Don't fail if we're tearing down.
+      return false;
+    }
+  }
+  return true;
+}
+
 int WebRtcSoundclipStream::Read(void *buf, int len) {
   size_t res = 0;
   mem_.Read(buf, len, &res, NULL);
diff --git a/talk/media/webrtc/webrtcvoiceengine.h b/talk/media/webrtc/webrtcvoiceengine.h
index 69705cc..bc2a4e5 100644
--- a/talk/media/webrtc/webrtcvoiceengine.h
+++ b/talk/media/webrtc/webrtcvoiceengine.h
@@ -54,6 +54,10 @@
 #error "Bogus include."
 #endif
 
+namespace webrtc {
+class VideoEngine;
+}
+
 namespace cricket {
 
 // WebRtcSoundclipStream is an adapter object that allows a memory stream to be
@@ -377,6 +381,8 @@
   int GetReceiveChannelNum(uint32 ssrc);
   int GetSendChannelNum(uint32 ssrc);
 
+  bool SetupSharedBandwidthEstimation(webrtc::VideoEngine* vie,
+                                      int vie_channel);
  protected:
   int GetLastEngineError() { return engine()->GetLastEngineError(); }
   int GetOutputLevel(int channel);
@@ -419,6 +425,7 @@
 
   bool SetHeaderExtension(ExtensionSetterFunction setter, int channel_id,
                           const RtpHeaderExtension* extension);
+  bool SetupSharedBweOnChannel(int voe_channel);
 
   bool SetChannelRecvRtpHeaderExtensions(
     int channel_id,
@@ -442,6 +449,11 @@
   bool typing_noise_detected_;
   SendFlags desired_send_;
   SendFlags send_;
+  // shared_bwe_vie_ and shared_bwe_vie_channel_ together identifies a WebRTC
+  // VideoEngine channel that this voice channel should forward incoming packets
+  // to for Bandwidth Estimation purposes.
+  webrtc::VideoEngine* shared_bwe_vie_;
+  int shared_bwe_vie_channel_;
 
   // send_channels_ contains the channels which are being used for sending.
   // When the default channel (voe_channel) is used for sending, it is
diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
index 89d4c4d..f5b8260 100644
--- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc
+++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
@@ -38,6 +38,7 @@
 #include "talk/media/base/fakenetworkinterface.h"
 #include "talk/media/base/fakertp.h"
 #include "talk/media/webrtc/fakewebrtcvoiceengine.h"
+#include "talk/media/webrtc/webrtcvie.h"
 #include "talk/media/webrtc/webrtcvoiceengine.h"
 #include "talk/p2p/base/fakesession.h"
 #include "talk/session/media/channel.h"
@@ -3160,3 +3161,113 @@
   CoUninitialize();
 }
 #endif
+
+TEST_F(WebRtcVoiceEngineTestFake, ChangeCombinedAudioVideoBweOption) {
+  // Test that changing the combined_audio_video_bwe option results in the
+  // expected state changes in VoiceEngine.
+  cricket::ViEWrapper vie;
+  const int kVieCh = 667;
+
+  EXPECT_TRUE(SetupEngine());
+  cricket::WebRtcVoiceMediaChannel* media_channel =
+      static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
+  EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie.engine(),
+                                                            kVieCh));
+  EXPECT_TRUE(media_channel->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(2)));
+  int recv_ch = voe_.GetLastChannel();
+
+  // Combined BWE should not be set up yet.
+  EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch));
+  EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch));
+
+  // Enable combined BWE option - now it should be set up.
+  cricket::AudioOptions options;
+  options.combined_audio_video_bwe.Set(true);
+  EXPECT_TRUE(media_channel->SetOptions(options));
+  EXPECT_EQ(vie.network(), voe_.GetViENetwork(recv_ch));
+  EXPECT_EQ(kVieCh, voe_.GetVideoChannel(recv_ch));
+
+  // Disable combined BWE option - should be disabled again.
+  options.combined_audio_video_bwe.Set(false);
+  EXPECT_TRUE(media_channel->SetOptions(options));
+  EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch));
+  EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch));
+
+  EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1));
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, SetupSharedBandwidthEstimation) {
+  // Test that calling SetupSharedBandwidthEstimation() on the voice media
+  // channel results in the expected state changes in VoiceEngine.
+  cricket::ViEWrapper vie1;
+  cricket::ViEWrapper vie2;
+  const int kVieCh1 = 667;
+  const int kVieCh2 = 70;
+
+  EXPECT_TRUE(SetupEngine());
+  cricket::WebRtcVoiceMediaChannel* media_channel =
+      static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
+  cricket::AudioOptions options;
+  options.combined_audio_video_bwe.Set(true);
+  EXPECT_TRUE(media_channel->SetOptions(options));
+  EXPECT_TRUE(media_channel->AddRecvStream(
+      cricket::StreamParams::CreateLegacy(2)));
+  int recv_ch = voe_.GetLastChannel();
+
+  // Combined BWE should not be set up yet.
+  EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch));
+  EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch));
+
+  // Register - should be enabled.
+  EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie1.engine(),
+                                                            kVieCh1));
+  EXPECT_EQ(vie1.network(), voe_.GetViENetwork(recv_ch));
+  EXPECT_EQ(kVieCh1, voe_.GetVideoChannel(recv_ch));
+
+  // Re-register - should still be enabled.
+  EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie2.engine(),
+                                                            kVieCh2));
+  EXPECT_EQ(vie2.network(), voe_.GetViENetwork(recv_ch));
+  EXPECT_EQ(kVieCh2, voe_.GetVideoChannel(recv_ch));
+
+  // Unregister - should be disabled again.
+  EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1));
+  EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch));
+  EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch));
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, ConfigureCombinedBweForNewRecvStreams) {
+  // Test that adding receive streams after enabling combined bandwidth
+  // estimation will correctly configure each channel.
+  cricket::ViEWrapper vie;
+  const int kVieCh = 667;
+
+  EXPECT_TRUE(SetupEngine());
+  cricket::WebRtcVoiceMediaChannel* media_channel =
+      static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
+  EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie.engine(),
+                                                            kVieCh));
+  cricket::AudioOptions options;
+  options.combined_audio_video_bwe.Set(true);
+  EXPECT_TRUE(media_channel->SetOptions(options));
+
+  static const uint32 kSsrcs[] = {1, 2, 3, 4};
+  int voe_channels[ARRAY_SIZE(kSsrcs)] = {0};
+  for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs); ++i) {
+    EXPECT_TRUE(media_channel->AddRecvStream(
+        cricket::StreamParams::CreateLegacy(kSsrcs[i])));
+    int recv_ch = media_channel->GetReceiveChannelNum(kSsrcs[i]);
+    EXPECT_NE(-1, recv_ch);
+    voe_channels[i] = recv_ch;
+    EXPECT_EQ(vie.network(), voe_.GetViENetwork(recv_ch));
+    EXPECT_EQ(kVieCh, voe_.GetVideoChannel(recv_ch));
+  }
+
+  EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1));
+
+  for (unsigned int i = 0; i < ARRAY_SIZE(voe_channels); ++i) {
+    EXPECT_EQ(NULL, voe_.GetViENetwork(voe_channels[i]));
+    EXPECT_EQ(-1, voe_.GetVideoChannel(voe_channels[i]));
+  }
+}