Update stable to r4945.

git-svn-id: http://webrtc.googlecode.com/svn/stable/talk@4946 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/OWNERS b/OWNERS
index b55b3a3..ddb55cf 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,4 +7,6 @@
 perkj@webrtc.org
 sergeyu@chromium.org
 tommi@webrtc.org
-wu@webrtc.org
\ No newline at end of file
+wu@webrtc.org
+per-file *.isolate=kjellander@webrtc.org
+
diff --git a/app/webrtc/datachannel.cc b/app/webrtc/datachannel.cc
index 9409fd7..3de001b 100644
--- a/app/webrtc/datachannel.cc
+++ b/app/webrtc/datachannel.cc
@@ -28,7 +28,7 @@
 
 #include <string>
 
-#include "talk/app/webrtc/webrtcsession.h"
+#include "talk/app/webrtc/mediastreamprovider.h"
 #include "talk/base/logging.h"
 #include "talk/base/refcount.h"
 
@@ -38,24 +38,29 @@
 static size_t kMaxQueuedSendDataPackets = 100;
 
 talk_base::scoped_refptr<DataChannel> DataChannel::Create(
-    WebRtcSession* session,
+    DataChannelProviderInterface* provider,
+    cricket::DataChannelType dct,
     const std::string& label,
     const DataChannelInit* config) {
   talk_base::scoped_refptr<DataChannel> channel(
-      new talk_base::RefCountedObject<DataChannel>(session, label));
+      new talk_base::RefCountedObject<DataChannel>(provider, dct, label));
   if (!channel->Init(config)) {
     return NULL;
   }
   return channel;
 }
 
-DataChannel::DataChannel(WebRtcSession* session, const std::string& label)
+DataChannel::DataChannel(
+    DataChannelProviderInterface* provider,
+    cricket::DataChannelType dct,
+    const std::string& label)
     : label_(label),
       observer_(NULL),
       state_(kConnecting),
       was_ever_writable_(false),
-      session_(session),
-      data_session_(NULL),
+      connected_to_provider_(false),
+      data_channel_type_(dct),
+      provider_(provider),
       send_ssrc_set_(false),
       send_ssrc_(0),
       receive_ssrc_set_(false),
@@ -64,7 +69,7 @@
 
 bool DataChannel::Init(const DataChannelInit* config) {
   if (config) {
-    if (session_->data_channel_type() == cricket::DCT_RTP &&
+    if (data_channel_type_ == cricket::DCT_RTP &&
         (config->reliable ||
          config->id != -1 ||
          config->maxRetransmits != -1 ||
@@ -72,7 +77,7 @@
       LOG(LS_ERROR) << "Failed to initialize the RTP data channel due to "
                     << "invalid DataChannelInit.";
       return false;
-    } else if (session_->data_channel_type() == cricket::DCT_SCTP) {
+    } else if (data_channel_type_ == cricket::DCT_SCTP) {
       if (config->id < -1 ||
           config->maxRetransmits < -1 ||
           config->maxRetransmitTime < -1) {
@@ -111,7 +116,7 @@
 }
 
 bool DataChannel::reliable() const {
-  if (session_->data_channel_type() == cricket::DCT_RTP) {
+  if (data_channel_type_ == cricket::DCT_RTP) {
     return false;
   } else {
     return config_.maxRetransmits == -1 &&
@@ -165,14 +170,15 @@
 }
 
 bool DataChannel::SendControl(const talk_base::Buffer* buffer) {
+  if (data_channel_type_ == cricket::DCT_RTP) {
+    delete buffer;
+    return false;
+  }
+
   if (state_ != kOpen) {
     QueueControl(buffer);
     return true;
   }
-  if (session_->data_channel_type() == cricket::DCT_RTP) {
-    delete buffer;
-    return false;
-  }
 
   cricket::SendDataParams send_params;
   send_params.ssrc = config_.id;
@@ -180,8 +186,7 @@
   send_params.type = cricket::DMT_CONTROL;
 
   cricket::SendDataResult send_result;
-  bool retval = session_->data_channel()->SendData(
-      send_params, *buffer, &send_result);
+  bool retval = provider_->SendData(send_params, *buffer, &send_result);
   if (!retval && send_result == cricket::SDR_BLOCK) {
     // Link is congested.  Queue for later.
     QueueControl(buffer);
@@ -193,7 +198,7 @@
 
 void DataChannel::SetReceiveSsrc(uint32 receive_ssrc) {
   if (receive_ssrc_set_) {
-    ASSERT(session_->data_channel_type() == cricket::DCT_RTP ||
+    ASSERT(data_channel_type_ == cricket::DCT_RTP ||
            !send_ssrc_set_ ||
            receive_ssrc_ == send_ssrc_);
     return;
@@ -210,7 +215,7 @@
 
 void DataChannel::SetSendSsrc(uint32 send_ssrc) {
   if (send_ssrc_set_) {
-    ASSERT(session_->data_channel_type() == cricket::DCT_RTP ||
+    ASSERT(data_channel_type_ == cricket::DCT_RTP ||
            !receive_ssrc_set_ ||
            receive_ssrc_ == send_ssrc_);
     return;
@@ -221,7 +226,7 @@
 }
 
 // The underlaying data engine is closing.
-// This function make sure the DataChannel is disconneced and change state to
+// This function makes sure the DataChannel is disconnected and changes state to
 // kClosed.
 void DataChannel::OnDataEngineClose() {
   DoClose();
@@ -277,7 +282,7 @@
   switch (state_) {
     case kConnecting: {
       if (HasNegotiationCompleted()) {
-        if (!IsConnectedToDataSession()) {
+        if (!connected_to_provider_) {
           ConnectToDataSession();
         }
         if (was_ever_writable_) {
@@ -293,7 +298,7 @@
       break;
     }
     case kClosing: {
-      if (IsConnectedToDataSession()) {
+      if (connected_to_provider_) {
         DisconnectFromDataSession();
       }
       if (HasNegotiationCompleted()) {
@@ -314,28 +319,12 @@
 }
 
 void DataChannel::ConnectToDataSession() {
-  if (!session_->data_channel()) {
-    LOG(LS_ERROR) << "The DataEngine does not exist.";
-    ASSERT(session_->data_channel() != NULL);
-    return;
-  }
-
-  data_session_ = session_->data_channel();
-  data_session_->SignalReadyToSendData.connect(this,
-                                               &DataChannel::OnChannelReady);
-  data_session_->SignalDataReceived.connect(this, &DataChannel::OnDataReceived);
-  cricket::StreamParams params =
-    cricket::StreamParams::CreateLegacy(id());
-  data_session_->AddRecvStream(params);
-  data_session_->AddSendStream(params);
+  connected_to_provider_ = provider_->ConnectDataChannel(this);
 }
 
 void DataChannel::DisconnectFromDataSession() {
-  data_session_->RemoveSendStream(id());
-  data_session_->RemoveRecvStream(id());
-  data_session_->SignalReadyToSendData.disconnect(this);
-  data_session_->SignalDataReceived.disconnect(this);
-  data_session_ = NULL;
+  provider_->DisconnectDataChannel(this);
+  connected_to_provider_ = false;
 }
 
 void DataChannel::DeliverQueuedReceivedData() {
@@ -409,15 +398,14 @@
   cricket::SendDataParams send_params;
 
   send_params.ssrc = send_ssrc_;
-  if (session_->data_channel_type() == cricket::DCT_SCTP) {
+  if (data_channel_type_ == cricket::DCT_SCTP) {
     send_params.ordered = config_.ordered;
     send_params.max_rtx_count = config_.maxRetransmits;
     send_params.max_rtx_ms = config_.maxRetransmitTime;
   }
   send_params.type = buffer.binary ? cricket::DMT_BINARY : cricket::DMT_TEXT;
 
-  return session_->data_channel()->SendData(send_params, buffer.data,
-                                            send_result);
+  return provider_->SendData(send_params, buffer.data, send_result);
 }
 
 bool DataChannel::QueueSendData(const DataBuffer& buffer) {
diff --git a/app/webrtc/datachannel.h b/app/webrtc/datachannel.h
index 3ce3c1b..a09c7f6 100644
--- a/app/webrtc/datachannel.h
+++ b/app/webrtc/datachannel.h
@@ -35,11 +35,24 @@
 #include "talk/app/webrtc/proxy.h"
 #include "talk/base/scoped_ref_ptr.h"
 #include "talk/base/sigslot.h"
+#include "talk/media/base/mediachannel.h"
 #include "talk/session/media/channel.h"
 
 namespace webrtc {
 
-class WebRtcSession;
+class DataChannel;
+
+class DataChannelProviderInterface {
+ public:
+  virtual bool SendData(const cricket::SendDataParams& params,
+                        const talk_base::Buffer& payload,
+                        cricket::SendDataResult* result) = 0;
+  virtual bool ConnectDataChannel(DataChannel* data_channel) = 0;
+  virtual void DisconnectDataChannel(DataChannel* data_channel) = 0;
+
+ protected:
+  virtual ~DataChannelProviderInterface() {}
+};
 
 // DataChannel is a an implementation of the DataChannelInterface based on
 // libjingle's data engine. It provides an implementation of unreliable data
@@ -60,7 +73,8 @@
                     public sigslot::has_slots<> {
  public:
   static talk_base::scoped_refptr<DataChannel> Create(
-      WebRtcSession* session,
+      DataChannelProviderInterface* client,
+      cricket::DataChannelType dct,
       const std::string& label,
       const DataChannelInit* config);
 
@@ -105,24 +119,26 @@
   // underlying DataMediaChannel becomes ready, or when this channel is a new
   // stream on an existing DataMediaChannel, and we've finished negotiation.
   void OnChannelReady(bool writable);
- protected:
-  DataChannel(WebRtcSession* session, const std::string& label);
-  virtual ~DataChannel();
-
-  bool Init(const DataChannelInit* config);
-  bool HasNegotiationCompleted();
 
   // Sigslots from cricket::DataChannel
   void OnDataReceived(cricket::DataChannel* channel,
                       const cricket::ReceiveDataParams& params,
                       const talk_base::Buffer& payload);
 
+ protected:
+  DataChannel(DataChannelProviderInterface* client,
+              cricket::DataChannelType dct,
+              const std::string& label);
+  virtual ~DataChannel();
+
+  bool Init(const DataChannelInit* config);
+  bool HasNegotiationCompleted();
+
  private:
   void DoClose();
   void UpdateState();
   void SetState(DataState state);
   void DisconnectFromDataSession();
-  bool IsConnectedToDataSession() { return data_session_ != NULL; }
   void DeliverQueuedControlData();
   void QueueControl(const talk_base::Buffer* buffer);
   void ClearQueuedControlData();
@@ -139,8 +155,9 @@
   DataChannelObserver* observer_;
   DataState state_;
   bool was_ever_writable_;
-  WebRtcSession* session_;
-  cricket::DataChannel* data_session_;
+  bool connected_to_provider_;
+  cricket::DataChannelType data_channel_type_;
+  DataChannelProviderInterface* provider_;
   bool send_ssrc_set_;
   uint32 send_ssrc_;
   bool receive_ssrc_set_;
diff --git a/app/webrtc/datachannel_unittest.cc b/app/webrtc/datachannel_unittest.cc
index 2b0a9fe..4d66907 100644
--- a/app/webrtc/datachannel_unittest.cc
+++ b/app/webrtc/datachannel_unittest.cc
@@ -26,118 +26,78 @@
  */
 
 #include "talk/app/webrtc/datachannel.h"
-#include "talk/app/webrtc/jsep.h"
-#include "talk/app/webrtc/mediastreamsignaling.h"
-#include "talk/app/webrtc/test/fakeconstraints.h"
-#include "talk/app/webrtc/test/fakedtlsidentityservice.h"
-#include "talk/app/webrtc/webrtcsession.h"
 #include "talk/base/gunit.h"
-#include "talk/media/base/fakemediaengine.h"
-#include "talk/media/devices/fakedevicemanager.h"
-#include "talk/session/media/channelmanager.h"
 
-using webrtc::CreateSessionDescriptionObserver;
-using webrtc::MediaConstraintsInterface;
-using webrtc::SessionDescriptionInterface;
+using webrtc::DataChannel;
 
-const uint32 kFakeSsrc = 1;
-
-class CreateSessionDescriptionObserverForTest
-    : public talk_base::RefCountedObject<CreateSessionDescriptionObserver> {
+class FakeDataChannelClient : public webrtc::DataChannelProviderInterface {
  public:
-  virtual void OnSuccess(SessionDescriptionInterface* desc) {
-    description_.reset(desc);
+  FakeDataChannelClient() : send_blocked_(false) {}
+  virtual ~FakeDataChannelClient() {}
+
+  virtual bool SendData(const cricket::SendDataParams& params,
+                        const talk_base::Buffer& payload,
+                        cricket::SendDataResult* result) OVERRIDE {
+    if (send_blocked_) {
+      *result = cricket::SDR_BLOCK;
+      return false;
+    }
+    last_send_data_params_ = params;
+    return true;
   }
-  virtual void OnFailure(const std::string& error) {}
-
-  SessionDescriptionInterface* description() { return description_.get(); }
-
-  SessionDescriptionInterface* ReleaseDescription() {
-    return description_.release();
+  virtual bool ConnectDataChannel(DataChannel* data_channel) OVERRIDE {
+    return true;
   }
+  virtual void DisconnectDataChannel(DataChannel* data_channel) OVERRIDE {}
 
- protected:
-  ~CreateSessionDescriptionObserverForTest() {}
+  void set_send_blocked(bool blocked) { send_blocked_ = blocked; }
+  cricket::SendDataParams last_send_data_params() {
+      return last_send_data_params_;
+  }
 
  private:
-  talk_base::scoped_ptr<SessionDescriptionInterface> description_;
+  cricket::SendDataParams last_send_data_params_;
+  bool send_blocked_;
 };
 
 class SctpDataChannelTest : public testing::Test {
  protected:
   SctpDataChannelTest()
-      : media_engine_(new cricket::FakeMediaEngine),
-        data_engine_(new cricket::FakeDataEngine),
-        channel_manager_(
-            new cricket::ChannelManager(media_engine_,
-                                        data_engine_,
-                                        new cricket::FakeDeviceManager(),
-                                        new cricket::CaptureManager(),
-                                        talk_base::Thread::Current())),
-        media_stream_signaling_(
-            new webrtc::MediaStreamSignaling(talk_base::Thread::Current(),
-                                             NULL, channel_manager_.get())),
-        session_(channel_manager_.get(),
-                 talk_base::Thread::Current(),
-                 talk_base::Thread::Current(),
-                 NULL,
-                 media_stream_signaling_.get()),
-        webrtc_data_channel_(NULL) {}
-
-  virtual void SetUp() {
-    if (!talk_base::SSLStreamAdapter::HaveDtlsSrtp()) {
-      return;
-    }
-    channel_manager_->Init();
-    webrtc::FakeConstraints constraints;
-    constraints.AddMandatory(MediaConstraintsInterface::kEnableDtlsSrtp, true);
-    constraints.AddMandatory(MediaConstraintsInterface::kEnableSctpDataChannels,
-                             true);
-    ASSERT_TRUE(session_.Initialize(&constraints,
-                                    new FakeIdentityService()));
-    webrtc_data_channel_ = webrtc::DataChannel::Create(&session_, "test", NULL);
-    ASSERT_TRUE(media_stream_signaling_->AddDataChannel(webrtc_data_channel_));
-
-    talk_base::scoped_refptr<CreateSessionDescriptionObserverForTest> observer
-        = new CreateSessionDescriptionObserverForTest();
-    session_.CreateOffer(observer.get(), NULL);
-    EXPECT_TRUE_WAIT(observer->description() != NULL, 2000);
-    ASSERT_TRUE(observer->description() != NULL);
-    ASSERT_TRUE(session_.SetLocalDescription(observer->ReleaseDescription(),
-                                             NULL));
-    // Connect to the media channel.
-    webrtc_data_channel_->SetSendSsrc(kFakeSsrc);
-    webrtc_data_channel_->SetReceiveSsrc(kFakeSsrc);
-    session_.data_channel()->SignalReadyToSendData(true);
+      : webrtc_data_channel_(
+          DataChannel::Create(&client_, cricket::DCT_SCTP, "test", &init_)) {
   }
 
-  void SetSendBlocked(bool blocked) {
-    bool was_blocked = data_engine_->GetChannel(0)->is_send_blocked();
-    data_engine_->GetChannel(0)->set_send_blocked(blocked);
-    if (!blocked && was_blocked) {
-      session_.data_channel()->SignalReadyToSendData(true);
-    }
+  void SetChannelReady() {
+    webrtc_data_channel_->OnChannelReady(true);
   }
-  cricket::FakeMediaEngine* media_engine_;
-  cricket::FakeDataEngine* data_engine_;
-  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
-  talk_base::scoped_ptr<webrtc::MediaStreamSignaling> media_stream_signaling_;
-  webrtc::WebRtcSession session_;
-  talk_base::scoped_refptr<webrtc::DataChannel> webrtc_data_channel_;
+
+  webrtc::DataChannelInit init_;
+  FakeDataChannelClient client_;
+  talk_base::scoped_refptr<DataChannel> webrtc_data_channel_;
 };
 
+// Tests the state of the data channel.
+TEST_F(SctpDataChannelTest, StateTransition) {
+  EXPECT_EQ(webrtc::DataChannelInterface::kConnecting,
+            webrtc_data_channel_->state());
+  SetChannelReady();
+  EXPECT_EQ(webrtc::DataChannelInterface::kOpen, webrtc_data_channel_->state());
+  webrtc_data_channel_->Close();
+  EXPECT_EQ(webrtc::DataChannelInterface::kClosed,
+            webrtc_data_channel_->state());
+}
+
 // Tests that DataChannel::buffered_amount() is correct after the channel is
 // blocked.
 TEST_F(SctpDataChannelTest, BufferedAmountWhenBlocked) {
-  if (!talk_base::SSLStreamAdapter::HaveDtlsSrtp()) {
-    return;
-  }
+  SetChannelReady();
   webrtc::DataBuffer buffer("abcd");
   EXPECT_TRUE(webrtc_data_channel_->Send(buffer));
 
   EXPECT_EQ(0U, webrtc_data_channel_->buffered_amount());
 
-  SetSendBlocked(true);
+  client_.set_send_blocked(true);
+
   const int number_of_packets = 3;
   for (int i = 0; i < number_of_packets; ++i) {
     EXPECT_TRUE(webrtc_data_channel_->Send(buffer));
@@ -149,13 +109,20 @@
 // Tests that the queued data are sent when the channel transitions from blocked
 // to unblocked.
 TEST_F(SctpDataChannelTest, QueuedDataSentWhenUnblocked) {
-  if (!talk_base::SSLStreamAdapter::HaveDtlsSrtp()) {
-    return;
-  }
+  SetChannelReady();
   webrtc::DataBuffer buffer("abcd");
-  SetSendBlocked(true);
+  client_.set_send_blocked(true);
   EXPECT_TRUE(webrtc_data_channel_->Send(buffer));
 
-  SetSendBlocked(false);
+  client_.set_send_blocked(false);
+  SetChannelReady();
   EXPECT_EQ(0U, webrtc_data_channel_->buffered_amount());
 }
+
+// Tests that the queued control message is sent when channel is ready.
+TEST_F(SctpDataChannelTest, QueuedControlMessageSent) {
+  talk_base::Buffer* buffer = new talk_base::Buffer("abcd", 4);
+  EXPECT_TRUE(webrtc_data_channel_->SendControl(buffer));
+  SetChannelReady();
+  EXPECT_EQ(cricket::DMT_CONTROL, client_.last_send_data_params().type);
+}
diff --git a/app/webrtc/java/jni/peerconnection_jni.cc b/app/webrtc/java/jni/peerconnection_jni.cc
index e364b3a..218864c 100644
--- a/app/webrtc/java/jni/peerconnection_jni.cc
+++ b/app/webrtc/java/jni/peerconnection_jni.cc
@@ -1241,7 +1241,7 @@
     JNIEnv* jni, jclass, jobject context) {
   CHECK(g_jvm, "JNI_OnLoad failed to run?");
   bool failure = false;
-  failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm, context);
+  failure |= webrtc::VideoEngine::SetAndroidObjects(g_jvm);
   failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context);
   return !failure;
 }
@@ -1543,7 +1543,7 @@
   CHECK(device_manager->Init(), "DeviceManager::Init() failed");
   cricket::Device device;
   if (!device_manager->GetVideoCaptureDevice(device_name, &device)) {
-    LOG(LS_ERROR) << "GetVideoCaptureDevice failed";
+    LOG(LS_ERROR) << "GetVideoCaptureDevice failed for " << device_name;
     return 0;
   }
   talk_base::scoped_ptr<cricket::VideoCapturer> capturer(
@@ -1566,6 +1566,28 @@
   return (jlong)renderer.release();
 }
 
+JOW(jlong, VideoSource_stop)(JNIEnv* jni, jclass, jlong j_p) {
+  cricket::VideoCapturer* capturer =
+      reinterpret_cast<VideoSourceInterface*>(j_p)->GetVideoCapturer();
+  talk_base::scoped_ptr<cricket::VideoFormatPod> format(
+      new cricket::VideoFormatPod(*capturer->GetCaptureFormat()));
+  capturer->Stop();
+  return jlongFromPointer(format.release());
+}
+
+JOW(void, VideoSource_restart)(
+    JNIEnv* jni, jclass, jlong j_p_source, jlong j_p_format) {
+  talk_base::scoped_ptr<cricket::VideoFormatPod> format(
+      reinterpret_cast<cricket::VideoFormatPod*>(j_p_format));
+  reinterpret_cast<VideoSourceInterface*>(j_p_source)->GetVideoCapturer()->
+      StartCapturing(cricket::VideoFormat(*format));
+}
+
+JOW(void, VideoSource_freeNativeVideoFormat)(
+    JNIEnv* jni, jclass, jlong j_p) {
+  delete reinterpret_cast<cricket::VideoFormatPod*>(j_p);
+}
+
 JOW(jstring, MediaStreamTrack_nativeId)(JNIEnv* jni, jclass, jlong j_p) {
   return JavaStringFromStdString(
       jni, reinterpret_cast<MediaStreamTrackInterface*>(j_p)->id());
diff --git a/app/webrtc/java/src/org/webrtc/VideoSource.java b/app/webrtc/java/src/org/webrtc/VideoSource.java
index f29f312..65e6004 100644
--- a/app/webrtc/java/src/org/webrtc/VideoSource.java
+++ b/app/webrtc/java/src/org/webrtc/VideoSource.java
@@ -28,9 +28,46 @@
 
 package org.webrtc;
 
-/** Java version of VideoSourceInterface. */
+/**
+ * Java version of VideoSourceInterface, extended with stop/restart
+ * functionality to allow explicit control of the camera device on android,
+ * where there is no support for multiple open capture devices and the cost of
+ * holding a camera open (even if MediaStreamTrack.setEnabled(false) is muting
+ * its output to the encoder) can be too high to bear.
+ */
 public class VideoSource extends MediaSource {
+  private long nativeVideoFormatAtStop;
+
   public VideoSource(long nativeSource) {
     super(nativeSource);
   }
+
+  // Stop capture feeding this source.
+  public void stop() {
+    nativeVideoFormatAtStop = stop(nativeSource);
+  }
+
+  // Restart capture feeding this source.  stop() must have been called since
+  // the last call to restart() (if any).  Note that this isn't "start()";
+  // sources are started by default at birth.
+  public void restart() {
+    restart(nativeSource, nativeVideoFormatAtStop);
+    nativeVideoFormatAtStop = 0;
+  }
+
+  @Override
+  public void dispose() {
+    if (nativeVideoFormatAtStop != 0) {
+      freeNativeVideoFormat(nativeVideoFormatAtStop);
+      nativeVideoFormatAtStop = 0;
+    }
+    super.dispose();
+  }
+
+  // This stop() returns an owned C++ VideoFormat pointer for use in restart()
+  // and dispose().
+  private static native long stop(long nativeSource);
+  private static native void restart(
+      long nativeSource, long nativeVideoFormatAtStop);
+  private static native void freeNativeVideoFormat(long nativeVideoFormat);
 }
diff --git a/app/webrtc/peerconnection.cc b/app/webrtc/peerconnection.cc
index 9eaf915..a736fa2 100644
--- a/app/webrtc/peerconnection.cc
+++ b/app/webrtc/peerconnection.cc
@@ -172,8 +172,10 @@
     }
     std::string address = tokens[1];
     int port = kDefaultStunPort;
-    if (service_type == TURNS)
+    if (service_type == TURNS) {
       port = kDefaultStunTlsPort;
+      turn_transport_type = kTcpTransportType;
+    }
 
     if (tokens.size() > kMinIceUriTokens) {
       if (!talk_base::FromString(tokens[2], &port)) {
diff --git a/app/webrtc/peerconnectionfactory_unittest.cc b/app/webrtc/peerconnectionfactory_unittest.cc
index 182c01c..e3def6c 100644
--- a/app/webrtc/peerconnectionfactory_unittest.cc
+++ b/app/webrtc/peerconnectionfactory_unittest.cc
@@ -61,6 +61,8 @@
     "turn:test@hello.com?transport=tcp";
 static const char kSecureTurnIceServer[] =
     "turns:test@hello.com?transport=tcp";
+static const char kSecureTurnIceServerWithoutTransportParam[] =
+    "turns:test_no_transport@hello.com";
 static const char kTurnIceServerWithNoUsernameInUri[] =
     "turn:test.com:1234";
 static const char kTurnPassword[] = "turnpassword";
@@ -242,6 +244,9 @@
   ice_server.uri = kSecureTurnIceServer;
   ice_server.password = kTurnPassword;
   ice_servers.push_back(ice_server);
+  ice_server.uri = kSecureTurnIceServerWithoutTransportParam;
+  ice_server.password = kTurnPassword;
+  ice_servers.push_back(ice_server);
   talk_base::scoped_refptr<PeerConnectionInterface> pc(
       factory_->CreatePeerConnection(ice_servers, NULL,
                                      allocator_factory_.get(),
@@ -249,9 +254,14 @@
                                      &observer_));
   EXPECT_TRUE(pc.get() != NULL);
   TurnConfigurations turn_configs;
-  webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn(
+  webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn1(
       "hello.com", kDefaultStunTlsPort, "test", kTurnPassword, "tcp", true);
-  turn_configs.push_back(turn);
+  turn_configs.push_back(turn1);
+  // TURNS with transport param should be default to tcp.
+  webrtc::PortAllocatorFactoryInterface::TurnConfiguration turn2(
+      "hello.com", kDefaultStunTlsPort, "test_no_transport",
+      kTurnPassword, "tcp", true);
+  turn_configs.push_back(turn2);
   VerifyTurnConfigurations(turn_configs);
 }
 
diff --git a/app/webrtc/statscollector.cc b/app/webrtc/statscollector.cc
index 06c4b44..f5ff708 100644
--- a/app/webrtc/statscollector.cc
+++ b/app/webrtc/statscollector.cc
@@ -30,6 +30,8 @@
 #include <utility>
 #include <vector>
 
+#include "talk/base/base64.h"
+#include "talk/base/scoped_ptr.h"
 #include "talk/session/media/channel.h"
 
 namespace webrtc {
@@ -52,6 +54,7 @@
 const char StatsReport::kStatsValueNameCodecName[] = "googCodecName";
 const char StatsReport::kStatsValueNameComponent[] = "googComponent";
 const char StatsReport::kStatsValueNameContentName[] = "googContentName";
+const char StatsReport::kStatsValueNameDer[] = "googDerBase64";
 // Echo metrics from the audio processing module.
 const char StatsReport::kStatsValueNameEchoCancellationQualityMin[] =
     "googEchoCancellationQualityMin";
@@ -64,6 +67,7 @@
 const char StatsReport::kStatsValueNameEchoReturnLossEnhancement[] =
     "googEchoCancellationReturnLossEnhancement";
 
+const char StatsReport::kStatsValueNameFingerprint[] = "googFingerprint";
 const char StatsReport::kStatsValueNameFirsReceived[] = "googFirsReceived";
 const char StatsReport::kStatsValueNameFirsSent[] = "googFirsSent";
 const char StatsReport::kStatsValueNameFrameHeightReceived[] =
@@ -82,8 +86,11 @@
     "googFrameWidthReceived";
 const char StatsReport::kStatsValueNameFrameWidthSent[] = "googFrameWidthSent";
 const char StatsReport::kStatsValueNameInitiator[] = "googInitiator";
+const char StatsReport::kStatsValueNameIssuerId[] = "googIssuerId";
 const char StatsReport::kStatsValueNameJitterReceived[] = "googJitterReceived";
 const char StatsReport::kStatsValueNameLocalAddress[] = "googLocalAddress";
+const char StatsReport::kStatsValueNameLocalCertificateId[] =
+    "googLocalCertificateId";
 const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived";
 const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent";
 const char StatsReport::kStatsValueNamePacketsReceived[] = "packetsReceived";
@@ -91,6 +98,8 @@
 const char StatsReport::kStatsValueNamePacketsLost[] = "packetsLost";
 const char StatsReport::kStatsValueNameReadable[] = "googReadable";
 const char StatsReport::kStatsValueNameRemoteAddress[] = "googRemoteAddress";
+const char StatsReport::kStatsValueNameRemoteCertificateId[] =
+    "googRemoteCertificateId";
 const char StatsReport::kStatsValueNameRetransmitBitrate[] =
     "googRetransmitBitrate";
 const char StatsReport::kStatsValueNameRtt[] = "googRtt";
@@ -114,6 +123,7 @@
 const char StatsReport::kStatsReportTypeTransport[] = "googTransport";
 const char StatsReport::kStatsReportTypeComponent[] = "googComponent";
 const char StatsReport::kStatsReportTypeCandidatePair[] = "googCandidatePair";
+const char StatsReport::kStatsReportTypeCertificate[] = "googCertificate";
 
 const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo";
 
@@ -434,6 +444,58 @@
   return report;
 }
 
+std::string StatsCollector::AddOneCertificateReport(
+    const talk_base::SSLCertificate* cert, const std::string& issuer_id) {
+  // TODO(bemasc): Move this computation to a helper class that caches these
+  // values to reduce CPU use in GetStats.  This will require adding a fast
+  // SSLCertificate::Equals() method to detect certificate changes.
+  talk_base::scoped_ptr<talk_base::SSLFingerprint> ssl_fingerprint(
+      talk_base::SSLFingerprint::Create(talk_base::DIGEST_SHA_256, cert));
+  std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint();
+
+  talk_base::Buffer der_buffer;
+  cert->ToDER(&der_buffer);
+  std::string der_base64;
+  talk_base::Base64::EncodeFromArray(
+      der_buffer.data(), der_buffer.length(), &der_base64);
+
+  StatsReport report;
+  report.type = StatsReport::kStatsReportTypeCertificate;
+  report.id = StatsId(report.type, fingerprint);
+  report.timestamp = stats_gathering_started_;
+  report.AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint);
+  report.AddValue(StatsReport::kStatsValueNameDer, der_base64);
+  if (!issuer_id.empty())
+    report.AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id);
+  reports_[report.id] = report;
+  return report.id;
+}
+
+std::string StatsCollector::AddCertificateReports(
+    const talk_base::SSLCertificate* cert) {
+  // Produces a chain of StatsReports representing this certificate and the rest
+  // of its chain, and adds those reports to |reports_|.  The return value is
+  // the id of the leaf report.  The provided cert must be non-null, so at least
+  // one report will always be provided and the returned string will never be
+  // empty.
+  ASSERT(cert != NULL);
+
+  std::string issuer_id;
+  talk_base::scoped_ptr<talk_base::SSLCertChain> chain;
+  if (cert->GetChain(chain.accept())) {
+    // This loop runs in reverse, i.e. from root to leaf, so that each
+    // certificate's issuer's report ID is known before the child certificate's
+    // report is generated.  The root certificate does not have an issuer ID
+    // value.
+    for (ptrdiff_t i = chain->GetSize() - 1; i >= 0; --i) {
+      const talk_base::SSLCertificate& cert_i = chain->Get(i);
+      issuer_id = AddOneCertificateReport(&cert_i, issuer_id);
+    }
+  }
+  // Add the leaf certificate.
+  return AddOneCertificateReport(cert, issuer_id);
+}
+
 void StatsCollector::ExtractSessionInfo() {
   // Extract information from the base session.
   StatsReport report;
@@ -454,6 +516,22 @@
     for (cricket::TransportStatsMap::iterator transport_iter
              = stats.transport_stats.begin();
          transport_iter != stats.transport_stats.end(); ++transport_iter) {
+      // Attempt to get a copy of the certificates from the transport and
+      // expose them in stats reports.  All channels in a transport share the
+      // same local and remote certificates.
+      std::string local_cert_report_id, remote_cert_report_id;
+      cricket::Transport* transport =
+          session_->GetTransport(transport_iter->second.content_name);
+      if (transport) {
+        talk_base::scoped_ptr<talk_base::SSLIdentity> identity;
+        if (transport->GetIdentity(identity.accept()))
+          local_cert_report_id = AddCertificateReports(
+              &(identity->certificate()));
+
+        talk_base::scoped_ptr<talk_base::SSLCertificate> cert;
+        if (transport->GetRemoteCertificate(cert.accept()))
+          remote_cert_report_id = AddCertificateReports(cert.get());
+      }
       for (cricket::TransportChannelStatsList::iterator channel_iter
                = transport_iter->second.channel_stats.begin();
            channel_iter != transport_iter->second.channel_stats.end();
@@ -467,6 +545,14 @@
         channel_report.timestamp = stats_gathering_started_;
         channel_report.AddValue(StatsReport::kStatsValueNameComponent,
                                 channel_iter->component);
+        if (!local_cert_report_id.empty())
+          channel_report.AddValue(
+              StatsReport::kStatsValueNameLocalCertificateId,
+              local_cert_report_id);
+        if (!remote_cert_report_id.empty())
+          channel_report.AddValue(
+              StatsReport::kStatsValueNameRemoteCertificateId,
+              remote_cert_report_id);
         reports_[channel_report.id] = channel_report;
         for (size_t i = 0;
              i < channel_iter->connection_infos.size();
diff --git a/app/webrtc/statscollector.h b/app/webrtc/statscollector.h
index 03a32c4..c34b5a0 100644
--- a/app/webrtc/statscollector.h
+++ b/app/webrtc/statscollector.h
@@ -75,6 +75,14 @@
  private:
   bool CopySelectedReports(const std::string& selector, StatsReports* reports);
 
+  // Helper method for AddCertificateReports.
+  std::string AddOneCertificateReport(
+      const talk_base::SSLCertificate* cert, const std::string& issuer_id);
+
+  // Adds a report for this certificate and every certificate in its chain, and
+  // returns the leaf certificate's report's ID.
+  std::string AddCertificateReports(const talk_base::SSLCertificate* cert);
+
   void ExtractSessionInfo();
   void ExtractVoiceInfo();
   void ExtractVideoInfo();
diff --git a/app/webrtc/statscollector_unittest.cc b/app/webrtc/statscollector_unittest.cc
index cce1645..9829832 100644
--- a/app/webrtc/statscollector_unittest.cc
+++ b/app/webrtc/statscollector_unittest.cc
@@ -30,6 +30,8 @@
 
 #include "talk/app/webrtc/mediastream.h"
 #include "talk/app/webrtc/videotrack.h"
+#include "talk/base/base64.h"
+#include "talk/base/fakesslidentity.h"
 #include "talk/base/gunit.h"
 #include "talk/media/base/fakemediaengine.h"
 #include "talk/media/devices/fakedevicemanager.h"
@@ -60,11 +62,12 @@
  public:
   explicit MockWebRtcSession(cricket::ChannelManager* channel_manager)
     : WebRtcSession(channel_manager, talk_base::Thread::Current(),
-                    NULL, NULL, NULL) {
+                    talk_base::Thread::Current(), NULL, NULL) {
   }
   MOCK_METHOD0(video_channel, cricket::VideoChannel*());
   MOCK_METHOD2(GetTrackIdBySsrc, bool(uint32, std::string*));
   MOCK_METHOD1(GetStats, bool(cricket::SessionStats*));
+  MOCK_METHOD1(GetTransport, cricket::Transport*(const std::string&));
 };
 
 class MockVideoMediaChannel : public cricket::FakeVideoMediaChannel {
@@ -76,8 +79,21 @@
   MOCK_METHOD1(GetStats, bool(cricket::VideoMediaInfo*));
 };
 
+bool GetValue(const webrtc::StatsReport* report,
+              const std::string& name,
+              std::string* value) {
+  webrtc::StatsReport::Values::const_iterator it = report->values.begin();
+  for (; it != report->values.end(); ++it) {
+    if (it->name == name) {
+      *value = it->value;
+      return true;
+    }
+  }
+  return false;
+}
+
 std::string ExtractStatsValue(const std::string& type,
-                              webrtc::StatsReports reports,
+                              const webrtc::StatsReports& reports,
                               const std::string name) {
   if (reports.empty()) {
     return kNoReports;
@@ -85,12 +101,9 @@
   for (size_t i = 0; i < reports.size(); ++i) {
     if (reports[i].type != type)
       continue;
-    webrtc::StatsReport::Values::const_iterator it =
-        reports[i].values.begin();
-    for (; it != reports[i].values.end(); ++it) {
-      if (it->name == name) {
-        return it->value;
-      }
+    std::string ret;
+    if (GetValue(&reports[i], name, &ret)) {
+      return ret;
     }
   }
 
@@ -99,9 +112,8 @@
 
 // Finds the |n|-th report of type |type| in |reports|.
 // |n| starts from 1 for finding the first report.
-const webrtc::StatsReport* FindNthReportByType(webrtc::StatsReports reports,
-                                               const std::string& type,
-                                               int n) {
+const webrtc::StatsReport* FindNthReportByType(
+    const webrtc::StatsReports& reports, const std::string& type, int n) {
   for (size_t i = 0; i < reports.size(); ++i) {
     if (reports[i].type == type) {
       n--;
@@ -112,7 +124,7 @@
   return NULL;
 }
 
-const webrtc::StatsReport* FindReportById(webrtc::StatsReports reports,
+const webrtc::StatsReport* FindReportById(const webrtc::StatsReports& reports,
                                           const std::string& id) {
   for (size_t i = 0; i < reports.size(); ++i) {
     if (reports[i].id == id) {
@@ -134,6 +146,42 @@
       webrtc::StatsReport::kStatsReportTypeBwe, reports, name);
 }
 
+std::string DerToPem(const std::string& der) {
+  return talk_base::SSLIdentity::DerToPem(
+        talk_base::kPemTypeCertificate,
+        reinterpret_cast<const unsigned char*>(der.c_str()),
+        der.length());
+}
+
+std::vector<std::string> DersToPems(
+    const std::vector<std::string>& ders) {
+  std::vector<std::string> pems(ders.size());
+  std::transform(ders.begin(), ders.end(), pems.begin(), DerToPem);
+  return pems;
+}
+
+void CheckCertChainReports(const webrtc::StatsReports& reports,
+                           const std::vector<std::string>& ders,
+                           const std::string& start_id) {
+  std::string certificate_id = start_id;
+  size_t i = 0;
+  while (true) {
+    const webrtc::StatsReport* report = FindReportById(reports, certificate_id);
+    ASSERT_TRUE(report != NULL);
+    std::string der_base64;
+    EXPECT_TRUE(GetValue(
+        report, webrtc::StatsReport::kStatsValueNameDer, &der_base64));
+    std::string der = talk_base::Base64::Decode(der_base64,
+                                                talk_base::Base64::DO_STRICT);
+    EXPECT_EQ(ders[i], der);
+    ++i;
+    if (!GetValue(
+        report, webrtc::StatsReport::kStatsValueNameIssuerId, &certificate_id))
+      break;
+  }
+  EXPECT_EQ(ders.size(), i);
+}
+
 class StatsCollectorTest : public testing::Test {
  protected:
   StatsCollectorTest()
@@ -147,6 +195,77 @@
     EXPECT_CALL(session_, GetStats(_)).WillRepeatedly(Return(false));
   }
 
+  void TestCertificateReports(const talk_base::FakeSSLCertificate& local_cert,
+                              const std::vector<std::string>& local_ders,
+                              const talk_base::FakeSSLCertificate& remote_cert,
+                              const std::vector<std::string>& remote_ders) {
+    webrtc::StatsCollector stats;  // Implementation under test.
+    webrtc::StatsReports reports;  // returned values.
+    stats.set_session(&session_);
+
+    // Fake stats to process.
+    cricket::TransportChannelStats channel_stats;
+    channel_stats.component = 1;
+
+    cricket::TransportStats transport_stats;
+    transport_stats.content_name = "audio";
+    transport_stats.channel_stats.push_back(channel_stats);
+
+    cricket::SessionStats session_stats;
+    session_stats.transport_stats[transport_stats.content_name] =
+        transport_stats;
+
+    // Fake certificates to report.
+    talk_base::FakeSSLIdentity local_identity(local_cert);
+    talk_base::scoped_ptr<talk_base::FakeSSLCertificate> remote_cert_copy(
+        remote_cert.GetReference());
+
+    // Fake transport object.
+    talk_base::scoped_ptr<cricket::FakeTransport> transport(
+        new cricket::FakeTransport(
+            session_.signaling_thread(),
+            session_.worker_thread(),
+            transport_stats.content_name));
+    transport->SetIdentity(&local_identity);
+    cricket::FakeTransportChannel* channel =
+        static_cast<cricket::FakeTransportChannel*>(
+            transport->CreateChannel(channel_stats.component));
+    EXPECT_FALSE(channel == NULL);
+    channel->SetRemoteCertificate(remote_cert_copy.get());
+
+    // Configure MockWebRtcSession
+    EXPECT_CALL(session_, GetTransport(transport_stats.content_name))
+      .WillOnce(Return(transport.get()));
+    EXPECT_CALL(session_, GetStats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(session_stats),
+                      Return(true)));
+    EXPECT_CALL(session_, video_channel())
+      .WillRepeatedly(ReturnNull());
+
+    stats.UpdateStats();
+
+    stats.GetStats(NULL, &reports);
+
+    const webrtc::StatsReport* channel_report = FindNthReportByType(
+        reports, webrtc::StatsReport::kStatsReportTypeComponent, 1);
+    EXPECT_TRUE(channel_report != NULL);
+
+    // Check local certificate chain.
+    std::string local_certificate_id = ExtractStatsValue(
+        webrtc::StatsReport::kStatsReportTypeComponent,
+        reports,
+        webrtc::StatsReport::kStatsValueNameLocalCertificateId);
+    EXPECT_NE(kNotFound, local_certificate_id);
+    CheckCertChainReports(reports, local_ders, 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);
+  }
   cricket::FakeMediaEngine* media_engine_;
   talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
   MockWebRtcSession session_;
@@ -439,4 +558,142 @@
   ASSERT_FALSE(transport_report == NULL);
 }
 
+// This test verifies that all chained certificates are correctly
+// reported
+TEST_F(StatsCollectorTest, DISABLED_ChainedCertificateReportsCreated) {
+  // Build local certificate chain.
+  std::vector<std::string> local_ders(5);
+  local_ders[0] = "These";
+  local_ders[1] = "are";
+  local_ders[2] = "some";
+  local_ders[3] = "der";
+  local_ders[4] = "values";
+  talk_base::FakeSSLCertificate local_cert(DersToPems(local_ders));
+
+  // Build remote certificate chain
+  std::vector<std::string> remote_ders(4);
+  remote_ders[0] = "A";
+  remote_ders[1] = "non-";
+  remote_ders[2] = "intersecting";
+  remote_ders[3] = "set";
+  talk_base::FakeSSLCertificate remote_cert(DersToPems(remote_ders));
+
+  TestCertificateReports(local_cert, local_ders, remote_cert, remote_ders);
+}
+
+// This test verifies that all certificates without chains are correctly
+// reported.
+TEST_F(StatsCollectorTest, DISABLED_ChainlessCertificateReportsCreated) {
+  // Build local certificate.
+  std::string local_der = "This is the local der.";
+  talk_base::FakeSSLCertificate local_cert(DerToPem(local_der));
+
+  // Build remote certificate.
+  std::string remote_der = "This is somebody else's der.";
+  talk_base::FakeSSLCertificate remote_cert(DerToPem(remote_der));
+
+  TestCertificateReports(local_cert, std::vector<std::string>(1, local_der),
+                         remote_cert, std::vector<std::string>(1, remote_der));
+}
+
+// This test verifies that the stats are generated correctly when no
+// transport is present.
+TEST_F(StatsCollectorTest, DISABLED_NoTransport) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  webrtc::StatsReports reports;  // returned values.
+  stats.set_session(&session_);
+
+  // Fake stats to process.
+  cricket::TransportChannelStats channel_stats;
+  channel_stats.component = 1;
+
+  cricket::TransportStats transport_stats;
+  transport_stats.content_name = "audio";
+  transport_stats.channel_stats.push_back(channel_stats);
+
+  cricket::SessionStats session_stats;
+  session_stats.transport_stats[transport_stats.content_name] =
+      transport_stats;
+
+  // Configure MockWebRtcSession
+  EXPECT_CALL(session_, GetTransport(transport_stats.content_name))
+    .WillOnce(ReturnNull());
+  EXPECT_CALL(session_, GetStats(_))
+    .WillOnce(DoAll(SetArgPointee<0>(session_stats),
+                    Return(true)));
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(ReturnNull());
+
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+
+  // Check that the local certificate is absent.
+  std::string local_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameLocalCertificateId);
+  ASSERT_EQ(kNotFound, local_certificate_id);
+
+  // Check that the remote certificate is absent.
+  std::string remote_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameRemoteCertificateId);
+  ASSERT_EQ(kNotFound, remote_certificate_id);
+}
+
+// This test verifies that the stats are generated correctly when the transport
+// does not have any certificates.
+TEST_F(StatsCollectorTest, DISABLED_NoCertificates) {
+  webrtc::StatsCollector stats;  // Implementation under test.
+  webrtc::StatsReports reports;  // returned values.
+  stats.set_session(&session_);
+
+  // Fake stats to process.
+  cricket::TransportChannelStats channel_stats;
+  channel_stats.component = 1;
+
+  cricket::TransportStats transport_stats;
+  transport_stats.content_name = "audio";
+  transport_stats.channel_stats.push_back(channel_stats);
+
+  cricket::SessionStats session_stats;
+  session_stats.transport_stats[transport_stats.content_name] =
+      transport_stats;
+
+  // Fake transport object.
+  talk_base::scoped_ptr<cricket::FakeTransport> transport(
+      new cricket::FakeTransport(
+          session_.signaling_thread(),
+          session_.worker_thread(),
+          transport_stats.content_name));
+
+  // Configure MockWebRtcSession
+  EXPECT_CALL(session_, GetTransport(transport_stats.content_name))
+    .WillOnce(Return(transport.get()));
+  EXPECT_CALL(session_, GetStats(_))
+    .WillOnce(DoAll(SetArgPointee<0>(session_stats),
+                    Return(true)));
+  EXPECT_CALL(session_, video_channel())
+    .WillRepeatedly(ReturnNull());
+
+  stats.UpdateStats();
+  stats.GetStats(NULL, &reports);
+
+  // Check that the local certificate is absent.
+  std::string local_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameLocalCertificateId);
+  ASSERT_EQ(kNotFound, local_certificate_id);
+
+  // Check that the remote certificate is absent.
+  std::string remote_certificate_id = ExtractStatsValue(
+      webrtc::StatsReport::kStatsReportTypeComponent,
+      reports,
+      webrtc::StatsReport::kStatsValueNameRemoteCertificateId);
+  ASSERT_EQ(kNotFound, remote_certificate_id);
+}
+
+
 }  // namespace
diff --git a/app/webrtc/statstypes.h b/app/webrtc/statstypes.h
index 30a8b84..fe36885 100644
--- a/app/webrtc/statstypes.h
+++ b/app/webrtc/statstypes.h
@@ -99,6 +99,14 @@
   // The id of StatsReport of type VideoBWE.
   static const char kStatsReportVideoBweId[];
 
+  // A StatsReport of |type| = "googCertificate" contains an SSL certificate
+  // transmitted by one of the endpoints of this connection.  The |id| is
+  // controlled by the fingerprint, and is used to identify the certificate in
+  // the Channel stats (as "googLocalCertificateId" or
+  // "googRemoteCertificateId") and in any child certificates (as
+  // "googIssuerId").
+  static const char kStatsReportTypeCertificate[];
+
   // StatsValue names
   static const char kStatsValueNameAudioOutputLevel[];
   static const char kStatsValueNameAudioInputLevel[];
@@ -152,6 +160,11 @@
   static const char kStatsValueNameTrackId[];
   static const char kStatsValueNameSsrc[];
   static const char kStatsValueNameTypingNoiseState[];
+  static const char kStatsValueNameDer[];
+  static const char kStatsValueNameFingerprint[];
+  static const char kStatsValueNameIssuerId[];
+  static const char kStatsValueNameLocalCertificateId[];
+  static const char kStatsValueNameRemoteCertificateId[];
 };
 
 typedef std::vector<StatsReport> StatsReports;
diff --git a/app/webrtc/webrtcsdp.cc b/app/webrtc/webrtcsdp.cc
index 60c427d..ae17117 100644
--- a/app/webrtc/webrtcsdp.cc
+++ b/app/webrtc/webrtcsdp.cc
@@ -145,6 +145,7 @@
 static const char kAttributeSetup[] = "setup";
 static const char kAttributeFmtp[] = "fmtp";
 static const char kAttributeRtpmap[] = "rtpmap";
+static const char kAttributeSctpmap[] = "sctpmap";
 static const char kAttributeRtcp[] = "rtcp";
 static const char kAttributeIceUfrag[] = "ice-ufrag";
 static const char kAttributeIcePwd[] = "ice-pwd";
@@ -210,7 +211,7 @@
 static const int kIsacSwbDefaultRate = 56000;  // From acm_common_defs.h
 
 static const int kDefaultSctpFmt = 5000;
-static const char kDefaultSctpFmtProtocol[] = "webrtc-datachannel";
+static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel";
 
 struct SsrcInfo {
   SsrcInfo()
@@ -1268,10 +1269,14 @@
 }
 
 void BuildSctpContentAttributes(std::string* message) {
-  cricket::DataCodec sctp_codec(kDefaultSctpFmt, kDefaultSctpFmtProtocol, 0);
-  sctp_codec.SetParam(kCodecParamSctpProtocol, kDefaultSctpFmtProtocol);
-  sctp_codec.SetParam(kCodecParamSctpStreams, cricket::kMaxSctpSid + 1);
-  AddFmtpLine(sctp_codec, message);
+  // draft-ietf-mmusic-sctp-sdp-04
+  // a=sctpmap:sctpmap-number  protocol  [streams]
+  std::ostringstream os;
+  InitAttrLine(kAttributeSctpmap, &os);
+  os << kSdpDelimiterColon << kDefaultSctpFmt << kSdpDelimiterSpace
+     << kDefaultSctpmapProtocol << kSdpDelimiterSpace
+     << (cricket::kMaxSctpSid + 1);
+  AddLine(os.str(), message);
 }
 
 void BuildRtpContentAttributes(
@@ -2111,10 +2116,25 @@
           codec_preference,
           static_cast<AudioContentDescription*>(content.get()));
     } else if (HasAttribute(line, kMediaTypeData)) {
-      content.reset(ParseContentDescription<DataContentDescription>(
+      DataContentDescription* desc =
+          ParseContentDescription<DataContentDescription>(
                     message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
                     codec_preference, pos, &content_name,
-                    &transport, candidates, error));
+                    &transport, candidates, error);
+
+      if (protocol == cricket::kMediaProtocolDtlsSctp) {
+        // Add the SCTP Port number as a pseudo-codec "port" parameter
+        cricket::DataCodec codec_port(
+            cricket::kGoogleSctpDataCodecId, cricket::kGoogleSctpDataCodecName,
+            0);
+        codec_port.SetParam(cricket::kCodecParamPort, fields[3]);
+        LOG(INFO) << "ParseMediaDescription: Got SCTP Port Number "
+                  << fields[3];
+        desc->AddCodec(codec_port);
+      }
+
+      content.reset(desc);
+
       // We should always use the default bandwidth for RTP-based data
       // channels.  Don't allow SDP to set the bandwidth, because that
       // would give JS the opportunity to "break the Internet".
diff --git a/app/webrtc/webrtcsdp_unittest.cc b/app/webrtc/webrtcsdp_unittest.cc
index b1505aa..6f2e388 100644
--- a/app/webrtc/webrtcsdp_unittest.cc
+++ b/app/webrtc/webrtcsdp_unittest.cc
@@ -76,6 +76,7 @@
 typedef std::vector<AudioCodec> AudioCodecs;
 typedef std::vector<Candidate> Candidates;
 
+static const uint32 kDefaultSctpPort = 5000;
 static const char kSessionTime[] = "t=0 0\r\n";
 static const uint32 kCandidatePriority = 2130706432U;  // pref = 1.0
 static const char kCandidateUfragVoice[] = "ufrag_voice";
@@ -281,7 +282,7 @@
     "a=ice-ufrag:ufrag_data\r\n"
     "a=ice-pwd:pwd_data\r\n"
     "a=mid:data_content_name\r\n"
-    "a=fmtp:5000 protocol=webrtc-datachannel; streams=65536\r\n";
+    "a=sctpmap:5000 webrtc-datachannel 65536\r\n";
 
 static const char kSdpSctpDataChannelWithCandidatesString[] =
     "m=application 2345 DTLS/SCTP 5000\r\n"
@@ -296,7 +297,7 @@
     "a=ice-ufrag:ufrag_data\r\n"
     "a=ice-pwd:pwd_data\r\n"
     "a=mid:data_content_name\r\n"
-    "a=fmtp:5000 protocol=webrtc-datachannel; streams=65536\r\n";
+    "a=sctpmap:5000 webrtc-datachannel 65536\r\n";
 
 
 // One candidate reference string as per W3c spec.
@@ -974,6 +975,10 @@
         new DataContentDescription());
     data_desc_ = data.get();
     data_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp);
+    DataCodec codec(cricket::kGoogleSctpDataCodecId,
+                    cricket::kGoogleSctpDataCodecName, 0);
+    codec.SetParam(cricket::kCodecParamPort, kDefaultSctpPort);
+    data_desc_->AddCodec(codec);
     desc_.AddContent(kDataContentName, NS_JINGLE_DRAFT_SCTP, data.release());
     EXPECT_TRUE(desc_.AddTransportInfo(
            TransportInfo(kDataContentName,
@@ -1761,6 +1766,41 @@
   EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
 }
 
+TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndNewPort) {
+  AddSctpDataChannel();
+  const uint16 kUnusualSctpPort = 9556;
+  char default_portstr[16];
+  char unusual_portstr[16];
+  talk_base::sprintfn(default_portstr, sizeof(default_portstr), "%d",
+                      kDefaultSctpPort);
+  talk_base::sprintfn(unusual_portstr, sizeof(unusual_portstr), "%d",
+                      kUnusualSctpPort);
+
+  JsepSessionDescription jdesc(kDummyString);
+  // take our pre-built session description and change the SCTP port.
+  cricket::SessionDescription* mutant = desc_.Copy();
+  DataContentDescription* dcdesc = static_cast<DataContentDescription*>(
+      mutant->GetContentDescriptionByName(kDataContentName));
+  std::vector<cricket::DataCodec> codecs(dcdesc->codecs());
+  EXPECT_EQ(codecs.size(), 1UL);
+  EXPECT_EQ(codecs[0].id, cricket::kGoogleSctpDataCodecId);
+  codecs[0].SetParam(cricket::kCodecParamPort, kUnusualSctpPort);
+
+  // note: mutant's owned by jdesc now.
+  ASSERT_TRUE(jdesc.Initialize(mutant, kSessionId, kSessionVersion));
+  mutant = NULL;
+
+  std::string sdp_with_data = kSdpString;
+  sdp_with_data.append(kSdpSctpDataChannelString);
+  talk_base::replace_substrs(default_portstr, strlen(default_portstr),
+                             unusual_portstr, strlen(unusual_portstr),
+                             &sdp_with_data);
+  JsepSessionDescription jdesc_output(kDummyString);
+
+  EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output));
+  EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
+}
+
 TEST_F(WebRtcSdpTest, DeserializeSdpWithRtpDataChannelsAndBandwidth) {
   AddRtpDataChannel();
   JsepSessionDescription jdesc(kDummyString);
diff --git a/app/webrtc/webrtcsession.cc b/app/webrtc/webrtcsession.cc
index ff331a9..04cbe02 100644
--- a/app/webrtc/webrtcsession.cc
+++ b/app/webrtc/webrtcsession.cc
@@ -927,9 +927,43 @@
   return &SignalVoiceChannelDestroyed;
 }
 
+bool WebRtcSession::SendData(const cricket::SendDataParams& params,
+                             const talk_base::Buffer& payload,
+                             cricket::SendDataResult* result) {
+  if (!data_channel_.get()) {
+    LOG(LS_ERROR) << "SendData called when data_channel_ is NULL.";
+    return false;
+  }
+  return data_channel_->SendData(params, payload, result);
+}
+
+bool WebRtcSession::ConnectDataChannel(DataChannel* webrtc_data_channel) {
+  if (!data_channel_.get()) {
+    LOG(LS_ERROR) << "ConnectDataChannel called when data_channel_ is NULL.";
+    return false;
+  }
+
+  data_channel_->SignalReadyToSendData.connect(webrtc_data_channel,
+                                               &DataChannel::OnChannelReady);
+  data_channel_->SignalDataReceived.connect(webrtc_data_channel,
+                                            &DataChannel::OnDataReceived);
+  cricket::StreamParams params =
+      cricket::StreamParams::CreateLegacy(webrtc_data_channel->id());
+  data_channel_->AddRecvStream(params);
+  data_channel_->AddSendStream(params);
+  return true;
+}
+
+void WebRtcSession::DisconnectDataChannel(DataChannel* webrtc_data_channel) {
+  data_channel_->RemoveSendStream(webrtc_data_channel->id());
+  data_channel_->RemoveRecvStream(webrtc_data_channel->id());
+  data_channel_->SignalReadyToSendData.disconnect(webrtc_data_channel);
+  data_channel_->SignalDataReceived.disconnect(webrtc_data_channel);
+}
+
 talk_base::scoped_refptr<DataChannel> WebRtcSession::CreateDataChannel(
-      const std::string& label,
-      const DataChannelInit* config) {
+    const std::string& label,
+    const DataChannelInit* config) {
   if (state() == STATE_RECEIVEDTERMINATE) {
     return NULL;
   }
@@ -953,7 +987,7 @@
   }
 
   talk_base::scoped_refptr<DataChannel> channel(
-      DataChannel::Create(this, label, &new_config));
+      DataChannel::Create(this, data_channel_type_, label, &new_config));
   if (channel == NULL)
     return NULL;
   if (!mediastream_signaling_->AddDataChannel(channel))
diff --git a/app/webrtc/webrtcsession.h b/app/webrtc/webrtcsession.h
index eee579d..e461028 100644
--- a/app/webrtc/webrtcsession.h
+++ b/app/webrtc/webrtcsession.h
@@ -99,7 +99,8 @@
                       public AudioProviderInterface,
                       public DataChannelFactory,
                       public VideoProviderInterface,
-                      public DtmfProviderInterface {
+                      public DtmfProviderInterface,
+                      public DataChannelProviderInterface {
  public:
   WebRtcSession(cricket::ChannelManager* channel_manager,
                 talk_base::Thread* signaling_thread,
@@ -181,6 +182,14 @@
                           int code, int duration);
   virtual sigslot::signal0<>* GetOnDestroyedSignal();
 
+  // Implements DataChannelProviderInterface.
+  virtual bool SendData(const cricket::SendDataParams& params,
+                        const talk_base::Buffer& payload,
+                        cricket::SendDataResult* result) OVERRIDE;
+  virtual bool ConnectDataChannel(DataChannel* webrtc_data_channel) OVERRIDE;
+  virtual void DisconnectDataChannel(DataChannel* webrtc_data_channel) OVERRIDE;
+
+
   talk_base::scoped_refptr<DataChannel> CreateDataChannel(
       const std::string& label,
       const DataChannelInit* config);
diff --git a/base/fakesslidentity.h b/base/fakesslidentity.h
index f3c44e4..5efa268 100644
--- a/base/fakesslidentity.h
+++ b/base/fakesslidentity.h
@@ -28,6 +28,9 @@
 #ifndef TALK_BASE_FAKESSLIDENTITY_H_
 #define TALK_BASE_FAKESSLIDENTITY_H_
 
+#include <algorithm>
+#include <vector>
+
 #include "talk/base/messagedigest.h"
 #include "talk/base/sslidentity.h"
 
@@ -36,12 +39,25 @@
 class FakeSSLCertificate : public talk_base::SSLCertificate {
  public:
   explicit FakeSSLCertificate(const std::string& data) : data_(data) {}
+  explicit FakeSSLCertificate(const std::vector<std::string>& certs)
+      : data_(certs.front()) {
+    std::vector<std::string>::const_iterator it;
+    // Skip certs[0].
+    for (it = certs.begin() + 1; it != certs.end(); ++it) {
+      certs_.push_back(FakeSSLCertificate(*it));
+    }
+  }
   virtual FakeSSLCertificate* GetReference() const {
     return new FakeSSLCertificate(*this);
   }
   virtual std::string ToPEMString() const {
     return data_;
   }
+  virtual void ToDER(Buffer* der_buffer) const {
+    std::string der_string;
+    VERIFY(SSLIdentity::PemToDer(kPemTypeCertificate, data_, &der_string));
+    der_buffer->SetData(der_string.c_str(), der_string.size());
+  }
   virtual bool ComputeDigest(const std::string &algorithm,
                              unsigned char *digest, std::size_t size,
                              std::size_t *length) const {
@@ -49,13 +65,27 @@
                                        digest, size);
     return (*length != 0);
   }
+  virtual bool GetChain(SSLCertChain** chain) const {
+    if (certs_.empty())
+      return false;
+    std::vector<SSLCertificate*> new_certs(certs_.size());
+    std::transform(certs_.begin(), certs_.end(), new_certs.begin(), DupCert);
+    *chain = new SSLCertChain(new_certs);
+    return true;
+  }
+
  private:
+  static FakeSSLCertificate* DupCert(FakeSSLCertificate cert) {
+    return cert.GetReference();
+  }
   std::string data_;
+  std::vector<FakeSSLCertificate> certs_;
 };
 
 class FakeSSLIdentity : public talk_base::SSLIdentity {
  public:
   explicit FakeSSLIdentity(const std::string& data) : cert_(data) {}
+  explicit FakeSSLIdentity(const FakeSSLCertificate& cert) : cert_(cert) {}
   virtual FakeSSLIdentity* GetReference() const {
     return new FakeSSLIdentity(*this);
   }
diff --git a/base/latebindingsymboltable.h b/base/latebindingsymboltable.h
index a53648b..f4ad5a6 100644
--- a/base/latebindingsymboltable.h
+++ b/base/latebindingsymboltable.h
@@ -67,6 +67,9 @@
   bool LoadFromPath(const char *dll_path);
   void Unload();
 
+  // Gets the raw OS handle to the DLL. Be careful what you do with it.
+  DllHandle GetDllHandle() const { return handle_; }
+
  private:
   void ClearSymbols();
 
diff --git a/base/logging.cc b/base/logging.cc
index 6653d34..4c7eae1 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -123,8 +123,6 @@
                        LogErrorContext err_ctx, int err, const char* module)
     : severity_(sev),
       warn_slow_logs_delay_(WARN_SLOW_LOGS_DELAY) {
-  // Android's logging facility keeps track of timestamp and thread.
-#ifndef ANDROID
   if (timestamp_) {
     uint32 time = TimeSince(LogStartTime());
     // Also ensure WallClockStartTime is initialized, so that it matches
@@ -141,7 +139,6 @@
     print_stream_ << "[" << std::hex << id << std::dec << "] ";
 #endif  // WIN32
   }
-#endif  // !ANDROID
 
   if (severity_ >= ctx_sev_) {
     print_stream_ << Describe(sev) << "(" << DescribeFile(file)
diff --git a/base/logging.h b/base/logging.h
index 2f341fa..b563302 100644
--- a/base/logging.h
+++ b/base/logging.h
@@ -41,6 +41,8 @@
 // LOG_V(sev) Like LOG(), but sev is a run-time variable of the LoggingSeverity
 //     type (basically, it just doesn't prepend the namespace).
 // LOG_F(sev) Like LOG(), but includes the name of the current function.
+// LOG_T(sev) Like LOG(), but includes the this pointer.
+// LOG_T_F(sev) Like LOG_F(), but includes the this pointer.
 // LOG_GLE(M)(sev [, mod]) attempt to add a string description of the
 //     HRESULT returned by GetLastError.  The "M" variant allows searching of a
 //     DLL's string table for the error description.
@@ -328,6 +330,9 @@
                           talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
         .stream()
 
+#define LOG_T(sev) LOG(sev) << this << ": "
+#define LOG_T_F(level) LOG_F(level) << this << ": "
+
 #else  // !LOGGING
 
 // Hopefully, the compiler will optimize away some of this code.
@@ -348,6 +353,8 @@
                           talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
       .stream()
 
+#define LOG_T(sev) LOG(sev) << this << ": "
+#define LOG_T_F(level) LOG_F(level) << this << " "
 #endif  // !LOGGING
 
 #define LOG_ERRNO_EX(sev, err) \
diff --git a/base/md5digest_unittest.cc b/base/md5digest_unittest.cc
index 40b19e5..9232b40 100644
--- a/base/md5digest_unittest.cc
+++ b/base/md5digest_unittest.cc
@@ -38,7 +38,7 @@
 
 TEST(Md5DigestTest, TestSize) {
   Md5Digest md5;
-  EXPECT_EQ(16U, Md5Digest::kSize);
+  EXPECT_EQ(16, static_cast<int>(Md5Digest::kSize));
   EXPECT_EQ(16U, md5.Size());
 }
 
diff --git a/base/network_unittest.cc b/base/network_unittest.cc
index d12a1eb..ce19120 100644
--- a/base/network_unittest.cc
+++ b/base/network_unittest.cc
@@ -1,6 +1,6 @@
 /*
  * libjingle
- * Copyright 2004--2011, Google Inc.
+ * Copyright 2004 Google Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -37,6 +37,9 @@
 #endif
 #endif
 #include "talk/base/gunit.h"
+#ifdef WIN32
+#include "talk/base/logging.h"  // For LOG_GLE
+#endif
 
 namespace talk_base {
 
@@ -130,7 +133,7 @@
     IPAddress ip = (*it)->ip();
     SocketAddress bindaddress(ip, 0);
     bindaddress.SetScopeID((*it)->scope_id());
-    // TODO: Make this use talk_base::AsyncSocket once it supports IPv6.
+    // TODO(thaloun): Use talk_base::AsyncSocket once it supports IPv6.
     int fd = static_cast<int>(socket(ip.family(), SOCK_STREAM, IPPROTO_TCP));
     if (fd > 0) {
       size_t ipsize = bindaddress.ToSockAddrStorage(&storage);
@@ -138,6 +141,9 @@
       int success = ::bind(fd,
                            reinterpret_cast<sockaddr*>(&storage),
                            static_cast<int>(ipsize));
+#ifdef WIN32
+      if (success) LOG_GLE(LS_ERROR) << "Socket bind failed.";
+#endif
       EXPECT_EQ(0, success);
 #ifdef WIN32
       closesocket(fd);
@@ -486,7 +492,7 @@
   NetworkManager::NetworkList list;
 #ifndef WIN32
   // There should be at least one IPv6 network (fe80::/64 should be in there).
-  // TODO: Disabling this test on windows for the moment as the test
+  // TODO(thaloun): Disabling this test on windows for the moment as the test
   // machines don't seem to have IPv6 installed on them at all.
   manager.set_ipv6_enabled(true);
   list = GetNetworks(manager, true);
@@ -534,5 +540,49 @@
 }
 #endif  // defined(POSIX)
 
+#if defined(LINUX)
+// If you want to test non-default routes, you can do the following on a linux
+// machine:
+// 1) Load the dummy network driver:
+// sudo modprobe dummy
+// sudo ifconfig dummy0 127.0.0.1
+// 2) Run this test and confirm the output says it found a dummy route (and
+// passes).
+// 3) When done:
+// sudo rmmmod dummy
+TEST_F(NetworkTest, TestIgnoreNonDefaultRoutes) {
+  BasicNetworkManager manager;
+  NetworkManager::NetworkList list;
+  list = GetNetworks(manager, false);
+  bool found_dummy = false;
+  LOG(LS_INFO) << "Looking for dummy network: ";
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    LOG(LS_INFO) << "  Network name: " << (*it)->name();
+    found_dummy |= (*it)->name().find("dummy0") != std::string::npos;
+  }
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    delete (*it);
+  }
+  if (!found_dummy) {
+    LOG(LS_INFO) << "No dummy found, quitting.";
+    return;
+  }
+  LOG(LS_INFO) << "Found dummy, running again while ignoring non-default "
+               << "routes.";
+  manager.set_ignore_non_default_routes(true);
+  list = GetNetworks(manager, false);
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    LOG(LS_INFO) << "  Network name: " << (*it)->name();
+    EXPECT_TRUE((*it)->name().find("dummy0") == std::string::npos);
+  }
+  for (NetworkManager::NetworkList::iterator it = list.begin();
+       it != list.end(); ++it) {
+    delete (*it);
+  }
+}
+#endif
 
 }  // namespace talk_base
diff --git a/base/nssidentity.cc b/base/nssidentity.cc
index c660aee..96bfcc3 100644
--- a/base/nssidentity.cc
+++ b/base/nssidentity.cc
@@ -26,6 +26,10 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <algorithm>
+#include <string>
+#include <vector>
+
 #if HAVE_CONFIG_H
 #include "config.h"
 #endif  // HAVE_CONFIG_H
@@ -34,8 +38,6 @@
 
 #include "talk/base/nssidentity.h"
 
-#include <string>
-
 #include "cert.h"
 #include "cryptohi.h"
 #include "keyhi.h"
@@ -90,6 +92,43 @@
   return new NSSKeyPair(privkey, pubkey);
 }
 
+NSSCertificate::NSSCertificate(CERTCertificate* cert)
+    : certificate_(CERT_DupCertificate(cert)) {
+  ASSERT(certificate_ != NULL);
+}
+
+static void DeleteCert(SSLCertificate* cert) {
+  delete cert;
+}
+
+NSSCertificate::NSSCertificate(CERTCertList* cert_list) {
+  // Copy the first cert into certificate_.
+  CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+  certificate_ = CERT_DupCertificate(node->cert);
+
+  // Put any remaining certificates into the chain.
+  node = CERT_LIST_NEXT(node);
+  std::vector<SSLCertificate*> certs;
+  for (; !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) {
+    certs.push_back(new NSSCertificate(node->cert));
+  }
+
+  if (!certs.empty())
+    chain_.reset(new SSLCertChain(certs));
+
+  // The SSLCertChain constructor copies its input, so now we have to delete
+  // the originals.
+  std::for_each(certs.begin(), certs.end(), DeleteCert);
+}
+
+NSSCertificate::NSSCertificate(CERTCertificate* cert, SSLCertChain* chain)
+    : certificate_(CERT_DupCertificate(cert)) {
+  ASSERT(certificate_ != NULL);
+  if (chain)
+    chain_.reset(chain->Copy());
+}
+
+
 NSSCertificate *NSSCertificate::FromPEMString(const std::string &pem_string) {
   std::string der;
   if (!SSLIdentity::PemToDer(kPemTypeCertificate, pem_string, &der))
@@ -105,15 +144,13 @@
   if (!cert)
     return NULL;
 
-  return new NSSCertificate(cert);
+  NSSCertificate* ret = new NSSCertificate(cert);
+  CERT_DestroyCertificate(cert);
+  return ret;
 }
 
 NSSCertificate *NSSCertificate::GetReference() const {
-  CERTCertificate *certificate = CERT_DupCertificate(certificate_);
-  if (!certificate)
-    return NULL;
-
-  return new NSSCertificate(certificate);
+  return new NSSCertificate(certificate_, chain_.get());
 }
 
 std::string NSSCertificate::ToPEMString() const {
@@ -122,6 +159,10 @@
                                certificate_->derCert.len);
 }
 
+void NSSCertificate::ToDER(Buffer* der_buffer) const {
+  der_buffer->SetData(certificate_->derCert.data, certificate_->derCert.len);
+}
+
 bool NSSCertificate::GetDigestLength(const std::string &algorithm,
                                      std::size_t *length) {
   const SECHashObject *ho;
@@ -156,6 +197,14 @@
   return true;
 }
 
+bool NSSCertificate::GetChain(SSLCertChain** chain) const {
+  if (!chain_)
+    return false;
+
+  *chain = chain_->Copy();
+  return true;
+}
+
 bool NSSCertificate::Equals(const NSSCertificate *tocompare) const {
   if (!certificate_->derCert.len)
     return false;
@@ -301,9 +350,9 @@
 
  fail:
   delete keypair;
-  CERT_DestroyCertificate(certificate);
 
  done:
+  if (certificate) CERT_DestroyCertificate(certificate);
   if (subject_name) CERT_DestroyName(subject_name);
   if (spki) SECKEY_DestroySubjectPublicKeyInfo(spki);
   if (certreq) CERT_DestroyCertificateRequest(certreq);
diff --git a/base/nssidentity.h b/base/nssidentity.h
index 725c546..f4bfc8b 100644
--- a/base/nssidentity.h
+++ b/base/nssidentity.h
@@ -66,7 +66,10 @@
 class NSSCertificate : public SSLCertificate {
  public:
   static NSSCertificate* FromPEMString(const std::string& pem_string);
-  explicit NSSCertificate(CERTCertificate* cert) : certificate_(cert) {}
+  // The caller retains ownership of the argument to all the constructors,
+  // and the constructor makes a copy.
+  explicit NSSCertificate(CERTCertificate* cert);
+  explicit NSSCertificate(CERTCertList* cert_list);
   virtual ~NSSCertificate() {
     if (certificate_)
       CERT_DestroyCertificate(certificate_);
@@ -76,24 +79,30 @@
 
   virtual std::string ToPEMString() const;
 
+  virtual void ToDER(Buffer* der_buffer) const;
+
   virtual bool ComputeDigest(const std::string& algorithm,
                              unsigned char* digest, std::size_t size,
                              std::size_t* length) const;
 
+  virtual bool GetChain(SSLCertChain** chain) const;
+
   CERTCertificate* certificate() { return certificate_; }
 
   // Helper function to get the length of a digest
   static bool GetDigestLength(const std::string& algorithm,
                               std::size_t* length);
 
-  // Comparison
+  // Comparison.  Only the certificate itself is considered, not the chain.
   bool Equals(const NSSCertificate* tocompare) const;
 
  private:
+  NSSCertificate(CERTCertificate* cert, SSLCertChain* chain);
   static bool GetDigestObject(const std::string& algorithm,
                               const SECHashObject** hash_object);
 
   CERTCertificate* certificate_;
+  scoped_ptr<SSLCertChain> chain_;
 
   DISALLOW_EVIL_CONSTRUCTORS(NSSCertificate);
 };
diff --git a/base/nssstreamadapter.cc b/base/nssstreamadapter.cc
index c9a540d..185c243 100644
--- a/base/nssstreamadapter.cc
+++ b/base/nssstreamadapter.cc
@@ -821,6 +821,13 @@
 
   if (ok) {
     stream->cert_ok_ = true;
+
+    // Record the peer's certificate chain.
+    CERTCertList* cert_list = SSL_PeerCertificateChain(fd);
+    ASSERT(cert_list != NULL);
+
+    stream->peer_certificate_.reset(new NSSCertificate(cert_list));
+    CERT_DestroyCertList(cert_list);
     return SECSuccess;
   }
 
diff --git a/base/openssladapter.cc b/base/openssladapter.cc
index 8e33a03..af92f0c 100644
--- a/base/openssladapter.cc
+++ b/base/openssladapter.cc
@@ -234,7 +234,7 @@
   if (!InitializeSSLThread() || !SSL_library_init())
       return false;
 #if !defined(ADDRESS_SANITIZER) || !defined(OSX)
-  // Loading the error strings crashed mac_asan. Omit this debugging aid there.
+  // Loading the error strings crashes mac_asan.  Omit this debugging aid there.
   SSL_load_error_strings();
 #endif
   ERR_load_BIO_strings();
diff --git a/base/opensslidentity.cc b/base/opensslidentity.cc
index a48c94f..7408af1 100644
--- a/base/opensslidentity.cc
+++ b/base/opensslidentity.cc
@@ -40,6 +40,7 @@
 #include <openssl/rsa.h>
 #include <openssl/crypto.h>
 
+#include "talk/base/checks.h"
 #include "talk/base/helpers.h"
 #include "talk/base/logging.h"
 #include "talk/base/openssldigest.h"
@@ -211,7 +212,9 @@
 #ifdef _DEBUG
   PrintCert(x509);
 #endif
-  return new OpenSSLCertificate(x509);
+  OpenSSLCertificate* ret = new OpenSSLCertificate(x509);
+  X509_free(x509);
+  return ret;
 }
 
 OpenSSLCertificate* OpenSSLCertificate::FromPEMString(
@@ -224,10 +227,12 @@
   X509 *x509 = PEM_read_bio_X509(bio, NULL, NULL,
                                  const_cast<char*>("\0"));
   BIO_free(bio);
-  if (x509)
-    return new OpenSSLCertificate(x509);
-  else
+  if (!x509)
     return NULL;
+
+  OpenSSLCertificate* ret = new OpenSSLCertificate(x509);
+  X509_free(x509);
+  return ret;
 }
 
 bool OpenSSLCertificate::ComputeDigest(const std::string &algorithm,
@@ -264,11 +269,14 @@
 
 std::string OpenSSLCertificate::ToPEMString() const {
   BIO* bio = BIO_new(BIO_s_mem());
-  if (!bio)
-    return NULL;
+  if (!bio) {
+    UNREACHABLE();
+    return std::string();
+  }
   if (!PEM_write_bio_X509(bio, x509_)) {
     BIO_free(bio);
-    return NULL;
+    UNREACHABLE();
+    return std::string();
   }
   BIO_write(bio, "\0", 1);
   char* buffer;
@@ -278,7 +286,29 @@
   return ret;
 }
 
+void OpenSSLCertificate::ToDER(Buffer* der_buffer) const {
+  // In case of failure, make sure to leave the buffer empty.
+  der_buffer->SetData(NULL, 0);
+
+  // Calculates the DER representation of the certificate, from scratch.
+  BIO* bio = BIO_new(BIO_s_mem());
+  if (!bio) {
+    UNREACHABLE();
+    return;
+  }
+  if (!i2d_X509_bio(bio, x509_)) {
+    BIO_free(bio);
+    UNREACHABLE();
+    return;
+  }
+  char* data;
+  size_t length = BIO_get_mem_data(bio, &data);
+  der_buffer->SetData(data, length);
+  BIO_free(bio);
+}
+
 void OpenSSLCertificate::AddReference() const {
+  ASSERT(x509_ != NULL);
   CRYPTO_add(&x509_->references, 1, CRYPTO_LOCK_X509);
 }
 
diff --git a/base/opensslidentity.h b/base/opensslidentity.h
index ca001b5..0d1bf73 100644
--- a/base/opensslidentity.h
+++ b/base/opensslidentity.h
@@ -25,8 +25,8 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef TALK_BASE_OPENSSLIDENTITY_H__
-#define TALK_BASE_OPENSSLIDENTITY_H__
+#ifndef TALK_BASE_OPENSSLIDENTITY_H_
+#define TALK_BASE_OPENSSLIDENTITY_H_
 
 #include <openssl/evp.h>
 #include <openssl/x509.h>
@@ -72,6 +72,11 @@
 // which is also reference counted inside the OpenSSL library.
 class OpenSSLCertificate : public SSLCertificate {
  public:
+  // Caller retains ownership of the X509 object.
+  explicit OpenSSLCertificate(X509* x509) : x509_(x509) {
+    AddReference();
+  }
+
   static OpenSSLCertificate* Generate(OpenSSLKeyPair* key_pair,
                                       const std::string& common_name);
   static OpenSSLCertificate* FromPEMString(const std::string& pem_string);
@@ -79,7 +84,6 @@
   virtual ~OpenSSLCertificate();
 
   virtual OpenSSLCertificate* GetReference() const {
-    AddReference();
     return new OpenSSLCertificate(x509_);
   }
 
@@ -87,6 +91,8 @@
 
   virtual std::string ToPEMString() const;
 
+  virtual void ToDER(Buffer* der_buffer) const;
+
   // Compute the digest of the certificate given algorithm
   virtual bool ComputeDigest(const std::string &algorithm,
                              unsigned char *digest, std::size_t size,
@@ -99,10 +105,14 @@
                             std::size_t size,
                             std::size_t *length);
 
- private:
-  explicit OpenSSLCertificate(X509* x509) : x509_(x509) {
-    ASSERT(x509_ != NULL);
+  virtual bool GetChain(SSLCertChain** chain) const {
+    // Chains are not yet supported when using OpenSSL.
+    // OpenSSLStreamAdapter::SSLVerifyCallback currently requires the remote
+    // certificate to be self-signed.
+    return false;
   }
+
+ private:
   void AddReference() const;
 
   X509* x509_;
@@ -148,4 +158,4 @@
 
 }  // namespace talk_base
 
-#endif  // TALK_BASE_OPENSSLIDENTITY_H__
+#endif  // TALK_BASE_OPENSSLIDENTITY_H_
diff --git a/base/opensslstreamadapter.cc b/base/opensslstreamadapter.cc
index 16021a9..034dfcf 100644
--- a/base/opensslstreamadapter.cc
+++ b/base/opensslstreamadapter.cc
@@ -217,6 +217,14 @@
   peer_certificate_.reset(static_cast<OpenSSLCertificate*>(cert));
 }
 
+bool OpenSSLStreamAdapter::GetPeerCertificate(SSLCertificate** cert) const {
+  if (!peer_certificate_)
+    return false;
+
+  *cert = peer_certificate_->GetReference();
+  return true;
+}
+
 bool OpenSSLStreamAdapter::SetPeerCertificateDigest(const std::string
                                                     &digest_alg,
                                                     const unsigned char*
@@ -857,6 +865,9 @@
           LOG(LS_INFO) <<
               "Accepted self-signed peer certificate authority";
           ok = 1;
+
+          // Record the peer's certificate.
+          stream->peer_certificate_.reset(new OpenSSLCertificate(cert));
         }
       }
     }
diff --git a/base/opensslstreamadapter.h b/base/opensslstreamadapter.h
index 8e92a10..3c47818 100644
--- a/base/opensslstreamadapter.h
+++ b/base/opensslstreamadapter.h
@@ -86,6 +86,8 @@
                                         const unsigned char* digest_val,
                                         size_t digest_len);
 
+  virtual bool GetPeerCertificate(SSLCertificate** cert) const;
+
   virtual int StartSSLWithServer(const char* server_name);
   virtual int StartSSLWithPeer();
   virtual void SetMode(SSLMode mode);
@@ -190,8 +192,8 @@
   // in traditional mode, the server name that the server's certificate
   // must specify. Empty in peer-to-peer mode.
   std::string ssl_server_name_;
-  // In peer-to-peer mode, the certificate that the peer must
-  // present. Empty in traditional mode.
+  // The certificate that the peer must present or did present. Initially
+  // null in traditional mode, until the connection is established.
   scoped_ptr<OpenSSLCertificate> peer_certificate_;
   // In peer-to-peer mode, the digest of the certificate that
   // the peer must present.
diff --git a/base/profiler.cc b/base/profiler.cc
index 9d0f32b..68bcfe4 100644
--- a/base/profiler.cc
+++ b/base/profiler.cc
@@ -130,12 +130,7 @@
   for (iterator it = events_.begin(); it != events_.end(); ++it) {
     if (event_prefix.empty() || it->first.find(event_prefix) == 0) {
       LogMessage(file, line, severity_to_use).stream()
-          << it->first << " count=" << it->second.event_count()
-          << " total=" << FormattedTime(it->second.total_time())
-          << " mean=" << FormattedTime(it->second.mean())
-          << " min=" << FormattedTime(it->second.minimum())
-          << " max=" << FormattedTime(it->second.maximum())
-          << " sd=" << it->second.standard_deviation();
+          << it->first << " " << it->second;
     }
   }
   LogMessage(file, line, severity_to_use).stream()
@@ -168,4 +163,15 @@
   return result;
 }
 
+std::ostream& operator<<(std::ostream& stream,
+                         const ProfilerEvent& profiler_event) {
+  stream << "count=" << profiler_event.event_count()
+         << " total=" << FormattedTime(profiler_event.total_time())
+         << " mean=" << FormattedTime(profiler_event.mean())
+         << " min=" << FormattedTime(profiler_event.minimum())
+         << " max=" << FormattedTime(profiler_event.maximum())
+         << " sd=" << profiler_event.standard_deviation();
+  return stream;
+}
+
 }  // namespace talk_base
diff --git a/base/profiler.h b/base/profiler.h
index 1198b8e..91ad6a5 100644
--- a/base/profiler.h
+++ b/base/profiler.h
@@ -164,6 +164,9 @@
   DISALLOW_COPY_AND_ASSIGN(ProfilerScope);
 };
 
+std::ostream& operator<<(std::ostream& stream,
+                         const ProfilerEvent& profiler_event);
+
 }  // namespace talk_base
 
 #endif  // TALK_BASE_PROFILER_H_
diff --git a/base/sha1digest_unittest.cc b/base/sha1digest_unittest.cc
index 5ab6819..d6c14b6 100644
--- a/base/sha1digest_unittest.cc
+++ b/base/sha1digest_unittest.cc
@@ -38,7 +38,7 @@
 
 TEST(Sha1DigestTest, TestSize) {
   Sha1Digest sha1;
-  EXPECT_EQ(20U, Sha1Digest::kSize);
+  EXPECT_EQ(20, static_cast<int>(Sha1Digest::kSize));
   EXPECT_EQ(20U, sha1.Size());
 }
 
diff --git a/base/sslfingerprint.h b/base/sslfingerprint.h
index 4d41156..b857789 100644
--- a/base/sslfingerprint.h
+++ b/base/sslfingerprint.h
@@ -47,9 +47,14 @@
       return NULL;
     }
 
+    return Create(algorithm, &(identity->certificate()));
+  }
+
+  static SSLFingerprint* Create(const std::string& algorithm,
+                                const talk_base::SSLCertificate* cert) {
     uint8 digest_val[64];
     size_t digest_len;
-    bool ret = identity->certificate().ComputeDigest(
+    bool ret = cert->ComputeDigest(
         algorithm, digest_val, sizeof(digest_val), &digest_len);
     if (!ret) {
       return NULL;
diff --git a/base/sslidentity.h b/base/sslidentity.h
index b9425f7..345691c 100644
--- a/base/sslidentity.h
+++ b/base/sslidentity.h
@@ -30,11 +30,18 @@
 #ifndef TALK_BASE_SSLIDENTITY_H_
 #define TALK_BASE_SSLIDENTITY_H_
 
+#include <algorithm>
 #include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
 #include "talk/base/messagedigest.h"
 
 namespace talk_base {
 
+// Forward declaration due to circular dependency with SSLCertificate.
+class SSLCertChain;
+
 // Abstract interface overridden by SSL library specific
 // implementations.
 
@@ -55,19 +62,72 @@
   virtual ~SSLCertificate() {}
 
   // Returns a new SSLCertificate object instance wrapping the same
-  // underlying certificate.
+  // underlying certificate, including its chain if present.
   // Caller is responsible for freeing the returned object.
   virtual SSLCertificate* GetReference() const = 0;
 
+  // Provides the cert chain, or returns false.  The caller owns the chain.
+  // The chain includes a copy of each certificate, excluding the leaf.
+  virtual bool GetChain(SSLCertChain** chain) const = 0;
+
   // Returns a PEM encoded string representation of the certificate.
   virtual std::string ToPEMString() const = 0;
 
+  // Provides a DER encoded binary representation of the certificate.
+  virtual void ToDER(Buffer* der_buffer) const = 0;
+
   // Compute the digest of the certificate given algorithm
   virtual bool ComputeDigest(const std::string &algorithm,
                              unsigned char* digest, std::size_t size,
                              std::size_t* length) const = 0;
 };
 
+// SSLCertChain is a simple wrapper for a vector of SSLCertificates. It serves
+// primarily to ensure proper memory management (especially deletion) of the
+// SSLCertificate pointers.
+class SSLCertChain {
+ public:
+  // These constructors copy the provided SSLCertificate(s), so the caller
+  // retains ownership.
+  explicit SSLCertChain(const std::vector<SSLCertificate*>& certs) {
+    ASSERT(!certs.empty());
+    certs_.resize(certs.size());
+    std::transform(certs.begin(), certs.end(), certs_.begin(), DupCert);
+  }
+  explicit SSLCertChain(const SSLCertificate* cert) {
+    certs_.push_back(cert->GetReference());
+  }
+
+  ~SSLCertChain() {
+    std::for_each(certs_.begin(), certs_.end(), DeleteCert);
+  }
+
+  // Vector access methods.
+  size_t GetSize() const { return certs_.size(); }
+
+  // Returns a temporary reference, only valid until the chain is destroyed.
+  const SSLCertificate& Get(size_t pos) const { return *(certs_[pos]); }
+
+  // Returns a new SSLCertChain object instance wrapping the same underlying
+  // certificate chain.  Caller is responsible for freeing the returned object.
+  SSLCertChain* Copy() const {
+    return new SSLCertChain(certs_);
+  }
+
+ private:
+  // Helper function for duplicating a vector of certificates.
+  static SSLCertificate* DupCert(const SSLCertificate* cert) {
+    return cert->GetReference();
+  }
+
+  // Helper function for deleting a vector of certificates.
+  static void DeleteCert(SSLCertificate* cert) { delete cert; }
+
+  std::vector<SSLCertificate*> certs_;
+
+  DISALLOW_COPY_AND_ASSIGN(SSLCertChain);
+};
+
 // Our identity in an SSL negotiation: a keypair and certificate (both
 // with the same public key).
 // This too is pretty much immutable once created.
@@ -108,4 +168,4 @@
 
 }  // namespace talk_base
 
-#endif  // TALK_BASE_SSLIDENTITY_H__
+#endif  // TALK_BASE_SSLIDENTITY_H_
diff --git a/base/sslstreamadapter.h b/base/sslstreamadapter.h
index 2afe1da..3a77973 100644
--- a/base/sslstreamadapter.h
+++ b/base/sslstreamadapter.h
@@ -25,8 +25,8 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef TALK_BASE_SSLSTREAMADAPTER_H__
-#define TALK_BASE_SSLSTREAMADAPTER_H__
+#ifndef TALK_BASE_SSLSTREAMADAPTER_H_
+#define TALK_BASE_SSLSTREAMADAPTER_H_
 
 #include <string>
 #include <vector>
@@ -111,9 +111,9 @@
   // mode.
   // Generally, SetIdentity() and possibly SetServerRole() should have
   // been called before this.
-  // SetPeerCertificate() must also be called. It may be called after
-  // StartSSLWithPeer() but must be called before the underlying
-  // stream opens.
+  // SetPeerCertificate() or SetPeerCertificateDigest() must also be called.
+  // It may be called after StartSSLWithPeer() but must be called before the
+  // underlying stream opens.
   virtual int StartSSLWithPeer() = 0;
 
   // Specify the certificate that our peer is expected to use in
@@ -138,6 +138,13 @@
                                         const unsigned char* digest_val,
                                         size_t digest_len) = 0;
 
+  // Retrieves the peer's X.509 certificate, if a certificate has been
+  // provided by SetPeerCertificate or a connection has been established. If
+  // a connection has been established, this returns the
+  // certificate transmitted over SSL, including the entire chain.
+  // The returned certificate is owned by the caller.
+  virtual bool GetPeerCertificate(SSLCertificate** cert) const = 0;
+
   // Key Exporter interface from RFC 5705
   // Arguments are:
   // label               -- the exporter label.
@@ -182,4 +189,4 @@
 
 }  // namespace talk_base
 
-#endif  // TALK_BASE_SSLSTREAMADAPTER_H__
+#endif  // TALK_BASE_SSLSTREAMADAPTER_H_
diff --git a/base/sslstreamadapter_unittest.cc b/base/sslstreamadapter_unittest.cc
index 1fe1a66..e7335be 100644
--- a/base/sslstreamadapter_unittest.cc
+++ b/base/sslstreamadapter_unittest.cc
@@ -33,6 +33,7 @@
 
 #include "talk/base/gunit.h"
 #include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
 #include "talk/base/ssladapter.h"
 #include "talk/base/sslconfig.h"
 #include "talk/base/sslidentity.h"
@@ -388,6 +389,13 @@
       return server_ssl_->GetDtlsSrtpCipher(retval);
   }
 
+  bool GetPeerCertificate(bool client, talk_base::SSLCertificate** cert) {
+    if (client)
+      return client_ssl_->GetPeerCertificate(cert);
+    else
+      return server_ssl_->GetPeerCertificate(cert);
+  }
+
   bool ExportKeyingMaterial(const char *label,
                             const unsigned char *context,
                             size_t context_len,
@@ -885,3 +893,42 @@
   TestHandshake();
   TestTransfer(100);
 }
+
+// Test getting the remote certificate.
+TEST_F(SSLStreamAdapterTestDTLSFromPEMStrings, TestDTLSGetPeerCertificate) {
+  MAYBE_SKIP_TEST(HaveDtls);
+
+  // Peer certificates haven't been received yet.
+  talk_base::scoped_ptr<talk_base::SSLCertificate> client_peer_cert;
+  ASSERT_FALSE(GetPeerCertificate(true, client_peer_cert.accept()));
+  ASSERT_FALSE(client_peer_cert != NULL);
+
+  talk_base::scoped_ptr<talk_base::SSLCertificate> server_peer_cert;
+  ASSERT_FALSE(GetPeerCertificate(false, server_peer_cert.accept()));
+  ASSERT_FALSE(server_peer_cert != NULL);
+
+  TestHandshake();
+
+  // The client should have a peer certificate after the handshake.
+  ASSERT_TRUE(GetPeerCertificate(true, client_peer_cert.accept()));
+  ASSERT_TRUE(client_peer_cert != NULL);
+
+  // It's not kCERT_PEM.
+  std::string client_peer_string = client_peer_cert->ToPEMString();
+  ASSERT_NE(kCERT_PEM, client_peer_string);
+
+  // It must not have a chain, because the test certs are self-signed.
+  talk_base::SSLCertChain* client_peer_chain;
+  ASSERT_FALSE(client_peer_cert->GetChain(&client_peer_chain));
+
+  // The server should have a peer certificate after the handshake.
+  ASSERT_TRUE(GetPeerCertificate(false, server_peer_cert.accept()));
+  ASSERT_TRUE(server_peer_cert != NULL);
+
+  // It's kCERT_PEM
+  ASSERT_EQ(kCERT_PEM, server_peer_cert->ToPEMString());
+
+  // It must not have a chain, because the test certs are self-signed.
+  talk_base::SSLCertChain* server_peer_chain;
+  ASSERT_FALSE(server_peer_cert->GetChain(&server_peer_chain));
+}
diff --git a/base/sslstreamadapterhelper.cc b/base/sslstreamadapterhelper.cc
index 5a1a255..b42faa8 100644
--- a/base/sslstreamadapterhelper.cc
+++ b/base/sslstreamadapterhelper.cc
@@ -87,6 +87,14 @@
   peer_certificate_.reset(cert);
 }
 
+bool SSLStreamAdapterHelper::GetPeerCertificate(SSLCertificate** cert) const {
+  if (!peer_certificate_)
+    return false;
+
+  *cert = peer_certificate_->GetReference();
+  return true;
+}
+
 bool SSLStreamAdapterHelper::SetPeerCertificateDigest(
     const std::string &digest_alg,
     const unsigned char* digest_val,
diff --git a/base/sslstreamadapterhelper.h b/base/sslstreamadapterhelper.h
index e8cb3b0..7c28056 100644
--- a/base/sslstreamadapterhelper.h
+++ b/base/sslstreamadapterhelper.h
@@ -2,26 +2,26 @@
  * libjingle
  * Copyright 2004--2008, Google Inc.
  *
- * Redistribution and use in source and binary forms, with or without 
+ * 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, 
+ *  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 
+ *  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 
+ * 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, 
+ * 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 
+ * 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.
  */
 
@@ -63,6 +63,7 @@
   virtual bool SetPeerCertificateDigest(const std::string& digest_alg,
                                         const unsigned char* digest_val,
                                         size_t digest_len);
+  virtual bool GetPeerCertificate(SSLCertificate** cert) const;
   virtual StreamState GetState() const;
   virtual void Close();
 
diff --git a/base/stream.cc b/base/stream.cc
index 20adfcf..996908d 100644
--- a/base/stream.cc
+++ b/base/stream.cc
@@ -523,6 +523,106 @@
   fclose(file_);
 }
 
+CircularFileStream::CircularFileStream(size_t max_size)
+  : max_write_size_(max_size),
+    position_(0),
+    marked_position_(max_size / 2),
+    last_write_position_(0),
+    read_segment_(READ_LATEST),
+    read_segment_available_(0) {
+}
+
+bool CircularFileStream::Open(
+    const std::string& filename, const char* mode, int* error) {
+  if (!FileStream::Open(filename.c_str(), mode, error))
+    return false;
+
+  if (strchr(mode, "r") != NULL) {  // Opened in read mode.
+    // Check if the buffer has been overwritten and determine how to read the
+    // log in time sequence.
+    size_t file_size;
+    GetSize(&file_size);
+    if (file_size == position_) {
+      // The buffer has not been overwritten yet. Read 0 .. file_size
+      read_segment_ = READ_LATEST;
+      read_segment_available_ = file_size;
+    } else {
+      // The buffer has been over written. There are three segments: The first
+      // one is 0 .. marked_position_, which is the marked earliest log. The
+      // second one is position_ .. file_size, which is the middle log. The
+      // last one is marked_position_ .. position_, which is the latest log.
+      read_segment_ = READ_MARKED;
+      read_segment_available_ = marked_position_;
+      last_write_position_ = position_;
+    }
+
+    // Read from the beginning.
+    position_ = 0;
+    SetPosition(position_);
+  }
+
+  return true;
+}
+
+StreamResult CircularFileStream::Read(void* buffer, size_t buffer_len,
+                                      size_t* read, int* error) {
+  if (read_segment_available_ == 0) {
+    size_t file_size;
+    switch (read_segment_) {
+      case READ_MARKED:  // Finished READ_MARKED and start READ_MIDDLE.
+        read_segment_ = READ_MIDDLE;
+        position_ = last_write_position_;
+        SetPosition(position_);
+        GetSize(&file_size);
+        read_segment_available_ = file_size - position_;
+        break;
+
+      case READ_MIDDLE:  // Finished READ_MIDDLE and start READ_LATEST.
+        read_segment_ = READ_LATEST;
+        position_ = marked_position_;
+        SetPosition(position_);
+        read_segment_available_ = last_write_position_ - position_;
+        break;
+
+      default:  // Finished READ_LATEST and return EOS.
+        return talk_base::SR_EOS;
+    }
+  }
+
+  size_t local_read;
+  if (!read) read = &local_read;
+
+  size_t to_read = talk_base::_min(buffer_len, read_segment_available_);
+  talk_base::StreamResult result
+    = talk_base::FileStream::Read(buffer, to_read, read, error);
+  if (result == talk_base::SR_SUCCESS) {
+    read_segment_available_ -= *read;
+    position_ += *read;
+  }
+  return result;
+}
+
+StreamResult CircularFileStream::Write(const void* data, size_t data_len,
+                                       size_t* written, int* error) {
+  if (position_ >= max_write_size_) {
+    ASSERT(position_ == max_write_size_);
+    position_ = marked_position_;
+    SetPosition(position_);
+  }
+
+  size_t local_written;
+  if (!written) written = &local_written;
+
+  size_t to_eof = max_write_size_ - position_;
+  size_t to_write = talk_base::_min(data_len, to_eof);
+  talk_base::StreamResult result
+    = talk_base::FileStream::Write(data, to_write, written, error);
+  if (result == talk_base::SR_SUCCESS) {
+    position_ += *written;
+  }
+  return result;
+}
+
 AsyncWriteStream::~AsyncWriteStream() {
   write_thread_->Clear(this, 0, NULL);
   ClearBufferAndWrite();
diff --git a/base/stream.h b/base/stream.h
index 6700c40..457396c 100644
--- a/base/stream.h
+++ b/base/stream.h
@@ -468,6 +468,35 @@
   DISALLOW_EVIL_CONSTRUCTORS(FileStream);
 };
 
+// A stream that caps the output at a certain size, dropping content from the
+// middle of the logical stream and maintaining equal parts of the start/end of
+// the logical stream.
+class CircularFileStream : public FileStream {
+ public:
+  explicit CircularFileStream(size_t max_size);
+
+  virtual bool Open(const std::string& filename, const char* mode, int* error);
+  virtual StreamResult Read(void* buffer, size_t buffer_len,
+                            size_t* read, int* error);
+  virtual StreamResult Write(const void* data, size_t data_len,
+                             size_t* written, int* error);
+
+ private:
+  enum ReadSegment {
+    READ_MARKED,  // Read 0 .. marked_position_
+    READ_MIDDLE,  // Read position_ .. file_size
+    READ_LATEST,  // Read marked_position_ .. position_ if the buffer was
+                  // overwritten or 0 .. position_ otherwise.
+  };
+
+  size_t max_write_size_;
+  size_t position_;
+  size_t marked_position_;
+  size_t last_write_position_;
+  ReadSegment read_segment_;
+  size_t read_segment_available_;
+};
+
 
 // A stream which pushes writes onto a separate thread and
 // returns from the write call immediately.
diff --git a/base/thread_unittest.cc b/base/thread_unittest.cc
index f114ff1..148e27b 100644
--- a/base/thread_unittest.cc
+++ b/base/thread_unittest.cc
@@ -159,7 +159,6 @@
   bool* flag_;
 };
 
-
 // See: https://code.google.com/p/webrtc/issues/detail?id=2409
 TEST(ThreadTest, DISABLED_Main) {
   const SocketAddress addr("127.0.0.1", 0);
diff --git a/build/OWNERS b/build/OWNERS
new file mode 100644
index 0000000..a833cd4
--- /dev/null
+++ b/build/OWNERS
@@ -0,0 +1,3 @@
+andrew@webrtc.org

+kjellander@webrtc.org

+

diff --git a/build/isolate.gypi b/build/isolate.gypi
new file mode 100644
index 0000000..83dd502
--- /dev/null
+++ b/build/isolate.gypi
@@ -0,0 +1,136 @@
+#
+# libjingle
+# Copyright 2013, 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.
+#
+
+# Copied from Chromium's src/build/isolate.gypi
+#
+# It was necessary to copy this file to libjingle , because the path to
+# build/common.gypi is different for the standalone and Chromium builds. Gyp
+# doesn't permit conditional inclusion or variable expansion in include paths.
+# http://code.google.com/p/gyp/wiki/InputFormatReference#Including_Other_Files
+
+# This file is meant to be included into a target to provide a rule
+# to "build" .isolate files into a .isolated file.
+#
+# To use this, create a gyp target with the following form:
+# 'conditions': [
+#   ['test_isolation_mode != "noop"', {
+#     'targets': [
+#       {
+#         'target_name': 'foo_test_run',
+#         'type': 'none',
+#         'dependencies': [
+#           'foo_test',
+#         ],
+#         'includes': [
+#           '../build/isolate.gypi',
+#           'foo_test.isolate',
+#         ],
+#         'sources': [
+#           'foo_test.isolate',
+#         ],
+#       },
+#     ],
+#   }],
+# ],
+#
+# Note: foo_test.isolate is included and a source file. It is an inherent
+# property of the .isolate format. This permits to define GYP variables but is
+# a stricter format than GYP so isolate.py can read it.
+#
+# The generated .isolated file will be:
+#   <(PRODUCT_DIR)/foo_test.isolated
+
+{
+  'rules': [
+    {
+      'rule_name': 'isolate',
+      'extension': 'isolate',
+      'inputs': [
+        # Files that are known to be involved in this step.
+        '<(DEPTH)/tools/swarm_client/isolate.py',
+        '<(DEPTH)/tools/swarm_client/run_isolated.py',
+        '<(DEPTH)/tools/swarm_client/googletest/run_test_cases.py',
+
+        # Disable file tracking by the build driver for now. This means the
+        # project must have the proper build-time dependency for their runtime
+        # dependency. This improves the runtime of the build driver since it
+        # doesn't have to stat() all these files.
+        #
+        # More importantly, it means that even if a isolate_dependency_tracked
+        # file is missing, for example if a file was deleted and the .isolate
+        # file was not updated, that won't break the build, especially in the
+        # case where foo_tests_run is not built! This should be reenabled once
+        # the switch-over to running tests on Swarm is completed.
+        #'<@(isolate_dependency_tracked)',
+      ],
+      'outputs': [
+        '<(PRODUCT_DIR)/<(RULE_INPUT_ROOT).isolated',
+      ],
+      'conditions': [
+        ["test_isolation_outdir==''", {
+          'action': [
+            'python',
+            '<(DEPTH)/tools/swarm_client/isolate.py',
+            '<(test_isolation_mode)',
+            # GYP will eliminate duplicate arguments so '<(PRODUCT_DIR)' cannot
+            # be provided twice. To work around this behavior, append '/'.
+            #
+            # Also have a space after <(PRODUCT_DIR) or visual studio will
+            # escape the argument wrappping " with the \ and merge it into
+            # the following arguments.
+            #
+            # Other variables should use the -V FOO=<(FOO) form so frequent
+            # values, like '0' or '1', aren't stripped out by GYP.
+            '--outdir', '<(PRODUCT_DIR)/ ',
+            '--variable', 'PRODUCT_DIR', '<(PRODUCT_DIR) ',
+            '--variable', 'OS=<(OS)',
+            '--result', '<@(_outputs)',
+            '--isolate', '<(RULE_INPUT_PATH)',
+          ],
+        }, {
+          'action': [
+            'python',
+            '<(DEPTH)/tools/swarm_client/isolate.py',
+            '<(test_isolation_mode)',
+            '--outdir', '<(test_isolation_outdir)',
+            # See comment above.
+            '--variable', 'PRODUCT_DIR', '<(PRODUCT_DIR) ',
+            '--variable', 'OS=<(OS)',
+            '--result', '<@(_outputs)',
+            '--isolate', '<(RULE_INPUT_PATH)',
+          ],
+        }],
+        ['test_isolation_fail_on_missing == 0', {
+            'action': ['--ignore_broken_items'],
+          },
+        ],
+      ],
+
+      'msvs_cygwin_shell': 0,
+    },
+  ],
+}
diff --git a/examples/android/project.properties b/examples/android/project.properties
index a3ee5ab..bc163b0 100644
--- a/examples/android/project.properties
+++ b/examples/android/project.properties
@@ -12,3 +12,5 @@
 
 # Project target.
 target=android-17
+
+java.compilerargs=-Xlint:all -Werror
diff --git a/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/examples/android/src/org/appspot/apprtc/AppRTCClient.java
index fe41564..3d369e0 100644
--- a/examples/android/src/org/appspot/apprtc/AppRTCClient.java
+++ b/examples/android/src/org/appspot/apprtc/AppRTCClient.java
@@ -272,6 +272,7 @@
       MediaConstraints videoConstraints = constraintsFromJSON(
           getVideoConstraints(
               getVarValue(roomHtml, "mediaConstraints", false)));
+
       Log.d(TAG, "videoConstraints: " + videoConstraints);
 
       return new AppRTCSignalingParameters(
@@ -282,17 +283,28 @@
     private String getVideoConstraints(String mediaConstraintsString) {
       try {
         JSONObject json = new JSONObject(mediaConstraintsString);
-        JSONObject videoJson = json.optJSONObject("video");
-        if (videoJson == null) {
-          return "";
+        // Tricksy handling of values that are allowed to be (boolean or
+        // MediaTrackConstraints) by the getUserMedia() spec.  There are three
+        // cases below.
+        if (!json.has("video") || !json.optBoolean("video", true)) {
+          // Case 1: "video" is not present, or is an explicit "false" boolean.
+          return null;
         }
-        return videoJson.toString();
+        if (json.optBoolean("video", false)) {
+          // Case 2: "video" is an explicit "true" boolean.
+          return "{\"mandatory\": {}, \"optional\": []}";
+        }
+        // Case 3: "video" is an object.
+        return json.getJSONObject("video").toString();
       } catch (JSONException e) {
         throw new RuntimeException(e);
       }
     }
 
     private MediaConstraints constraintsFromJSON(String jsonString) {
+      if (jsonString == null) {
+        return null;
+      }
       try {
         MediaConstraints constraints = new MediaConstraints();
         JSONObject json = new JSONObject(jsonString);
@@ -301,7 +313,7 @@
           JSONArray mandatoryKeys = mandatoryJSON.names();
           if (mandatoryKeys != null) {
             for (int i = 0; i < mandatoryKeys.length(); ++i) {
-              String key = (String) mandatoryKeys.getString(i);
+              String key = mandatoryKeys.getString(i);
               String value = mandatoryJSON.getString(key);
               constraints.mandatory.add(
                   new MediaConstraints.KeyValuePair(key, value));
diff --git a/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
index b89e9ac..81c5ba9 100644
--- a/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
+++ b/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.util.Log;
+import android.view.WindowManager;
 import android.webkit.JavascriptInterface;
 import android.widget.EditText;
 import android.widget.Toast;
@@ -93,21 +94,11 @@
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
 
-    // Since the error-handling of this demo consists of throwing
-    // RuntimeExceptions and we assume that'll terminate the app, we install
-    // this default handler so it's applied to background threads as well.
     Thread.setDefaultUncaughtExceptionHandler(
-        new Thread.UncaughtExceptionHandler() {
-          public void uncaughtException(Thread t, Throwable e) {
-            e.printStackTrace();
-            System.exit(-1);
-          }
-        });
+        new UnhandledExceptionHandler(this));
 
-    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
-    wakeLock = powerManager.newWakeLock(
-        PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "AppRTCDemo");
-    wakeLock.acquire();
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 
     Point displaySize = new Point();
     getWindowManager().getDefaultDisplay().getSize(displaySize);
@@ -119,9 +110,13 @@
 
     AudioManager audioManager =
         ((AudioManager) getSystemService(AUDIO_SERVICE));
-    audioManager.setMode(audioManager.isWiredHeadsetOn() ?
+    // TODO(fischman): figure out how to do this Right(tm) and remove the
+    // suppression.
+    @SuppressWarnings("deprecation")
+    boolean isWiredHeadsetOn = audioManager.isWiredHeadsetOn();
+    audioManager.setMode(isWiredHeadsetOn ?
         AudioManager.MODE_IN_CALL : AudioManager.MODE_IN_COMMUNICATION);
-    audioManager.setSpeakerphoneOn(!audioManager.isWiredHeadsetOn());
+    audioManager.setSpeakerphoneOn(!isWiredHeadsetOn);
 
     sdpMediaConstraints = new MediaConstraints();
     sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
@@ -164,19 +159,18 @@
   public void onPause() {
     super.onPause();
     vsv.onPause();
-    // TODO(fischman): IWBN to support pause/resume, but the WebRTC codebase
-    // isn't ready for that yet; e.g.
-    // https://code.google.com/p/webrtc/issues/detail?id=1407
-    // Instead, simply exit instead of pausing (the alternative leads to
-    // system-borking with wedged cameras; e.g. b/8224551)
-    disconnectAndExit();
+    if (videoSource != null) {
+      videoSource.stop();
+    }
   }
 
   @Override
   public void onResume() {
-    // The onResume() is a lie!  See TODO(fischman) in onPause() above.
     super.onResume();
     vsv.onResume();
+    if (videoSource != null) {
+      videoSource.restart();
+    }
   }
 
   @Override
@@ -220,14 +214,17 @@
 
     {
       logAndToast("Creating local video source...");
-      VideoCapturer capturer = getVideoCapturer();
-      videoSource = factory.createVideoSource(
-          capturer, appRtcClient.videoConstraints());
       MediaStream lMS = factory.createLocalMediaStream("ARDAMS");
-      VideoTrack videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
-      videoTrack.addRenderer(new VideoRenderer(new VideoCallbacks(
-          vsv, VideoStreamsView.Endpoint.LOCAL)));
-      lMS.addTrack(videoTrack);
+      if (appRtcClient.videoConstraints() != null) {
+        VideoCapturer capturer = getVideoCapturer();
+        videoSource = factory.createVideoSource(
+            capturer, appRtcClient.videoConstraints());
+        VideoTrack videoTrack =
+            factory.createVideoTrack("ARDAMSv0", videoSource);
+        videoTrack.addRenderer(new VideoRenderer(new VideoCallbacks(
+            vsv, VideoStreamsView.Endpoint.LOCAL)));
+        lMS.addTrack(videoTrack);
+      }
       lMS.addTrack(factory.createAudioTrack("ARDAMSa0"));
       pc.addStream(lMS, new MediaConstraints());
     }
@@ -257,7 +254,8 @@
   }
 
   @Override
-  public void onDestroy() {
+  protected void onDestroy() {
+    disconnectAndExit();
     super.onDestroy();
   }
 
@@ -532,7 +530,6 @@
         return;
       }
       quit[0] = true;
-      wakeLock.release();
       if (pc != null) {
         pc.dispose();
         pc = null;
@@ -550,6 +547,7 @@
         factory.dispose();
         factory = null;
       }
+      wakeLock.release();
       finish();
     }
   }
diff --git a/examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java b/examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java
new file mode 100644
index 0000000..502a5d5
--- /dev/null
+++ b/examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java
@@ -0,0 +1,103 @@
+/*
+ * libjingle
+ * Copyright 2013, 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.
+ */
+
+package org.appspot.apprtc;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.util.Log;
+import android.util.TypedValue;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Singleton helper: install a default unhandled exception handler which shows
+ * an informative dialog and kills the app.  Useful for apps whose
+ * error-handling consists of throwing RuntimeExceptions.
+ * NOTE: almost always more useful to
+ * Thread.setDefaultUncaughtExceptionHandler() rather than
+ * Thread.setUncaughtExceptionHandler(), to apply to background threads as well.
+ */
+public class UnhandledExceptionHandler
+    implements Thread.UncaughtExceptionHandler {
+  private static final String TAG = "AppRTCDemoActivity";
+  private final Activity activity;
+
+  public UnhandledExceptionHandler(final Activity activity) {
+    this.activity = activity;
+  }
+
+  public void uncaughtException(Thread unusedThread, final Throwable e) {
+    activity.runOnUiThread(new Runnable() {
+        @Override public void run() {
+          String title = "Fatal error: " + getTopLevelCauseMessage(e);
+          String msg = getRecursiveStackTrace(e);
+          TextView errorView = new TextView(activity);
+          errorView.setText(msg);
+          errorView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 8);
+          ScrollView scrollingContainer = new ScrollView(activity);
+          scrollingContainer.addView(errorView);
+          Log.e(TAG, title + "\n\n" + msg);
+          DialogInterface.OnClickListener listener =
+              new DialogInterface.OnClickListener() {
+                @Override public void onClick(
+                    DialogInterface dialog, int which) {
+                  dialog.dismiss();
+                  System.exit(1);
+                }
+              };
+          AlertDialog.Builder builder =
+              new AlertDialog.Builder(activity);
+          builder
+              .setTitle(title)
+              .setView(scrollingContainer)
+              .setPositiveButton("Exit", listener).show();
+        }
+      });
+  }
+
+  // Returns the Message attached to the original Cause of |t|.
+  private static String getTopLevelCauseMessage(Throwable t) {
+    Throwable topLevelCause = t;
+    while (topLevelCause.getCause() != null) {
+      topLevelCause = topLevelCause.getCause();
+    }
+    return topLevelCause.getMessage();
+  }
+
+  // Returns a human-readable String of the stacktrace in |t|, recursively
+  // through all Causes that led to |t|.
+  private static String getRecursiveStackTrace(Throwable t) {
+    StringWriter writer = new StringWriter();
+    t.printStackTrace(new PrintWriter(writer));
+    return writer.toString();
+  }
+}
diff --git a/examples/android/src/org/appspot/apprtc/VideoStreamsView.java b/examples/android/src/org/appspot/apprtc/VideoStreamsView.java
index 30c9bf8..906aa92 100644
--- a/examples/android/src/org/appspot/apprtc/VideoStreamsView.java
+++ b/examples/android/src/org/appspot/apprtc/VideoStreamsView.java
@@ -75,6 +75,7 @@
   public VideoStreamsView(Context c, Point screenDimensions) {
     super(c);
     this.screenDimensions = screenDimensions;
+    setPreserveEGLContextOnPause(true);
     setEGLContextClientVersion(2);
     setRenderer(this);
     setRenderMode(RENDERMODE_WHEN_DIRTY);
diff --git a/libjingle.gyp b/libjingle.gyp
index 5250eec..f9edd83 100755
--- a/libjingle.gyp
+++ b/libjingle.gyp
@@ -107,7 +107,6 @@
                 'android_java_files': [
                   '<(webrtc_modules_dir)/audio_device/android/java/src/org/webrtc/voiceengine/WebRTCAudioDevice.java',
                   '<(webrtc_modules_dir)/audio_device/android/java/src/org/webrtc/voiceengine/AudioManagerAndroid.java',
-                  '<(webrtc_modules_dir)/video_capture/android/java/src/org/webrtc/videoengine/CaptureCapabilityAndroid.java',
                   '<(webrtc_modules_dir)/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java',
                   '<(webrtc_modules_dir)/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java',
                   '<(webrtc_modules_dir)/video_render/android/java/src/org/webrtc/videoengine/ViEAndroidGLES20.java',
diff --git a/libjingle_examples.gyp b/libjingle_examples.gyp
index d4aa4a4..a22daf1 100755
--- a/libjingle_examples.gyp
+++ b/libjingle_examples.gyp
@@ -325,6 +325,7 @@
                 'examples/android/res/values/strings.xml',
                 'examples/android/src/org/appspot/apprtc/AppRTCClient.java',
                 'examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java',
+                'examples/android/src/org/appspot/apprtc/UnhandledExceptionHandler.java',
                 'examples/android/src/org/appspot/apprtc/FramePool.java',
                 'examples/android/src/org/appspot/apprtc/GAEChannelClient.java',
                 'examples/android/src/org/appspot/apprtc/VideoStreamsView.java',
diff --git a/libjingle_media_unittest.isolate b/libjingle_media_unittest.isolate
new file mode 100644
index 0000000..4c4ee31
--- /dev/null
+++ b/libjingle_media_unittest.isolate
@@ -0,0 +1,47 @@
+#
+# libjingle
+# Copyright 2013, 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.
+#
+{
+  'conditions': [
+    ['OS=="linux" or OS=="mac" or OS=="win"', {
+      'variables': {
+        'command': [
+          '../testing/test_env.py',
+          '../tools/swarm_client/googletest/run_test_cases.py',
+          '<(PRODUCT_DIR)/libjingle_media_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_tracked': [
+          'media/testdata/captured-320x240-2s-48.frames',
+          '../testing/test_env.py',
+          '<(PRODUCT_DIR)/libjingle_media_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_untracked': [
+          '../tools/swarm_client/',
+        ],
+      },
+    }],
+  ],
+}
diff --git a/libjingle_p2p_unittest.isolate b/libjingle_p2p_unittest.isolate
new file mode 100644
index 0000000..848f234
--- /dev/null
+++ b/libjingle_p2p_unittest.isolate
@@ -0,0 +1,46 @@
+#
+# libjingle
+# Copyright 2013, 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.
+#
+{
+  'conditions': [
+    ['OS=="linux" or OS=="mac" or OS=="win"', {
+      'variables': {
+        'command': [
+          '../testing/test_env.py',
+          '../tools/swarm_client/googletest/run_test_cases.py',
+          '<(PRODUCT_DIR)/libjingle_p2p_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_tracked': [
+          '../testing/test_env.py',
+          '<(PRODUCT_DIR)/libjingle_p2p_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_untracked': [
+          '../tools/swarm_client/',
+        ],
+      },
+    }],
+  ],
+}
diff --git a/libjingle_peerconnection_unittest.isolate b/libjingle_peerconnection_unittest.isolate
new file mode 100644
index 0000000..660ffd5
--- /dev/null
+++ b/libjingle_peerconnection_unittest.isolate
@@ -0,0 +1,46 @@
+#
+# libjingle
+# Copyright 2013, 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.
+#
+{
+  'conditions': [
+    ['OS=="linux" or OS=="mac" or OS=="win"', {
+      'variables': {
+        'command': [
+          '../testing/test_env.py',
+          '../tools/swarm_client/googletest/run_test_cases.py',
+          '<(PRODUCT_DIR)/libjingle_peerconnection_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_tracked': [
+          '../testing/test_env.py',
+          '<(PRODUCT_DIR)/libjingle_peerconnection_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_untracked': [
+          '../tools/swarm_client/',
+        ],
+      },
+    }],
+  ],
+}
diff --git a/libjingle_sound_unittest.isolate b/libjingle_sound_unittest.isolate
new file mode 100644
index 0000000..6da7d88
--- /dev/null
+++ b/libjingle_sound_unittest.isolate
@@ -0,0 +1,46 @@
+#
+# libjingle
+# Copyright 2013, 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.
+#
+{
+  'conditions': [
+    ['OS=="linux" or OS=="mac" or OS=="win"', {
+      'variables': {
+        'command': [
+          '../testing/test_env.py',
+          '../tools/swarm_client/googletest/run_test_cases.py',
+          '<(PRODUCT_DIR)/libjingle_sound_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_tracked': [
+          '../testing/test_env.py',
+          '<(PRODUCT_DIR)/libjingle_sound_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_untracked': [
+          '../tools/swarm_client/',
+        ],
+      },
+    }],
+  ],
+}
diff --git a/libjingle_tests.gyp b/libjingle_tests.gyp
index 546767d..bce81a5 100755
--- a/libjingle_tests.gyp
+++ b/libjingle_tests.gyp
@@ -517,5 +517,79 @@
         },  # target libjingle_peerconnection_objc_test
       ],
     }],
+    ['test_isolation_mode != "noop"', {
+      'targets': [
+        {
+          'target_name': 'libjingle_media_unittest_run',
+          'type': 'none',
+          'dependencies': [
+            'libjingle_media_unittest',
+          ],
+          'includes': [
+            'build/isolate.gypi',
+            'libjingle_media_unittest.isolate',
+          ],
+          'sources': [
+            'libjingle_media_unittest.isolate',
+          ],
+        },
+        {
+          'target_name': 'libjingle_p2p_unittest_run',
+          'type': 'none',
+          'dependencies': [
+            'libjingle_p2p_unittest',
+          ],
+          'includes': [
+            'build/isolate.gypi',
+            'libjingle_p2p_unittest.isolate',
+          ],
+          'sources': [
+            'libjingle_p2p_unittest.isolate',
+          ],
+        },
+        {
+          'target_name': 'libjingle_peerconnection_unittest_run',
+          'type': 'none',
+          'dependencies': [
+            'libjingle_peerconnection_unittest',
+          ],
+          'includes': [
+            'build/isolate.gypi',
+            'libjingle_peerconnection_unittest.isolate',
+          ],
+          'sources': [
+            'libjingle_peerconnection_unittest.isolate',
+          ],
+        },
+        {
+          'target_name': 'libjingle_sound_unittest_run',
+          'type': 'none',
+          'dependencies': [
+            'libjingle_sound_unittest',
+          ],
+          'includes': [
+            'build/isolate.gypi',
+            'libjingle_sound_unittest.isolate',
+          ],
+          'sources': [
+            'libjingle_sound_unittest.isolate',
+          ],
+        },
+        {
+          'target_name': 'libjingle_unittest_run',
+          'type': 'none',
+          'dependencies': [
+            'libjingle_unittest',
+          ],
+          'includes': [
+            'build/isolate.gypi',
+            'libjingle_unittest.isolate',
+          ],
+          'sources': [
+            'libjingle_unittest.isolate',
+          ],
+        },
+      ],
+    }],
   ],
 }
diff --git a/libjingle_unittest.isolate b/libjingle_unittest.isolate
new file mode 100644
index 0000000..efb8625
--- /dev/null
+++ b/libjingle_unittest.isolate
@@ -0,0 +1,46 @@
+#
+# libjingle
+# Copyright 2013, 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.
+#
+{
+  'conditions': [
+    ['OS=="linux" or OS=="mac" or OS=="win"', {
+      'variables': {
+        'command': [
+          '../testing/test_env.py',
+          '../tools/swarm_client/googletest/run_test_cases.py',
+          '<(PRODUCT_DIR)/libjingle_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_tracked': [
+          '../testing/test_env.py',
+          '<(PRODUCT_DIR)/libjingle_unittest<(EXECUTABLE_SUFFIX)',
+        ],
+        'isolate_dependency_untracked': [
+          '../tools/swarm_client/',
+        ],
+      },
+    }],
+  ],
+}
diff --git a/media/base/constants.cc b/media/base/constants.cc
index 5529c2a..bbe5eb7 100644
--- a/media/base/constants.cc
+++ b/media/base/constants.cc
@@ -85,6 +85,7 @@
 const char* kCodecParamMaxBitrate = "x-google-max-bitrate";
 const char* kCodecParamMinBitrate = "x-google-min-bitrate";
 const char* kCodecParamMaxQuantization = "x-google-max-quantization";
+const char* kCodecParamPort = "x-google-port";
 
 const int kGoogleRtpDataCodecId = 101;
 const char kGoogleRtpDataCodecName[] = "google-data";
diff --git a/media/base/constants.h b/media/base/constants.h
index afcfb13..80af77e 100644
--- a/media/base/constants.h
+++ b/media/base/constants.h
@@ -99,6 +99,7 @@
 extern const char* kCodecParamMaxBitrate;
 extern const char* kCodecParamMinBitrate;
 extern const char* kCodecParamMaxQuantization;
+extern const char* kCodecParamPort;
 
 // We put the data codec names here so callers of
 // DataEngine::CreateChannel don't have to import rtpdataengine.h or
diff --git a/media/base/fakemediaengine.h b/media/base/fakemediaengine.h
index 257c198..c44db68 100644
--- a/media/base/fakemediaengine.h
+++ b/media/base/fakemediaengine.h
@@ -837,6 +837,9 @@
     default_encoder_config_ = config;
     return true;
   }
+  VideoEncoderConfig GetDefaultEncoderConfig() const {
+    return default_encoder_config_;
+  }
   const VideoEncoderConfig& default_encoder_config() const {
     return default_encoder_config_;
   }
diff --git a/media/base/filemediaengine.h b/media/base/filemediaengine.h
index b7a7ac1..9cdfe83 100644
--- a/media/base/filemediaengine.h
+++ b/media/base/filemediaengine.h
@@ -93,6 +93,9 @@
   virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
     return true;
   }
+  virtual VideoEncoderConfig GetDefaultVideoEncoderConfig() const {
+    return VideoEncoderConfig();
+  }
   virtual bool SetSoundDevices(const Device* in_dev, const Device* out_dev) {
     return true;
   }
diff --git a/media/base/hybridvideoengine.h b/media/base/hybridvideoengine.h
index 7bef97e..ab62cc7 100644
--- a/media/base/hybridvideoengine.h
+++ b/media/base/hybridvideoengine.h
@@ -204,6 +204,20 @@
     }
     return true;
   }
+  VideoEncoderConfig GetDefaultEncoderConfig() const {
+    // This looks pretty strange, but, in practice, it'll do sane things if
+    // GetDefaultEncoderConfig is only called after SetDefaultEncoderConfig,
+    // since both engines should be essentially equivalent at that point. If it
+    // hasn't been called, though, we'll use the first meaningful encoder
+    // config, or the config from the second video engine if neither are
+    // meaningful.
+    VideoEncoderConfig config = video1_.GetDefaultEncoderConfig();
+    if (config.max_codec.width != 0) {
+      return config;
+    } else {
+      return video2_.GetDefaultEncoderConfig();
+    }
+  }
   const std::vector<VideoCodec>& codecs() const {
     return codecs_;
   }
diff --git a/media/base/mediaengine.h b/media/base/mediaengine.h
index c9baeb2..f916572 100644
--- a/media/base/mediaengine.h
+++ b/media/base/mediaengine.h
@@ -98,6 +98,10 @@
   // and encode video.
   virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config)
       = 0;
+  // Gets the default (maximum) codec/resolution and encoder option used to
+  // capture and encode video, as set by SetDefaultVideoEncoderConfig or the
+  // default from the video engine if not previously set.
+  virtual VideoEncoderConfig GetDefaultVideoEncoderConfig() const = 0;
 
   // Device selection
   // TODO(tschmelcher): Add method for selecting the soundclip device.
@@ -203,6 +207,9 @@
   virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
     return video_.SetDefaultEncoderConfig(config);
   }
+  virtual VideoEncoderConfig GetDefaultVideoEncoderConfig() const {
+    return video_.GetDefaultEncoderConfig();
+  }
 
   virtual bool SetSoundDevices(const Device* in_device,
                                const Device* out_device) {
@@ -327,6 +334,9 @@
     return NULL;
   }
   bool SetOptions(const VideoOptions& options) { return true; }
+  VideoEncoderConfig GetDefaultEncoderConfig() const {
+    return VideoEncoderConfig();
+  }
   bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) {
     return true;
   }
diff --git a/media/devices/libudevsymboltable.cc b/media/devices/libudevsymboltable.cc
index b1d9d31..9620cd2 100644
--- a/media/devices/libudevsymboltable.cc
+++ b/media/devices/libudevsymboltable.cc
@@ -27,6 +27,10 @@
 
 #include "talk/media/devices/libudevsymboltable.h"
 
+#include <dlfcn.h>
+
+#include "talk/base/logging.h"
+
 namespace cricket {
 
 #define LATE_BINDING_SYMBOL_TABLE_CLASS_NAME LIBUDEV_SYMBOLS_CLASS_NAME
@@ -37,4 +41,30 @@
 #undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
 #undef LATE_BINDING_SYMBOL_TABLE_DLL_NAME
 
+bool IsWrongLibUDevAbiVersion(talk_base::DllHandle libudev_0) {
+  talk_base::DllHandle libudev_1 = dlopen("libudev.so.1", RTLD_NOW|RTLD_NOLOAD);
+  bool unsafe_symlink = (libudev_0 == libudev_1);
+  if (unsafe_symlink) {
+    // .0 and .1 are distinct ABIs, so if they point to the same thing then one
+    // of them must be wrong. Probably the old has been symlinked to the new in
+    // a misguided attempt at backwards compatibility.
+    LOG(LS_ERROR) << "libudev.so.0 and libudev.so.1 unsafely point to the"
+                     " same thing; not using libudev";
+  } else if (libudev_1) {
+    // If libudev.so.1 is resident but distinct from libudev.so.0, then some
+    // system library loaded the new ABI separately. This is not a problem for
+    // LateBindingSymbolTable because its symbol look-ups are restricted to its
+    // DllHandle, but having libudev.so.0 resident may cause problems for that
+    // system library because symbol names are not namespaced by DLL.
+    LOG(LS_WARNING)
+        << "libudev.so.1 is resident but distinct from libudev.so.0";
+  }
+  if (libudev_1) {
+    // Release the refcount that we acquired above. (Does not unload the DLL;
+    // whoever loaded it still needs it.)
+    dlclose(libudev_1);
+  }
+  return unsafe_symlink;
+}
+
 }  // namespace cricket
diff --git a/media/devices/libudevsymboltable.h b/media/devices/libudevsymboltable.h
index 046a4b0..aa8c590 100644
--- a/media/devices/libudevsymboltable.h
+++ b/media/devices/libudevsymboltable.h
@@ -66,6 +66,14 @@
 #undef LATE_BINDING_SYMBOL_TABLE_CLASS_NAME
 #undef LATE_BINDING_SYMBOL_TABLE_SYMBOLS_LIST
 
+// libudev has changed ABIs to libudev.so.1 in recent distros and lots of users
+// and/or software (including Google Chrome) are symlinking the old to the new.
+// The entire point of ABI versions is that you can't safely do that, and
+// it has caused crashes in the wild. This function checks if the DllHandle that
+// we got back for libudev.so.0 is actually for libudev.so.1. If so, the library
+// cannot safely be used.
+bool IsWrongLibUDevAbiVersion(talk_base::DllHandle libudev_0);
+
 }  // namespace cricket
 
 #endif  // TALK_MEDIA_DEVICES_LIBUDEVSYMBOLTABLE_H_
diff --git a/media/devices/linuxdeviceinfo.cc b/media/devices/linuxdeviceinfo.cc
index 7b52001..b1bc9dd 100644
--- a/media/devices/linuxdeviceinfo.cc
+++ b/media/devices/linuxdeviceinfo.cc
@@ -52,7 +52,8 @@
   ScopedLibUdev() {}
 
   bool Init() {
-    return libudev_.Load();
+    return libudev_.Load() &&
+           !IsWrongLibUDevAbiVersion(libudev_.GetDllHandle());
   }
 
   LibUDevSymbolTable libudev_;
diff --git a/media/devices/linuxdevicemanager.cc b/media/devices/linuxdevicemanager.cc
index 2096eeb..e3e55ff 100644
--- a/media/devices/linuxdevicemanager.cc
+++ b/media/devices/linuxdevicemanager.cc
@@ -306,8 +306,9 @@
   // We deliberately return true in the failure paths here because libudev is
   // not a critical component of a Linux system so it may not be present/usable,
   // and we don't want to halt LinuxDeviceManager initialization in such a case.
-  if (!libudev_.Load()) {
-    LOG(LS_WARNING) << "libudev not present/usable; LinuxDeviceWatcher disabled";
+  if (!libudev_.Load() || IsWrongLibUDevAbiVersion(libudev_.GetDllHandle())) {
+    LOG(LS_WARNING)
+        << "libudev not present/usable; LinuxDeviceWatcher disabled";
     return true;
   }
   udev_ = libudev_.udev_new()();
diff --git a/media/sctp/sctpdataengine.cc b/media/sctp/sctpdataengine.cc
index 3d450c6..2e8e90c 100644
--- a/media/sctp/sctpdataengine.cc
+++ b/media/sctp/sctpdataengine.cc
@@ -242,8 +242,8 @@
 
 SctpDataMediaChannel::SctpDataMediaChannel(talk_base::Thread* thread)
     : worker_thread_(thread),
-      local_port_(kSctpDefaultPort),
-      remote_port_(kSctpDefaultPort),
+      local_port_(-1),
+      remote_port_(-1),
       sock_(NULL),
       sending_(false),
       receiving_(false),
@@ -344,6 +344,12 @@
 
 bool SctpDataMediaChannel::Connect() {
   LOG(LS_VERBOSE) << debug_name_ << "->Connect().";
+  if (remote_port_ < 0) {
+    remote_port_ = kSctpDefaultPort;
+  }
+  if (local_port_ < 0) {
+    local_port_ = kSctpDefaultPort;
+  }
 
   // If we already have a socket connection, just return.
   if (sock_) {
@@ -677,6 +683,38 @@
   }
 }
 
+// Puts the specified |param| from the codec identified by |id| into |dest|
+// and returns true.  Or returns false if it wasn't there, leaving |dest|
+// untouched.
+static bool GetCodecIntParameter(const std::vector<DataCodec>& codecs,
+                                 int id, const std::string& name,
+                                 const std::string& param, int* dest) {
+  std::string value;
+  Codec match_pattern;
+  match_pattern.id = id;
+  match_pattern.name = name;
+  for (size_t i = 0; i < codecs.size(); ++i) {
+    if (codecs[i].Matches(match_pattern)) {
+      if (codecs[i].GetParam(param, &value)) {
+        *dest = talk_base::FromString<int>(value);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+bool SctpDataMediaChannel::SetSendCodecs(const std::vector<DataCodec>& codecs) {
+  return GetCodecIntParameter(
+      codecs, kGoogleSctpDataCodecId, kGoogleSctpDataCodecName, kCodecParamPort,
+      &remote_port_);
+}
+
+bool SctpDataMediaChannel::SetRecvCodecs(const std::vector<DataCodec>& codecs) {
+  return GetCodecIntParameter(
+      codecs, kGoogleSctpDataCodecId, kGoogleSctpDataCodecName, kCodecParamPort,
+      &local_port_);
+}
 
 void SctpDataMediaChannel::OnPacketFromSctpToNetwork(
     talk_base::Buffer* buffer) {
diff --git a/media/sctp/sctpdataengine.h b/media/sctp/sctpdataengine.h
index 429016e..cadf78c 100644
--- a/media/sctp/sctpdataengine.h
+++ b/media/sctp/sctpdataengine.h
@@ -168,12 +168,8 @@
       const std::vector<RtpHeaderExtension>& extensions) { return true; }
   virtual bool SetSendRtpHeaderExtensions(
       const std::vector<RtpHeaderExtension>& extensions) { return true; }
-  virtual bool SetSendCodecs(const std::vector<DataCodec>& codecs) {
-    return true;
-  }
-  virtual bool SetRecvCodecs(const std::vector<DataCodec>& codecs) {
-    return true;
-  }
+  virtual bool SetSendCodecs(const std::vector<DataCodec>& codecs);
+  virtual bool SetRecvCodecs(const std::vector<DataCodec>& codecs);
   virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
   virtual void OnReadyToSend(bool ready) {}
 
@@ -211,7 +207,8 @@
   talk_base::Thread* worker_thread_;
   // The local and remote SCTP port to use. These are passed along the wire
   // and the listener and connector must be using the same port. It is not
-  // related to the ports at the IP level.
+  // related to the ports at the IP level.  If set to -1, we default to
+  // kSctpDefaultPort.
   int local_port_;
   int remote_port_;
   struct socket* sock_;  // The socket created by usrsctp_socket(...).
diff --git a/media/webrtc/webrtcvideoengine.cc b/media/webrtc/webrtcvideoengine.cc
index cdc1727..3f4667d 100644
--- a/media/webrtc/webrtcvideoengine.cc
+++ b/media/webrtc/webrtcvideoengine.cc
@@ -318,20 +318,32 @@
   virtual void IncomingRate(const int videoChannel,
                             const unsigned int framerate,
                             const unsigned int bitrate) {
+    talk_base::CritScope cs(&crit_);
     ASSERT(video_channel_ == videoChannel);
     framerate_ = framerate;
     bitrate_ = bitrate;
   }
   virtual void RequestNewKeyFrame(const int videoChannel) {
+    talk_base::CritScope cs(&crit_);
     ASSERT(video_channel_ == videoChannel);
     ++firs_requested_;
   }
 
-  int framerate() const { return framerate_; }
-  int bitrate() const { return bitrate_; }
-  int firs_requested() const { return firs_requested_; }
+  int framerate() const {
+    talk_base::CritScope cs(&crit_);
+    return framerate_;
+  }
+  int bitrate() const {
+    talk_base::CritScope cs(&crit_);
+    return bitrate_;
+  }
+  int firs_requested() const {
+    talk_base::CritScope cs(&crit_);
+    return firs_requested_;
+  }
 
  private:
+  mutable talk_base::CriticalSection crit_;
   int video_channel_;
   int framerate_;
   int bitrate_;
@@ -350,15 +362,23 @@
   virtual void OutgoingRate(const int videoChannel,
                             const unsigned int framerate,
                             const unsigned int bitrate) {
+    talk_base::CritScope cs(&crit_);
     ASSERT(video_channel_ == videoChannel);
     framerate_ = framerate;
     bitrate_ = bitrate;
   }
 
-  int framerate() const { return framerate_; }
-  int bitrate() const { return bitrate_; }
+  int framerate() const {
+    talk_base::CritScope cs(&crit_);
+    return framerate_;
+  }
+  int bitrate() const {
+    talk_base::CritScope cs(&crit_);
+    return bitrate_;
+  }
 
  private:
+  mutable talk_base::CriticalSection crit_;
   int video_channel_;
   int framerate_;
   int bitrate_;
@@ -914,6 +934,17 @@
   return SetDefaultCodec(config.max_codec);
 }
 
+VideoEncoderConfig WebRtcVideoEngine::GetDefaultEncoderConfig() const {
+  ASSERT(!video_codecs_.empty());
+  VideoCodec max_codec(kVideoCodecPrefs[0].payload_type,
+                       kVideoCodecPrefs[0].name,
+                       video_codecs_[0].width,
+                       video_codecs_[0].height,
+                       video_codecs_[0].framerate,
+                       0);
+  return VideoEncoderConfig(max_codec);
+}
+
 // SetDefaultCodec may be called while the capturer is running. For example, a
 // test call is started in a page with QVGA default codec, and then a real call
 // is started in another page with VGA default codec. This is the corner case
@@ -924,6 +955,7 @@
     return false;
   }
 
+  ASSERT(!video_codecs_.empty());
   default_codec_format_ = VideoFormat(
       video_codecs_[0].width,
       video_codecs_[0].height,
diff --git a/media/webrtc/webrtcvideoengine.h b/media/webrtc/webrtcvideoengine.h
index 2489753..dba8cc9 100644
--- a/media/webrtc/webrtcvideoengine.h
+++ b/media/webrtc/webrtcvideoengine.h
@@ -106,6 +106,7 @@
   int GetCapabilities();
   bool SetOptions(const VideoOptions &options);
   bool SetDefaultEncoderConfig(const VideoEncoderConfig& config);
+  VideoEncoderConfig GetDefaultEncoderConfig() const;
 
   WebRtcVideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_channel);
 
diff --git a/media/webrtc/webrtcvoiceengine.cc b/media/webrtc/webrtcvoiceengine.cc
index 0e96472..5c16d6e 100644
--- a/media/webrtc/webrtcvoiceengine.cc
+++ b/media/webrtc/webrtcvoiceengine.cc
@@ -234,6 +234,9 @@
   }
 
   bool Init() {
+    if (!engine_->voe_sc()) {
+      return false;
+    }
     webrtc_channel_ = engine_->voe_sc()->base()->CreateChannel();
     if (webrtc_channel_ == -1) {
       LOG_RTCERR0(CreateChannel);
@@ -300,6 +303,7 @@
 WebRtcVoiceEngine::WebRtcVoiceEngine()
     : voe_wrapper_(new VoEWrapper()),
       voe_wrapper_sc_(new VoEWrapper()),
+      voe_wrapper_sc_initialized_(false),
       tracing_(new VoETraceWrapper()),
       adm_(NULL),
       adm_sc_(NULL),
@@ -316,6 +320,7 @@
                                      VoETraceWrapper* tracing)
     : voe_wrapper_(voe_wrapper),
       voe_wrapper_sc_(voe_wrapper_sc),
+      voe_wrapper_sc_initialized_(false),
       tracing_(tracing),
       adm_(NULL),
       adm_sc_(NULL),
@@ -539,6 +544,23 @@
     LOG(LS_INFO) << ToString(*it);
   }
 
+  // Disable the DTMF playout when a tone is sent.
+  // PlayDtmfTone will be used if local playout is needed.
+  if (voe_wrapper_->dtmf()->SetDtmfFeedbackStatus(false) == -1) {
+    LOG_RTCERR1(SetDtmfFeedbackStatus, false);
+  }
+
+  initialized_ = true;
+  return true;
+}
+
+bool WebRtcVoiceEngine::EnsureSoundclipEngineInit() {
+  if (voe_wrapper_sc_initialized_) {
+    return true;
+  }
+  // Note that, if initialization fails, voe_wrapper_sc_initialized_ will still
+  // be false, so subsequent calls to EnsureSoundclipEngineInit will
+  // probably just fail again. That's acceptable behavior.
 #if defined(LINUX) && !defined(HAVE_LIBPULSE)
   voe_wrapper_sc_->hw()->SetAudioDeviceLayer(webrtc::kAudioLinuxAlsa);
 #endif
@@ -572,14 +594,8 @@
     }
   }
 #endif
-
-  // Disable the DTMF playout when a tone is sent.
-  // PlayDtmfTone will be used if local playout is needed.
-  if (voe_wrapper_->dtmf()->SetDtmfFeedbackStatus(false) == -1) {
-    LOG_RTCERR1(SetDtmfFeedbackStatus, false);
-  }
-
-  initialized_ = true;
+  voe_wrapper_sc_initialized_ = true;
+  LOG(LS_INFO) << "Initialized WebRtc soundclip engine.";
   return true;
 }
 
@@ -589,7 +605,10 @@
 
   StopAecDump();
 
-  voe_wrapper_sc_->base()->Terminate();
+  if (voe_wrapper_sc_) {
+    voe_wrapper_sc_initialized_ = false;
+    voe_wrapper_sc_->base()->Terminate();
+  }
   voe_wrapper_->base()->Terminate();
   desired_local_monitor_enable_ = false;
 }
@@ -608,6 +627,11 @@
 }
 
 SoundclipMedia *WebRtcVoiceEngine::CreateSoundclip() {
+  if (!EnsureSoundclipEngineInit()) {
+    LOG(LS_ERROR) << "Unable to create soundclip: soundclip engine failed to "
+                  << "initialize.";
+    return NULL;
+  }
   WebRtcSoundclipMedia *soundclip = new WebRtcSoundclipMedia(this);
   if (!soundclip->Init() || !soundclip->Enable()) {
     delete soundclip;
@@ -2158,6 +2182,11 @@
     return false;
   uint32 ssrc = sp.first_ssrc();
 
+  if (ssrc == 0) {
+    LOG(LS_WARNING) << "AddRecvStream with 0 ssrc is not supported.";
+    return false;
+  }
+
   if (receive_channels_.find(ssrc) != receive_channels_.end()) {
     LOG(LS_ERROR) << "Stream already exists with ssrc " << ssrc;
     return false;
@@ -2181,6 +2210,21 @@
     return false;
   }
 
+  if (!ConfigureRecvChannel(channel)) {
+    DeleteChannel(channel);
+    return false;
+  }
+
+  receive_channels_.insert(
+      std::make_pair(ssrc, WebRtcVoiceChannelInfo(channel, NULL)));
+
+  LOG(LS_INFO) << "New audio stream " << ssrc
+               << " registered to VoiceEngine channel #"
+               << channel << ".";
+  return true;
+}
+
+bool WebRtcVoiceMediaChannel::ConfigureRecvChannel(int channel) {
   // Configure to use external transport, like our default channel.
   if (engine()->voe()->network()->RegisterExternalTransport(
           channel, *this) == -1) {
@@ -2237,13 +2281,6 @@
   }
   SetNack(channel, nack_enabled_);
 
-  receive_channels_.insert(
-      std::make_pair(ssrc, WebRtcVoiceChannelInfo(channel, NULL)));
-
-  // TODO(juberti): We should rollback the add if SetPlayout fails.
-  LOG(LS_INFO) << "New audio stream " << ssrc
-            << " registered to VoiceEngine channel #"
-            << channel << ".";
   return SetPlayout(channel, playout_);
 }
 
diff --git a/media/webrtc/webrtcvoiceengine.h b/media/webrtc/webrtcvoiceengine.h
index 6e11485..7809706 100644
--- a/media/webrtc/webrtcvoiceengine.h
+++ b/media/webrtc/webrtcvoiceengine.h
@@ -184,6 +184,7 @@
   void Construct();
   void ConstructCodecs();
   bool InitInternal();
+  bool EnsureSoundclipEngineInit();
   void SetTraceFilter(int filter);
   void SetTraceOptions(const std::string& options);
   // Applies either options or overrides.  Every option that is "set"
@@ -227,6 +228,7 @@
   talk_base::scoped_ptr<VoEWrapper> voe_wrapper_;
   // A secondary instance, for playing out soundclips (on the 'ring' device).
   talk_base::scoped_ptr<VoEWrapper> voe_wrapper_sc_;
+  bool voe_wrapper_sc_initialized_;
   talk_base::scoped_ptr<VoETraceWrapper> tracing_;
   // The external audio device manager
   webrtc::AudioDeviceModule* adm_;
@@ -381,6 +383,7 @@
   bool ChangeSend(SendFlags send);
   bool ChangeSend(int channel, SendFlags send);
   void ConfigureSendChannel(int channel);
+  bool ConfigureRecvChannel(int channel);
   bool DeleteChannel(int channel);
   bool InConferenceMode() const {
     return options_.conference_mode.GetWithDefaultIfUnset(false);
diff --git a/media/webrtc/webrtcvoiceengine_unittest.cc b/media/webrtc/webrtcvoiceengine_unittest.cc
index 2b2d36a..8dc0dff 100644
--- a/media/webrtc/webrtcvoiceengine_unittest.cc
+++ b/media/webrtc/webrtcvoiceengine_unittest.cc
@@ -294,7 +294,8 @@
   EXPECT_FALSE(voe_sc_.IsInited());
   EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
   EXPECT_TRUE(voe_.IsInited());
-  EXPECT_TRUE(voe_sc_.IsInited());
+  // The soundclip engine is lazily initialized.
+  EXPECT_FALSE(voe_sc_.IsInited());
   engine_.Terminate();
   EXPECT_FALSE(voe_.IsInited());
   EXPECT_FALSE(voe_sc_.IsInited());
@@ -2044,6 +2045,26 @@
   EXPECT_EQ(0, voe_.GetNumChannels());
 }
 
+TEST_F(WebRtcVoiceEngineTestFake, TestAddRecvStreamFailWithZeroSsrc) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(0)));
+}
+
+TEST_F(WebRtcVoiceEngineTestFake, TestNoLeakingWhenAddRecvStreamFail) {
+  EXPECT_TRUE(SetupEngine());
+  // Stream 1 reuses default channel.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  // Manually delete default channel to simulate a failure.
+  int default_channel = voe_.GetLastChannel();
+  EXPECT_EQ(0, voe_.DeleteChannel(default_channel));
+  // Add recv stream 2 should fail because default channel is gone.
+  EXPECT_FALSE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(2)));
+  int new_channel = voe_.GetLastChannel();
+  EXPECT_NE(default_channel, new_channel);
+  // The last created channel should have already been deleted.
+  EXPECT_EQ(-1, voe_.DeleteChannel(new_channel));
+}
+
 // Test the InsertDtmf on default send stream as caller.
 TEST_F(WebRtcVoiceEngineTestFake, InsertDtmfOnDefaultSendStreamAsCaller) {
   TestInsertDtmf(0, true);
@@ -2122,7 +2143,9 @@
 // Tests creating soundclips, and make sure they come from the right engine.
 TEST_F(WebRtcVoiceEngineTestFake, CreateSoundclip) {
   EXPECT_TRUE(engine_.Init(talk_base::Thread::Current()));
+  EXPECT_FALSE(voe_sc_.IsInited());
   soundclip_ = engine_.CreateSoundclip();
+  EXPECT_TRUE(voe_sc_.IsInited());
   ASSERT_TRUE(soundclip_ != NULL);
   EXPECT_EQ(0, voe_.GetNumChannels());
   EXPECT_EQ(1, voe_sc_.GetNumChannels());
@@ -2131,6 +2154,10 @@
   delete soundclip_;
   soundclip_ = NULL;
   EXPECT_EQ(0, voe_sc_.GetNumChannels());
+  // Make sure the soundclip engine is uninitialized on shutdown, now that
+  // we've initialized it by creating a soundclip.
+  engine_.Terminate();
+  EXPECT_FALSE(voe_sc_.IsInited());
 }
 
 // Tests playing out a fake sound.
diff --git a/p2p/base/dtlstransport.h b/p2p/base/dtlstransport.h
index 93da103..7492171 100644
--- a/p2p/base/dtlstransport.h
+++ b/p2p/base/dtlstransport.h
@@ -58,6 +58,13 @@
   virtual void SetIdentity_w(talk_base::SSLIdentity* identity) {
     identity_ = identity;
   }
+  virtual bool GetIdentity_w(talk_base::SSLIdentity** identity) {
+    if (!identity_)
+      return false;
+
+    *identity = identity_->GetReference();
+    return true;
+  }
 
   virtual bool ApplyLocalTransportDescription_w(TransportChannelImpl*
                                                 channel) {
diff --git a/p2p/base/dtlstransportchannel.cc b/p2p/base/dtlstransportchannel.cc
index dead3a5..a92e7cc 100644
--- a/p2p/base/dtlstransportchannel.cc
+++ b/p2p/base/dtlstransportchannel.cc
@@ -173,6 +173,15 @@
   return true;
 }
 
+bool DtlsTransportChannelWrapper::GetLocalIdentity(
+    talk_base::SSLIdentity** identity) const {
+  if (!local_identity_)
+    return false;
+
+  *identity = local_identity_->GetReference();
+  return true;
+}
+
 bool DtlsTransportChannelWrapper::SetSslRole(talk_base::SSLRole role) {
   if (dtls_state_ == STATE_OPEN) {
     if (ssl_role_ != role) {
@@ -230,6 +239,14 @@
   return true;
 }
 
+bool DtlsTransportChannelWrapper::GetRemoteCertificate(
+    talk_base::SSLCertificate** cert) const {
+  if (!dtls_)
+    return false;
+
+  return dtls_->GetPeerCertificate(cert);
+}
+
 bool DtlsTransportChannelWrapper::SetupDtls() {
   StreamInterfaceChannel* downward =
       new StreamInterfaceChannel(worker_thread_, channel_);
diff --git a/p2p/base/dtlstransportchannel.h b/p2p/base/dtlstransportchannel.h
index aec8c7a..29d97a2 100644
--- a/p2p/base/dtlstransportchannel.h
+++ b/p2p/base/dtlstransportchannel.h
@@ -128,6 +128,7 @@
     return channel_->GetIceRole();
   }
   virtual bool SetLocalIdentity(talk_base::SSLIdentity *identity);
+  virtual bool GetLocalIdentity(talk_base::SSLIdentity** identity) const;
 
   virtual bool SetRemoteFingerprint(const std::string& digest_alg,
                                     const uint8* digest,
@@ -164,6 +165,10 @@
   virtual bool GetSslRole(talk_base::SSLRole* role) const;
   virtual bool SetSslRole(talk_base::SSLRole role);
 
+  // Once DTLS has been established, this method retrieves the certificate in
+  // use by the remote peer, for use in external identity verification.
+  virtual bool GetRemoteCertificate(talk_base::SSLCertificate** cert) const;
+
   // Once DTLS has established (i.e., this channel is writable), this method
   // extracts the keys negotiated during the DTLS handshake, for use in external
   // encryption. DTLS-SRTP uses this to extract the needed SRTP keys.
diff --git a/p2p/base/dtlstransportchannel_unittest.cc b/p2p/base/dtlstransportchannel_unittest.cc
index 267d60b..6a4e5ad 100644
--- a/p2p/base/dtlstransportchannel_unittest.cc
+++ b/p2p/base/dtlstransportchannel_unittest.cc
@@ -751,3 +751,56 @@
   TestTransfer(0, 1000, 100, true);
   TestTransfer(1, 1000, 100, true);
 }
+
+// Test Certificates state after negotiation but before connection.
+TEST_F(DtlsTransportChannelTest, TestCertificatesBeforeConnect) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  PrepareDtls(true, true);
+  Negotiate();
+
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity1;
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity2;
+  talk_base::scoped_ptr<talk_base::SSLCertificate> remote_cert1;
+  talk_base::scoped_ptr<talk_base::SSLCertificate> remote_cert2;
+
+  // After negotiation, each side has a distinct local certificate, but still no
+  // remote certificate, because connection has not yet occurred.
+  ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept()));
+  ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept()));
+  ASSERT_NE(identity1->certificate().ToPEMString(),
+            identity2->certificate().ToPEMString());
+  ASSERT_FALSE(
+      client1_.transport()->GetRemoteCertificate(remote_cert1.accept()));
+  ASSERT_FALSE(remote_cert1 != NULL);
+  ASSERT_FALSE(
+      client2_.transport()->GetRemoteCertificate(remote_cert2.accept()));
+  ASSERT_FALSE(remote_cert2 != NULL);
+}
+
+// Test Certificates state after connection.
+TEST_F(DtlsTransportChannelTest, TestCertificatesAfterConnect) {
+  MAYBE_SKIP_TEST(HaveDtls);
+  PrepareDtls(true, true);
+  ASSERT_TRUE(Connect());
+
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity1;
+  talk_base::scoped_ptr<talk_base::SSLIdentity> identity2;
+  talk_base::scoped_ptr<talk_base::SSLCertificate> remote_cert1;
+  talk_base::scoped_ptr<talk_base::SSLCertificate> remote_cert2;
+
+  // After connection, each side has a distinct local certificate.
+  ASSERT_TRUE(client1_.transport()->GetIdentity(identity1.accept()));
+  ASSERT_TRUE(client2_.transport()->GetIdentity(identity2.accept()));
+  ASSERT_NE(identity1->certificate().ToPEMString(),
+            identity2->certificate().ToPEMString());
+
+  // Each side's remote certificate is the other side's local certificate.
+  ASSERT_TRUE(
+      client1_.transport()->GetRemoteCertificate(remote_cert1.accept()));
+  ASSERT_EQ(remote_cert1->ToPEMString(),
+            identity2->certificate().ToPEMString());
+  ASSERT_TRUE(
+      client2_.transport()->GetRemoteCertificate(remote_cert2.accept()));
+  ASSERT_EQ(remote_cert2->ToPEMString(),
+            identity1->certificate().ToPEMString());
+}
diff --git a/p2p/base/fakesession.h b/p2p/base/fakesession.h
index bc05ce2..9a8fada 100644
--- a/p2p/base/fakesession.h
+++ b/p2p/base/fakesession.h
@@ -33,6 +33,7 @@
 #include <vector>
 
 #include "talk/base/buffer.h"
+#include "talk/base/fakesslidentity.h"
 #include "talk/base/sigslot.h"
 #include "talk/base/sslfingerprint.h"
 #include "talk/base/messagequeue.h"
@@ -212,11 +213,16 @@
     return true;
   }
 
-  bool IsDtlsActive() const {
+
+  void SetRemoteCertificate(talk_base::FakeSSLCertificate* cert) {
+    remote_cert_ = cert;
+  }
+
+  virtual bool IsDtlsActive() const {
     return do_dtls_;
   }
 
-  bool SetSrtpCiphers(const std::vector<std::string>& ciphers) {
+  virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) {
     srtp_ciphers_ = ciphers;
     return true;
   }
@@ -229,6 +235,22 @@
     return false;
   }
 
+  virtual bool GetLocalIdentity(talk_base::SSLIdentity** identity) const {
+    if (!identity_)
+      return false;
+
+    *identity = identity_->GetReference();
+    return true;
+  }
+
+  virtual bool GetRemoteCertificate(talk_base::SSLCertificate** cert) const {
+    if (!remote_cert_)
+      return false;
+
+    *cert = remote_cert_->GetReference();
+    return true;
+  }
+
   virtual bool ExportKeyingMaterial(const std::string& label,
                                     const uint8* context,
                                     size_t context_len,
@@ -272,6 +294,7 @@
   State state_;
   bool async_;
   talk_base::SSLIdentity* identity_;
+  talk_base::FakeSSLCertificate* remote_cert_;
   bool do_dtls_;
   std::vector<std::string> srtp_ciphers_;
   std::string chosen_srtp_cipher_;
@@ -349,6 +372,16 @@
     channels_.erase(channel->component());
     delete channel;
   }
+  virtual void SetIdentity_w(talk_base::SSLIdentity* identity) {
+    identity_ = identity;
+  }
+  virtual bool GetIdentity_w(talk_base::SSLIdentity** identity) {
+    if (!identity_)
+      return false;
+
+    *identity = identity_->GetReference();
+    return true;
+  }
 
  private:
   FakeTransportChannel* GetFakeChannel(int component) {
diff --git a/p2p/base/p2ptransportchannel.h b/p2p/base/p2ptransportchannel.h
index 2fc7186..63ec6aa 100644
--- a/p2p/base/p2ptransportchannel.h
+++ b/p2p/base/p2ptransportchannel.h
@@ -127,6 +127,15 @@
     return false;
   }
 
+  // Returns false because the channel is not encrypted by default.
+  virtual bool GetLocalIdentity(talk_base::SSLIdentity** identity) const {
+    return false;
+  }
+
+  virtual bool GetRemoteCertificate(talk_base::SSLCertificate** cert) const {
+    return false;
+  }
+
   // Allows key material to be extracted for external encryption.
   virtual bool ExportKeyingMaterial(
       const std::string& label,
diff --git a/p2p/base/rawtransportchannel.h b/p2p/base/rawtransportchannel.h
index 2aac2b5..ed38952 100644
--- a/p2p/base/rawtransportchannel.h
+++ b/p2p/base/rawtransportchannel.h
@@ -128,6 +128,15 @@
     return false;
   }
 
+  // Returns false because the channel is not DTLS.
+  virtual bool GetLocalIdentity(talk_base::SSLIdentity** identity) const {
+    return false;
+  }
+
+  virtual bool GetRemoteCertificate(talk_base::SSLCertificate** cert) const {
+    return false;
+  }
+
   // Allows key material to be extracted for external encryption.
   virtual bool ExportKeyingMaterial(
       const std::string& label,
diff --git a/p2p/base/session.h b/p2p/base/session.h
index 12310bc..292e7a5 100644
--- a/p2p/base/session.h
+++ b/p2p/base/session.h
@@ -350,7 +350,7 @@
 
   // Returns the transport that has been negotiated or NULL if
   // negotiation is still in progress.
-  Transport* GetTransport(const std::string& content_name);
+  virtual Transport* GetTransport(const std::string& content_name);
 
   // Creates a new channel with the given names.  This method may be called
   // immediately after creating the session.  However, the actual
diff --git a/p2p/base/transport.cc b/p2p/base/transport.cc
index 3e4ad70..4404c08 100644
--- a/p2p/base/transport.cc
+++ b/p2p/base/transport.cc
@@ -107,6 +107,29 @@
   worker_thread_->Invoke<void>(Bind(&Transport::SetIdentity_w, this, identity));
 }
 
+bool Transport::GetIdentity(talk_base::SSLIdentity** identity) {
+  // The identity is set on the worker thread, so for safety it must also be
+  // acquired on the worker thread.
+  return worker_thread_->Invoke<bool>(
+      Bind(&Transport::GetIdentity_w, this, identity));
+}
+
+bool Transport::GetRemoteCertificate(talk_base::SSLCertificate** cert) {
+  // Channels can be deleted on the worker thread, so for safety the remote
+  // certificate is acquired on the worker thread.
+  return worker_thread_->Invoke<bool>(
+      Bind(&Transport::GetRemoteCertificate_w, this, cert));
+}
+
+bool Transport::GetRemoteCertificate_w(talk_base::SSLCertificate** cert) {
+  ASSERT(worker_thread()->IsCurrent());
+  if (channels_.empty())
+    return false;
+
+  ChannelMap::iterator iter = channels_.begin();
+  return iter->second->GetRemoteCertificate(cert);
+}
+
 bool Transport::SetLocalTransportDescription(
     const TransportDescription& description, ContentAction action) {
   return worker_thread_->Invoke<bool>(Bind(
diff --git a/p2p/base/transport.h b/p2p/base/transport.h
index 381215f..f9e9d88 100644
--- a/p2p/base/transport.h
+++ b/p2p/base/transport.h
@@ -247,6 +247,12 @@
   // Must be called before applying local session description.
   void SetIdentity(talk_base::SSLIdentity* identity);
 
+  // Get a copy of the local identity provided by SetIdentity.
+  bool GetIdentity(talk_base::SSLIdentity** identity);
+
+  // Get a copy of the remote certificate in use by the specified channel.
+  bool GetRemoteCertificate(talk_base::SSLCertificate** cert);
+
   TransportProtocol protocol() const { return protocol_; }
 
   // Create, destroy, and lookup the channels of this type by their components.
@@ -349,6 +355,10 @@
 
   virtual void SetIdentity_w(talk_base::SSLIdentity* identity) {}
 
+  virtual bool GetIdentity_w(talk_base::SSLIdentity** identity) {
+    return false;
+  }
+
   // Pushes down the transport parameters from the local description, such
   // as the ICE ufrag and pwd.
   // Derived classes can override, but must call the base as well.
@@ -462,6 +472,8 @@
   bool SetRemoteTransportDescription_w(const TransportDescription& desc,
                                        ContentAction action);
   bool GetStats_w(TransportStats* infos);
+  bool GetRemoteCertificate_w(talk_base::SSLCertificate** cert);
+
 
   talk_base::Thread* signaling_thread_;
   talk_base::Thread* worker_thread_;
diff --git a/p2p/base/transportchannel.h b/p2p/base/transportchannel.h
index 85fff7a..c48e1a5 100644
--- a/p2p/base/transportchannel.h
+++ b/p2p/base/transportchannel.h
@@ -101,12 +101,18 @@
   // Default implementation.
   virtual bool GetSslRole(talk_base::SSLRole* role) const = 0;
 
-  // Set up the ciphers to use for DTLS-SRTP.
+  // Sets up the ciphers to use for DTLS-SRTP.
   virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers) = 0;
 
-  // Find out which DTLS-SRTP cipher was negotiated
+  // Finds out which DTLS-SRTP cipher was negotiated
   virtual bool GetSrtpCipher(std::string* cipher) = 0;
 
+  // Gets a copy of the local SSL identity, owned by the caller.
+  virtual bool GetLocalIdentity(talk_base::SSLIdentity** identity) const = 0;
+
+  // Gets a copy of the remote side's SSL certificate, owned by the caller.
+  virtual bool GetRemoteCertificate(talk_base::SSLCertificate** cert) const = 0;
+
   // Allows key material to be extracted for external encryption.
   virtual bool ExportKeyingMaterial(const std::string& label,
       const uint8* context,
diff --git a/p2p/base/transportchannelimpl.h b/p2p/base/transportchannelimpl.h
index cde2441..d8432b7 100644
--- a/p2p/base/transportchannelimpl.h
+++ b/p2p/base/transportchannelimpl.h
@@ -93,7 +93,10 @@
   virtual void OnCandidate(const Candidate& candidate) = 0;
 
   // DTLS methods
-  // Set DTLS local identity.
+  // Set DTLS local identity.  The identity object is not copied, but the caller
+  // retains ownership and must delete it after this TransportChannelImpl is
+  // destroyed.
+  // TODO(bemasc): Fix the ownership semantics of this method.
   virtual bool SetLocalIdentity(talk_base::SSLIdentity* identity) = 0;
 
   // Set DTLS Remote fingerprint. Must be after local identity set.
diff --git a/p2p/base/transportchannelproxy.cc b/p2p/base/transportchannelproxy.cc
index 318d133..9a10603 100644
--- a/p2p/base/transportchannelproxy.cc
+++ b/p2p/base/transportchannelproxy.cc
@@ -180,6 +180,24 @@
   return impl_->GetSrtpCipher(cipher);
 }
 
+bool TransportChannelProxy::GetLocalIdentity(
+    talk_base::SSLIdentity** identity) const {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return false;
+  }
+  return impl_->GetLocalIdentity(identity);
+}
+
+bool TransportChannelProxy::GetRemoteCertificate(
+    talk_base::SSLCertificate** cert) const {
+  ASSERT(talk_base::Thread::Current() == worker_thread_);
+  if (!impl_) {
+    return false;
+  }
+  return impl_->GetRemoteCertificate(cert);
+}
+
 bool TransportChannelProxy::ExportKeyingMaterial(const std::string& label,
                                                  const uint8* context,
                                                  size_t context_len,
diff --git a/p2p/base/transportchannelproxy.h b/p2p/base/transportchannelproxy.h
index 29f4663..3559ed5 100644
--- a/p2p/base/transportchannelproxy.h
+++ b/p2p/base/transportchannelproxy.h
@@ -75,6 +75,8 @@
   virtual bool SetSslRole(talk_base::SSLRole role);
   virtual bool SetSrtpCiphers(const std::vector<std::string>& ciphers);
   virtual bool GetSrtpCipher(std::string* cipher);
+  virtual bool GetLocalIdentity(talk_base::SSLIdentity** identity) const;
+  virtual bool GetRemoteCertificate(talk_base::SSLCertificate** cert) const;
   virtual bool ExportKeyingMaterial(const std::string& label,
                             const uint8* context,
                             size_t context_len,
diff --git a/session/media/channel.cc b/session/media/channel.cc
index cfe58a7..e00ba16 100644
--- a/session/media/channel.cc
+++ b/session/media/channel.cc
@@ -411,6 +411,7 @@
 
 BaseChannel::~BaseChannel() {
   ASSERT(worker_thread_ == talk_base::Thread::Current());
+  Deinit();
   StopConnectionMonitor();
   FlushRtcpMessages();  // Send any outstanding RTCP packets.
   Clear();  // eats any outstanding messages or packets
@@ -455,6 +456,10 @@
   return true;
 }
 
+void BaseChannel::Deinit() {
+  media_channel_->SetInterface(NULL);
+}
+
 // Can be called from thread other than worker thread
 bool BaseChannel::Enable(bool enable) {
   Send(enable ? MSG_ENABLE : MSG_DISABLE);
@@ -1466,6 +1471,7 @@
   StopMediaMonitor();
   // this can't be done in the base class, since it calls a virtual
   DisableMedia_w();
+  Deinit();
 }
 
 bool VoiceChannel::Init() {
@@ -1961,6 +1967,8 @@
   StopMediaMonitor();
   // this can't be done in the base class, since it calls a virtual
   DisableMedia_w();
+
+  Deinit();
 }
 
 bool VideoChannel::SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
@@ -2464,6 +2472,8 @@
   StopMediaMonitor();
   // this can't be done in the base class, since it calls a virtual
   DisableMedia_w();
+
+  Deinit();
 }
 
 bool DataChannel::Init() {
diff --git a/session/media/channel.h b/session/media/channel.h
index 0d66be9..ccfdd07 100644
--- a/session/media/channel.h
+++ b/session/media/channel.h
@@ -66,6 +66,12 @@
 // BaseChannel contains logic common to voice and video, including
 // enable/mute, marshaling calls to a worker thread, and
 // connection and media monitors.
+//
+// WARNING! SUBCLASSES MUST CALL Deinit() IN THEIR DESTRUCTORS!
+// This is required to avoid a data race between the destructor modifying the
+// vtable, and the media channel's thread using BaseChannel as the
+// NetworkInterface.
+
 class BaseChannel
     : public talk_base::MessageHandler, public sigslot::has_slots<>,
       public MediaChannel::NetworkInterface {
@@ -76,6 +82,9 @@
   virtual ~BaseChannel();
   bool Init(TransportChannel* transport_channel,
             TransportChannel* rtcp_transport_channel);
+  // Deinit may be called multiple times and is simply ignored if it's alreay
+  // done.
+  void Deinit();
 
   talk_base::Thread* worker_thread() const { return worker_thread_; }
   BaseSession* session() const { return session_; }
diff --git a/session/media/mediasession.cc b/session/media/mediasession.cc
index 799fe5f..3d1c468 100644
--- a/session/media/mediasession.cc
+++ b/session/media/mediasession.cc
@@ -599,6 +599,7 @@
     return false;
   }
 
+  bool common_cryptos_needed = false;
   // Get the common cryptos.
   const ContentNames& content_names = bundle_group.content_names();
   CryptoParamsVec common_cryptos;
@@ -607,6 +608,11 @@
     if (!IsRtpContent(sdesc, *it)) {
       continue;
     }
+    // The common cryptos are needed if any of the content does not have DTLS
+    // enabled.
+    if (!sdesc->GetTransportInfoByName(*it)->description.secure()) {
+      common_cryptos_needed = true;
+    }
     if (it == content_names.begin()) {
       // Initial the common_cryptos with the first content in the bundle group.
       if (!GetCryptosByName(sdesc, *it, &common_cryptos)) {
@@ -625,7 +631,7 @@
     }
   }
 
-  if (common_cryptos.empty()) {
+  if (common_cryptos.empty() && common_cryptos_needed) {
     return false;
   }
 
diff --git a/session/media/mediasession_unittest.cc b/session/media/mediasession_unittest.cc
index f2e576c..f014e78 100644
--- a/session/media/mediasession_unittest.cc
+++ b/session/media/mediasession_unittest.cc
@@ -536,8 +536,8 @@
   ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
   EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
 }
-// Create a typical data offer, and ensure it matches what we expect.
-TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataOffer) {
+// Create a RTP data offer, and ensure it matches what we expect.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateRtpDataOffer) {
   MediaSessionOptions opts;
   opts.data_channel_type = cricket::DCT_RTP;
   f1_.set_secure(SEC_ENABLED);
@@ -571,6 +571,18 @@
   EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
 }
 
+// Create an SCTP data offer with bundle without error.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) {
+  MediaSessionOptions opts;
+  opts.has_audio = false;
+  opts.bundle_enabled = true;
+  opts.data_channel_type = cricket::DCT_SCTP;
+  f1_.set_secure(SEC_ENABLED);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  EXPECT_TRUE(offer.get() != NULL);
+  EXPECT_TRUE(offer->GetContentByName("data") != NULL);
+}
+
 // Create an audio, video offer without legacy StreamParams.
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateOfferWithoutLegacyStreams) {