Update talk to 61538839.

TBR=mallinath

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5548 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/mediastreamhandler.cc b/talk/app/webrtc/mediastreamhandler.cc
index a94eef3..ca8e105 100644
--- a/talk/app/webrtc/mediastreamhandler.cc
+++ b/talk/app/webrtc/mediastreamhandler.cc
@@ -106,6 +106,8 @@
 void LocalAudioTrackHandler::OnEnabledChanged() {
   cricket::AudioOptions options;
   if (audio_track_->enabled() && audio_track_->GetSource()) {
+    // TODO(xians): Remove this static_cast since we should be able to connect
+    // a remote audio track to peer connection.
     options = static_cast<LocalAudioSource*>(
         audio_track_->GetSource())->options();
   }
@@ -125,10 +127,12 @@
     : TrackHandler(track, ssrc),
       audio_track_(track),
       provider_(provider) {
+  track->GetSource()->RegisterAudioObserver(this);
   OnEnabledChanged();
 }
 
 RemoteAudioTrackHandler::~RemoteAudioTrackHandler() {
+  audio_track_->GetSource()->UnregisterAudioObserver(this);
 }
 
 void RemoteAudioTrackHandler::Stop() {
@@ -143,6 +147,14 @@
                              audio_track_->GetRenderer());
 }
 
+void RemoteAudioTrackHandler::OnSetVolume(double volume) {
+  // When the track is disabled, the volume of the source, which is the
+  // corresponding WebRtc Voice Engine channel will be 0. So we do not allow
+  // setting the volume to the source when the track is disabled.
+  if (audio_track_->enabled())
+    provider_->SetAudioPlayoutVolume(ssrc(), volume);
+}
+
 LocalVideoTrackHandler::LocalVideoTrackHandler(
     VideoTrackInterface* track,
     uint32 ssrc,
diff --git a/talk/app/webrtc/mediastreamhandler.h b/talk/app/webrtc/mediastreamhandler.h
index 625de85..53afd55 100644
--- a/talk/app/webrtc/mediastreamhandler.h
+++ b/talk/app/webrtc/mediastreamhandler.h
@@ -118,7 +118,8 @@
 // RemoteAudioTrackHandler listen to events on a remote AudioTrack instance
 // connected to a PeerConnection and orders the |provider| to executes the
 // requested change.
-class RemoteAudioTrackHandler : public TrackHandler {
+class RemoteAudioTrackHandler : public AudioSourceInterface::AudioObserver,
+                                public TrackHandler {
  public:
   RemoteAudioTrackHandler(AudioTrackInterface* track,
                           uint32 ssrc,
@@ -131,6 +132,9 @@
   virtual void OnEnabledChanged() OVERRIDE;
 
  private:
+  // AudioSourceInterface::AudioObserver implementation.
+  virtual void OnSetVolume(double volume) OVERRIDE;
+
   AudioTrackInterface* audio_track_;
   AudioProviderInterface* provider_;
 };
diff --git a/talk/app/webrtc/mediastreamhandler_unittest.cc b/talk/app/webrtc/mediastreamhandler_unittest.cc
index 475258e..6eedb7e 100644
--- a/talk/app/webrtc/mediastreamhandler_unittest.cc
+++ b/talk/app/webrtc/mediastreamhandler_unittest.cc
@@ -31,6 +31,7 @@
 
 #include "talk/app/webrtc/audiotrack.h"
 #include "talk/app/webrtc/mediastream.h"
+#include "talk/app/webrtc/remoteaudiosource.h"
 #include "talk/app/webrtc/streamcollection.h"
 #include "talk/app/webrtc/videosource.h"
 #include "talk/app/webrtc/videotrack.h"
@@ -59,6 +60,7 @@
   MOCK_METHOD4(SetAudioSend, void(uint32 ssrc, bool enable,
                                   const cricket::AudioOptions& options,
                                   cricket::AudioRenderer* renderer));
+  MOCK_METHOD2(SetAudioPlayoutVolume, void(uint32 ssrc, double volume));
 };
 
 // Helper class to test MediaStreamHandler.
@@ -110,12 +112,11 @@
         FakeVideoSource::Create());
     video_track_ = VideoTrack::Create(kVideoTrackId, source);
     EXPECT_TRUE(stream_->AddTrack(video_track_));
-    audio_track_ = AudioTrack::Create(kAudioTrackId,
-                                           NULL);
-    EXPECT_TRUE(stream_->AddTrack(audio_track_));
   }
 
   void AddLocalAudioTrack() {
+    audio_track_ = AudioTrack::Create(kAudioTrackId, NULL);
+    EXPECT_TRUE(stream_->AddTrack(audio_track_));
     EXPECT_CALL(audio_provider_, SetAudioSend(kAudioSsrc, true, _, _));
     handlers_.AddLocalAudioTrack(stream_, stream_->GetAudioTracks()[0],
                                  kAudioSsrc);
@@ -144,6 +145,9 @@
   }
 
   void AddRemoteAudioTrack() {
+    audio_track_ = AudioTrack::Create(kAudioTrackId,
+                                      RemoteAudioSource::Create().get());
+    EXPECT_TRUE(stream_->AddTrack(audio_track_));
     EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, true, _));
     handlers_.AddRemoteAudioTrack(stream_, stream_->GetAudioTracks()[0],
                                   kAudioSsrc);
@@ -292,4 +296,27 @@
   handlers_.TearDown();
 }
 
+TEST_F(MediaStreamHandlerTest, RemoteAudioTrackSetVolume) {
+  AddRemoteAudioTrack();
+
+  double volume = 0.5;
+  EXPECT_CALL(audio_provider_, SetAudioPlayoutVolume(kAudioSsrc, volume));
+  audio_track_->GetSource()->SetVolume(volume);
+
+  // Disable the audio track, this should prevent setting the volume.
+  EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, false, _));
+  audio_track_->set_enabled(false);
+  audio_track_->GetSource()->SetVolume(1.0);
+
+  EXPECT_CALL(audio_provider_, SetAudioPlayout(kAudioSsrc, true, _));
+  audio_track_->set_enabled(true);
+
+  double new_volume = 0.8;
+  EXPECT_CALL(audio_provider_, SetAudioPlayoutVolume(kAudioSsrc, new_volume));
+  audio_track_->GetSource()->SetVolume(new_volume);
+
+  RemoveRemoteAudioTrack();
+  handlers_.TearDown();
+}
+
 }  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreaminterface.h b/talk/app/webrtc/mediastreaminterface.h
index 96d0942..fa0572e 100644
--- a/talk/app/webrtc/mediastreaminterface.h
+++ b/talk/app/webrtc/mediastreaminterface.h
@@ -142,9 +142,24 @@
 
 // AudioSourceInterface is a reference counted source used for AudioTracks.
 // The same source can be used in multiple AudioTracks.
-// TODO(perkj): Extend this class with necessary methods to allow separate
-// sources for each audio track.
 class AudioSourceInterface : public MediaSourceInterface {
+ public:
+  class AudioObserver {
+   public:
+    virtual void OnSetVolume(double volume) = 0;
+
+   protected:
+    virtual ~AudioObserver() {}
+  };
+
+  // TODO(xians): Makes all the interface pure virtual after Chrome has their
+  // implementations.
+  // Sets the volume to the source. |volume| is in  the range of [0, 10].
+  virtual void SetVolume(double volume) {}
+
+  // Registers/unregisters observer to the audio source.
+  virtual void RegisterAudioObserver(AudioObserver* observer) {}
+  virtual void UnregisterAudioObserver(AudioObserver* observer) {}
 };
 
 // Interface for receiving audio data from a AudioTrack.
diff --git a/talk/app/webrtc/mediastreamprovider.h b/talk/app/webrtc/mediastreamprovider.h
index ae00b1d..5cf0e27 100644
--- a/talk/app/webrtc/mediastreamprovider.h
+++ b/talk/app/webrtc/mediastreamprovider.h
@@ -53,6 +53,10 @@
                             const cricket::AudioOptions& options,
                             cricket::AudioRenderer* renderer) = 0;
 
+  // Sets the audio playout volume of a remote audio track with |ssrc|.
+  // |volume| is in the range of [0, 10].
+  virtual void SetAudioPlayoutVolume(uint32 ssrc, double volume) = 0;
+
  protected:
   virtual ~AudioProviderInterface() {}
 };
diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc
index 610b3f8..14648ee 100644
--- a/talk/app/webrtc/mediastreamsignaling.cc
+++ b/talk/app/webrtc/mediastreamsignaling.cc
@@ -33,6 +33,7 @@
 #include "talk/app/webrtc/mediastreamproxy.h"
 #include "talk/app/webrtc/mediaconstraintsinterface.h"
 #include "talk/app/webrtc/mediastreamtrackproxy.h"
+#include "talk/app/webrtc/remoteaudiosource.h"
 #include "talk/app/webrtc/remotevideocapturer.h"
 #include "talk/app/webrtc/sctputils.h"
 #include "talk/app/webrtc/videosource.h"
@@ -140,7 +141,7 @@
   AudioTrackInterface* AddAudioTrack(webrtc::MediaStreamInterface* stream,
                                      const std::string& track_id) {
     return AddTrack<AudioTrackInterface, AudioTrack, AudioTrackProxy>(
-        stream, track_id, static_cast<AudioSourceInterface*>(NULL));
+        stream, track_id, RemoteAudioSource::Create().get());
   }
 
   VideoTrackInterface* AddVideoTrack(webrtc::MediaStreamInterface* stream,
diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc
index 40640cf..b404ec4 100644
--- a/talk/app/webrtc/peerconnection.cc
+++ b/talk/app/webrtc/peerconnection.cc
@@ -459,13 +459,19 @@
 }
 
 bool PeerConnection::GetStats(StatsObserver* observer,
-                              MediaStreamTrackInterface* track) {
+                              webrtc::MediaStreamTrackInterface* track) {
+  return GetStats(observer, track, kStatsOutputLevelStandard);
+}
+
+bool PeerConnection::GetStats(StatsObserver* observer,
+                              MediaStreamTrackInterface* track,
+                              StatsOutputLevel level) {
   if (!VERIFY(observer != NULL)) {
     LOG(LS_ERROR) << "GetStats - observer is NULL.";
     return false;
   }
 
-  stats_.UpdateStats();
+  stats_.UpdateStats(level);
   talk_base::scoped_ptr<GetStatsMsg> msg(new GetStatsMsg(observer));
   if (!stats_.GetStats(track, &(msg->reports))) {
     return false;
@@ -542,7 +548,7 @@
   }
   // Update stats here so that we have the most recent stats for tracks and
   // streams that might be removed by updating the session description.
-  stats_.UpdateStats();
+  stats_.UpdateStats(kStatsOutputLevelStandard);
   std::string error;
   if (!session_->SetLocalDescription(desc, &error)) {
     PostSetSessionDescriptionFailure(observer, error);
@@ -565,7 +571,7 @@
   }
   // Update stats here so that we have the most recent stats for tracks and
   // streams that might be removed by updating the session description.
-  stats_.UpdateStats();
+  stats_.UpdateStats(kStatsOutputLevelStandard);
   std::string error;
   if (!session_->SetRemoteDescription(desc, &error)) {
     PostSetSessionDescriptionFailure(observer, error);
@@ -606,7 +612,7 @@
 void PeerConnection::Close() {
   // Update stats here so that we have the most recent stats for tracks and
   // streams before the channels are closed.
-  stats_.UpdateStats();
+  stats_.UpdateStats(kStatsOutputLevelStandard);
 
   session_->Terminate();
 }
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
index 9cc9f38..70155d9 100644
--- a/talk/app/webrtc/peerconnection.h
+++ b/talk/app/webrtc/peerconnection.h
@@ -76,6 +76,9 @@
       const DataChannelInit* config);
   virtual bool GetStats(StatsObserver* observer,
                         webrtc::MediaStreamTrackInterface* track);
+  virtual bool GetStats(StatsObserver* observer,
+                        webrtc::MediaStreamTrackInterface* track,
+                        StatsOutputLevel level);
 
   virtual SignalingState signaling_state();
 
diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h
index 667774e2..2f44885 100644
--- a/talk/app/webrtc/peerconnectioninterface.h
+++ b/talk/app/webrtc/peerconnectioninterface.h
@@ -166,6 +166,15 @@
   };
   typedef std::vector<IceServer> IceServers;
 
+  // Used by GetStats to decide which stats to include in the stats reports.
+  // |kStatsOutputLevelStandard| includes the standard stats for Javascript API;
+  // |kStatsOutputLevelDebug| includes both the standard stats and additional
+  // stats for debugging purposes.
+  enum StatsOutputLevel {
+    kStatsOutputLevelStandard,
+    kStatsOutputLevelDebug,
+  };
+
   // Accessor methods to active local streams.
   virtual talk_base::scoped_refptr<StreamCollectionInterface>
       local_streams() = 0;
@@ -190,9 +199,14 @@
   virtual talk_base::scoped_refptr<DtmfSenderInterface> CreateDtmfSender(
       AudioTrackInterface* track) = 0;
 
+  // TODO(jiayl): remove the old API once all Chrome overrides are updated.
   virtual bool GetStats(StatsObserver* observer,
                         MediaStreamTrackInterface* track) = 0;
 
+  virtual bool GetStats(StatsObserver* observer,
+                        MediaStreamTrackInterface* track,
+                        StatsOutputLevel level) = 0;
+
   virtual talk_base::scoped_refptr<DataChannelInterface> CreateDataChannel(
       const std::string& label,
       const DataChannelInit* config) = 0;
diff --git a/talk/app/webrtc/peerconnectionproxy.h b/talk/app/webrtc/peerconnectionproxy.h
index f07416d..57bee51 100644
--- a/talk/app/webrtc/peerconnectionproxy.h
+++ b/talk/app/webrtc/peerconnectionproxy.h
@@ -45,6 +45,9 @@
   PROXY_METHOD1(talk_base::scoped_refptr<DtmfSenderInterface>,
                 CreateDtmfSender, AudioTrackInterface*)
   PROXY_METHOD2(bool, GetStats, StatsObserver*, MediaStreamTrackInterface*)
+  PROXY_METHOD3(bool, GetStats, StatsObserver*,
+                MediaStreamTrackInterface*,
+                StatsOutputLevel)
   PROXY_METHOD2(talk_base::scoped_refptr<DataChannelInterface>,
                 CreateDataChannel, const std::string&, const DataChannelInit*)
   PROXY_CONSTMETHOD0(const SessionDescriptionInterface*, local_description)
diff --git a/talk/app/webrtc/remoteaudiosource.cc b/talk/app/webrtc/remoteaudiosource.cc
new file mode 100644
index 0000000..1c275c7
--- /dev/null
+++ b/talk/app/webrtc/remoteaudiosource.cc
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2014, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/app/webrtc/remoteaudiosource.h"
+
+#include <algorithm>
+#include <functional>
+
+#include "talk/base/logging.h"
+
+namespace webrtc {
+
+talk_base::scoped_refptr<RemoteAudioSource> RemoteAudioSource::Create() {
+  return new talk_base::RefCountedObject<RemoteAudioSource>();
+}
+
+RemoteAudioSource::RemoteAudioSource() {
+}
+
+RemoteAudioSource::~RemoteAudioSource() {
+  ASSERT(audio_observers_.empty());
+}
+
+MediaSourceInterface::SourceState RemoteAudioSource::state() const {
+  return MediaSourceInterface::kLive;
+}
+
+void RemoteAudioSource::SetVolume(double volume) {
+  ASSERT(volume >= 0 && volume <= 10);
+  for (AudioObserverList::iterator it = audio_observers_.begin();
+       it != audio_observers_.end(); ++it) {
+    (*it)->OnSetVolume(volume);
+  }
+}
+
+void RemoteAudioSource::RegisterAudioObserver(AudioObserver* observer) {
+  ASSERT(observer != NULL);
+  ASSERT(std::find(audio_observers_.begin(), audio_observers_.end(),
+                   observer) == audio_observers_.end());
+  audio_observers_.push_back(observer);
+}
+
+void RemoteAudioSource::UnregisterAudioObserver(AudioObserver* observer) {
+  ASSERT(observer != NULL);
+  audio_observers_.remove(observer);
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/remoteaudiosource.h b/talk/app/webrtc/remoteaudiosource.h
new file mode 100644
index 0000000..ed24214
--- /dev/null
+++ b/talk/app/webrtc/remoteaudiosource.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2014, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_APP_WEBRTC_REMOTEAUDIOSOURCE_H_
+#define TALK_APP_WEBRTC_REMOTEAUDIOSOURCE_H_
+
+#include <list>
+
+#include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/notifier.h"
+
+namespace webrtc {
+
+using webrtc::AudioSourceInterface;
+
+// This class implements the audio source used by the remote audio track.
+class RemoteAudioSource : public Notifier<AudioSourceInterface> {
+ public:
+  // Creates an instance of RemoteAudioSource.
+  static talk_base::scoped_refptr<RemoteAudioSource> Create();
+
+ protected:
+  RemoteAudioSource();
+  virtual ~RemoteAudioSource();
+
+ private:
+  typedef std::list<AudioObserver*> AudioObserverList;
+
+  // MediaSourceInterface implementation.
+  virtual MediaSourceInterface::SourceState state() const OVERRIDE;
+
+  // AudioSourceInterface implementation.
+  virtual void SetVolume(double volume) OVERRIDE;
+  virtual void RegisterAudioObserver(AudioObserver* observer) OVERRIDE;
+  virtual void UnregisterAudioObserver(AudioObserver* observer) OVERRIDE;
+
+  AudioObserverList audio_observers_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_REMOTEAUDIOSOURCE_H_
diff --git a/talk/app/webrtc/statscollector.cc b/talk/app/webrtc/statscollector.cc
index 2efc11b..a900bba 100644
--- a/talk/app/webrtc/statscollector.cc
+++ b/talk/app/webrtc/statscollector.cc
@@ -78,6 +78,7 @@
 
 const char StatsReport::kStatsValueNameEncodeUsagePercent[] =
     "googEncodeUsagePercent";
+const char StatsReport::kStatsValueNameExpandRate[] = "googExpandRate";
 const char StatsReport::kStatsValueNameFingerprint[] = "googFingerprint";
 const char StatsReport::kStatsValueNameFingerprintAlgorithm[] =
     "googFingerprintAlgorithm";
@@ -121,12 +122,17 @@
     "googLocalCertificateId";
 const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived";
 const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent";
-const char StatsReport::kStatsValueNameNetEqExpandRate[] =
-    "googNetEqExpandRate";
 const char StatsReport::kStatsValueNamePacketsReceived[] = "packetsReceived";
 const char StatsReport::kStatsValueNamePacketsSent[] = "packetsSent";
 const char StatsReport::kStatsValueNamePacketsLost[] = "packetsLost";
 const char StatsReport::kStatsValueNameReadable[] = "googReadable";
+const char StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug[] =
+    "googReceivedPacketGroupArrivalTimeDebug";
+const char StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug[] =
+    "googReceivedPacketGroupPropagationDeltaDebug";
+const char
+StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug[] =
+    "googReceivedPacketGroupPropagationDeltaSumDebug";
 const char StatsReport::kStatsValueNameRemoteAddress[] = "googRemoteAddress";
 const char StatsReport::kStatsValueNameRemoteCandidateType[] =
     "googRemoteCandidateType";
@@ -175,6 +181,20 @@
   AddValue(name, talk_base::ToString<int64>(value));
 }
 
+template <typename T>
+void StatsReport::AddValue(const std::string& name,
+                           const std::vector<T>& value) {
+  std::ostringstream oss;
+  oss << "[";
+  for (size_t i = 0; i < value.size(); ++i) {
+    oss << talk_base::ToString<T>(value[i]);
+    if (i != value.size() - 1)
+      oss << ", ";
+  }
+  oss << "]";
+  AddValue(name, oss.str());
+}
+
 void StatsReport::AddBoolean(const std::string& name, bool value) {
   AddValue(name, value ? "true" : "false");
 }
@@ -221,7 +241,7 @@
                    info.bytes_rcvd);
   report->AddValue(StatsReport::kStatsValueNameJitterReceived,
                    info.jitter_ms);
-  report->AddValue(StatsReport::kStatsValueNameNetEqExpandRate,
+  report->AddValue(StatsReport::kStatsValueNameExpandRate,
                    talk_base::ToString<float>(info.expand_rate));
   report->AddValue(StatsReport::kStatsValueNamePacketsReceived,
                    info.packets_rcvd);
@@ -334,6 +354,7 @@
 
 void ExtractStats(const cricket::BandwidthEstimationInfo& info,
                   double stats_gathering_started,
+                  PeerConnectionInterface::StatsOutputLevel level,
                   StatsReport* report) {
   report->id = StatsReport::kStatsReportVideoBweId;
   report->type = StatsReport::kStatsReportTypeBwe;
@@ -358,6 +379,19 @@
                    info.transmit_bitrate);
   report->AddValue(StatsReport::kStatsValueNameBucketDelay,
                    info.bucket_delay);
+  if (level >= PeerConnectionInterface::kStatsOutputLevelDebug) {
+    report->AddValue(
+        StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug,
+        info.total_received_propagation_delta_ms);
+    if (info.recent_received_propagation_delta_ms.size() > 0) {
+      report->AddValue(
+          StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug,
+          info.recent_received_propagation_delta_ms);
+      report->AddValue(
+          StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug,
+          info.recent_received_packet_group_arrival_time_ms);
+    }
+  }
 }
 
 void ExtractRemoteStats(const cricket::MediaSenderInfo& info,
@@ -399,7 +433,7 @@
       ExtractRemoteStats(*it, report);
     }
   }
-};
+}
 
 }  // namespace
 
@@ -463,7 +497,8 @@
   return true;
 }
 
-void StatsCollector::UpdateStats() {
+void
+StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) {
   double time_now = GetTimeNow();
   // Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of
   // ms apart will be ignored.
@@ -476,7 +511,7 @@
   if (session_) {
     ExtractSessionInfo();
     ExtractVoiceInfo();
-    ExtractVideoInfo();
+    ExtractVideoInfo(level);
   }
 }
 
@@ -569,6 +604,14 @@
 
   talk_base::scoped_ptr<talk_base::SSLFingerprint> ssl_fingerprint(
       talk_base::SSLFingerprint::Create(digest_algorithm, cert));
+
+  // SSLFingerprint::Create can fail if the algorithm returned by
+  // SSLCertificate::GetSignatureDigestAlgorithm is not supported by the
+  // implementation of SSLCertificate::ComputeDigest.  This currently happens
+  // with MD5- and SHA-224-signed certificates when linked to libNSS.
+  if (!ssl_fingerprint)
+    return std::string();
+
   std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint();
 
   talk_base::Buffer der_buffer;
@@ -737,12 +780,17 @@
   ExtractStatsFromList(voice_info.senders, transport_id, this);
 }
 
-void StatsCollector::ExtractVideoInfo() {
+void StatsCollector::ExtractVideoInfo(
+    PeerConnectionInterface::StatsOutputLevel level) {
   if (!session_->video_channel()) {
     return;
   }
+  cricket::StatsOptions options;
+  options.include_received_propagation_stats =
+      (level >= PeerConnectionInterface::kStatsOutputLevelDebug) ?
+          true : false;
   cricket::VideoMediaInfo video_info;
-  if (!session_->video_channel()->GetStats(&video_info)) {
+  if (!session_->video_channel()->GetStats(options, &video_info)) {
     LOG(LS_ERROR) << "Failed to get video channel stats.";
     return;
   }
@@ -760,7 +808,7 @@
   } else {
     StatsReport* report = &reports_[StatsReport::kStatsReportVideoBweId];
     ExtractStats(
-        video_info.bw_estimations[0], stats_gathering_started_, report);
+        video_info.bw_estimations[0], stats_gathering_started_, level, report);
   }
 }
 
diff --git a/talk/app/webrtc/statscollector.h b/talk/app/webrtc/statscollector.h
index 01da059..6256d77 100644
--- a/talk/app/webrtc/statscollector.h
+++ b/talk/app/webrtc/statscollector.h
@@ -35,6 +35,7 @@
 #include <map>
 
 #include "talk/app/webrtc/mediastreaminterface.h"
+#include "talk/app/webrtc/peerconnectioninterface.h"
 #include "talk/app/webrtc/statstypes.h"
 #include "talk/app/webrtc/webrtcsession.h"
 
@@ -57,13 +58,14 @@
   void AddStream(MediaStreamInterface* stream);
 
   // Gather statistics from the session and store them for future use.
-  void UpdateStats();
+  void UpdateStats(PeerConnectionInterface::StatsOutputLevel level);
 
   // Gets a StatsReports of the last collected stats. Note that UpdateStats must
   // be called before this function to get the most recent stats. |selector| is
   // a track label or empty string. The most recent reports are stored in
   // |reports|.
-  bool GetStats(MediaStreamTrackInterface* track, StatsReports* reports);
+  bool GetStats(MediaStreamTrackInterface* track,
+                StatsReports* reports);
 
   // Prepare an SSRC report for the given ssrc. Used internally
   // in the ExtractStatsFromList template.
@@ -87,7 +89,7 @@
 
   void ExtractSessionInfo();
   void ExtractVoiceInfo();
-  void ExtractVideoInfo();
+  void ExtractVideoInfo(PeerConnectionInterface::StatsOutputLevel level);
   double GetTimeNow();
   void BuildSsrcToTransportId();
   WebRtcSession* session() { return session_; }
diff --git a/talk/app/webrtc/statscollector_unittest.cc b/talk/app/webrtc/statscollector_unittest.cc
index 1adcb0e..a7cda16 100644
--- a/talk/app/webrtc/statscollector_unittest.cc
+++ b/talk/app/webrtc/statscollector_unittest.cc
@@ -39,11 +39,14 @@
 #include "talk/session/media/channelmanager.h"
 #include "testing/base/public/gmock.h"
 
+using cricket::StatsOptions;
 using testing::_;
 using testing::DoAll;
+using testing::Field;
 using testing::Return;
 using testing::ReturnNull;
 using testing::SetArgPointee;
+using webrtc::PeerConnectionInterface;
 
 namespace cricket {
 
@@ -80,7 +83,7 @@
     : cricket::FakeVideoMediaChannel(NULL) {
   }
   // MOCK_METHOD0(transport_channel, cricket::TransportChannel*());
-  MOCK_METHOD1(GetStats, bool(cricket::VideoMediaInfo*));
+  MOCK_METHOD2(GetStats, bool(const StatsOptions&, cricket::VideoMediaInfo*));
 };
 
 bool GetValue(const webrtc::StatsReport* report,
@@ -289,7 +292,7 @@
     EXPECT_CALL(session_, video_channel())
       .WillRepeatedly(ReturnNull());
 
-    stats.UpdateStats();
+    stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
 
     stats.GetStats(NULL, &reports);
 
@@ -302,16 +305,24 @@
         webrtc::StatsReport::kStatsReportTypeComponent,
         reports,
         webrtc::StatsReport::kStatsValueNameLocalCertificateId);
-    EXPECT_NE(kNotFound, local_certificate_id);
-    CheckCertChainReports(reports, local_ders, local_certificate_id);
+    if (local_ders.size() > 0) {
+      EXPECT_NE(kNotFound, local_certificate_id);
+      CheckCertChainReports(reports, local_ders, local_certificate_id);
+    } else {
+      EXPECT_EQ(kNotFound, local_certificate_id);
+    }
 
     // Check remote certificate chain.
     std::string remote_certificate_id = ExtractStatsValue(
         webrtc::StatsReport::kStatsReportTypeComponent,
         reports,
         webrtc::StatsReport::kStatsValueNameRemoteCertificateId);
-    EXPECT_NE(kNotFound, remote_certificate_id);
-    CheckCertChainReports(reports, remote_ders, remote_certificate_id);
+    if (remote_ders.size() > 0) {
+      EXPECT_NE(kNotFound, remote_certificate_id);
+      CheckCertChainReports(reports, remote_ders, remote_certificate_id);
+    } else {
+      EXPECT_EQ(kNotFound, remote_certificate_id);
+    }
   }
 
   cricket::FakeMediaEngine* media_engine_;
@@ -347,10 +358,10 @@
 
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(Return(&video_channel));
-  EXPECT_CALL(*media_channel, GetStats(_))
-    .WillOnce(DoAll(SetArgPointee<0>(stats_read),
+  EXPECT_CALL(*media_channel, GetStats(_, _))
+    .WillOnce(DoAll(SetArgPointee<1>(stats_read),
                     Return(true)));
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
   std::string result = ExtractSsrcStatsValue(reports, "bytesSent");
   EXPECT_EQ(kBytesSentString, result);
@@ -386,11 +397,11 @@
 
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(Return(&video_channel));
-  EXPECT_CALL(*media_channel, GetStats(_))
-    .WillOnce(DoAll(SetArgPointee<0>(stats_read),
+  EXPECT_CALL(*media_channel, GetStats(_, _))
+    .WillOnce(DoAll(SetArgPointee<1>(stats_read),
                     Return(true)));
 
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
   std::string result = ExtractSsrcStatsValue(reports, "bytesSent");
   EXPECT_EQ(kBytesSentString, result);
@@ -406,7 +417,7 @@
   stats.set_session(&session_);
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(ReturnNull());
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
   const webrtc::StatsReport* session_report = FindNthReportByType(
       reports, webrtc::StatsReport::kStatsReportTypeSession, 1);
@@ -421,8 +432,8 @@
   stats.set_session(&session_);
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(ReturnNull());
-  stats.UpdateStats();
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
   const webrtc::StatsReport* session_report = FindNthReportByType(
       reports, webrtc::StatsReport::kStatsReportTypeSession, 1);
@@ -485,11 +496,11 @@
 
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(Return(&video_channel));
-  EXPECT_CALL(*media_channel, GetStats(_))
-    .WillOnce(DoAll(SetArgPointee<0>(stats_read),
+  EXPECT_CALL(*media_channel, GetStats(_, _))
+    .WillOnce(DoAll(SetArgPointee<1>(stats_read),
                     Return(true)));
 
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
   // |reports| should contain at least one session report, one track report,
   // and one ssrc report.
@@ -543,8 +554,8 @@
 
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(Return(&video_channel));
-  EXPECT_CALL(*media_channel, GetStats(_))
-    .WillRepeatedly(DoAll(SetArgPointee<0>(stats_read),
+  EXPECT_CALL(*media_channel, GetStats(_, _))
+    .WillRepeatedly(DoAll(SetArgPointee<1>(stats_read),
                           Return(true)));
 
   InitSessionStats(kVcName);
@@ -552,7 +563,7 @@
     .WillRepeatedly(DoAll(SetArgPointee<0>(session_stats_),
                           Return(true)));
 
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
   std::string transport_id = ExtractStatsValue(
       webrtc::StatsReport::kStatsReportTypeSsrc,
@@ -581,7 +592,7 @@
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(ReturnNull());
 
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   webrtc::StatsReports reports;
   stats.GetStats(NULL, &reports);
   const webrtc::StatsReport* remote_report = FindNthReportByType(reports,
@@ -624,11 +635,11 @@
 
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(Return(&video_channel));
-  EXPECT_CALL(*media_channel, GetStats(_))
-    .WillRepeatedly(DoAll(SetArgPointee<0>(stats_read),
+  EXPECT_CALL(*media_channel, GetStats(_, _))
+    .WillRepeatedly(DoAll(SetArgPointee<1>(stats_read),
                           Return(true)));
 
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
   const webrtc::StatsReport* remote_report = FindNthReportByType(reports,
       webrtc::StatsReport::kStatsReportTypeRemoteSsrc, 1);
@@ -703,7 +714,7 @@
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(ReturnNull());
 
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
 
   // Check that the local certificate is absent.
@@ -756,7 +767,7 @@
   EXPECT_CALL(session_, video_channel())
     .WillRepeatedly(ReturnNull());
 
-  stats.UpdateStats();
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
   stats.GetStats(NULL, &reports);
 
   // Check that the local certificate is absent.
@@ -774,4 +785,63 @@
   ASSERT_EQ(kNotFound, remote_certificate_id);
 }
 
+// This test verifies that a remote certificate with an unsupported digest
+// algorithm is correctly ignored.
+TEST_F(StatsCollectorTest, UnsupportedDigestIgnored) {
+  // Build a local certificate.
+  std::string local_der = "This is the local der.";
+  talk_base::FakeSSLCertificate local_cert(DerToPem(local_der));
+
+  // Build a remote certificate with an unsupported digest algorithm.
+  std::string remote_der = "This is somebody else's der.";
+  talk_base::FakeSSLCertificate remote_cert(DerToPem(remote_der));
+  remote_cert.set_digest_algorithm("foobar");
+
+  TestCertificateReports(local_cert, std::vector<std::string>(1, local_der),
+                         remote_cert, std::vector<std::string>());
+}
+
+// Verifies the correct optons are passed to the VideoMediaChannel when using
+// verbose output level.
+TEST_F(StatsCollectorTest, StatsOutputLevelVerbose) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  MockVideoMediaChannel* media_channel = new MockVideoMediaChannel;
+  cricket::VideoChannel video_channel(talk_base::Thread::Current(),
+      media_engine_, media_channel, &session_, "", false, NULL);
+  stats.set_session(&session_);
+
+  webrtc::StatsReports reports;  // returned values.
+  cricket::VideoMediaInfo stats_read;
+  cricket::BandwidthEstimationInfo bwe;
+  bwe.total_received_propagation_delta_ms = 10;
+  bwe.recent_received_propagation_delta_ms.push_back(100);
+  bwe.recent_received_propagation_delta_ms.push_back(200);
+  bwe.recent_received_packet_group_arrival_time_ms.push_back(1000);
+  bwe.recent_received_packet_group_arrival_time_ms.push_back(2000);
+  stats_read.bw_estimations.push_back(bwe);
+
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(Return(&video_channel));
+
+  StatsOptions options;
+  options.include_received_propagation_stats = true;
+  EXPECT_CALL(*media_channel, GetStats(
+      Field(&StatsOptions::include_received_propagation_stats, true),
+      _))
+    .WillOnce(DoAll(SetArgPointee<1>(stats_read),
+                    Return(true)));
+
+  stats.UpdateStats(PeerConnectionInterface::kStatsOutputLevelDebug);
+  stats.GetStats(NULL, &reports);
+  std::string result = ExtractBweStatsValue(
+      reports, "googReceivedPacketGroupPropagationDeltaSumDebug");
+  EXPECT_EQ("10", result);
+  result = ExtractBweStatsValue(
+      reports, "googReceivedPacketGroupPropagationDeltaDebug");
+  EXPECT_EQ("[100, 200]", result);
+  result = ExtractBweStatsValue(
+      reports, "googReceivedPacketGroupArrivalTimeDebug");
+  EXPECT_EQ("[1000, 2000]", result);
+}
+
 }  // namespace
diff --git a/talk/app/webrtc/statstypes.h b/talk/app/webrtc/statstypes.h
index 9110da3..39441e2 100644
--- a/talk/app/webrtc/statstypes.h
+++ b/talk/app/webrtc/statstypes.h
@@ -53,6 +53,8 @@
 
   void AddValue(const std::string& name, const std::string& value);
   void AddValue(const std::string& name, int64 value);
+  template <typename T>
+  void AddValue(const std::string& name, const std::vector<T>& value);
   void AddBoolean(const std::string& name, bool value);
 
   double timestamp;  // Time since 1970-01-01T00:00:00Z in milliseconds.
@@ -141,6 +143,7 @@
   static const char kStatsValueNameEchoDelayStdDev[];
   static const char kStatsValueNameEchoReturnLoss[];
   static const char kStatsValueNameEchoReturnLossEnhancement[];
+  static const char kStatsValueNameExpandRate[];
   static const char kStatsValueNameFirsReceived[];
   static const char kStatsValueNameFirsSent[];
   static const char kStatsValueNameFrameHeightInput[];
@@ -164,7 +167,6 @@
   static const char kStatsValueNameJitterReceived[];
   static const char kStatsValueNameNacksReceived[];
   static const char kStatsValueNameNacksSent[];
-  static const char kStatsValueNameNetEqExpandRate[];
   static const char kStatsValueNameRtt[];
   static const char kStatsValueNameAvailableSendBandwidth[];
   static const char kStatsValueNameAvailableReceiveBandwidth[];
@@ -189,6 +191,9 @@
   static const char kStatsValueNameRemoteCertificateId[];
   static const char kStatsValueNameLocalCandidateType[];
   static const char kStatsValueNameRemoteCandidateType[];
+  static const char kStatsValueNameRecvPacketGroupArrivalTimeDebug[];
+  static const char kStatsValueNameRecvPacketGroupPropagationDeltaDebug[];
+  static const char kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug[];
 };
 
 typedef std::vector<StatsReport> StatsReports;
diff --git a/talk/app/webrtc/webrtc.scons b/talk/app/webrtc/webrtc.scons
index 9b1af3c..dd4bea0 100644
--- a/talk/app/webrtc/webrtc.scons
+++ b/talk/app/webrtc/webrtc.scons
@@ -31,6 +31,7 @@
       'peerconnectionfactory.cc',
       'peerconnection.cc',
       'portallocatorfactory.cc',
+      'remoteaudiosource.cc',
       'roapmessages.cc',
       'roapsession.cc',
       'roapsignaling.cc',
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index 59d7270..ef6af49 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -866,6 +866,18 @@
     voice_channel_->SetChannelOptions(options);
 }
 
+void WebRtcSession::SetAudioPlayoutVolume(uint32 ssrc, double volume) {
+  ASSERT(signaling_thread()->IsCurrent());
+  ASSERT(volume >= 0 && volume <= 10);
+  if (!voice_channel_) {
+    LOG(LS_ERROR) << "SetAudioPlayoutVolume: No audio channel exists.";
+    return;
+  }
+
+  if (!voice_channel_->SetOutputScaling(ssrc, volume, volume))
+    ASSERT(false);
+}
+
 bool WebRtcSession::SetCaptureDevice(uint32 ssrc,
                                      cricket::VideoCapturer* camera) {
   ASSERT(signaling_thread()->IsCurrent());
diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h
index 384ac47..628aa1e 100644
--- a/talk/app/webrtc/webrtcsession.h
+++ b/talk/app/webrtc/webrtcsession.h
@@ -165,6 +165,7 @@
   virtual void SetAudioSend(uint32 ssrc, bool enable,
                             const cricket::AudioOptions& options,
                             cricket::AudioRenderer* renderer) OVERRIDE;
+  virtual void SetAudioPlayoutVolume(uint32 ssrc, double volume) OVERRIDE;
 
   // Implements VideoMediaProviderInterface.
   virtual bool SetCaptureDevice(uint32 ssrc,