Update to libjingle 0.6.11.

git-svn-id: http://libjingle.googlecode.com/svn/trunk@110 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/CHANGELOG b/CHANGELOG
index 955082a..2e92e53 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,14 +1,19 @@
 Libjingle
 
+0.6.11 - Jan 24, 2012
+  - Improved ipv6 support.
+  - Initial DTLS support.
+  - Initial BUNDLE support.
+
 0.6.10 - Jan 11, 2012
-   - Support fullscreen screencasting of secondary displays.
-   - Add IPv6 support for libjingle's STUN components.
-   - Enable SRTP in PeerConnection v1.
-   - Bug fixes.
+  - Support fullscreen screencasting of secondary displays.
+  - Add IPv6 support for libjingle's STUN components.
+  - Enable SRTP in PeerConnection v1.
+  - Bug fixes.
 
 0.6.9 - Jan 09, 2012
-   - Enable SRTP in PeerConnection.
-   - Bug fixes.
+  - Enable SRTP in PeerConnection.
+  - Bug fixes.
 
 0.6.8 - Dec 22, 2011
   - Add a lot of unit tests
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
index 6a9b18c..24a1cc4 100644
--- a/talk/app/webrtc/peerconnection.h
+++ b/talk/app/webrtc/peerconnection.h
@@ -209,7 +209,7 @@
   struct TurnConfiguration {
     TurnConfiguration(const std::string& address,
                       int port,
-                      const std::string& user_name,
+                      const std::string& username,
                       const std::string& password)
         : server(address, port),
           username(username),
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index 54f742f..7e8a0a1 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -54,8 +54,8 @@
 static const size_t kAllowedCandidates = 4;
 // TODO - These are magic string used by cricket::VideoChannel.
 // These should be moved to a common place.
-static const std::string kRtpVideoChannelStr = "video_rtp";
-static const std::string kRtcpVideoChannelStr = "video_rtcp";
+static const char kRtpVideoChannelStr[] = "video_rtp";
+static const char kRtcpVideoChannelStr[] = "video_rtcp";
 
 WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager,
                              talk_base::Thread* signaling_thread,
diff --git a/talk/app/webrtcv1/webrtcsession.cc b/talk/app/webrtcv1/webrtcsession.cc
index dc2364d..d30ee43 100644
--- a/talk/app/webrtcv1/webrtcsession.cc
+++ b/talk/app/webrtcv1/webrtcsession.cc
@@ -122,7 +122,7 @@
   // Limit the amount of time that setting up a call may take.
   StartTransportTimeout(kCallSetupTimeout);
   // Set default secure option to SEC_REQUIRED.
-  // desc_factory_.set_secure(cricket::SEC_REQUIRED);
+  desc_factory_.set_secure(cricket::SEC_REQUIRED);
   return true;
 }
 
diff --git a/talk/base/ipaddress.cc b/talk/base/ipaddress.cc
index 9069269..036584f 100644
--- a/talk/base/ipaddress.cc
+++ b/talk/base/ipaddress.cc
@@ -41,6 +41,7 @@
 #include <stdio.h>
 
 #include "talk/base/ipaddress.h"
+#include "talk/base/byteorder.h"
 #include "talk/base/nethelpers.h"
 #include "talk/base/logging.h"
 #include "talk/base/win32.h"
@@ -293,4 +294,95 @@
   }
   return 0;
 }
+
+IPAddress TruncateIP(const IPAddress& ip, int length) {
+  if (length < 0) {
+    return IPAddress();
+  }
+  if (ip.family() == AF_INET) {
+    if (length > 31) {
+      return ip;
+    }
+    if (length == 0) {
+      return IPAddress(INADDR_ANY);
+    }
+    int mask = (0xFFFFFFFF << (32 - length));
+    uint32 host_order_ip = NetworkToHost32(ip.ipv4_address().s_addr);
+    in_addr masked;
+    masked.s_addr = HostToNetwork32(host_order_ip & mask);
+    return IPAddress(masked);
+  } else if (ip.family() == AF_INET6) {
+    if (length > 127) {
+      return ip;
+    }
+    if (length == 0) {
+      return IPAddress(in6addr_any);
+    }
+    in6_addr v6addr = ip.ipv6_address();
+    int position = length / 32;
+    int inner_length = (length - (position * 32));
+    int inner_mask = (0xFFFFFFFF  << (32 - inner_length));
+    uint32* v6_as_ints =
+        reinterpret_cast<uint32*>(&v6addr.s6_addr);
+    in6_addr ip_addr = ip.ipv6_address();
+    for (int i = 0; i < 4; ++i) {
+      if (i == position) {
+        uint32 host_order_inner = NetworkToHost32(v6_as_ints[i]);
+        v6_as_ints[i] = HostToNetwork32(host_order_inner & inner_mask);
+      } else if (i > position) {
+        v6_as_ints[i] = 0;
+      }
+    }
+    return IPAddress(v6addr);
+  }
+  return IPAddress();
+}
+
+int CountIPMaskBits(IPAddress mask) {
+  // Doing this the lazy/simple way.
+  // Clever bit tricks welcome but please be careful re: byte order.
+  uint32 word_to_count = 0;
+  int bits = 0;
+  switch (mask.family()) {
+    case AF_INET: {
+      word_to_count = NetworkToHost32(mask.ipv4_address().s_addr);
+      break;
+    }
+    case AF_INET6: {
+      in6_addr v6addr = mask.ipv6_address();
+      const uint32* v6_as_ints =
+          reinterpret_cast<const uint32*>(&v6addr.s6_addr);
+      int i = 0;
+      for (; i < 4; ++i) {
+        if (v6_as_ints[i] != 0xFFFFFFFF) {
+          break;
+        }
+      }
+      if (i < 4) {
+        word_to_count = NetworkToHost32(v6_as_ints[i]);
+      }
+      bits = (i * 32);
+      break;
+    }
+    default: {
+      return 0;
+    }
+  }
+  // Check for byte boundaries before scanning.
+  if (word_to_count == 0) {
+    return bits;
+  } else if (word_to_count == 0xFF000000) {
+    return bits + 8;
+  } else if (word_to_count == 0xFFFF0000) {
+    return bits + 16;
+  } else if (word_to_count == 0xFFFFFF00) {
+    return bits + 24;
+  }
+
+  while (word_to_count & 0x80000000) {
+    word_to_count <<= 1;
+    ++bits;
+  }
+  return bits;
+}
 }  // Namespace talk base
diff --git a/talk/base/ipaddress.h b/talk/base/ipaddress.h
index a421f16..b1063fd 100644
--- a/talk/base/ipaddress.h
+++ b/talk/base/ipaddress.h
@@ -124,6 +124,13 @@
 bool IPIsLoopback(const IPAddress& ip);
 bool IPIsPrivate(const IPAddress& ip);
 size_t HashIP(const IPAddress& ip);
+
+// Returns 'ip' truncated to be 'length' bits long.
+IPAddress TruncateIP(const IPAddress& ip, int length);
+// Returns the number of contiguously set bits, counting from the MSB in network
+// byte order, in this IPAddress. Bits after the first 0 encountered are not
+// counted.
+int CountIPMaskBits(IPAddress mask);
 }  // namespace talk_base
 
 #endif  // TALK_BASE_IPADDRESS_H_
diff --git a/talk/base/ipaddress_unittest.cc b/talk/base/ipaddress_unittest.cc
index 38a50f9..40cf187 100644
--- a/talk/base/ipaddress_unittest.cc
+++ b/talk/base/ipaddress_unittest.cc
@@ -28,7 +28,6 @@
 #include "talk/base/gunit.h"
 #include "talk/base/ipaddress.h"
 
-
 namespace talk_base {
 
 static const unsigned int kIPv4AddrSize = 4;
@@ -164,6 +163,21 @@
   return addr.family() == AF_UNSPEC;
 }
 
+bool CheckMaskCount(const std::string& mask, int expected_length) {
+  IPAddress addr;
+  return IPFromString(mask, &addr) &&
+      (expected_length == CountIPMaskBits(addr));
+}
+
+bool CheckTruncateIP(const std::string& initial, int truncate_length,
+                     const std::string& expected_result) {
+  IPAddress addr, expected;
+  IPFromString(initial, &addr);
+  IPFromString(expected_result, &expected);
+  IPAddress truncated = TruncateIP(addr, truncate_length);
+  return truncated == expected;
+}
+
 TEST(IPAddressTest, TestDefaultCtor) {
   IPAddress addr;
   EXPECT_FALSE(IPIsAny(addr));
@@ -629,4 +643,123 @@
   EXPECT_EQ(addr, addr2);
 }
 
+TEST(IPAddressTest, TestCountIPMaskBits) {
+  IPAddress mask;
+  // IPv4 on byte boundaries
+  EXPECT_PRED2(CheckMaskCount, "255.255.255.255", 32);
+  EXPECT_PRED2(CheckMaskCount, "255.255.255.0", 24);
+  EXPECT_PRED2(CheckMaskCount, "255.255.0.0", 16);
+  EXPECT_PRED2(CheckMaskCount, "255.0.0.0", 8);
+  EXPECT_PRED2(CheckMaskCount, "0.0.0.0", 0);
+
+  // IPv4 not on byte boundaries
+  EXPECT_PRED2(CheckMaskCount, "128.0.0.0", 1);
+  EXPECT_PRED2(CheckMaskCount, "224.0.0.0", 3);
+  EXPECT_PRED2(CheckMaskCount, "255.248.0.0", 13);
+  EXPECT_PRED2(CheckMaskCount, "255.255.224.0", 19);
+  EXPECT_PRED2(CheckMaskCount, "255.255.255.252", 30);
+
+  // V6 on byte boundaries
+  EXPECT_PRED2(CheckMaskCount, "::", 0);
+  EXPECT_PRED2(CheckMaskCount, "ff00::", 8);
+  EXPECT_PRED2(CheckMaskCount, "ffff::", 16);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ff00::", 24);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff::", 32);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00::", 40);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::", 48);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00::", 56);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff::", 64);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff00::", 72);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff::", 80);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff00::", 88);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff::", 96);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0000", 104);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0000", 112);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128);
+
+  // V6 not on byte boundaries.
+  EXPECT_PRED2(CheckMaskCount, "8000::", 1);
+  EXPECT_PRED2(CheckMaskCount, "ff80::", 9);
+  EXPECT_PRED2(CheckMaskCount, "ffff:fe00::", 23);
+  EXPECT_PRED2(CheckMaskCount, "ffff:fffe::", 31);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:e000::", 35);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffe0::", 43);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:f800::", 53);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:fff8::", 61);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fc00::", 70);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:fffc::", 78);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:8000::", 81);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff80::", 89);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fe00::", 103);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0000", 111);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00", 118);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc", 126);
+
+  // Non-contiguous ranges. These are kind-of invalid but lets test them
+  // to make sure they don't crash anything or infinite loop or something.
+  EXPECT_PRED2(CheckMaskCount, "217.0.0.0", 2);
+  EXPECT_PRED2(CheckMaskCount, "255.185.0.0", 9);
+  EXPECT_PRED2(CheckMaskCount, "255.255.251.0", 21);
+  EXPECT_PRED2(CheckMaskCount, "255.255.251.255", 21);
+  EXPECT_PRED2(CheckMaskCount, "255.255.254.201", 23);
+  EXPECT_PRED2(CheckMaskCount, "::1", 0);
+  EXPECT_PRED2(CheckMaskCount, "fe80::1", 7);
+  EXPECT_PRED2(CheckMaskCount, "ff80::1", 9);
+  EXPECT_PRED2(CheckMaskCount, "ffff::1", 16);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ff00:1::1", 24);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff::ffff:1", 32);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ff00:1::", 40);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff::ff00", 48);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ff00:1234::", 56);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:0012::ffff", 64);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ff01::", 72);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:7f00::", 80);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ff7a::", 88);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:7f00:0000", 96);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ff70:0000", 104);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0211", 112);
+  EXPECT_PRED2(CheckMaskCount, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff7f", 120);
+}
+
+TEST(IPAddressTest, TestTruncateIP) {
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 24, "255.255.255.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 16, "255.255.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.255", 8, "255.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "202.67.7.255", 24, "202.67.7.0");
+  EXPECT_PRED3(CheckTruncateIP, "202.129.65.205", 16, "202.129.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "55.25.2.77", 8, "55.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "74.128.99.254", 1, "0.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "106.55.99.254", 3, "96.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "172.167.53.222", 13, "172.160.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.224.0", 18, "255.255.192.0");
+  EXPECT_PRED3(CheckTruncateIP, "255.255.255.252", 28, "255.255.255.240");
+
+  EXPECT_PRED3(CheckTruncateIP, "fe80:1111:2222:3333:4444:5555:6666:7777", 1,
+               "8000::");
+  EXPECT_PRED3(CheckTruncateIP, "fff0:1111:2222:3333:4444:5555:6666:7777", 9,
+               "ff80::");
+  EXPECT_PRED3(CheckTruncateIP, "ffff:ff80:1111:2222:3333:4444:5555:6666", 23,
+               "ffff:fe00::");
+  EXPECT_PRED3(CheckTruncateIP, "2400:f9af:e456:1111:2222:3333:4444:5555", 35,
+               "2400:f9af:e000::");
+  EXPECT_PRED3(CheckTruncateIP, "9999:1111:2233:4444:5555:6666:7777:8888", 53,
+               "9999:1111:2233:4000::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 68,
+               "1111:2222:3333:4444:5000::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 92,
+               "1111:2222:3333:4444:5555:6660::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 105,
+               "1111:2222:3333:4444:5555:6666:7700::");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 124,
+               "1111:2222:3333:4444:5555:6666:7777:8880");
+
+  // Slightly degenerate cases
+  EXPECT_PRED3(CheckTruncateIP, "202.165.33.127", 32, "202.165.33.127");
+  EXPECT_PRED3(CheckTruncateIP, "235.105.77.12", 0, "0.0.0.0");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 128,
+               "1111:2222:3333:4444:5555:6666:7777:8888");
+  EXPECT_PRED3(CheckTruncateIP, "1111:2222:3333:4444:5555:6666:7777:8888", 0,
+               "::");
+}
 }  // namespace talk_base
diff --git a/talk/base/opensslidentity.cc b/talk/base/opensslidentity.cc
index f18b776..68b113b 100644
--- a/talk/base/opensslidentity.cc
+++ b/talk/base/opensslidentity.cc
@@ -268,11 +268,12 @@
 
 bool OpenSSLCertificate::GetDigestEVP(const std::string &algorithm,
                                       const EVP_MD **mdp) {
-#if defined(HAS_OPENSSL_1_0) && defined(LINUX)
   const EVP_MD *md;
   if (algorithm == DIGEST_SHA_1) {
     md = EVP_sha1();
-  } else if (algorithm == DIGEST_SHA_224) {
+  }
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+  else if (algorithm == DIGEST_SHA_224) {
     md = EVP_sha224();
   } else if (algorithm == DIGEST_SHA_256) {
     md = EVP_sha256();
@@ -280,7 +281,9 @@
     md = EVP_sha384();
   } else if (algorithm == DIGEST_SHA_512) {
     md = EVP_sha512();
-  } else {
+  }
+#endif
+  else {
     return false;
   }
 
@@ -288,9 +291,6 @@
   ASSERT(EVP_MD_size(md) >= 20);
   *mdp = md;
   return true;
-#else
-  return false;
-#endif
 }
 
 OpenSSLCertificate::~OpenSSLCertificate() {
diff --git a/talk/base/opensslstreamadapter.cc b/talk/base/opensslstreamadapter.cc
index 18cef5c..be061ad 100644
--- a/talk/base/opensslstreamadapter.cc
+++ b/talk/base/opensslstreamadapter.cc
@@ -40,6 +40,8 @@
 #include <openssl/ssl.h>
 #include <openssl/x509v3.h>
 
+#include <vector>
+
 #include "talk/base/common.h"
 #include "talk/base/logging.h"
 #include "talk/base/stream.h"
@@ -50,6 +52,28 @@
 
 namespace talk_base {
 
+#if (OPENSSL_VERSION_NUMBER >= 0x10001000L)
+#define HAVE_DTLS_SRTP
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
+#define HAVE_DTLS
+#endif
+
+#ifdef HAVE_DTLS_SRTP
+// SRTP cipher suite table
+struct SrtpCipherMapEntry {
+  const char* external_name;
+  const char* internal_name;
+};
+
+// This isn't elegant, but it's better than an external reference
+static SrtpCipherMapEntry SrtpCipherMap[] = {
+  {"AES_CM_128_HMAC_SHA1_80", "SRTP_AES128_CM_SHA1_80"},
+  {"AES_CM_128_HMAC_SHA1_32", "SRTP_AES128_CM_SHA1_32"},
+  {NULL, NULL}
+};
+#endif
 
 //////////////////////////////////////////////////////////////////////
 // StreamBIO
@@ -215,6 +239,95 @@
   return true;
 }
 
+// Key Extractor interface
+bool OpenSSLStreamAdapter::ExportKeyingMaterial(const std::string& label,
+                                                const uint8* context,
+                                                size_t context_len,
+                                                bool use_context,
+                                                uint8* result,
+                                                size_t result_len) {
+#ifdef HAVE_DTLS_SRTP
+  int i;
+
+  i = SSL_export_keying_material(ssl_, result, result_len,
+                                 label.c_str(), label.length(),
+                                 const_cast<uint8 *>(context),
+                                 context_len, use_context);
+
+  if (i != 1)
+    return false;
+
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::SetDtlsSrtpCiphers(
+    const std::vector<std::string>& ciphers) {
+  std::string internal_ciphers;
+
+  if (state_ != SSL_NONE)
+    return false;
+
+#ifdef HAVE_DTLS_SRTP
+  for (std::vector<std::string>::const_iterator cipher = ciphers.begin();
+       cipher != ciphers.end(); ++cipher) {
+    bool found = false;
+    for (SrtpCipherMapEntry *entry = SrtpCipherMap; entry->internal_name;
+         ++entry) {
+      if (*cipher == entry->external_name) {
+        found = true;
+        if (!internal_ciphers.empty())
+          internal_ciphers += ":";
+        internal_ciphers += entry->internal_name;
+        break;
+      }
+    }
+
+    if (!found) {
+      LOG(LS_ERROR) << "Could not find cipher: " << *cipher;
+      return false;
+    }
+  }
+
+  if (internal_ciphers.empty())
+    return false;
+
+  srtp_ciphers_ = internal_ciphers;
+  return true;
+#else
+  return false;
+#endif
+}
+
+bool OpenSSLStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) {
+#ifdef HAVE_DTLS_SRTP
+  ASSERT(state_ == SSL_CONNECTED);
+  if (state_ != SSL_CONNECTED)
+    return false;
+
+  SRTP_PROTECTION_PROFILE *srtp_profile =
+      SSL_get_selected_srtp_profile(ssl_);
+
+  if (!srtp_profile)
+    return NULL;
+
+  for (SrtpCipherMapEntry *entry = SrtpCipherMap;
+       entry->internal_name; ++entry) {
+    if (!strcmp(entry->internal_name, srtp_profile->name)) {
+      *cipher = entry->external_name;
+      return true;
+    }
+  }
+
+  ASSERT(false);  // This should never happen
+
+  return false;
+#else
+  return false;
+#endif
+}
 
 int OpenSSLStreamAdapter::StartSSLWithServer(const char* server_name) {
   ASSERT(server_name != NULL && server_name[0] != '\0');
@@ -550,9 +663,9 @@
       StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
       break;
 
-    case SSL_ERROR_WANT_READ:{
+    case SSL_ERROR_WANT_READ: {
         LOG(LS_INFO) << " -- error want read";
-#if defined(HAS_OPENSSL_1_0) && defined(LINUX)
+#ifdef HAVE_DTLS
         struct timeval timeout;
         if (DTLSv1_get_timeout(ssl_, &timeout)) {
           int delay = timeout.tv_sec * 1000 + timeout.tv_usec/1000;
@@ -614,7 +727,7 @@
   // Process our own messages and then pass others to the superclass
   if (MSG_TIMEOUT == msg->message_id) {
     LOG(LS_INFO) << "DTLS timeout expired";
-#if defined(HAS_OPENSSL_1_0) && defined(LINUX)
+#ifdef HAVE_DTLS
     DTLSv1_handle_timeout(ssl_);
 #endif
     ContinueSSL();
@@ -626,15 +739,21 @@
 SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() {
   SSL_CTX *ctx = NULL;
 
-#if defined(HAS_OPENSSL_1_0) && defined(LINUX)
   if (role_ == SSL_CLIENT) {
+#ifdef HAVE_DTLS
     ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ?
         DTLSv1_client_method() : TLSv1_client_method());
+#else
+    ctx = SSL_CTX_new(TLSv1_client_method());
+#endif
   } else {
+#ifdef HAVE_DTLS
     ctx = SSL_CTX_new(ssl_mode_ == SSL_MODE_DTLS ?
         DTLSv1_server_method() : TLSv1_server_method());
-  }
+#else
+    ctx = SSL_CTX_new(TLSv1_server_method());
 #endif
+  }
   if (ctx == NULL)
     return NULL;
 
@@ -664,6 +783,15 @@
   SSL_CTX_set_verify_depth(ctx, 4);
   SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
 
+#ifdef HAVE_DTLS_SRTP
+  if (!srtp_ciphers_.empty()) {
+    if (SSL_CTX_set_tlsext_use_srtp(ctx, srtp_ciphers_.c_str())) {
+      SSL_CTX_free(ctx);
+      return NULL;
+    }
+  }
+#endif
+
   return ctx;
 }
 
@@ -781,8 +909,29 @@
   return ok;
 }
 
+bool OpenSSLStreamAdapter::HaveDtls() {
+#ifdef HAVE_DTLS
+  return true;
+#else
+  return false;
+#endif
+}
 
+bool OpenSSLStreamAdapter::HaveDtlsSrtp() {
+#ifdef HAVE_DTLS_SRTP
+  return true;
+#else
+  return false;
+#endif
+}
 
+bool OpenSSLStreamAdapter::HaveExporter() {
+#ifdef HAVE_DTLS_SRTP
+  return true;
+#else
+  return false;
+#endif
+}
 
 }  // namespace talk_base
 
diff --git a/talk/base/opensslstreamadapter.h b/talk/base/opensslstreamadapter.h
index 1f94e2b..8e92a10 100644
--- a/talk/base/opensslstreamadapter.h
+++ b/talk/base/opensslstreamadapter.h
@@ -29,6 +29,8 @@
 #define TALK_BASE_OPENSSLSTREAMADAPTER_H__
 
 #include <string>
+#include <vector>
+
 #include "talk/base/buffer.h"
 #include "talk/base/sslstreamadapter.h"
 #include "talk/base/opensslidentity.h"
@@ -95,6 +97,24 @@
   virtual void Close();
   virtual StreamState GetState() const;
 
+  // Key Extractor interface
+  virtual bool ExportKeyingMaterial(const std::string& label,
+                                    const uint8* context,
+                                    size_t context_len,
+                                    bool use_context,
+                                    uint8* result,
+                                    size_t result_len);
+
+
+  // DTLS-SRTP interface
+  virtual bool SetDtlsSrtpCiphers(const std::vector<std::string>& ciphers);
+  virtual bool GetDtlsSrtpCipher(std::string* cipher);
+
+  // Capabilities interfaces
+  static bool HaveDtls();
+  static bool HaveDtlsSrtp();
+  static bool HaveExporter();
+
  protected:
   virtual void OnEvent(StreamInterface* stream, int events, int err);
 
@@ -181,6 +201,9 @@
   // OpenSSLAdapter::custom_verify_callback_ result
   bool custom_verification_succeeded_;
 
+  // The DtlsSrtp ciphers
+  std::string srtp_ciphers_;
+
   // Do DTLS or not
   SSLMode ssl_mode_;
 };
diff --git a/talk/base/socketaddress.cc b/talk/base/socketaddress.cc
index 9bf713f..a4c7275 100644
--- a/talk/base/socketaddress.cc
+++ b/talk/base/socketaddress.cc
@@ -324,13 +324,14 @@
 }
 
 static size_t ToSockAddrStorageHelper(sockaddr_storage* addr,
-                                    IPAddress ip, int port) {
+                                      IPAddress ip, int port, int scope_id) {
   memset(addr, 0, sizeof(sockaddr_storage));
   addr->ss_family = ip.family();
   if (addr->ss_family == AF_INET6) {
     sockaddr_in6* saddr = reinterpret_cast<sockaddr_in6*>(addr);
     saddr->sin6_addr = ip.ipv6_address();
     saddr->sin6_port = HostToNetwork16(port);
+    saddr->sin6_scope_id = scope_id;
     return sizeof(sockaddr_in6);
   } else if (addr->ss_family == AF_INET) {
     sockaddr_in* saddr = reinterpret_cast<sockaddr_in*>(addr);
@@ -342,11 +343,11 @@
 }
 
 size_t SocketAddress::ToDualStackSockAddrStorage(sockaddr_storage *addr) const {
-  return ToSockAddrStorageHelper(addr, ip_.AsIPv6Address(), port_);
+  return ToSockAddrStorageHelper(addr, ip_.AsIPv6Address(), port_, scope_id_);
 }
 
 size_t SocketAddress::ToSockAddrStorage(sockaddr_storage* addr) const {
-  return ToSockAddrStorageHelper(addr, ip_, port_);
+  return ToSockAddrStorageHelper(addr, ip_, port_, scope_id_);
 }
 
 std::string SocketAddress::IPToString(uint32 ip_as_host_order_integer) {
@@ -440,6 +441,7 @@
     const sockaddr_in6* saddr = reinterpret_cast<const sockaddr_in6*>(&addr);
     *out = SocketAddress(IPAddress(saddr->sin6_addr),
                          NetworkToHost16(saddr->sin6_port));
+    out->SetScopeID(saddr->sin6_scope_id);
     return true;
   }
   return false;
diff --git a/talk/base/socketaddress.h b/talk/base/socketaddress.h
index 575e042..9bd1fad 100644
--- a/talk/base/socketaddress.h
+++ b/talk/base/socketaddress.h
@@ -108,6 +108,14 @@
   // Returns the port part of this address.
   uint16 port() const;
 
+  // Returns the scope ID associated with this address. Scope IDs are a
+  // necessary addition to IPv6 link-local addresses, with different network
+  // interfaces having different scope-ids for their link-local addresses.
+  // IPv4 address do not have scope_ids and sockaddr_in structures do not have
+  // a field for them.
+  int scope_id() const {return scope_id_; }
+  void SetScopeID(int id) { scope_id_ = id; }
+
   // Returns the IP address (or hostname) in printable form.
   std::string IPAsString() const;
 
@@ -211,6 +219,7 @@
   std::string hostname_;
   IPAddress ip_;
   uint16 port_;
+  int scope_id_;
   bool literal_;  // Indicates that 'hostname_' contains a literal IP string.
 };
 
diff --git a/talk/base/socketaddress_unittest.cc b/talk/base/socketaddress_unittest.cc
index 6e2ef7f..5ef05b5 100644
--- a/talk/base/socketaddress_unittest.cc
+++ b/talk/base/socketaddress_unittest.cc
@@ -224,6 +224,29 @@
   EXPECT_EQ("", addr.hostname());
   EXPECT_EQ("[::ffff:1.2.3.4]:5678", addr.ToString());
 
+  addr.Clear();
+  memset(&addr_storage, 0, sizeof(sockaddr_storage));
+  from = SocketAddress(kTestV6AddrString, 5678);
+  from.SetScopeID(6);
+  from.ToSockAddrStorage(&addr_storage);
+  EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
+  EXPECT_EQ(6, addr.scope_id());
+
+  addr.Clear();
+  from.ToDualStackSockAddrStorage(&addr_storage);
+  EXPECT_TRUE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
+  EXPECT_FALSE(addr.IsUnresolvedIP());
+  EXPECT_EQ(IPAddress(kTestV6Addr), addr.ipaddr());
+  EXPECT_EQ(5678, addr.port());
+  EXPECT_EQ("", addr.hostname());
+  EXPECT_EQ(kTestV6AddrFullString, addr.ToString());
+  EXPECT_EQ(6, addr.scope_id());
+
   addr = from;
   addr_storage.ss_family = AF_UNSPEC;
   EXPECT_FALSE(SocketAddressFromSockAddrStorage(addr_storage, &addr));
diff --git a/talk/base/sslstreamadapter.cc b/talk/base/sslstreamadapter.cc
index 17121fd..aa0f468 100644
--- a/talk/base/sslstreamadapter.cc
+++ b/talk/base/sslstreamadapter.cc
@@ -65,6 +65,23 @@
 #endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
 }
 
+// Note: this matches the logic above with SCHANNEL dominating
+#if SSL_USE_SCHANNEL || !SSL_USE_OPENSSL
+bool SSLStreamAdapter::HaveDtls() { return false; }
+bool SSLStreamAdapter::HaveDtlsSrtp() { return false; }
+bool SSLStreamAdapter::HaveExporter() { return false; }
+#else
+bool SSLStreamAdapter::HaveDtls() {
+  return OpenSSLStreamAdapter::HaveDtls();
+}
+bool SSLStreamAdapter::HaveDtlsSrtp() {
+  return OpenSSLStreamAdapter::HaveDtlsSrtp();
+}
+bool SSLStreamAdapter::HaveExporter() {
+  return OpenSSLStreamAdapter::HaveExporter();
+}
+#endif  // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
 ///////////////////////////////////////////////////////////////////////////////
 
 }  // namespace talk_base
diff --git a/talk/base/sslstreamadapter.h b/talk/base/sslstreamadapter.h
index 28870b3..2afe1da 100644
--- a/talk/base/sslstreamadapter.h
+++ b/talk/base/sslstreamadapter.h
@@ -29,6 +29,7 @@
 #define TALK_BASE_SSLSTREAMADAPTER_H__
 
 #include <string>
+#include <vector>
 
 #include "talk/base/stream.h"
 #include "talk/base/sslidentity.h"
@@ -137,6 +138,42 @@
                                         const unsigned char* digest_val,
                                         size_t digest_len) = 0;
 
+  // Key Exporter interface from RFC 5705
+  // Arguments are:
+  // label               -- the exporter label.
+  //                        part of the RFC defining each exporter
+  //                        usage (IN)
+  // context/context_len -- a context to bind to for this connection;
+  //                        optional, can be NULL, 0 (IN)
+  // use_context         -- whether to use the context value
+  //                        (needed to distinguish no context from
+  //                        zero-length ones).
+  // result              -- where to put the computed value
+  // result_len          -- the length of the computed value
+  virtual bool ExportKeyingMaterial(const std::string& label,
+                                    const uint8* context,
+                                    size_t context_len,
+                                    bool use_context,
+                                    uint8* result,
+                                    size_t result_len) {
+    return false;  // Default is unsupported
+  }
+
+
+  // DTLS-SRTP interface
+  virtual bool SetDtlsSrtpCiphers(const std::vector<std::string>& ciphers) {
+    return false;
+  }
+
+  virtual bool GetDtlsSrtpCipher(std::string* cipher) {
+    return false;
+  }
+
+  // Capabilities testing
+  static bool HaveDtls();
+  static bool HaveDtlsSrtp();
+  static bool HaveExporter();
+
   // If true, the server certificate need not match the configured
   // server_name, and in fact missing certificate authority and other
   // verification errors are ignored.
diff --git a/talk/base/sslstreamadapter_unittest.cc b/talk/base/sslstreamadapter_unittest.cc
index 0206f87..befcdbc 100644
--- a/talk/base/sslstreamadapter_unittest.cc
+++ b/talk/base/sslstreamadapter_unittest.cc
@@ -38,6 +38,17 @@
 #include "talk/base/stream.h"
 
 static const int kBlockSize = 4096;
+static const char kAES_CM_HMAC_SHA1_80[] = "AES_CM_128_HMAC_SHA1_80";
+static const char kAES_CM_HMAC_SHA1_32[] = "AES_CM_128_HMAC_SHA1_32";
+static const char kExporterLabel[] = "label";
+static const unsigned char kExporterContext[] = "context";
+static int kExporterContextLen = sizeof(kExporterContext);
+
+#define MAYBE_SKIP_TEST(feature)                    \
+  if (!(talk_base::SSLStreamAdapter::feature())) {  \
+    LOG(LS_INFO) << "Feature disabled... skipping"; \
+    return;                                         \
+  }
 
 class SSLStreamAdapterTestBase;
 
@@ -300,6 +311,40 @@
     handshake_wait_ = wait;
   }
 
+  void SetDtlsSrtpCiphers(const std::vector<std::string> &ciphers,
+    bool client) {
+    if (client)
+      client_ssl_->SetDtlsSrtpCiphers(ciphers);
+    else
+      server_ssl_->SetDtlsSrtpCiphers(ciphers);
+  }
+
+  bool GetDtlsSrtpCipher(bool client, std::string *retval) {
+    if (client)
+      return client_ssl_->GetDtlsSrtpCipher(retval);
+    else
+      return server_ssl_->GetDtlsSrtpCipher(retval);
+  }
+
+  bool ExportKeyingMaterial(const char *label,
+                            const unsigned char *context,
+                            size_t context_len,
+                            bool use_context,
+                            bool client,
+                            unsigned char *result,
+                            size_t result_len) {
+    if (client)
+      return client_ssl_->ExportKeyingMaterial(label,
+                                               context, context_len,
+                                               use_context,
+                                               result, result_len);
+    else
+      return server_ssl_->ExportKeyingMaterial(label,
+                                               context, context_len,
+                                               use_context,
+                                               result, result_len);
+  }
+
   // To be implemented by subclasses.
   virtual void WriteData() = 0;
   virtual void ReadData(talk_base::StreamInterface *stream) = 0;
@@ -438,7 +483,7 @@
     unsigned char *packet = new unsigned char[1600];
 
     do {
-      memset(packet, sent_ & 0xff, sizeof(packet));
+      memset(packet, sent_ & 0xff, packet_size_);
       *(reinterpret_cast<uint32_t *>(packet)) = sent_;
 
       size_t sent;
@@ -459,13 +504,13 @@
   }
 
   virtual void ReadData(talk_base::StreamInterface *stream) {
-    unsigned char *buffer = new unsigned char[1600];
+    unsigned char *buffer = new unsigned char[2000];
     size_t bread;
     int err2;
     talk_base::StreamResult r;
 
     for (;;) {
-      r = stream->Read(buffer, sizeof(buffer),
+      r = stream->Read(buffer, 2000,
                        &bread, &err2);
 
       if (r == talk_base::SR_ERROR) {
@@ -569,21 +614,24 @@
 
 // Basic tests: DTLS
 // Test that we can make a handshake work
-TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSConnect) {
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnect) {
+  MAYBE_SKIP_TEST(HaveDtls);
   TestHandshake();
 };
 
 // Test that we can make a handshake work if the first packet in
 // each direction is lost. This gives us predictable loss
 // rather than having to tune random
-TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSConnectWithLostFirstPacket) {
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithLostFirstPacket) {
+  MAYBE_SKIP_TEST(HaveDtls);
   SetLoseFirstPacket(true);
   TestHandshake();
 };
 
 // Test a handshake with loss and delay
 TEST_F(SSLStreamAdapterTestDTLS,
-       DISABLED_TestDTLSConnectWithLostFirstPacketDelay2s) {
+       TestDTLSConnectWithLostFirstPacketDelay2s) {
+  MAYBE_SKIP_TEST(HaveDtls);
   SetLoseFirstPacket(true);
   SetDelay(2000);
   SetHandshakeWait(20000);
@@ -592,19 +640,117 @@
 
 // Test a handshake with small MTU
 TEST_F(SSLStreamAdapterTestDTLS, TestDTLSConnectWithSmallMtu) {
+  MAYBE_SKIP_TEST(HaveDtls);
   SetMtu(700);
   TestHandshake();
 };
 
 // Test transfer -- trivial
-TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSTransfer) {
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransfer) {
+  MAYBE_SKIP_TEST(HaveDtls);
   TestHandshake();
   TestTransfer(100);
 };
 
-TEST_F(SSLStreamAdapterTestDTLS, DISABLED_TestDTLSTransferWithLoss) {
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSTransferWithLoss) {
+  MAYBE_SKIP_TEST(HaveDtls);
   TestHandshake();
   SetLoss(10);
   TestTransfer(100);
 };
 
+// Test DTLS-SRTP with all high ciphers
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHigh) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> high;
+  high.push_back(kAES_CM_HMAC_SHA1_80);
+  SetDtlsSrtpCiphers(high, true);
+  SetDtlsSrtpCiphers(high, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+  ASSERT_EQ(client_cipher, server_cipher);
+  ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80);
+};
+
+// Test DTLS-SRTP with all low ciphers
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpLow) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> low;
+  low.push_back(kAES_CM_HMAC_SHA1_32);
+  SetDtlsSrtpCiphers(low, true);
+  SetDtlsSrtpCiphers(low, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+  ASSERT_EQ(client_cipher, server_cipher);
+  ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_32);
+};
+
+
+// Test DTLS-SRTP with a mismatch -- should not converge
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpHighLow) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> high;
+  high.push_back(kAES_CM_HMAC_SHA1_80);
+  std::vector<std::string> low;
+  low.push_back(kAES_CM_HMAC_SHA1_32);
+  SetDtlsSrtpCiphers(high, true);
+  SetDtlsSrtpCiphers(low, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_FALSE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_FALSE(GetDtlsSrtpCipher(false, &server_cipher));
+};
+
+// Test DTLS-SRTP with each side being mixed -- should select high
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSSrtpMixed) {
+  MAYBE_SKIP_TEST(HaveDtlsSrtp);
+  std::vector<std::string> mixed;
+  mixed.push_back(kAES_CM_HMAC_SHA1_80);
+  mixed.push_back(kAES_CM_HMAC_SHA1_32);
+  SetDtlsSrtpCiphers(mixed, true);
+  SetDtlsSrtpCiphers(mixed, false);
+  TestHandshake();
+
+  std::string client_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(true, &client_cipher));
+  std::string server_cipher;
+  ASSERT_TRUE(GetDtlsSrtpCipher(false, &server_cipher));
+
+  ASSERT_EQ(client_cipher, server_cipher);
+  ASSERT_EQ(client_cipher, kAES_CM_HMAC_SHA1_80);
+};
+
+// Test an exporter
+TEST_F(SSLStreamAdapterTestDTLS, TestDTLSExporter) {
+  MAYBE_SKIP_TEST(HaveExporter);
+  TestHandshake();
+  unsigned char client_out[20];
+  unsigned char server_out[20];
+
+  bool result;
+  result = ExportKeyingMaterial(kExporterLabel,
+                                kExporterContext, kExporterContextLen,
+                                true, true,
+                                client_out, sizeof(client_out));
+  ASSERT_TRUE(result);
+
+  result = ExportKeyingMaterial(kExporterLabel,
+                                kExporterContext, kExporterContextLen,
+                                true, false,
+                                server_out, sizeof(server_out));
+  ASSERT_TRUE(result);
+
+  ASSERT_TRUE(!memcmp(client_out, server_out, sizeof(client_out)));
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index 8ea2eaf..9b8fb1a 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -442,8 +442,8 @@
 
 void CallClient::OnCallCreate(cricket::Call* call) {
   call->SignalSessionState.connect(this, &CallClient::OnSessionState);
-  call->SignalMediaSourcesUpdate.connect(
-      this, &CallClient::OnMediaSourcesUpdate);
+  call->SignalMediaStreamsUpdate.connect(
+      this, &CallClient::OnMediaStreamsUpdate);
 }
 
 void CallClient::OnSessionState(cricket::Call* call,
@@ -489,17 +489,17 @@
 
 void CallClient::OnSpeakerChanged(cricket::Call* call,
                                   cricket::Session* session,
-                                  const cricket::NamedSource& speaker) {
-  if (speaker.ssrc == 0) {
+                                  const cricket::StreamParams& speaker) {
+  if (!speaker.has_ssrcs()) {
     console_->PrintLine("Session %s has no current speaker.",
                         session->id().c_str());
   } else if (speaker.nick.empty()) {
     console_->PrintLine("Session %s speaker change to unknown (%u).",
-                        session->id().c_str(), speaker.ssrc);
+                        session->id().c_str(), speaker.first_ssrc());
   } else {
     console_->PrintLine("Session %s speaker changed to %s (%u).",
                         session->id().c_str(), speaker.nick.c_str(),
-                        speaker.ssrc);
+                        speaker.first_ssrc());
   }
 }
 
@@ -1112,20 +1112,22 @@
   media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
 }
 
-void CallClient::OnMediaSourcesUpdate(cricket::Call* call,
+void CallClient::OnMediaStreamsUpdate(cricket::Call* call,
                                       cricket::Session* session,
-                                      const cricket::MediaSources& sources) {
-  for (cricket::NamedSources::const_iterator it = sources.video().begin();
-       it != sources.video().end(); ++it) {
-    if (it->removed) {
-      RemoveStaticRenderedView(it->ssrc);
-    } else {
-      if (render_) {
-        // TODO: Make dimensions and positions more configurable.
-        int offset = (50 * static_views_accumulated_count_) % 300;
-        AddStaticRenderedView(session, it->ssrc, 640, 400, 30,
-                              offset, offset);
-      }
+                                      const cricket::MediaStreams& added,
+                                      const cricket::MediaStreams& removed) {
+  for (std::vector<cricket::StreamParams>::const_iterator
+       it = removed.video().begin(); it != removed.video().end(); ++it) {
+    RemoveStaticRenderedView(it->first_ssrc());
+  }
+
+  if (render_) {
+    for (std::vector<cricket::StreamParams>::const_iterator
+         it = added.video().begin(); it != added.video().end(); ++it) {
+      // TODO: Make dimensions and positions more configurable.
+      int offset = (50 * static_views_accumulated_count_) % 300;
+      AddStaticRenderedView(session, it->first_ssrc(), 640, 400, 30,
+                            offset, offset);
     }
   }
 
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index 83378b1..76184dc 100644
--- a/talk/examples/call/callclient.h
+++ b/talk/examples/call/callclient.h
@@ -73,7 +73,8 @@
 class Call;
 class SessionManagerTask;
 struct CallOptions;
-struct NamedSource;
+struct MediaStreams;
+struct StreamParams;
 }
 
 struct RosterItem {
@@ -201,12 +202,13 @@
                                 const std::string& mutee_nick,
                                 const buzz::XmlElement* stanza);
   void OnDevicesChange();
-  void OnMediaSourcesUpdate(cricket::Call* call,
+  void OnMediaStreamsUpdate(cricket::Call* call,
                             cricket::Session* session,
-                            const cricket::MediaSources& sources);
+                            const cricket::MediaStreams& added,
+                            const cricket::MediaStreams& removed);
   void OnSpeakerChanged(cricket::Call* call,
                         cricket::Session* session,
-                        const cricket::NamedSource& speaker_source);
+                        const cricket::StreamParams& speaker_stream);
   void OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
                             const buzz::MucRoomInfo& room_info);
   void OnRoomLookupError(buzz::IqTask* task,
diff --git a/talk/examples/login/autoportallocator.h b/talk/examples/login/autoportallocator.h
index f1993ca..7e0b062 100644
--- a/talk/examples/login/autoportallocator.h
+++ b/talk/examples/login/autoportallocator.h
@@ -38,8 +38,7 @@
 
 // This class sets the relay and stun servers using XmppClient.
 // It enables the client to traverse Proxy and NAT.
-class AutoPortAllocator : public cricket::HttpPortAllocator,
-    public sigslot::has_slots<> {
+class AutoPortAllocator : public cricket::HttpPortAllocator {
  public:
   AutoPortAllocator(talk_base::NetworkManager* network_manager,
                     const std::string& user_agent)
diff --git a/talk/examples/peerconnection/client/main_wnd.cc b/talk/examples/peerconnection/client/main_wnd.cc
index e9f79e4..e4e0539 100644
--- a/talk/examples/peerconnection/client/main_wnd.cc
+++ b/talk/examples/peerconnection/client/main_wnd.cc
@@ -587,10 +587,11 @@
     AutoLock<VideoRenderer> lock(this);
 
     ASSERT(image_.get() != NULL);
-    frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB, image_.get(),
-        bmi_.bmiHeader.biSizeImage,
-        bmi_.bmiHeader.biWidth *
-        (bmi_.bmiHeader.biBitCount >> 3));
+    frame->ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+                              image_.get(),
+                              bmi_.bmiHeader.biSizeImage,
+                              bmi_.bmiHeader.biWidth *
+                                bmi_.bmiHeader.biBitCount / 8);
   }
 
   InvalidateRect(wnd_, NULL, TRUE);
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index 4cfed14..6cdf5b6 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -201,6 +201,9 @@
                "p2p/base/p2ptransportchannel.cc",
                "p2p/base/parsing.cc",
                "p2p/base/port.cc",
+               "p2p/base/portallocator.cc",
+               "p2p/base/portallocatorsessionproxy.cc",
+               "p2p/base/portproxy.cc",
                "p2p/base/pseudotcp.cc",
                "p2p/base/relayport.cc",
                "p2p/base/relayserver.cc",
diff --git a/talk/main.scons b/talk/main.scons
index e23f481..a58e115 100644
--- a/talk/main.scons
+++ b/talk/main.scons
@@ -311,6 +311,9 @@
     #'fill_plist',
   ],
 )
+# Use static OpenSSL on mac so that we can use the latest APIs on all
+# supported mac platforms (10.5+).
+mac_env.SetBits('use_static_openssl')
 mac_env.Append(
   CPPDEFINES = [
     'OSX',
diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc
index 4d65f5b..fe89aa7 100644
--- a/talk/p2p/base/constants.cc
+++ b/talk/p2p/base/constants.cc
@@ -99,7 +99,7 @@
 const char CN_VIDEO[] = "video";
 const char CN_OTHER[] = "main";
 // other SDP related strings
-const char GN_TOGETHER[] = "TOGETHER";
+const char GN_BUNDLE[] = "BUNDLE";
 
 const char NS_JINGLE_RTP[] = "urn:xmpp:jingle:apps:rtp:1";
 const buzz::StaticQName QN_JINGLE_RTP_CONTENT =
diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h
index 0c84dd6..3e85914 100644
--- a/talk/p2p/base/constants.h
+++ b/talk/p2p/base/constants.h
@@ -118,7 +118,7 @@
 extern const char CN_OTHER[];
 // other SDP related strings
 // GN stands for group name
-extern const char GN_TOGETHER[];
+extern const char GN_BUNDLE[];
 
 extern const char NS_JINGLE_RTP[];
 extern const buzz::StaticQName QN_JINGLE_RTP_CONTENT;
diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc
index 17f52a7..87a0222 100644
--- a/talk/p2p/base/p2ptransportchannel.cc
+++ b/talk/p2p/base/p2ptransportchannel.cc
@@ -301,7 +301,7 @@
 // Handle stun packets
 void P2PTransportChannel::OnUnknownAddress(
     Port *port, const talk_base::SocketAddress &address, StunMessage *stun_msg,
-    const std::string &remote_username) {
+    const std::string &remote_username, bool port_muxed) {
   ASSERT(worker_thread_ == talk_base::Thread::Current());
 
   // Port has received a valid stun packet from an address that no Connection
@@ -316,11 +316,17 @@
       break;
     }
   }
+
   if (candidate == NULL) {
+    if (port_muxed) {
+      // When Ports are muxed, SignalUnknownAddress is delivered to all
+      // P2PTransportChannel belong to a session. Return from here will
+      // save us from sending stun binding error message from incorrect channel.
+      return;
+    }
     // Don't know about this username, the request is bogus
     // This sometimes happens if a binding response comes in before the ACCEPT
     // message.  It is totally valid; the retry state machine will try again.
-
     port->SendBindingErrorResponse(stun_msg, address,
         STUN_ERROR_STALE_CREDENTIALS, STUN_ERROR_REASON_STALE_CREDENTIALS);
     delete stun_msg;
@@ -451,6 +457,13 @@
   return true;
 }
 
+bool P2PTransportChannel::FindConnection(
+    cricket::Connection* connection) const {
+  std::vector<Connection*>::const_iterator citer =
+      std::find(connections_.begin(), connections_.end(), connection);
+  return citer != connections_.end();
+}
+
 // Maintain our remote candidate list, adding this new remote one.
 void P2PTransportChannel::RememberRemoteCandidate(
     const Candidate& remote_candidate, Port* origin_port) {
@@ -900,8 +913,11 @@
                                        const char *data, size_t len) {
   ASSERT(worker_thread_ == talk_base::Thread::Current());
 
-  // Let the client know of an incoming packet
+  // Do not deliver, if packet doesn't belong to the correct transport channel.
+  if (!FindConnection(connection))
+    return;
 
+  // Let the client know of an incoming packet
   SignalReadPacket(this, data, len);
 }
 
@@ -933,7 +949,8 @@
 void P2PTransportChannel::OnSignalingReady() {
   if (waiting_for_signaling_) {
     waiting_for_signaling_ = false;
-    AddAllocatorSession(allocator_->CreateSession(name(), content_type()));
+    AddAllocatorSession(allocator_->CreateSession(
+        session_id(), name(), content_type()));
     thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE);
   }
 }
diff --git a/talk/p2p/base/p2ptransportchannel.h b/talk/p2p/base/p2ptransportchannel.h
index 0083d23..6b4d979 100644
--- a/talk/p2p/base/p2ptransportchannel.h
+++ b/talk/p2p/base/p2ptransportchannel.h
@@ -116,11 +116,12 @@
                          bool readable);
   bool CreateConnection(Port* port, const Candidate& remote_candidate,
                         Port* origin_port, bool readable);
+  bool FindConnection(cricket::Connection* connection) const;
   void RememberRemoteCandidate(const Candidate& remote_candidate,
                                Port* origin_port);
   void OnUnknownAddress(Port *port, const talk_base::SocketAddress &addr,
                         StunMessage *stun_msg,
-                        const std::string &remote_username);
+                        const std::string &remote_username, bool port_muxed);
   void OnPortReady(PortAllocatorSession *session, Port* port);
   void OnCandidatesReady(PortAllocatorSession *session,
                          const std::vector<Candidate>& candidates);
diff --git a/talk/p2p/base/parsing.cc b/talk/p2p/base/parsing.cc
index f302956..ebe0596 100644
--- a/talk/p2p/base/parsing.cc
+++ b/talk/p2p/base/parsing.cc
@@ -91,18 +91,6 @@
   return NULL;
 }
 
-const buzz::XmlElement* GetXmlElement(const XmlElements& elems,
-                                      const buzz::QName& name) {
-  for (XmlElements::const_iterator iter = elems.begin();
-       iter != elems.end(); ++iter) {
-    const buzz::XmlElement* elem = *iter;
-    if (elem->Name() == name) {
-      return elem;
-    }
-  }
-  return NULL;
-}
-
 bool RequireXmlChild(const buzz::XmlElement* parent,
                      const std::string& name,
                      const buzz::XmlElement** child,
diff --git a/talk/p2p/base/parsing.h b/talk/p2p/base/parsing.h
index 589d9d7..96f4e8b 100644
--- a/talk/p2p/base/parsing.h
+++ b/talk/p2p/base/parsing.h
@@ -112,8 +112,6 @@
 
 const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
                                     const std::string& name);
-const buzz::XmlElement* GetXmlElement(const XmlElements& elems,
-                                      const buzz::QName& name);
 
 bool RequireXmlChild(const buzz::XmlElement* parent,
                      const std::string& name,
diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc
index a016f63..d47e90e 100644
--- a/talk/p2p/base/port.cc
+++ b/talk/p2p/base/port.cc
@@ -212,7 +212,7 @@
   } else if (!msg) {
     // STUN message handled already
   } else if (msg->type() == STUN_BINDING_REQUEST) {
-    SignalUnknownAddress(this, addr, msg, remote_username);
+    SignalUnknownAddress(this, addr, msg, remote_username, false);
   } else {
     // NOTE(tschmelcher): STUN_BINDING_RESPONSE is benign. It occurs if we
     // pruned a connection for this port while it had STUN requests in flight,
diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h
index 396fce0..1e81b0a 100644
--- a/talk/p2p/base/port.h
+++ b/talk/p2p/base/port.h
@@ -107,7 +107,6 @@
   const std::string& password() const { return password_; }
   void set_password(const std::string& password) { password_ = password; }
 
-
   // A value in [0,1] that indicates the preference for this port versus other
   // ports on this client.  (Larger indicates more preference.)
   float preference() const { return preference_; }
@@ -141,7 +140,8 @@
   const AddressMap& connections() { return connections_; }
 
   // Returns the connection to the given address or NULL if none exists.
-  Connection* GetConnection(const talk_base::SocketAddress& remote_addr);
+  virtual Connection* GetConnection(
+      const talk_base::SocketAddress& remote_addr);
 
   // Creates a new connection to the given address.
   enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE };
@@ -160,15 +160,15 @@
   // Indicates that we received a successful STUN binding request from an
   // address that doesn't correspond to any current connection.  To turn this
   // into a real connection, call CreateConnection.
-  sigslot::signal4<Port*, const talk_base::SocketAddress&, StunMessage*,
-                   const std::string&> SignalUnknownAddress;
+  sigslot::signal5<Port*, const talk_base::SocketAddress&, StunMessage*,
+                   const std::string&, bool> SignalUnknownAddress;
 
   // Sends a response message (normal or error) to the given request.  One of
   // these methods should be called as a response to SignalUnknownAddress.
   // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse.
-  void SendBindingResponse(StunMessage* request,
-                           const talk_base::SocketAddress& addr);
-  void SendBindingErrorResponse(
+  virtual void SendBindingResponse(StunMessage* request,
+                                   const talk_base::SocketAddress& addr);
+  virtual void SendBindingErrorResponse(
       StunMessage* request, const talk_base::SocketAddress& addr,
       int error_code, const std::string& reason);
 
@@ -211,6 +211,9 @@
 
   // Debugging description of this port
   std::string ToString() const;
+  talk_base::IPAddress& ip() { return ip_; }
+  int min_port() { return min_port_; }
+  int max_port() { return max_port_; }
 
  protected:
   // Fills in the local address of the port.
diff --git a/talk/p2p/base/port_unittest.cc b/talk/p2p/base/port_unittest.cc
index 1bdac3d..9a84606 100644
--- a/talk/p2p/base/port_unittest.cc
+++ b/talk/p2p/base/port_unittest.cc
@@ -123,7 +123,8 @@
   }
 
   void OnUnknownAddress(Port* port, const SocketAddress& addr,
-                        StunMessage* msg, const std::string& rf) {
+                        StunMessage* msg, const std::string& rf,
+                        bool /*port_muxed*/) {
     ASSERT_EQ(src_.get(), port);
     if (!remote_address_.IsAny()) {
       ASSERT_EQ(remote_address_, addr);
diff --git a/talk/p2p/base/portallocator.cc b/talk/p2p/base/portallocator.cc
new file mode 100644
index 0000000..974117f
--- /dev/null
+++ b/talk/p2p/base/portallocator.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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/p2p/base/portallocator.h"
+
+#include "talk/p2p/base/portallocatorsessionproxy.h"
+
+namespace cricket {
+
+PortAllocator::~PortAllocator() {
+  for (SessionMuxerMap::iterator iter = muxers_.begin();
+       iter != muxers_.end(); ++iter) {
+    delete iter->second;
+  }
+}
+
+PortAllocatorSession* PortAllocator::CreateSession(
+    const std::string& sid,
+    const std::string& name,
+    const std::string& session_type) {
+  if (flags_ & PORTALLOCATOR_ENABLE_BUNDLE) {
+    PortAllocatorSessionMuxer* muxer = GetSessionMuxer(sid);
+    if (!muxer) {
+      PortAllocatorSession* session_impl = CreateSession(name, session_type);
+      // Create PortAllocatorSessionMuxer object for |session_impl|.
+      muxer = new PortAllocatorSessionMuxer(session_impl);
+      muxer->SignalDestroyed.connect(
+          this, &PortAllocator::OnSessionMuxerDestroyed);
+      // Add PortAllocatorSession to the map.
+      muxers_[sid] = muxer;
+    }
+    PortAllocatorSessionProxy* proxy =
+        new PortAllocatorSessionProxy(name, session_type, flags_);
+    muxer->RegisterSessionProxy(proxy);
+    return proxy;
+  }
+  return CreateSession(name, session_type);
+}
+
+PortAllocatorSessionMuxer* PortAllocator::GetSessionMuxer(
+    const std::string& sid) const {
+  SessionMuxerMap::const_iterator iter = muxers_.find(sid);
+  if (iter != muxers_.end())
+    return iter->second;
+  return NULL;
+}
+
+void PortAllocator::OnSessionMuxerDestroyed(
+    PortAllocatorSessionMuxer* session) {
+  SessionMuxerMap::iterator iter;
+  for (iter = muxers_.begin(); iter != muxers_.end(); ++iter) {
+    if (iter->second == session)
+      break;
+  }
+  if (iter != muxers_.end())
+    muxers_.erase(iter);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/portallocator.h b/talk/p2p/base/portallocator.h
index 175bdbc..79a08c1 100644
--- a/talk/p2p/base/portallocator.h
+++ b/talk/p2p/base/portallocator.h
@@ -47,18 +47,31 @@
 const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04;
 const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08;
 const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10;
+const uint32 PORTALLOCATOR_ENABLE_BUNDLE = 0x20;
 
 const uint32 kDefaultPortAllocatorFlags = 0;
 
+class PortAllocatorSessionMuxer;
+
 class PortAllocatorSession : public sigslot::has_slots<> {
  public:
-  explicit PortAllocatorSession(uint32 flags) : flags_(flags) {}
+  // TODO Remove session_type argument (and other places), as
+  // its not used.
+  explicit PortAllocatorSession(const std::string& name,
+                                const std::string& session_type,
+                                uint32 flags) :
+      name_(name),
+      session_type_(session_type),
+      flags_(flags) {
+  }
 
   // Subclasses should clean up any ports created.
   virtual ~PortAllocatorSession() {}
 
   uint32 flags() const { return flags_; }
   void set_flags(uint32 flags) { flags_ = flags; }
+  const std::string& name() const { return name_; }
+  const std::string& session_type() const { return session_type_; }
 
   // Prepares an initial set of ports to try.
   virtual void GetInitialPorts() = 0;
@@ -74,23 +87,33 @@
 
   uint32 generation() { return generation_; }
   void set_generation(uint32 generation) { generation_ = generation; }
+  sigslot::signal1<PortAllocatorSession*> SignalDestroyed;
+
+ protected:
+  std::string name_;
+  std::string session_type_;
 
  private:
   uint32 flags_;
   uint32 generation_;
 };
 
-class PortAllocator {
+class PortAllocator : public sigslot::has_slots<> {
  public:
   PortAllocator() :
       flags_(kDefaultPortAllocatorFlags),
       min_port_(0),
       max_port_(0) {
   }
-  virtual ~PortAllocator() {}
+  virtual ~PortAllocator();
 
-  virtual PortAllocatorSession *CreateSession(const std::string &name,
-      const std::string &session_type) = 0;
+  PortAllocatorSession* CreateSession(
+      const std::string& sid,
+      const std::string& name,
+      const std::string& session_type);
+
+  PortAllocatorSessionMuxer* GetSessionMuxer(const std::string& sid) const;
+  void OnSessionMuxerDestroyed(PortAllocatorSessionMuxer* session);
 
   uint32 flags() const { return flags_; }
   void set_flags(uint32 flags) { flags_ = flags; }
@@ -116,11 +139,18 @@
   }
 
  protected:
+  virtual PortAllocatorSession* CreateSession(const std::string &name,
+      const std::string &session_type) = 0;
+
+  typedef std::map<std::string, PortAllocatorSessionMuxer*> SessionMuxerMap;
+
   uint32 flags_;
   std::string agent_;
   talk_base::ProxyInfo proxy_;
   int min_port_;
   int max_port_;
+
+  SessionMuxerMap muxers_;
 };
 
 }  // namespace cricket
diff --git a/talk/p2p/base/portallocatorsessionproxy.cc b/talk/p2p/base/portallocatorsessionproxy.cc
new file mode 100644
index 0000000..8305cd3
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.cc
@@ -0,0 +1,154 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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/p2p/base/portallocatorsessionproxy.h"
+
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/portproxy.h"
+
+namespace cricket {
+
+PortAllocatorSessionMuxer::PortAllocatorSessionMuxer(
+    PortAllocatorSession* session)
+    : session_(session) {
+  session_->SignalPortReady.connect(
+      this, &PortAllocatorSessionMuxer::OnPortReady);
+}
+
+PortAllocatorSessionMuxer::~PortAllocatorSessionMuxer() {
+  for (size_t i = 0; i < session_proxies_.size(); ++i)
+    delete session_proxies_[i];
+
+  SignalDestroyed(this);
+}
+
+void PortAllocatorSessionMuxer::RegisterSessionProxy(
+    PortAllocatorSessionProxy* session_proxy) {
+  session_proxies_.push_back(session_proxy);
+  session_proxy->SignalDestroyed.connect(
+      this, &PortAllocatorSessionMuxer::OnSessionProxyDestroyed);
+  session_proxy->set_impl(session_.get());
+}
+
+void PortAllocatorSessionMuxer::OnPortReady(PortAllocatorSession* session,
+                                            Port* port) {
+  ASSERT(session == session_.get());
+  ports_.push_back(port);
+  port->SignalDestroyed.connect(
+      this, &PortAllocatorSessionMuxer::OnPortDestroyed);
+}
+
+void PortAllocatorSessionMuxer::OnPortDestroyed(Port* port) {
+  std::vector<Port*>::iterator it =
+      std::find(ports_.begin(), ports_.end(), port);
+  if (it != ports_.end())
+    ports_.erase(it);
+}
+
+void PortAllocatorSessionMuxer::OnSessionProxyDestroyed(
+    PortAllocatorSession* proxy) {
+  std::vector<PortAllocatorSessionProxy*>::iterator it =
+      std::find(session_proxies_.begin(), session_proxies_.end(), proxy);
+  if (it != session_proxies_.end())
+    session_proxies_.erase(it);
+
+  if (session_proxies_.empty()) {
+    // Destroy PortAllocatorSession and its associated muxer object if all
+    // proxies belonging to this session are already destroyed.
+    delete this;
+  }
+}
+
+PortAllocatorSessionProxy::~PortAllocatorSessionProxy() {
+  std::map<Port*, PortProxy*>::iterator it;
+  for (it = proxy_ports_.begin(); it != proxy_ports_.end(); it++)
+    delete it->second;
+
+  SignalDestroyed(this);
+}
+
+void PortAllocatorSessionProxy::set_impl(
+    PortAllocatorSession* session) {
+  impl_ = session;
+
+  impl_->SignalCandidatesReady.connect(
+      this, &PortAllocatorSessionProxy::OnCandidatesReady);
+  impl_->SignalPortReady.connect(
+      this, &PortAllocatorSessionProxy::OnPortReady);
+}
+
+void PortAllocatorSessionProxy::GetInitialPorts() {
+  ASSERT(impl_ != NULL);
+  impl_->GetInitialPorts();
+}
+
+void PortAllocatorSessionProxy::StartGetAllPorts() {
+  ASSERT(impl_ != NULL);
+  impl_->StartGetAllPorts();
+}
+
+void PortAllocatorSessionProxy::StopGetAllPorts() {
+  ASSERT(impl_ != NULL);
+  impl_->StartGetAllPorts();
+}
+
+bool PortAllocatorSessionProxy::IsGettingAllPorts() {
+  ASSERT(impl_ != NULL);
+  return impl_->IsGettingAllPorts();
+}
+
+void PortAllocatorSessionProxy::OnPortReady(PortAllocatorSession* session,
+                                            Port* port) {
+  ASSERT(session == impl_);
+
+  PortProxy* proxy_port = new PortProxy(
+      port->thread(), port->type(), port->socket_factory(), port->network(),
+      port->ip(), port->min_port(), port->max_port());
+  proxy_port->set_impl(port);
+  proxy_ports_[port] = proxy_port;
+  SignalPortReady(this, proxy_port);
+}
+
+void PortAllocatorSessionProxy::OnCandidatesReady(
+    PortAllocatorSession* session,
+    const std::vector<Candidate>& candidates) {
+  ASSERT(session == impl_);
+
+  // Since all proxy sessions share a common PortAllocatorSession,
+  // all Candidates will have name associated with the common PAS.
+  // Change Candidate name with the PortAllocatorSessionProxy name.
+  std::vector<Candidate> our_candidates;
+  for (size_t i = 0; i < candidates.size(); ++i) {
+    Candidate new_local_candidate = candidates[i];
+    new_local_candidate.set_name(name_);
+    our_candidates.push_back(new_local_candidate);
+  }
+
+  SignalCandidatesReady(this, our_candidates);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/portallocatorsessionproxy.h b/talk/p2p/base/portallocatorsessionproxy.h
new file mode 100644
index 0000000..5f9eaec
--- /dev/null
+++ b/talk/p2p/base/portallocatorsessionproxy.h
@@ -0,0 +1,101 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+#define TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
+
+#include <string>
+
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace cricket {
+class PortAllocator;
+class PortAllocatorSessionProxy;
+class PortProxy;
+
+// This class maintains the list of cricket::Port* objects. Ports will be
+// deleted upon receiving SignalDestroyed signal. This class is used when
+// PORTALLOCATOR_ENABLE_BUNDLE flag is set.
+
+class PortAllocatorSessionMuxer : public sigslot::has_slots<> {
+ public:
+  explicit PortAllocatorSessionMuxer(PortAllocatorSession* session);
+  virtual ~PortAllocatorSessionMuxer();
+
+  void RegisterSessionProxy(PortAllocatorSessionProxy* session_proxy);
+
+  void OnPortReady(PortAllocatorSession* session, Port* port);
+  void OnPortDestroyed(Port* port);
+
+  const std::vector<Port*>& ports() { return ports_; }
+
+  sigslot::signal1<PortAllocatorSessionMuxer*> SignalDestroyed;
+
+ private:
+  void OnSessionProxyDestroyed(PortAllocatorSession* proxy);
+
+  // Port will be deleted when SignalDestroyed received, otherwise delete
+  // happens when PortAllocatorSession dtor is called.
+  std::vector<Port*> ports_;
+  talk_base::scoped_ptr<PortAllocatorSession> session_;
+  std::vector<PortAllocatorSessionProxy*> session_proxies_;
+};
+
+class PortAllocatorSessionProxy : public PortAllocatorSession {
+ public:
+  PortAllocatorSessionProxy(const std::string& name,
+                            const std::string& session_type,
+                            uint32 flags)
+      : PortAllocatorSession(name, session_type, flags),
+        impl_(NULL) {}
+
+  virtual ~PortAllocatorSessionProxy();
+
+  PortAllocatorSession* impl() { return impl_; }
+  void set_impl(PortAllocatorSession* session);
+
+  // Forwards call to the actual PortAllocatorSession.
+  virtual void GetInitialPorts();
+  virtual void StartGetAllPorts();
+  virtual void StopGetAllPorts();
+  virtual bool IsGettingAllPorts();
+
+ private:
+  void OnPortReady(PortAllocatorSession* session, Port* port);
+  void OnCandidatesReady(PortAllocatorSession* session,
+                         const std::vector<Candidate>& candidates);
+  void OnPortDestroyed(Port* port);
+
+  // This is the actual PortAllocatorSession, owned by PortAllocator.
+  PortAllocatorSession* impl_;
+  std::map<Port*, PortProxy*> proxy_ports_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTALLOCATORSESSIONPROXY_H_
diff --git a/talk/p2p/base/portproxy.cc b/talk/p2p/base/portproxy.cc
new file mode 100644
index 0000000..6c20127
--- /dev/null
+++ b/talk/p2p/base/portproxy.cc
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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/p2p/base/portproxy.h"
+
+namespace cricket {
+
+void PortProxy::set_impl(Port* port) {
+  impl_ = port;
+  impl_->SignalUnknownAddress.connect(
+      this, &PortProxy::OnUnknownAddress);
+  impl_->SignalDestroyed.connect(this, &PortProxy::OnPortDestroyed);
+}
+
+void PortProxy::PrepareAddress() {
+  impl_->PrepareAddress();
+}
+
+Connection* PortProxy::CreateConnection(const Candidate& remote_candidate,
+                                        CandidateOrigin origin) {
+  ASSERT(impl_ != NULL);
+  return impl_->CreateConnection(remote_candidate, origin);
+}
+
+int PortProxy::SendTo(const void* data,
+                      size_t size,
+                      const talk_base::SocketAddress& addr,
+                      bool payload) {
+  ASSERT(impl_ != NULL);
+  return impl_->SendTo(data, size, addr, payload);
+}
+
+int PortProxy::SetOption(talk_base::Socket::Option opt,
+                         int value) {
+  ASSERT(impl_ != NULL);
+  return impl_->SetOption(opt, value);
+}
+
+int PortProxy::GetError() {
+  ASSERT(impl_ != NULL);
+  return impl_->GetError();
+}
+
+void PortProxy::OnUnknownAddress(
+    Port *port,
+    const talk_base::SocketAddress &addr,
+    StunMessage *stun_msg,
+    const std::string &remote_username,
+    bool port_muxed) {
+  ASSERT(port == impl_);
+  ASSERT(!port_muxed);
+  SignalUnknownAddress(this, addr, stun_msg, remote_username, true);
+}
+
+void PortProxy::OnPortDestroyed(Port* port) {
+  ASSERT(port == impl_);
+  // |port| will be destroyed in PortAllocatorSessionMuxer.
+  SignalDestroyed(this);
+}
+
+}  // namespace cricket
diff --git a/talk/p2p/base/portproxy.h b/talk/p2p/base/portproxy.h
new file mode 100644
index 0000000..49707d4
--- /dev/null
+++ b/talk/p2p/base/portproxy.h
@@ -0,0 +1,85 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_BASE_PORTPROXY_H_
+#define TALK_P2P_BASE_PORTPROXY_H_
+
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+class PortProxy : public Port {
+ public:
+  PortProxy(talk_base::Thread* thread, const std::string& type,
+            talk_base::PacketSocketFactory* factory,
+            talk_base::Network* network,
+            const talk_base::IPAddress& ip, int min_port, int max_port)
+      : Port(thread, type, factory, network, ip, min_port, max_port) {
+  }
+  virtual ~PortProxy() {}
+
+  Port* impl() { return impl_; }
+  void set_impl(Port* port);
+
+  // Forwards call to the actual Port.
+  virtual void PrepareAddress();
+  virtual Connection* CreateConnection(const Candidate& remote_candidate,
+    CandidateOrigin origin);
+  virtual int SendTo(
+      const void* data, size_t size, const talk_base::SocketAddress& addr,
+      bool payload);
+  virtual int SetOption(talk_base::Socket::Option opt, int value);
+  virtual int GetError();
+
+  virtual void SendBindingResponse(StunMessage* request,
+                           const talk_base::SocketAddress& addr) {
+    impl_->SendBindingResponse(request, addr);
+  }
+
+  virtual Connection* GetConnection(
+      const talk_base::SocketAddress& remote_addr) {
+    return impl_->GetConnection(remote_addr);
+  }
+
+  virtual void SendBindingErrorResponse(
+        StunMessage* request, const talk_base::SocketAddress& addr,
+        int error_code, const std::string& reason) {
+    impl_->SendBindingErrorResponse(request, addr, error_code, reason);
+  }
+
+ private:
+  void OnUnknownAddress(Port *port, const talk_base::SocketAddress &addr,
+                        StunMessage *stun_msg,
+                        const std::string &remote_username,
+                        bool port_muxed);
+  void OnPortDestroyed(Port* port);
+  Port* impl_;
+};
+
+}  // namespace cricket
+
+#endif  // TALK_P2P_BASE_PORTPROXY_H_
diff --git a/talk/p2p/base/rawtransportchannel.cc b/talk/p2p/base/rawtransportchannel.cc
index 8736053..0c2691c 100644
--- a/talk/p2p/base/rawtransportchannel.cc
+++ b/talk/p2p/base/rawtransportchannel.cc
@@ -95,7 +95,8 @@
 
 void RawTransportChannel::Connect() {
   // Create an allocator that only returns stun and relay ports.
-  allocator_session_ = allocator_->CreateSession(name(), content_type());
+  allocator_session_ = allocator_->CreateSession(
+      session_id(), name(), content_type());
 
   uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP;
 
diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc
index 9891ee2..2439682 100644
--- a/talk/p2p/base/session.cc
+++ b/talk/p2p/base/session.cc
@@ -156,6 +156,7 @@
   TransportChannelImpl* impl = transport_->GetChannel(name);
   if (impl == NULL) {
     impl = transport_->CreateChannel(name, content_type);
+    impl->set_session_id(sid_);
   }
   return impl;
 }
@@ -284,7 +285,7 @@
   transport->SignalChannelGone.connect(
       this, &BaseSession::OnTransportChannelGone);
 
-  transproxy = new TransportProxy(content_name, transport);
+  transproxy = new TransportProxy(sid_, content_name, transport);
   transports_[content_name] = transproxy;
 
   return transproxy;
@@ -363,8 +364,8 @@
   // in SDP. It may be necessary to check content_names in groups of both
   // local and remote descriptions. Assumption here is that when this method
   // returns true, media contents can be muxed.
-  if (local_description()->HasGroup(GN_TOGETHER) &&
-      remote_description()->HasGroup(GN_TOGETHER)) {
+  if (local_description()->HasGroup(GN_BUNDLE) &&
+      remote_description()->HasGroup(GN_BUNDLE)) {
     return true;
   }
   return false;
@@ -378,7 +379,7 @@
     // Always use first content name from the group for muxing. Hence ordering
     // of content names in SDP should match to the order in group.
     const ContentGroup* muxed_content_group =
-        local_description()->GetGroupByName(GN_TOGETHER);
+        local_description()->GetGroupByName(GN_BUNDLE);
     const std::string* content_name =
         muxed_content_group->FirstContentName();
     if (content_name) {
@@ -966,22 +967,22 @@
     if (remote_description()->GetContentByName(it->name) == NULL) {
       return false;
     }
-
-    // TODO: We should add a check to ensure that the updated
-    // contents are compatible with the original contents.
   }
 
-  // Merge the updates into the remote description.
-  // TODO: Merge streams instead of overwriting.
-  for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
-    LOG(LS_INFO) << "Updating content " << it->name;
-    remote_description()->RemoveContentByName(it->name);
-    remote_description()->AddContent(it->name, it->type, it->description);
-  }
+  // TODO: We used to replace contents from an update, but
+  // that no longer works with partial updates.  We need to figure out
+  // a way to merge patial updates into contents.  For now, users of
+  // Session should listen to SignalRemoteDescriptionUpdate and handle
+  // updates.  They should not expect remote_description to be the
+  // latest value.
+  //
+  // for (it = updated_contents.begin(); it != updated_contents.end(); ++it) {
+  //     remote_description()->RemoveContentByName(it->name);
+  //     remote_description()->AddContent(it->name, it->type, it->description);
+  //   }
+  // }
 
-  // TODO: Add an argument that shows what streams were changed.
-  SignalRemoteDescriptionUpdate(this);
-
+  SignalRemoteDescriptionUpdate(this, updated_contents);
   return true;
 }
 
diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h
index f3b6f25..b57468b 100644
--- a/talk/p2p/base/session.h
+++ b/talk/p2p/base/session.h
@@ -81,8 +81,12 @@
 
 class TransportProxy {
  public:
-  TransportProxy(const std::string& content_name, Transport* transport)
-      : content_name_(content_name),
+  TransportProxy(
+      const std::string& sid,
+      const std::string& content_name,
+      Transport* transport)
+      : sid_(sid),
+        content_name_(content_name),
         transport_(transport),
         owner_(true),
         state_(STATE_INIT),
@@ -126,6 +130,7 @@
                                         const std::string& content_type);
   void SetProxyImpl(const std::string& name, TransportChannelProxy* proxy);
 
+  std::string sid_;
   std::string content_name_;
   Transport* transport_;
   bool owner_;
@@ -241,12 +246,12 @@
   // Returns the current state of the session.  See the enum above for details.
   // Each time the state changes, we will fire this signal.
   State state() const { return state_; }
-  sigslot::signal2<BaseSession *, State> SignalState;
+  sigslot::signal2<BaseSession* , State> SignalState;
 
   // Returns the last error in the session.  See the enum above for details.
   // Each time the an error occurs, we will fire this signal.
   Error error() const { return error_; }
-  sigslot::signal2<BaseSession *, Error> SignalError;
+  sigslot::signal2<BaseSession* , Error> SignalError;
 
   // Updates the state, signaling if necessary.
   virtual void SetState(State state);
@@ -254,8 +259,10 @@
   // Updates the error state, signaling if necessary.
   virtual void SetError(Error error);
 
-  // Fired when the remote description is updated.
-  sigslot::signal1<BaseSession *> SignalRemoteDescriptionUpdate;
+  // Fired when the remote description is updated, with the updated
+  // contents.
+  sigslot::signal2<BaseSession* , const ContentInfos&>
+      SignalRemoteDescriptionUpdate;
 
   // Returns the transport that has been negotiated or NULL if
   // negotiation is still in progress.
@@ -534,7 +541,7 @@
   // sending of each message.  When messages are received by the other client,
   // they should be handed to OnIncomingMessage.
   // (These are called only by SessionManager.)
-  sigslot::signal2<Session *, const buzz::XmlElement*> SignalOutgoingMessage;
+  sigslot::signal2<Session* , const buzz::XmlElement*> SignalOutgoingMessage;
   void OnIncomingMessage(const SessionMessage& msg);
 
   void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
diff --git a/talk/p2p/base/session_unittest.cc b/talk/p2p/base/session_unittest.cc
index e663113..aec4986 100644
--- a/talk/p2p/base/session_unittest.cc
+++ b/talk/p2p/base/session_unittest.cc
@@ -561,9 +561,9 @@
 class TestPortAllocatorSession : public cricket::PortAllocatorSession {
  public:
   TestPortAllocatorSession(const std::string& name,
+                           const std::string& session_type,
                            const int port_offset)
-      : PortAllocatorSession(0),
-        name_(name),
+      : PortAllocatorSession(name, session_type, 0),
         port_offset_(port_offset),
         ports_(kNumPorts),
         address_("127.0.0.1", 0),
@@ -618,7 +618,6 @@
   }
 
  private:
-  std::string name_;
   int port_offset_;
   std::vector<cricket::Port*> ports_;
   talk_base::SocketAddress address_;
@@ -636,7 +635,7 @@
   CreateSession(const std::string &name,
                 const std::string &content_type) {
     port_offset_ += 2;
-    return new TestPortAllocatorSession(name, port_offset_ - 2);
+    return new TestPortAllocatorSession(name, content_type, port_offset_ - 2);
   }
 
   int port_offset_;
@@ -984,7 +983,8 @@
     }
   }
 
-  void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session) {
+  void OnSessionRemoteDescriptionUpdate(cricket::BaseSession* session,
+      const cricket::ContentInfos& contents) {
     session_remote_description_update_count++;
   }
 
@@ -1338,14 +1338,23 @@
           new_content_b->description;
       EXPECT_TRUE(new_content_desc_a != NULL);
       EXPECT_TRUE(new_content_desc_b != NULL);
-      EXPECT_NE(old_content_desc_a, new_content_desc_a);
 
-      if (content_name_a != content_name_b) {
-        // If content_name_a != content_name_b, then b's content description
-        // should not have changed since the description-info message only
-        // contained an update for content_name_a.
-        EXPECT_EQ(old_content_desc_b, new_content_desc_b);
-      }
+      // TODO: We used to replace contents from an update, but
+      // that no longer works with partial updates.  We need to figure out
+      // a way to merge patial updates into contents.  For now, users of
+      // Session should listen to SignalRemoteDescriptionUpdate and handle
+      // updates.  They should not expect remote_description to be the
+      // latest value.
+      // See session.cc OnDescriptionInfoMessage.
+
+      // EXPECT_NE(old_content_desc_a, new_content_desc_a);
+
+      // if (content_name_a != content_name_b) {
+      //   // If content_name_a != content_name_b, then b's content description
+      //   // should not have changed since the description-info message only
+      //   // contained an update for content_name_a.
+      //   EXPECT_EQ(old_content_desc_b, new_content_desc_b);
+      // }
 
       EXPECT_TRUE_WAIT(initiator->sent_stanza_count() > 0, kEventTimeout);
       initiator->ExpectSentStanza(
@@ -1998,7 +2007,7 @@
         content_name_a, content_type,
         content_name_b, content_type);
     // Add group information to the offer
-    cricket::ContentGroup group(cricket::GN_TOGETHER);
+    cricket::ContentGroup group(cricket::GN_BUNDLE);
     group.AddContentName(content_name_a);
     group.AddContentName(content_name_b);
     EXPECT_TRUE(group.HasContentName(content_name_a));
@@ -2012,12 +2021,12 @@
         content_name_b, content_type);
     // Check if group "TOGETHER" exists in the offer. If it's present then
     // remote supports muxing.
-    EXPECT_TRUE(offer->HasGroup(cricket::GN_TOGETHER));
+    EXPECT_TRUE(offer->HasGroup(cricket::GN_BUNDLE));
     const cricket::ContentGroup* group_offer =
-        offer->GetGroupByName(cricket::GN_TOGETHER);
+        offer->GetGroupByName(cricket::GN_BUNDLE);
     // Not creating new copy in answer, for test we can use this for test.
     answer->AddGroup(*group_offer);
-    EXPECT_TRUE(answer->HasGroup(cricket::GN_TOGETHER));
+    EXPECT_TRUE(answer->HasGroup(cricket::GN_BUNDLE));
 
     initiator->CreateSession();
     EXPECT_TRUE(initiator->session->Initiate(
diff --git a/talk/p2p/base/transportchannel.h b/talk/p2p/base/transportchannel.h
index 2672e80..74d0107 100644
--- a/talk/p2p/base/transportchannel.h
+++ b/talk/p2p/base/transportchannel.h
@@ -47,6 +47,8 @@
         readable_(false), writable_(false) {}
   virtual ~TransportChannel() {}
 
+  // Returns the session id of this channel.
+  const std::string& session_id() const { return session_id_; }
   // Returns the name of this channel.
   const std::string& name() const { return name_; }
   const std::string& content_type() const { return content_type_; }
@@ -66,6 +68,12 @@
   // supported by all transport types.
   virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
 
+  // Sets session id which created this transport channel.
+  // This is called from TransportProxy::GetOrCreateImpl.
+  void set_session_id(const std::string& session_id) {
+    session_id_ = session_id;
+  }
+
   // Returns the most recent error that occurred on this channel.
   virtual int GetError() = 0;
 
@@ -95,7 +103,9 @@
   // Sets the writable state, signaling if necessary.
   void set_writable(bool writable);
 
+
  private:
+  std::string session_id_;
   std::string name_;
   std::string content_type_;
   bool readable_;
diff --git a/talk/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc
index ec13b86..b77d5ed 100644
--- a/talk/p2p/client/basicportallocator.cc
+++ b/talk/p2p/client/basicportallocator.cc
@@ -229,8 +229,8 @@
     BasicPortAllocator *allocator,
     const std::string &name,
     const std::string &session_type)
-    : PortAllocatorSession(allocator->flags()), allocator_(allocator),
-      name_(name), session_type_(session_type), network_thread_(NULL),
+    : PortAllocatorSession(name, session_type, allocator->flags()),
+      allocator_(allocator), network_thread_(NULL),
       socket_factory_(allocator->socket_factory()), allocation_started_(false),
       network_manager_started_(false),
       running_(false) {
diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h
index d5fce7e..f38bac6 100644
--- a/talk/p2p/client/basicportallocator.h
+++ b/talk/p2p/client/basicportallocator.h
@@ -114,8 +114,6 @@
   ~BasicPortAllocatorSession();
 
   virtual BasicPortAllocator* allocator() { return allocator_; }
-  const std::string& name() const { return name_; }
-  const std::string& session_type() const { return session_type_; }
   talk_base::Thread* network_thread() { return network_thread_; }
   talk_base::PacketSocketFactory* socket_factory() { return socket_factory_; }
 
@@ -154,8 +152,6 @@
   void OnShake();
 
   BasicPortAllocator* allocator_;
-  std::string name_;
-  std::string session_type_;
   talk_base::Thread* network_thread_;
   talk_base::scoped_ptr<talk_base::PacketSocketFactory> owned_socket_factory_;
   talk_base::PacketSocketFactory* socket_factory_;
diff --git a/talk/p2p/client/portallocator_unittest.cc b/talk/p2p/client/portallocator_unittest.cc
index be3a4e1..f5bb37c 100644
--- a/talk/p2p/client/portallocator_unittest.cc
+++ b/talk/p2p/client/portallocator_unittest.cc
@@ -35,6 +35,8 @@
 #include "talk/base/socketaddress.h"
 #include "talk/base/thread.h"
 #include "talk/base/virtualsocketserver.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/portallocatorsessionproxy.h"
 #include "talk/p2p/base/testrelayserver.h"
 #include "talk/p2p/base/teststunserver.h"
 #include "talk/p2p/client/basicportallocator.h"
@@ -44,6 +46,7 @@
 using talk_base::Thread;
 
 static const SocketAddress kClientAddr("11.11.11.11", 0);
+static const SocketAddress kRemoteClientAddr("22.22.22.22", 0);
 static const SocketAddress kStunAddr("99.99.99.1", cricket::STUN_SERVER_PORT);
 static const SocketAddress kRelayUdpIntAddr("99.99.99.2", 5000);
 static const SocketAddress kRelayUdpExtAddr("99.99.99.3", 5001);
@@ -99,6 +102,18 @@
     return true;
   }
 
+  cricket::PortAllocatorSession* CreateSession(
+      const std::string& sid, const std::string& name,
+      const std::string& type, cricket::PortAllocator* allocator) {
+    cricket::PortAllocatorSession* session =
+        allocator->CreateSession(sid, name, type);
+    session->SignalPortReady.connect(this,
+            &PortAllocatorTest::OnPortReady);
+    session->SignalCandidatesReady.connect(this,
+        &PortAllocatorTest::OnCandidatesReady);
+    return session;
+  }
+
   static bool CheckCandidate(const cricket::Candidate& c,
                              const std::string& name, const std::string& type,
                              const std::string& proto,
@@ -112,6 +127,17 @@
     return (addr.port() >= min_port && addr.port() <= max_port);
   }
 
+  cricket::PortAllocator* CreateAllocator() {
+    return new cricket::BasicPortAllocator(
+        &network_manager_, kStunAddr, SocketAddress(),
+        SocketAddress(), SocketAddress());
+  }
+
+  cricket::P2PTransportChannel* CreateTransportChannel(
+      const std::string& name, cricket::PortAllocator* allocator) {
+    return new cricket::P2PTransportChannel(name, "unittest", NULL, allocator);
+  }
+
  protected:
   cricket::BasicPortAllocator& allocator() { return allocator_; }
 
@@ -319,3 +345,52 @@
   EXPECT_EQ(2U, alloc.relay_hosts().size());
   EXPECT_EQ(2U, alloc.stun_hosts().size());
 }
+
+TEST_F(PortAllocatorTest, TestBasicMuxFeatures) {
+  talk_base::scoped_ptr<cricket::PortAllocator> allocator(CreateAllocator());
+  allocator->set_flags(cricket::PORTALLOCATOR_ENABLE_BUNDLE);
+  // Session ID - session1.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session1(
+      CreateSession("session1", "rtp", "audio", allocator.get()));
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session2(
+      CreateSession("session1", "rtcp", "audio", allocator.get()));
+  // We know that PortAllocator is creating a proxy session when bundle flag
+  // is enabled, it's safe to type cast session objects.
+  cricket::PortAllocatorSessionProxy* proxy1 =
+      static_cast<cricket::PortAllocatorSessionProxy*>(session1.get());
+  ASSERT_TRUE(proxy1 != NULL);
+  cricket::PortAllocatorSessionProxy* proxy2 =
+      static_cast<cricket::PortAllocatorSessionProxy*>(session2.get());
+  ASSERT_TRUE(proxy2 != NULL);
+  EXPECT_EQ(proxy1->impl(), proxy2->impl());
+  AddInterface(kClientAddr);
+  session1->GetInitialPorts();
+  session2->GetInitialPorts();
+  // Each session should receive two proxy ports of local and stun.
+  ASSERT_EQ_WAIT(4U, ports_.size(), 1000);
+  EXPECT_EQ(4U, candidates_.size());
+  EXPECT_PRED5(CheckCandidate, candidates_[0],
+      "rtp", "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[1],
+      "rtcp", "local", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[2],
+      "rtp", "stun", "udp", kClientAddr);
+  EXPECT_PRED5(CheckCandidate, candidates_[3],
+      "rtcp", "stun", "udp", kClientAddr);
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session3(
+      CreateSession("session1", "video_rtp", "video", allocator.get()));
+  // ListenToEvents(session3.get());
+  session3->GetInitialPorts();
+  // Since real ports and sessions are already allocated and signal sent, no
+  // new ports will be allocated when new proxy session created.
+  talk_base::Thread::Current()->ProcessMessages(1000);
+  EXPECT_NE(6U, ports_.size());
+  // Creating a PortAllocatorSession with different session name from above.
+  // In this case proxy PAS should have a different PAS.
+  // Session ID - session2.
+  talk_base::scoped_ptr<cricket::PortAllocatorSession> session4(
+        CreateSession("session2", "video_rtp", "video", allocator.get()));
+  cricket::PortAllocatorSessionProxy* proxy4 =
+        static_cast<cricket::PortAllocatorSessionProxy*>(session4.get());
+  EXPECT_NE(proxy4->impl(), proxy1->impl());
+}
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
index 3c770af..1157519 100644
--- a/talk/session/phone/call.cc
+++ b/talk/session/phone/call.cc
@@ -29,6 +29,7 @@
 #include "talk/base/helpers.h"
 #include "talk/base/logging.h"
 #include "talk/base/thread.h"
+#include "talk/p2p/base/parsing.h"
 #include "talk/session/phone/call.h"
 #include "talk/session/phone/mediasessionclient.h"
 
@@ -140,8 +141,8 @@
   StaticVideoViews::const_iterator it;
   for (it = view_request.static_video_views.begin();
        it != view_request.static_video_views.end(); ++it) {
-    NamedSource found_source;
-    bool found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
+    StreamParams found_stream;
+    bool found = recv_streams_.GetVideoStreamBySsrc(it->ssrc, &found_stream);
     if (!found) {
       LOG(LS_WARNING) <<
           "Tried sending view request for bad ssrc: " << it->ssrc;
@@ -178,32 +179,41 @@
   }
 }
 
-void Call::AddVoiceStream(Session *session, uint32 voice_ssrc) {
+
+
+
+void Call::AddAudioRecvStream(Session *session, const StreamParams& stream) {
   VoiceChannel *voice_channel = GetVoiceChannel(session);
-  if (voice_channel && voice_ssrc) {
-    voice_channel->AddRecvStream(StreamParams::CreateLegacy(voice_ssrc));
+  if (voice_channel && stream.has_ssrcs()) {
+    voice_channel->AddRecvStream(stream);
   }
+  recv_streams_.AddAudioStream(stream);
 }
 
-void Call::AddVideoStream(Session *session, uint32 video_ssrc) {
+void Call::AddVideoRecvStream(Session *session, const StreamParams& stream) {
   VideoChannel *video_channel = GetVideoChannel(session);
-  if (video_channel && video_ssrc) {
-    video_channel->AddRecvStream(StreamParams::CreateLegacy(video_ssrc));
+  if (video_channel && stream.has_ssrcs()) {
+    video_channel->AddRecvStream(stream);
   }
+  recv_streams_.AddVideoStream(stream);
 }
 
-void Call::RemoveVoiceStream(Session *session, uint32 voice_ssrc) {
+void Call::RemoveAudioRecvStream(Session *session, const StreamParams& stream) {
   VoiceChannel *voice_channel = GetVoiceChannel(session);
-  if (voice_channel && voice_ssrc) {
-    voice_channel->RemoveRecvStream(voice_ssrc);
+  // TODO: Change RemoveRecvStream to take a stream argument.
+  if (voice_channel && stream.has_ssrcs()) {
+    voice_channel->RemoveRecvStream(stream.first_ssrc());
   }
+  recv_streams_.RemoveAudioStreamByNickAndName(stream.nick, stream.name);
 }
 
-void Call::RemoveVideoStream(Session *session, uint32 video_ssrc) {
+void Call::RemoveVideoRecvStream(Session *session, const StreamParams& stream) {
   VideoChannel *video_channel = GetVideoChannel(session);
-  if (video_channel && video_ssrc) {
-    video_channel->RemoveRecvStream(video_ssrc);
+  // TODO: Change RemoveRecvStream to take a stream argument.
+  if (video_channel && stream.has_ssrcs()) {
+    video_channel->RemoveRecvStream(stream.first_ssrc());
   }
+  recv_streams_.RemoveVideoStreamByNickAndName(stream.nick, stream.name);
 }
 
 void Call::OnMessage(talk_base::Message *message) {
@@ -273,7 +283,10 @@
     sessions_.push_back(session);
     session->SignalState.connect(this, &Call::OnSessionState);
     session->SignalError.connect(this, &Call::OnSessionError);
-    session->SignalInfoMessage.connect(this, &Call::OnSessionInfo);
+    session->SignalInfoMessage.connect(
+        this, &Call::OnSessionInfoMessage);
+    session->SignalRemoteDescriptionUpdate.connect(
+        this, &Call::OnRemoteDescriptionUpdate);
     session->SignalReceivedTerminateReason
       .connect(this, &Call::OnReceivedTerminateReason);
 
@@ -542,11 +555,10 @@
 }
 
 void Call::OnSpeakerMonitor(CurrentSpeakerMonitor* monitor, uint32 ssrc) {
-  NamedSource source;
-  source.ssrc = ssrc;
-  media_sources_.GetAudioSourceBySsrc(ssrc, &source);
+  StreamParams stream;
+  recv_streams_.GetAudioStreamBySsrc(ssrc, &stream);
   SignalSpeakerMonitor(this, static_cast<Session *>(monitor->session()),
-                       source);
+                       stream);
 }
 
 void Call::OnConnectionMonitor(VideoChannel *channel,
@@ -582,92 +594,168 @@
   SignalSessionError(this, static_cast<Session *>(session), error);
 }
 
-void Call::OnSessionInfo(Session *session,
-                         const buzz::XmlElement* action_elem) {
-  // We have a different list of "updates" because we only want to
-  // signal the sources that were added or removed.  We want to filter
-  // out un-changed sources.
-  cricket::MediaSources updates;
+void Call::OnSessionInfoMessage(Session *session,
+                                const buzz::XmlElement* action_elem) {
+  if (!IsJingleViewRequest(action_elem)) {
+    return;
+  }
 
-  if (IsSourcesNotify(action_elem)) {
-    MediaSources sources;
-    ParseError error;
-    if (!ParseSourcesNotify(action_elem, session->remote_description(),
-                            &sources, &error)) {
-      // TODO: Is there a way we can signal an IQ error
-      // back to the sender?
-      LOG(LS_WARNING) << "Invalid sources notify message: " << error.text;
-      return;
-    }
+  ViewRequest view_request;
+  ParseError error;
+  if (!ParseJingleViewRequest(action_elem, &view_request, &error)) {
+    LOG(LS_WARNING) << "Failed to parse view request: " << error.text;
+    return;
+  }
 
-    NamedSources::iterator it;
-    for (it = sources.mutable_audio()->begin();
-         it != sources.mutable_audio()->end(); ++it) {
-      bool found = false;
-      NamedSource found_source;
-      if (it->ssrc_set) {
-        found = media_sources_.GetAudioSourceBySsrc(it->ssrc, &found_source);
-      } else {
-        // For backwards compatibility, we remove by nick.
-        // TODO: Remove once all senders use explicit remove by ssrc.
-        found = media_sources_.GetFirstAudioSourceByNick(it->nick,
-                                                         &found_source);
-        if (found) {
-          it->SetSsrc(found_source.ssrc);
-          it->removed = true;
-        } else {
-          continue;  // No ssrc to remove.
-        }
-      }
-      if (it->removed && found) {
-        RemoveVoiceStream(session, found_source.ssrc);
-        media_sources_.RemoveAudioSourceBySsrc(it->ssrc);
-        updates.mutable_audio()->push_back(*it);
-        LOG(LS_INFO) << "Removed voice stream:  " << found_source.ssrc;
-      } else if (!it->removed && !found) {
-        AddVoiceStream(session, it->ssrc);
-        media_sources_.AddAudioSource(*it);
-        updates.mutable_audio()->push_back(*it);
-        LOG(LS_INFO) << "Added voice stream:  " << it->ssrc;
-      }
-    }
-    for (it = sources.mutable_video()->begin();
-         it != sources.mutable_video()->end(); ++it) {
-      bool found = false;
-      NamedSource found_source;
-      if (it->ssrc_set) {
-        found = media_sources_.GetVideoSourceBySsrc(it->ssrc, &found_source);
-      } else {
-        // For backwards compatibility, we remove by nick.
-        // TODO: Remove once all senders use explicit remove by ssrc.
-        found = media_sources_.GetFirstVideoSourceByNick(it->nick,
-                                                         &found_source);
-        if (found) {
-          it->SetSsrc(found_source.ssrc);
-          it->removed = true;
-        } else {
-          continue;  // No ssrc to remove.
-        }
-      }
-      if (it->removed && found) {
-        RemoveVideoStream(session, found_source.ssrc);
-        media_sources_.RemoveVideoSourceBySsrc(it->ssrc);
-        updates.mutable_video()->push_back(*it);
-        LOG(LS_INFO) << "Removed video stream:  " << found_source.ssrc;
-      } else if (!it->removed && !found) {
-        AddVideoStream(session, it->ssrc);
-        media_sources_.AddVideoSource(*it);
-        updates.mutable_video()->push_back(*it);
-        LOG(LS_INFO) << "Added video stream:  " << it->ssrc;
-      }
-    }
+  if (!SetSendResolutions(session, view_request)) {
+    LOG(LS_WARNING) << "Failed to set send resolutions.";
+  }
+}
 
-    if (!updates.audio().empty() || !updates.video().empty()) {
-      SignalMediaSourcesUpdate(this, session, updates);
+bool Call::SetSendResolutions(
+    Session *session, const ViewRequest& view_request) {
+  // TODO: Change resolution depending on ssrc.  For now,
+  // assume we have the same resolution for all, and change by setting
+  // the remote content to change the codecs, which changes the
+  // resolution.
+  //
+  // Eventually it should look something like this:
+  // VideoChannel *video_channel = GetVideoChannel(session);
+  // for (StaticVideoViews::const_iterator view =
+  //          view_request.static_video_views.begin();
+  //      view != view_request.static_video_views.end(); ++view) {
+  //   if (!video_channel->SetResolution(
+  //           view->ssrc, view->width, view->height, view->framerate)) {
+  //     LOG(LS_WARNING) <<
+  //         "Failed to set view request resolution of ssrc " << view->ssrc;
+  //   }
+  // }
+
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (video_channel == NULL) {
+    return false;
+  }
+
+  // If there are no views, set resolution to 0x0x0.
+  StaticVideoView target_view(0U, 0, 0, 0);
+  if (!view_request.static_video_views.empty()) {
+    target_view = view_request.static_video_views[0];
+  }
+  VideoContentDescription* target_video = new VideoContentDescription();
+  const VideoContentDescription* current_video =
+      GetFirstVideoContentDescription(session->remote_description());
+  for (VideoCodecs::const_iterator current_codec =
+           current_video->codecs().begin();
+       current_codec != current_video->codecs().end(); ++current_codec) {
+    VideoCodec target_codec = *current_codec;
+    target_codec.width = target_view.width;
+    target_codec.height = target_view.height;
+    target_codec.framerate = target_view.framerate;
+    target_video->AddCodec(target_codec);
+  }
+  return video_channel->SetRemoteContent(target_video, CA_UPDATE);
+}
+
+void FindStreamChanges(const std::vector<StreamParams>& streams,
+                       const std::vector<StreamParams>& updates,
+                       std::vector<StreamParams>* added_streams,
+                       std::vector<StreamParams>* removed_streams) {
+  for (std::vector<StreamParams>::const_iterator update = updates.begin();
+       update != updates.end(); ++update) {
+    StreamParams stream;
+    if (GetStreamByNickAndName(streams, update->nick, update->name, &stream)) {
+      if (!update->has_ssrcs()) {
+        removed_streams->push_back(stream);
+      }
+    } else {
+      added_streams->push_back(*update);
     }
   }
 }
 
+void Call::OnRemoteDescriptionUpdate(BaseSession *base_session,
+                                     const ContentInfos& updated_contents) {
+  Session* session = static_cast<Session *>(base_session);
+
+  cricket::MediaStreams added_streams;
+  cricket::MediaStreams removed_streams;
+  std::vector<StreamParams>::const_iterator stream;
+
+  const ContentInfo* audio_content = GetFirstAudioContent(updated_contents);
+  if (audio_content) {
+    const AudioContentDescription* audio_update =
+        static_cast<const AudioContentDescription*>(audio_content->description);
+    if (!audio_update->codecs().empty()) {
+      UpdateVoiceChannelRemoteContent(session, audio_update);
+    }
+
+    FindStreamChanges(recv_streams_.audio(),
+                      audio_update->streams(),
+                      added_streams.mutable_audio(),
+                      removed_streams.mutable_audio());
+    for (stream = added_streams.audio().begin();
+         stream != added_streams.audio().end();
+         ++stream) {
+      AddAudioRecvStream(session, *stream);
+    }
+    for (stream = removed_streams.audio().begin();
+         stream != removed_streams.audio().end();
+         ++stream) {
+      RemoveAudioRecvStream(session, *stream);
+    }
+  }
+
+  const ContentInfo* video_content = GetFirstVideoContent(updated_contents);
+  if (video_content) {
+    const VideoContentDescription* video_update =
+        static_cast<const VideoContentDescription*>(video_content->description);
+    if (!video_update->codecs().empty()) {
+      UpdateVideoChannelRemoteContent(session, video_update);
+    }
+
+    FindStreamChanges(recv_streams_.video(),
+                      video_update->streams(),
+                      added_streams.mutable_video(),
+                      removed_streams.mutable_video());
+    for (stream = added_streams.video().begin();
+         stream != added_streams.video().end();
+         ++stream) {
+      AddVideoRecvStream(session, *stream);
+    }
+    for (stream = removed_streams.video().begin();
+         stream != removed_streams.video().end();
+         ++stream) {
+      RemoveVideoRecvStream(session, *stream);
+    }
+  }
+
+  if (!added_streams.empty() || !removed_streams.empty()) {
+    SignalMediaStreamsUpdate(this, session, added_streams, removed_streams);
+  }
+}
+
+bool Call::UpdateVoiceChannelRemoteContent(
+    Session* session, const AudioContentDescription* audio) {
+  VoiceChannel *voice_channel = GetVoiceChannel(session);
+  if (!voice_channel->SetRemoteContent(audio, CA_UPDATE)) {
+    LOG(LS_ERROR) << "Failure in audio SetRemoteContent with CA_UPDATE";
+    session->SetError(BaseSession::ERROR_CONTENT);
+    return false;
+  }
+  return true;
+}
+
+bool Call::UpdateVideoChannelRemoteContent(
+    Session* session, const VideoContentDescription* video) {
+  VideoChannel *video_channel = GetVideoChannel(session);
+  if (!video_channel->SetRemoteContent(video, CA_UPDATE)) {
+    LOG(LS_ERROR) << "Failure in video SetRemoteContent with CA_UPDATE";
+    session->SetError(BaseSession::ERROR_CONTENT);
+    return false;
+  }
+  return true;
+}
+
 void Call::OnReceivedTerminateReason(Session *session,
                                      const std::string &reason) {
   session_client_->session_manager()->signaling_thread()->Clear(this,
diff --git a/talk/session/phone/call.h b/talk/session/phone/call.h
index 357f884..69d2941 100644
--- a/talk/session/phone/call.h
+++ b/talk/session/phone/call.h
@@ -40,6 +40,7 @@
 #include "talk/session/phone/currentspeakermonitor.h"
 #include "talk/session/phone/mediamessages.h"
 #include "talk/session/phone/mediasession.h"
+#include "talk/session/phone/streamparams.h"
 
 namespace cricket {
 
@@ -61,7 +62,7 @@
   void RejectSession(Session *session);
   void TerminateSession(Session *session);
   void Terminate();
-  bool SendViewRequest(Session* session,
+  bool SendViewRequest(Session *session,
                        const ViewRequest& view_request);
   void SetLocalRenderer(VideoRenderer* renderer);
   void SetVideoRenderer(Session *session, uint32 ssrc,
@@ -105,23 +106,30 @@
       SignalConnectionMonitor;
   sigslot::signal2<Call *, const VoiceMediaInfo&> SignalMediaMonitor;
   sigslot::signal2<Call *, const AudioInfo&> SignalAudioMonitor;
-  // Empty nick on NamedSource means "unknown".
-  // Ssrc of 0 on NamedSource means "no current speaker".
+  // Empty nick on StreamParams means "unknown".
+  // No ssrcs in StreamParams means "no current speaker".
   sigslot::signal3<Call *,
                    Session *,
-                   const NamedSource&> SignalSpeakerMonitor;
+                   const StreamParams&> SignalSpeakerMonitor;
   sigslot::signal2<Call *, const std::vector<ConnectionInfo> &>
       SignalVideoConnectionMonitor;
   sigslot::signal2<Call *, const VideoMediaInfo&> SignalVideoMediaMonitor;
-  sigslot::signal3<Call *,
+  // Gives added streams and removed streams, in that order.
+  sigslot::signal4<Call *,
                    Session *,
-                   const MediaSources&> SignalMediaSourcesUpdate;
+                   const MediaStreams&,
+                   const MediaStreams&> SignalMediaStreamsUpdate;
 
  private:
   void OnMessage(talk_base::Message *message);
   void OnSessionState(BaseSession *session, BaseSession::State state);
   void OnSessionError(BaseSession *session, Session::Error error);
-  void OnSessionInfo(Session *session, const buzz::XmlElement* action_elem);
+  void OnSessionInfoMessage(
+      Session *session, const buzz::XmlElement* action_elem);
+  void OnViewRequest(
+      Session *session, const ViewRequest& view_request);
+  void OnRemoteDescriptionUpdate(
+      BaseSession *session, const ContentInfos& updated_contents);
   void OnReceivedTerminateReason(Session *session, const std::string &reason);
   void IncomingSession(Session *session, const SessionDescription* offer);
   // Returns true on success.
@@ -137,18 +145,25 @@
   void OnConnectionMonitor(VideoChannel *channel,
                            const std::vector<ConnectionInfo> &infos);
   void OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info);
-  VoiceChannel* GetVoiceChannel(Session* session);
-  VideoChannel* GetVideoChannel(Session* session);
-  void AddVoiceStream(Session *session, uint32 voice_ssrc);
-  void AddVideoStream(Session *session, uint32 video_ssrc);
-  void RemoveVoiceStream(Session *session, uint32 voice_ssrc);
-  void RemoveVideoStream(Session *session, uint32 video_ssrc);
+  VoiceChannel* GetVoiceChannel(Session *session);
+  VideoChannel* GetVideoChannel(Session *session);
+  bool SetSendResolutions(Session *session, const ViewRequest& view_request);
+  bool UpdateVoiceChannelRemoteContent(Session *session,
+                                       const AudioContentDescription* audio);
+  bool UpdateVideoChannelRemoteContent(Session *session,
+                                       const VideoContentDescription* video);
+  void AddAudioRecvStream(Session *session, const StreamParams& audio_stream);
+  void AddVideoRecvStream(Session *session, const StreamParams& video_stream);
+  void RemoveAudioRecvStream(
+      Session *session, const StreamParams& audio_stream);
+  void RemoveVideoRecvStream(
+      Session *session, const StreamParams& video_stream);
   void ContinuePlayDTMF();
 
   uint32 id_;
   MediaSessionClient *session_client_;
   std::vector<Session *> sessions_;
-  MediaSources media_sources_;
+  MediaStreams recv_streams_;
   std::map<std::string, VoiceChannel *> voice_channel_map_;
   std::map<std::string, VideoChannel *> video_channel_map_;
   std::map<std::string, CurrentSpeakerMonitor *> speaker_monitor_map_;
diff --git a/talk/session/phone/channel.cc b/talk/session/phone/channel.cc
index a58734f..9e39088 100644
--- a/talk/session/phone/channel.cc
+++ b/talk/session/phone/channel.cc
@@ -137,8 +137,6 @@
       this, &BaseChannel::OnChannelRead);
 
   session_->SignalState.connect(this, &BaseChannel::OnSessionState);
-  session_->SignalRemoteDescriptionUpdate.connect(this,
-      &BaseChannel::OnRemoteDescriptionUpdate);
 
   OnSessionState(session(), session()->state());
   set_rtcp_transport_channel(rtcp_transport_channel);
@@ -450,16 +448,6 @@
   }
 }
 
-void BaseChannel::OnRemoteDescriptionUpdate(BaseSession* session) {
-  const MediaContentDescription* content =
-      GetFirstContent(session->remote_description());
-
-  if (content && !SetRemoteContent(content, CA_UPDATE)) {
-    LOG(LS_ERROR) << "Failure in SetRemoteContent with CA_UPDATE";
-    session->SetError(BaseSession::ERROR_CONTENT);
-  }
-}
-
 void BaseChannel::EnableMedia_w() {
   ASSERT(worker_thread_ == talk_base::Thread::Current());
   if (enabled_)
@@ -713,16 +701,12 @@
                                         ContentAction action) {
   bool ret = UpdateLocalStreams_w(content->streams(), action);
   // Set local SRTP parameters (what we will encrypt with).
-  if (ret) {
-    ret = SetSrtp_w(content->cryptos(), action, CS_LOCAL);
-  }
+  ret &= SetSrtp_w(content->cryptos(), action, CS_LOCAL);
   // Set local RTCP mux parameters.
-  if (ret) {
-    ret = SetRtcpMux_w(content->rtcp_mux(), action, CS_LOCAL);
-  }
+  ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_LOCAL);
   // Set local RTP header extensions.
-  if (ret && content->rtp_header_extensions_set()) {
-    ret = media_channel()->SetRecvRtpHeaderExtensions(
+  if (content->rtp_header_extensions_set()) {
+    ret &= media_channel()->SetRecvRtpHeaderExtensions(
         content->rtp_header_extensions());
   }
   return ret;
@@ -732,17 +716,12 @@
                                          ContentAction action) {
   bool ret = UpdateRemoteStreams_w(content->streams(), action);
   // Set remote SRTP parameters (what the other side will encrypt with).
-  if (ret) {
-    ret = SetSrtp_w(content->cryptos(), action, CS_REMOTE);
-  }
+  ret &= SetSrtp_w(content->cryptos(), action, CS_REMOTE);
   // Set remote RTCP mux parameters.
-  if (ret) {
-    ret = SetRtcpMux_w(content->rtcp_mux(), action, CS_REMOTE);
-  }
-
+  ret &= SetRtcpMux_w(content->rtcp_mux(), action, CS_REMOTE);
   // Set remote RTP header extensions.
-  if (ret && content->rtp_header_extensions_set()) {
-    ret = media_channel()->SetSendRtpHeaderExtensions(
+  if (content->rtp_header_extensions_set()) {
+    ret &= media_channel()->SetSendRtpHeaderExtensions(
         content->rtp_header_extensions());
   }
   return ret;
@@ -995,12 +974,14 @@
   const AudioContentDescription* audio =
       static_cast<const AudioContentDescription*>(content);
   ASSERT(audio != NULL);
+  if (!audio) return false;
 
   bool ret = SetBaseLocalContent_w(content, action);
-
   // Set local audio codecs (what we want to receive).
-  if (ret) {
-    ret = media_channel()->SetRecvCodecs(audio->codecs());
+  // TODO: Change action != CA_UPDATE to !audio->partial() when partial
+  // is set properly.
+  if (action != CA_UPDATE || audio->has_codecs()) {
+    ret &= media_channel()->SetRecvCodecs(audio->codecs());
   }
 
   // If everything worked, see if we can start receiving.
@@ -1021,25 +1002,29 @@
   const AudioContentDescription* audio =
       static_cast<const AudioContentDescription*>(content);
   ASSERT(audio != NULL);
+  if (!audio) return false;
 
+  bool ret = true;
   // Set remote video codecs (what the other side wants to receive).
-  bool ret = media_channel()->SetSendCodecs(audio->codecs());
-
-  if (ret) {
-    ret =  SetBaseRemoteContent_w(content, action);
+  if (action != CA_UPDATE || audio->has_codecs()) {
+    ret &= media_channel()->SetSendCodecs(audio->codecs());
   }
 
-  // Tweak our audio processing settings, if needed.
-  int audio_options = 0;
-  if (audio->conference_mode()) {
-    audio_options |= OPT_CONFERENCE;
-  }
-  if (audio->agc_minus_10db()) {
-    audio_options |= OPT_AGC_MINUS_10DB;
-  }
-  if (!media_channel()->SetOptions(audio_options)) {
-    // Log an error on failure, but don't abort the call.
-    LOG(LS_ERROR) << "Failed to set voice channel options";
+  ret &= SetBaseRemoteContent_w(content, action);
+
+  if (action != CA_UPDATE) {
+    // Tweak our audio processing settings, if needed.
+    int audio_options = 0;
+    if (audio->conference_mode()) {
+      audio_options |= OPT_CONFERENCE;
+    }
+    if (audio->agc_minus_10db()) {
+      audio_options |= OPT_AGC_MINUS_10DB;
+    }
+    if (!media_channel()->SetOptions(audio_options)) {
+      // Log an error on failure, but don't abort the call.
+      LOG(LS_ERROR) << "Failed to set voice channel options";
+    }
   }
 
   // If everything worked, see if we can start sending.
@@ -1305,12 +1290,12 @@
   const VideoContentDescription* video =
       static_cast<const VideoContentDescription*>(content);
   ASSERT(video != NULL);
+  if (!video) return false;
 
   bool ret = SetBaseLocalContent_w(content, action);
-
   // Set local video codecs (what we want to receive).
-  if (ret) {
-    ret = media_channel()->SetRecvCodecs(video->codecs());
+  if (action != CA_UPDATE || video->has_codecs()) {
+    ret &= media_channel()->SetRecvCodecs(video->codecs());
   }
 
   // If everything worked, see if we can start receiving.
@@ -1331,28 +1316,32 @@
   const VideoContentDescription* video =
       static_cast<const VideoContentDescription*>(content);
   ASSERT(video != NULL);
+  if (!video) return false;
 
+  bool ret = true;
   // Set remote video codecs (what the other side wants to receive).
-  bool ret = media_channel()->SetSendCodecs(video->codecs());
+  if (action != CA_UPDATE || video->has_codecs()) {
+    ret &= media_channel()->SetSendCodecs(video->codecs());
+  }
 
-  if (ret) {
-    ret =  SetBaseRemoteContent_w(content, action);
-  }
-  // Tweak our video processing settings, if needed.
-  int video_options = 0;
-  if (video->conference_mode()) {
-    video_options |= OPT_CONFERENCE;
-  }
-  if (!media_channel()->SetOptions(video_options)) {
-    // Log an error on failure, but don't abort the call.
-    LOG(LS_ERROR) << "Failed to set video channel options";
-  }
-  // Set bandwidth parameters (what the other side wants to get, default=auto)
-  if (ret) {
+  ret &= SetBaseRemoteContent_w(content, action);
+
+  if (action != CA_UPDATE) {
+    // Tweak our video processing settings, if needed.
+    int video_options = 0;
+    if (video->conference_mode()) {
+      video_options |= OPT_CONFERENCE;
+    }
+    if (!media_channel()->SetOptions(video_options)) {
+      // Log an error on failure, but don't abort the call.
+      LOG(LS_ERROR) << "Failed to set video channel options";
+    }
+    // Set bandwidth parameters (what the other side wants to get, default=auto)
     int bandwidth_bps = video->bandwidth();
     bool auto_bandwidth = (bandwidth_bps == kAutoBandwidth);
-    ret = media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
+    ret &= media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
   }
+
   // If everything worked, see if we can start sending.
   if (ret) {
     set_has_remote_content(true);
diff --git a/talk/session/phone/channel.h b/talk/session/phone/channel.h
index f262e00..42f3969 100644
--- a/talk/session/phone/channel.h
+++ b/talk/session/phone/channel.h
@@ -168,6 +168,13 @@
 
   SsrcMuxFilter* ssrc_filter() { return &ssrc_filter_; }
 
+  const std::vector<StreamParams>& local_streams() const {
+    return local_streams_;
+  }
+  const std::vector<StreamParams>& remote_streams() const {
+    return remote_streams_;
+  }
+
  protected:
   MediaEngineInterface* media_engine() const { return media_engine_; }
   virtual MediaChannel* media_channel() const { return media_channel_; }
@@ -208,7 +215,6 @@
 
   // Setting the send codec based on the remote description.
   void OnSessionState(BaseSession* session, BaseSession::State state);
-  void OnRemoteDescriptionUpdate(BaseSession* session);
 
   void EnableMedia_w();
   void DisableMedia_w();
diff --git a/talk/session/phone/channel_unittest.cc b/talk/session/phone/channel_unittest.cc
index b53cca0..a134f45 100644
--- a/talk/session/phone/channel_unittest.cc
+++ b/talk/session/phone/channel_unittest.cc
@@ -458,16 +458,19 @@
                              media_channel1_->codecs()[0]));
     // Now update with other codecs.
     typename T::Content update_content;
+    update_content.set_partial(true);
     CreateContent(0, kIsacCodec, kH264SvcCodec, &update_content);
     EXPECT_TRUE(channel1_->SetRemoteContent(&update_content, CA_UPDATE));
     ASSERT_EQ(1U, media_channel1_->codecs().size());
     EXPECT_TRUE(CodecMatches(update_content.codecs()[0],
                              media_channel1_->codecs()[0]));
-
-    // Now update without any codec.
+    // Now update without any codecs. This is ignored.
     typename T::Content empty_content;
+    empty_content.set_partial(true);
     EXPECT_TRUE(channel1_->SetRemoteContent(&empty_content, CA_UPDATE));
-    ASSERT_EQ(0U, media_channel1_->codecs().size());
+    ASSERT_EQ(1U, media_channel1_->codecs().size());
+    EXPECT_TRUE(CodecMatches(update_content.codecs()[0],
+                             media_channel1_->codecs()[0]));
   }
 
   // Test that Add/RemoveStream properly forward to the media channel.
@@ -518,9 +521,9 @@
     // Update the local streams by adding another sending stream.
     // Use a partial updated session description.
     typename T::Content content2;
-    CreateContent(0, kPcmuCodec, kH264Codec, &content2);
     content2.AddStream(stream2);
     content2.AddStream(stream3);
+    content2.set_partial(true);
     EXPECT_TRUE(channel1_->SetLocalContent(&content2, CA_UPDATE));
     ASSERT_EQ(3u, media_channel1_->send_streams().size());
     EXPECT_EQ(stream1, media_channel1_->send_streams()[0]);
@@ -530,10 +533,9 @@
     // Update the local streams by removing the first sending stream.
     // This is done by removing all SSRCS for this particular stream.
     typename T::Content content3;
-    CreateContent(0, kPcmuCodec, kH264Codec, &content3);
     stream1.ssrcs.clear();
     content3.AddStream(stream1);
-
+    content3.set_partial(true);
     EXPECT_TRUE(channel1_->SetLocalContent(&content3, CA_UPDATE));
     ASSERT_EQ(2u, media_channel1_->send_streams().size());
     EXPECT_EQ(stream2, media_channel1_->send_streams()[0]);
@@ -570,15 +572,16 @@
     EXPECT_EQ(0u, media_channel1_->recv_streams().size());
     EXPECT_TRUE(channel1_->SetRemoteContent(&content1, CA_OFFER));
 
+    ASSERT_EQ(1u, media_channel1_->codecs().size());
     ASSERT_EQ(1u, media_channel1_->recv_streams().size());
     EXPECT_EQ(stream1, media_channel1_->recv_streams()[0]);
 
-    // Update the local streams by adding another sending stream.
+    // Update the remote streams by adding another sending stream.
     // Use a partial updated session description.
     typename T::Content content2;
-    CreateContent(0, kPcmuCodec, kH264Codec, &content2);
     content2.AddStream(stream2);
     content2.AddStream(stream3);
+    content2.set_partial(true);
     EXPECT_TRUE(channel1_->SetRemoteContent(&content2, CA_UPDATE));
     ASSERT_EQ(3u, media_channel1_->recv_streams().size());
     EXPECT_EQ(stream1, media_channel1_->recv_streams()[0]);
@@ -588,10 +591,9 @@
     // Update the remote streams by removing the first stream.
     // This is done by removing all SSRCS for this particular stream.
     typename T::Content content3;
-    CreateContent(0, kPcmuCodec, kH264Codec, &content3);
     stream1.ssrcs.clear();
     content3.AddStream(stream1);
-
+    content3.set_partial(true);
     EXPECT_TRUE(channel1_->SetRemoteContent(&content3, CA_UPDATE));
     ASSERT_EQ(2u, media_channel1_->recv_streams().size());
     EXPECT_EQ(stream2, media_channel1_->recv_streams()[0]);
diff --git a/talk/session/phone/currentspeakermonitor.cc b/talk/session/phone/currentspeakermonitor.cc
index 1ca6bd9..a148b7a 100644
--- a/talk/session/phone/currentspeakermonitor.cc
+++ b/talk/session/phone/currentspeakermonitor.cc
@@ -56,8 +56,8 @@
   if (!started_) {
     call_->SignalAudioMonitor.connect(
         this, &CurrentSpeakerMonitor::OnAudioMonitor);
-    call_->SignalMediaSourcesUpdate.connect(
-        this, &CurrentSpeakerMonitor::OnMediaSourcesUpdate);
+    call_->SignalMediaStreamsUpdate.connect(
+        this, &CurrentSpeakerMonitor::OnMediaStreamsUpdate);
 
     started_ = true;
   }
@@ -66,7 +66,7 @@
 void CurrentSpeakerMonitor::Stop() {
   if (started_) {
     call_->SignalAudioMonitor.disconnect(this);
-    call_->SignalMediaSourcesUpdate.disconnect(this);
+    call_->SignalMediaStreamsUpdate.disconnect(this);
 
     started_ = false;
     ssrc_to_speaking_state_map_.clear();
@@ -187,21 +187,20 @@
   }
 }
 
-void CurrentSpeakerMonitor::OnMediaSourcesUpdate(Call* call,
+void CurrentSpeakerMonitor::OnMediaStreamsUpdate(Call* call,
                                                  Session* session,
-                                                 const MediaSources& sources) {
+                                                 const MediaStreams& added,
+                                                 const MediaStreams& removed) {
   if (call == call_ && session == session_) {
-    // Update the speaking state map based on new or removed sources.
-    NamedSources::const_iterator it;
-    for (it = sources.audio().begin(); it != sources.audio().end(); it++) {
-      if (it->ssrc_set) {
-        if (it->removed) {
-          ssrc_to_speaking_state_map_.erase(it->ssrc);
-        } else if (ssrc_to_speaking_state_map_.find(it->ssrc) ==
-            ssrc_to_speaking_state_map_.begin()) {
-          ssrc_to_speaking_state_map_[it->ssrc] = SS_NOT_SPEAKING;
-        }
-      }
+    // Update the speaking state map based on added and removed streams.
+    for (std::vector<cricket::StreamParams>::const_iterator
+           it = removed.video().begin(); it != removed.video().end(); ++it) {
+      ssrc_to_speaking_state_map_.erase(it->first_ssrc());
+    }
+
+    for (std::vector<cricket::StreamParams>::const_iterator
+           it = added.video().begin(); it != added.video().end(); ++it) {
+      ssrc_to_speaking_state_map_[it->first_ssrc()] = SS_NOT_SPEAKING;
     }
   }
 }
diff --git a/talk/session/phone/currentspeakermonitor.h b/talk/session/phone/currentspeakermonitor.h
index 84207fb..7871a82 100644
--- a/talk/session/phone/currentspeakermonitor.h
+++ b/talk/session/phone/currentspeakermonitor.h
@@ -42,7 +42,7 @@
 class Call;
 class Session;
 struct AudioInfo;
-struct MediaSources;
+struct MediaStreams;
 
 // Note that the call's audio monitor must be started before this is started.
 // It's recommended that the audio monitor be started with a 100 ms period.
@@ -68,9 +68,10 @@
 
  private:
   void OnAudioMonitor(Call* call, const AudioInfo& info);
-  void OnMediaSourcesUpdate(Call* call,
+  void OnMediaStreamsUpdate(Call* call,
                             Session* session,
-                            const MediaSources& sources);
+                            const MediaStreams& added,
+                            const MediaStreams& removed);
 
   // These are states that a participant will pass through so that we gradually
   // recognize that they have started and stopped speaking.  This avoids
diff --git a/talk/session/phone/fakewebrtccommon.h b/talk/session/phone/fakewebrtccommon.h
index 1ade630..a960e8b 100644
--- a/talk/session/phone/fakewebrtccommon.h
+++ b/talk/session/phone/fakewebrtccommon.h
@@ -42,6 +42,9 @@
 #define WEBRTC_FUNC_CONST(method, args) \
   virtual int method args const
 
+#define WEBRTC_BOOL_FUNC(method, args) \
+  virtual bool method args
+
 #define WEBRTC_CHECK_CHANNEL(channel) \
   if (channels_.find(channel) == channels_.end()) return -1;
 
diff --git a/talk/session/phone/fakewebrtcvideoengine.h b/talk/session/phone/fakewebrtcvideoengine.h
index 3833a44..cbac785 100644
--- a/talk/session/phone/fakewebrtcvideoengine.h
+++ b/talk/session/phone/fakewebrtcvideoengine.h
@@ -84,7 +84,10 @@
           rtcp_status_(webrtc::kRtcpNone),
           key_frame_request_method_(webrtc::kViEKeyFrameRequestNone),
           tmmbr_(false),
-          nack_(false) {
+          remb_send_(false),
+          remb_(false),
+          nack_(false),
+          hybrid_nack_fec_(false) {
       memset(&send_codec, 0, sizeof(send_codec));
     }
     int capture_id_;
@@ -96,7 +99,10 @@
     webrtc::ViERTCPMode rtcp_status_;
     webrtc::ViEKeyFrameRequestMethod key_frame_request_method_;
     bool tmmbr_;
+    bool remb_send_;  // This channel send REMB packets.
+    bool remb_;  // This channel report BWE using remb.
     bool nack_;
+    bool hybrid_nack_fec_;
     std::vector<webrtc::VideoCodec> recv_codecs;
     webrtc::VideoCodec send_codec;
   };
@@ -186,10 +192,22 @@
     WEBRTC_ASSERT_CHANNEL(channel);
     return channels_.find(channel)->second->tmmbr_;
   }
+  bool GetRembStatus(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->remb_;
+  }
+  bool GetRembStatusSend(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->remb_send_;
+  }
   bool GetNackStatus(int channel) const {
     WEBRTC_ASSERT_CHANNEL(channel);
     return channels_.find(channel)->second->nack_;
   }
+  bool GetHybridNackFecStatus(int channel) const {
+    WEBRTC_ASSERT_CHANNEL(channel);
+    return channels_.find(channel)->second->hybrid_nack_fec_;
+  }
 
   bool ReceiveCodecRegistered(int channel,
                               const webrtc::VideoCodec& codec) const {
@@ -258,9 +276,12 @@
     const cricket::VideoCodec& c(*codecs_[list_number]);
     if ("I420" == c.name) {
       out_codec.codecType = webrtc::kVideoCodecI420;
-    }
-    else if ("VP8" == c.name) {
+    } else if ("VP8" == c.name) {
       out_codec.codecType = webrtc::kVideoCodecVP8;
+    } else if ("red" == c.name) {
+      out_codec.codecType = webrtc::kVideoCodecRED;
+    } else if ("ulpfec" == c.name) {
+      out_codec.codecType = webrtc::kVideoCodecULPFEC;
     } else {
       out_codec.codecType = webrtc::kVideoCodecUnknown;
     }
@@ -493,18 +514,18 @@
     channels_[channel]->rtcp_status_ = mode;
     return 0;
   }
-  WEBRTC_STUB(GetRTCPStatus, (const int, webrtc::ViERTCPMode&));
+  WEBRTC_STUB_CONST(GetRTCPStatus, (const int, webrtc::ViERTCPMode&));
   WEBRTC_FUNC(SetRTCPCName, (const int channel,
                              const char rtcp_cname[KMaxRTCPCNameLength])) {
     WEBRTC_CHECK_CHANNEL(channel);
     channels_[channel]->cname_.assign(rtcp_cname);
     return 0;
   }
-  WEBRTC_FUNC(GetRTCPCName, (const int channel,
-                             char rtcp_cname[KMaxRTCPCNameLength])) {
+  WEBRTC_FUNC_CONST(GetRTCPCName, (const int channel,
+                                   char rtcp_cname[KMaxRTCPCNameLength])) {
     WEBRTC_CHECK_CHANNEL(channel);
     talk_base::strcpyn(rtcp_cname, KMaxRTCPCNameLength,
-                       channels_[channel]->cname_.c_str());
+                       channels_.find(channel)->second->cname_.c_str());
     return 0;
   }
   WEBRTC_STUB_CONST(GetRemoteRTCPCName, (const int, char*));
@@ -513,12 +534,23 @@
   WEBRTC_FUNC(SetNACKStatus, (const int channel, const bool enable)) {
     WEBRTC_CHECK_CHANNEL(channel);
     channels_[channel]->nack_ = enable;
+    channels_[channel]->hybrid_nack_fec_ = false;
     return 0;
   }
   WEBRTC_STUB(SetFECStatus, (const int, const bool, const unsigned char,
       const unsigned char));
-  WEBRTC_STUB(SetHybridNACKFECStatus, (const int, const bool,
-      const unsigned char, const unsigned char));
+  WEBRTC_FUNC(SetHybridNACKFECStatus, (const int channel, const bool enable,
+      const unsigned char red_type, const unsigned char fec_type)) {
+    WEBRTC_CHECK_CHANNEL(channel);
+    if (red_type == fec_type ||
+        red_type == channels_[channel]->send_codec.plType ||
+        fec_type == channels_[channel]->send_codec.plType) {
+      return -1;
+    }
+    channels_[channel]->nack_ = false;
+    channels_[channel]->hybrid_nack_fec_ = true;
+    return 0;
+  }
   WEBRTC_FUNC(SetKeyFrameRequestMethod,
               (const int channel,
                const webrtc::ViEKeyFrameRequestMethod method)) {
@@ -526,6 +558,13 @@
     channels_[channel]->key_frame_request_method_ = method;
     return 0;
   }
+  WEBRTC_BOOL_FUNC(SetRembStatus, (int channel, bool send, bool receive)) {
+    if (channels_.find(channel) == channels_.end())
+      return false;
+    channels_[channel]->remb_send_ = send;
+    channels_[channel]->remb_ = receive;
+    return true;
+  }
   WEBRTC_FUNC(SetTMMBRStatus, (const int channel, const bool enable)) {
     WEBRTC_CHECK_CHANNEL(channel);
     channels_[channel]->tmmbr_ = enable;
@@ -542,7 +581,8 @@
 
   WEBRTC_STUB(SetRTPKeepAliveStatus, (const int, bool, const char,
       const unsigned int));
-  WEBRTC_STUB(GetRTPKeepAliveStatus, (const int, bool&, char&, unsigned int&));
+  WEBRTC_STUB_CONST(GetRTPKeepAliveStatus,
+                    (const int, bool&, char&, unsigned int&));
   WEBRTC_STUB(StartRTPDump, (const int, const char*, webrtc::RTPDirections));
   WEBRTC_STUB(StopRTPDump, (const int, webrtc::RTPDirections));
   WEBRTC_STUB(RegisterRTPObserver, (const int, webrtc::ViERTPObserver&));
diff --git a/talk/session/phone/fakewebrtcvoiceengine.h b/talk/session/phone/fakewebrtcvoiceengine.h
index 5b3ff0c..29735f8 100644
--- a/talk/session/phone/fakewebrtcvoiceengine.h
+++ b/talk/session/phone/fakewebrtcvoiceengine.h
@@ -541,7 +541,6 @@
 
   // webrtc::VoENetEqStats
   WEBRTC_STUB(GetNetworkStatistics, (int, webrtc::NetworkStatistics&));
-  WEBRTC_STUB(GetJitterStatistics, (int, webrtc::JitterStatistics&));
   WEBRTC_STUB(GetPreferredBufferSize, (int, short unsigned int&));
   WEBRTC_STUB(ResetJitterStatistics, (int));
 
diff --git a/talk/session/phone/mediaengine.h b/talk/session/phone/mediaengine.h
index 0f42adb..cc77099 100644
--- a/talk/session/phone/mediaengine.h
+++ b/talk/session/phone/mediaengine.h
@@ -32,6 +32,7 @@
 #include <CoreAudio/CoreAudio.h>
 #endif
 
+#include <climits>
 #include <string>
 #include <vector>
 
@@ -46,6 +47,13 @@
 
 namespace cricket {
 
+// TODO: For now, a hard-coded ssrc is used as the video ssrc.
+// This is because when the video frame is passed to the mediaprocessor for
+// processing, it doesn't have the correct ssrc. Since currently only Tx
+// Video processing is supported, this is ok. When we switch over to trigger
+// from capturer, this should be fixed and this const removed.
+const uint32 kDummyVideoSsrc = 0xFFFFFFFF;
+
 class VideoCapturer;
 
 // MediaEngineInterface is an abstraction of a media engine which can be
@@ -150,7 +158,6 @@
   static MediaEngineInterface* Create();
 };
 
-
 // CompositeMediaEngine constructs a MediaEngine from separate
 // voice and video engine classes.
 template<class VOICE, class VIDEO>
diff --git a/talk/session/phone/mediamessages.cc b/talk/session/phone/mediamessages.cc
index c40b03f..3dd6982 100644
--- a/talk/session/phone/mediamessages.cc
+++ b/talk/session/phone/mediamessages.cc
@@ -43,46 +43,10 @@
 
 namespace {
 
-bool GetFirstSourceByNick(const NamedSources& sources,
-                          const std::string& nick,
-                          NamedSource* source_out) {
-  for (NamedSources::const_iterator source = sources.begin();
-       source != sources.end(); ++source) {
-    if (source->nick == nick) {
-      *source_out = *source;
-      return true;
-    }
-  }
-  return false;
-}
-
-bool GetSourceBySsrc(const NamedSources& sources, uint32 ssrc,
-                     NamedSource* source_out) {
-  for (NamedSources::const_iterator source = sources.begin();
-       source != sources.end(); ++source) {
-    if (source->ssrc == ssrc) {
-      *source_out = *source;
-      return true;
-    }
-  }
-  return false;
-}
-
-// NOTE: There is no check here for duplicate sources, so check before
+// NOTE: There is no check here for duplicate streams, so check before
 // adding.
-void AddSource(NamedSources* sources, const NamedSource& source) {
-  sources->push_back(source);
-}
-
-void RemoveSourceBySsrc(uint32 ssrc, NamedSources* sources) {
-  for (NamedSources::iterator source = sources->begin();
-       source != sources->end(); ) {
-    if (source->ssrc == ssrc) {
-      source = sources->erase(source);
-    } else {
-      ++source;
-    }
-  }
+void AddStream(std::vector<StreamParams>* streams, const StreamParams& stream) {
+  streams->push_back(stream);
 }
 
 bool ParseSsrc(const std::string& string, uint32* ssrc) {
@@ -96,29 +60,6 @@
   return ParseSsrc(element->BodyText(), ssrc);
 }
 
-bool ParseNamedSource(const buzz::XmlElement* source_elem,
-                      NamedSource* named_source,
-                      ParseError* error) {
-  named_source->nick = source_elem->Attr(QN_NICK);
-  if (named_source->nick.empty()) {
-    return BadParse("Missing or invalid nick.", error);
-  }
-
-  named_source->name = source_elem->Attr(QN_NAME);
-
-  const buzz::XmlElement* ssrc_elem =
-      source_elem->FirstNamed(QN_JINGLE_DRAFT_SSRC);
-  if (ssrc_elem != NULL && !ssrc_elem->BodyText().empty()) {
-    uint32 ssrc;
-    if (!ParseSsrc(ssrc_elem->BodyText(), &ssrc)) {
-      return BadParse("Missing or invalid ssrc.", error);
-    }
-    named_source->SetSsrc(ssrc);
-  }
-
-  return true;
-}
-
 // Builds a <view> element according to the following spec:
 // goto/jinglemuc
 buzz::XmlElement* CreateViewElem(const std::string& name,
@@ -157,47 +98,49 @@
 
 }  //  namespace
 
-bool MediaSources::GetFirstAudioSourceByNick(
-    const std::string& nick, NamedSource* source) {
-  return GetFirstSourceByNick(audio_, nick, source);
+bool MediaStreams::GetAudioStreamByNickAndName(
+    const std::string& nick, const std::string& name, StreamParams* stream) {
+  return GetStreamByNickAndName(audio_, nick, name, stream);
 }
 
-bool MediaSources::GetFirstVideoSourceByNick(
-    const std::string& nick, NamedSource* source) {
-  return GetFirstSourceByNick(video_, nick, source);
+bool MediaStreams::GetVideoStreamByNickAndName(
+    const std::string& nick, const std::string& name, StreamParams* stream) {
+  return GetStreamByNickAndName(video_, nick, name, stream);
 }
 
-void MediaSources::CopyFrom(const MediaSources& sources) {
-  audio_ = sources.audio_;
-  video_ = sources.video_;
+void MediaStreams::CopyFrom(const MediaStreams& streams) {
+  audio_ = streams.audio_;
+  video_ = streams.video_;
 }
 
-bool MediaSources::GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source) {
-  return GetSourceBySsrc(audio_, ssrc, source);
+bool MediaStreams::GetAudioStreamBySsrc(uint32 ssrc, StreamParams* stream) {
+  return GetStreamBySsrc(audio_, ssrc, stream);
 }
 
-bool MediaSources::GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source) {
-  return GetSourceBySsrc(video_, ssrc, source);
+bool MediaStreams::GetVideoStreamBySsrc(uint32 ssrc, StreamParams* stream) {
+  return GetStreamBySsrc(video_, ssrc, stream);
 }
 
-void MediaSources::AddAudioSource(const NamedSource& source) {
-  AddSource(&audio_, source);
+void MediaStreams::AddAudioStream(const StreamParams& stream) {
+  AddStream(&audio_, stream);
 }
 
-void MediaSources::AddVideoSource(const NamedSource& source) {
-  AddSource(&video_, source);
+void MediaStreams::AddVideoStream(const StreamParams& stream) {
+  AddStream(&video_, stream);
 }
 
-void MediaSources::RemoveAudioSourceBySsrc(uint32 ssrc) {
-  RemoveSourceBySsrc(ssrc, &audio_);
+void MediaStreams::RemoveAudioStreamByNickAndName(
+    const std::string& nick, const std::string& name) {
+  RemoveStreamByNickAndName(&audio_, nick, name);
 }
 
-void MediaSources::RemoveVideoSourceBySsrc(uint32 ssrc) {
-  RemoveSourceBySsrc(ssrc, &video_);
+void MediaStreams::RemoveVideoStreamByNickAndName(
+    const std::string& nick, const std::string& name) {
+  RemoveStreamByNickAndName(&video_, nick, name);
 }
 
-bool IsJingleViewRequest(const XmlElements& action_elems) {
-  return GetXmlElement(action_elems, QN_JINGLE_DRAFT_VIEW) != NULL;
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem) {
+  return action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW) != NULL;
 }
 
 bool ParseStaticVideoView(const buzz::XmlElement* view_elem,
@@ -221,26 +164,25 @@
   return true;
 }
 
-bool ParseJingleViewRequest(const XmlElements& action_elems,
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
                             ViewRequest* view_request,
                             ParseError* error) {
-  for (XmlElements::const_iterator iter = action_elems.begin();
-       iter != action_elems.end(); ++iter) {
-    const buzz::XmlElement* view_elem = *iter;
-    if (view_elem->Name() == QN_JINGLE_DRAFT_VIEW) {
-      std::string type = view_elem->Attr(QN_TYPE);
-      if (STR_JINGLE_DRAFT_VIEW_TYPE_NONE == type) {
-        view_request->static_video_views.clear();
-        return true;
-      } else if (STR_JINGLE_DRAFT_VIEW_TYPE_STATIC == type) {
-        StaticVideoView static_video_view(0, 0, 0, 0);
-        if (!ParseStaticVideoView(view_elem, &static_video_view, error)) {
-          return false;
-        }
-        view_request->static_video_views.push_back(static_video_view);
-      } else {
-        LOG(LS_INFO) << "Ingnoring unknown view type: " << type;
+  for (const buzz::XmlElement* view_elem =
+           action_elem->FirstNamed(QN_JINGLE_DRAFT_VIEW);
+       view_elem != NULL;
+       view_elem = view_elem->NextNamed(QN_JINGLE_DRAFT_VIEW)) {
+    std::string type = view_elem->Attr(QN_TYPE);
+    if (STR_JINGLE_DRAFT_VIEW_TYPE_NONE == type) {
+      view_request->static_video_views.clear();
+      return true;
+    } else if (STR_JINGLE_DRAFT_VIEW_TYPE_STATIC == type) {
+      StaticVideoView static_video_view(0, 0, 0, 0);
+      if (!ParseStaticVideoView(view_elem, &static_video_view, error)) {
+        return false;
       }
+      view_request->static_video_views.push_back(static_video_view);
+    } else {
+      LOG(LS_INFO) << "Ingnoring unknown view type: " << type;
     }
   }
   return true;
@@ -262,48 +204,6 @@
   return true;
 }
 
-bool IsSourcesNotify(const buzz::XmlElement* action_elem) {
-  return action_elem->FirstNamed(QN_JINGLE_LEGACY_NOTIFY) != NULL;
-}
-
-bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
-                        const SessionDescription* session_description,
-                        MediaSources* sources,
-                        ParseError* error) {
-  for (const buzz::XmlElement* notify_elem =
-           action_elem->FirstNamed(QN_JINGLE_LEGACY_NOTIFY);
-       notify_elem != NULL;
-       notify_elem = notify_elem->NextNamed(QN_JINGLE_LEGACY_NOTIFY)) {
-    std::string content_name = notify_elem->Attr(QN_NAME);
-    for (const buzz::XmlElement* source_elem =
-             notify_elem->FirstNamed(QN_JINGLE_LEGACY_SOURCE);
-         source_elem != NULL;
-         source_elem = source_elem->NextNamed(QN_JINGLE_LEGACY_SOURCE)) {
-      NamedSource named_source;
-      if (!ParseNamedSource(source_elem, &named_source, error)) {
-        return false;
-      }
-
-      if (session_description == NULL) {
-        return BadParse("Unknown content name: " + content_name, error);
-      }
-      const ContentInfo* content =
-          FindContentInfoByName(session_description->contents(), content_name);
-      if (content == NULL) {
-        return BadParse("Unknown content name: " + content_name, error);
-      }
-
-      if (IsAudioContent(content)) {
-        sources->mutable_audio()->push_back(named_source);
-      } else if (IsVideoContent(content)) {
-        sources->mutable_video()->push_back(named_source);
-      }
-    }
-  }
-
-  return true;
-}
-
 bool ParseSsrcAsLegacyStream(const buzz::XmlElement* desc_elem,
                              std::vector<StreamParams>* streams,
                              ParseError* error) {
diff --git a/talk/session/phone/mediamessages.h b/talk/session/phone/mediamessages.h
index 11b9e89..8a8a6de 100644
--- a/talk/session/phone/mediamessages.h
+++ b/talk/session/phone/mediamessages.h
@@ -45,63 +45,50 @@
 
 namespace cricket {
 
-struct StreamParams;
+class StreamParams;
 
-// In a <notify> message, there are number of sources with names.
-// This represents one such named source.
-struct NamedSource {
-  NamedSource() : ssrc(0), ssrc_set(false), removed(false) {}
+// A collection of audio and video streams. Most of the methods are
+// merely for convenience. Many of these methods are keyed by ssrc,
+// which is the source identifier in the RTP spec
+// (http://tools.ietf.org/html/rfc3550).
+struct MediaStreams {
+ public:
+  MediaStreams() {}
+  void CopyFrom(const MediaStreams& sources);
 
-  void SetSsrc(uint32 ssrc) {
-    this->ssrc = ssrc;
-    this->ssrc_set = true;
+  bool empty() const {
+    return audio_.empty() && video_.empty();
   }
 
-  std::string nick;
-  std::string name;
-  uint32 ssrc;
-  bool ssrc_set;
-  bool removed;
-};
+  std::vector<StreamParams>* mutable_audio() { return &audio_; }
+  std::vector<StreamParams>* mutable_video() { return &video_; }
+  const std::vector<StreamParams>& audio() const { return audio_; }
+  const std::vector<StreamParams>& video() const { return video_; }
 
-// TODO: Remove this, according to c++ readability.
-typedef std::vector<NamedSource> NamedSources;
-
-// A collection of named audio sources and named video sources, as
-// would be found in a typical <notify> message.  Most of the methods
-// are merely for convenience. Many of these methods are keyed by
-// ssrc, which is the source identifier in the RTP spec
-// (http://tools.ietf.org/html/rfc3550).
-struct MediaSources {
- public:
-  MediaSources() {}
-  void CopyFrom(const MediaSources& sources);
-
-  NamedSources* mutable_audio() { return &audio_; }
-  NamedSources* mutable_video() { return &video_; }
-  const NamedSources& audio() const { return audio_; }
-  const NamedSources& video() const { return video_; }
-
+  // Remove the streams with the given name.  Names are only unique to
+  // nicks, so you need the nick as well.
+  bool GetAudioStreamByNickAndName(
+      const std::string& nick, const std::string& name, StreamParams* source);
+  bool GetVideoStreamByNickAndName(
+      const std::string& nick, const std::string& name, StreamParams* source);
   // Get the source with the given ssrc.  Returns true if found.
-  bool GetAudioSourceBySsrc(uint32 ssrc, NamedSource* source);
-  bool GetVideoSourceBySsrc(uint32 ssrc, NamedSource* source);
-  // Get the first source with the given nick.  Returns true if found.
-  // TODO: Remove the following two methods once all
-  // senders use explicit-remove by ssrc.
-  bool GetFirstAudioSourceByNick(const std::string& nick, NamedSource* source);
-  bool GetFirstVideoSourceByNick(const std::string& nick, NamedSource* source);
+  bool GetAudioStreamBySsrc(uint32 ssrc, StreamParams* source);
+  bool GetVideoStreamBySsrc(uint32 ssrc, StreamParams* source);
   // Add a source.
-  void AddAudioSource(const NamedSource& source);
-  void AddVideoSource(const NamedSource& source);
-  // Remove the source with the given ssrc.
-  void RemoveAudioSourceBySsrc(uint32 ssrc);
-  void RemoveVideoSourceBySsrc(uint32 ssrc);
+  void AddAudioStream(const StreamParams& source);
+  void AddVideoStream(const StreamParams& source);
+  // Remove the source with the given name.  Names are only unique to
+  // nicks, so you need the nick as well.
+  void RemoveAudioStreamByNickAndName(const std::string& nick,
+                                      const std::string& name);
+  void RemoveVideoStreamByNickAndName(const std::string& nick,
+                                      const std::string& name);
 
  private:
-  NamedSources audio_;
-  NamedSources video_;
+  std::vector<StreamParams> audio_;
+  std::vector<StreamParams> video_;
 
-  DISALLOW_COPY_AND_ASSIGN(MediaSources);
+  DISALLOW_COPY_AND_ASSIGN(MediaStreams);
 };
 
 // In a <view> message, there are a number of views specified.  This
@@ -129,12 +116,13 @@
   StaticVideoViews static_video_views;
 };
 
-// If the elems of a parent (usually <jingle>) constitute a view request.
-bool IsJingleViewRequest(const XmlElements& elems);
+// If the parent element (usually <jingle>) is a jingle view.
+bool IsJingleViewRequest(const buzz::XmlElement* action_elem);
 
-// Parses a view request from jingle contents (<view>s).  If it
-// fails, returns false and fills an error message.
-bool ParseJingleViewRequest(const XmlElements& elems,
+// Parses a view request from the parent element (usually
+// <jingle>). If it fails, it returns false and fills an error
+// message.
+bool ParseJingleViewRequest(const buzz::XmlElement* action_elem,
                             ViewRequest* view_request,
                             ParseError* error);
 
@@ -149,14 +137,6 @@
 // description-info as soon as reflector is capable of sending it.
 bool IsSourcesNotify(const buzz::XmlElement* action_elem);
 
-// Parses a notify message from XML.  If it fails, returns false and
-// fills in an error message.
-// The session_description is needed to map content_name => media type.
-bool ParseSourcesNotify(const buzz::XmlElement* action_elem,
-                        const SessionDescription* session_description,
-                        MediaSources* sources,
-                        ParseError* error);
-
 // If the given elem has <streams>.
 bool HasJingleStreams(const buzz::XmlElement* desc_elem);
 
diff --git a/talk/session/phone/mediamessages_unittest.cc b/talk/session/phone/mediamessages_unittest.cc
index 0290c02..a76d296 100644
--- a/talk/session/phone/mediamessages_unittest.cc
+++ b/talk/session/phone/mediamessages_unittest.cc
@@ -48,11 +48,6 @@
     "  type='none'"
     "/>";
 
-static const char kNotifyEmptyXml[] =
-    "<notify xmlns='google:jingle'"
-    "  name='video1'"
-    "/>";
-
 class MediaMessagesTest : public testing::Test {
  public:
   // CreateMediaSessionDescription uses a static variable cricket::NS_JINGLE_RTP
@@ -78,55 +73,6 @@
              "</view>";
   }
 
-  static std::string NotifyAddXml(const std::string& content_name,
-                                  const std::string& nick,
-                                  const std::string& name,
-                                  const std::string& ssrc) {
-    return "<notify xmlns='google:jingle'"
-           "  name='" + content_name + "'"
-           ">"
-           "  <source"
-           "    nick='" + nick + "'"
-           "    name='" + name + "'"
-           "  >"
-           "    <ssrc>" + ssrc + "</ssrc>"
-           "  </source>"
-           "</notify>";
-  }
-
-  static std::string NotifyTwoSourceXml(const std::string& name,
-                                        const std::string& nick1,
-                                        const std::string& ssrc1,
-                                        const std::string& nick2,
-                                        const std::string& ssrc2) {
-    return "<notify xmlns='google:jingle'"
-           "  name='" + name + "'"
-           ">"
-           "  <source"
-           "    nick='" + nick1 + "'"
-           "  >"
-           "    <ssrc>" + ssrc1 + "</ssrc>"
-           "  </source>"
-           "  <source"
-           "    nick='" + nick2 + "'"
-           "  >"
-           "    <ssrc>" + ssrc2 + "</ssrc>"
-           "  </source>"
-           "</notify>";
-  }
-
-  static std::string NotifyImplicitRemoveXml(const std::string& content_name,
-                                             const std::string& nick) {
-    return "<notify xmlns='google:jingle'"
-           "  name='" + content_name + "'"
-           ">"
-           "  <source"
-           "    nick='" + nick + "'"
-           "  >"
-           "  </source>"
-           "</notify>";
-  }
-
   static cricket::StreamParams CreateStream(const std::string& nick,
                                             const std::string& name,
                                             uint32 ssrc1,
@@ -197,14 +143,19 @@
 
 // Test serializing/deserializing an empty <view> message.
 TEST_F(MediaMessagesTest, ViewNoneToFromXml) {
-  talk_base::scoped_ptr<buzz::XmlElement> expected_view_elem(
-      buzz::XmlElement::ForStr(kViewVideoNoneXml));
+  buzz::XmlElement* expected_view_elem =
+      buzz::XmlElement::ForStr(kViewVideoNoneXml);
+  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+
+  EXPECT_FALSE(cricket::IsJingleViewRequest(action_elem.get()));
+  action_elem->AddElement(expected_view_elem);
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
 
   cricket::ViewRequest view_request;
   cricket::XmlElements actual_view_elems;
   cricket::WriteError error;
 
-  EXPECT_FALSE(cricket::IsJingleViewRequest(actual_view_elems));
   ASSERT_TRUE(cricket::WriteJingleViewRequest(
       "video1", view_request, &actual_view_elems, &error));
 
@@ -212,18 +163,22 @@
   EXPECT_EQ(expected_view_elem->Str(), actual_view_elems[0]->Str());
 
   cricket::ParseError parse_error;
-  EXPECT_TRUE(cricket::IsJingleViewRequest(actual_view_elems));
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
   ASSERT_TRUE(cricket::ParseJingleViewRequest(
-      actual_view_elems, &view_request, &parse_error));
+      action_elem.get(), &view_request, &parse_error));
   EXPECT_EQ(0U, view_request.static_video_views.size());
 }
 
 // Test serializing/deserializing an a simple vga <view> message.
 TEST_F(MediaMessagesTest, ViewVgaToFromXml) {
-  talk_base::scoped_ptr<buzz::XmlElement> expected_view_elem1(
-      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("1234")));
-  talk_base::scoped_ptr<buzz::XmlElement> expected_view_elem2(
-      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("2468")));
+  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+  buzz::XmlElement* expected_view_elem1 =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("1234"));
+  buzz::XmlElement* expected_view_elem2 =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("2468"));
+  action_elem->AddElement(expected_view_elem1);
+  action_elem->AddElement(expected_view_elem2);
 
   cricket::ViewRequest view_request;
   cricket::XmlElements actual_view_elems;
@@ -243,9 +198,9 @@
 
   view_request.static_video_views.clear();
   cricket::ParseError parse_error;
-  EXPECT_TRUE(cricket::IsJingleViewRequest(actual_view_elems));
+  EXPECT_TRUE(cricket::IsJingleViewRequest(action_elem.get()));
   ASSERT_TRUE(cricket::ParseJingleViewRequest(
-      actual_view_elems, &view_request, &parse_error));
+      action_elem.get(), &view_request, &parse_error));
   EXPECT_EQ(2U, view_request.static_video_views.size());
   EXPECT_EQ(1234U, view_request.static_video_views[0].ssrc);
   EXPECT_EQ(640, view_request.static_video_views[0].width);
@@ -256,128 +211,18 @@
 
 // Test deserializing bad view XML.
 TEST_F(MediaMessagesTest, ParseBadViewXml) {
-  talk_base::scoped_ptr<buzz::XmlElement> view_elem(
-      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("not-an-ssrc")));
-  XmlElements view_elems;
-  view_elems.push_back(view_elem.get());
+  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
+      new buzz::XmlElement(QN_JINGLE));
+  buzz::XmlElement* view_elem =
+      buzz::XmlElement::ForStr(ViewVideoStaticVgaXml("not-an-ssrc"));
+  action_elem->AddElement(view_elem);
 
   cricket::ViewRequest view_request;
   cricket::ParseError parse_error;
   ASSERT_FALSE(cricket::ParseJingleViewRequest(
-      view_elems, &view_request, &parse_error));
+      action_elem.get(), &view_request, &parse_error));
 }
 
-// Test serializing/deserializing an empty session-info message.
-TEST_F(MediaMessagesTest, NotifyFromEmptyXml) {
-  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
-      new buzz::XmlElement(cricket::QN_JINGLE));
-  EXPECT_FALSE(cricket::IsSourcesNotify(action_elem.get()));
-}
-
-// Test serializing/deserializing an empty <notify> message.
-TEST_F(MediaMessagesTest, NotifyEmptyFromXml) {
-  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
-      new buzz::XmlElement(cricket::QN_JINGLE));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(kNotifyEmptyXml));
-
-  cricket::MediaSources sources;
-  cricket::ParseError error;
-
-  EXPECT_TRUE(cricket::IsSourcesNotify(action_elem.get()));
-  ASSERT_TRUE(cricket::ParseSourcesNotify(action_elem.get(),
-                                          remote_description_.get(),
-                                          &sources, &error));
-
-  EXPECT_EQ(0U, sources.audio().size());
-  EXPECT_EQ(0U, sources.video().size());
-}
-
-// Test serializing/deserializing a complex <notify> message.
-TEST_F(MediaMessagesTest, NotifyFromXml) {
-  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
-      new buzz::XmlElement(cricket::QN_JINGLE));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyAddXml(
-          "video1", "Joe", "Facetime", "1234")));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyAddXml(
-          "video1", "Bob", "Microsoft Word", "2468")));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyAddXml(
-          "video1", "Bob", "", "3692")));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyImplicitRemoveXml(
-          "audio1", "Joe")));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyAddXml(
-          "audio1", "Bob", "", "3692")));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyTwoSourceXml(
-          "video1", "Joe", "1234", "Bob", "2468")));
-
-  cricket::MediaSources sources;
-  cricket::ParseError error;
-
-  EXPECT_TRUE(cricket::IsSourcesNotify(action_elem.get()));
-  ASSERT_TRUE(cricket::ParseSourcesNotify(action_elem.get(),
-                                          remote_description_.get(),
-                                          &sources, &error));
-
-  ASSERT_EQ(5U, sources.video().size());
-  ASSERT_EQ(2U, sources.audio().size());
-
-  EXPECT_EQ("Joe", sources.video()[0].nick);
-  EXPECT_EQ("Facetime", sources.video()[0].name);
-  EXPECT_EQ(1234U, sources.video()[0].ssrc);
-  EXPECT_TRUE(sources.video()[0].ssrc_set);
-  EXPECT_FALSE(sources.video()[0].removed);
-
-  EXPECT_EQ("Bob", sources.video()[1].nick);
-  EXPECT_EQ("Microsoft Word", sources.video()[1].name);
-  EXPECT_EQ(2468U, sources.video()[1].ssrc);
-  EXPECT_TRUE(sources.video()[1].ssrc_set);
-  EXPECT_FALSE(sources.video()[0].removed);
-
-  EXPECT_EQ("Bob", sources.video()[2].nick);
-  EXPECT_EQ(3692U, sources.video()[2].ssrc);
-  EXPECT_TRUE(sources.video()[2].ssrc_set);
-  EXPECT_EQ("", sources.video()[2].name);
-  EXPECT_FALSE(sources.video()[0].removed);
-
-  EXPECT_EQ("Joe", sources.video()[3].nick);
-  EXPECT_EQ(1234U, sources.video()[3].ssrc);
-
-  EXPECT_EQ("Bob", sources.video()[4].nick);
-  EXPECT_EQ(2468U, sources.video()[4].ssrc);
-
-  EXPECT_EQ("Joe", sources.audio()[0].nick);
-  EXPECT_FALSE(sources.audio()[0].ssrc_set);
-  EXPECT_FALSE(sources.video()[0].removed);
-}
-
-// Test serializing/deserializing a malformed <notify> message.
-TEST_F(MediaMessagesTest, NotifyFromBadXml) {
-  MediaSources sources;
-  ParseError error;
-
-  // Bad ssrc
-  talk_base::scoped_ptr<buzz::XmlElement> action_elem(
-      new buzz::XmlElement(cricket::QN_JINGLE));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyAddXml("video1", "Joe", "", "XYZ")));
-  EXPECT_TRUE(cricket::IsSourcesNotify(action_elem.get()));
-  EXPECT_FALSE(cricket::ParseSourcesNotify(
-      action_elem.get(), remote_description_.get(), &sources, &error));
-
-  // Bad nick
-  action_elem.reset(new buzz::XmlElement(cricket::QN_JINGLE));
-  action_elem->AddElement(
-      buzz::XmlElement::ForStr(NotifyAddXml("video1", "", "", "1234")));
-  EXPECT_TRUE(cricket::IsSourcesNotify(action_elem.get()));
-  EXPECT_FALSE(cricket::ParseSourcesNotify(
-      action_elem.get(), remote_description_.get(), &sources, &error));
-}
 
 // Test serializing/deserializing typical streams xml.
 TEST_F(MediaMessagesTest, StreamsToFromXml) {
diff --git a/talk/session/phone/mediasession.cc b/talk/session/phone/mediasession.cc
index 0784b56..67ac082 100644
--- a/talk/session/phone/mediasession.cc
+++ b/talk/session/phone/mediasession.cc
@@ -288,6 +288,7 @@
       // TODO: Remove this legacy stream when all apps use StreamParams.
       audio->AddLegacyStream(talk_base::CreateRandomNonZeroId());
     }
+    audio->set_multistream(options.is_muc);
     audio->set_rtcp_mux(options.rtcp_mux_enabled);
     audio->set_lang(lang_);
 
@@ -340,6 +341,7 @@
       // TODO: Remove this legacy stream when all apps use StreamParams.
       video->AddLegacyStream(talk_base::CreateRandomNonZeroId());
     }
+    video->set_multistream(options.is_muc);
     video->set_bandwidth(options.video_bandwidth);
     video->set_rtcp_mux(options.rtcp_mux_enabled);
 
@@ -347,11 +349,9 @@
       CryptoParamsVec video_cryptos;
       if (current_description) {
         // Copy crypto parameters from the previous offer.
-        const ContentInfo* info =
-            GetFirstVideoContent(current_description);
-        if (info) {
-          const VideoContentDescription* desc =
-              static_cast<const VideoContentDescription*>(info->description);
+        const VideoContentDescription* desc =
+            GetFirstVideoContentDescription(current_description);
+        if (desc) {
           video_cryptos = desc->cryptos();
         }
       }
@@ -491,10 +491,9 @@
         if (current_description) {
           // Check if this crypto already exist in the previous
           // session description. Use it in that case.
-          const ContentInfo* info = GetFirstVideoContent(current_description);
-          if (info) {
-            const VideoContentDescription* desc =
-                static_cast<const VideoContentDescription*>(info->description);
+          const VideoContentDescription* desc =
+              GetFirstVideoContentDescription(current_description);
+          if (desc) {
             const CryptoParamsVec& cryptos = desc->cryptos();
             for (CryptoParamsVec::const_iterator it = cryptos.begin();
                  it != cryptos.end(); ++it) {
@@ -539,12 +538,8 @@
   return IsMediaContent(content, MEDIA_TYPE_VIDEO);
 }
 
-static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+static const ContentInfo* GetFirstMediaContent(const ContentInfos& contents,
                                                MediaType media_type) {
-  if (sdesc == NULL)
-    return NULL;
-
-  const ContentInfos& contents = sdesc->contents();
   for (ContentInfos::const_iterator content = contents.begin();
        content != contents.end(); content++) {
     if (IsMediaContent(&*content, media_type)) {
@@ -554,6 +549,22 @@
   return NULL;
 }
 
+const ContentInfo* GetFirstAudioContent(const ContentInfos& contents) {
+  return GetFirstMediaContent(contents, MEDIA_TYPE_AUDIO);
+}
+
+const ContentInfo* GetFirstVideoContent(const ContentInfos& contents) {
+  return GetFirstMediaContent(contents, MEDIA_TYPE_VIDEO);
+}
+
+static const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+                                               MediaType media_type) {
+  if (sdesc == NULL)
+    return NULL;
+
+  return GetFirstMediaContent(sdesc->contents(), media_type);
+}
+
 const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) {
   return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO);
 }
@@ -562,4 +573,18 @@
   return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO);
 }
 
+const AudioContentDescription* GetFirstAudioContentDescription(
+    const SessionDescription* sdesc) {
+  const ContentInfo* content = GetFirstAudioContent(sdesc);
+  const ContentDescription* description = content ? content->description : NULL;
+  return static_cast<const AudioContentDescription*>(description);
+}
+
+const VideoContentDescription* GetFirstVideoContentDescription(
+    const SessionDescription* sdesc) {
+  const ContentInfo* content = GetFirstVideoContent(sdesc);
+  const ContentDescription* description = content ? content->description : NULL;
+  return static_cast<const VideoContentDescription*>(description);
+}
+
 }  // namespace cricket
diff --git a/talk/session/phone/mediasession.h b/talk/session/phone/mediasession.h
index c35a994..c2798c0 100644
--- a/talk/session/phone/mediasession.h
+++ b/talk/session/phone/mediasession.h
@@ -125,6 +125,7 @@
   }
 
   virtual MediaType type() const = 0;
+  virtual bool has_codecs() const = 0;
 
   bool rtcp_mux() const { return rtcp_mux_; }
   void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
@@ -227,6 +228,8 @@
   };
 
   const std::vector<C>& codecs() const { return codecs_; }
+  void set_codecs(const std::vector<C>& codecs) { codecs_ = codecs; }
+  virtual bool has_codecs() const { return !codecs_.empty(); }
   void AddCodec(const C& codec) {
     codecs_.push_back(codec);
   }
@@ -302,8 +305,14 @@
 // Convenience functions.
 bool IsAudioContent(const ContentInfo* content);
 bool IsVideoContent(const ContentInfo* content);
+const ContentInfo* GetFirstAudioContent(const ContentInfos& contents);
+const ContentInfo* GetFirstVideoContent(const ContentInfos& contents);
 const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc);
 const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc);
+const AudioContentDescription* GetFirstAudioContentDescription(
+    const SessionDescription* sdesc);
+const VideoContentDescription* GetFirstVideoContentDescription(
+    const SessionDescription* sdesc);
 
 }  // namespace cricket
 
diff --git a/talk/session/phone/mediasession_unittest.cc b/talk/session/phone/mediasession_unittest.cc
index 4dd88c9..9bb2db2 100644
--- a/talk/session/phone/mediasession_unittest.cc
+++ b/talk/session/phone/mediasession_unittest.cc
@@ -56,8 +56,9 @@
 using cricket::ContentInfo;
 using cricket::CryptoParamsVec;
 using cricket::AudioContentDescription;
-using cricket::MediaContentDescription;
 using cricket::VideoContentDescription;
+using cricket::GetFirstAudioContentDescription;
+using cricket::GetFirstVideoContentDescription;
 using cricket::kAutoBandwidth;
 using cricket::AudioCodec;
 using cricket::VideoCodec;
@@ -163,29 +164,6 @@
   }
 
  protected:
-  const MediaContentDescription*  GetMediaDescription(
-      const SessionDescription* sdesc,
-      const std::string& content_name) {
-    const ContentInfo* content = sdesc->GetContentByName(content_name);
-    if (content == NULL) {
-      return NULL;
-    }
-    return static_cast<const MediaContentDescription*>(
-        content->description);
-  }
-
-  const AudioContentDescription*  GetAudioDescription(
-      const SessionDescription* sdesc) {
-    return static_cast<const AudioContentDescription*>(
-        GetMediaDescription(sdesc, "audio"));
-  }
-
-  const VideoContentDescription*  GetVideoDescription(
-      const SessionDescription* sdesc) {
-    return static_cast<const VideoContentDescription*>(
-        GetMediaDescription(sdesc, "video"));
-  }
-
   MediaSessionDescriptionFactory f1_;
   MediaSessionDescriptionFactory f2_;
 };
@@ -342,56 +320,56 @@
 
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
-  ASSERT_TRUE(NULL != GetAudioDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetAudioDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(answer.get()));
-  EXPECT_TRUE(GetAudioDescription(offer.get())->rtcp_mux());
-  EXPECT_TRUE(GetVideoDescription(offer.get())->rtcp_mux());
-  EXPECT_TRUE(GetAudioDescription(answer.get())->rtcp_mux());
-  EXPECT_TRUE(GetVideoDescription(answer.get())->rtcp_mux());
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
 
   offer_opts.rtcp_mux_enabled = true;
   answer_opts.rtcp_mux_enabled = false;
 
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
-  ASSERT_TRUE(NULL != GetAudioDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetAudioDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(answer.get()));
-  EXPECT_TRUE(GetAudioDescription(offer.get())->rtcp_mux());
-  EXPECT_TRUE(GetVideoDescription(offer.get())->rtcp_mux());
-  EXPECT_FALSE(GetAudioDescription(answer.get())->rtcp_mux());
-  EXPECT_FALSE(GetVideoDescription(answer.get())->rtcp_mux());
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
 
   offer_opts.rtcp_mux_enabled = false;
   answer_opts.rtcp_mux_enabled = true;
 
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
-  ASSERT_TRUE(NULL != GetAudioDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetAudioDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(answer.get()));
-  EXPECT_FALSE(GetAudioDescription(offer.get())->rtcp_mux());
-  EXPECT_FALSE(GetVideoDescription(offer.get())->rtcp_mux());
-  EXPECT_FALSE(GetAudioDescription(answer.get())->rtcp_mux());
-  EXPECT_FALSE(GetVideoDescription(answer.get())->rtcp_mux());
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
 
   offer_opts.rtcp_mux_enabled = false;
   answer_opts.rtcp_mux_enabled = false;
 
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
-  ASSERT_TRUE(NULL != GetAudioDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(offer.get()));
-  ASSERT_TRUE(NULL != GetAudioDescription(answer.get()));
-  ASSERT_TRUE(NULL != GetVideoDescription(answer.get()));
-  EXPECT_FALSE(GetAudioDescription(offer.get())->rtcp_mux());
-  EXPECT_FALSE(GetVideoDescription(offer.get())->rtcp_mux());
-  EXPECT_FALSE(GetAudioDescription(answer.get())->rtcp_mux());
-  EXPECT_FALSE(GetVideoDescription(answer.get())->rtcp_mux());
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(offer.get()));
+  ASSERT_TRUE(NULL != GetFirstAudioContentDescription(answer.get()));
+  ASSERT_TRUE(NULL != GetFirstVideoContentDescription(answer.get()));
+  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
+  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
 }
 
 // Create an audio-only answer to a video offer.
diff --git a/talk/session/phone/mediasessionclient_unittest.cc b/talk/session/phone/mediasessionclient_unittest.cc
index 0b99106..933ef35 100644
--- a/talk/session/phone/mediasessionclient_unittest.cc
+++ b/talk/session/phone/mediasessionclient_unittest.cc
@@ -849,8 +849,9 @@
       "</cli:iq>";
 }
 
-std::string JingleNotifyAdd(const std::string& content_name,
+std::string JingleStreamAdd(const std::string& content_name,
                             const std::string& nick,
+                            const std::string& name,
                             const std::string& ssrc) {
   return \
       "<iq"
@@ -861,21 +862,30 @@
       "  id='150'>"
       "  <jingle"
       "    xmlns='urn:xmpp:jingle:1'"
-      "    action='session-info'>"
-      "    <notify"
-      "      xmlns='google:jingle'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
       "      name='" + content_name + "'>"
-      "      <source"
-      "        nick='" + nick + "'>"
-      "        <ssrc>"  + ssrc + "</ssrc>"
-      "      </source>"
-      "    </notify>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'>"
+      "            <ssrc>"  + ssrc + "</ssrc>"
+      "          </stream>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
       "  </jingle>"
       "</iq>";
 }
 
-std::string JingleNotifyImplicitRemove(const std::string& content_name,
-                                       const std::string& nick) {
+std::string JingleStreamRemove(const std::string& content_name,
+                               const std::string& nick,
+                               const std::string& name) {
   return \
       "<iq"
       "  xmlns='jabber:client'"
@@ -885,14 +895,21 @@
       "  id='150'>"
       "  <jingle"
       "    xmlns='urn:xmpp:jingle:1'"
-      "    action='session-info'>"
-      "    <notify"
-      "      xmlns='google:jingle'"
+      "    action='description-info'>"
+      "    <content"
+      "      xmlns='urn:xmpp:jingle:1'"
       "      name='" + content_name + "'>"
-      "      <source"
-      "        nick='" + nick + "'>"
-       "      </source>"
-      "    </notify>"
+      "      <description"
+      "        xmlns='urn:xmpp:jingle:apps:rtp:1'"
+      "        media='" + content_name + "'>"
+      "        <streams"
+      "          xmlns='google:jingle'>"
+      "          <stream"
+      "            nick='" + nick + "'"
+      "            name='" + name + "'/>"
+      "        </streams>"
+      "      </description>"
+      "    </content>"
       "  </jingle>"
       "</iq>";
 }
@@ -2015,9 +2032,7 @@
       buzz::XmlElement* content_desc =
           content->FirstNamed(cricket::QN_JINGLE_RTP_CONTENT);
       ASSERT_TRUE(content_desc != NULL);
-      // TODO: Allow option for intiator to select ssrc.
-      // Right now, for MUC, we set it to a random value.
-      ASSERT_NE("", content_desc->Attr(cricket::QN_SSRC));
+      ASSERT_EQ("", content_desc->Attr(cricket::QN_SSRC));
     }
     delete stanzas_[0];
     stanzas_.clear();
@@ -2063,7 +2078,7 @@
     jingle->SetAttr(cricket::QN_SID, call_->sessions()[0]->id());
   }
 
-  void TestSourceNotifiesAndViewRequests() {
+  void TestStreamsUpdateAndViewRequests() {
     cricket::CallOptions options;
     options.has_video = true;
     options.is_muc = true;
@@ -2071,8 +2086,10 @@
     client_->CreateCall();
     call_->InitiateSession(buzz::Jid("me@mydomain.com"), options);
     ASSERT_EQ(1U, ClearStanzas());
-    ASSERT_EQ(0U, last_sources_update_.audio().size());
-    ASSERT_EQ(0U, last_sources_update_.video().size());
+    ASSERT_EQ(0U, last_streams_added_.audio().size());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
 
     talk_base::scoped_ptr<buzz::XmlElement> accept_stanza(
         buzz::XmlElement::ForStr(kJingleAcceptWithSsrcs));
@@ -2083,53 +2100,68 @@
     ASSERT_EQ(1U, stanzas_.size());
     ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
     ClearStanzas();
+    call_->sessions()[0]->SetState(cricket::Session::STATE_INPROGRESS);
 
-    talk_base::scoped_ptr<buzz::XmlElement> notify_stanza(
-        buzz::XmlElement::ForStr(JingleNotifyAdd("video", "Bob", "ABC")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
+    talk_base::scoped_ptr<buzz::XmlElement> streams_stanza(
+        buzz::XmlElement::ForStr(
+            JingleStreamAdd("video", "Bob", "video1", "ABC")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
     // First one is ignored because of bad syntax.
     ASSERT_EQ(1U, stanzas_.size());
     // TODO: Figure out how to make this an ERROR rather than RESULT.
-    ASSERT_EQ(std::string(buzz::STR_RESULT), stanzas_[0]->Attr(buzz::QN_TYPE));
+    ASSERT_EQ(std::string(buzz::STR_ERROR), stanzas_[0]->Attr(buzz::QN_TYPE));
     ClearStanzas();
-    ASSERT_EQ(0U, last_sources_update_.audio().size());
-    ASSERT_EQ(0U, last_sources_update_.video().size());
+    ASSERT_EQ(0U, last_streams_added_.audio().size());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
 
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyAdd("audio", "Bob", "1234")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(1U, last_sources_update_.audio().size());
-    ASSERT_EQ("Bob", last_sources_update_.audio()[0].nick);
-    ASSERT_TRUE(last_sources_update_.audio()[0].ssrc_set);
-    ASSERT_EQ(1234U, last_sources_update_.audio()[0].ssrc);
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("audio", "Bob", "audio1", "1234")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ("Bob", last_streams_added_.audio()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+    ASSERT_EQ(1234U, last_streams_added_.audio()[0].first_ssrc());
 
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyAdd("audio", "Joe", "2468")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(1U, last_sources_update_.audio().size());
-    ASSERT_EQ("Joe", last_sources_update_.audio()[0].nick);
-    ASSERT_TRUE(last_sources_update_.audio()[0].ssrc_set);
-    ASSERT_EQ(2468U, last_sources_update_.audio()[0].ssrc);
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("audio", "Joe", "audio1", "2468")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.audio().size());
+    ASSERT_EQ("Joe", last_streams_added_.audio()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.audio()[0].ssrcs.size());
+    ASSERT_EQ(2468U, last_streams_added_.audio()[0].first_ssrc());
 
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyAdd("video", "Bob", "5678")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(1U, last_sources_update_.video().size());
-    ASSERT_EQ("Bob", last_sources_update_.video()[0].nick);
-    ASSERT_TRUE(last_sources_update_.video()[0].ssrc_set);
-    ASSERT_EQ(5678U, last_sources_update_.video()[0].ssrc);
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video1", "5678")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.video().size());
+    ASSERT_EQ("Bob", last_streams_added_.video()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+    ASSERT_EQ(5678U, last_streams_added_.video()[0].first_ssrc());
 
     // We're testing that a "duplicate" is effectively ignored.
-    last_sources_update_.mutable_video()->clear();
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyAdd("video", "Bob", "5678")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(0U, last_sources_update_.video().size());
+    last_streams_added_.mutable_video()->clear();
+    last_streams_removed_.mutable_video()->clear();
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video1", "5678")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(0U, last_streams_added_.video().size());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
+
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamAdd("video", "Bob", "video2", "5679")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_added_.video().size());
+    ASSERT_EQ("Bob", last_streams_added_.video()[0].nick);
+    ASSERT_EQ(1U, last_streams_added_.video()[0].ssrcs.size());
+    ASSERT_EQ(5679U, last_streams_added_.video()[0].first_ssrc());
 
     cricket::FakeVoiceMediaChannel* voice_channel = fme_->GetVoiceChannel(0);
     ASSERT_TRUE(voice_channel != NULL);
@@ -2153,41 +2185,45 @@
     ASSERT_EQ(expected_view_elem->Str(), stanzas_[0]->Str());
     ClearStanzas();
 
-    // Implicit removal of audio ssrc.
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyImplicitRemove("audio", "Bob")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(1U, last_sources_update_.audio().size());
-    ASSERT_TRUE(last_sources_update_.audio()[0].removed);
-    ASSERT_TRUE(last_sources_update_.audio()[0].ssrc_set);
-    ASSERT_EQ(1234U, last_sources_update_.audio()[0].ssrc);
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("audio", "Bob", "audio1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_removed_.audio().size());
+    ASSERT_EQ(1U, last_streams_removed_.audio()[0].ssrcs.size());
+    ASSERT_EQ(1234U, last_streams_removed_.audio()[0].first_ssrc());
 
-    // Implicit removal of video ssrc.
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyImplicitRemove("video", "Bob")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(1U, last_sources_update_.video().size());
-    ASSERT_TRUE(last_sources_update_.video()[0].removed);
-    ASSERT_TRUE(last_sources_update_.video()[0].ssrc_set);
-    ASSERT_EQ(5678U, last_sources_update_.video()[0].ssrc);
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("video", "Bob", "video1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_removed_.video().size());
+    ASSERT_EQ(1U, last_streams_removed_.audio()[0].ssrcs.size());
+    ASSERT_EQ(5678U, last_streams_removed_.audio()[0].first_ssrc());
 
-    // Implicit removal of non-existent audio ssrc: should be ignored.
-    last_sources_update_.mutable_audio()->clear();
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyImplicitRemove("audio", "Bob")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(0U, last_sources_update_.audio().size());
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("video", "Bob", "video2")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(1U, last_streams_removed_.video().size());
+    ASSERT_EQ(1U, last_streams_removed_.audio()[0].ssrcs.size());
+    ASSERT_EQ(5679U, last_streams_removed_.audio()[0].first_ssrc());
 
-    // Implicit removal of non-existent video ssrc: should be ignored.
-    last_sources_update_.mutable_video()->clear();
-    notify_stanza.reset(
-        buzz::XmlElement::ForStr(JingleNotifyImplicitRemove("video", "Bob")));
-    SetJingleSid(notify_stanza.get());
-    client_->session_manager()->OnIncomingMessage(notify_stanza.get());
-    ASSERT_EQ(0U, last_sources_update_.video().size());
+    // Duplicate removal: should be ignored.
+    last_streams_removed_.mutable_audio()->clear();
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("audio", "Bob", "audio1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(0U, last_streams_removed_.audio().size());
+
+    // Duplicate removal: should be ignored.
+    last_streams_removed_.mutable_video()->clear();
+    streams_stanza.reset(buzz::XmlElement::ForStr(
+        JingleStreamRemove("video", "Bob", "video1")));
+    SetJingleSid(streams_stanza.get());
+    client_->session_manager()->OnIncomingMessage(streams_stanza.get());
+    ASSERT_EQ(0U, last_streams_removed_.video().size());
 
     voice_channel = fme_->GetVoiceChannel(0);
     ASSERT_TRUE(voice_channel != NULL);
@@ -2237,18 +2273,20 @@
 
   void OnCallCreate(cricket::Call *call) {
     call_ = call;
-    call->SignalMediaSourcesUpdate.connect(
-        this, &MediaSessionClientTest::OnMediaSourcesUpdate);
+    call->SignalMediaStreamsUpdate.connect(
+        this, &MediaSessionClientTest::OnMediaStreamsUpdate);
   }
 
   void OnCallDestroy(cricket::Call *call) {
     call_ = NULL;
   }
 
-  void OnMediaSourcesUpdate(cricket::Call *call,
+  void OnMediaStreamsUpdate(cricket::Call *call,
                             cricket::Session *session,
-                            const cricket::MediaSources& sources) {
-    last_sources_update_.CopyFrom(sources);
+                            const cricket::MediaStreams& added,
+                            const cricket::MediaStreams& removed) {
+    last_streams_added_.CopyFrom(added);
+    last_streams_removed_.CopyFrom(removed);
   }
 
   talk_base::NetworkManager* nm_;
@@ -2265,7 +2303,8 @@
   bool expect_outgoing_crypto_;
   int expected_video_bandwidth_;
   bool expected_video_rtcp_mux_;
-  cricket::MediaSources last_sources_update_;
+  cricket::MediaStreams last_streams_added_;
+  cricket::MediaStreams last_streams_removed_;
 };
 
 MediaSessionClientTest* GingleTest() {
@@ -2482,9 +2521,9 @@
   test->TestIncomingAcceptWithSsrcs(kJingleAcceptWithSsrcs);
 }
 
-TEST(MediaSessionTest, JingleNotifyAndView) {
+TEST(MediaSessionTest, JingleStreamsUpdateAndView) {
   talk_base::scoped_ptr<MediaSessionClientTest> test(JingleTest());
-  test->TestSourceNotifiesAndViewRequests();
+  test->TestStreamsUpdateAndViewRequests();
 }
 
 // Gingle tests
diff --git a/talk/session/phone/srtpfilter.cc b/talk/session/phone/srtpfilter.cc
index af02a7c..7a1cb78 100644
--- a/talk/session/phone/srtpfilter.cc
+++ b/talk/session/phone/srtpfilter.cc
@@ -369,7 +369,6 @@
 #ifdef HAVE_SRTP
 
 bool SrtpSession::inited_ = false;
-std::list<SrtpSession*> SrtpSession::sessions_;
 
 SrtpSession::SrtpSession()
     : session_(NULL),
@@ -377,12 +376,12 @@
       rtcp_auth_tag_len_(0),
       srtp_stat_(new SrtpStat()),
       last_send_seq_num_(-1) {
-  sessions_.push_back(this);
+  sessions()->push_back(this);
   SignalSrtpError.repeat(srtp_stat_->SignalSrtpError);
 }
 
 SrtpSession::~SrtpSession() {
-  sessions_.erase(std::find(sessions_.begin(), sessions_.end(), this));
+  sessions()->erase(std::find(sessions()->begin(), sessions()->end(), this));
   if (session_) {
     srtp_dealloc(session_);
   }
@@ -582,8 +581,8 @@
 }
 
 void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
-  for (std::list<SrtpSession*>::iterator it = sessions_.begin();
-       it != sessions_.end(); ++it) {
+  for (std::list<SrtpSession*>::iterator it = sessions()->begin();
+       it != sessions()->end(); ++it) {
     if ((*it)->session_ == ev->session) {
       (*it)->HandleEvent(ev);
       break;
@@ -591,6 +590,11 @@
   }
 }
 
+std::list<SrtpSession*>* SrtpSession::sessions() {
+  LIBJINGLE_DEFINE_STATIC_LOCAL(std::list<SrtpSession*>, sessions, ());
+  return &sessions;
+}
+
 #else   // !HAVE_SRTP
 
 // On some systems, SRTP is not (yet) available.
diff --git a/talk/session/phone/srtpfilter.h b/talk/session/phone/srtpfilter.h
index da7a236..991d4bf 100644
--- a/talk/session/phone/srtpfilter.h
+++ b/talk/session/phone/srtpfilter.h
@@ -194,13 +194,13 @@
   static bool Init();
   void HandleEvent(const srtp_event_data_t* ev);
   static void HandleEventThunk(srtp_event_data_t* ev);
+  static std::list<SrtpSession*>* sessions();
 
   srtp_t session_;
   int rtp_auth_tag_len_;
   int rtcp_auth_tag_len_;
   talk_base::scoped_ptr<SrtpStat> srtp_stat_;
   static bool inited_;
-  static std::list<SrtpSession*> sessions_;
   int last_send_seq_num_;
   DISALLOW_COPY_AND_ASSIGN(SrtpSession);
 };
diff --git a/talk/session/phone/streamparams.h b/talk/session/phone/streamparams.h
index 3e2bd94..e202f41 100644
--- a/talk/session/phone/streamparams.h
+++ b/talk/session/phone/streamparams.h
@@ -100,6 +100,9 @@
   bool has_ssrc(uint32 ssrc) const {
     return std::find(ssrcs.begin(), ssrcs.end(), ssrc) != ssrcs.end();
   }
+  void add_ssrc(uint32 ssrc) {
+    ssrcs.push_back(ssrc);
+  }
 
   // Resource of the MUC jid of the participant of with this stream.
   // For 1:1 calls, should be left empty (which means remote streams
diff --git a/talk/session/phone/videoframe.cc b/talk/session/phone/videoframe.cc
index 0db3352..dd02a9a 100644
--- a/talk/session/phone/videoframe.cc
+++ b/talk/session/phone/videoframe.cc
@@ -87,8 +87,7 @@
 
 size_t VideoFrame::StretchToBuffer(size_t w, size_t h,
                                    uint8* buffer, size_t size,
-                                   bool interpolate,
-                                   bool vert_crop) const {
+                                   bool interpolate, bool vert_crop) const {
   if (!buffer) return 0;
 
   size_t needed = SizeOf(w, h);
@@ -103,8 +102,7 @@
 }
 
 void VideoFrame::StretchToFrame(VideoFrame *target,
-                                bool interpolate,
-                                bool vert_crop) const {
+                                bool interpolate, bool vert_crop) const {
   if (!target) return;
 
   StretchToPlanes(target->GetYPlane(),
@@ -120,6 +118,16 @@
   target->SetTimeStamp(GetTimeStamp());
 }
 
+VideoFrame* VideoFrame::Stretch(size_t w, size_t h,
+                                bool interpolate, bool vert_crop) const {
+  VideoFrame* dest = CreateEmptyFrame(w, h, GetPixelWidth(), GetPixelHeight(),
+                                      GetElapsedTime(), GetTimeStamp());
+  if (dest) {
+    StretchToFrame(dest, interpolate, vert_crop);
+  }
+  return dest;
+}
+
 bool VideoFrame::SetToBlack() {
 #ifdef HAVE_YUV
   return libyuv::I420Rect(GetYPlane(), GetYPitch(),
diff --git a/talk/session/phone/videoframe.h b/talk/session/phone/videoframe.h
index 1c5ae5c..c34db88 100644
--- a/talk/session/phone/videoframe.h
+++ b/talk/session/phone/videoframe.h
@@ -50,13 +50,24 @@
 
  public:
   VideoFrame() : rendered_(false) {}
-
   virtual ~VideoFrame() {}
 
+  // Creates a frame from a raw sample with FourCC |format| and size |w| x |h|.
+  // |h| can be negative indicating a vertically flipped image.
+  // |dw| is destination width; can be less than |w| if cropping is desired.
+  // |dh| is destination height, like |dw|, but must be a positive number.
+  // Returns whether the function succeeded or failed.
+  virtual bool Reset(uint32 fourcc, int w, int h, int dw, int dh,
+                     uint8 *sample, size_t sample_size,
+                     size_t pixel_width, size_t pixel_height,
+                     int64 elapsed_time, int64 time_stamp, int rotation) = 0;
+
+  // Basic accessors.
   virtual size_t GetWidth() const = 0;
   virtual size_t GetHeight() const = 0;
   size_t GetChromaWidth() const { return (GetWidth() + 1) / 2; }
   size_t GetChromaHeight() const { return (GetHeight() + 1) / 2; }
+  size_t GetChromaSize() const { return GetUPitch() * GetChromaHeight(); }
   virtual const uint8 *GetYPlane() const = 0;
   virtual const uint8 *GetUPlane() const = 0;
   virtual const uint8 *GetVPlane() const = 0;
@@ -72,8 +83,6 @@
   virtual size_t GetPixelWidth() const = 0;
   virtual size_t GetPixelHeight() const = 0;
 
-  // TODO: Add a fourcc format here and probably combine VideoFrame
-  // with CapturedFrame.
   virtual int64 GetElapsedTime() const = 0;
   virtual int64 GetTimeStamp() const = 0;
   virtual void SetElapsedTime(int64 elapsed_time) = 0;
@@ -102,10 +111,10 @@
 
   // Converts the I420 data to RGB of a certain type such as ARGB and ABGR.
   // Returns the frame's actual size, regardless of whether it was written or
-  // not (like snprintf). Parameters size and pitch_rgb are in units of bytes.
+  // not (like snprintf). Parameters size and stride_rgb are in units of bytes.
   // If there is insufficient space, nothing is written.
   virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
-                                    size_t size, size_t pitch_rgb) const = 0;
+                                    size_t size, int stride_rgb) const = 0;
 
   // Writes the frame into the given planes, stretched to the given width and
   // height. The parameter "interpolate" controls whether to interpolate or just
@@ -138,9 +147,9 @@
   // just take the nearest-point. The parameter "crop" controls whether to crop
   // this frame to the aspect ratio of the given dimensions before stretching.
   virtual VideoFrame *Stretch(size_t w, size_t h, bool interpolate,
-                              bool crop) const = 0;
+                              bool crop) const;
 
-  // Set the video frame to black.
+  // Sets the video frame to black.
   bool SetToBlack();
 
   // Size of an I420 image of given dimensions when stored as a frame buffer.
@@ -149,6 +158,12 @@
   }
 
  protected:
+  // Creates an empty frame.
+  virtual VideoFrame* CreateEmptyFrame(int w, int h,
+                                       size_t pixel_width, size_t pixel_height,
+                                       int64 elapsed_time,
+                                       int64 time_stamp) const = 0;
+
   // The frame needs to be rendered to magiccam only once.
   // TODO: Remove this flag once magiccam rendering is fully replaced
   // by client3d rendering.
diff --git a/talk/session/phone/videorenderer.h b/talk/session/phone/videorenderer.h
index f1fe547..c428d57 100644
--- a/talk/session/phone/videorenderer.h
+++ b/talk/session/phone/videorenderer.h
@@ -28,6 +28,10 @@
 #ifndef TALK_SESSION_PHONE_VIDEORENDERER_H_
 #define TALK_SESSION_PHONE_VIDEORENDERER_H_
 
+#ifdef _DEBUG
+#include <string>
+#endif  // _DEBUG
+
 namespace cricket {
 
 class VideoFrame;
@@ -40,6 +44,11 @@
   virtual bool SetSize(int width, int height, int reserved) = 0;
   // Called when a new frame is available for display.
   virtual bool RenderFrame(const VideoFrame *frame) = 0;
+
+#ifdef _DEBUG
+  // Allow renderer dumping out rendered frames.
+  virtual bool SetDumpPath(const std::string &path) { return true; }
+#endif  // _DEBUG
 };
 
 }  // namespace cricket
diff --git a/talk/session/phone/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
index 9667538..c3896a2 100644
--- a/talk/session/phone/webrtcvideoengine.cc
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -49,9 +49,6 @@
 #include "talk/session/phone/webrtcvie.h"
 #include "talk/session/phone/webrtcvoe.h"
 
-// TODO Change video protection calls when WebRTC API has changed.
-#define WEBRTC_VIDEO_AVPF_NACK_ONLY
-
 namespace cricket {
 
 static const int kDefaultLogSeverity = talk_base::LS_WARNING;
@@ -272,10 +269,8 @@
 const WebRtcVideoEngine::VideoCodecPref
     WebRtcVideoEngine::kVideoCodecPrefs[] = {
     {kVp8PayloadName, 100, 0},
-#ifndef WEBRTC_VIDEO_AVPF_NACK_ONLY
     {kRedPayloadName, 101, 1},
     {kFecPayloadName, 102, 2},
-#endif
 };
 
 static const int64 kNsPerFrame = 33333333;  // 30fps
@@ -339,7 +334,7 @@
   video_capturer_ = NULL;
   capture_started_ = false;
 
-  ApplyLogging();
+  ApplyLogging("");
   if (tracing_->SetTraceCallback(this) != 0) {
     LOG_RTCERR1(SetTraceCallback, this);
   }
@@ -366,6 +361,8 @@
     Terminate();
   }
   tracing_->SetTraceCallback(NULL);
+  // Test to see if the media processor was deregistered properly.
+  ASSERT(SignalMediaFrame.is_empty());
 }
 
 bool WebRtcVideoEngine::Init() {
@@ -643,6 +640,18 @@
     return;
   }
 
+  // TODO: This is the trigger point for Tx video processing.
+  // Once the capturer refactoring is done, we will move this into the
+  // capturer...it's not there right now because that image is in not in the
+  // I420 color space.
+  // The clients that subscribe will obtain meta info from the frame.
+  // When this trigger is switched over to capturer, need to pass in the real
+  // ssrc.
+  {
+    talk_base::CritScope cs(&signal_media_critical_);
+    SignalMediaFrame(kDummyVideoSsrc, &i420_frame);
+  }
+
   // Send I420 frame to the local renderer.
   if (local_renderer_) {
     if (local_renderer_w_ != static_cast<int>(i420_frame.GetWidth()) ||
@@ -666,8 +675,11 @@
 }
 
 void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
-  log_level_ = min_sev;
-  ApplyLogging();
+  // if min_sev == -1, we keep the current log level.
+  if (min_sev >= 0) {
+    log_level_ = min_sev;
+  }
+  ApplyLogging(filter);
 }
 
 int WebRtcVideoEngine::GetLastEngineError() {
@@ -785,7 +797,7 @@
   int ncodecs = vie_wrapper_->codec()->NumberOfCodecs();
   for (int i = 0; i < ncodecs; ++i) {
     if (vie_wrapper_->codec()->GetCodec(i, *out_codec) == 0 &&
-      in_codec.name == out_codec->plName) {
+        _stricmp(in_codec.name.c_str(), out_codec->plName) == 0) {
       found = true;
       break;
     }
@@ -846,7 +858,10 @@
   return true;
 }
 
-void WebRtcVideoEngine::ApplyLogging() {
+// See https://sites.google.com/a/google.com/wavelet/
+//     Home/Magic-Flute--RTC-Engine-/Magic-Flute-Command-Line-Parameters
+// for all supported command line setttings.
+void WebRtcVideoEngine::ApplyLogging(const std::string& log_filter) {
   int filter = 0;
   switch (log_level_) {
     case talk_base::LS_VERBOSE: filter |= webrtc::kTraceAll;
@@ -856,6 +871,18 @@
         webrtc::kTraceError | webrtc::kTraceCritical;
   }
   tracing_->SetTraceFilter(filter);
+
+  // Set WebRTC trace file.
+  std::vector<std::string> opts;
+  talk_base::tokenize(log_filter, ' ', '"', '"', &opts);
+  std::vector<std::string>::iterator tracefile =
+      std::find(opts.begin(), opts.end(), "tracefile");
+  if (tracefile != opts.end() && ++tracefile != opts.end()) {
+    // Write WebRTC debug output (at same loglevel) to file
+    if (tracing_->SetTraceFile(tracefile->c_str()) == -1) {
+      LOG_RTCERR1(SetTraceFile, *tracefile);
+    }
+  }
 }
 
 // Rebuilds the codec list to be only those that are less intensive
@@ -958,13 +985,17 @@
   }
 }
 
-// TODO: stubs for now
 bool WebRtcVideoEngine::RegisterProcessor(
     VideoProcessor* video_processor) {
+  talk_base::CritScope cs(&signal_media_critical_);
+  SignalMediaFrame.connect(video_processor,
+                           &VideoProcessor::OnFrame);
   return true;
 }
 bool WebRtcVideoEngine::UnregisterProcessor(
     VideoProcessor* video_processor) {
+  talk_base::CritScope cs(&signal_media_critical_);
+  SignalMediaFrame.disconnect(video_processor);
   return true;
 }
 
@@ -1131,12 +1162,10 @@
     return false;
   }
 
-#ifndef WEBRTC_VIDEO_AVPF_NACK_ONLY
-  // Configure FEC if enabled.
-  if (!SetNackFec(red_type, fec_type)) {
+  // Configure video protection.
+  if (!SetNackFec(vie_channel_, red_type, fec_type)) {
     return false;
   }
-#endif
 
   // Select the first matched codec.
   webrtc::VideoCodec& codec(send_codecs[0]);
@@ -1714,14 +1743,6 @@
                 channel_id, webrtc::kViEKeyFrameRequestPliRtcp);
     return false;
   }
-
-#ifdef WEBRTC_VIDEO_AVPF_NACK_ONLY
-  // Turn on NACK-only loss handling.
-  if (engine_->vie()->rtp()->SetNACKStatus(channel_id, true) != 0) {
-    LOG_RTCERR1(SetNACKStatus, channel_id);
-    return false;
-  }
-#endif
   return true;
 }
 
@@ -1753,10 +1774,14 @@
     return false;
   }
 
-  // Turn on TMMBR-based BWE reporting.
-  // TODO: We should use REMB per default when it is implemented.
-  if (engine_->vie()->rtp()->SetTMMBRStatus(channel_id, true) != 0) {
-    LOG_RTCERR1(SetTMMBRStatus, channel_id);
+  // Turn on REMB-based BWE reporting.
+  // First parameter is channel id, second is if this channel should send REMB
+  // packets, last parameter is if it should report BWE using REMB.
+  // TODO: |send_remb| should be channel id so we can have several
+  // REMB groups.
+  bool send_remb = (remote_ssrc == 0);  // SSRC 0 is our default channel.
+  if (!engine_->vie()->rtp()->SetRembStatus(channel_id, send_remb, true)) {
+    LOG_RTCERR3(SetRembStatus, channel_id, send_remb, true);
     return false;
   }
 
@@ -1805,14 +1830,27 @@
   return true;
 }
 
-bool WebRtcVideoMediaChannel::SetNackFec(int red_payload_type,
+bool WebRtcVideoMediaChannel::SetNackFec(int channel_id,
+                                         int red_payload_type,
                                          int fec_payload_type) {
-  bool enable = (red_payload_type != -1 && fec_payload_type != -1);
-  if (engine_->vie()->rtp()->SetHybridNACKFECStatus(
-          vie_channel_, enable, red_payload_type, fec_payload_type) != 0) {
-    LOG_RTCERR4(SetHybridNACKFECStatus,
-                vie_channel_, enable, red_payload_type, fec_payload_type);
-    return false;
+  // Enable hybrid NACK/FEC if negotiated and not in a conference, use only NACK
+  // otherwise.
+  bool enable = (red_payload_type != -1 && fec_payload_type != -1 &&
+      !(channel_options_ & OPT_CONFERENCE));
+  if (enable) {
+    if (engine_->vie()->rtp()->SetHybridNACKFECStatus(
+        channel_id, enable, red_payload_type, fec_payload_type) != 0) {
+      LOG_RTCERR4(SetHybridNACKFECStatus,
+                  channel_id, enable, red_payload_type, fec_payload_type);
+      return false;
+    }
+    LOG(LS_INFO) << "Hybrid NACK/FEC enabled for channel " << channel_id;
+  } else {
+    if (engine_->vie()->rtp()->SetNACKStatus(channel_id, true) != 0) {
+      LOG_RTCERR1(SetNACKStatus, channel_id);
+      return false;
+    }
+    LOG(LS_INFO) << "NACK enabled for channel " << channel_id;
   }
   return true;
 }
@@ -1842,14 +1880,29 @@
 }
 
 bool WebRtcVideoMediaChannel::SetReceiveCodecs(int channel_id) {
+  int red_type = -1;
+  int fec_type = -1;
   for (std::vector<webrtc::VideoCodec>::iterator it = receive_codecs_.begin();
        it != receive_codecs_.end(); ++it) {
+    if (it->codecType == webrtc::kVideoCodecRED) {
+      red_type = it->plType;
+    } else if (it->codecType == webrtc::kVideoCodecULPFEC) {
+      fec_type = it->plType;
+    }
     if (engine()->vie()->codec()->SetReceiveCodec(channel_id, *it) != 0) {
       LOG_RTCERR2(SetReceiveCodec, channel_id, it->plName);
       return false;
     }
   }
 
+  // Enable video protection. For a sending channel, this will be taken care of
+  // in SetSendCodecs.
+  if (channel_id != vie_channel_) {
+    if (!SetNackFec(channel_id, red_type, fec_type)) {
+      return false;
+    }
+  }
+
   // Start receiving packets if at least one receive codec has been set.
   if (!receive_codecs_.empty()) {
     if (engine()->vie()->base()->StartReceive(channel_id) != 0) {
diff --git a/talk/session/phone/webrtcvideoengine.h b/talk/session/phone/webrtcvideoengine.h
index c25c4da..0e65a34 100644
--- a/talk/session/phone/webrtcvideoengine.h
+++ b/talk/session/phone/webrtcvideoengine.h
@@ -158,7 +158,7 @@
                  WebRtcVoiceEngine* voice_engine);
   bool SetDefaultCodec(const VideoCodec& codec);
   bool RebuildCodecList(const VideoCodec& max_codec);
-  void ApplyLogging();
+  void ApplyLogging(const std::string& log_filter);
   bool InitVideoEngine();
   bool SetCapturer(VideoCapturer* capturer, bool own_capturer);
 
@@ -266,7 +266,7 @@
   // Creates and initializes a WebRtc video channel.
   bool ConfigureChannel(int channel_id);
   bool ConfigureReceiving(int channel_id, uint32 remote_ssrc);
-  bool SetNackFec(int red_payload_type, int fec_payload_type);
+  bool SetNackFec(int channel_id, int red_payload_type, int fec_payload_type);
   bool SetSendCodec(const webrtc::VideoCodec& codec,
                     int min_bitrate,
                     int start_bitrate,
diff --git a/talk/session/phone/webrtcvideoengine_unittest.cc b/talk/session/phone/webrtcvideoengine_unittest.cc
index 88d1505..b4f608a 100644
--- a/talk/session/phone/webrtcvideoengine_unittest.cc
+++ b/talk/session/phone/webrtcvideoengine_unittest.cc
@@ -27,6 +27,7 @@
 
 #include "talk/base/gunit.h"
 #include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/fakemediaprocessor.h"
 #include "talk/session/phone/fakewebrtcvideocapturemodule.h"
 #include "talk/session/phone/fakewebrtcvideoengine.h"
 #include "talk/session/phone/fakewebrtcvoiceengine.h"
@@ -174,12 +175,11 @@
   EXPECT_TRUE(channel_ == NULL);
 }
 
-// Test that we apply plain old VP8 codecs properly.
+// Test that we apply our default codecs properly.
 TEST_F(WebRtcVideoEngineTestFake, SetSendCodecs) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = vie_.GetLastChannel();
   std::vector<cricket::VideoCodec> codecs(engine_.codecs());
-  codecs.resize(1);  // toss out red and ulpfec
   EXPECT_TRUE(channel_->SetSendCodecs(codecs));
   webrtc::VideoCodec gcodec;
   EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
@@ -190,7 +190,8 @@
   EXPECT_EQ(kMinBandwidthKbps, gcodec.minBitrate);
   EXPECT_EQ(kStartBandwidthKbps, gcodec.startBitrate);
   EXPECT_EQ(kMaxBandwidthKbps, gcodec.maxBitrate);
-  // TODO: Check HybridNackFecStatus.
+  EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_FALSE(vie_.GetNackStatus(channel_num));
   // TODO: Check RTCP, PLI, TMMBR.
 }
 
@@ -349,20 +350,103 @@
             vie_.GetKeyFrameRequestMethod(channel_num));
 }
 
-// Test that tmmmbr is enabled on the channel.
-TEST_F(WebRtcVideoEngineTestFake, TmmbrEnabled) {
+// Test that remb is enabled on the default channel.
+TEST_F(WebRtcVideoEngineTestFake, RembEnabled) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = vie_.GetLastChannel();
-  EXPECT_TRUE(vie_.GetTmmbrStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_TRUE(vie_.GetRembStatusSend(channel_num));
 }
 
-// Test that nack is enabled on the channel.
+// Test that remb is enabled on a receive channel but it uses the default
+// channel for sending remb packets.
+TEST_F(WebRtcVideoEngineTestFake, RembEnabledOnReceiveChannels) {
+  EXPECT_TRUE(SetupEngine());
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(vie_.GetRembStatus(channel_num));
+  EXPECT_FALSE(vie_.GetRembStatusSend(channel_num));
+}
+
+// Test that nack is enabled on the channel if we don't offer red/fec.
 TEST_F(WebRtcVideoEngineTestFake, NackEnabled) {
   EXPECT_TRUE(SetupEngine());
   int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> codecs(engine_.codecs());
+  codecs.resize(1);  // toss out red and ulpfec
+  EXPECT_TRUE(channel_->SetSendCodecs(codecs));
   EXPECT_TRUE(vie_.GetNackStatus(channel_num));
 }
 
+// Test that we enable hybrid NACK FEC mode.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFec) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that we enable hybrid NACK FEC mode when calling SetSendCodecs and
+// SetReceiveCodecs in reversed order.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFecReversedOrder) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+  EXPECT_TRUE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_FALSE(vie_.GetNackStatus(channel_num));
+}
+
+// Test NACK vs Hybrid NACK/FEC interop call setup, i.e. only use NACK even if
+// red/fec is offered as receive codec.
+TEST_F(WebRtcVideoEngineTestFake, VideoProtectionInterop) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> recv_codecs(engine_.codecs());
+  std::vector<cricket::VideoCodec> send_codecs(engine_.codecs());
+  // Only add VP8 as send codec.
+  send_codecs.resize(1);
+  EXPECT_TRUE(channel_->SetRecvCodecs(recv_codecs));
+  EXPECT_TRUE(channel_->SetSendCodecs(send_codecs));
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test NACK vs Hybrid NACK/FEC interop call setup, i.e. only use NACK even if
+// red/fec is offered as receive codec. Call order reversed compared to
+// VideoProtectionInterop.
+TEST_F(WebRtcVideoEngineTestFake, VideoProtectionInteropReversed) {
+  EXPECT_TRUE(SetupEngine());
+  int channel_num = vie_.GetLastChannel();
+  std::vector<cricket::VideoCodec> recv_codecs(engine_.codecs());
+  std::vector<cricket::VideoCodec> send_codecs(engine_.codecs());
+  // Only add VP8 as send codec.
+  send_codecs.resize(1);
+  EXPECT_TRUE(channel_->SetSendCodecs(send_codecs));
+  EXPECT_TRUE(channel_->SetRecvCodecs(recv_codecs));
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(channel_num));
+}
+
+// Test that NACK, not hybrid mode, is enabled in conference mode.
+TEST_F(WebRtcVideoEngineTestFake, HybridNackFecConference) {
+  EXPECT_TRUE(SetupEngine());
+  // Setup the send channel.
+  int send_channel_num = vie_.GetLastChannel();
+  EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+  EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs()));
+  EXPECT_TRUE(channel_->SetSendCodecs(engine_.codecs()));
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(send_channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(send_channel_num));
+  // Add a receive stream.
+  EXPECT_TRUE(channel_->AddRecvStream(cricket::StreamParams::CreateLegacy(1)));
+  int receive_channel_num = vie_.GetLastChannel();
+  EXPECT_FALSE(vie_.GetHybridNackFecStatus(receive_channel_num));
+  EXPECT_TRUE(vie_.GetNackStatus(receive_channel_num));
+}
+
 // Test that we can create a channel and start/stop rendering out on it.
 TEST_F(WebRtcVideoEngineTestFake, SetRender) {
   EXPECT_TRUE(SetupEngine());
@@ -402,8 +486,6 @@
   EXPECT_FALSE(vie_.GetSend(channel_num));
 }
 
-// TODO: Add test for FEC.
-
 // Test that we set bandwidth properly when using full auto bandwidth mode.
 TEST_F(WebRtcVideoEngineTestFake, SetBandwidthAuto) {
   EXPECT_TRUE(SetupEngine());
@@ -514,7 +596,7 @@
 TEST_F(WebRtcVideoEngineTest, FindCodec) {
   // We should not need to init engine in order to get codecs.
   const std::vector<cricket::VideoCodec>& c = engine_.codecs();
-  EXPECT_EQ(1U, c.size());
+  EXPECT_EQ(3U, c.size());
 
   cricket::VideoCodec vp8(104, "VP8", 320, 200, 30, 0);
   EXPECT_TRUE(engine_.FindCodec(vp8));
@@ -538,8 +620,6 @@
   cricket::VideoCodec vp8_zero_res(104, "VP8", 0, 0, 30, 0);
   EXPECT_TRUE(engine_.FindCodec(vp8_zero_res));
 
-  // TODO: Re-enable when we re-enable FEC.
-#if 0
   cricket::VideoCodec red(101, "RED", 0, 0, 30, 0);
   EXPECT_TRUE(engine_.FindCodec(red));
 
@@ -551,7 +631,6 @@
 
   cricket::VideoCodec fec_ci(102, "ulpfec", 0, 0, 30, 0);
   EXPECT_TRUE(engine_.FindCodec(fec));
-#endif
 }
 
 TEST_F(WebRtcVideoEngineTest, StartupShutdown) {
@@ -643,6 +722,21 @@
   EXPECT_FALSE(engine_.IsCapturing());
 }
 
+TEST_F(WebRtcVideoEngineTest, TestRegisterVideoProcessor) {
+  cricket::FakeMediaProcessor vp;
+  EXPECT_TRUE(engine_.Init());
+
+  EXPECT_TRUE(engine_.RegisterProcessor(&vp));
+  engine_.TriggerMediaFrame(0, NULL);
+  EXPECT_EQ(1, vp.video_frame_count());
+
+  EXPECT_TRUE(engine_.UnregisterProcessor(&vp));
+  engine_.TriggerMediaFrame(0, NULL);
+  EXPECT_EQ(1, vp.video_frame_count());
+
+  engine_.Terminate();
+}
+
 TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecs) {
   std::vector<cricket::VideoCodec> codecs;
   codecs.push_back(kVP8Codec);
diff --git a/talk/session/phone/webrtcvideoframe.cc b/talk/session/phone/webrtcvideoframe.cc
index 1585e09..5eb849d 100644
--- a/talk/session/phone/webrtcvideoframe.cc
+++ b/talk/session/phone/webrtcvideoframe.cc
@@ -27,6 +27,7 @@
 
 #include "talk/session/phone/webrtcvideoframe.h"
 
+#include "libyuv/convert.h"
 #include "libyuv/planar_functions.h"
 #include "talk/base/logging.h"
 #include "talk/session/phone/videocapturer.h"
@@ -51,52 +52,21 @@
                             size_t pixel_width, size_t pixel_height,
                             int64 elapsed_time, int64 time_stamp,
                             int rotation) {
-  // WebRtcVideoFrame currently doesn't support color conversion or rotation.
-  // TODO: Add horizontal cropping support.
-  if (format != FOURCC_I420 || dw != w || dh < 0 || dh > abs(h) ||
-      rotation != 0) {
-    return false;
-  }
-
-  size_t desired_size = SizeOf(dw, dh);
-  uint8* buffer = new uint8[desired_size];
-  Attach(buffer, desired_size, dw, dh, pixel_width, pixel_height,
-         elapsed_time, time_stamp, rotation);
-  if (dh == h) {
-    // Uncropped
-    memcpy(buffer, sample, desired_size);
-  } else {
-    // Cropped
-    // TODO: use I420Copy which supports horizontal crop and vertical
-    // flip.
-    int horiz_crop = ((w - dw) / 2) & ~1;
-    int vert_crop = ((abs(h) - dh) / 2) & ~1;
-    int y_crop_offset = w * vert_crop + horiz_crop;
-    int halfwidth = (w + 1) / 2;
-    int halfheight = (h + 1) / 2;
-    int uv_size = GetChromaSize();
-    int uv_crop_offset = (halfwidth * vert_crop + horiz_crop) / 2;
-    uint8* src_y = sample + y_crop_offset;
-    uint8* src_u = sample + w * h + uv_crop_offset;
-    uint8* src_v = sample + w * h + halfwidth * halfheight + uv_crop_offset;
-    memcpy(GetYPlane(), src_y, dw * dh);
-    memcpy(GetUPlane(), src_u, uv_size);
-    memcpy(GetVPlane(), src_v, uv_size);
-  }
-  return true;
+  return Reset(format, w, h, dw, dh, sample, sample_size,
+               pixel_width, pixel_height, elapsed_time, time_stamp, rotation);
 }
 
 bool WebRtcVideoFrame::Init(const CapturedFrame* frame, int dw, int dh) {
-  return Init(frame->fourcc, frame->width, frame->height, dw, dh,
-              static_cast<uint8*>(frame->data), frame->data_size,
-              frame->pixel_width, frame->pixel_height,
-              frame->elapsed_time, frame->time_stamp, frame->rotation);
+  return Reset(frame->fourcc, frame->width, frame->height, dw, dh,
+               static_cast<uint8*>(frame->data), frame->data_size,
+               frame->pixel_width, frame->pixel_height,
+               frame->elapsed_time, frame->time_stamp, frame->rotation);
 }
 
 bool WebRtcVideoFrame::InitToBlack(int w, int h,
                                    size_t pixel_width, size_t pixel_height,
                                    int64 elapsed_time, int64 time_stamp) {
-  CreateBuffer(w, h, pixel_width, pixel_height, elapsed_time, time_stamp);
+  InitToEmptyBuffer(w, h, pixel_width, pixel_height, elapsed_time, time_stamp);
   return SetToBlack();
 }
 
@@ -217,70 +187,30 @@
 size_t WebRtcVideoFrame::ConvertToRgbBuffer(uint32 to_fourcc,
                                             uint8* buffer,
                                             size_t size,
-                                            size_t stride_rgb) const {
+                                            int stride_rgb) const {
   if (!video_frame_.Buffer()) {
     return 0;
   }
   size_t width = video_frame_.Width();
   size_t height = video_frame_.Height();
-  size_t needed = stride_rgb * height;
+  size_t needed = (stride_rgb >= 0 ? stride_rgb : -stride_rgb) * height;
   if (size < needed) {
     LOG(LS_WARNING) << "RGB buffer is not large enough";
     return needed;
   }
 
-  // TODO: Use libyuv::ConvertFromI420
-  switch (to_fourcc) {
-    case FOURCC_ARGB:
-      libyuv::I420ToARGB(
-          GetYPlane(), GetYPitch(),
-          GetUPlane(), GetUPitch(),
-          GetVPlane(), GetVPitch(),
-          buffer, stride_rgb, width, height);
-      break;
-
-    case FOURCC_BGRA:
-      libyuv::I420ToBGRA(
-          GetYPlane(), GetYPitch(),
-          GetUPlane(), GetUPitch(),
-          GetVPlane(), GetVPitch(),
-          buffer, stride_rgb, width, height);
-      break;
-
-    case FOURCC_ABGR:
-      libyuv::I420ToABGR(
-          GetYPlane(), GetYPitch(),
-          GetUPlane(), GetUPitch(),
-          GetVPlane(), GetVPitch(),
-          buffer, stride_rgb, width, height);
-      break;
-
-    default:
-      needed = 0;
-      LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
-      break;
+  if (libyuv::ConvertFromI420(GetYPlane(), GetYPitch(),
+                              GetUPlane(), GetUPitch(),
+                              GetVPlane(), GetVPitch(),
+                              buffer, stride_rgb,
+                              width, height,
+                              to_fourcc)) {
+    LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
+    return 0;  // 0 indicates error
   }
   return needed;
 }
 
-VideoFrame* WebRtcVideoFrame::Stretch(size_t w, size_t h,
-    bool interpolate, bool vert_crop) const {
-  WebRtcVideoFrame* frame = new WebRtcVideoFrame();
-  frame->CreateBuffer(w, h, 1, 1, 0, 0);
-  StretchToFrame(frame, interpolate, vert_crop);
-
-  return frame;
-}
-
-void WebRtcVideoFrame::CreateBuffer(int w, int h,
-                                    size_t pixel_width, size_t pixel_height,
-                                    int64 elapsed_time, int64 time_stamp) {
-  size_t buffer_size = VideoFrame::SizeOf(w, h);
-  uint8* buffer = new uint8[buffer_size];
-  Attach(buffer, buffer_size, w, h, pixel_width, pixel_height,
-         elapsed_time, time_stamp, 0);
-}
-
 // Add a square watermark near the left-low corner. clamp Y.
 // Returns false on error.
 bool WebRtcVideoFrame::AddWatermark() {
@@ -304,4 +234,75 @@
   return true;
 }
 
+bool WebRtcVideoFrame::Reset(uint32 format, int w, int h, int dw, int dh,
+                             uint8* sample, size_t sample_size,
+                             size_t pixel_width, size_t pixel_height,
+                             int64 elapsed_time, int64 time_stamp,
+                             int rotation) {
+  // WebRtcVideoFrame currently doesn't support color conversion or rotation.
+  // TODO: Add horizontal cropping support.
+  if (format != FOURCC_I420 || dw != w || dh < 0 || dh > abs(h) ||
+      rotation != 0) {
+    return false;
+  }
+
+  // Discard the existing buffer.
+  uint8* old_buffer;
+  size_t old_buffer_size;
+  Detach(&old_buffer, &old_buffer_size);
+  delete[] old_buffer;
+
+  // Set up a new buffer.
+  size_t desired_size = SizeOf(dw, dh);
+  uint8* buffer = new uint8[desired_size];
+  Attach(buffer, desired_size, dw, dh, pixel_width, pixel_height,
+         elapsed_time, time_stamp, rotation);
+
+  if (dh == h) {
+    // Uncropped
+    memcpy(buffer, sample, desired_size);
+  } else {
+    // Cropped
+    // TODO: use I420Copy which supports horizontal crop and vertical
+    // flip.
+    int horiz_crop = ((w - dw) / 2) & ~1;
+    int vert_crop = ((abs(h) - dh) / 2) & ~1;
+    int y_crop_offset = w * vert_crop + horiz_crop;
+    int halfwidth = (w + 1) / 2;
+    int halfheight = (h + 1) / 2;
+    int uv_size = GetChromaSize();
+    int uv_crop_offset = (halfwidth * vert_crop + horiz_crop) / 2;
+    uint8* src_y = sample + y_crop_offset;
+    uint8* src_u = sample + w * h + uv_crop_offset;
+    uint8* src_v = sample + w * h + halfwidth * halfheight + uv_crop_offset;
+    memcpy(GetYPlane(), src_y, dw * dh);
+    memcpy(GetUPlane(), src_u, uv_size);
+    memcpy(GetVPlane(), src_v, uv_size);
+  }
+
+  return true;
+}
+
+VideoFrame* WebRtcVideoFrame::CreateEmptyFrame(int w, int h,
+                                               size_t pixel_width,
+                                               size_t pixel_height,
+                                               int64 elapsed_time,
+                                               int64 time_stamp) const {
+  WebRtcVideoFrame* frame = new WebRtcVideoFrame();
+  frame->InitToEmptyBuffer(w, h, pixel_width, pixel_height,
+                           elapsed_time, time_stamp);
+  return frame;
+}
+
+void WebRtcVideoFrame::InitToEmptyBuffer(int w, int h,
+                                         size_t pixel_width,
+                                         size_t pixel_height,
+                                         int64 elapsed_time,
+                                         int64 time_stamp) {
+  size_t buffer_size = VideoFrame::SizeOf(w, h);
+  uint8* buffer = new uint8[buffer_size];
+  Attach(buffer, buffer_size, w, h, pixel_width, pixel_height,
+         elapsed_time, time_stamp, 0);
+}
+
 }  // namespace cricket
diff --git a/talk/session/phone/webrtcvideoframe.h b/talk/session/phone/webrtcvideoframe.h
index 6519543..e041003 100644
--- a/talk/session/phone/webrtcvideoframe.h
+++ b/talk/session/phone/webrtcvideoframe.h
@@ -68,6 +68,11 @@
   webrtc::VideoFrame* frame() { return &video_frame_; }
 
   // From base class VideoFrame.
+  virtual bool Reset(uint32 format, int w, int h, int dw, int dh,
+                   uint8* sample, size_t sample_size,
+                   size_t pixel_width, size_t pixel_height,
+                   int64 elapsed_time, int64 time_stamp, int rotation);
+
   virtual size_t GetWidth() const;
   virtual size_t GetHeight() const;
   virtual const uint8* GetYPlane() const;
@@ -97,14 +102,17 @@
   virtual bool MakeExclusive();
   virtual size_t CopyToBuffer(uint8* buffer, size_t size) const;
   virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8* buffer,
-                                    size_t size, size_t pitch_rgb) const;
-  virtual VideoFrame* Stretch(size_t w, size_t h, bool interpolate,
-                              bool vert_crop) const;
+                                    size_t size, int stride_rgb) const;
 
  private:
-  size_t GetChromaSize() const { return GetUPitch() * GetChromaHeight(); }
-  void CreateBuffer(int w, int h, size_t pixel_width, size_t pixel_height,
-                    int64 elapsed_time, int64 time_stamp);
+  virtual VideoFrame* CreateEmptyFrame(int w, int h,
+                                       size_t pixel_width, size_t pixel_height,
+                                       int64 elapsed_time,
+                                       int64 time_stamp) const;
+  void InitToEmptyBuffer(int w, int h,
+                         size_t pixel_width, size_t pixel_height,
+                         int64 elapsed_time, int64 time_stamp);
+
   webrtc::VideoFrame video_frame_;
   size_t pixel_width_;
   size_t pixel_height_;
diff --git a/talk/session/phone/webrtcvideoframe_unittest.cc b/talk/session/phone/webrtcvideoframe_unittest.cc
index b115625..ea47a72 100644
--- a/talk/session/phone/webrtcvideoframe_unittest.cc
+++ b/talk/session/phone/webrtcvideoframe_unittest.cc
@@ -54,25 +54,44 @@
 // TEST_WEBRTCVIDEOFRAME(ConstructCopyIsRef)
 TEST_WEBRTCVIDEOFRAME(ConstructBlack)
 // TODO: Implement Jpeg
-// TEST_LMIVIDEOFRAME(ConstructMjpgI420)
-// TEST_LMIVIDEOFRAME(ConstructMjpgI422)
-// TEST_LMIVIDEOFRAME(ConstructMjpgI444)
-// TEST_LMIVIDEOFRAME(ConstructMjpgI400)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI420)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI444)
+// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI400)
 // TODO: WebRtcVideoFrame does not support odd sizes.
 // Re-evaluate once WebRTC switches to libyuv
-// TEST_LMIVIDEOFRAME(ConstructYuy2AllSizes)
-// TODO: WebRtcVideoFrame currently only supports ARGB output.
-#ifdef HAVE_YUV
-TEST_WEBRTCVIDEOFRAME(ConvertToBGRABuffer)
+// TEST_WEBRTCVIDEOFRAME(ConstructYuy2AllSizes)
+TEST_WEBRTCVIDEOFRAME(Reset)
 TEST_WEBRTCVIDEOFRAME(ConvertToABGRBuffer)
-#endif
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToABGRBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB1555BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGB4444BufferInverted)
 TEST_WEBRTCVIDEOFRAME(ConvertToARGBBuffer)
-//TEST_LMIVIDEOFRAME(ConvertToRGB24Buffer)
-//TEST_LMIVIDEOFRAME(ConvertToRAWBuffer)
-//TEST_LMIVIDEOFRAME(ConvertToRGB565Buffer)
-//TEST_LMIVIDEOFRAME(ConvertToARGB1555Buffer)
-//TEST_LMIVIDEOFRAME(ConvertToARGB4444Buffer)
-//TEST_WEBRTCVIDEOFRAME(ConvertToYUY2Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToARGBBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToBGRABufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRAWBufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB24BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToRGB565BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2Buffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2BufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToYUY2BufferInverted)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBuffer)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBufferStride)
+TEST_WEBRTCVIDEOFRAME(ConvertToUYVYBufferInverted)
 //TEST_WEBRTCVIDEOFRAME(ConvertToI422Buffer)
 TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerGRBG)
 TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerGBRG)
@@ -80,6 +99,8 @@
 TEST_WEBRTCVIDEOFRAME(ConvertARGBToBayerRGGB)
 TEST_WEBRTCVIDEOFRAME(CopyToBuffer)
 TEST_WEBRTCVIDEOFRAME(CopyToBuffer1Pixel)
+//TEST_WEBRTCVIDEOFRAME(ConstructARGBBlackWhitePixel)
+
 TEST_WEBRTCVIDEOFRAME(StretchToFrame)
 TEST_WEBRTCVIDEOFRAME(Copy)
 // TODO: WebRtcVideoFrame is not currently refcounted.
diff --git a/talk/session/phone/webrtcvoiceengine.cc b/talk/session/phone/webrtcvoiceengine.cc
index da9829a..58706f9 100644
--- a/talk/session/phone/webrtcvoiceengine.cc
+++ b/talk/session/phone/webrtcvoiceengine.cc
@@ -382,17 +382,23 @@
   // First check whether there is a valid sound device for playback.
   // TODO: Clean this up when we support setting the soundclip device.
 #ifdef WIN32
-  int num_of_devices = 0;
-  if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
-      num_of_devices > 0) {
-    if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
-        == -1) {
-      LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
-                      voe_wrapper_sc_->error());
-      return false;
+  // The SetPlayoutDevice may not be implemented in the case of external ADM.
+  // TODO: We should only check the adm_sc_ here, but current
+  // PeerConnection interface never set the adm_sc_, so need to check both
+  // in order to determine if the external adm is used.
+  if (!adm_ && !adm_sc_) {
+    int num_of_devices = 0;
+    if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
+        num_of_devices > 0) {
+      if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
+          == -1) {
+        LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
+                       voe_wrapper_sc_->error());
+        return false;
+      }
+    } else {
+      LOG(LS_WARNING) << "No valid sound playout device found.";
     }
-  } else {
-    LOG(LS_WARNING) << "No valid sound playout device found.";
   }
 #endif
 
diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc
index 8f637fd..8db2d45 100644
--- a/talk/xmpp/hangoutpubsubclient.cc
+++ b/talk/xmpp/hangoutpubsubclient.cc
@@ -341,6 +341,10 @@
         QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
     return true;
   }
+
+  virtual bool StatesEqual(bool state1, bool state2) {
+    return false;  // Make every item trigger an event, even if state doesn't change.
+  }
 };
 
 HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
diff --git a/talk/xmpp/hangoutpubsubclient_unittest.cc b/talk/xmpp/hangoutpubsubclient_unittest.cc
index f994992..a0c2439 100644
--- a/talk/xmpp/hangoutpubsubclient_unittest.cc
+++ b/talk/xmpp/hangoutpubsubclient_unittest.cc
@@ -278,7 +278,7 @@
   xmpp_client->HandleStanza(
       buzz::XmlElement::ForStr(incoming_presenter_resets_message));
   EXPECT_EQ("presenting-nick", listener->last_presenter_nick);
-  EXPECT_TRUE(listener->last_was_presenting);
+  //EXPECT_TRUE(listener->last_was_presenting);
   EXPECT_FALSE(listener->last_is_presenting);
 
   std::string incoming_presenter_retracts_message =
@@ -335,6 +335,12 @@
   EXPECT_FALSE(listener->last_was_presenting);
   EXPECT_TRUE(listener->last_is_presenting);
 
+  xmpp_client->HandleStanza(
+      buzz::XmlElement::ForStr(incoming_presenter_changes_message));
+  EXPECT_EQ("presenting-nick2", listener->last_presenter_nick);
+  EXPECT_TRUE(listener->last_was_presenting);
+  EXPECT_TRUE(listener->last_is_presenting);
+
   std::string incoming_media_changes_message =
       "<message xmlns='jabber:client' from='room@domain.com'>"
       "  <event xmlns='http://jabber.org/protocol/pubsub#event'>"