Implement AudioSendStream::GetStats().

BUG=webrtc:4690

Review URL: https://codereview.webrtc.org/1414743004

Cr-Commit-Position: refs/heads/master@{#10424}
diff --git a/talk/media/webrtc/fakewebrtccall.cc b/talk/media/webrtc/fakewebrtccall.cc
index 04deeb4..d86bfb5 100644
--- a/talk/media/webrtc/fakewebrtccall.cc
+++ b/talk/media/webrtc/fakewebrtccall.cc
@@ -39,8 +39,9 @@
   RTC_DCHECK(config.voe_channel_id != -1);
 }
 
-webrtc::AudioSendStream::Stats FakeAudioSendStream::GetStats() const {
-  return webrtc::AudioSendStream::Stats();
+void FakeAudioSendStream::SetStats(
+    const webrtc::AudioSendStream::Stats& stats) {
+  stats_ = stats;
 }
 
 const webrtc::AudioSendStream::Config&
@@ -48,6 +49,10 @@
   return config_;
 }
 
+webrtc::AudioSendStream::Stats FakeAudioSendStream::GetStats() const {
+  return stats_;
+}
+
 FakeAudioReceiveStream::FakeAudioReceiveStream(
     const webrtc::AudioReceiveStream::Config& config)
     : config_(config), received_packets_(0) {
@@ -68,6 +73,10 @@
   received_packets_++;
 }
 
+webrtc::AudioReceiveStream::Stats FakeAudioReceiveStream::GetStats() const {
+  return stats_;
+}
+
 FakeVideoSendStream::FakeVideoSendStream(
     const webrtc::VideoSendStream::Config& config,
     const webrtc::VideoEncoderConfig& encoder_config)
diff --git a/talk/media/webrtc/fakewebrtccall.h b/talk/media/webrtc/fakewebrtccall.h
index 212c062..88edc60 100644
--- a/talk/media/webrtc/fakewebrtccall.h
+++ b/talk/media/webrtc/fakewebrtccall.h
@@ -53,10 +53,8 @@
   explicit FakeAudioSendStream(
       const webrtc::AudioSendStream::Config& config);
 
-  // webrtc::AudioSendStream implementation.
-  webrtc::AudioSendStream::Stats GetStats() const override;
-
   const webrtc::AudioSendStream::Config& GetConfig() const;
+  void SetStats(const webrtc::AudioSendStream::Stats& stats);
 
  private:
   // webrtc::SendStream implementation.
@@ -67,7 +65,11 @@
     return true;
   }
 
+  // webrtc::AudioSendStream implementation.
+  webrtc::AudioSendStream::Stats GetStats() const override;
+
   webrtc::AudioSendStream::Config config_;
+  webrtc::AudioSendStream::Stats stats_;
 };
 
 class FakeAudioReceiveStream : public webrtc::AudioReceiveStream {
@@ -95,9 +97,7 @@
   }
 
   // webrtc::AudioReceiveStream implementation.
-  webrtc::AudioReceiveStream::Stats GetStats() const override {
-    return stats_;
-  }
+  webrtc::AudioReceiveStream::Stats GetStats() const override;
 
   webrtc::AudioReceiveStream::Config config_;
   webrtc::AudioReceiveStream::Stats stats_;
diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h
index 9b91327..2405e07 100644
--- a/talk/media/webrtc/fakewebrtcvoiceengine.h
+++ b/talk/media/webrtc/fakewebrtcvoiceengine.h
@@ -45,11 +45,6 @@
 
 namespace cricket {
 
-// Function returning stats will return these values
-// for all values based on type.
-const int kIntStatValue = 123;
-const float kFractionLostStatValue = 0.5;
-
 static const char kFakeDefaultDeviceName[] = "Fake Default";
 static const int kFakeDefaultDeviceId = -1;
 static const char kFakeDeviceName[] = "Fake Device";
@@ -268,6 +263,8 @@
     }
   }
 
+  bool ec_metrics_enabled() const { return ec_metrics_enabled_; }
+
   bool IsInited() const { return inited_; }
   int GetLastChannel() const { return last_channel_; }
   int GetChannelFromLocalSsrc(uint32_t local_ssrc) const {
@@ -279,6 +276,9 @@
     return -1;
   }
   int GetNumChannels() const { return static_cast<int>(channels_.size()); }
+  uint32_t GetLocalSSRC(int channel) {
+    return channels_[channel]->send_ssrc;
+  }
   bool GetPlayout(int channel) {
     return channels_[channel]->playout;
   }
@@ -727,11 +727,7 @@
     channels_[channel]->send_ssrc = ssrc;
     return 0;
   }
-  WEBRTC_FUNC(GetLocalSSRC, (int channel, unsigned int& ssrc)) {
-    WEBRTC_CHECK_CHANNEL(channel);
-    ssrc = channels_[channel]->send_ssrc;
-    return 0;
-  }
+  WEBRTC_STUB(GetLocalSSRC, (int channel, unsigned int& ssrc));
   WEBRTC_STUB(GetRemoteSSRC, (int channel, unsigned int& ssrc));
   WEBRTC_FUNC(SetSendAudioLevelIndicationStatus, (int channel, bool enable,
       unsigned char id)) {
@@ -773,39 +769,12 @@
                                   unsigned int& playoutTimestamp,
                                   unsigned int* jitter,
                                   unsigned short* fractionLost));
-  WEBRTC_FUNC(GetRemoteRTCPReportBlocks,
-              (int channel, std::vector<webrtc::ReportBlock>* receive_blocks)) {
-    WEBRTC_CHECK_CHANNEL(channel);
-    webrtc::ReportBlock block;
-    block.source_SSRC = channels_[channel]->send_ssrc;
-    webrtc::CodecInst send_codec = channels_[channel]->send_codec;
-    if (send_codec.pltype >= 0) {
-      block.fraction_lost = (unsigned char)(kFractionLostStatValue * 256);
-      if (send_codec.plfreq / 1000 > 0) {
-        block.interarrival_jitter = kIntStatValue * (send_codec.plfreq / 1000);
-      }
-      block.cumulative_num_packets_lost = kIntStatValue;
-      block.extended_highest_sequence_number = kIntStatValue;
-      receive_blocks->push_back(block);
-    }
-    return 0;
-  }
+  WEBRTC_STUB(GetRemoteRTCPReportBlocks,
+              (int channel, std::vector<webrtc::ReportBlock>* receive_blocks));
   WEBRTC_STUB(GetRTPStatistics, (int channel, unsigned int& averageJitterMs,
                                  unsigned int& maxJitterMs,
                                  unsigned int& discardedPackets));
-  WEBRTC_FUNC(GetRTCPStatistics, (int channel, webrtc::CallStatistics& stats)) {
-    WEBRTC_CHECK_CHANNEL(channel);
-    stats.fractionLost = static_cast<int16_t>(kIntStatValue);
-    stats.cumulativeLost = kIntStatValue;
-    stats.extendedMax = kIntStatValue;
-    stats.jitterSamples = kIntStatValue;
-    stats.rttMs = kIntStatValue;
-    stats.bytesSent = kIntStatValue;
-    stats.packetsSent = kIntStatValue;
-    stats.bytesReceived = kIntStatValue;
-    stats.packetsReceived = kIntStatValue;
-    return 0;
-  }
+  WEBRTC_STUB(GetRTCPStatistics, (int channel, webrtc::CallStatistics& stats));
   WEBRTC_FUNC(SetREDStatus, (int channel, bool enable, int redPayloadtype)) {
     return SetFECStatus(channel, enable, redPayloadtype);
   }
@@ -931,10 +900,7 @@
     ec_metrics_enabled_ = enable;
     return 0;
   }
-  WEBRTC_FUNC(GetEcMetricsStatus, (bool& enabled)) {
-    enabled = ec_metrics_enabled_;
-    return 0;
-  }
+  WEBRTC_STUB(GetEcMetricsStatus, (bool& enabled));
   WEBRTC_STUB(GetEchoMetrics, (int& ERL, int& ERLE, int& RERL, int& A_NLP));
   WEBRTC_STUB(GetEcDelayMetrics, (int& delay_median, int& delay_std,
       float& fraction_poor_delays));
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
index fd93535..1d12fbf 100644
--- a/talk/media/webrtc/webrtcvoiceengine.cc
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -1321,7 +1321,11 @@
       : channel_(ch),
         voe_audio_transport_(voe_audio_transport),
         call_(call) {
+    RTC_DCHECK_GE(ch, 0);
+    // TODO(solenberg): Once we're not using FakeWebRtcVoiceEngine anymore:
+    // RTC_DCHECK(voe_audio_transport);
     RTC_DCHECK(call);
+    audio_capture_thread_checker_.DetachFromThread();
     webrtc::AudioSendStream::Config config(nullptr);
     config.voe_channel_id = channel_;
     config.rtp.ssrc = ssrc;
@@ -1329,6 +1333,7 @@
     RTC_DCHECK(stream_);
   }
   ~WebRtcAudioSendStream() override {
+    RTC_DCHECK(signal_thread_checker_.CalledOnValidThread());
     Stop();
     call_->DestroyAudioSendStream(stream_);
   }
@@ -1338,7 +1343,7 @@
   // This method is called on the libjingle worker thread.
   // TODO(xians): Make sure Start() is called only once.
   void Start(AudioRenderer* renderer) {
-    rtc::CritScope lock(&lock_);
+    RTC_DCHECK(signal_thread_checker_.CalledOnValidThread());
     RTC_DCHECK(renderer);
     if (renderer_) {
       RTC_DCHECK(renderer_ == renderer);
@@ -1348,11 +1353,16 @@
     renderer_ = renderer;
   }
 
+  webrtc::AudioSendStream::Stats GetStats() const {
+    RTC_DCHECK(signal_thread_checker_.CalledOnValidThread());
+    return stream_->GetStats();
+  }
+
   // Stops rendering by setting the sink of the renderer to nullptr. No data
   // callback will be received after this method.
   // This method is called on the libjingle worker thread.
   void Stop() {
-    rtc::CritScope lock(&lock_);
+    RTC_DCHECK(signal_thread_checker_.CalledOnValidThread());
     if (renderer_) {
       renderer_->SetSink(nullptr);
       renderer_ = nullptr;
@@ -1366,6 +1376,7 @@
               int sample_rate,
               int number_of_channels,
               size_t number_of_frames) override {
+    RTC_DCHECK(audio_capture_thread_checker_.CalledOnValidThread());
     RTC_DCHECK(voe_audio_transport_);
     voe_audio_transport_->OnData(channel_,
                                  audio_data,
@@ -1378,16 +1389,21 @@
   // Callback from the |renderer_| when it is going away. In case Start() has
   // never been called, this callback won't be triggered.
   void OnClose() override {
-    rtc::CritScope lock(&lock_);
+    RTC_DCHECK(signal_thread_checker_.CalledOnValidThread());
     // Set |renderer_| to nullptr to make sure no more callback will get into
     // the renderer.
     renderer_ = nullptr;
   }
 
   // Accessor to the VoE channel ID.
-  int channel() const { return channel_; }
+  int channel() const {
+    RTC_DCHECK(signal_thread_checker_.CalledOnValidThread());
+    return channel_;
+  }
 
  private:
+  rtc::ThreadChecker signal_thread_checker_;
+  rtc::ThreadChecker audio_capture_thread_checker_;
   const int channel_ = -1;
   webrtc::AudioTransport* const voe_audio_transport_ = nullptr;
   webrtc::Call* call_ = nullptr;
@@ -1398,9 +1414,6 @@
   // goes away.
   AudioRenderer* renderer_ = nullptr;
 
-  // Protects |renderer_| in Start(), Stop() and OnClose().
-  rtc::CriticalSection lock_;
-
   RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(WebRtcAudioSendStream);
 };
 
@@ -1433,7 +1446,6 @@
       desired_send_(SEND_NOTHING),
       send_(SEND_NOTHING),
       call_(call) {
-  RTC_DCHECK(thread_checker_.CalledOnValidThread());
   LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel";
   RTC_DCHECK(nullptr != call);
   engine->RegisterChannel(this);
@@ -2618,109 +2630,36 @@
 
 bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) {
   RTC_DCHECK(thread_checker_.CalledOnValidThread());
+  RTC_DCHECK(info);
 
-  bool echo_metrics_on = false;
-  // These can take on valid negative values, so use the lowest possible level
-  // as default rather than -1.
-  int echo_return_loss = -100;
-  int echo_return_loss_enhancement = -100;
-  // These can also be negative, but in practice -1 is only used to signal
-  // insufficient data, since the resolution is limited to multiples of 4 ms.
-  int echo_delay_median_ms = -1;
-  int echo_delay_std_ms = -1;
-  if (engine()->voe()->processing()->GetEcMetricsStatus(
-          echo_metrics_on) != -1 && echo_metrics_on) {
-    // TODO(ajm): we may want to use VoECallReport::GetEchoMetricsSummary
-    // here, but it appears to be unsuitable currently. Revisit after this is
-    // investigated: http://b/issue?id=5666755
-    int erl, erle, rerl, anlp;
-    if (engine()->voe()->processing()->GetEchoMetrics(
-            erl, erle, rerl, anlp) != -1) {
-      echo_return_loss = erl;
-      echo_return_loss_enhancement = erle;
-    }
-
-    int median, std;
-    float dummy;
-    if (engine()->voe()->processing()->GetEcDelayMetrics(
-        median, std, dummy) != -1) {
-      echo_delay_median_ms = median;
-      echo_delay_std_ms = std;
-    }
-  }
-
-  for (const auto& ch : send_streams_) {
-    const int channel = ch.second->channel();
-
-    // Fill in the sender info, based on what we know, and what the
-    // remote side told us it got from its RTCP report.
+  // Get SSRC and stats for each sender.
+  RTC_DCHECK(info->senders.size() == 0);
+  for (const auto& stream : send_streams_) {
+    webrtc::AudioSendStream::Stats stats = stream.second->GetStats();
     VoiceSenderInfo sinfo;
-
-    webrtc::CallStatistics cs = {0};
-    unsigned int ssrc = 0;
-    if (engine()->voe()->rtp()->GetRTCPStatistics(channel, cs) == -1 ||
-        engine()->voe()->rtp()->GetLocalSSRC(channel, ssrc) == -1) {
-      continue;
-    }
-
-    sinfo.add_ssrc(ssrc);
-    sinfo.codec_name = send_codec_.get() ? send_codec_->plname : "";
-    sinfo.bytes_sent = cs.bytesSent;
-    sinfo.packets_sent = cs.packetsSent;
-    // RTT isn't known until a RTCP report is received. Until then, VoiceEngine
-    // returns 0 to indicate an error value.
-    sinfo.rtt_ms = (cs.rttMs > 0) ? cs.rttMs : -1;
-
-    // Get data from the last remote RTCP report. Use default values if no data
-    // available.
-    sinfo.fraction_lost = -1.0;
-    sinfo.jitter_ms = -1;
-    sinfo.packets_lost = -1;
-    sinfo.ext_seqnum = -1;
-    std::vector<webrtc::ReportBlock> receive_blocks;
-    webrtc::CodecInst codec = {0};
-    if (engine()->voe()->rtp()->GetRemoteRTCPReportBlocks(
-            channel, &receive_blocks) != -1 &&
-        engine()->voe()->codec()->GetSendCodec(channel, codec) != -1) {
-      for (const webrtc::ReportBlock& block : receive_blocks) {
-        // Lookup report for send ssrc only.
-        if (block.source_SSRC == sinfo.ssrc()) {
-          // Convert Q8 to floating point.
-          sinfo.fraction_lost = static_cast<float>(block.fraction_lost) / 256;
-          // Convert samples to milliseconds.
-          if (codec.plfreq / 1000 > 0) {
-            sinfo.jitter_ms = block.interarrival_jitter / (codec.plfreq / 1000);
-          }
-          sinfo.packets_lost = block.cumulative_num_packets_lost;
-          sinfo.ext_seqnum = block.extended_highest_sequence_number;
-          break;
-        }
-      }
-    }
-
-    // Local speech level.
-    unsigned int level = 0;
-    sinfo.audio_level = (engine()->voe()->volume()->
-        GetSpeechInputLevelFullRange(level) != -1) ? level : -1;
-
-    // TODO(xians): We are injecting the same APM logging to all the send
-    // channels here because there is no good way to know which send channel
-    // is using the APM. The correct fix is to allow the send channels to have
-    // their own APM so that we can feed the correct APM logging to different
-    // send channels. See issue crbug/264611 .
-    sinfo.echo_return_loss = echo_return_loss;
-    sinfo.echo_return_loss_enhancement = echo_return_loss_enhancement;
-    sinfo.echo_delay_median_ms = echo_delay_median_ms;
-    sinfo.echo_delay_std_ms = echo_delay_std_ms;
-    // TODO(ajm): Re-enable this metric once we have a reliable implementation.
-    sinfo.aec_quality_min = -1;
+    sinfo.add_ssrc(stats.local_ssrc);
+    sinfo.bytes_sent = stats.bytes_sent;
+    sinfo.packets_sent = stats.packets_sent;
+    sinfo.packets_lost = stats.packets_lost;
+    sinfo.fraction_lost = stats.fraction_lost;
+    sinfo.codec_name = stats.codec_name;
+    sinfo.ext_seqnum = stats.ext_seqnum;
+    sinfo.jitter_ms = stats.jitter_ms;
+    sinfo.rtt_ms = stats.rtt_ms;
+    sinfo.audio_level = stats.audio_level;
+    sinfo.aec_quality_min = stats.aec_quality_min;
+    sinfo.echo_delay_median_ms = stats.echo_delay_median_ms;
+    sinfo.echo_delay_std_ms = stats.echo_delay_std_ms;
+    sinfo.echo_return_loss = stats.echo_return_loss;
+    sinfo.echo_return_loss_enhancement = stats.echo_return_loss_enhancement;
     sinfo.typing_noise_detected = typing_noise_detected_;
-
+    // TODO(solenberg): Move to AudioSendStream.
+    //  sinfo.typing_noise_detected = stats.typing_noise_detected;
     info->senders.push_back(sinfo);
   }
 
-  // Get the SSRC and stats for each receiver.
-  info->receivers.clear();
+  // Get SSRC and stats for each receiver.
+  RTC_DCHECK(info->receivers.size() == 0);
   for (const auto& stream : receive_streams_) {
     webrtc::AudioReceiveStream::Stats stats = stream.second->GetStats();
     VoiceReceiverInfo rinfo;
diff --git a/talk/media/webrtc/webrtcvoiceengine_unittest.cc b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
index 4491929..ce5115c 100644
--- a/talk/media/webrtc/webrtcvoiceengine_unittest.cc
+++ b/talk/media/webrtc/webrtcvoiceengine_unittest.cc
@@ -57,9 +57,9 @@
     &kPcmuCodec, &kIsacCodec, &kOpusCodec, &kG722CodecVoE, &kRedCodec,
     &kCn8000Codec, &kCn16000Codec, &kTelephoneEventCodec,
 };
-static uint32_t kSsrc1 = 0x99;
-static uint32_t kSsrc2 = 0x98;
-static const uint32_t kSsrcs4[] = {1, 2, 3, 4};
+const uint32_t kSsrc1 = 0x99;
+const uint32_t kSsrc2 = 0x98;
+const uint32_t kSsrcs4[] = { 1, 2, 3, 4 };
 
 class FakeVoEWrapper : public cricket::VoEWrapper {
  public:
@@ -124,13 +124,11 @@
     EXPECT_TRUE(SetupEngineWithSendStream());
     // Remove stream added in Setup.
     int default_channel_num = voe_.GetLastChannel();
-    uint32_t default_send_ssrc = 0u;
-    EXPECT_EQ(0, voe_.GetLocalSSRC(default_channel_num, default_send_ssrc));
-    EXPECT_EQ(kSsrc1, default_send_ssrc);
-    EXPECT_TRUE(channel_->RemoveSendStream(default_send_ssrc));
+    EXPECT_EQ(kSsrc1, voe_.GetLocalSSRC(default_channel_num));
+    EXPECT_TRUE(channel_->RemoveSendStream(kSsrc1));
 
     // Verify the channel does not exist.
-    EXPECT_EQ(-1, voe_.GetLocalSSRC(default_channel_num, default_send_ssrc));
+    EXPECT_EQ(-1, voe_.GetChannelFromLocalSsrc(kSsrc1));
   }
   void DeliverPacket(const void* data, int len) {
     rtc::Buffer packet(reinterpret_cast<const uint8_t*>(data), len);
@@ -290,34 +288,79 @@
     EXPECT_EQ(-1, voe_.GetReceiveRtpExtensionId(new_channel_num, ext));
   }
 
-  const webrtc::AudioReceiveStream::Stats& GetAudioReceiveStreamStats() const {
-    static webrtc::AudioReceiveStream::Stats stats;
-    if (stats.remote_ssrc == 0) {
-      stats.remote_ssrc = 123;
-      stats.bytes_rcvd = 456;
-      stats.packets_rcvd = 768;
-      stats.packets_lost = 101;
-      stats.fraction_lost = 23.45f;
-      stats.codec_name = "codec_name";
-      stats.ext_seqnum = 678;
-      stats.jitter_ms = 901;
-      stats.jitter_buffer_ms = 234;
-      stats.jitter_buffer_preferred_ms = 567;
-      stats.delay_estimate_ms = 890;
-      stats.audio_level = 1234;
-      stats.expand_rate = 5.67f;
-      stats.speech_expand_rate = 8.90f;
-      stats.secondary_decoded_rate = 1.23f;
-      stats.accelerate_rate = 4.56f;
-      stats.preemptive_expand_rate = 7.89f;
-      stats.decoding_calls_to_silence_generator = 012;
-      stats.decoding_calls_to_neteq = 345;
-      stats.decoding_normal = 67890;
-      stats.decoding_plc = 1234;
-      stats.decoding_cng = 5678;
-      stats.decoding_plc_cng = 9012;
-      stats.capture_start_ntp_time_ms = 3456;
+  webrtc::AudioSendStream::Stats GetAudioSendStreamStats() const {
+    webrtc::AudioSendStream::Stats stats;
+    stats.local_ssrc = 12;
+    stats.bytes_sent = 345;
+    stats.packets_sent = 678;
+    stats.packets_lost = 9012;
+    stats.fraction_lost = 34.56f;
+    stats.codec_name = "codec_name_send";
+    stats.ext_seqnum = 789;
+    stats.jitter_ms = 12;
+    stats.rtt_ms = 345;
+    stats.audio_level = 678;
+    stats.aec_quality_min = 9.01f;
+    stats.echo_delay_median_ms = 234;
+    stats.echo_delay_std_ms = 567;
+    stats.echo_return_loss = 890;
+    stats.echo_return_loss_enhancement = 1234;
+    stats.typing_noise_detected = true;
+    return stats;
+  }
+  void SetAudioSendStreamStats() {
+    for (auto* s : call_.GetAudioSendStreams()) {
+      s->SetStats(GetAudioSendStreamStats());
     }
+  }
+  void VerifyVoiceSenderInfo(const cricket::VoiceSenderInfo& info) {
+    const auto stats = GetAudioSendStreamStats();
+    EXPECT_EQ(info.ssrc(), stats.local_ssrc);
+    EXPECT_EQ(info.bytes_sent, stats.bytes_sent);
+    EXPECT_EQ(info.packets_sent, stats.packets_sent);
+    EXPECT_EQ(info.packets_lost, stats.packets_lost);
+    EXPECT_EQ(info.fraction_lost, stats.fraction_lost);
+    EXPECT_EQ(info.codec_name, stats.codec_name);
+    EXPECT_EQ(info.ext_seqnum, stats.ext_seqnum);
+    EXPECT_EQ(info.jitter_ms, stats.jitter_ms);
+    EXPECT_EQ(info.rtt_ms, stats.rtt_ms);
+    EXPECT_EQ(info.audio_level, stats.audio_level);
+    EXPECT_EQ(info.aec_quality_min, stats.aec_quality_min);
+    EXPECT_EQ(info.echo_delay_median_ms, stats.echo_delay_median_ms);
+    EXPECT_EQ(info.echo_delay_std_ms, stats.echo_delay_std_ms);
+    EXPECT_EQ(info.echo_return_loss, stats.echo_return_loss);
+    EXPECT_EQ(info.echo_return_loss_enhancement,
+              stats.echo_return_loss_enhancement);
+    // TODO(solenberg): Move typing noise detection into AudioSendStream.
+    // EXPECT_EQ(info.typing_noise_detected, stats.typing_noise_detected);
+  }
+
+  webrtc::AudioReceiveStream::Stats GetAudioReceiveStreamStats() const {
+    webrtc::AudioReceiveStream::Stats stats;
+    stats.remote_ssrc = 123;
+    stats.bytes_rcvd = 456;
+    stats.packets_rcvd = 768;
+    stats.packets_lost = 101;
+    stats.fraction_lost = 23.45f;
+    stats.codec_name = "codec_name_recv";
+    stats.ext_seqnum = 678;
+    stats.jitter_ms = 901;
+    stats.jitter_buffer_ms = 234;
+    stats.jitter_buffer_preferred_ms = 567;
+    stats.delay_estimate_ms = 890;
+    stats.audio_level = 1234;
+    stats.expand_rate = 5.67f;
+    stats.speech_expand_rate = 8.90f;
+    stats.secondary_decoded_rate = 1.23f;
+    stats.accelerate_rate = 4.56f;
+    stats.preemptive_expand_rate = 7.89f;
+    stats.decoding_calls_to_silence_generator = 12;
+    stats.decoding_calls_to_neteq = 345;
+    stats.decoding_normal = 67890;
+    stats.decoding_plc = 1234;
+    stats.decoding_cng = 5678;
+    stats.decoding_plc_cng = 9012;
+    stats.capture_start_ntp_time_ms = 3456;
     return stats;
   }
   void SetAudioReceiveStreamStats() {
@@ -326,33 +369,33 @@
     }
   }
   void VerifyVoiceReceiverInfo(const cricket::VoiceReceiverInfo& info) {
-    const auto& kStats = GetAudioReceiveStreamStats();
-    EXPECT_EQ(info.local_stats.front().ssrc, kStats.remote_ssrc);
-    EXPECT_EQ(info.bytes_rcvd, kStats.bytes_rcvd);
-    EXPECT_EQ(info.packets_rcvd, kStats.packets_rcvd);
-    EXPECT_EQ(info.packets_lost, kStats.packets_lost);
-    EXPECT_EQ(info.fraction_lost, kStats.fraction_lost);
-    EXPECT_EQ(info.codec_name, kStats.codec_name);
-    EXPECT_EQ(info.ext_seqnum, kStats.ext_seqnum);
-    EXPECT_EQ(info.jitter_ms, kStats.jitter_ms);
-    EXPECT_EQ(info.jitter_buffer_ms, kStats.jitter_buffer_ms);
+    const auto stats = GetAudioReceiveStreamStats();
+    EXPECT_EQ(info.ssrc(), stats.remote_ssrc);
+    EXPECT_EQ(info.bytes_rcvd, stats.bytes_rcvd);
+    EXPECT_EQ(info.packets_rcvd, stats.packets_rcvd);
+    EXPECT_EQ(info.packets_lost, stats.packets_lost);
+    EXPECT_EQ(info.fraction_lost, stats.fraction_lost);
+    EXPECT_EQ(info.codec_name, stats.codec_name);
+    EXPECT_EQ(info.ext_seqnum, stats.ext_seqnum);
+    EXPECT_EQ(info.jitter_ms, stats.jitter_ms);
+    EXPECT_EQ(info.jitter_buffer_ms, stats.jitter_buffer_ms);
     EXPECT_EQ(info.jitter_buffer_preferred_ms,
-              kStats.jitter_buffer_preferred_ms);
-    EXPECT_EQ(info.delay_estimate_ms, kStats.delay_estimate_ms);
-    EXPECT_EQ(info.audio_level, kStats.audio_level);
-    EXPECT_EQ(info.expand_rate, kStats.expand_rate);
-    EXPECT_EQ(info.speech_expand_rate, kStats.speech_expand_rate);
-    EXPECT_EQ(info.secondary_decoded_rate, kStats.secondary_decoded_rate);
-    EXPECT_EQ(info.accelerate_rate, kStats.accelerate_rate);
-    EXPECT_EQ(info.preemptive_expand_rate, kStats.preemptive_expand_rate);
+              stats.jitter_buffer_preferred_ms);
+    EXPECT_EQ(info.delay_estimate_ms, stats.delay_estimate_ms);
+    EXPECT_EQ(info.audio_level, stats.audio_level);
+    EXPECT_EQ(info.expand_rate, stats.expand_rate);
+    EXPECT_EQ(info.speech_expand_rate, stats.speech_expand_rate);
+    EXPECT_EQ(info.secondary_decoded_rate, stats.secondary_decoded_rate);
+    EXPECT_EQ(info.accelerate_rate, stats.accelerate_rate);
+    EXPECT_EQ(info.preemptive_expand_rate, stats.preemptive_expand_rate);
     EXPECT_EQ(info.decoding_calls_to_silence_generator,
-              kStats.decoding_calls_to_silence_generator);
-    EXPECT_EQ(info.decoding_calls_to_neteq, kStats.decoding_calls_to_neteq);
-    EXPECT_EQ(info.decoding_normal, kStats.decoding_normal);
-    EXPECT_EQ(info.decoding_plc, kStats.decoding_plc);
-    EXPECT_EQ(info.decoding_cng, kStats.decoding_cng);
-    EXPECT_EQ(info.decoding_plc_cng, kStats.decoding_plc_cng);
-    EXPECT_EQ(info.capture_start_ntp_time_ms, kStats.capture_start_ntp_time_ms);
+              stats.decoding_calls_to_silence_generator);
+    EXPECT_EQ(info.decoding_calls_to_neteq, stats.decoding_calls_to_neteq);
+    EXPECT_EQ(info.decoding_normal, stats.decoding_normal);
+    EXPECT_EQ(info.decoding_plc, stats.decoding_plc);
+    EXPECT_EQ(info.decoding_cng, stats.decoding_cng);
+    EXPECT_EQ(info.decoding_plc_cng, stats.decoding_plc_cng);
+    EXPECT_EQ(info.capture_start_ntp_time_ms, stats.capture_start_ntp_time_ms);
   }
 
  protected:
@@ -2028,6 +2071,8 @@
     EXPECT_TRUE(channel_->AddSendStream(
         cricket::StreamParams::CreateLegacy(ssrc)));
   }
+  SetAudioSendStreamStats();
+
   // Create a receive stream to check that none of the send streams end up in
   // the receive stream stats.
   EXPECT_TRUE(channel_->AddRecvStream(
@@ -2036,41 +2081,42 @@
   EXPECT_TRUE(channel_->SetSendParameters(send_parameters_));
   EXPECT_TRUE(channel_->SetRecvParameters(recv_parameters_));
 
-  cricket::VoiceMediaInfo info;
-  EXPECT_EQ(true, channel_->GetStats(&info));
-  EXPECT_EQ(static_cast<size_t>(ARRAY_SIZE(kSsrcs4)), info.senders.size());
+  // Check stats for the added streams.
+  {
+    cricket::VoiceMediaInfo info;
+    EXPECT_EQ(true, channel_->GetStats(&info));
 
-  // Verify the statistic information is correct.
-  // TODO(solenberg): Make this loop ordering independent.
-  for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs4); ++i) {
-    EXPECT_EQ(kSsrcs4[i], info.senders[i].ssrc());
-    EXPECT_EQ(kPcmuCodec.name, info.senders[i].codec_name);
-    EXPECT_EQ(cricket::kIntStatValue, info.senders[i].bytes_sent);
-    EXPECT_EQ(cricket::kIntStatValue, info.senders[i].packets_sent);
-    EXPECT_EQ(cricket::kIntStatValue, info.senders[i].packets_lost);
-    EXPECT_EQ(cricket::kFractionLostStatValue, info.senders[i].fraction_lost);
-    EXPECT_EQ(cricket::kIntStatValue, info.senders[i].ext_seqnum);
-    EXPECT_EQ(cricket::kIntStatValue, info.senders[i].rtt_ms);
-    EXPECT_EQ(cricket::kIntStatValue, info.senders[i].jitter_ms);
-    EXPECT_EQ(kPcmuCodec.name, info.senders[i].codec_name);
+    // We have added 4 send streams. We should see empty stats for all.
+    EXPECT_EQ(static_cast<size_t>(ARRAY_SIZE(kSsrcs4)), info.senders.size());
+    for (const auto& sender : info.senders) {
+      VerifyVoiceSenderInfo(sender);
+    }
+
+    // We have added one receive stream. We should see empty stats.
+    EXPECT_EQ(info.receivers.size(), 1u);
+    EXPECT_EQ(info.receivers[0].ssrc(), 0);
   }
 
-  // We have added one receive stream. We should see empty stats.
-  EXPECT_EQ(info.receivers.size(), 1u);
-  EXPECT_EQ(info.receivers[0].local_stats.front().ssrc, 0);
-
   // Remove the kSsrc2 stream. No receiver stats.
-  EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc2));
-  EXPECT_EQ(true, channel_->GetStats(&info));
-  EXPECT_EQ(0u, info.receivers.size());
+  {
+    cricket::VoiceMediaInfo info;
+    EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc2));
+    EXPECT_EQ(true, channel_->GetStats(&info));
+    EXPECT_EQ(static_cast<size_t>(ARRAY_SIZE(kSsrcs4)), info.senders.size());
+    EXPECT_EQ(0u, info.receivers.size());
+  }
 
   // Deliver a new packet - a default receive stream should be created and we
   // should see stats again.
-  DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
-  SetAudioReceiveStreamStats();
-  EXPECT_EQ(true, channel_->GetStats(&info));
-  EXPECT_EQ(1u, info.receivers.size());
-  VerifyVoiceReceiverInfo(info.receivers[0]);
+  {
+    cricket::VoiceMediaInfo info;
+    DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+    SetAudioReceiveStreamStats();
+    EXPECT_EQ(true, channel_->GetStats(&info));
+    EXPECT_EQ(static_cast<size_t>(ARRAY_SIZE(kSsrcs4)), info.senders.size());
+    EXPECT_EQ(1u, info.receivers.size());
+    VerifyVoiceReceiverInfo(info.receivers[0]);
+  }
 }
 
 // Test that we can add and remove receive streams, and do proper send/playout.
@@ -2292,17 +2338,13 @@
 // SSRC is set in SetupEngine by calling AddSendStream.
 TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrc) {
   EXPECT_TRUE(SetupEngineWithSendStream());
-  int channel_num = voe_.GetLastChannel();
-  unsigned int send_ssrc;
-  EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
-  EXPECT_NE(0U, send_ssrc);
-  EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
-  EXPECT_EQ(kSsrc1, send_ssrc);
+  EXPECT_EQ(kSsrc1, voe_.GetLocalSSRC(voe_.GetLastChannel()));
 }
 
 TEST_F(WebRtcVoiceEngineTestFake, GetStats) {
   // Setup. We need send codec to be set to get all stats.
   EXPECT_TRUE(SetupEngineWithSendStream());
+  SetAudioSendStreamStats();
   // SetupEngineWithSendStream adds a send stream with kSsrc1, so the receive
   // stream has to use a different SSRC.
   EXPECT_TRUE(channel_->AddRecvStream(
@@ -2310,58 +2352,48 @@
   EXPECT_TRUE(channel_->SetSendParameters(send_parameters_));
   EXPECT_TRUE(channel_->SetRecvParameters(recv_parameters_));
 
-  cricket::VoiceMediaInfo info;
-  EXPECT_EQ(true, channel_->GetStats(&info));
-  EXPECT_EQ(1u, info.senders.size());
-  EXPECT_EQ(kSsrc1, info.senders[0].ssrc());
-  EXPECT_EQ(kPcmuCodec.name, info.senders[0].codec_name);
-  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].bytes_sent);
-  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].packets_sent);
-  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].packets_lost);
-  EXPECT_EQ(cricket::kFractionLostStatValue, info.senders[0].fraction_lost);
-  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].ext_seqnum);
-  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].rtt_ms);
-  EXPECT_EQ(cricket::kIntStatValue, info.senders[0].jitter_ms);
-  EXPECT_EQ(kPcmuCodec.name, info.senders[0].codec_name);
-  // TODO(sriniv): Add testing for more fields. These are not populated
-  // in FakeWebrtcVoiceEngine yet.
-  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].audio_level);
-  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].echo_delay_median_ms);
-  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].echo_delay_std_ms);
-  // EXPECT_EQ(cricket::kIntStatValue, info.senders[0].echo_return_loss);
-  // EXPECT_EQ(cricket::kIntStatValue,
-  //           info.senders[0].echo_return_loss_enhancement);
-  // We have added one receive stream. We should see empty stats.
-  EXPECT_EQ(info.receivers.size(), 1u);
-  EXPECT_EQ(info.receivers[0].local_stats.front().ssrc, 0);
+  // Check stats for the added streams.
+  {
+    cricket::VoiceMediaInfo info;
+    EXPECT_EQ(true, channel_->GetStats(&info));
+
+    // We have added one send stream. We should see the stats we've set.
+    EXPECT_EQ(1u, info.senders.size());
+    VerifyVoiceSenderInfo(info.senders[0]);
+    // We have added one receive stream. We should see empty stats.
+    EXPECT_EQ(info.receivers.size(), 1u);
+    EXPECT_EQ(info.receivers[0].ssrc(), 0);
+  }
 
   // Remove the kSsrc2 stream. No receiver stats.
-  EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc2));
-  EXPECT_EQ(true, channel_->GetStats(&info));
-  EXPECT_EQ(0u, info.receivers.size());
+  {
+    cricket::VoiceMediaInfo info;
+    EXPECT_TRUE(channel_->RemoveRecvStream(kSsrc2));
+    EXPECT_EQ(true, channel_->GetStats(&info));
+    EXPECT_EQ(1u, info.senders.size());
+    EXPECT_EQ(0u, info.receivers.size());
+  }
 
   // Deliver a new packet - a default receive stream should be created and we
   // should see stats again.
-  DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
-  SetAudioReceiveStreamStats();
-  EXPECT_EQ(true, channel_->GetStats(&info));
-  EXPECT_EQ(1u, info.receivers.size());
-  VerifyVoiceReceiverInfo(info.receivers[0]);
+  {
+    cricket::VoiceMediaInfo info;
+    DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+    SetAudioReceiveStreamStats();
+    EXPECT_EQ(true, channel_->GetStats(&info));
+    EXPECT_EQ(1u, info.senders.size());
+    EXPECT_EQ(1u, info.receivers.size());
+    VerifyVoiceReceiverInfo(info.receivers[0]);
+  }
 }
 
 // Test that we can set the outgoing SSRC properly with multiple streams.
 // SSRC is set in SetupEngine by calling AddSendStream.
 TEST_F(WebRtcVoiceEngineTestFake, SetSendSsrcWithMultipleStreams) {
   EXPECT_TRUE(SetupEngineWithSendStream());
-  int channel_num1 = voe_.GetLastChannel();
-  unsigned int send_ssrc;
-  EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num1, send_ssrc));
-  EXPECT_EQ(kSsrc1, send_ssrc);
-
+  EXPECT_EQ(kSsrc1, voe_.GetLocalSSRC(voe_.GetLastChannel()));
   EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
-  int channel_num2 = voe_.GetLastChannel();
-  EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num2, send_ssrc));
-  EXPECT_EQ(kSsrc1, send_ssrc);
+  EXPECT_EQ(kSsrc1, voe_.GetLocalSSRC(voe_.GetLastChannel()));
 }
 
 // Test that the local SSRC is the same on sending and receiving channels if the
@@ -2376,12 +2408,8 @@
       cricket::StreamParams::CreateLegacy(1234)));
   int send_channel_num = voe_.GetLastChannel();
 
-  unsigned int ssrc = 0;
-  EXPECT_EQ(0, voe_.GetLocalSSRC(send_channel_num, ssrc));
-  EXPECT_EQ(1234U, ssrc);
-  ssrc = 0;
-  EXPECT_EQ(0, voe_.GetLocalSSRC(receive_channel_num, ssrc));
-  EXPECT_EQ(1234U, ssrc);
+  EXPECT_EQ(1234U, voe_.GetLocalSSRC(send_channel_num));
+  EXPECT_EQ(1234U, voe_.GetLocalSSRC(receive_channel_num));
 }
 
 // Test that we can properly receive packets.
@@ -2545,7 +2573,6 @@
 
   bool ec_enabled;
   webrtc::EcModes ec_mode;
-  bool ec_metrics_enabled;
   webrtc::AecmModes aecm_mode;
   bool cng_enabled;
   bool agc_enabled;
@@ -2557,7 +2584,6 @@
   bool stereo_swapping_enabled;
   bool typing_detection_enabled;
   voe_.GetEcStatus(ec_enabled, ec_mode);
-  voe_.GetEcMetricsStatus(ec_metrics_enabled);
   voe_.GetAecmMode(aecm_mode, cng_enabled);
   voe_.GetAgcStatus(agc_enabled, agc_mode);
   voe_.GetAgcConfig(agc_config);
@@ -2566,7 +2592,7 @@
   stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
   voe_.GetTypingDetectionStatus(typing_detection_enabled);
   EXPECT_TRUE(ec_enabled);
-  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_TRUE(voe_.ec_metrics_enabled());
   EXPECT_FALSE(cng_enabled);
   EXPECT_TRUE(agc_enabled);
   EXPECT_EQ(0, agc_config.targetLeveldBOv);
@@ -2581,7 +2607,6 @@
   cricket::AudioOptions options;
   ASSERT_TRUE(engine_.SetOptions(options));
   voe_.GetEcStatus(ec_enabled, ec_mode);
-  voe_.GetEcMetricsStatus(ec_metrics_enabled);
   voe_.GetAecmMode(aecm_mode, cng_enabled);
   voe_.GetAgcStatus(agc_enabled, agc_mode);
   voe_.GetAgcConfig(agc_config);
@@ -2590,7 +2615,7 @@
   stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
   voe_.GetTypingDetectionStatus(typing_detection_enabled);
   EXPECT_TRUE(ec_enabled);
-  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_TRUE(voe_.ec_metrics_enabled());
   EXPECT_FALSE(cng_enabled);
   EXPECT_TRUE(agc_enabled);
   EXPECT_EQ(0, agc_config.targetLeveldBOv);
@@ -2615,7 +2640,6 @@
   options.echo_cancellation.Set(true);
   ASSERT_TRUE(engine_.SetOptions(options));
   voe_.GetEcStatus(ec_enabled, ec_mode);
-  voe_.GetEcMetricsStatus(ec_metrics_enabled);
   voe_.GetAecmMode(aecm_mode, cng_enabled);
   voe_.GetAgcStatus(agc_enabled, agc_mode);
   voe_.GetAgcConfig(agc_config);
@@ -2624,7 +2648,7 @@
   stereo_swapping_enabled = voe_.IsStereoChannelSwappingEnabled();
   voe_.GetTypingDetectionStatus(typing_detection_enabled);
   EXPECT_TRUE(ec_enabled);
-  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_TRUE(voe_.ec_metrics_enabled());
   EXPECT_TRUE(agc_enabled);
   EXPECT_EQ(0, agc_config.targetLeveldBOv);
   EXPECT_TRUE(ns_enabled);
@@ -2639,10 +2663,9 @@
   options.delay_agnostic_aec.Set(true);
   ASSERT_TRUE(engine_.SetOptions(options));
   voe_.GetEcStatus(ec_enabled, ec_mode);
-  voe_.GetEcMetricsStatus(ec_metrics_enabled);
   voe_.GetAecmMode(aecm_mode, cng_enabled);
   EXPECT_TRUE(ec_enabled);
-  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_TRUE(voe_.ec_metrics_enabled());
   EXPECT_EQ(ec_mode, webrtc::kEcConference);
 
   // Turn off echo cancellation and delay agnostic aec.
@@ -2656,9 +2679,8 @@
   options.delay_agnostic_aec.Set(true);
   ASSERT_TRUE(engine_.SetOptions(options));
   voe_.GetEcStatus(ec_enabled, ec_mode);
-  voe_.GetEcMetricsStatus(ec_metrics_enabled);
   EXPECT_TRUE(ec_enabled);
-  EXPECT_TRUE(ec_metrics_enabled);
+  EXPECT_TRUE(voe_.ec_metrics_enabled());
   EXPECT_EQ(ec_mode, webrtc::kEcConference);
 
   // Turn off AGC
@@ -2706,7 +2728,6 @@
 
   bool ec_enabled;
   webrtc::EcModes ec_mode;
-  bool ec_metrics_enabled;
   bool agc_enabled;
   webrtc::AgcModes agc_mode;
   bool ns_enabled;
@@ -2716,7 +2737,6 @@
   bool typing_detection_enabled;
 
   voe_.GetEcStatus(ec_enabled, ec_mode);
-  voe_.GetEcMetricsStatus(ec_metrics_enabled);
   voe_.GetAgcStatus(agc_enabled, agc_mode);
   voe_.GetNsStatus(ns_enabled, ns_mode);
   highpass_filter_enabled = voe_.IsHighPassFilterEnabled();
@@ -2978,7 +2998,7 @@
   for (uint32_t ssrc : ssrcs) {
     const auto* s = call_.GetAudioReceiveStream(ssrc);
     EXPECT_NE(nullptr, s);
-    EXPECT_EQ(false, s->GetConfig().combined_audio_video_bwe);
+    EXPECT_FALSE(s->GetConfig().combined_audio_video_bwe);
   }
 
   // Enable combined BWE option - now it should be set up.
@@ -2996,7 +3016,7 @@
   for (uint32_t ssrc : ssrcs) {
     const auto* s = call_.GetAudioReceiveStream(ssrc);
     EXPECT_NE(nullptr, s);
-    EXPECT_EQ(false, s->GetConfig().combined_audio_video_bwe);
+    EXPECT_FALSE(s->GetConfig().combined_audio_video_bwe);
   }
 
   EXPECT_EQ(2, call_.GetAudioReceiveStreams().size());
diff --git a/webrtc/audio/audio_receive_stream.cc b/webrtc/audio/audio_receive_stream.cc
index 0fd96d0..b3cacba 100644
--- a/webrtc/audio/audio_receive_stream.cc
+++ b/webrtc/audio/audio_receive_stream.cc
@@ -28,6 +28,7 @@
 std::string AudioReceiveStream::Config::Rtp::ToString() const {
   std::stringstream ss;
   ss << "{remote_ssrc: " << remote_ssrc;
+  ss << ", local_ssrc: " << local_ssrc;
   ss << ", extensions: [";
   for (size_t i = 0; i < extensions.size(); ++i) {
     ss << extensions[i].ToString();
@@ -43,10 +44,16 @@
 std::string AudioReceiveStream::Config::ToString() const {
   std::stringstream ss;
   ss << "{rtp: " << rtp.ToString();
+  ss << ", receive_transport: "
+     << (receive_transport ? "(Transport)" : "nullptr");
+  ss << ", rtcp_send_transport: "
+     << (rtcp_send_transport ? "(Transport)" : "nullptr");
   ss << ", voe_channel_id: " << voe_channel_id;
   if (!sync_group.empty()) {
     ss << ", sync_group: " << sync_group;
   }
+  ss << ", combined_audio_video_bwe: "
+     << (combined_audio_video_bwe ? "true" : "false");
   ss << '}';
   return ss.str();
 }
@@ -61,7 +68,6 @@
       voice_engine_(voice_engine),
       voe_base_(voice_engine),
       rtp_header_parser_(RtpHeaderParser::Create()) {
-  RTC_DCHECK(thread_checker_.CalledOnValidThread());
   LOG(LS_INFO) << "AudioReceiveStream: " << config_.ToString();
   RTC_DCHECK(config.voe_channel_id != -1);
   RTC_DCHECK(remote_bitrate_estimator_ != nullptr);
@@ -101,26 +107,25 @@
   ScopedVoEInterface<VoEVideoSync> sync(voice_engine_);
   ScopedVoEInterface<VoEVolumeControl> volume(voice_engine_);
   unsigned int ssrc = 0;
-  webrtc::CallStatistics cs = {0};
-  webrtc::CodecInst ci = {0};
+  webrtc::CallStatistics call_stats = {0};
+  webrtc::CodecInst codec_inst = {0};
   // Only collect stats if we have seen some traffic with the SSRC.
   if (rtp->GetRemoteSSRC(config_.voe_channel_id, ssrc) == -1 ||
-      rtp->GetRTCPStatistics(config_.voe_channel_id, cs) == -1 ||
-      codec->GetRecCodec(config_.voe_channel_id, ci) == -1) {
+      rtp->GetRTCPStatistics(config_.voe_channel_id, call_stats) == -1 ||
+      codec->GetRecCodec(config_.voe_channel_id, codec_inst) == -1) {
     return stats;
   }
 
-  stats.bytes_rcvd = cs.bytesReceived;
-  stats.packets_rcvd = cs.packetsReceived;
-  stats.packets_lost = cs.cumulativeLost;
-  stats.fraction_lost = static_cast<float>(cs.fractionLost) / (1 << 8);
-  if (ci.pltype != -1) {
-    stats.codec_name = ci.plname;
+  stats.bytes_rcvd = call_stats.bytesReceived;
+  stats.packets_rcvd = call_stats.packetsReceived;
+  stats.packets_lost = call_stats.cumulativeLost;
+  stats.fraction_lost = Q8ToFloat(call_stats.fractionLost);
+  if (codec_inst.pltype != -1) {
+    stats.codec_name = codec_inst.plname;
   }
-
-  stats.ext_seqnum = cs.extendedMax;
-  if (ci.plfreq / 1000 > 0) {
-    stats.jitter_ms = cs.jitterSamples / (ci.plfreq / 1000);
+  stats.ext_seqnum = call_stats.extendedMax;
+  if (codec_inst.plfreq / 1000 > 0) {
+    stats.jitter_ms = call_stats.jitterSamples / (codec_inst.plfreq / 1000);
   }
   {
     int jitter_buffer_delay_ms = 0;
@@ -161,7 +166,7 @@
     stats.decoding_plc_cng = ds.decoded_plc_cng;
   }
 
-  stats.capture_start_ntp_time_ms = cs.capture_start_ntp_time_ms_;
+  stats.capture_start_ntp_time_ms = call_stats.capture_start_ntp_time_ms_;
 
   return stats;
 }
diff --git a/webrtc/audio/audio_receive_stream.h b/webrtc/audio/audio_receive_stream.h
index 5c77653..5d02b0e 100644
--- a/webrtc/audio/audio_receive_stream.h
+++ b/webrtc/audio/audio_receive_stream.h
@@ -24,7 +24,7 @@
 
 namespace internal {
 
-class AudioReceiveStream : public webrtc::AudioReceiveStream {
+class AudioReceiveStream final : public webrtc::AudioReceiveStream {
  public:
   AudioReceiveStream(RemoteBitrateEstimator* remote_bitrate_estimator,
                      const webrtc::AudioReceiveStream::Config& config,
@@ -53,6 +53,8 @@
   // We hold one interface pointer to the VoE to make sure it is kept alive.
   ScopedVoEInterface<VoEBase> voe_base_;
   rtc::scoped_ptr<RtpHeaderParser> rtp_header_parser_;
+
+  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioReceiveStream);
 };
 }  // namespace internal
 }  // namespace webrtc
diff --git a/webrtc/audio/audio_receive_stream_unittest.cc b/webrtc/audio/audio_receive_stream_unittest.cc
index 8809b35..4e267f1 100644
--- a/webrtc/audio/audio_receive_stream_unittest.cc
+++ b/webrtc/audio/audio_receive_stream_unittest.cc
@@ -61,12 +61,36 @@
 namespace webrtc {
 namespace test {
 
+TEST(AudioReceiveStreamTest, ConfigToString) {
+  const int kAbsSendTimeId = 3;
+  AudioReceiveStream::Config config;
+  config.rtp.remote_ssrc = 1234;
+  config.rtp.local_ssrc = 5678;
+  config.rtp.extensions.push_back(
+      RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeId));
+  config.voe_channel_id = 1;
+  config.combined_audio_video_bwe = true;
+  EXPECT_EQ("{rtp: {remote_ssrc: 1234, local_ssrc: 5678, extensions: [{name: "
+      "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, id: 3}]}, "
+      "receive_transport: nullptr, rtcp_send_transport: nullptr, "
+      "voe_channel_id: 1, combined_audio_video_bwe: true}", config.ToString());
+}
+
+TEST(AudioReceiveStreamTest, ConstructDestruct) {
+  MockRemoteBitrateEstimator remote_bitrate_estimator;
+  FakeVoiceEngine voice_engine;
+  AudioReceiveStream::Config config;
+  config.voe_channel_id = 1;
+  internal::AudioReceiveStream recv_stream(&remote_bitrate_estimator, config,
+                                           &voice_engine);
+}
+
 TEST(AudioReceiveStreamTest, AudioPacketUpdatesBweWithTimestamp) {
   MockRemoteBitrateEstimator remote_bitrate_estimator;
   FakeVoiceEngine voice_engine;
   AudioReceiveStream::Config config;
   config.combined_audio_video_bwe = true;
-  config.voe_channel_id = voice_engine.kReceiveChannelId;
+  config.voe_channel_id = FakeVoiceEngine::kRecvChannelId;
   const int kAbsSendTimeId = 3;
   config.rtp.extensions.push_back(
       RtpExtension(RtpExtension::kAbsSendTime, kAbsSendTimeId));
@@ -86,38 +110,35 @@
 }
 
 TEST(AudioReceiveStreamTest, GetStats) {
-  const uint32_t kSsrc1 = 667;
-
   MockRemoteBitrateEstimator remote_bitrate_estimator;
   FakeVoiceEngine voice_engine;
   AudioReceiveStream::Config config;
-  config.rtp.remote_ssrc = kSsrc1;
-  config.voe_channel_id = voice_engine.kReceiveChannelId;
+  config.rtp.remote_ssrc = FakeVoiceEngine::kRecvSsrc;
+  config.voe_channel_id = FakeVoiceEngine::kRecvChannelId;
   internal::AudioReceiveStream recv_stream(&remote_bitrate_estimator, config,
                                            &voice_engine);
 
   AudioReceiveStream::Stats stats = recv_stream.GetStats();
-  const CallStatistics& call_stats = voice_engine.GetRecvCallStats();
-  const CodecInst& codec_inst = voice_engine.GetRecvRecCodecInst();
-  const NetworkStatistics& net_stats = voice_engine.GetRecvNetworkStats();
+  const CallStatistics& call_stats = FakeVoiceEngine::kRecvCallStats;
+  const CodecInst& codec_inst = FakeVoiceEngine::kRecvCodecInst;
+  const NetworkStatistics& net_stats = FakeVoiceEngine::kRecvNetworkStats;
   const AudioDecodingCallStats& decode_stats =
-      voice_engine.GetRecvAudioDecodingCallStats();
-  EXPECT_EQ(kSsrc1, stats.remote_ssrc);
+      FakeVoiceEngine::kRecvAudioDecodingCallStats;
+  EXPECT_EQ(FakeVoiceEngine::kRecvSsrc, stats.remote_ssrc);
   EXPECT_EQ(static_cast<int64_t>(call_stats.bytesReceived), stats.bytes_rcvd);
   EXPECT_EQ(static_cast<uint32_t>(call_stats.packetsReceived),
             stats.packets_rcvd);
   EXPECT_EQ(call_stats.cumulativeLost, stats.packets_lost);
-  EXPECT_EQ(static_cast<float>(call_stats.fractionLost) / 256,
-            stats.fraction_lost);
+  EXPECT_EQ(Q8ToFloat(call_stats.fractionLost), stats.fraction_lost);
   EXPECT_EQ(std::string(codec_inst.plname), stats.codec_name);
   EXPECT_EQ(call_stats.extendedMax, stats.ext_seqnum);
   EXPECT_EQ(call_stats.jitterSamples / (codec_inst.plfreq / 1000),
             stats.jitter_ms);
   EXPECT_EQ(net_stats.currentBufferSize, stats.jitter_buffer_ms);
   EXPECT_EQ(net_stats.preferredBufferSize, stats.jitter_buffer_preferred_ms);
-  EXPECT_EQ(static_cast<uint32_t>(voice_engine.kRecvJitterBufferDelay +
-      voice_engine.kRecvPlayoutBufferDelay), stats.delay_estimate_ms);
-  EXPECT_EQ(static_cast<int32_t>(voice_engine.kRecvSpeechOutputLevel),
+  EXPECT_EQ(static_cast<uint32_t>(FakeVoiceEngine::kRecvJitterBufferDelay +
+      FakeVoiceEngine::kRecvPlayoutBufferDelay), stats.delay_estimate_ms);
+  EXPECT_EQ(static_cast<int32_t>(FakeVoiceEngine::kRecvSpeechOutputLevel),
             stats.audio_level);
   EXPECT_EQ(Q14ToFloat(net_stats.currentExpandRate), stats.expand_rate);
   EXPECT_EQ(Q14ToFloat(net_stats.currentSpeechExpandRate),
diff --git a/webrtc/audio/audio_send_stream.cc b/webrtc/audio/audio_send_stream.cc
index 0d0c072..ccfdca5 100644
--- a/webrtc/audio/audio_send_stream.cc
+++ b/webrtc/audio/audio_send_stream.cc
@@ -12,8 +12,13 @@
 
 #include <string>
 
+#include "webrtc/audio/conversion.h"
 #include "webrtc/base/checks.h"
 #include "webrtc/base/logging.h"
+#include "webrtc/voice_engine/include/voe_audio_processing.h"
+#include "webrtc/voice_engine/include/voe_codec.h"
+#include "webrtc/voice_engine/include/voe_rtp_rtcp.h"
+#include "webrtc/voice_engine/include/voe_volume_control.h"
 
 namespace webrtc {
 std::string AudioSendStream::Config::Rtp::ToString() const {
@@ -22,8 +27,9 @@
   ss << ", extensions: [";
   for (size_t i = 0; i < extensions.size(); ++i) {
     ss << extensions[i].ToString();
-    if (i != extensions.size() - 1)
+    if (i != extensions.size() - 1) {
       ss << ", ";
+    }
   }
   ss << ']';
   ss << '}';
@@ -42,30 +48,134 @@
 }
 
 namespace internal {
-AudioSendStream::AudioSendStream(const webrtc::AudioSendStream::Config& config)
-    : config_(config) {
+AudioSendStream::AudioSendStream(const webrtc::AudioSendStream::Config& config,
+                                 VoiceEngine* voice_engine)
+    : config_(config),
+      voice_engine_(voice_engine),
+      voe_base_(voice_engine) {
   LOG(LS_INFO) << "AudioSendStream: " << config_.ToString();
-  RTC_DCHECK(config.voe_channel_id != -1);
+  RTC_DCHECK_NE(config.voe_channel_id, -1);
+  RTC_DCHECK(voice_engine_);
 }
 
 AudioSendStream::~AudioSendStream() {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
   LOG(LS_INFO) << "~AudioSendStream: " << config_.ToString();
 }
 
 webrtc::AudioSendStream::Stats AudioSendStream::GetStats() const {
-  return webrtc::AudioSendStream::Stats();
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
+  webrtc::AudioSendStream::Stats stats;
+  stats.local_ssrc = config_.rtp.ssrc;
+  ScopedVoEInterface<VoEAudioProcessing> processing(voice_engine_);
+  ScopedVoEInterface<VoECodec> codec(voice_engine_);
+  ScopedVoEInterface<VoERTP_RTCP> rtp(voice_engine_);
+  ScopedVoEInterface<VoEVolumeControl> volume(voice_engine_);
+  unsigned int ssrc = 0;
+  webrtc::CallStatistics call_stats = {0};
+  if (rtp->GetLocalSSRC(config_.voe_channel_id, ssrc) == -1 ||
+      rtp->GetRTCPStatistics(config_.voe_channel_id, call_stats) == -1) {
+    return stats;
+  }
+
+  stats.bytes_sent = call_stats.bytesSent;
+  stats.packets_sent = call_stats.packetsSent;
+
+  webrtc::CodecInst codec_inst = {0};
+  if (codec->GetSendCodec(config_.voe_channel_id, codec_inst) != -1) {
+    RTC_DCHECK_NE(codec_inst.pltype, -1);
+    stats.codec_name = codec_inst.plname;
+
+    // Get data from the last remote RTCP report.
+    std::vector<webrtc::ReportBlock> blocks;
+    if (rtp->GetRemoteRTCPReportBlocks(config_.voe_channel_id, &blocks) != -1) {
+      for (const webrtc::ReportBlock& block : blocks) {
+        // Lookup report for send ssrc only.
+        if (block.source_SSRC == stats.local_ssrc) {
+          stats.packets_lost = block.cumulative_num_packets_lost;
+          stats.fraction_lost = Q8ToFloat(block.fraction_lost);
+          stats.ext_seqnum = block.extended_highest_sequence_number;
+          // Convert samples to milliseconds.
+          if (codec_inst.plfreq / 1000 > 0) {
+            stats.jitter_ms =
+                block.interarrival_jitter / (codec_inst.plfreq / 1000);
+          }
+          break;
+        }
+      }
+    }
+  }
+
+  // RTT isn't known until a RTCP report is received. Until then, VoiceEngine
+  // returns 0 to indicate an error value.
+  if (call_stats.rttMs > 0) {
+    stats.rtt_ms = call_stats.rttMs;
+  }
+
+  // Local speech level.
+  {
+    unsigned int level = 0;
+    if (volume->GetSpeechInputLevelFullRange(level) != -1) {
+      stats.audio_level = static_cast<int32_t>(level);
+    }
+  }
+
+  // TODO(ajm): Re-enable this metric once we have a reliable implementation.
+  stats.aec_quality_min = -1;
+
+  bool echo_metrics_on = false;
+  if (processing->GetEcMetricsStatus(echo_metrics_on) != -1 &&
+      echo_metrics_on) {
+    // These can also be negative, but in practice -1 is only used to signal
+    // insufficient data, since the resolution is limited to multiples of 4 ms.
+    int median = -1;
+    int std = -1;
+    float dummy = 0.0f;
+    if (processing->GetEcDelayMetrics(median, std, dummy) != -1) {
+      stats.echo_delay_median_ms = median;
+      stats.echo_delay_std_ms = std;
+    }
+
+    // These can take on valid negative values, so use the lowest possible level
+    // as default rather than -1.
+    int erl = -100;
+    int erle = -100;
+    int dummy1 = 0;
+    int dummy2 = 0;
+    if (processing->GetEchoMetrics(erl, erle, dummy1, dummy2) != -1) {
+      stats.echo_return_loss = erl;
+      stats.echo_return_loss_enhancement = erle;
+    }
+  }
+
+  // TODO(solenberg): Collect typing noise warnings here too!
+  // bool typing_noise_detected = typing_noise_detected_;
+
+  return stats;
+}
+
+const webrtc::AudioSendStream::Config& AudioSendStream::config() const {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
+  return config_;
 }
 
 void AudioSendStream::Start() {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
 }
 
 void AudioSendStream::Stop() {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
 }
 
 void AudioSendStream::SignalNetworkState(NetworkState state) {
+  RTC_DCHECK(thread_checker_.CalledOnValidThread());
 }
 
 bool AudioSendStream::DeliverRtcp(const uint8_t* packet, size_t length) {
+  // TODO(solenberg): Tests call this function on a network thread, libjingle
+  // calls on the worker thread. We should move towards always using a network
+  // thread. Then this check can be enabled.
+  // RTC_DCHECK(!thread_checker_.CalledOnValidThread());
   return false;
 }
 }  // namespace internal
diff --git a/webrtc/audio/audio_send_stream.h b/webrtc/audio/audio_send_stream.h
index 54046fc..ae81dfc 100644
--- a/webrtc/audio/audio_send_stream.h
+++ b/webrtc/audio/audio_send_stream.h
@@ -12,13 +12,20 @@
 #define WEBRTC_AUDIO_AUDIO_SEND_STREAM_H_
 
 #include "webrtc/audio_send_stream.h"
+#include "webrtc/audio/scoped_voe_interface.h"
+#include "webrtc/base/thread_checker.h"
+#include "webrtc/voice_engine/include/voe_base.h"
 
 namespace webrtc {
+
+class VoiceEngine;
+
 namespace internal {
 
-class AudioSendStream : public webrtc::AudioSendStream {
+class AudioSendStream final : public webrtc::AudioSendStream {
  public:
-  explicit AudioSendStream(const webrtc::AudioSendStream::Config& config);
+  AudioSendStream(const webrtc::AudioSendStream::Config& config,
+                  VoiceEngine* voice_engine);
   ~AudioSendStream() override;
 
   // webrtc::SendStream implementation.
@@ -30,12 +37,16 @@
   // webrtc::AudioSendStream implementation.
   webrtc::AudioSendStream::Stats GetStats() const override;
 
-  const webrtc::AudioSendStream::Config& config() const {
-    return config_;
-  }
+  const webrtc::AudioSendStream::Config& config() const;
 
  private:
+  rtc::ThreadChecker thread_checker_;
   const webrtc::AudioSendStream::Config config_;
+  VoiceEngine* voice_engine_;
+  // We hold one interface pointer to the VoE to make sure it is kept alive.
+  ScopedVoEInterface<VoEBase> voe_base_;
+
+  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(AudioSendStream);
 };
 }  // namespace internal
 }  // namespace webrtc
diff --git a/webrtc/audio/audio_send_stream_unittest.cc b/webrtc/audio/audio_send_stream_unittest.cc
index e5d73ff..227ec83 100644
--- a/webrtc/audio/audio_send_stream_unittest.cc
+++ b/webrtc/audio/audio_send_stream_unittest.cc
@@ -11,8 +11,11 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 #include "webrtc/audio/audio_send_stream.h"
+#include "webrtc/audio/conversion.h"
+#include "webrtc/test/fake_voice_engine.h"
 
 namespace webrtc {
+namespace test {
 
 TEST(AudioSendStreamTest, ConfigToString) {
   const int kAbsSendTimeId = 3;
@@ -23,12 +26,51 @@
   config.voe_channel_id = 1;
   config.cng_payload_type = 42;
   config.red_payload_type = 17;
-  EXPECT_GT(config.ToString().size(), 0u);
+  EXPECT_EQ("{rtp: {ssrc: 1234, extensions: [{name: "
+      "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time, id: 3}]}, "
+      "voe_channel_id: 1, cng_payload_type: 42, red_payload_type: 17}",
+      config.ToString());
 }
 
 TEST(AudioSendStreamTest, ConstructDestruct) {
+  FakeVoiceEngine voice_engine;
   AudioSendStream::Config config(nullptr);
   config.voe_channel_id = 1;
-  internal::AudioSendStream send_stream(config);
+  internal::AudioSendStream send_stream(config, &voice_engine);
 }
+
+TEST(AudioSendStreamTest, GetStats) {
+  FakeVoiceEngine voice_engine;
+  AudioSendStream::Config config(nullptr);
+  config.rtp.ssrc = FakeVoiceEngine::kSendSsrc;
+  config.voe_channel_id = FakeVoiceEngine::kSendChannelId;
+  internal::AudioSendStream send_stream(config, &voice_engine);
+
+  AudioSendStream::Stats stats = send_stream.GetStats();
+  const CallStatistics& call_stats = FakeVoiceEngine::kSendCallStats;
+  const CodecInst& codec_inst = FakeVoiceEngine::kSendCodecInst;
+  const ReportBlock& report_block = FakeVoiceEngine::kSendReportBlock;
+  EXPECT_EQ(FakeVoiceEngine::kSendSsrc, stats.local_ssrc);
+  EXPECT_EQ(static_cast<int64_t>(call_stats.bytesSent), stats.bytes_sent);
+  EXPECT_EQ(call_stats.packetsSent, stats.packets_sent);
+  EXPECT_EQ(static_cast<int32_t>(report_block.cumulative_num_packets_lost),
+            stats.packets_lost);
+  EXPECT_EQ(Q8ToFloat(report_block.fraction_lost), stats.fraction_lost);
+  EXPECT_EQ(std::string(codec_inst.plname), stats.codec_name);
+  EXPECT_EQ(static_cast<int32_t>(report_block.extended_highest_sequence_number),
+            stats.ext_seqnum);
+  EXPECT_EQ(static_cast<int32_t>(report_block.interarrival_jitter /
+                (codec_inst.plfreq / 1000)), stats.jitter_ms);
+  EXPECT_EQ(call_stats.rttMs, stats.rtt_ms);
+  EXPECT_EQ(static_cast<int32_t>(FakeVoiceEngine::kSendSpeechInputLevel),
+            stats.audio_level);
+  EXPECT_EQ(-1, stats.aec_quality_min);
+  EXPECT_EQ(FakeVoiceEngine::kSendEchoDelayMedian, stats.echo_delay_median_ms);
+  EXPECT_EQ(FakeVoiceEngine::kSendEchoDelayStdDev, stats.echo_delay_std_ms);
+  EXPECT_EQ(FakeVoiceEngine::kSendEchoReturnLoss, stats.echo_return_loss);
+  EXPECT_EQ(FakeVoiceEngine::kSendEchoReturnLossEnhancement,
+            stats.echo_return_loss_enhancement);
+  EXPECT_FALSE(stats.typing_noise_detected);
+}
+}  // namespace test
 }  // namespace webrtc
diff --git a/webrtc/audio/conversion.h b/webrtc/audio/conversion.h
index c1cf9b6..6ae3243 100644
--- a/webrtc/audio/conversion.h
+++ b/webrtc/audio/conversion.h
@@ -13,8 +13,13 @@
 
 namespace webrtc {
 
+// Convert fixed point number with 8 bit fractional part, to floating point.
+inline float Q8ToFloat(uint32_t v) {
+  return static_cast<float>(v) / (1 << 8);
+}
+
 // Convert fixed point number with 14 bit fractional part, to floating point.
-inline float Q14ToFloat(uint16_t v) {
+inline float Q14ToFloat(uint32_t v) {
   return static_cast<float>(v) / (1 << 14);
 }
 }  // namespace webrtc
diff --git a/webrtc/audio_send_stream.h b/webrtc/audio_send_stream.h
index b96a8ef..89b73e6 100644
--- a/webrtc/audio_send_stream.h
+++ b/webrtc/audio_send_stream.h
@@ -25,7 +25,25 @@
 
 class AudioSendStream : public SendStream {
  public:
-  struct Stats {};
+  struct Stats {
+    // TODO(solenberg): Harmonize naming and defaults with receive stream stats.
+    uint32_t local_ssrc = 0;
+    int64_t bytes_sent = 0;
+    int32_t packets_sent = 0;
+    int32_t packets_lost = -1;
+    float fraction_lost = -1.0f;
+    std::string codec_name;
+    int32_t ext_seqnum = -1;
+    int32_t jitter_ms = -1;
+    int64_t rtt_ms = -1;
+    int32_t audio_level = -1;
+    float aec_quality_min = -1.0f;
+    int32_t echo_delay_median_ms = -1;
+    int32_t echo_delay_std_ms = -1;
+    int32_t echo_return_loss = -100;
+    int32_t echo_return_loss_enhancement = -100;
+    bool typing_noise_detected = false;
+  };
 
   struct Config {
     Config() = delete;
diff --git a/webrtc/call/call.cc b/webrtc/call/call.cc
index cdb4f5d..eda209a 100644
--- a/webrtc/call/call.cc
+++ b/webrtc/call/call.cc
@@ -145,7 +145,6 @@
       network_enabled_(true),
       receive_crit_(RWLockWrapper::CreateRWLock()),
       send_crit_(RWLockWrapper::CreateRWLock()) {
-  RTC_DCHECK(configuration_thread_checker_.CalledOnValidThread());
   RTC_DCHECK_GE(config.bitrate_config.min_bitrate_bps, 0);
   RTC_DCHECK_GE(config.bitrate_config.start_bitrate_bps,
                 config.bitrate_config.min_bitrate_bps);
@@ -199,7 +198,8 @@
     const webrtc::AudioSendStream::Config& config) {
   TRACE_EVENT0("webrtc", "Call::CreateAudioSendStream");
   RTC_DCHECK(configuration_thread_checker_.CalledOnValidThread());
-  AudioSendStream* send_stream = new AudioSendStream(config);
+  AudioSendStream* send_stream =
+      new AudioSendStream(config, config_.voice_engine);
   if (!network_enabled_)
     send_stream->SignalNetworkState(kNetworkDown);
   {
diff --git a/webrtc/test/fake_voice_engine.cc b/webrtc/test/fake_voice_engine.cc
new file mode 100644
index 0000000..1a32e08
--- /dev/null
+++ b/webrtc/test/fake_voice_engine.cc
@@ -0,0 +1,70 @@
+/*
+ *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/test/fake_voice_engine.h"
+
+namespace {
+
+webrtc::AudioDecodingCallStats MakeAudioDecodingCallStats() {
+  webrtc::AudioDecodingCallStats stats;
+  stats.calls_to_silence_generator = 234;
+  stats.calls_to_neteq = 567;
+  stats.decoded_normal = 890;
+  stats.decoded_plc = 123;
+  stats.decoded_cng = 456;
+  stats.decoded_plc_cng = 789;
+  return stats;
+}
+}  // namespace
+
+namespace webrtc {
+namespace test {
+
+const int FakeVoiceEngine::kSendChannelId = 1;
+const int FakeVoiceEngine::kRecvChannelId = 2;
+const uint32_t FakeVoiceEngine::kSendSsrc = 665;
+const uint32_t FakeVoiceEngine::kRecvSsrc = 667;
+const int FakeVoiceEngine::kSendEchoDelayMedian = 254;
+const int FakeVoiceEngine::kSendEchoDelayStdDev = -3;
+const int FakeVoiceEngine::kSendEchoReturnLoss = -65;
+const int FakeVoiceEngine::kSendEchoReturnLossEnhancement = 101;
+const int FakeVoiceEngine::kRecvJitterBufferDelay = -7;
+const int FakeVoiceEngine::kRecvPlayoutBufferDelay = 302;
+const unsigned int FakeVoiceEngine::kSendSpeechInputLevel = 96;
+const unsigned int FakeVoiceEngine::kRecvSpeechOutputLevel = 99;
+
+const CallStatistics FakeVoiceEngine::kSendCallStats = {
+  1345, 1678, 1901, 1234, 112, 13456, 17890, 1567, -1890, -1123
+};
+
+const CodecInst FakeVoiceEngine::kSendCodecInst = {
+  -121, "codec_name_send", 48000, -231, -451, -671
+};
+
+const ReportBlock FakeVoiceEngine::kSendReportBlock = {
+  456, 780, 123, 567, 890, 132, 143, 13354
+};
+
+const CallStatistics FakeVoiceEngine::kRecvCallStats = {
+  345, 678, 901, 234, -12, 3456, 7890, 567, 890, 123
+};
+
+const CodecInst FakeVoiceEngine::kRecvCodecInst = {
+  123, "codec_name_recv", 96000, -187, -198, -103
+};
+
+const NetworkStatistics FakeVoiceEngine::kRecvNetworkStats = {
+  123, 456, false, 0, 0, 789, 12, 345, 678, 901, -1, -1, -1, -1, -1, 0
+};
+
+const AudioDecodingCallStats FakeVoiceEngine::kRecvAudioDecodingCallStats =
+    MakeAudioDecodingCallStats();
+}  // namespace test
+}  // namespace webrtc
diff --git a/webrtc/test/fake_voice_engine.h b/webrtc/test/fake_voice_engine.h
index 72f6b27..8f08929 100644
--- a/webrtc/test/fake_voice_engine.h
+++ b/webrtc/test/fake_voice_engine.h
@@ -24,12 +24,25 @@
 // able to get the various interfaces as usual, via T::GetInterface().
 class FakeVoiceEngine final : public VoiceEngineImpl {
  public:
-  const int kSendChannelId = 1;
-  const int kReceiveChannelId = 2;
-
-  const int kRecvJitterBufferDelay = -7;
-  const int kRecvPlayoutBufferDelay = 302;
-  const unsigned int kRecvSpeechOutputLevel = 99;
+  static const int kSendChannelId;
+  static const int kRecvChannelId;
+  static const uint32_t kSendSsrc;
+  static const uint32_t kRecvSsrc;
+  static const int kSendEchoDelayMedian;
+  static const int kSendEchoDelayStdDev;
+  static const int kSendEchoReturnLoss;
+  static const int kSendEchoReturnLossEnhancement;
+  static const int kRecvJitterBufferDelay;
+  static const int kRecvPlayoutBufferDelay;
+  static const unsigned int kSendSpeechInputLevel;
+  static const unsigned int kRecvSpeechOutputLevel;
+  static const CallStatistics kSendCallStats;
+  static const CodecInst kSendCodecInst;
+  static const ReportBlock kSendReportBlock;
+  static const CallStatistics kRecvCallStats;
+  static const CodecInst kRecvCodecInst;
+  static const NetworkStatistics kRecvNetworkStats;
+  static const AudioDecodingCallStats kRecvAudioDecodingCallStats;
 
   FakeVoiceEngine() : VoiceEngineImpl(new Config(), true) {
     // Increase ref count so this object isn't automatically deleted whenever
@@ -42,39 +55,83 @@
     --_ref_count;
   }
 
-  const CallStatistics& GetRecvCallStats() const {
-    static const CallStatistics kStats = {
-      345, 678, 901, 234, -1, 0, 0, 567, 890, 123
-    };
-    return kStats;
+  // VoEAudioProcessing
+  int SetNsStatus(bool enable, NsModes mode = kNsUnchanged) override {
+    return -1;
   }
-
-  const CodecInst& GetRecvRecCodecInst() const {
-    static const CodecInst kStats = {
-      123, "codec_name", 96000, -1, -1, -1
-    };
-    return kStats;
+  int GetNsStatus(bool& enabled, NsModes& mode) override { return -1; }
+  int SetAgcStatus(bool enable, AgcModes mode = kAgcUnchanged) override {
+    return -1;
   }
-
-  const NetworkStatistics& GetRecvNetworkStats() const {
-    static const NetworkStatistics kStats = {
-      123, 456, false, 0, 0, 789, 12, 345, 678, 901, -1, -1, -1, -1, -1, 0
-    };
-    return kStats;
+  int GetAgcStatus(bool& enabled, AgcModes& mode) override { return -1; }
+  int SetAgcConfig(AgcConfig config) override { return -1; }
+  int GetAgcConfig(AgcConfig& config) override { return -1; }
+  int SetEcStatus(bool enable, EcModes mode = kEcUnchanged) override {
+    return -1;
   }
-
-  const AudioDecodingCallStats& GetRecvAudioDecodingCallStats() const {
-    static AudioDecodingCallStats stats;
-    if (stats.calls_to_silence_generator == 0) {
-      stats.calls_to_silence_generator = 234;
-      stats.calls_to_neteq = 567;
-      stats.decoded_normal = 890;
-      stats.decoded_plc = 123;
-      stats.decoded_cng = 456;
-      stats.decoded_plc_cng = 789;
-    }
-    return stats;
+  int GetEcStatus(bool& enabled, EcModes& mode) override { return -1; }
+  int EnableDriftCompensation(bool enable) override { return -1; }
+  bool DriftCompensationEnabled() override { return false; }
+  void SetDelayOffsetMs(int offset) override {}
+  int DelayOffsetMs() override { return -1; }
+  int SetAecmMode(AecmModes mode = kAecmSpeakerphone,
+                  bool enableCNG = true) override { return -1; }
+  int GetAecmMode(AecmModes& mode, bool& enabledCNG) override { return -1; }
+  int EnableHighPassFilter(bool enable) override { return -1; }
+  bool IsHighPassFilterEnabled() override { return false; }
+  int SetRxNsStatus(int channel,
+                    bool enable,
+                    NsModes mode = kNsUnchanged) override { return -1; }
+  int GetRxNsStatus(int channel, bool& enabled, NsModes& mode) override {
+    return -1;
   }
+  int SetRxAgcStatus(int channel,
+                     bool enable,
+                     AgcModes mode = kAgcUnchanged) override { return -1; }
+  int GetRxAgcStatus(int channel, bool& enabled, AgcModes& mode) override {
+    return -1;
+  }
+  int SetRxAgcConfig(int channel, AgcConfig config) override { return -1; }
+  int GetRxAgcConfig(int channel, AgcConfig& config) override { return -1; }
+  int RegisterRxVadObserver(int channel,
+                            VoERxVadCallback& observer) override { return -1; }
+  int DeRegisterRxVadObserver(int channel) override { return -1; }
+  int VoiceActivityIndicator(int channel) override { return -1; }
+  int SetEcMetricsStatus(bool enable) override { return -1; }
+  int GetEcMetricsStatus(bool& enabled) override {
+    enabled = true;
+    return 0;
+  }
+  int GetEchoMetrics(int& ERL, int& ERLE, int& RERL, int& A_NLP) override {
+    ERL = kSendEchoReturnLoss;
+    ERLE = kSendEchoReturnLossEnhancement;
+    RERL = -123456789;
+    A_NLP = 123456789;
+    return 0;
+  }
+  int GetEcDelayMetrics(int& delay_median,
+                        int& delay_std,
+                        float& fraction_poor_delays) override {
+    delay_median = kSendEchoDelayMedian;
+    delay_std = kSendEchoDelayStdDev;
+    fraction_poor_delays = -12345.7890f;
+    return 0;
+  }
+  int StartDebugRecording(const char* fileNameUTF8) override { return -1; }
+  int StartDebugRecording(FILE* file_handle) override { return -1; }
+  int StopDebugRecording() override { return -1; }
+  int SetTypingDetectionStatus(bool enable) override { return -1; }
+  int GetTypingDetectionStatus(bool& enabled) override { return -1; }
+  int TimeSinceLastTyping(int& seconds) override { return -1; }
+  int SetTypingDetectionParameters(int timeWindow,
+                                   int costPerTyping,
+                                   int reportingThreshold,
+                                   int penaltyDecay,
+                                   int typeEventDelay = 0) override {
+    return -1;
+  }
+  void EnableStereoChannelSwapping(bool enable) override {}
+  bool IsStereoChannelSwappingEnabled() override { return false; }
 
   // VoEBase
   int RegisterVoiceEngineObserver(VoiceEngineObserver& observer) override {
@@ -105,11 +162,15 @@
   int NumOfCodecs() override { return -1; }
   int GetCodec(int index, CodecInst& codec) override { return -1; }
   int SetSendCodec(int channel, const CodecInst& codec) override { return -1; }
-  int GetSendCodec(int channel, CodecInst& codec) override { return -1; }
+  int GetSendCodec(int channel, CodecInst& codec) override {
+    EXPECT_EQ(channel, kSendChannelId);
+    codec = kSendCodecInst;
+    return 0;
+  }
   int SetBitRate(int channel, int bitrate_bps) override { return -1; }
   int GetRecCodec(int channel, CodecInst& codec) override {
-    EXPECT_EQ(channel, kReceiveChannelId);
-    codec = GetRecvRecCodecInst();
+    EXPECT_EQ(channel, kRecvChannelId);
+    codec = kRecvCodecInst;
     return 0;
   }
   int SetRecPayloadType(int channel, const CodecInst& codec) override {
@@ -295,23 +356,27 @@
 
   // VoENetEqStats
   int GetNetworkStatistics(int channel, NetworkStatistics& stats) override {
-    EXPECT_EQ(channel, kReceiveChannelId);
-    stats = GetRecvNetworkStats();
+    EXPECT_EQ(channel, kRecvChannelId);
+    stats = kRecvNetworkStats;
     return 0;
   }
   int GetDecodingCallStatistics(int channel,
                                 AudioDecodingCallStats* stats) const override {
-    EXPECT_EQ(channel, kReceiveChannelId);
+    EXPECT_EQ(channel, kRecvChannelId);
     EXPECT_NE(nullptr, stats);
-    *stats = GetRecvAudioDecodingCallStats();
+    *stats = kRecvAudioDecodingCallStats;
     return 0;
   }
 
   // VoERTP_RTCP
   int SetLocalSSRC(int channel, unsigned int ssrc) override { return -1; }
-  int GetLocalSSRC(int channel, unsigned int& ssrc) override { return -1; }
+  int GetLocalSSRC(int channel, unsigned int& ssrc) override {
+    EXPECT_EQ(channel, kSendChannelId);
+    ssrc = 0;
+    return 0;
+  }
   int GetRemoteSSRC(int channel, unsigned int& ssrc) override {
-    EXPECT_EQ(channel, kReceiveChannelId);
+    EXPECT_EQ(channel, kRecvChannelId);
     ssrc = 0;
     return 0;
   }
@@ -347,13 +412,28 @@
                        unsigned int& maxJitterMs,
                        unsigned int& discardedPackets) override { return -1; }
   int GetRTCPStatistics(int channel, CallStatistics& stats) override {
-    EXPECT_EQ(channel, kReceiveChannelId);
-    stats = GetRecvCallStats();
+    if (channel == kSendChannelId) {
+      stats = kSendCallStats;
+    } else {
+      EXPECT_EQ(channel, kRecvChannelId);
+      stats = kRecvCallStats;
+    }
     return 0;
   }
   int GetRemoteRTCPReportBlocks(
       int channel,
-      std::vector<ReportBlock>* receive_blocks) override { return -1; }
+      std::vector<ReportBlock>* receive_blocks) override {
+    EXPECT_EQ(channel, kSendChannelId);
+    EXPECT_NE(receive_blocks, nullptr);
+    EXPECT_EQ(receive_blocks->size(), 0u);
+    webrtc::ReportBlock block = kSendReportBlock;
+    receive_blocks->push_back(block);   // Has wrong SSRC.
+    block.source_SSRC = kSendSsrc;
+    receive_blocks->push_back(block);   // Correct block.
+    block.fraction_lost = 0;
+    receive_blocks->push_back(block);   // Duplicate SSRC, bad fraction_lost.
+    return 0;
+  }
   int SetNACKStatus(int channel, bool enable, int maxNoPackets) override {
     return -1;
   }
@@ -365,7 +445,7 @@
   int GetDelayEstimate(int channel,
                        int* jitter_buffer_delay_ms,
                        int* playout_buffer_delay_ms) override {
-    EXPECT_EQ(channel, kReceiveChannelId);
+    EXPECT_EQ(channel, kRecvChannelId);
     *jitter_buffer_delay_ms = kRecvJitterBufferDelay;
     *playout_buffer_delay_ms = kRecvPlayoutBufferDelay;
     return 0;
@@ -395,10 +475,13 @@
   int GetSpeechOutputLevel(int channel, unsigned int& level) override {
     return -1;
   }
-  int GetSpeechInputLevelFullRange(unsigned int& level) override { return -1; }
+  int GetSpeechInputLevelFullRange(unsigned int& level) override {
+    level = kSendSpeechInputLevel;
+    return 0;
+  }
   int GetSpeechOutputLevelFullRange(int channel,
                                     unsigned int& level) override {
-    EXPECT_EQ(channel, kReceiveChannelId);
+    EXPECT_EQ(channel, kRecvChannelId);
     level = kRecvSpeechOutputLevel;
     return 0;
   }
diff --git a/webrtc/test/webrtc_test_common.gyp b/webrtc/test/webrtc_test_common.gyp
index 5076900..42fa1e7 100644
--- a/webrtc/test/webrtc_test_common.gyp
+++ b/webrtc/test/webrtc_test_common.gyp
@@ -30,6 +30,7 @@
         'fake_encoder.h',
         'fake_network_pipe.cc',
         'fake_network_pipe.h',
+        'fake_voice_engine.cc',
         'fake_voice_engine.h',
         'frame_generator_capturer.cc',
         'frame_generator_capturer.h',