Update talk to 55821645.

TEST=try bots
R=mallinath@webrtc.org

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

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5053 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/datachannel.cc b/talk/app/webrtc/datachannel.cc
index 3de001b..3a6046e 100644
--- a/talk/app/webrtc/datachannel.cc
+++ b/talk/app/webrtc/datachannel.cc
@@ -31,6 +31,7 @@
 #include "talk/app/webrtc/mediastreamprovider.h"
 #include "talk/base/logging.h"
 #include "talk/base/refcount.h"
+#include "talk/media/sctp/sctputils.h"
 
 namespace webrtc {
 
@@ -68,36 +69,35 @@
 }
 
 bool DataChannel::Init(const DataChannelInit* config) {
-  if (config) {
-    if (data_channel_type_ == cricket::DCT_RTP &&
-        (config->reliable ||
-         config->id != -1 ||
-         config->maxRetransmits != -1 ||
-         config->maxRetransmitTime != -1)) {
-      LOG(LS_ERROR) << "Failed to initialize the RTP data channel due to "
+  if (data_channel_type_ == cricket::DCT_RTP &&
+      (config->reliable ||
+       config->id != -1 ||
+       config->maxRetransmits != -1 ||
+       config->maxRetransmitTime != -1)) {
+    LOG(LS_ERROR) << "Failed to initialize the RTP data channel due to "
+                  << "invalid DataChannelInit.";
+    return false;
+  } else if (data_channel_type_ == cricket::DCT_SCTP) {
+    if (config->id < -1 ||
+        config->maxRetransmits < -1 ||
+        config->maxRetransmitTime < -1) {
+      LOG(LS_ERROR) << "Failed to initialize the SCTP data channel due to "
                     << "invalid DataChannelInit.";
       return false;
-    } else if (data_channel_type_ == cricket::DCT_SCTP) {
-      if (config->id < -1 ||
-          config->maxRetransmits < -1 ||
-          config->maxRetransmitTime < -1) {
-        LOG(LS_ERROR) << "Failed to initialize the SCTP data channel due to "
-                      << "invalid DataChannelInit.";
-        return false;
-      }
-      if (config->maxRetransmits != -1 && config->maxRetransmitTime != -1) {
-        LOG(LS_ERROR) <<
-            "maxRetransmits and maxRetransmitTime should not be both set.";
-        return false;
-      }
+    }
+    if (config->maxRetransmits != -1 && config->maxRetransmitTime != -1) {
+      LOG(LS_ERROR) <<
+          "maxRetransmits and maxRetransmitTime should not be both set.";
+      return false;
     }
     config_ = *config;
-  }
-  return true;
-}
 
-bool DataChannel::HasNegotiationCompleted() {
-  return send_ssrc_set_ == receive_ssrc_set_;
+    // Try to connect to the transport in case the transport channel already
+    // exists.
+    OnTransportChannelCreated();
+  }
+
+  return true;
 }
 
 DataChannel::~DataChannel() {
@@ -169,16 +169,13 @@
   queued_control_data_.push(buffer);
 }
 
-bool DataChannel::SendControl(const talk_base::Buffer* buffer) {
-  if (data_channel_type_ == cricket::DCT_RTP) {
-    delete buffer;
-    return false;
-  }
+bool DataChannel::SendOpenMessage(const talk_base::Buffer* raw_buffer) {
+  ASSERT(data_channel_type_ == cricket::DCT_SCTP &&
+         was_ever_writable_ &&
+         config_.id >= 0 &&
+         !config_.negotiated);
 
-  if (state_ != kOpen) {
-    QueueControl(buffer);
-    return true;
-  }
+  talk_base::scoped_ptr<const talk_base::Buffer> buffer(raw_buffer);
 
   cricket::SendDataParams send_params;
   send_params.ssrc = config_.id;
@@ -189,18 +186,15 @@
   bool retval = provider_->SendData(send_params, *buffer, &send_result);
   if (!retval && send_result == cricket::SDR_BLOCK) {
     // Link is congested.  Queue for later.
-    QueueControl(buffer);
-  } else {
-    delete buffer;
+    QueueControl(buffer.release());
   }
   return retval;
 }
 
 void DataChannel::SetReceiveSsrc(uint32 receive_ssrc) {
+  ASSERT(data_channel_type_ == cricket::DCT_RTP);
+
   if (receive_ssrc_set_) {
-    ASSERT(data_channel_type_ == cricket::DCT_RTP ||
-           !send_ssrc_set_ ||
-           receive_ssrc_ == send_ssrc_);
     return;
   }
   receive_ssrc_ = receive_ssrc;
@@ -214,10 +208,8 @@
 }
 
 void DataChannel::SetSendSsrc(uint32 send_ssrc) {
+  ASSERT(data_channel_type_ == cricket::DCT_RTP);
   if (send_ssrc_set_) {
-    ASSERT(data_channel_type_ == cricket::DCT_RTP ||
-           !receive_ssrc_set_ ||
-           receive_ssrc_ == send_ssrc_);
     return;
   }
   send_ssrc_ = send_ssrc;
@@ -263,8 +255,18 @@
   // for sending and now unblocked, so send the queued data now.
   if (!was_ever_writable_) {
     was_ever_writable_ = true;
+
+    if (data_channel_type_ == cricket::DCT_SCTP && !config_.negotiated) {
+      talk_base::Buffer* payload = new talk_base::Buffer;
+      if (!cricket::WriteDataChannelOpenMessage(label_, config_, payload)) {
+        // TODO(jiayl): close the data channel on this error.
+        LOG(LS_ERROR) << "Could not write data channel OPEN message";
+        return;
+      }
+      SendOpenMessage(payload);
+    }
+
     UpdateState();
-    DeliverQueuedControlData();
     ASSERT(queued_send_data_.empty());
   } else if (state_ == kOpen) {
     DeliverQueuedSendData();
@@ -281,11 +283,15 @@
 void DataChannel::UpdateState() {
   switch (state_) {
     case kConnecting: {
-      if (HasNegotiationCompleted()) {
-        if (!connected_to_provider_) {
-          ConnectToDataSession();
+      if (send_ssrc_set_ == receive_ssrc_set_) {
+        if (data_channel_type_ == cricket::DCT_RTP && !connected_to_provider_) {
+          connected_to_provider_ = provider_->ConnectDataChannel(this);
+          provider_->AddRtpDataStream(send_ssrc_, receive_ssrc_);
         }
         if (was_ever_writable_) {
+          // TODO(jiayl): Do not transition to kOpen if we failed to send the
+          // OPEN message.
+          DeliverQueuedControlData();
           SetState(kOpen);
           // If we have received buffers before the channel got writable.
           // Deliver them now.
@@ -298,10 +304,9 @@
       break;
     }
     case kClosing: {
-      if (connected_to_provider_) {
-        DisconnectFromDataSession();
-      }
-      if (HasNegotiationCompleted()) {
+      DisconnectFromTransport();
+
+      if (!send_ssrc_set_ && !receive_ssrc_set_) {
         SetState(kClosed);
       }
       break;
@@ -318,13 +323,18 @@
   }
 }
 
-void DataChannel::ConnectToDataSession() {
-  connected_to_provider_ = provider_->ConnectDataChannel(this);
-}
+void DataChannel::DisconnectFromTransport() {
+  if (!connected_to_provider_)
+    return;
 
-void DataChannel::DisconnectFromDataSession() {
   provider_->DisconnectDataChannel(this);
   connected_to_provider_ = false;
+
+  if (data_channel_type_ == cricket::DCT_RTP) {
+    provider_->RemoveRtpDataStream(send_ssrc_, receive_ssrc_);
+  } else {
+    provider_->RemoveSctpDataStream(config_.id);
+  }
 }
 
 void DataChannel::DeliverQueuedReceivedData() {
@@ -349,10 +359,12 @@
 }
 
 void DataChannel::DeliverQueuedSendData() {
+  ASSERT(was_ever_writable_ && state_ == kOpen);
+
+  // TODO(jiayl): Sending OPEN message here contradicts with the pre-condition
+  // that the readyState is open. According to the standard, the channel should
+  // not become open before the OPEN message is sent.
   DeliverQueuedControlData();
-  if (!was_ever_writable_) {
-    return;
-  }
 
   while (!queued_send_data_.empty()) {
     DataBuffer* buffer = queued_send_data_.front();
@@ -376,12 +388,11 @@
 }
 
 void DataChannel::DeliverQueuedControlData() {
-  if (was_ever_writable_) {
-    while (!queued_control_data_.empty()) {
-      const talk_base::Buffer *buf = queued_control_data_.front();
-      queued_control_data_.pop();
-      SendControl(buf);
-    }
+  ASSERT(was_ever_writable_);
+  while (!queued_control_data_.empty()) {
+    const talk_base::Buffer* buf = queued_control_data_.front();
+    queued_control_data_.pop();
+    SendOpenMessage(buf);
   }
 }
 
@@ -417,4 +428,22 @@
   return true;
 }
 
+void DataChannel::SetSctpSid(int sid) {
+  ASSERT(config_.id < 0 && sid >= 0 && data_channel_type_ == cricket::DCT_SCTP);
+  config_.id = sid;
+  provider_->AddSctpDataStream(sid);
+}
+
+void DataChannel::OnTransportChannelCreated() {
+  ASSERT(data_channel_type_ == cricket::DCT_SCTP);
+  if (!connected_to_provider_) {
+    connected_to_provider_ = provider_->ConnectDataChannel(this);
+  }
+  // The sid may have been unassigned when provider_->ConnectDataChannel was
+  // done. So always add the streams even if connected_to_provider_ is true.
+  if (config_.id >= 0) {
+    provider_->AddSctpDataStream(config_.id);
+  }
+}
+
 }  // namespace webrtc
diff --git a/talk/app/webrtc/datachannel.h b/talk/app/webrtc/datachannel.h
index a09c7f6..0d67293 100644
--- a/talk/app/webrtc/datachannel.h
+++ b/talk/app/webrtc/datachannel.h
@@ -44,24 +44,35 @@
 
 class DataChannelProviderInterface {
  public:
+  // Sends the data to the transport.
   virtual bool SendData(const cricket::SendDataParams& params,
                         const talk_base::Buffer& payload,
                         cricket::SendDataResult* result) = 0;
+  // Connects to the transport signals.
   virtual bool ConnectDataChannel(DataChannel* data_channel) = 0;
+  // Disconnects from the transport signals.
   virtual void DisconnectDataChannel(DataChannel* data_channel) = 0;
+  // Adds the send and receive stream ssrc to the transport for RTP.
+  virtual void AddRtpDataStream(uint32 send_ssrc, uint32 recv_ssrc) = 0;
+  // Adds the data channel SID to the transport for SCTP.
+  virtual void AddSctpDataStream(uint32 sid) = 0;
+  // Removes the data channel ssrcs from the transport for RTP.
+  virtual void RemoveRtpDataStream(uint32 send_ssrc, uint32 recv_ssrc) = 0;
+  // Removes the data channel SID from the transport for SCTP.
+  virtual void RemoveSctpDataStream(uint32 sid) = 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
-// channels. Currently this class is specifically designed to use RtpDataEngine,
-// and will changed to use SCTP in the future.
+// libjingle's data engine. It provides an implementation of unreliable or
+// reliabledata channels. Currently this class is specifically designed to use
+// both RtpDataEngine and SctpDataEngine.
 
 // DataChannel states:
-// kConnecting: The channel has been created but SSRC for sending and receiving
-//              has not yet been set and the transport might not yet be ready.
+// kConnecting: The channel has been created the transport might not yet be
+//              ready.
 // kOpen: The channel have a local SSRC set by a call to UpdateSendSsrc
 //        and a remote SSRC set by call to UpdateReceiveSsrc and the transport
 //        has been writable once.
@@ -73,7 +84,7 @@
                     public sigslot::has_slots<> {
  public:
   static talk_base::scoped_refptr<DataChannel> Create(
-      DataChannelProviderInterface* client,
+      DataChannelProviderInterface* provider,
       cricket::DataChannelType dct,
       const std::string& label,
       const DataChannelInit* config);
@@ -97,20 +108,6 @@
   virtual void Close();
   virtual DataState state() const { return state_; }
   virtual bool Send(const DataBuffer& buffer);
-  // Send a control message right now, or queue for later.
-  virtual bool SendControl(const talk_base::Buffer* buffer);
-  void ConnectToDataSession();
-
-  // Set the SSRC this channel should use to receive data from the
-  // underlying data engine.
-  void SetReceiveSsrc(uint32 receive_ssrc);
-  // The remote peer request that this channel should be closed.
-  void RemotePeerRequestClose();
-
-  // Set the SSRC this channel should use to send data on the
-  // underlying data engine. |send_ssrc| == 0 means that the channel is no
-  // longer part of the session negotiation.
-  void SetSendSsrc(uint32 send_ssrc);
 
   // Called if the underlying data engine is closing.
   void OnDataEngineClose();
@@ -125,20 +122,38 @@
                       const cricket::ReceiveDataParams& params,
                       const talk_base::Buffer& payload);
 
+  // The remote peer request that this channel should be closed.
+  void RemotePeerRequestClose();
+
+  // The following methods are for SCTP only.
+
+  // Sets the SCTP sid and adds to transport layer if not set yet.
+  void SetSctpSid(int sid);
+  // Called when the transport channel is created.
+  void OnTransportChannelCreated();
+
+  // The following methods are for RTP only.
+
+  // Set the SSRC this channel should use to send data on the
+  // underlying data engine. |send_ssrc| == 0 means that the channel is no
+  // longer part of the session negotiation.
+  void SetSendSsrc(uint32 send_ssrc);
+  // Set the SSRC this channel should use to receive data from the
+  // underlying data engine.
+  void SetReceiveSsrc(uint32 receive_ssrc);
+
  protected:
   DataChannel(DataChannelProviderInterface* client,
               cricket::DataChannelType dct,
               const std::string& label);
   virtual ~DataChannel();
 
-  bool Init(const DataChannelInit* config);
-  bool HasNegotiationCompleted();
-
  private:
+  bool Init(const DataChannelInit* config);
   void DoClose();
   void UpdateState();
   void SetState(DataState state);
-  void DisconnectFromDataSession();
+  void DisconnectFromTransport();
   void DeliverQueuedControlData();
   void QueueControl(const talk_base::Buffer* buffer);
   void ClearQueuedControlData();
@@ -149,6 +164,8 @@
   bool InternalSendWithoutQueueing(const DataBuffer& buffer,
                                    cricket::SendDataResult* send_result);
   bool QueueSendData(const DataBuffer& buffer);
+  bool SendOpenMessage(const talk_base::Buffer* buffer);
+
 
   std::string label_;
   DataChannelInit config_;
diff --git a/talk/app/webrtc/datachannel_unittest.cc b/talk/app/webrtc/datachannel_unittest.cc
index 4d66907..5d29811 100644
--- a/talk/app/webrtc/datachannel_unittest.cc
+++ b/talk/app/webrtc/datachannel_unittest.cc
@@ -26,56 +26,56 @@
  */
 
 #include "talk/app/webrtc/datachannel.h"
+#include "talk/app/webrtc/test/fakedatachannelprovider.h"
 #include "talk/base/gunit.h"
 
 using webrtc::DataChannel;
 
-class FakeDataChannelClient : public webrtc::DataChannelProviderInterface {
- public:
-  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 bool ConnectDataChannel(DataChannel* data_channel) OVERRIDE {
-    return true;
-  }
-  virtual void DisconnectDataChannel(DataChannel* data_channel) OVERRIDE {}
-
-  void set_send_blocked(bool blocked) { send_blocked_ = blocked; }
-  cricket::SendDataParams last_send_data_params() {
-      return last_send_data_params_;
-  }
-
- private:
-  cricket::SendDataParams last_send_data_params_;
-  bool send_blocked_;
-};
-
 class SctpDataChannelTest : public testing::Test {
  protected:
   SctpDataChannelTest()
       : webrtc_data_channel_(
-          DataChannel::Create(&client_, cricket::DCT_SCTP, "test", &init_)) {
+          DataChannel::Create(&provider_, cricket::DCT_SCTP, "test", &init_)) {
   }
 
   void SetChannelReady() {
+    webrtc_data_channel_->OnTransportChannelCreated();
+    if (webrtc_data_channel_->id() < 0) {
+      webrtc_data_channel_->SetSctpSid(0);
+    }
     webrtc_data_channel_->OnChannelReady(true);
   }
 
   webrtc::DataChannelInit init_;
-  FakeDataChannelClient client_;
+  FakeDataChannelProvider provider_;
   talk_base::scoped_refptr<DataChannel> webrtc_data_channel_;
 };
 
+// Verifies that the data channel is connected to the transport after creation.
+TEST_F(SctpDataChannelTest, ConnectedToTransportOnCreated) {
+  EXPECT_TRUE(provider_.IsConnected(webrtc_data_channel_.get()));
+  // The sid is not set yet, so it should not have added the streams.
+  EXPECT_FALSE(provider_.IsSendStreamAdded(webrtc_data_channel_->id()));
+  EXPECT_FALSE(provider_.IsRecvStreamAdded(webrtc_data_channel_->id()));
+
+  webrtc_data_channel_->SetSctpSid(0);
+  EXPECT_TRUE(provider_.IsSendStreamAdded(webrtc_data_channel_->id()));
+  EXPECT_TRUE(provider_.IsRecvStreamAdded(webrtc_data_channel_->id()));
+}
+
+// Verifies that the data channel is connected to the transport if the transport
+// is not available initially and becomes available later.
+TEST_F(SctpDataChannelTest, ConnectedAfterTransportBecomesAvailable) {
+  provider_.set_transport_available(false);
+  talk_base::scoped_refptr<DataChannel> dc = DataChannel::Create(
+      &provider_, cricket::DCT_SCTP, "test1", &init_);
+  EXPECT_FALSE(provider_.IsConnected(dc.get()));
+
+  provider_.set_transport_available(true);
+  dc->OnTransportChannelCreated();
+  EXPECT_TRUE(provider_.IsConnected(dc.get()));
+}
+
 // Tests the state of the data channel.
 TEST_F(SctpDataChannelTest, StateTransition) {
   EXPECT_EQ(webrtc::DataChannelInterface::kConnecting,
@@ -85,6 +85,8 @@
   webrtc_data_channel_->Close();
   EXPECT_EQ(webrtc::DataChannelInterface::kClosed,
             webrtc_data_channel_->state());
+  // Verifies that it's disconnected from the transport.
+  EXPECT_FALSE(provider_.IsConnected(webrtc_data_channel_.get()));
 }
 
 // Tests that DataChannel::buffered_amount() is correct after the channel is
@@ -96,7 +98,7 @@
 
   EXPECT_EQ(0U, webrtc_data_channel_->buffered_amount());
 
-  client_.set_send_blocked(true);
+  provider_.set_send_blocked(true);
 
   const int number_of_packets = 3;
   for (int i = 0; i < number_of_packets; ++i) {
@@ -111,18 +113,22 @@
 TEST_F(SctpDataChannelTest, QueuedDataSentWhenUnblocked) {
   SetChannelReady();
   webrtc::DataBuffer buffer("abcd");
-  client_.set_send_blocked(true);
+  provider_.set_send_blocked(true);
   EXPECT_TRUE(webrtc_data_channel_->Send(buffer));
 
-  client_.set_send_blocked(false);
+  provider_.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));
+TEST_F(SctpDataChannelTest, OpenMessageSent) {
+  // Initially the id is unassigned.
+  EXPECT_EQ(-1, webrtc_data_channel_->id());
+
   SetChannelReady();
-  EXPECT_EQ(cricket::DMT_CONTROL, client_.last_send_data_params().type);
+  EXPECT_GE(webrtc_data_channel_->id(), 0);
+  EXPECT_EQ(cricket::DMT_CONTROL, provider_.last_send_data_params().type);
+  EXPECT_EQ(provider_.last_send_data_params().ssrc,
+            static_cast<uint32>(webrtc_data_channel_->id()));
 }
diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc
index ef9f3e0..91dde24 100644
--- a/talk/app/webrtc/mediastreamsignaling.cc
+++ b/talk/app/webrtc/mediastreamsignaling.cc
@@ -37,6 +37,7 @@
 #include "talk/app/webrtc/videosource.h"
 #include "talk/app/webrtc/videotrack.h"
 #include "talk/base/bytebuffer.h"
+#include "talk/base/stringutils.h"
 #include "talk/media/sctp/sctpdataengine.h"
 
 static const char kDefaultStreamLabel[] = "default";
@@ -189,7 +190,8 @@
       remote_streams_(StreamCollection::Create()),
       remote_stream_factory_(new RemoteMediaStreamFactory(signaling_thread,
                                                           channel_manager)),
-      last_allocated_sctp_id_(0) {
+      last_allocated_sctp_even_sid_(-2),
+      last_allocated_sctp_odd_sid_(-1) {
   options_.has_video = false;
   options_.has_audio = false;
 }
@@ -203,36 +205,37 @@
   OnDataChannelClose();
 }
 
-bool MediaStreamSignaling::IsSctpIdAvailable(int id) const {
-  if (id < 0 || id > static_cast<int>(cricket::kMaxSctpSid))
+bool MediaStreamSignaling::IsSctpSidAvailable(int sid) const {
+  if (sid < 0 || sid > static_cast<int>(cricket::kMaxSctpSid))
     return false;
   for (DataChannels::const_iterator iter = data_channels_.begin();
        iter != data_channels_.end();
        ++iter) {
-    if (iter->second->id() == id) {
+    if (iter->second->id() == sid) {
       return false;
     }
   }
   return true;
 }
 
-// Gets the first id that has not been taken by existing data
-// channels. Starting from 1.
-// Returns false if no id can be allocated.
-// TODO(jiayl): Update to some kind of even/odd random number selection when the
-// rules are fully standardized.
-bool MediaStreamSignaling::AllocateSctpId(int* id) {
-  do {
-    last_allocated_sctp_id_++;
-  } while (last_allocated_sctp_id_ <= static_cast<int>(cricket::kMaxSctpSid) &&
-           !IsSctpIdAvailable(last_allocated_sctp_id_));
+// Gets the first unused odd/even id based on the DTLS role. If |role| is
+// SSL_CLIENT, the allocated id starts from 0 and takes even numbers; otherwise,
+// the id starts from 1 and takes odd numbers. Returns false if no id can be
+// allocated.
+bool MediaStreamSignaling::AllocateSctpSid(talk_base::SSLRole role, int* sid) {
+  int& last_id = (role == talk_base::SSL_CLIENT) ?
+      last_allocated_sctp_even_sid_ : last_allocated_sctp_odd_sid_;
 
-  if (last_allocated_sctp_id_ > static_cast<int>(cricket::kMaxSctpSid)) {
-    last_allocated_sctp_id_ = cricket::kMaxSctpSid;
+  do {
+    last_id += 2;
+  } while (last_id <= static_cast<int>(cricket::kMaxSctpSid) &&
+           !IsSctpSidAvailable(last_id));
+
+  if (last_id > static_cast<int>(cricket::kMaxSctpSid)) {
     return false;
   }
 
-  *id = last_allocated_sctp_id_;
+  *sid = last_id;
   return true;
 }
 
@@ -392,9 +395,8 @@
     const cricket::DataContentDescription* data_desc =
         static_cast<const cricket::DataContentDescription*>(
             data_content->description);
-    if (data_desc->protocol() == cricket::kMediaProtocolDtlsSctp) {
-      UpdateRemoteSctpDataChannels();
-    } else {
+    if (talk_base::starts_with(
+            data_desc->protocol().data(), cricket::kMediaProtocolRtpPrefix)) {
       UpdateRemoteRtpDataChannels(data_desc->streams());
     }
   }
@@ -448,9 +450,8 @@
     const cricket::DataContentDescription* data_desc =
         static_cast<const cricket::DataContentDescription*>(
             data_content->description);
-    if (data_desc->protocol() == cricket::kMediaProtocolDtlsSctp) {
-      UpdateLocalSctpDataChannels();
-    } else {
+    if (talk_base::starts_with(
+            data_desc->protocol().data(), cricket::kMediaProtocolRtpPrefix)) {
       UpdateLocalRtpDataChannels(data_desc->streams());
     }
   }
@@ -919,20 +920,26 @@
   stream_observer_->OnAddDataChannel(channel);
 }
 
-
-void MediaStreamSignaling::UpdateLocalSctpDataChannels() {
+void MediaStreamSignaling::OnDataTransportCreatedForSctp() {
   DataChannels::iterator it = data_channels_.begin();
   for (; it != data_channels_.end(); ++it) {
     DataChannel* data_channel = it->second;
-    data_channel->SetSendSsrc(data_channel->id());
+    data_channel->OnTransportChannelCreated();
   }
 }
 
-void MediaStreamSignaling::UpdateRemoteSctpDataChannels() {
+void MediaStreamSignaling::OnDtlsRoleReadyForSctp(talk_base::SSLRole role) {
   DataChannels::iterator it = data_channels_.begin();
   for (; it != data_channels_.end(); ++it) {
     DataChannel* data_channel = it->second;
-    data_channel->SetReceiveSsrc(data_channel->id());
+    if (data_channel->id() < 0) {
+      int sid;
+      if (!AllocateSctpSid(role, &sid)) {
+        LOG(LS_ERROR) << "Failed to allocate SCTP sid.";
+        continue;
+      }
+      data_channel->SetSctpSid(sid);
+    }
   }
 }
 
diff --git a/talk/app/webrtc/mediastreamsignaling.h b/talk/app/webrtc/mediastreamsignaling.h
index 067ed2f..a0ed619 100644
--- a/talk/app/webrtc/mediastreamsignaling.h
+++ b/talk/app/webrtc/mediastreamsignaling.h
@@ -174,11 +174,11 @@
   }
 
   // Checks if |id| is available to be assigned to a new SCTP data channel.
-  bool IsSctpIdAvailable(int id) const;
+  bool IsSctpSidAvailable(int sid) const;
 
   // Gets the first available SCTP id that is not assigned to any existing
   // data channels.
-  bool AllocateSctpId(int* id);
+  bool AllocateSctpSid(talk_base::SSLRole role, int* sid);
 
   // Adds |local_stream| to the collection of known MediaStreams that will be
   // offered in a SessionDescription.
@@ -249,8 +249,8 @@
   StreamCollectionInterface* remote_streams() const {
     return remote_streams_.get();
   }
-  void UpdateLocalSctpDataChannels();
-  void UpdateRemoteSctpDataChannels();
+  void OnDataTransportCreatedForSctp();
+  void OnDtlsRoleReadyForSctp(talk_base::SSLRole role);
 
  private:
   struct RemotePeerInfo {
@@ -380,7 +380,9 @@
   TrackInfos local_audio_tracks_;
   TrackInfos local_video_tracks_;
 
-  int last_allocated_sctp_id_;
+  int last_allocated_sctp_even_sid_;
+  int last_allocated_sctp_odd_sid_;
+
   typedef std::map<std::string, talk_base::scoped_refptr<DataChannel> >
       DataChannels;
   DataChannels data_channels_;
diff --git a/talk/app/webrtc/mediastreamsignaling_unittest.cc b/talk/app/webrtc/mediastreamsignaling_unittest.cc
index ea13364..df4b1f5 100644
--- a/talk/app/webrtc/mediastreamsignaling_unittest.cc
+++ b/talk/app/webrtc/mediastreamsignaling_unittest.cc
@@ -32,6 +32,7 @@
 #include "talk/app/webrtc/mediastreamsignaling.h"
 #include "talk/app/webrtc/streamcollection.h"
 #include "talk/app/webrtc/test/fakeconstraints.h"
+#include "talk/app/webrtc/test/fakedatachannelprovider.h"
 #include "talk/app/webrtc/videotrack.h"
 #include "talk/base/gunit.h"
 #include "talk/base/scoped_ptr.h"
@@ -127,7 +128,7 @@
     "o=- 0 0 IN IP4 127.0.0.1\r\n"
     "s=-\r\n"
     "t=0 0\r\n"
-    "a:msid-semantic: WMS\r\n"
+    "a=msid-semantic: WMS\r\n"
     "m=audio 1 RTP/AVPF 103\r\n"
     "a=mid:audio\r\n"
     "a=rtpmap:103 ISAC/16000\r\n"
@@ -1012,4 +1013,43 @@
   observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[0], 98);
 }
 
+// Verifies that an even SCTP id is allocated for SSL_CLIENT and an odd id for
+// SSL_SERVER.
+TEST_F(MediaStreamSignalingTest, SctpIdAllocationBasedOnRole) {
+  int id;
+  ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_SERVER, &id));
+  EXPECT_EQ(1, id);
+  ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_CLIENT, &id));
+  EXPECT_EQ(0, id);
+  ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_SERVER, &id));
+  EXPECT_EQ(3, id);
+  ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_CLIENT, &id));
+  EXPECT_EQ(2, id);
+}
 
+// Verifies that SCTP ids of existing DataChannels are not reused.
+TEST_F(MediaStreamSignalingTest, SctpIdAllocationNoReuse) {
+  talk_base::scoped_ptr<FakeDataChannelProvider> provider(
+      new FakeDataChannelProvider());
+  // Creates a DataChannel with id 1.
+  webrtc::DataChannelInit config;
+  config.id = 1;
+  talk_base::scoped_refptr<webrtc::DataChannel> data_channel(
+      webrtc::DataChannel::Create(
+          provider.get(), cricket::DCT_SCTP, "a", &config));
+  ASSERT_TRUE(data_channel.get() != NULL);
+  ASSERT_TRUE(signaling_->AddDataChannel(data_channel.get()));
+
+  int new_id;
+  ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_SERVER, &new_id));
+  EXPECT_NE(config.id, new_id);
+
+  // Creates a DataChannel with id 0.
+  config.id = 0;
+  data_channel = webrtc::DataChannel::Create(
+      provider.get(), cricket::DCT_SCTP, "b", &config);
+  ASSERT_TRUE(data_channel.get() != NULL);
+  ASSERT_TRUE(signaling_->AddDataChannel(data_channel.get()));
+  ASSERT_TRUE(signaling_->AllocateSctpSid(talk_base::SSL_CLIENT, &new_id));
+  EXPECT_NE(config.id, new_id);
+}
diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc
index de88f88..bc69d48 100644
--- a/talk/app/webrtc/peerconnection.cc
+++ b/talk/app/webrtc/peerconnection.cc
@@ -427,14 +427,6 @@
   if (!channel.get())
     return NULL;
 
-  // If we've already passed the underlying channel's setup phase, have the
-  // MediaStreamSignaling update data channels manually.
-  if (session_->data_channel() != NULL &&
-      session_->data_channel_type() == cricket::DCT_SCTP) {
-    mediastream_signaling_->UpdateLocalSctpDataChannels();
-    mediastream_signaling_->UpdateRemoteSctpDataChannels();
-  }
-
   observer_->OnRenegotiationNeeded();
 
   return DataChannelProxy::Create(signaling_thread(), channel.get());
diff --git a/talk/app/webrtc/peerconnectioninterface_unittest.cc b/talk/app/webrtc/peerconnectioninterface_unittest.cc
index b1d3544..ea94ee1 100644
--- a/talk/app/webrtc/peerconnectioninterface_unittest.cc
+++ b/talk/app/webrtc/peerconnectioninterface_unittest.cc
@@ -981,31 +981,6 @@
   EXPECT_TRUE(channel == NULL);
 }
 
-// The test verifies that the first id not used by existing data channels is
-// assigned to a new data channel if no id is specified.
-TEST_F(PeerConnectionInterfaceTest, AssignSctpDataChannelId) {
-  FakeConstraints constraints;
-  constraints.SetAllowDtlsSctpDataChannels();
-  CreatePeerConnection(&constraints);
-
-  webrtc::DataChannelInit config;
-
-  scoped_refptr<DataChannelInterface> channel =
-      pc_->CreateDataChannel("1", &config);
-  EXPECT_TRUE(channel != NULL);
-  EXPECT_EQ(1, channel->id());
-
-  config.id = 4;
-  channel = pc_->CreateDataChannel("4", &config);
-  EXPECT_TRUE(channel != NULL);
-  EXPECT_EQ(config.id, channel->id());
-
-  config.id = -1;
-  channel = pc_->CreateDataChannel("2", &config);
-  EXPECT_TRUE(channel != NULL);
-  EXPECT_EQ(2, channel->id());
-}
-
 // The test verifies that creating a SCTP data channel with an id already in use
 // or out of range should fail.
 TEST_F(PeerConnectionInterfaceTest,
@@ -1015,13 +990,13 @@
   CreatePeerConnection(&constraints);
 
   webrtc::DataChannelInit config;
+  scoped_refptr<DataChannelInterface> channel;
 
-  scoped_refptr<DataChannelInterface> channel =
-      pc_->CreateDataChannel("1", &config);
+  config.id = 1;
+  channel = pc_->CreateDataChannel("1", &config);
   EXPECT_TRUE(channel != NULL);
   EXPECT_EQ(1, channel->id());
 
-  config.id = 1;
   channel = pc_->CreateDataChannel("x", &config);
   EXPECT_TRUE(channel == NULL);
 
@@ -1095,6 +1070,8 @@
 // Test that we can create a session description from an SDP string from
 // FireFox, use it as a remote session description, generate an answer and use
 // the answer as a local description.
+// TODO(mallinath): re-enable per
+// https://code.google.com/p/webrtc/issues/detail?id=2574
 TEST_F(PeerConnectionInterfaceTest, DISABLED_ReceiveFireFoxOffer) {
   MAYBE_SKIP_TEST(talk_base::SSLStreamAdapter::HaveDtlsSrtp);
   FakeConstraints constraints;
diff --git a/talk/app/webrtc/test/fakedatachannelprovider.h b/talk/app/webrtc/test/fakedatachannelprovider.h
new file mode 100644
index 0000000..3326419
--- /dev/null
+++ b/talk/app/webrtc/test/fakedatachannelprovider.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#include "talk/app/webrtc/datachannel.h"
+
+class FakeDataChannelProvider : public webrtc::DataChannelProviderInterface {
+ public:
+  FakeDataChannelProvider()
+      : id_allocation_should_fail_(false),
+        send_blocked_(false),
+        transport_available_(true) {}
+  virtual ~FakeDataChannelProvider() {}
+
+  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 bool ConnectDataChannel(webrtc::DataChannel* data_channel) OVERRIDE {
+    ASSERT(connected_channels_.find(data_channel) == connected_channels_.end());
+    if (!transport_available_) {
+      return false;
+    }
+    LOG(LS_INFO) << "DataChannel connected " << data_channel;
+    connected_channels_.insert(data_channel);
+    return true;
+  }
+  virtual void DisconnectDataChannel(
+      webrtc::DataChannel* data_channel) OVERRIDE {
+    ASSERT(connected_channels_.find(data_channel) != connected_channels_.end());
+    LOG(LS_INFO) << "DataChannel disconnected " << data_channel;
+    connected_channels_.erase(data_channel);
+  }
+  virtual void AddRtpDataStream(uint32 send_ssrc, uint32 recv_ssrc) OVERRIDE {
+    send_ssrcs_.insert(send_ssrc);
+    recv_ssrcs_.insert(recv_ssrc);
+  }
+  virtual void AddSctpDataStream(uint32 sid) OVERRIDE {
+    AddRtpDataStream(sid, sid);
+  }
+  virtual void RemoveRtpDataStream(
+      uint32 send_ssrc, uint32 recv_ssrc) OVERRIDE {
+    send_ssrcs_.erase(send_ssrc);
+    recv_ssrcs_.erase(recv_ssrc);
+  }
+  virtual void RemoveSctpDataStream(uint32 sid) OVERRIDE {
+    RemoveRtpDataStream(sid, sid);
+  }
+
+  void set_send_blocked(bool blocked) {
+    send_blocked_ = blocked;
+  }
+  cricket::SendDataParams last_send_data_params() const {
+    return last_send_data_params_;
+  }
+  void set_id_allocaiton_should_fail(bool fail) {
+    id_allocation_should_fail_ = fail;
+  }
+  void set_transport_available(bool available) {
+    transport_available_ = available;
+  }
+  bool IsConnected(webrtc::DataChannel* data_channel) const {
+    return connected_channels_.find(data_channel) != connected_channels_.end();
+  }
+  bool IsSendStreamAdded(uint32 stream) const {
+    return send_ssrcs_.find(stream) != send_ssrcs_.end();
+  }
+  bool IsRecvStreamAdded(uint32 stream) const {
+    return recv_ssrcs_.find(stream) != recv_ssrcs_.end();
+  }
+
+ private:
+  cricket::SendDataParams last_send_data_params_;
+  bool id_allocation_should_fail_;
+  bool send_blocked_;
+  bool transport_available_;
+  std::set<webrtc::DataChannel*> connected_channels_;
+  std::set<uint32> send_ssrcs_;
+  std::set<uint32> recv_ssrcs_;
+};
diff --git a/talk/app/webrtc/webrtcsdp.cc b/talk/app/webrtc/webrtcsdp.cc
index 610250e..ce23459 100644
--- a/talk/app/webrtc/webrtcsdp.cc
+++ b/talk/app/webrtc/webrtcsdp.cc
@@ -2188,6 +2188,12 @@
       return ParseFailed("", description.str(), error);
     }
   }
+
+  size_t end_of_message = message.size();
+  if (mline_index == -1 && *pos != end_of_message) {
+    ParseFailed(message, *pos, "Expects m line.", error);
+    return false;
+  }
   return true;
 }
 
diff --git a/talk/app/webrtc/webrtcsdp_unittest.cc b/talk/app/webrtc/webrtcsdp_unittest.cc
index 777a707..97ec843 100644
--- a/talk/app/webrtc/webrtcsdp_unittest.cc
+++ b/talk/app/webrtc/webrtcsdp_unittest.cc
@@ -1484,6 +1484,19 @@
   EXPECT_TRUE(CompareSessionDescription(jdesc_, jdesc));
 }
 
+TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutMline) {
+  JsepSessionDescription jdesc(kDummyString);
+  const char kSdpWithoutMline[] =
+    "v=0\r\n"
+    "o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "t=0 0\r\n"
+    "a=msid-semantic: WMS local_stream_1 local_stream_2\r\n";
+  // Deserialize
+  EXPECT_TRUE(SdpDeserialize(kSdpWithoutMline, &jdesc));
+  EXPECT_EQ(0u, jdesc.description()->contents().size());
+}
+
 TEST_F(WebRtcSdpTest, DeserializeSessionDescriptionWithoutCarriageReturn) {
   JsepSessionDescription jdesc(kDummyString);
   std::string sdp_without_carriage_return = kSdpFullString;
@@ -1886,6 +1899,7 @@
   ReplaceAndTryToParse("t=", kSdpDestroyer);
 
   // Broken media description
+  ReplaceAndTryToParse("m=audio", "c=IN IP4 74.125.224.39");
   ReplaceAndTryToParse("m=video", kSdpDestroyer);
 
   // Invalid lines
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index 69a47c4..3f14268 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -42,7 +42,6 @@
 #include "talk/base/stringencode.h"
 #include "talk/media/base/constants.h"
 #include "talk/media/base/videocapturer.h"
-#include "talk/media/sctp/sctputils.h"
 #include "talk/session/media/channel.h"
 #include "talk/session/media/channelmanager.h"
 #include "talk/session/media/mediasession.h"
@@ -629,6 +628,10 @@
   // local session description.
   mediastream_signaling_->OnLocalDescriptionChanged(local_desc_.get());
 
+  talk_base::SSLRole role;
+  if (data_channel_type_ == cricket::DCT_SCTP && GetSslRole(&role)) {
+    mediastream_signaling_->OnDtlsRoleReadyForSctp(role);
+  }
   if (error() != cricket::BaseSession::ERROR_NONE) {
     return BadLocalSdp(SessionErrorMsg(error()), err_desc);
   }
@@ -681,6 +684,12 @@
   ice_restart_latch_->CheckForRemoteIceRestart(remote_desc_.get(),
                                                desc);
   remote_desc_.reset(desc_temp.release());
+
+  talk_base::SSLRole role;
+  if (data_channel_type_ == cricket::DCT_SCTP && GetSslRole(&role)) {
+    mediastream_signaling_->OnDtlsRoleReadyForSctp(role);
+  }
+
   if (error() != cricket::BaseSession::ERROR_NONE) {
     return BadRemoteSdp(SessionErrorMsg(error()), err_desc);
   }
@@ -961,25 +970,49 @@
     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());
+  if (!data_channel_.get()) {
+    LOG(LS_ERROR) << "DisconnectDataChannel called when data_channel_ is NULL.";
+    return;
+  }
   data_channel_->SignalReadyToSendData.disconnect(webrtc_data_channel);
   data_channel_->SignalDataReceived.disconnect(webrtc_data_channel);
 }
 
+void WebRtcSession::AddRtpDataStream(uint32 send_ssrc, uint32 recv_ssrc) {
+  if (!data_channel_.get()) {
+    LOG(LS_ERROR) << "AddDataChannelStreams called when data_channel_ is NULL.";
+    return;
+  }
+  data_channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(recv_ssrc));
+  data_channel_->AddSendStream(cricket::StreamParams::CreateLegacy(send_ssrc));
+}
+
+void WebRtcSession::AddSctpDataStream(uint32 sid) {
+  AddRtpDataStream(sid, sid);
+}
+
+void WebRtcSession::RemoveRtpDataStream(uint32 send_ssrc, uint32 recv_ssrc) {
+  if (!data_channel_.get()) {
+    LOG(LS_ERROR) << "RemoveDataChannelStreams called when data_channel_ is "
+                  << "NULL.";
+    return;
+  }
+  data_channel_->RemoveRecvStream(recv_ssrc);
+  data_channel_->RemoveSendStream(send_ssrc);
+}
+
+void WebRtcSession::RemoveSctpDataStream(uint32 sid) {
+  RemoveRtpDataStream(sid, sid);
+}
+
 talk_base::scoped_refptr<DataChannel> WebRtcSession::CreateDataChannel(
     const std::string& label,
     const DataChannelInit* config) {
@@ -994,11 +1027,13 @@
 
   if (data_channel_type_ == cricket::DCT_SCTP) {
     if (new_config.id < 0) {
-      if (!mediastream_signaling_->AllocateSctpId(&new_config.id)) {
+      talk_base::SSLRole role;
+      if (GetSslRole(&role) &&
+          !mediastream_signaling_->AllocateSctpSid(role, &new_config.id)) {
         LOG(LS_ERROR) << "No id can be allocated for the SCTP data channel.";
         return NULL;
       }
-    } else if (!mediastream_signaling_->IsSctpIdAvailable(new_config.id)) {
+    } else if (!mediastream_signaling_->IsSctpSidAvailable(new_config.id)) {
       LOG(LS_ERROR) << "Failed to create a SCTP data channel "
                     << "because the id is already in use or out of range.";
       return NULL;
@@ -1007,30 +1042,9 @@
 
   talk_base::scoped_refptr<DataChannel> channel(
       DataChannel::Create(this, data_channel_type_, label, &new_config));
-  if (channel == NULL)
+  if (channel && !mediastream_signaling_->AddDataChannel(channel))
     return NULL;
-  if (!mediastream_signaling_->AddDataChannel(channel))
-    return NULL;
-  if (data_channel_type_ == cricket::DCT_SCTP) {
-    if (config == NULL) {
-      LOG(LS_WARNING) << "Could not send data channel OPEN message"
-                      << " because of NULL config.";
-      return NULL;
-    }
-    if (data_channel_.get()) {
-      channel->SetReceiveSsrc(new_config.id);
-      channel->SetSendSsrc(new_config.id);
-    }
-    if (!config->negotiated) {
-      talk_base::Buffer *payload = new talk_base::Buffer;
-      if (!cricket::WriteDataChannelOpenMessage(label, *config, payload)) {
-        LOG(LS_WARNING) << "Could not write data channel OPEN message";
-      }
-      // SendControl may queue the message until the data channel's set up,
-      // or congestion clears.
-      channel->SendControl(payload);
-    }
-  }
+
   return channel;
 }
 
@@ -1353,14 +1367,17 @@
 }
 
 bool WebRtcSession::CreateDataChannel(const cricket::ContentInfo* content) {
-  bool rtcp = (data_channel_type_ == cricket::DCT_RTP);
+  bool sctp = (data_channel_type_ == cricket::DCT_SCTP);
   data_channel_.reset(channel_manager_->CreateDataChannel(
-      this, content->name, rtcp, data_channel_type_));
+      this, content->name, !sctp, data_channel_type_));
   if (!data_channel_.get()) {
     return false;
   }
-  data_channel_->SignalNewStreamReceived.connect(
-      this, &WebRtcSession::OnNewDataChannelReceived);
+  if (sctp) {
+    mediastream_signaling_->OnDataTransportCreatedForSctp();
+    data_channel_->SignalNewStreamReceived.connect(
+        this, &WebRtcSession::OnNewDataChannelReceived);
+  }
   return true;
 }
 
diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h
index dde33ca..c4f6055 100644
--- a/talk/app/webrtc/webrtcsession.h
+++ b/talk/app/webrtc/webrtcsession.h
@@ -190,7 +190,10 @@
                         cricket::SendDataResult* result) OVERRIDE;
   virtual bool ConnectDataChannel(DataChannel* webrtc_data_channel) OVERRIDE;
   virtual void DisconnectDataChannel(DataChannel* webrtc_data_channel) OVERRIDE;
-
+  virtual void AddRtpDataStream(uint32 send_ssrc, uint32 recv_ssrc) OVERRIDE;
+  virtual void AddSctpDataStream(uint32 sid) OVERRIDE;
+  virtual void RemoveRtpDataStream(uint32 send_ssrc, uint32 recv_ssrc) OVERRIDE;
+  virtual void RemoveSctpDataStream(uint32 sid) OVERRIDE;
 
   talk_base::scoped_refptr<DataChannel> CreateDataChannel(
       const std::string& label,
diff --git a/talk/base/base64.cc b/talk/base/base64.cc
index 7765f10..79b045e 100644
--- a/talk/base/base64.cc
+++ b/talk/base/base64.cc
@@ -20,7 +20,6 @@
 
 #include "talk/base/common.h"
 
-using std::string;
 using std::vector;
 
 namespace talk_base {
@@ -96,7 +95,8 @@
   return true;
 }
 
-void Base64::EncodeFromArray(const void* data, size_t len, string* result) {
+void Base64::EncodeFromArray(const void* data, size_t len,
+                             std::string* result) {
   ASSERT(NULL != result);
   result->clear();
   result->resize(((len + 2) / 3) * 4);
@@ -190,8 +190,9 @@
 }
 
 bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
-                             string* result, size_t* data_used) {
-  return DecodeFromArrayTemplate<string>(data, len, flags, result, data_used);
+                             std::string* result, size_t* data_used) {
+  return DecodeFromArrayTemplate<std::string>(
+      data, len, flags, result, data_used);
 }
 
 bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
diff --git a/talk/base/logging.h b/talk/base/logging.h
index b563302..49e126b 100644
--- a/talk/base/logging.h
+++ b/talk/base/logging.h
@@ -312,8 +312,10 @@
 // The _F version prefixes the message with the current function name.
 #if (defined(__GNUC__) && defined(_DEBUG)) || defined(WANT_PRETTY_LOG_F)
 #define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": "
+#define LOG_T_F(sev) LOG(sev) << this << ": " << __PRETTY_FUNCTION__ << ": "
 #else
 #define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": "
+#define LOG_T_F(sev) LOG(sev) << this << ": " << __FUNCTION__ << ": "
 #endif
 
 #define LOG_CHECK_LEVEL(sev) \
@@ -331,7 +333,6 @@
         .stream()
 
 #define LOG_T(sev) LOG(sev) << this << ": "
-#define LOG_T_F(level) LOG_F(level) << this << ": "
 
 #else  // !LOGGING
 
@@ -354,7 +355,7 @@
       .stream()
 
 #define LOG_T(sev) LOG(sev) << this << ": "
-#define LOG_T_F(level) LOG_F(level) << this << " "
+#define LOG_T_F(sev) LOG(sev) << this << ": " << __FUNCTION__ <<
 #endif  // !LOGGING
 
 #define LOG_ERRNO_EX(sev, err) \
diff --git a/talk/base/physicalsocketserver.cc b/talk/base/physicalsocketserver.cc
index 891330a..58a22fa 100644
--- a/talk/base/physicalsocketserver.cc
+++ b/talk/base/physicalsocketserver.cc
@@ -466,6 +466,10 @@
     ASSERT((0 <= value) && (value <= 65536));
     *mtu = value;
     return 0;
+#elif defined(__native_client__)
+    // Most socket operations, including this, will fail in NaCl's sandbox.
+    error_ = EACCES;
+    return -1;
 #endif
   }
 
diff --git a/talk/base/profiler.cc b/talk/base/profiler.cc
index 68bcfe4..4c2aac4 100644
--- a/talk/base/profiler.cc
+++ b/talk/base/profiler.cc
@@ -71,8 +71,7 @@
   ++start_count_;
 }
 
-void ProfilerEvent::Stop() {
-  uint64 stop_time = TimeNanos();
+void ProfilerEvent::Stop(uint64 stop_time) {
   --start_count_;
   ASSERT(start_count_ >= 0);
   if (start_count_ == 0) {
@@ -94,6 +93,10 @@
   }
 }
 
+void ProfilerEvent::Stop() {
+  Stop(TimeNanos());
+}
+
 double ProfilerEvent::standard_deviation() const {
     if (event_count_ <= 1) return 0.0;
     return sqrt(sum_of_squared_differences_ / (event_count_ - 1.0));
@@ -105,11 +108,29 @@
 }
 
 void Profiler::StartEvent(const std::string& event_name) {
-  events_[event_name].Start();
+  lock_.LockShared();
+  EventMap::iterator it = events_.find(event_name);
+  bool needs_insert = (it == events_.end());
+  lock_.UnlockShared();
+
+  if (needs_insert) {
+    // Need an exclusive lock to modify the map.
+    ExclusiveScope scope(&lock_);
+    it = events_.insert(
+        EventMap::value_type(event_name, ProfilerEvent())).first;
+  }
+
+  it->second.Start();
 }
 
 void Profiler::StopEvent(const std::string& event_name) {
-  events_[event_name].Stop();
+  // Get the time ASAP, then wait for the lock.
+  uint64 stop_time = TimeNanos();
+  SharedScope scope(&lock_);
+  EventMap::iterator it = events_.find(event_name);
+  if (it != events_.end()) {
+    it->second.Stop(stop_time);
+  }
 }
 
 void Profiler::ReportToLog(const char* file, int line,
@@ -118,6 +139,9 @@
   if (!LogMessage::Loggable(severity_to_use)) {
     return;
   }
+
+  SharedScope scope(&lock_);
+
   { // Output first line.
     LogMessage msg(file, line, severity_to_use);
     msg.stream() << "=== Profile report ";
@@ -126,8 +150,8 @@
     }
     msg.stream() << "===";
   }
-  typedef std::map<std::string, ProfilerEvent>::const_iterator iterator;
-  for (iterator it = events_.begin(); it != events_.end(); ++it) {
+  for (EventMap::const_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 << " " << it->second;
@@ -143,15 +167,17 @@
 }
 
 const ProfilerEvent* Profiler::GetEvent(const std::string& event_name) const {
-  std::map<std::string, ProfilerEvent>::const_iterator it =
+  SharedScope scope(&lock_);
+  EventMap::const_iterator it =
       events_.find(event_name);
   return (it == events_.end()) ? NULL : &it->second;
 }
 
 bool Profiler::Clear() {
+  ExclusiveScope scope(&lock_);
   bool result = true;
   // Clear all events that aren't started.
-  std::map<std::string, ProfilerEvent>::iterator it = events_.begin();
+  EventMap::iterator it = events_.begin();
   while (it != events_.end()) {
     if (it->second.is_started()) {
       ++it;  // Can't clear started events.
diff --git a/talk/base/profiler.h b/talk/base/profiler.h
index 91ad6a5..90c5c72 100644
--- a/talk/base/profiler.h
+++ b/talk/base/profiler.h
@@ -37,7 +37,7 @@
 //   }
 // Another example:
 //   void StartAsyncProcess() {
-//     PROFILE_START("My event");
+//     PROFILE_START("My async event");
 //     DoSomethingAsyncAndThenCall(&Callback);
 //   }
 //   void Callback() {
@@ -54,6 +54,7 @@
 #include "talk/base/basictypes.h"
 #include "talk/base/common.h"
 #include "talk/base/logging.h"
+#include "talk/base/sharedexclusivelock.h"
 
 // Profiling could be switched via a build flag, but for now, it's always on.
 #define ENABLE_PROFILING
@@ -105,6 +106,7 @@
   ProfilerEvent();
   void Start();
   void Stop();
+  void Stop(uint64 stop_time);
   double standard_deviation() const;
   double total_time() const { return total_time_; }
   double mean() const { return mean_; }
@@ -142,7 +144,9 @@
  private:
   Profiler() {}
 
-  std::map<std::string, ProfilerEvent> events_;
+  typedef std::map<std::string, ProfilerEvent> EventMap;
+  EventMap events_;
+  mutable SharedExclusiveLock lock_;
 
   DISALLOW_COPY_AND_ASSIGN(Profiler);
 };
diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp
index bce81a5..80e1e96 100755
--- a/talk/libjingle_tests.gyp
+++ b/talk/libjingle_tests.gyp
@@ -391,6 +391,7 @@
         'app/webrtc/test/fakeaudiocapturemodule.h',
         'app/webrtc/test/fakeaudiocapturemodule_unittest.cc',
         'app/webrtc/test/fakeconstraints.h',
+        'app/webrtc/test/fakedatachannelprovider.h',
         'app/webrtc/test/fakedtlsidentityservice.h',
         'app/webrtc/test/fakemediastreamsignaling.h',
         'app/webrtc/test/fakeperiodicvideocapturer.h',
diff --git a/talk/media/base/mediachannel.h b/talk/media/base/mediachannel.h
index 3dc9c56..49b3336 100644
--- a/talk/media/base/mediachannel.h
+++ b/talk/media/base/mediachannel.h
@@ -306,6 +306,7 @@
     system_high_adaptation_threshhold.SetFrom(
         change.system_high_adaptation_threshhold);
     buffered_mode_latency.SetFrom(change.buffered_mode_latency);
+    lower_min_bitrate.SetFrom(change.lower_min_bitrate);
   }
 
   bool operator==(const VideoOptions& o) const {
@@ -329,7 +330,8 @@
             o.system_low_adaptation_threshhold &&
         system_high_adaptation_threshhold ==
             o.system_high_adaptation_threshhold &&
-        buffered_mode_latency == o.buffered_mode_latency;
+        buffered_mode_latency == o.buffered_mode_latency &&
+        lower_min_bitrate == o.lower_min_bitrate;
   }
 
   std::string ToString() const {
@@ -356,6 +358,7 @@
     ost << ToStringIfSet("low", system_low_adaptation_threshhold);
     ost << ToStringIfSet("high", system_high_adaptation_threshhold);
     ost << ToStringIfSet("buffered mode latency", buffered_mode_latency);
+    ost << ToStringIfSet("lower min bitrate", lower_min_bitrate);
     ost << "}";
     return ost.str();
   }
@@ -400,6 +403,8 @@
   SettablePercent system_high_adaptation_threshhold;
   // Specify buffered mode latency in milliseconds.
   Settable<int> buffered_mode_latency;
+  // Make minimum configured send bitrate even lower than usual, at 30kbit.
+  Settable<bool> lower_min_bitrate;
 };
 
 // A class for playing out soundclips.
diff --git a/talk/media/base/streamparams.cc b/talk/media/base/streamparams.cc
index 08eeea7..c508b68 100644
--- a/talk/media/base/streamparams.cc
+++ b/talk/media/base/streamparams.cc
@@ -33,6 +33,7 @@
 
 const char kFecSsrcGroupSemantics[] = "FEC";
 const char kFidSsrcGroupSemantics[] = "FID";
+const char kSimSsrcGroupSemantics[] = "SIM";
 
 static std::string SsrcsToString(const std::vector<uint32>& ssrcs) {
   std::ostringstream ost;
diff --git a/talk/media/base/streamparams.h b/talk/media/base/streamparams.h
index 1561d6f..dc25a6e 100644
--- a/talk/media/base/streamparams.h
+++ b/talk/media/base/streamparams.h
@@ -31,6 +31,14 @@
 // described by one StreamParams object
 // SsrcGroup is used to describe the relationship between the SSRCs that
 // are used for this media source.
+// E.x: Consider a source that is sent as 3 simulcast streams
+// Let the simulcast elements have SSRC 10, 20, 30.
+// Let each simulcast element use FEC and let the protection packets have
+// SSRC 11,21,31.
+// To describe this 4 SsrcGroups are needed,
+// StreamParams would then contain ssrc = {10,11,20,21,30,31} and
+// ssrc_groups = {{SIM,{10,20,30}, {FEC,{10,11}, {FEC, {20,21}, {FEC {30,31}}}
+// Please see RFC 5576.
 
 #ifndef TALK_MEDIA_BASE_STREAMPARAMS_H_
 #define TALK_MEDIA_BASE_STREAMPARAMS_H_
@@ -46,6 +54,7 @@
 
 extern const char kFecSsrcGroupSemantics[];
 extern const char kFidSsrcGroupSemantics[];
+extern const char kSimSsrcGroupSemantics[];
 
 struct SsrcGroup {
   SsrcGroup(const std::string& usage, const std::vector<uint32>& ssrcs)
diff --git a/talk/media/devices/devicemanager.cc b/talk/media/devices/devicemanager.cc
index 6f4aa33..150b558 100644
--- a/talk/media/devices/devicemanager.cc
+++ b/talk/media/devices/devicemanager.cc
@@ -278,18 +278,14 @@
 bool DeviceManager::GetAudioDevices(bool input,
                                     std::vector<Device>* devs) {
   devs->clear();
-#if defined(IOS) || defined(ANDROID)
-  // Under Android, we don't access the device file directly.
-  // Arbitrary use 0 for the mic and 1 for the output.
-  // These ids are used in MediaEngine::SetSoundDevices(in, out);
-  // The strings are for human consumption.
-  if (input) {
-      devs->push_back(Device("audiorecord", 0));
-  } else {
-      devs->push_back(Device("audiotrack", 1));
-  }
+#if defined(ANDROID)
+  // Under Android, 0 is always required for the playout device and 0 is the
+  // default for the recording device.
+  devs->push_back(Device("default-device", 0));
   return true;
 #else
+  // Other platforms either have their own derived class implementation
+  // (desktop) or don't use device manager for audio devices (iOS).
   return false;
 #endif
 }
diff --git a/talk/media/webrtc/fakewebrtcvideoengine.h b/talk/media/webrtc/fakewebrtcvideoengine.h
index 0b68728..b3922ff 100644
--- a/talk/media/webrtc/fakewebrtcvideoengine.h
+++ b/talk/media/webrtc/fakewebrtcvideoengine.h
@@ -1046,14 +1046,12 @@
     return 0;
   }
   WEBRTC_STUB(EnableColorEnhancement, (const int, const bool));
-#ifdef USE_WEBRTC_DEV_BRANCH
   WEBRTC_VOID_STUB(RegisterPreEncodeCallback,
                    (int, webrtc::I420FrameCallback*));
   WEBRTC_VOID_STUB(DeRegisterPreEncodeCallback, (int));
   WEBRTC_VOID_STUB(RegisterPreRenderCallback,
                    (int, webrtc::I420FrameCallback*));
   WEBRTC_VOID_STUB(DeRegisterPreRenderCallback, (int));
-#endif
   // webrtc::ViEExternalCodec
   WEBRTC_FUNC(RegisterExternalSendCodec,
       (const int channel, const unsigned char pl_type, webrtc::VideoEncoder*,
diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc
index 05f8b2b..f2827db 100644
--- a/talk/media/webrtc/webrtcvideoengine.cc
+++ b/talk/media/webrtc/webrtcvideoengine.cc
@@ -93,6 +93,9 @@
 static const int kMaxVideoBitrate = 2000;
 static const int kDefaultConferenceModeMaxVideoBitrate = 500;
 
+// Controlled by exp, try a super low minimum bitrate for poor connections.
+static const int kLowerMinBitrate = 30;
+
 static const int kVideoMtu = 1200;
 
 static const int kVideoRtpBufferSize = 65536;
@@ -2568,7 +2571,7 @@
   int max_bitrate;
   if (autobw) {
     // Use the default values for min bitrate.
-    min_bitrate = kMinVideoBitrate;
+    min_bitrate = send_min_bitrate_;
     // Use the default value or the bps for the max
     max_bitrate = (bps <= 0) ? send_max_bitrate_ : (bps / 1000);
     // Maximum start bitrate can be kStartVideoBitrate.
@@ -2631,6 +2634,17 @@
   // Adjust send codec bitrate if needed.
   int conf_max_bitrate = kDefaultConferenceModeMaxVideoBitrate;
 
+  // Save altered min_bitrate level and apply if necessary.
+  bool adjusted_min_bitrate = false;
+  if (options.lower_min_bitrate.IsSet()) {
+    bool lower;
+    options.lower_min_bitrate.Get(&lower);
+
+    int new_send_min_bitrate = lower ? kLowerMinBitrate : kMinVideoBitrate;
+    adjusted_min_bitrate = (new_send_min_bitrate != send_min_bitrate_);
+    send_min_bitrate_ = new_send_min_bitrate;
+  }
+
   int expected_bitrate = send_max_bitrate_;
   if (InConferenceMode()) {
     expected_bitrate = conf_max_bitrate;
@@ -2642,7 +2656,8 @@
   }
 
   if (send_codec_ &&
-      (send_max_bitrate_ != expected_bitrate || denoiser_changed)) {
+      (send_max_bitrate_ != expected_bitrate || denoiser_changed ||
+       adjusted_min_bitrate)) {
     // On success, SetSendCodec() will reset send_max_bitrate_ to
     // expected_bitrate.
     if (!SetSendCodec(*send_codec_,
diff --git a/talk/media/webrtc/webrtcvideoengine_unittest.cc b/talk/media/webrtc/webrtcvideoengine_unittest.cc
index 9fbbbe4..0b8bdda 100644
--- a/talk/media/webrtc/webrtcvideoengine_unittest.cc
+++ b/talk/media/webrtc/webrtcvideoengine_unittest.cc
@@ -380,6 +380,25 @@
       channel_num, kVP8Codec.width, kVP8Codec.height, 0, 20, 10, 20);
 }
 
+TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithLoweredBitrate) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs[0].params[cricket::kCodecParamMinBitrate] = "50";
+  codecs[0].params[cricket::kCodecParamMaxBitrate] = "100";
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 100, 50, 100);
+
+  // Verify that min bitrate changes after SetOptions().
+  cricket::VideoOptions options;
+  options.lower_min_bitrate.Set(true);
+  EXPECT_TRUE(channel_->SetOptions(options));
+  VerifyVP8SendCodec(
+      channel_num, kVP8Codec.width, kVP8Codec.height, 0, 100, 30, 100);
+}
+
 TEST_F(WebRtcVideoEngineTestFake, MaxBitrateResetWithConferenceMode) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = vie_.GetLastChannel();
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
index 121dd46..7f06009 100644
--- a/talk/media/webrtc/webrtcvoiceengine.cc
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -885,7 +885,7 @@
 // soundclip device. At that time, reinstate the soundclip pause/resume code.
 bool WebRtcVoiceEngine::SetDevices(const Device* in_device,
                                    const Device* out_device) {
-#if !defined(IOS) && !defined(ANDROID)
+#if !defined(IOS)
   int in_id = in_device ? talk_base::FromString<int>(in_device->id) :
       kDefaultAudioDeviceId;
   int out_id = out_device ? talk_base::FromString<int>(out_device->id) :
@@ -982,13 +982,13 @@
   return ret;
 #else
   return true;
-#endif  // !IOS && !ANDROID
+#endif  // !IOS
 }
 
 bool WebRtcVoiceEngine::FindWebRtcAudioDeviceId(
   bool is_input, const std::string& dev_name, int dev_id, int* rtc_id) {
   // In Linux, VoiceEngine uses the same device dev_id as the device manager.
-#ifdef LINUX
+#if defined(LINUX) || defined(ANDROID)
   *rtc_id = dev_id;
   return true;
 #else
diff --git a/talk/session/media/mediasession.cc b/talk/session/media/mediasession.cc
index ae3fb57..ba510b9 100644
--- a/talk/session/media/mediasession.cc
+++ b/talk/session/media/mediasession.cc
@@ -242,13 +242,12 @@
 }
 
 // Generate random SSRC values that are not already present in |params_vec|.
-// Either 2 or 1 ssrcs will be generated based on |include_rtx_stream| being
-// true or false. The generated values are added to |ssrcs|.
+// The generated values are added to |ssrcs|.
+// |num_ssrcs| is the number of the SSRC will be generated.
 static void GenerateSsrcs(const StreamParamsVec& params_vec,
-                          bool include_rtx_stream,
+                          int num_ssrcs,
                           std::vector<uint32>* ssrcs) {
-  unsigned int num_ssrcs = include_rtx_stream ? 2 : 1;
-  for (unsigned int i = 0; i < num_ssrcs; i++) {
+  for (int i = 0; i < num_ssrcs; i++) {
     uint32 candidate;
     do {
       candidate = talk_base::CreateRandomNonZeroId();
@@ -428,7 +427,8 @@
     if (IsSctp(content_description)) {
       GenerateSctpSids(*current_streams, &ssrcs);
     } else {
-      GenerateSsrcs(*current_streams, include_rtx_stream, &ssrcs);
+      int num_ssrcs = include_rtx_stream ? 2 : 1;
+      GenerateSsrcs(*current_streams, num_ssrcs, &ssrcs);
     }
     if (include_rtx_stream) {
       content_description->AddLegacyStream(ssrcs[0], ssrcs[1]);
@@ -462,13 +462,23 @@
       if (IsSctp(content_description)) {
         GenerateSctpSids(*current_streams, &ssrcs);
       } else {
-        GenerateSsrcs(*current_streams, include_rtx_stream, &ssrcs);
+        GenerateSsrcs(*current_streams, stream_it->num_sim_layers, &ssrcs);
       }
       StreamParams stream_param;
       stream_param.id = stream_it->id;
-      stream_param.ssrcs.push_back(ssrcs[0]);
+      // Add the generated ssrc.
+      for (size_t i = 0; i < ssrcs.size(); ++i) {
+        stream_param.ssrcs.push_back(ssrcs[i]);
+      }
+      if (stream_it->num_sim_layers > 1) {
+        SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs);
+        stream_param.ssrc_groups.push_back(group);
+      }
+      // Generate an extra ssrc for include_rtx_stream case.
       if (include_rtx_stream) {
-        stream_param.AddFidSsrc(ssrcs[0], ssrcs[1]);
+        std::vector<uint32> rtx_ssrc;
+        GenerateSsrcs(*current_streams, 1, &rtx_ssrc);
+        stream_param.AddFidSsrc(ssrcs[0], rtx_ssrc[0]);
         content_description->set_multistream(true);
       }
       stream_param.cname = cname;
@@ -1017,7 +1027,22 @@
 void MediaSessionOptions::AddStream(MediaType type,
                                     const std::string& id,
                                     const std::string& sync_label) {
-  streams.push_back(Stream(type, id, sync_label));
+  AddStreamInternal(type, id, sync_label, 1);
+}
+
+void MediaSessionOptions::AddVideoStream(
+    const std::string& id,
+    const std::string& sync_label,
+    int num_sim_layers) {
+  AddStreamInternal(MEDIA_TYPE_VIDEO, id, sync_label, num_sim_layers);
+}
+
+void MediaSessionOptions::AddStreamInternal(
+    MediaType type,
+    const std::string& id,
+    const std::string& sync_label,
+    int num_sim_layers) {
+  streams.push_back(Stream(type, id, sync_label, num_sim_layers));
 
   if (type == MEDIA_TYPE_VIDEO)
     has_video = true;
diff --git a/talk/session/media/mediasession.h b/talk/session/media/mediasession.h
index 5dfc765..ff25f5a 100644
--- a/talk/session/media/mediasession.h
+++ b/talk/session/media/mediasession.h
@@ -105,8 +105,18 @@
   void AddStream(MediaType type,
                  const std::string& id,
                  const std::string& sync_label);
+  void AddVideoStream(const std::string& id,
+                      const std::string& sync_label,
+                      int num_sim_layers);
   void RemoveStream(MediaType type, const std::string& id);
 
+
+  // Helper function.
+  void AddStreamInternal(MediaType type,
+                         const std::string& id,
+                         const std::string& sync_label,
+                         int num_sim_layers);
+
   bool has_audio;
   bool has_video;
   DataChannelType data_channel_type;
@@ -122,12 +132,15 @@
   struct Stream {
     Stream(MediaType type,
            const std::string& id,
-           const std::string& sync_label)
-        : type(type), id(id), sync_label(sync_label) {
+           const std::string& sync_label,
+           int num_sim_layers)
+        : type(type), id(id), sync_label(sync_label),
+          num_sim_layers(num_sim_layers) {
     }
     MediaType type;
     std::string id;
     std::string sync_label;
+    int num_sim_layers;
   };
 
   typedef std::vector<Stream> Streams;
diff --git a/talk/session/media/mediasession_unittest.cc b/talk/session/media/mediasession_unittest.cc
index ceb2bcd..dbd8db9 100644
--- a/talk/session/media/mediasession_unittest.cc
+++ b/talk/session/media/mediasession_unittest.cc
@@ -1162,6 +1162,28 @@
   EXPECT_EQ(updated_data_streams[0].cname, updated_data_streams[1].cname);
 }
 
+// Create an offer with simulcast video stream.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSimulcastVideoOffer) {
+  MediaSessionOptions opts;
+  const int num_sim_layers = 3;
+  opts.AddVideoStream(kVideoTrack1, kMediaStream1, num_sim_layers);
+  talk_base::scoped_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+
+  ASSERT_TRUE(offer.get() != NULL);
+  const ContentInfo* vc = offer->GetContentByName("video");
+  ASSERT_TRUE(vc != NULL);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(vc->description);
+
+  const StreamParamsVec& video_streams = vcd->streams();
+  ASSERT_EQ(1U, video_streams.size());
+  EXPECT_EQ(kVideoTrack1, video_streams[0].id);
+  const SsrcGroup* sim_ssrc_group =
+      video_streams[0].get_ssrc_group(cricket::kSimSsrcGroupSemantics);
+  ASSERT_TRUE(sim_ssrc_group != NULL);
+  EXPECT_EQ(static_cast<size_t>(num_sim_layers), sim_ssrc_group->ssrcs.size());
+}
+
 // Create an audio and video answer to a standard video offer with:
 // - one video track
 // - two audio tracks