Implement AudioSendStream::GetStats().

BUG=webrtc:4690

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

Cr-Commit-Position: refs/heads/master@{#10424}
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',