Add app/webrtc.

git-svn-id: http://libjingle.googlecode.com/svn/trunk@80 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/CHANGELOG b/CHANGELOG
index f97be9f..81b3f09 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,8 +1,9 @@
 Libjingle
 
 0.5.9 - Aug 31, 2011
-  - Updated STUN support (RFC 5389)
-  - Video output scaling
+  - Add app/webrtc
+  - Update STUN support some more (RFC 5389)
+  - Add video output scaling
   - Refactoring and bug fixes
 
 0.5.8 - July 1, 2011
diff --git a/talk/app/webrtc/peerconnection.h b/talk/app/webrtc/peerconnection.h
new file mode 100644
index 0000000..15c34b4
--- /dev/null
+++ b/talk/app/webrtc/peerconnection.h
@@ -0,0 +1,140 @@
+/*
+ * 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_APP_WEBRTC_PEERCONNECTION_H_
+#define TALK_APP_WEBRTC_PEERCONNECTION_H_
+
+// TODO - Add a factory class or some kind of PeerConnection manager
+// to support multiple PeerConnection object instantiation. This class will
+// create ChannelManager object and pass it to PeerConnection object. Otherwise
+// each PeerConnection object will have its own ChannelManager hence MediaEngine
+// and VoiceEngine/VideoEngine.
+
+#include <string>
+
+namespace cricket {
+class VideoRenderer;
+}
+
+namespace talk_base {
+class Thread;
+}
+
+namespace webrtc {
+
+class PeerConnectionObserver {
+ public:
+  // serialized signaling message
+  virtual void OnSignalingMessage(const std::string& msg) = 0;
+
+  // Triggered when a local stream has been added and initialized
+  // TODO: This callback was added intense to give the client a
+  // chance to set the capture device. It doesn't map to any JS callback. Once
+  // we figure out a better way to set video capture device, we should remove
+  // this callback.
+  virtual void OnLocalStreamInitialized(const std::string& stream_id,
+      bool video) = 0;
+
+  // Triggered when a remote peer accepts a media connection.
+  virtual void OnAddStream(const std::string& stream_id, bool video) = 0;
+
+  // Triggered when a remote peer closes a media stream.
+  virtual void OnRemoveStream(const std::string& stream_id, bool video) = 0;
+
+ protected:
+  // Dtor protected as objects shouldn't be deleted via this interface.
+  ~PeerConnectionObserver() {}
+};
+
+class PeerConnection {
+ public:
+  enum ReadyState {
+    NEW = 0,
+    NEGOTIATING,
+    ACTIVE,
+    CLOSED,
+  };
+
+  virtual ~PeerConnection() {}
+
+  // Register a listener
+  virtual void RegisterObserver(PeerConnectionObserver* observer) = 0;
+
+  // SignalingMessage in json format
+  virtual bool SignalingMessage(const std::string& msg) = 0;
+
+  // Asynchronously adds a local stream device to the peer
+  // connection. The operation is complete when
+  // PeerConnectionObserver::OnLocalStreamInitialized is called.
+  virtual bool AddStream(const std::string& stream_id, bool video) = 0;
+
+  // Asynchronously removes a local stream device from the peer
+  // connection. The operation is complete when
+  // PeerConnectionObserver::OnRemoveStream is called.
+  virtual bool RemoveStream(const std::string& stream_id) = 0;
+
+  // Info the peerconnection that it is time to return the signaling
+  // information. The operation is complete when
+  // PeerConnectionObserver::OnSignalingMessage is called.
+  virtual bool Connect() = 0;
+
+  // Remove all the streams and tear down the session.
+  // After the Close() is called, the OnSignalingMessage will be invoked
+  // asynchronously. And before OnSignalingMessage is called,
+  // OnRemoveStream will be called for each stream that was active.
+  // TODO: Add an event such as onclose, or onreadystatechanged
+  // when the readystate reaches the closed state (no more streams in the
+  // peerconnection object.
+  virtual bool Close() = 0;
+
+  // Set the audio input & output devices based on the given device name.
+  // An empty device name means to use the default audio device.
+  virtual bool SetAudioDevice(const std::string& wave_in_device,
+                              const std::string& wave_out_device,
+                              int opts) = 0;
+
+  // Set the video renderer for the camera preview.
+  virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer) = 0;
+
+  // Set the video renderer for the specified stream.
+  virtual bool SetVideoRenderer(const std::string& stream_id,
+                                cricket::VideoRenderer* renderer) = 0;
+
+  // Set video capture device
+  // For Chromium the cam_device should use the capture session id.
+  // For standalone app, cam_device is the camera name. It will try to
+  // set the default capture device when cam_device is "".
+  virtual bool SetVideoCapture(const std::string& cam_device) = 0;
+
+  // Returns the state of the PeerConnection object.  See the ReadyState
+  // enum for valid values.
+  virtual ReadyState GetReadyState() = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTION_H_
diff --git a/talk/app/webrtc/peerconnection_impl.cc b/talk/app/webrtc/peerconnection_impl.cc
new file mode 100644
index 0000000..f16f3f2
--- /dev/null
+++ b/talk/app/webrtc/peerconnection_impl.cc
@@ -0,0 +1,235 @@
+/*
+ * 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/app/webrtc/peerconnection_impl.h"
+
+#include "talk/app/webrtc/webrtc_json.h"
+#include "talk/app/webrtc/webrtcsession.h"
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/client/basicportallocator.h"
+
+namespace webrtc {
+
+
+PeerConnectionImpl::PeerConnectionImpl(
+    cricket::PortAllocator* port_allocator,
+    cricket::ChannelManager* channel_manager,
+    talk_base::Thread* signaling_thread)
+  : port_allocator_(port_allocator),
+    channel_manager_(channel_manager),
+    signaling_thread_(signaling_thread),
+    event_callback_(NULL),
+    session_(NULL) {
+}
+
+PeerConnectionImpl::~PeerConnectionImpl() {
+}
+
+bool PeerConnectionImpl::Init() {
+  std::string sid;
+  talk_base::CreateRandomString(8, &sid);
+  const bool incoming = false;
+  // default outgoing direction
+  session_.reset(CreateMediaSession(sid, incoming));
+  if (session_.get() == NULL) {
+    ASSERT(false && "failed to initialize a session");
+    return false;
+  }
+  return true;
+}
+
+void PeerConnectionImpl::RegisterObserver(PeerConnectionObserver* observer) {
+  // This assert is to catch cases where two observer pointers are registered.
+  // We only support one and if another is to be used, the current one must be
+  // cleared first.
+  ASSERT(observer == NULL || event_callback_ == NULL);
+  event_callback_ = observer;
+}
+
+bool PeerConnectionImpl::SignalingMessage(
+    const std::string& signaling_message) {
+  // Deserialize signaling message
+  cricket::SessionDescription* incoming_sdp = NULL;
+  std::vector<cricket::Candidate> candidates;
+  if (!ParseJsonSignalingMessage(signaling_message,
+                                 &incoming_sdp, &candidates)) {
+    return false;
+  }
+
+  bool ret = false;
+  if (GetReadyState() == NEW) {
+    // set direction to incoming, as message received first
+    session_->set_incoming(true);
+    ret = session_->OnInitiateMessage(incoming_sdp, candidates);
+  } else {
+    ret = session_->OnRemoteDescription(incoming_sdp, candidates);
+  }
+  return ret;
+}
+
+WebRtcSession* PeerConnectionImpl::CreateMediaSession(
+    const std::string& id, bool incoming) {
+  ASSERT(port_allocator_ != NULL);
+  WebRtcSession* session = new WebRtcSession(id, incoming,
+      port_allocator_, channel_manager_, signaling_thread_);
+
+  if (session->Initiate()) {
+    session->SignalAddStream.connect(
+        this,
+        &PeerConnectionImpl::OnAddStream);
+    session->SignalRemoveStream.connect(
+        this,
+        &PeerConnectionImpl::OnRemoveStream);
+    session->SignalRtcMediaChannelCreated.connect(
+        this,
+        &PeerConnectionImpl::OnRtcMediaChannelCreated);
+    session->SignalLocalDescription.connect(
+        this,
+        &PeerConnectionImpl::OnLocalDescription);
+    session->SignalFailedCall.connect(
+        this,
+        &PeerConnectionImpl::OnFailedCall);
+  } else {
+    delete session;
+    session = NULL;
+  }
+  return session;
+}
+
+bool PeerConnectionImpl::AddStream(const std::string& stream_id, bool video) {
+  bool ret = false;
+  if (session_->HasStream(stream_id)) {
+    ASSERT(false && "A stream with this name already exists");
+  } else {
+    if (!video) {
+      ret = !session_->HasAudioChannel() &&
+            session_->CreateVoiceChannel(stream_id);
+    } else {
+      ret = !session_->HasVideoChannel() &&
+            session_->CreateVideoChannel(stream_id);
+    }
+  }
+  return ret;
+}
+
+bool PeerConnectionImpl::RemoveStream(const std::string& stream_id) {
+  return session_->RemoveStream(stream_id);
+}
+
+void PeerConnectionImpl::OnLocalDescription(
+    const cricket::SessionDescription* desc,
+    const std::vector<cricket::Candidate>& candidates) {
+  if (!desc) {
+    LOG(WARNING) << "no local SDP ";
+    return;
+  }
+
+  std::string message;
+  if (GetJsonSignalingMessage(desc, candidates, &message)) {
+    if (event_callback_) {
+      event_callback_->OnSignalingMessage(message);
+    }
+  }
+}
+
+void PeerConnectionImpl::OnFailedCall() {
+  // TODO: implement.
+}
+
+bool PeerConnectionImpl::SetAudioDevice(const std::string& wave_in_device,
+                                        const std::string& wave_out_device,
+                                        int opts) {
+  return channel_manager_->SetAudioOptions(wave_in_device,
+                                           wave_out_device,
+                                           opts);
+}
+
+bool PeerConnectionImpl::SetLocalVideoRenderer(
+    cricket::VideoRenderer* renderer) {
+  return channel_manager_->SetLocalRenderer(renderer);
+}
+
+bool PeerConnectionImpl::SetVideoRenderer(const std::string& stream_id,
+                                          cricket::VideoRenderer* renderer) {
+  return session_->SetVideoRenderer(stream_id, renderer);
+}
+
+bool PeerConnectionImpl::SetVideoCapture(const std::string& cam_device) {
+  return channel_manager_->SetVideoOptions(cam_device);
+}
+
+bool PeerConnectionImpl::Connect() {
+  return session_->Connect();
+}
+
+// TODO - Close is not used anymore, should be removed.
+bool PeerConnectionImpl::Close() {
+  session_->RemoveAllStreams();
+  return true;
+}
+
+void PeerConnectionImpl::OnAddStream(const std::string& stream_id,
+                                     bool video) {
+  if (event_callback_) {
+    event_callback_->OnAddStream(stream_id, video);
+  }
+}
+
+void PeerConnectionImpl::OnRemoveStream(const std::string& stream_id,
+                                        bool video) {
+  if (event_callback_) {
+    event_callback_->OnRemoveStream(stream_id, video);
+  }
+}
+
+void PeerConnectionImpl::OnRtcMediaChannelCreated(const std::string& stream_id,
+                                                  bool video) {
+  if (event_callback_) {
+    event_callback_->OnLocalStreamInitialized(stream_id, video);
+  }
+}
+
+PeerConnectionImpl::ReadyState PeerConnectionImpl::GetReadyState() {
+  ReadyState ready_state;
+  cricket::BaseSession::State state = session_->state();
+  if (state == cricket::BaseSession::STATE_INIT) {
+    ready_state = NEW;
+  } else if (state == cricket::BaseSession::STATE_INPROGRESS) {
+    ready_state = ACTIVE;
+  } else if (state == cricket::BaseSession::STATE_DEINIT) {
+    ready_state = CLOSED;
+  } else {
+    ready_state = NEGOTIATING;
+  }
+  return ready_state;
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnection_impl.h b/talk/app/webrtc/peerconnection_impl.h
new file mode 100644
index 0000000..4c004ec
--- /dev/null
+++ b/talk/app/webrtc/peerconnection_impl.h
@@ -0,0 +1,99 @@
+/*
+ * 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_APP_WEBRTC_PEERCONNECTION_IMPL_H_
+#define TALK_APP_WEBRTC_PEERCONNECTION_IMPL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/channelmanager.h"
+
+namespace cricket {
+class ChannelManager;
+class PortAllocator;
+class SessionDescription;
+}
+
+namespace webrtc {
+class WebRtcSession;
+
+class PeerConnectionImpl : public PeerConnection,
+                           public sigslot::has_slots<> {
+ public:
+  PeerConnectionImpl(cricket::PortAllocator* port_allocator,
+                     cricket::ChannelManager* channel_manager,
+                     talk_base::Thread* signaling_thread);
+  virtual ~PeerConnectionImpl();
+
+  // PeerConnection interfaces
+  virtual void RegisterObserver(PeerConnectionObserver* observer);
+  virtual bool SignalingMessage(const std::string& msg);
+  virtual bool AddStream(const std::string& stream_id, bool video);
+  virtual bool RemoveStream(const std::string& stream_id);
+  virtual bool Connect();
+  virtual bool Close();
+  virtual bool SetAudioDevice(const std::string& wave_in_device,
+                              const std::string& wave_out_device, int opts);
+  virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer);
+  virtual bool SetVideoRenderer(const std::string& stream_id,
+                                cricket::VideoRenderer* renderer);
+  virtual bool SetVideoCapture(const std::string& cam_device);
+  virtual ReadyState GetReadyState();
+
+  cricket::ChannelManager* channel_manager() {
+    return channel_manager_;
+  }
+
+  // Callbacks from PeerConnectionImplCallbacks
+  void OnAddStream(const std::string& stream_id, bool video);
+  void OnRemoveStream(const std::string& stream_id, bool video);
+  void OnLocalDescription(
+      const cricket::SessionDescription* desc,
+      const std::vector<cricket::Candidate>& candidates);
+  void OnFailedCall();
+  void OnRtcMediaChannelCreated(const std::string& stream_id,
+                                bool video);
+  bool Init();
+
+ private:
+  WebRtcSession* CreateMediaSession(const std::string& id, bool incoming);
+
+  cricket::PortAllocator* port_allocator_;
+  cricket::ChannelManager* channel_manager_;
+  talk_base::Thread* signaling_thread_;
+  PeerConnectionObserver* event_callback_;
+  talk_base::scoped_ptr<WebRtcSession> session_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTION_IMPL_H_
diff --git a/talk/app/webrtc/peerconnection_proxy.cc b/talk/app/webrtc/peerconnection_proxy.cc
new file mode 100644
index 0000000..6591151
--- /dev/null
+++ b/talk/app/webrtc/peerconnection_proxy.cc
@@ -0,0 +1,313 @@
+/*
+ * 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/app/webrtc/peerconnection_proxy.h"
+
+#include "talk/app/webrtc/peerconnection_impl.h"
+#include "talk/base/logging.h"
+
+namespace webrtc {
+
+enum {
+  MSG_WEBRTC_ADDSTREAM = 1,
+  MSG_WEBRTC_CLOSE,
+  MSG_WEBRTC_CONNECT,
+  MSG_WEBRTC_INIT,
+  MSG_WEBRTC_REGISTEROBSERVER,
+  MSG_WEBRTC_RELEASE,
+  MSG_WEBRTC_REMOVESTREAM,
+  MSG_WEBRTC_SETAUDIODEVICE,
+  MSG_WEBRTC_SETLOCALRENDERER,
+  MSG_WEBRTC_SETVIDEOCAPTURE,
+  MSG_WEBRTC_SETVIDEORENDERER,
+  MSG_WEBRTC_SIGNALINGMESSAGE,
+  MSG_WEBRTC_GETREADYSTATE,
+};
+
+struct AddStreamParams : public talk_base::MessageData {
+  AddStreamParams(const std::string& stream_id, bool video)
+      : stream_id(stream_id),
+        video(video),
+        result(false) {}
+
+  std::string stream_id;
+  bool video;
+  bool result;
+};
+
+struct RemoveStreamParams : public talk_base::MessageData {
+  explicit RemoveStreamParams(const std::string& stream_id)
+      : stream_id(stream_id),
+        result(false) {}
+
+  std::string stream_id;
+  bool result;
+};
+
+struct SignalingMsgParams : public talk_base::MessageData {
+  explicit SignalingMsgParams(const std::string& signaling_message)
+      : signaling_message(signaling_message),
+        result(false) {}
+
+  std::string signaling_message;
+  bool result;
+};
+
+struct SetAudioDeviceParams : public talk_base::MessageData {
+  SetAudioDeviceParams(const std::string& wave_in_device,
+                       const std::string& wave_out_device,
+                       int opts)
+      : wave_in_device(wave_in_device), wave_out_device(wave_out_device),
+        opts(opts), result(false) {}
+
+  std::string wave_in_device;
+  std::string wave_out_device;
+  int opts;
+  bool result;
+};
+
+struct SetLocalRendererParams : public talk_base::MessageData {
+  explicit SetLocalRendererParams(cricket::VideoRenderer* renderer)
+      : renderer(renderer), result(false) {}
+
+  cricket::VideoRenderer* renderer;
+  bool result;
+};
+
+struct SetVideoRendererParams : public talk_base::MessageData {
+  SetVideoRendererParams(const std::string& stream_id,
+                         cricket::VideoRenderer* renderer)
+      : stream_id(stream_id), renderer(renderer), result(false) {}
+
+  std::string stream_id;
+  cricket::VideoRenderer* renderer;
+  bool result;
+};
+
+struct SetVideoCaptureParams : public talk_base::MessageData {
+  explicit SetVideoCaptureParams(const std::string& cam_device)
+      : cam_device(cam_device), result(false) {}
+
+  std::string cam_device;
+  bool result;
+};
+
+struct RegisterObserverParams : public talk_base::MessageData {
+  explicit RegisterObserverParams(PeerConnectionObserver* observer)
+      : observer(observer), result(false) {}
+
+  PeerConnectionObserver* observer;
+  bool result;
+};
+
+struct ResultParams : public talk_base::MessageData {
+  ResultParams()
+      : result(false) {}
+
+  bool result;
+};
+
+PeerConnectionProxy::PeerConnectionProxy(
+    cricket::PortAllocator* port_allocator,
+    cricket::ChannelManager* channel_manager,
+      talk_base::Thread* signaling_thread)
+  : peerconnection_impl_(new PeerConnectionImpl(port_allocator,
+                             channel_manager, signaling_thread)),
+    signaling_thread_(signaling_thread) {
+}
+
+PeerConnectionProxy::~PeerConnectionProxy() {
+  ResultParams params;
+  Send(MSG_WEBRTC_RELEASE, &params);
+}
+
+bool PeerConnectionProxy::Init() {
+  ResultParams params;
+  return (Send(MSG_WEBRTC_INIT, &params) && params.result);
+}
+
+void PeerConnectionProxy::RegisterObserver(PeerConnectionObserver* observer) {
+  RegisterObserverParams params(observer);
+  Send(MSG_WEBRTC_REGISTEROBSERVER, &params);
+}
+
+bool PeerConnectionProxy::SignalingMessage(
+    const std::string& signaling_message) {
+  SignalingMsgParams params(signaling_message);
+  return (Send(MSG_WEBRTC_SIGNALINGMESSAGE, &params) && params.result);
+}
+
+bool PeerConnectionProxy::AddStream(const std::string& stream_id, bool video) {
+  AddStreamParams params(stream_id, video);
+  return (Send(MSG_WEBRTC_ADDSTREAM, &params) && params.result);
+}
+
+bool PeerConnectionProxy::RemoveStream(const std::string& stream_id) {
+  RemoveStreamParams params(stream_id);
+  return (Send(MSG_WEBRTC_REMOVESTREAM, &params) && params.result);
+}
+
+bool PeerConnectionProxy::SetAudioDevice(const std::string& wave_in_device,
+                                         const std::string& wave_out_device,
+                                         int opts) {
+  SetAudioDeviceParams params(wave_in_device, wave_out_device, opts);
+  return (Send(MSG_WEBRTC_SETAUDIODEVICE, &params) && params.result);
+}
+
+bool PeerConnectionProxy::SetLocalVideoRenderer(
+    cricket::VideoRenderer* renderer) {
+  SetLocalRendererParams params(renderer);
+  return (Send(MSG_WEBRTC_SETLOCALRENDERER, &params) && params.result);
+}
+
+bool PeerConnectionProxy::SetVideoRenderer(const std::string& stream_id,
+                                          cricket::VideoRenderer* renderer) {
+  SetVideoRendererParams params(stream_id, renderer);
+  return (Send(MSG_WEBRTC_SETVIDEORENDERER, &params) && params.result);
+}
+
+bool PeerConnectionProxy::SetVideoCapture(const std::string& cam_device) {
+  SetVideoCaptureParams params(cam_device);
+  return (Send(MSG_WEBRTC_SETVIDEOCAPTURE, &params) && params.result);
+}
+
+PeerConnection::ReadyState PeerConnectionProxy::GetReadyState() {
+  PeerConnection::ReadyState ready_state = NEW;
+  Send(MSG_WEBRTC_GETREADYSTATE,
+       reinterpret_cast<talk_base::MessageData*>(&ready_state));
+  return ready_state;
+}
+
+bool PeerConnectionProxy::Connect() {
+  ResultParams params;
+  return (Send(MSG_WEBRTC_CONNECT, &params) && params.result);
+}
+
+bool PeerConnectionProxy::Close() {
+  ResultParams params;
+  return (Send(MSG_WEBRTC_CLOSE, &params) && params.result);
+}
+
+bool PeerConnectionProxy::Send(uint32 id, talk_base::MessageData* data) {
+  if (!signaling_thread_)
+    return false;
+  signaling_thread_->Send(this, id, data);
+  return true;
+}
+
+void PeerConnectionProxy::OnMessage(talk_base::Message* message) {
+  talk_base::MessageData* data = message->pdata;
+  switch (message->message_id) {
+    case MSG_WEBRTC_ADDSTREAM: {
+      AddStreamParams* params = reinterpret_cast<AddStreamParams*>(data);
+      params->result = peerconnection_impl_->AddStream(
+          params->stream_id, params->video);
+      break;
+    }
+    case MSG_WEBRTC_SIGNALINGMESSAGE: {
+      SignalingMsgParams* params =
+          reinterpret_cast<SignalingMsgParams*>(data);
+      params->result = peerconnection_impl_->SignalingMessage(
+          params->signaling_message);
+      break;
+    }
+    case MSG_WEBRTC_REMOVESTREAM: {
+      RemoveStreamParams* params = reinterpret_cast<RemoveStreamParams*>(data);
+      params->result = peerconnection_impl_->RemoveStream(
+          params->stream_id);
+      break;
+    }
+    case MSG_WEBRTC_SETAUDIODEVICE: {
+      SetAudioDeviceParams* params =
+          reinterpret_cast<SetAudioDeviceParams*>(data);
+      params->result = peerconnection_impl_->SetAudioDevice(
+          params->wave_in_device, params->wave_out_device, params->opts);
+      break;
+    }
+    case MSG_WEBRTC_SETLOCALRENDERER: {
+      SetLocalRendererParams* params =
+          reinterpret_cast<SetLocalRendererParams*>(data);
+      params->result = peerconnection_impl_->SetLocalVideoRenderer(
+          params->renderer);
+      break;
+    }
+    case MSG_WEBRTC_SETVIDEOCAPTURE: {
+      SetVideoCaptureParams* params =
+          reinterpret_cast<SetVideoCaptureParams*>(data);
+      params->result = peerconnection_impl_->SetVideoCapture(
+          params->cam_device);
+      break;
+    }
+    case MSG_WEBRTC_GETREADYSTATE: {
+      PeerConnection::ReadyState* ready_state =
+          reinterpret_cast<PeerConnection::ReadyState*>(data);
+      *ready_state = peerconnection_impl_->GetReadyState();
+      break;
+    }
+    case MSG_WEBRTC_SETVIDEORENDERER: {
+      SetVideoRendererParams* params =
+          reinterpret_cast<SetVideoRendererParams*>(data);
+      params->result = peerconnection_impl_->SetVideoRenderer(
+          params->stream_id, params->renderer);
+      break;
+    }
+    case MSG_WEBRTC_CONNECT: {
+      ResultParams* params =
+          reinterpret_cast<ResultParams*>(data);
+      params->result = peerconnection_impl_->Connect();
+      break;
+    }
+    case MSG_WEBRTC_CLOSE: {
+      ResultParams* params =
+          reinterpret_cast<ResultParams*>(data);
+      params->result = peerconnection_impl_->Close();
+      break;
+    }
+    case MSG_WEBRTC_INIT: {
+      ResultParams* params =
+          reinterpret_cast<ResultParams*>(data);
+      params->result = peerconnection_impl_->Init();
+      break;
+    }
+    case MSG_WEBRTC_REGISTEROBSERVER: {
+      RegisterObserverParams* params =
+          reinterpret_cast<RegisterObserverParams*>(data);
+      peerconnection_impl_->RegisterObserver(params->observer);
+      break;
+    }
+    case MSG_WEBRTC_RELEASE: {
+      peerconnection_impl_.reset();
+      break;
+    }
+    default: {
+      ASSERT(false);
+      break;
+    }
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/peerconnection_proxy.h b/talk/app/webrtc/peerconnection_proxy.h
new file mode 100644
index 0000000..6e7fa6c
--- /dev/null
+++ b/talk/app/webrtc/peerconnection_proxy.h
@@ -0,0 +1,81 @@
+/*
+ * 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_APP_WEBRTC_PEERCONNECTION_PROXY_H_
+#define TALK_APP_WEBRTC_PEERCONNECTION_PROXY_H_
+
+#include <string>
+
+#include "talk/app/webrtc/peerconnection.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+
+namespace cricket {
+class ChannelManager;
+class PortAllocator;
+}
+
+namespace webrtc {
+
+class PeerConnectionImpl;
+
+class PeerConnectionProxy : public PeerConnection,
+                            public talk_base::MessageHandler {
+ public:
+  PeerConnectionProxy(cricket::PortAllocator* port_allocator,
+                      cricket::ChannelManager* channel_manager,
+                      talk_base::Thread* signaling_thread);
+  virtual ~PeerConnectionProxy();
+
+  // PeerConnection interface implementation.
+  virtual void RegisterObserver(PeerConnectionObserver* observer);
+  virtual bool SignalingMessage(const std::string& msg);
+  virtual bool AddStream(const std::string& stream_id, bool video);
+  virtual bool RemoveStream(const std::string& stream_id);
+  virtual bool Connect();
+  virtual bool Close();
+  virtual bool SetAudioDevice(const std::string& wave_in_device,
+                              const std::string& wave_out_device, int opts);
+  virtual bool SetLocalVideoRenderer(cricket::VideoRenderer* renderer);
+  virtual bool SetVideoRenderer(const std::string& stream_id,
+                                cricket::VideoRenderer* renderer);
+  virtual bool SetVideoCapture(const std::string& cam_device);
+  virtual ReadyState GetReadyState();
+
+ private:
+  bool Init();
+  bool Send(uint32 id, talk_base::MessageData* data);
+  virtual void OnMessage(talk_base::Message* message);
+
+  talk_base::scoped_ptr<PeerConnectionImpl> peerconnection_impl_;
+  talk_base::Thread* signaling_thread_;
+
+  friend class PeerConnectionFactory;
+};
+}
+
+#endif  // TALK_APP_WEBRTC_PEERCONNECTION_PROXY_H_
diff --git a/talk/app/webrtc/webrtc.scons b/talk/app/webrtc/webrtc.scons
new file mode 100644
index 0000000..66f73af
--- /dev/null
+++ b/talk/app/webrtc/webrtc.scons
@@ -0,0 +1,57 @@
+# -*- Python -*-
+import talk
+
+Import('env')
+
+# local sources
+talk.Library(
+  env,
+  name = 'webrtc',
+  srcs = [
+    # TODO: remove underscores from filenames
+    'peerconnection_impl.cc',
+    'peerconnection_proxy.cc',
+    'peerconnectionfactory.cc',
+    'webrtc_json.cc',
+    'webrtcsession.cc',
+  ],
+)
+
+talk.Unittest(
+  env,
+  name = 'webrtc',
+  srcs = [
+    'webrtcsession_unittest.cc',
+  ],
+  libs = [
+    'srtp',
+    'base',
+    'jpeg',
+    'json',
+    'webrtc',
+    'p2p',
+    'phone',
+    'xmpp',
+    'xmllite',
+    'yuvscaler'
+  ],
+  include_talk_media_libs = True,
+  mac_libs = [
+    'crypto',
+    'ssl',
+  ],
+  mac_FRAMEWORKS = [
+    'Foundation',
+    'IOKit',
+    'QTKit',
+  ],
+  lin_libs = [
+    'rt',
+    'dl',
+    'sound',
+    'X11',
+    'Xext',
+    'Xfixes',
+    'Xrandr'
+  ],
+)
diff --git a/talk/app/webrtc/webrtc_json.cc b/talk/app/webrtc/webrtc_json.cc
new file mode 100644
index 0000000..d0e0256
--- /dev/null
+++ b/talk/app/webrtc/webrtc_json.cc
@@ -0,0 +1,454 @@
+/*
+ * 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/app/webrtc/webrtc_json.h"
+
+#include <stdio.h>
+#include <string>
+
+#include "talk/base/json.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/codec.h"
+
+namespace webrtc {
+static const int kIceComponent = 1;
+static const int kIceFoundation = 1;
+
+bool GetStunServer(const Json::Value& value, StunServiceDetails* stunServer) {
+  if (value.type() != Json::objectValue && value.type() != Json::nullValue) {
+    LOG(LS_WARNING) << "Failed to parse stun values";
+    return false;
+  }
+
+  Json::Value stun;
+  if (GetValueFromJsonObject(value, "stun_service", &stun)) {
+    if (stun.type() == Json::objectValue) {
+      if (!GetStringFromJsonObject(stun, "host", &stunServer->host) ||
+          !GetStringFromJsonObject(stun, "service", &stunServer->service) ||
+          !GetStringFromJsonObject(stun, "protocol", &stunServer->protocol)) {
+        LOG(LS_WARNING) << "Failed to parse JSON value: "
+                        << value.toStyledString();
+        return false;
+      }
+    } else {
+      LOG(LS_WARNING) << "Failed to find the stun_service member.";
+      return false;
+    }
+  } else {
+    LOG(LS_WARNING) << "Wrong ValueType. Expect Json::objectValue).";
+    return false;
+  }
+  return true;
+}
+
+bool GetJsonSignalingMessage(
+    const cricket::SessionDescription* sdp,
+    const std::vector<cricket::Candidate>& candidates,
+    std::string* signaling_message) {
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(sdp);
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(sdp);
+
+  std::vector<Json::Value> media;
+  if (audio_content) {
+    Json::Value value;
+    BuildMediaMessage(*audio_content, candidates, false, &value);
+    media.push_back(value);
+  }
+
+  if (video_content) {
+    Json::Value value;
+    BuildMediaMessage(*video_content, candidates, true, &value);
+    media.push_back(value);
+  }
+
+  Json::Value signal;
+  Append(&signal, "media", media);
+
+  // now serialize
+  *signaling_message = Serialize(signal);
+  return true;
+}
+
+bool BuildMediaMessage(
+    const cricket::ContentInfo& content_info,
+    const std::vector<cricket::Candidate>& candidates,
+    bool video,
+    Json::Value* params) {
+  if (video) {
+    Append(params, "label", 2);  // always video 2
+  } else {
+    Append(params, "label", 1);  // always audio 1
+  }
+
+  const cricket::MediaContentDescription* media_info =
+  static_cast<const cricket::MediaContentDescription*> (
+      content_info.description);
+  if (media_info->rtcp_mux()) {
+    Append(params, "rtcp_mux", true);
+  }
+
+  std::vector<Json::Value> rtpmap;
+  if (!BuildRtpMapParams(content_info, video, &rtpmap)) {
+    return false;
+  }
+
+  Append(params, "rtpmap", rtpmap);
+
+  Json::Value attributes;
+  std::vector<Json::Value> jcandidates;
+
+  if (!BuildAttributes(candidates, video, &jcandidates)) {
+    return false;
+  }
+  Append(&attributes, "candidate", jcandidates);
+  Append(params, "attributes", attributes);
+  return true;
+}
+
+bool BuildRtpMapParams(const cricket::ContentInfo& content_info,
+                       bool video,
+                       std::vector<Json::Value>* rtpmap) {
+  if (!video) {
+    const cricket::AudioContentDescription* audio_offer =
+        static_cast<const cricket::AudioContentDescription*>(
+            content_info.description);
+
+    std::vector<cricket::AudioCodec>::const_iterator iter =
+        audio_offer->codecs().begin();
+    std::vector<cricket::AudioCodec>::const_iterator iter_end =
+        audio_offer->codecs().end();
+    for (; iter != iter_end; ++iter) {
+      Json::Value codec;
+      std::string codec_str(std::string("audio/").append(iter->name));
+      // adding clockrate
+      Append(&codec, "clockrate", iter->clockrate);
+      Append(&codec, "codec", codec_str);
+      Json::Value codec_id;
+      Append(&codec_id, talk_base::ToString(iter->id), codec);
+      rtpmap->push_back(codec_id);
+    }
+  } else {
+    const cricket::VideoContentDescription* video_offer =
+        static_cast<const cricket::VideoContentDescription*>(
+            content_info.description);
+
+    std::vector<cricket::VideoCodec>::const_iterator iter =
+        video_offer->codecs().begin();
+    std::vector<cricket::VideoCodec>::const_iterator iter_end =
+        video_offer->codecs().end();
+    for (; iter != iter_end; ++iter) {
+      Json::Value codec;
+      std::string codec_str(std::string("video/").append(iter->name));
+      Append(&codec, "codec", codec_str);
+      Json::Value codec_id;
+      Append(&codec_id, talk_base::ToString(iter->id), codec);
+      rtpmap->push_back(codec_id);
+    }
+  }
+  return true;
+}
+
+bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
+                     bool video,
+                     std::vector<Json::Value>* jcandidates) {
+  std::vector<cricket::Candidate>::const_iterator iter =
+      candidates.begin();
+  std::vector<cricket::Candidate>::const_iterator iter_end =
+      candidates.end();
+  for (; iter != iter_end; ++iter) {
+    if ((video && (!iter->name().compare("video_rtcp") ||
+                  (!iter->name().compare("video_rtp")))) ||
+        (!video && (!iter->name().compare("rtp") ||
+                   (!iter->name().compare("rtcp"))))) {
+      Json::Value candidate;
+      Append(&candidate, "component", kIceComponent);
+      Append(&candidate, "foundation", kIceFoundation);
+      Append(&candidate, "generation", iter->generation());
+      Append(&candidate, "proto", iter->protocol());
+      Append(&candidate, "priority", iter->preference());
+      Append(&candidate, "ip", iter->address().IPAsString());
+      Append(&candidate, "port", iter->address().PortAsString());
+      Append(&candidate, "type", iter->type());
+      Append(&candidate, "name", iter->name());
+      Append(&candidate, "network_name", iter->network_name());
+      Append(&candidate, "username", iter->username());
+      Append(&candidate, "password", iter->password());
+      jcandidates->push_back(candidate);
+    }
+  }
+  return true;
+}
+
+std::string Serialize(const Json::Value& value) {
+  Json::StyledWriter writer;
+  return writer.write(value);
+}
+
+bool Deserialize(const std::string& message, Json::Value* value) {
+  Json::Reader reader;
+  return reader.parse(message, *value);
+}
+
+bool ParseJsonSignalingMessage(const std::string& signaling_message,
+                               cricket::SessionDescription** sdp,
+                               std::vector<cricket::Candidate>* candidates) {
+  ASSERT(!(*sdp));  // expect this to be NULL
+  // first deserialize message
+  Json::Value value;
+  if (!Deserialize(signaling_message, &value)) {
+    return false;
+  }
+
+  // get media objects
+  std::vector<Json::Value> mlines = ReadValues(value, "media");
+  if (mlines.empty()) {
+    // no m-lines found
+    return false;
+  }
+
+  *sdp = new cricket::SessionDescription();
+
+  // get codec information
+  for (size_t i = 0; i < mlines.size(); ++i) {
+    if (mlines[i]["label"].asInt() == 1) {
+      cricket::AudioContentDescription* audio_content =
+          new cricket::AudioContentDescription();
+      ParseAudioCodec(mlines[i], audio_content);
+      audio_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+      audio_content->SortCodecs();
+      (*sdp)->AddContent(cricket::CN_AUDIO,
+                         cricket::NS_JINGLE_RTP, audio_content);
+      ParseIceCandidates(mlines[i], candidates);
+    } else {
+      cricket::VideoContentDescription* video_content =
+          new cricket::VideoContentDescription();
+      ParseVideoCodec(mlines[i], video_content);
+
+      video_content->set_rtcp_mux(ParseRtcpMux(mlines[i]));
+      video_content->SortCodecs();
+      (*sdp)->AddContent(cricket::CN_VIDEO,
+                         cricket::NS_JINGLE_RTP, video_content);
+      ParseIceCandidates(mlines[i], candidates);
+    }
+  }
+  return true;
+}
+
+bool ParseRtcpMux(const Json::Value& value) {
+  Json::Value rtcp_mux(ReadValue(value, "rtcp_mux"));
+  if (!rtcp_mux.empty()) {
+    if (rtcp_mux.asBool()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool ParseAudioCodec(const Json::Value& value,
+                     cricket::AudioContentDescription* content) {
+  std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+  if (rtpmap.empty())
+    return false;
+
+  std::vector<Json::Value>::const_iterator iter =
+      rtpmap.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      rtpmap.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::AudioCodec codec;
+    std::string pltype(iter->begin().memberName());
+    talk_base::FromString(pltype, &codec.id);
+    Json::Value codec_info((*iter)[pltype]);
+    std::string codec_name(ReadString(codec_info, "codec"));
+    std::vector<std::string> tokens;
+    talk_base::split(codec_name, '/', &tokens);
+    codec.name = tokens[1];
+    codec.clockrate = ReadUInt(codec_info, "clockrate");
+    content->AddCodec(codec);
+  }
+
+  return true;
+}
+
+bool ParseVideoCodec(const Json::Value& value,
+                     cricket::VideoContentDescription* content) {
+  std::vector<Json::Value> rtpmap(ReadValues(value, "rtpmap"));
+  if (rtpmap.empty())
+    return false;
+
+  std::vector<Json::Value>::const_iterator iter =
+      rtpmap.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      rtpmap.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::VideoCodec codec;
+    std::string pltype(iter->begin().memberName());
+    talk_base::FromString(pltype, &codec.id);
+    Json::Value codec_info((*iter)[pltype]);
+    std::vector<std::string> tokens;
+    talk_base::split(codec_info["codec"].asString(), '/', &tokens);
+    codec.name = tokens[1];
+    content->AddCodec(codec);
+  }
+  return true;
+}
+
+bool ParseIceCandidates(const Json::Value& value,
+                        std::vector<cricket::Candidate>* candidates) {
+  Json::Value attributes(ReadValue(value, "attributes"));
+  std::string ice_pwd(ReadString(attributes, "ice-pwd"));
+  std::string ice_ufrag(ReadString(attributes, "ice-ufrag"));
+
+  std::vector<Json::Value> jcandidates(ReadValues(attributes, "candidate"));
+
+  std::vector<Json::Value>::const_iterator iter =
+      jcandidates.begin();
+  std::vector<Json::Value>::const_iterator iter_end =
+      jcandidates.end();
+  for (; iter != iter_end; ++iter) {
+    cricket::Candidate cand;
+
+    unsigned int generation;
+    if (!GetUIntFromJsonObject(*iter, "generation", &generation))
+      return false;
+    cand.set_generation_str(talk_base::ToString(generation));
+
+    std::string proto;
+    if (!GetStringFromJsonObject(*iter, "proto", &proto))
+      return false;
+    cand.set_protocol(proto);
+
+    double priority;
+    if (!GetDoubleFromJsonObject(*iter, "priority", &priority))
+      return false;
+    cand.set_preference_str(talk_base::ToString(priority));
+
+    std::string str;
+    talk_base::SocketAddress addr;
+    if (!GetStringFromJsonObject(*iter, "ip", &str))
+      return false;
+    addr.SetIP(str);
+    if (!GetStringFromJsonObject(*iter, "port", &str))
+      return false;
+    int port;
+    if (!talk_base::FromString(str, &port))
+      return false;
+    addr.SetPort(port);
+    cand.set_address(addr);
+
+    if (!GetStringFromJsonObject(*iter, "type", &str))
+      return false;
+    cand.set_type(str);
+
+    if (!GetStringFromJsonObject(*iter, "name", &str))
+      return false;
+    cand.set_name(str);
+
+    if (!GetStringFromJsonObject(*iter, "network_name", &str))
+      return false;
+    cand.set_network_name(str);
+
+    if (!GetStringFromJsonObject(*iter, "username", &str))
+      return false;
+    cand.set_username(str);
+
+    if (!GetStringFromJsonObject(*iter, "password", &str))
+      return false;
+    cand.set_password(str);
+
+    candidates->push_back(cand);
+  }
+  return true;
+}
+
+std::vector<Json::Value> ReadValues(
+    const Json::Value& value, const std::string& key) {
+  std::vector<Json::Value> objects;
+  for (size_t i = 0; i < value[key].size(); ++i) {
+    objects.push_back(value[key][i]);
+  }
+  return objects;
+}
+
+Json::Value ReadValue(const Json::Value& value, const std::string& key) {
+  return value[key];
+}
+
+std::string ReadString(const Json::Value& value, const std::string& key) {
+  return value[key].asString();
+}
+
+uint32 ReadUInt(const Json::Value& value, const std::string& key) {
+  return value[key].asUInt();
+}
+
+double ReadDouble(const Json::Value& value, const std::string& key) {
+  return value[key].asDouble();
+}
+
+// Add values
+void Append(Json::Value* object, const std::string& key, bool value) {
+  (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key, char * value) {
+  (*object)[key] = Json::Value(value);
+}
+void Append(Json::Value* object, const std::string& key, double value) {
+  (*object)[key] = Json::Value(value);
+}
+void Append(Json::Value* object, const std::string& key, float value) {
+  (*object)[key] = Json::Value(value);
+}
+void Append(Json::Value* object, const std::string& key, int value) {
+  (*object)[key] = Json::Value(value);
+}
+void Append(Json::Value* object, const std::string& key,
+            const std::string& value) {
+  (*object)[key] = Json::Value(value);
+}
+void Append(Json::Value* object, const std::string& key, uint32 value) {
+  (*object)[key] = Json::Value(value);
+}
+
+void Append(Json::Value* object, const std::string& key,
+            const Json::Value& value) {
+  (*object)[key] = value;
+}
+
+void Append(Json::Value* object,
+            const std::string & key,
+            const std::vector<Json::Value>& values) {
+  for (std::vector<Json::Value>::const_iterator iter = values.begin();
+      iter != values.end(); ++iter) {
+    (*object)[key].append(*iter);
+  }
+}
+
+}  // namespace webrtc
diff --git a/talk/app/webrtc/webrtc_json.h b/talk/app/webrtc/webrtc_json.h
new file mode 100644
index 0000000..b57adf0
--- /dev/null
+++ b/talk/app/webrtc/webrtc_json.h
@@ -0,0 +1,118 @@
+/*
+ * 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_APP_WEBRTC_WEBRTC_JSON_H_
+#define TALK_APP_WEBRTC_WEBRTC_JSON_H_
+
+#include <string>
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "json/json.h"
+#else
+#include "third_party/jsoncpp/json.h"
+#endif
+#include "talk/session/phone/codec.h"
+#include "talk/p2p/base/candidate.h"
+
+namespace Json {
+class Value;
+}
+
+namespace cricket {
+class AudioContentDescription;
+class VideoContentDescription;
+struct ContentInfo;
+class SessionDescription;
+}
+
+struct StunServiceDetails {
+  std::string host;
+  std::string service;
+  std::string protocol;
+};
+
+namespace webrtc {
+
+std::vector<Json::Value> ReadValues(const Json::Value& value,
+                                    const std::string& key);
+
+bool BuildMediaMessage(
+    const cricket::ContentInfo& content_info,
+    const std::vector<cricket::Candidate>& candidates,
+    bool video,
+    Json::Value* value);
+
+bool GetJsonSignalingMessage(
+    const cricket::SessionDescription* sdp,
+    const std::vector<cricket::Candidate>& candidates,
+    std::string* signaling_message);
+
+bool BuildRtpMapParams(
+    const cricket::ContentInfo& audio_offer,
+    bool video,
+    std::vector<Json::Value>* rtpmap);
+
+bool BuildAttributes(const std::vector<cricket::Candidate>& candidates,
+                     bool video,
+                     std::vector<Json::Value>* jcandidates);
+
+std::string Serialize(const Json::Value& value);
+bool Deserialize(const std::string& message, Json::Value& value);
+
+bool ParseJsonSignalingMessage(const std::string& signaling_message,
+                               cricket::SessionDescription** sdp,
+                               std::vector<cricket::Candidate>* candidates);
+bool ParseRtcpMux(const Json::Value& value);
+bool ParseAudioCodec(const Json::Value& value,
+                     cricket::AudioContentDescription* content);
+bool ParseVideoCodec(const Json::Value& value,
+                     cricket::VideoContentDescription* content);
+bool ParseIceCandidates(const Json::Value& value,
+                        std::vector<cricket::Candidate>* candidates);
+Json::Value ReadValue(const Json::Value& value, const std::string& key);
+std::string ReadString(const Json::Value& value, const std::string& key);
+double ReadDouble(const Json::Value& value, const std::string& key);
+uint32 ReadUInt(const Json::Value& value, const std::string& key);
+
+// Add values
+void Append(Json::Value* object, const std::string& key, bool value);
+
+void Append(Json::Value* object, const std::string& key, char * value);
+void Append(Json::Value* object, const std::string& key, double value);
+void Append(Json::Value* object, const std::string& key, float value);
+void Append(Json::Value* object, const std::string& key, int value);
+void Append(Json::Value* object, const std::string& key,
+            const std::string& value);
+void Append(Json::Value* object, const std::string& key, uint32 value);
+void Append(Json::Value* object, const std::string& key,
+            const Json::Value& value);
+void Append(Json::Value* object,
+            const std::string& key,
+            const std::vector<Json::Value>& values);
+}
+
+#endif  // TALK_APP_WEBRTC_WEBRTC_JSON_H_
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
new file mode 100644
index 0000000..d8046c4
--- /dev/null
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -0,0 +1,587 @@
+/*
+ * 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/app/webrtc/webrtcsession.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/json.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/voicechannel.h"
+
+namespace webrtc {
+
+enum {
+  MSG_CANDIDATE_TIMEOUT = 101,
+};
+
+static const int kAudioMonitorPollFrequency = 100;
+static const int kMonitorPollFrequency = 1000;
+
+// We allow 30 seconds to establish a connection; beyond that we consider
+// it an error
+static const int kCallSetupTimeout = 30 * 1000;
+// A loss of connectivity is probably due to the Internet connection going
+// down, and it might take a while to come back on wireless networks, so we
+// use a longer timeout for that.
+static const int kCallLostTimeout = 60 * 1000;
+
+static const char kVideoStream[] = "video_rtp";
+static const char kAudioStream[] = "rtp";
+
+WebRtcSession::WebRtcSession(const std::string& id,
+                             bool incoming,
+                             cricket::PortAllocator* allocator,
+                             cricket::ChannelManager* channelmgr,
+                             talk_base::Thread* signaling_thread)
+    : BaseSession(signaling_thread, channelmgr->worker_thread(),
+                  allocator, id, "", !incoming),
+      transport_(NULL),
+      channel_manager_(channelmgr),
+      transports_writable_(false),
+      muted_(false),
+      camera_muted_(false),
+      setup_timeout_(kCallSetupTimeout),
+      signaling_thread_(signaling_thread),
+      incoming_(incoming),
+      port_allocator_(allocator) {
+}
+
+WebRtcSession::~WebRtcSession() {
+  RemoveAllStreams();
+  // TODO: Do we still need Terminate?
+  // if (state_ != STATE_RECEIVEDTERMINATE) {
+  //   Terminate();
+  // }
+  if (transport_) {
+    delete transport_;
+    transport_ = NULL;
+  }
+}
+
+bool WebRtcSession::Initiate() {
+  if (signaling_thread_ == NULL)
+    return false;
+
+  transport_ = CreateTransport();
+
+  if (transport_ == NULL)
+    return false;
+
+  transport_->set_allow_local_ips(true);
+
+  // start transports
+  transport_->SignalRequestSignaling.connect(
+      this, &WebRtcSession::OnRequestSignaling);
+  transport_->SignalCandidatesReady.connect(
+      this, &WebRtcSession::OnCandidatesReady);
+  transport_->SignalWritableState.connect(
+      this, &WebRtcSession::OnWritableState);
+  // Limit the amount of time that setting up a call may take.
+  StartTransportTimeout(kCallSetupTimeout);
+  return true;
+}
+
+cricket::Transport* WebRtcSession::CreateTransport() {
+  ASSERT(signaling_thread()->IsCurrent());
+  return new cricket::P2PTransport(
+      talk_base::Thread::Current(),
+      channel_manager_->worker_thread(), port_allocator());
+}
+
+bool WebRtcSession::CreateVoiceChannel(const std::string& stream_id) {
+  StreamInfo* stream_info = new StreamInfo(stream_id);
+  stream_info->video = false;
+  streams_.push_back(stream_info);
+
+  // RTCP disabled
+  cricket::VoiceChannel* voice_channel =
+      channel_manager_->CreateVoiceChannel(this, stream_id, true);
+  ASSERT(voice_channel != NULL);
+  stream_info->channel = voice_channel;
+
+  if (!incoming()) {
+    SignalRtcMediaChannelCreated(stream_id, false);
+  }
+  return true;
+}
+
+bool WebRtcSession::CreateVideoChannel(const std::string& stream_id) {
+  StreamInfo* stream_info = new StreamInfo(stream_id);
+  stream_info->video = true;
+  streams_.push_back(stream_info);
+
+  // RTCP disabled
+  cricket::VideoChannel* video_channel =
+      channel_manager_->CreateVideoChannel(this, stream_id, true, NULL);
+  ASSERT(video_channel != NULL);
+  stream_info->channel = video_channel;
+
+  if (!incoming()) {
+    SignalRtcMediaChannelCreated(stream_id, true);
+  }
+  return true;
+}
+
+cricket::TransportChannel* WebRtcSession::CreateChannel(
+    const std::string& content_name,
+    const std::string& name) {
+  if (!transport_) {
+    return NULL;
+  }
+  std::string type;
+  if (content_name.compare(kVideoStream) == 0) {
+    type = cricket::NS_GINGLE_VIDEO;
+  } else {
+    type = cricket::NS_GINGLE_AUDIO;
+  }
+  cricket::TransportChannel* transport_channel =
+      transport_->CreateChannel(name, type);
+  ASSERT(transport_channel != NULL);
+  return transport_channel;
+}
+
+cricket::TransportChannel* WebRtcSession::GetChannel(
+    const std::string& content_name, const std::string& name) {
+  if (!transport_)
+    return NULL;
+
+  return transport_->GetChannel(name);
+}
+
+void WebRtcSession::DestroyChannel(
+    const std::string& content_name, const std::string& name) {
+  if (!transport_)
+    return;
+
+  transport_->DestroyChannel(name);
+}
+
+void WebRtcSession::OnMessage(talk_base::Message* message) {
+  switch (message->message_id) {
+    case MSG_CANDIDATE_TIMEOUT:
+      if (transport_->writable()) {
+        // This should never happen: The timout triggered even
+        // though a call was successfully set up.
+        ASSERT(false);
+      }
+      SignalFailedCall();
+      break;
+    default:
+      cricket::BaseSession::OnMessage(message);
+      break;
+  }
+}
+
+bool WebRtcSession::Connect() {
+  if (streams_.empty()) {
+    // nothing to initiate
+    return false;
+  }
+  // lets connect all the transport channels created before for this session
+  transport_->ConnectChannels();
+
+  // create an offer now. This is to call SetState
+  // Actual offer will be send when OnCandidatesReady callback received
+  cricket::SessionDescription* offer = CreateOffer();
+  set_local_description(offer);
+  SetState((incoming()) ? STATE_SENTACCEPT : STATE_SENTINITIATE);
+
+  // Enable all the channels
+  EnableAllStreams();
+  SetVideoCapture(true);
+  return true;
+}
+
+bool WebRtcSession::SetVideoRenderer(const std::string& stream_id,
+                                     cricket::VideoRenderer* renderer) {
+  bool ret = false;
+  StreamMap::iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* stream_info = (*iter);
+    if (stream_info->stream_id.compare(stream_id) == 0) {
+      ASSERT(stream_info->channel != NULL);
+      ASSERT(stream_info->video);
+      cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*>(
+          stream_info->channel);
+      ret = channel->SetRenderer(0, renderer);
+      break;
+    }
+  }
+  return ret;
+}
+
+bool WebRtcSession::SetVideoCapture(bool capture) {
+  channel_manager_->SetVideoCapture(capture);
+  return true;
+}
+
+bool WebRtcSession::RemoveStream(const std::string& stream_id) {
+  bool ret = false;
+  StreamMap::iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* sinfo = (*iter);
+    if (sinfo->stream_id.compare(stream_id) == 0) {
+      if (!sinfo->video) {
+        cricket::VoiceChannel* channel = static_cast<cricket::VoiceChannel*> (
+            sinfo->channel);
+        channel->Enable(false);
+        // Note: If later the channel is used by multiple streams, then we
+        // should not destroy the channel until all the streams are removed.
+        channel_manager_->DestroyVoiceChannel(channel);
+      } else {
+        cricket::VideoChannel* channel = static_cast<cricket::VideoChannel*> (
+            sinfo->channel);
+        channel->Enable(false);
+        // Note: If later the channel is used by multiple streams, then we
+        // should not destroy the channel until all the streams are removed.
+        channel_manager_->DestroyVideoChannel(channel);
+      }
+      // channel and transport will be deleted in
+      // DestroyVoiceChannel/DestroyVideoChannel
+      streams_.erase(iter);
+      ret = true;
+      break;
+    }
+  }
+  if (!ret) {
+    LOG(LERROR) << "No streams found for stream id " << stream_id;
+    // TODO: trigger onError callback
+  }
+  return ret;
+}
+
+void WebRtcSession::EnableAllStreams() {
+  StreamMap::const_iterator i;
+  for (i = streams_.begin(); i != streams_.end(); ++i) {
+    cricket::BaseChannel* channel = (*i)->channel;
+    if (channel)
+      channel->Enable(true);
+  }
+}
+
+void WebRtcSession::RemoveAllStreams() {
+  SetState(STATE_RECEIVEDTERMINATE);
+
+  // signaling_thread_->Post(this, MSG_RTC_REMOVEALLSTREAMS);
+  // First build a list of streams to remove and then remove them.
+  // The reason we do this is that if we remove the streams inside the
+  // loop, a stream might get removed while we're enumerating and the iterator
+  // will become invalid (and we crash).
+  // streams_ entry will be removed from ChannelManager callback method
+  // DestroyChannel
+  std::vector<std::string> streams_to_remove;
+  StreamMap::iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter)
+    streams_to_remove.push_back((*iter)->stream_id);
+
+  for (std::vector<std::string>::iterator i = streams_to_remove.begin();
+       i != streams_to_remove.end(); ++i) {
+    RemoveStream(*i);
+  }
+}
+
+bool WebRtcSession::HasStream(const std::string& stream_id) const {
+  StreamMap::const_iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* sinfo = (*iter);
+    if (stream_id.compare(sinfo->stream_id) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool WebRtcSession::HasChannel(bool video) const {
+  StreamMap::const_iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    StreamInfo* sinfo = (*iter);
+    if (sinfo->video == video) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool WebRtcSession::HasAudioChannel() const {
+  return HasChannel(false);
+}
+
+bool WebRtcSession::HasVideoChannel() const {
+  return HasChannel(true);
+}
+
+void WebRtcSession::OnRequestSignaling(cricket::Transport* transport) {
+  transport->OnSignalingReady();
+}
+
+void WebRtcSession::OnWritableState(cricket::Transport* transport) {
+  ASSERT(transport == transport_);
+  const bool transports_writable = transport_->writable();
+  if (transports_writable) {
+    if (transports_writable != transports_writable_) {
+      signaling_thread_->Clear(this, MSG_CANDIDATE_TIMEOUT);
+    } else {
+      // At one point all channels were writable and we had full connectivity,
+      // but then we lost it. Start the timeout again to kill the call if it
+      // doesn't come back.
+      StartTransportTimeout(kCallLostTimeout);
+    }
+    transports_writable_ = transports_writable;
+  }
+  NotifyTransportState();
+  return;
+}
+
+void WebRtcSession::StartTransportTimeout(int timeout) {
+  talk_base::Thread::Current()->PostDelayed(timeout, this,
+                                            MSG_CANDIDATE_TIMEOUT,
+                                            NULL);
+}
+
+void WebRtcSession::NotifyTransportState() {
+}
+
+bool WebRtcSession::OnInitiateMessage(
+    cricket::SessionDescription* offer,
+    const std::vector<cricket::Candidate>& candidates) {
+  if (!offer) {
+    LOG(LERROR) << "No SessionDescription from peer";
+    return false;
+  }
+
+  talk_base::scoped_ptr<cricket::SessionDescription> answer;
+  answer.reset(CreateAnswer(offer));
+
+  if (!answer.get()) {
+    return false;
+  }
+
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(
+      answer.get());
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(
+      answer.get());
+
+  if (!audio_content && !video_content) {
+    return false;
+  }
+
+  bool ret = true;
+  if (audio_content) {
+    ret = !HasAudioChannel() &&
+          CreateVoiceChannel(audio_content->name);
+    if (!ret) {
+      LOG(LERROR) << "Failed to create voice channel for "
+                  << audio_content->name;
+      return false;
+    }
+  }
+
+  if (video_content) {
+    ret = !HasVideoChannel() &&
+          CreateVideoChannel(video_content->name);
+    if (!ret) {
+      LOG(LERROR) << "Failed to create video channel for "
+                  << video_content->name;
+      return false;
+    }
+  }
+  // Provide remote candidates to the transport
+  transport_->OnRemoteCandidates(candidates);
+
+  set_remote_description(offer);
+  SetState(STATE_RECEIVEDINITIATE);
+
+  transport_->ConnectChannels();
+  EnableAllStreams();
+
+  set_local_description(answer.release());
+
+  // AddStream called only once with Video label
+  if (video_content) {
+    SignalAddStream(video_content->name, true);
+  } else {
+    SignalAddStream(audio_content->name, false);
+  }
+  SetState(STATE_SENTACCEPT);
+  return true;
+}
+
+bool WebRtcSession::OnRemoteDescription(
+    cricket::SessionDescription* desc,
+    const std::vector<cricket::Candidate>& candidates) {
+  if (state() == STATE_SENTACCEPT ||
+      state() == STATE_RECEIVEDACCEPT ||
+      state() == STATE_INPROGRESS) {
+    transport_->OnRemoteCandidates(candidates);
+    return true;
+  }
+  // Session description is always accepted.
+  set_remote_description(desc);
+  SetState(STATE_RECEIVEDACCEPT);
+  // Will trigger OnWritableState() if successful.
+  transport_->OnRemoteCandidates(candidates);
+
+  if (!incoming()) {
+    // Trigger OnAddStream callback at the initiator
+    const cricket::ContentInfo* video_content = GetFirstVideoContent(desc);
+    if (video_content) {
+      SignalAddStream(video_content->name, true);
+    } else {
+      const cricket::ContentInfo* audio_content = GetFirstAudioContent(desc);
+      if (audio_content)
+        SignalAddStream(audio_content->name, false);
+    }
+  }
+  return true;
+}
+
+cricket::SessionDescription* WebRtcSession::CreateOffer() {
+  cricket::SessionDescription* offer = new cricket::SessionDescription();
+  StreamMap::iterator iter;
+  for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
+    if ((*iter)->video) {
+      // add video codecs, if there is video stream added
+      cricket::VideoContentDescription* video =
+          new cricket::VideoContentDescription();
+      std::vector<cricket::VideoCodec> video_codecs;
+      channel_manager_->GetSupportedVideoCodecs(&video_codecs);
+      for (VideoCodecs::const_iterator codec = video_codecs.begin();
+           codec != video_codecs.end(); ++codec) {
+        video->AddCodec(*codec);
+      }
+
+      // enabling RTCP mux by default
+      video->set_rtcp_mux(true);
+      video->SortCodecs();
+      offer->AddContent(cricket::CN_VIDEO, cricket::NS_JINGLE_RTP, video);
+    } else {
+      cricket::AudioContentDescription* audio =
+          new cricket::AudioContentDescription();
+
+      std::vector<cricket::AudioCodec> audio_codecs;
+      channel_manager_->GetSupportedAudioCodecs(&audio_codecs);
+      for (AudioCodecs::const_iterator codec = audio_codecs.begin();
+           codec != audio_codecs.end(); ++codec) {
+        audio->AddCodec(*codec);
+      }
+
+      // enabling RTCP mux by default.
+      audio->set_rtcp_mux(true);
+      audio->SortCodecs();
+      offer->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP, audio);
+    }
+  }
+  return offer;
+}
+
+cricket::SessionDescription* WebRtcSession::CreateAnswer(
+    const cricket::SessionDescription* offer) {
+  cricket::SessionDescription* answer = new cricket::SessionDescription();
+
+  const cricket::ContentInfo* audio_content = GetFirstAudioContent(offer);
+  if (audio_content) {
+    const cricket::AudioContentDescription* audio_offer =
+        static_cast<const cricket::AudioContentDescription*>(
+            audio_content->description);
+
+    cricket::AudioContentDescription* audio_accept =
+        new cricket::AudioContentDescription();
+    AudioCodecs audio_codecs;
+    channel_manager_->GetSupportedAudioCodecs(&audio_codecs);
+
+    for (AudioCodecs::const_iterator ours = audio_codecs.begin();
+         ours != audio_codecs.end(); ++ours) {
+      for (AudioCodecs::const_iterator theirs = audio_offer->codecs().begin();
+           theirs != audio_offer->codecs().end(); ++theirs) {
+        if (ours->Matches(*theirs)) {
+          cricket::AudioCodec negotiated(*ours);
+          negotiated.id = theirs->id;
+          audio_accept->AddCodec(negotiated);
+        }
+      }
+    }
+
+    // RTCP mux is set based on what present in incoming offer
+    audio_accept->set_rtcp_mux(audio_offer->rtcp_mux());
+    audio_accept->SortCodecs();
+    answer->AddContent(audio_content->name, audio_content->type, audio_accept);
+  }
+
+  const cricket::ContentInfo* video_content = GetFirstVideoContent(offer);
+  if (video_content) {
+    const cricket::VideoContentDescription* video_offer =
+        static_cast<const cricket::VideoContentDescription*>(
+            video_content->description);
+
+    cricket::VideoContentDescription* video_accept =
+        new cricket::VideoContentDescription();
+    VideoCodecs video_codecs;
+    channel_manager_->GetSupportedVideoCodecs(&video_codecs);
+
+    for (VideoCodecs::const_iterator ours = video_codecs.begin();
+         ours != video_codecs.end(); ++ours) {
+      for (VideoCodecs::const_iterator theirs = video_offer->codecs().begin();
+           theirs != video_offer->codecs().end(); ++theirs) {
+        if (ours->Matches(*theirs)) {
+          cricket::VideoCodec negotiated(*ours);
+          negotiated.id = theirs->id;
+          video_accept->AddCodec(negotiated);
+        }
+      }
+    }
+
+    // RTCP mux is set based on what present in incoming offer
+    video_accept->set_rtcp_mux(video_offer->rtcp_mux());
+    video_accept->SortCodecs();
+    answer->AddContent(video_content->name, video_content->type, video_accept);
+  }
+  return answer;
+}
+
+void WebRtcSession::SetError(Error error) {
+  BaseSession::SetError(error);
+}
+
+void WebRtcSession::OnCandidatesReady(
+    cricket::Transport* transport,
+    const std::vector<cricket::Candidate>& candidates) {
+  std::vector<cricket::Candidate>::const_iterator iter;
+  for (iter = candidates.begin(); iter != candidates.end(); ++iter) {
+    local_candidates_.push_back(*iter);
+  }
+  SignalLocalDescription(local_description(), candidates);
+}
+} /* namespace webrtc */
diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h
new file mode 100644
index 0000000..bbddb07
--- /dev/null
+++ b/talk/app/webrtc/webrtcsession.h
@@ -0,0 +1,207 @@
+/*
+ * 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_APP_WEBRTC_WEBRTCSESSION_H_
+#define TALK_APP_WEBRTC_WEBRTCSESSION_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/messagehandler.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/mediachannel.h"
+
+namespace cricket {
+class ChannelManager;
+class Transport;
+class TransportChannel;
+class VoiceChannel;
+class VideoChannel;
+struct ConnectionInfo;
+}
+
+namespace Json {
+class Value;
+}
+
+namespace webrtc {
+
+typedef std::vector<cricket::AudioCodec> AudioCodecs;
+typedef std::vector<cricket::VideoCodec> VideoCodecs;
+
+class WebRtcSession : public cricket::BaseSession {
+ public:
+  WebRtcSession(const std::string& id,
+                    bool incoming,
+                    cricket::PortAllocator* allocator,
+                    cricket::ChannelManager* channelmgr,
+                    talk_base::Thread* signaling_thread);
+
+  ~WebRtcSession();
+
+  bool Initiate();
+  bool Connect();
+  bool OnRemoteDescription(cricket::SessionDescription* sdp,
+      const std::vector<cricket::Candidate>& candidates);
+  bool OnInitiateMessage(cricket::SessionDescription* sdp,
+      const std::vector<cricket::Candidate>& candidates);
+  bool CreateVoiceChannel(const std::string& stream_id);
+  bool CreateVideoChannel(const std::string& stream_id);
+  bool RemoveStream(const std::string& stream_id);
+  void RemoveAllStreams();
+
+  // Returns true if we have either a voice or video stream matching this label.
+  bool HasStream(const std::string& label) const;
+  bool HasChannel(bool video) const;
+
+  // Returns true if there's one or more audio channels in the session.
+  bool HasAudioChannel() const;
+
+  // Returns true if there's one or more video channels in the session.
+  bool HasVideoChannel() const;
+
+  bool SetVideoRenderer(const std::string& stream_id,
+                        cricket::VideoRenderer* renderer);
+
+  // This signal occurs when all the streams have been removed.
+  // It is triggered by a successful call to the RemoveAllStream or
+  // the OnRemoteDescription with stream deleted signaling message with the
+  // candidates port equal to 0.
+  sigslot::signal1<WebRtcSession*> SignalRemoveStreamMessage;
+
+  // This signal indicates a stream has been added properly.
+  // It is triggered by a successful call to the OnInitiateMessage or
+  // the OnRemoteDescription and if it's going to the STATE_RECEIVEDACCEPT.
+  sigslot::signal2<const std::string&, bool> SignalAddStream;
+
+  // This signal occurs when one stream is removed with the signaling
+  // message from the remote peer with the candidates port equal to 0.
+  sigslot::signal2<const std::string&, bool> SignalRemoveStream;
+
+  // This signal occurs when audio/video channel has been created for the
+  // new added stream.
+  sigslot::signal2<const std::string&, bool> SignalRtcMediaChannelCreated;
+
+  // This signal occurs when the local candidate is ready
+  sigslot::signal2<const cricket::SessionDescription*,
+      const std::vector<cricket::Candidate>&> SignalLocalDescription;
+
+  // This signal triggers when setting up or resuming a call has not been
+  // successful before a certain time out.
+  sigslot::signal0<> SignalFailedCall;
+
+  bool muted() const { return muted_; }
+  bool camera_muted() const { return camera_muted_; }
+  const std::vector<cricket::Candidate>& local_candidates() {
+    return local_candidates_;
+  }
+  void set_incoming(bool incoming) { incoming_ = incoming; }
+  bool incoming() const { return incoming_; }
+  cricket::PortAllocator* port_allocator() const { return port_allocator_; }
+  talk_base::Thread* signaling_thread() const { return signaling_thread_; }
+
+ protected:
+  // methods from cricket::BaseSession
+  virtual void SetError(cricket::BaseSession::Error error);
+  virtual cricket::TransportChannel* CreateChannel(
+      const std::string& content_name, const std::string& name);
+  virtual cricket::TransportChannel* GetChannel(
+      const std::string& content_name, const std::string& name);
+  virtual void DestroyChannel(
+      const std::string& content_name, const std::string& name);
+
+ private:
+  struct StreamInfo {
+    explicit StreamInfo(const std::string stream_id)
+        : channel(NULL),
+          video(false),
+          stream_id(stream_id) {}
+
+    StreamInfo()
+        : channel(NULL),
+          video(false) {}
+    cricket::BaseChannel* channel;
+    bool video;
+    std::string stream_id;
+  };
+  // Not really a map (vector).
+  typedef std::vector<StreamInfo*> StreamMap;
+
+  // methods signaled by the transport
+  void OnRequestSignaling(cricket::Transport* transport);
+  void OnCandidatesReady(cricket::Transport* transport,
+                         const std::vector<cricket::Candidate>& candidates);
+  void OnWritableState(cricket::Transport* transport);
+  void OnTransportError(cricket::Transport* transport);
+  void OnChannelGone(cricket::Transport* transport);
+
+  bool CheckForStreamDeleteMessage(
+      const std::vector<cricket::Candidate>& candidates);
+  void ProcessTerminateAccept(cricket::SessionDescription* desc);
+
+  void UpdateTransportWritableState();
+  bool CheckAllTransportsWritable();
+  void StartTransportTimeout(int timeout);
+  void NotifyTransportState();
+
+  cricket::SessionDescription* CreateOffer();
+  cricket::SessionDescription* CreateAnswer(
+      const cricket::SessionDescription* answer);
+
+  // from MessageHandler
+  virtual void OnMessage(talk_base::Message* message);
+
+  virtual cricket::Transport* CreateTransport();
+  cricket::Transport* GetTransport();
+
+  typedef std::map<std::string, cricket::TransportChannel*> TransportChannelMap;
+
+  bool SetVideoCapture(bool capture);
+  void EnableAllStreams();
+
+  cricket::Transport* transport_;
+  cricket::ChannelManager* channel_manager_;
+  std::vector<StreamInfo*> streams_;
+  TransportChannelMap transport_channels_;
+  bool transports_writable_;
+  bool muted_;
+  bool camera_muted_;
+  int setup_timeout_;
+  std::vector<cricket::Candidate> local_candidates_;
+
+  talk_base::Thread* signaling_thread_;
+  bool incoming_;
+  cricket::PortAllocator* port_allocator_;
+};
+
+}  // namespace webrtc
+
+#endif  // TALK_APP_WEBRTC_WEBRTCSESSION_H_
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
new file mode 100644
index 0000000..72610b9
--- /dev/null
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -0,0 +1,550 @@
+/*
+ * 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 <stdio.h>
+
+#include <list>
+
+#include "base/gunit.h"
+#include "base/helpers.h"
+#include "talk/app/webrtc/webrtcsession.h"
+#include "talk/base/fakenetwork.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/client/fakeportallocator.h"
+#include "talk/session/phone/fakesession.h"
+#include "talk/session/phone/mediasessionclient.h"
+
+namespace {
+cricket::VideoContentDescription* CopyVideoContentDescription(
+    const cricket::VideoContentDescription* video_description) {
+  cricket::VideoContentDescription* new_video_description =
+      new cricket::VideoContentDescription();
+  cricket::VideoCodecs::const_iterator iter =
+      video_description->codecs().begin();
+  for (; iter != video_description->codecs().end(); iter++) {
+    new_video_description->AddCodec(*iter);
+  }
+  new_video_description->SortCodecs();
+  return new_video_description;
+}
+
+cricket::AudioContentDescription* CopyAudioContentDescription(
+    const cricket::AudioContentDescription* audio_description) {
+  cricket::AudioContentDescription* new_audio_description =
+      new cricket::AudioContentDescription();
+  cricket::AudioCodecs::const_iterator iter =
+      audio_description->codecs().begin();
+  for (; iter != audio_description->codecs().end(); iter++) {
+    new_audio_description->AddCodec(*iter);
+  }
+  new_audio_description->SortCodecs();
+  return new_audio_description;
+}
+
+const cricket::ContentDescription* CopyContentDescription(
+    const cricket::ContentDescription* original) {
+  const cricket::MediaContentDescription* media =
+      static_cast<const cricket::MediaContentDescription*>(original);
+  const cricket::ContentDescription* new_content_description = NULL;
+  if (media->type() == cricket::MEDIA_TYPE_VIDEO) {
+    const cricket::VideoContentDescription* video_description =
+        static_cast<const cricket::VideoContentDescription*>(original);
+    new_content_description = static_cast<const cricket::ContentDescription*>
+        (CopyVideoContentDescription(video_description));
+  } else if (media->type() == cricket::MEDIA_TYPE_AUDIO) {
+    const cricket::AudioContentDescription* audio_description =
+        static_cast<const cricket::AudioContentDescription*>(original);
+    new_content_description = static_cast<const cricket::ContentDescription*>
+        (CopyAudioContentDescription(audio_description));
+  } else {
+    return NULL;
+  }
+  return new_content_description;
+}
+
+cricket::ContentInfos CopyContentInfos(const cricket::ContentInfos& original) {
+  cricket::ContentInfos new_content_infos;
+  for (cricket::ContentInfos::const_iterator iter = original.begin();
+       iter != original.end(); iter++) {
+    cricket::ContentInfo info;
+    info.name = (*iter).name;
+    info.type = (*iter).type;
+    info.description = CopyContentDescription((*iter).description);
+    new_content_infos.push_back(info);
+  }
+  return new_content_infos;
+}
+
+cricket::SessionDescription* CopySessionDescription(
+    const cricket::SessionDescription* original) {
+  const cricket::ContentInfos& content_infos = original->contents();
+  cricket::ContentInfos new_content_infos = CopyContentInfos(content_infos);
+  return new cricket::SessionDescription(new_content_infos);
+}
+
+cricket::SessionDescription* GenerateFakeSessionDescription(bool video) {
+  cricket::SessionDescription* fake_description =
+      new cricket::SessionDescription();
+  const std::string name = video ? std::string(cricket::CN_VIDEO) :
+                                   std::string(cricket::CN_AUDIO);
+  cricket::ContentDescription* description = NULL;
+  if (video) {
+    cricket::VideoContentDescription* video_dsc =
+        new cricket::VideoContentDescription;
+    video_dsc->SortCodecs();
+    description = static_cast<cricket::ContentDescription*>(video_dsc);
+  } else {
+    cricket::AudioContentDescription* audio_dsc =
+        new cricket::AudioContentDescription();
+    audio_dsc->SortCodecs();
+    description = static_cast<cricket::ContentDescription*>(audio_dsc);
+  }
+
+  // Cannot fail.
+  fake_description->AddContent(name, cricket::NS_JINGLE_RTP, description);
+  return fake_description;
+}
+
+void GenerateFakeCandidate(std::vector<cricket::Candidate>* candidates,
+                           bool video) {
+  // Next add a candidate.
+  // int port_index = 0;
+  std::string port_index_as_string("0");
+
+  cricket::Candidate candidate;
+  candidate.set_name("rtp");
+  candidate.set_protocol("udp");
+  talk_base::SocketAddress address("127.0.0.1", 1234);
+  candidate.set_address(address);
+  candidate.set_preference(1);
+  candidate.set_username("username" + port_index_as_string);
+  candidate.set_password(port_index_as_string);
+  candidate.set_type("local");
+  candidate.set_network_name("network");
+  candidate.set_generation(0);
+
+  candidates->push_back(candidate);
+}
+
+cricket::SessionDescription* GenerateFakeSession(
+    std::vector<cricket::Candidate>* candidates,
+    bool video) {
+  cricket::SessionDescription* fake_description =
+      GenerateFakeSessionDescription(video);
+  if (fake_description == NULL) {
+    return NULL;
+  }
+  GenerateFakeCandidate(candidates, video);
+  return fake_description;
+}
+}  // namespace
+
+class WebRtcSessionTest
+    : public sigslot::has_slots<>,
+      public testing::Test {
+ public:
+  enum CallbackId {
+    kNone,
+    kOnAddStream,
+    kOnRemoveStream,
+    kOnRtcMediaChannelCreated,
+    kOnLocalDescription,
+    kOnFailedCall,
+  };
+
+  WebRtcSessionTest()
+      : callback_ids_(),
+        last_stream_id_(""),
+        last_was_video_(false),
+        last_description_ptr_(NULL),
+        last_candidates_(),
+        session_(NULL),
+        id_(),
+        receiving_(false),
+        allocator_(NULL),
+        channel_manager_(NULL),
+        worker_thread_(NULL),
+        signaling_thread_(NULL) {
+  }
+
+  ~WebRtcSessionTest() {
+    session_.reset();
+  }
+
+  void OnAddStream(const std::string& stream_id, bool video) {
+    callback_ids_.push_back(kOnAddStream);
+    last_stream_id_ = stream_id;
+    last_was_video_ = video;
+  }
+  void OnRemoveStream(const std::string& stream_id, bool video) {
+    callback_ids_.push_back(kOnRemoveStream);
+    last_stream_id_ = stream_id;
+    last_was_video_ = video;
+  }
+  void OnRtcMediaChannelCreated(const std::string& stream_id,
+                                        bool video) {
+    callback_ids_.push_back(kOnRtcMediaChannelCreated);
+    last_stream_id_ = stream_id;
+    last_was_video_ = video;
+  }
+  void OnLocalDescription(
+      const cricket::SessionDescription* desc,
+      const std::vector<cricket::Candidate>& candidates) {
+    callback_ids_.push_back(kOnLocalDescription);
+    last_description_ptr_.reset(CopySessionDescription(desc));
+    last_candidates_.clear();
+    last_candidates_.insert(last_candidates_.end(), candidates.begin(),
+                            candidates.end());
+  }
+  cricket::SessionDescription* GetLocalDescription(
+      std::vector<cricket::Candidate>* candidates) {
+    if (last_candidates_.empty()) {
+      return NULL;
+    }
+    if (!last_description_ptr_.get()) {
+      return NULL;
+    }
+    candidates->insert(candidates->end(), last_candidates_.begin(),
+                       last_candidates_.end());
+    return CopySessionDescription(last_description_ptr_.get());
+  }
+
+  void OnFailedCall() {
+    callback_ids_.push_back(kOnFailedCall);
+  }
+
+  CallbackId PopOldestCallback() {
+    if (callback_ids_.empty()) {
+      return kNone;
+    }
+    const CallbackId return_value = callback_ids_.front();
+    callback_ids_.pop_front();
+    return return_value;
+  }
+
+  CallbackId PeekOldestCallback() {
+    if (callback_ids_.empty()) {
+      return kNone;
+    }
+    const CallbackId return_value = callback_ids_.front();
+    return return_value;
+  }
+
+  void Reset() {
+    callback_ids_.clear();
+    last_stream_id_ = "";
+    last_was_video_ = false;
+    last_description_ptr_.reset();
+    last_candidates_.clear();
+  }
+
+  bool WaitForCallback(CallbackId id, int timeout_ms) {
+    bool success = false;
+    for (int ms = 0; ms < timeout_ms; ms++) {
+      const CallbackId peek_id = PeekOldestCallback();
+      if (peek_id == id) {
+        PopOldestCallback();
+        success = true;
+        break;
+      } else if (peek_id != kNone) {
+        success = false;
+        break;
+      }
+      talk_base::Thread::Current()->ProcessMessages(1);
+    }
+    return success;
+  }
+
+  bool Init(bool receiving) {
+    if (signaling_thread_ != NULL)
+        return false;
+    signaling_thread_ = talk_base::Thread::Current();
+    receiving_ = receiving;
+
+    if (worker_thread_!= NULL)
+        return false;
+    worker_thread_ = talk_base::Thread::Current();
+
+    cricket::FakePortAllocator* fake_port_allocator =
+        new cricket::FakePortAllocator(worker_thread_, NULL);
+
+    allocator_.reset(static_cast<cricket::PortAllocator*>(fake_port_allocator));
+
+    channel_manager_.reset(new cricket::ChannelManager(worker_thread_));
+    if (!channel_manager_->Init())
+      return false;
+
+    talk_base::CreateRandomString(8, &id_);
+
+    session_.reset(new webrtc::WebRtcSession(
+        id_, receiving_ , allocator_.get(),
+        channel_manager_.get(),
+        signaling_thread_));
+    session_->SignalAddStream.connect(this, &WebRtcSessionTest::OnAddStream);
+    session_->SignalRemoveStream.connect(this,
+        &WebRtcSessionTest::OnRemoveStream);
+    session_->SignalRtcMediaChannelCreated.connect(this,
+        &WebRtcSessionTest::OnRtcMediaChannelCreated);
+    session_->SignalLocalDescription.connect(this,
+        &WebRtcSessionTest::OnLocalDescription);
+    session_->SignalFailedCall.connect(this, &WebRtcSessionTest::OnFailedCall);
+
+    return true;
+  }
+
+  // All session APIs must be called from the signaling thread.
+  bool CallInitiate() {
+    return session_->Initiate();
+  }
+
+  bool CallConnect() {
+    if (!session_->Connect())
+      return false;
+    // This callback does not happen with FakeTransport!
+    if (!WaitForCallback(kOnLocalDescription, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallOnRemoteDescription(
+      cricket::SessionDescription* description,
+      std::vector<cricket::Candidate> candidates) {
+    if (!session_->OnRemoteDescription(description, candidates)) {
+      return false;
+    }
+    if (!WaitForCallback(kOnAddStream, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallOnInitiateMessage(
+      cricket::SessionDescription* description,
+      const std::vector<cricket::Candidate>& candidates) {
+    if (!session_->OnInitiateMessage(description, candidates)) {
+      return false;
+    }
+    if (!WaitForCallback(kOnAddStream, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallCreateVoiceChannel(const std::string& stream_id) {
+    if (!session_->CreateVoiceChannel(stream_id)) {
+      return false;
+    }
+    if (!WaitForCallback(kOnRtcMediaChannelCreated, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallCreateVideoChannel(const std::string& stream_id) {
+    if (!session_->CreateVideoChannel(stream_id)) {
+      return false;
+    }
+    if (!WaitForCallback(kOnRtcMediaChannelCreated, 1000)) {
+      return false;
+    }
+    return true;
+  }
+
+  bool CallRemoveStream(const std::string& stream_id) {
+    return session_->RemoveStream(stream_id);
+  }
+
+  void CallRemoveAllStreams() {
+    session_->RemoveAllStreams();
+  }
+
+  bool CallHasChannel(const std::string& label) {
+    return session_->HasStream(label);
+  }
+
+  bool CallHasChannel(bool video) {
+    return session_->HasChannel(video);
+  }
+
+  bool CallHasAudioChannel() {
+    return session_->HasAudioChannel();
+  }
+
+  bool CallHasVideoChannel() {
+    return session_->HasVideoChannel();
+  }
+
+  bool CallSetVideoRenderer(const std::string& stream_id,
+                            cricket::VideoRenderer* renderer) {
+    return session_->SetVideoRenderer(stream_id, renderer);
+  }
+
+  const std::vector<cricket::Candidate>& CallLocalCandidates() {
+    return session_->local_candidates();
+  }
+
+ private:
+  std::list<CallbackId> callback_ids_;
+
+  std::string last_stream_id_;
+  bool last_was_video_;
+  talk_base::scoped_ptr<cricket::SessionDescription> last_description_ptr_;
+  std::vector<cricket::Candidate> last_candidates_;
+
+  talk_base::scoped_ptr<webrtc::WebRtcSession> session_;
+  std::string id_;
+  bool receiving_;
+
+  talk_base::scoped_ptr<cricket::PortAllocator> allocator_;
+
+  talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+
+  talk_base::Thread* worker_thread_;
+  talk_base::Thread* signaling_thread_;
+};
+
+bool CallbackReceived(WebRtcSessionTest* session, int timeout) {
+  EXPECT_EQ_WAIT(WebRtcSessionTest::kNone, session->PeekOldestCallback(),
+                 timeout);
+  const WebRtcSessionTest::CallbackId peek_id =
+      session->PeekOldestCallback();
+  return peek_id != WebRtcSessionTest::kNone;
+}
+
+TEST_F(WebRtcSessionTest, InitializationReceiveSanity) {
+  const bool kReceiving = true;
+  ASSERT_TRUE(Init(kReceiving));
+  ASSERT_TRUE(CallInitiate());
+
+  // Should return false because no stream has been set up yet.
+  EXPECT_FALSE(CallConnect());
+  const bool kVideo = true;
+  EXPECT_FALSE(CallHasChannel(kVideo));
+  EXPECT_FALSE(CallHasChannel(!kVideo));
+
+  EXPECT_EQ(kNone, PopOldestCallback());
+}
+
+TEST_F(WebRtcSessionTest, AudioSendCallSetUp) {
+  const bool kReceiving = false;
+  ASSERT_TRUE(Init(kReceiving));
+
+  ASSERT_TRUE(CallInitiate());
+
+  ASSERT_TRUE(CallCreateVoiceChannel("Audio"));
+  ASSERT_TRUE(CallConnect());
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session = GetLocalDescription(
+      &candidates);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+  if (!CallOnRemoteDescription(local_session, candidates)) {
+      delete local_session;
+      FAIL();
+  }
+
+  // All callbacks should be caught. Assert it.
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+  ASSERT_TRUE(CallHasAudioChannel() &&
+              !CallHasVideoChannel());
+}
+
+TEST_F(WebRtcSessionTest, VideoSendCallSetUp) {
+  const bool kReceiving = false;
+  ASSERT_TRUE(Init(kReceiving));
+
+  ASSERT_TRUE(CallInitiate());
+
+  ASSERT_TRUE(CallCreateVideoChannel("Video"));
+  ASSERT_TRUE(CallConnect());
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session = GetLocalDescription(
+      &candidates);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+
+  if (!CallOnRemoteDescription(local_session, candidates)) {
+      delete local_session;
+      FAIL();
+  }
+
+  // All callbacks should be caught. Assert it.
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+  ASSERT_TRUE(!CallHasAudioChannel() &&
+              CallHasVideoChannel());
+}
+
+TEST_F(WebRtcSessionTest, AudioReceiveCallSetUp) {
+  const bool kReceiving = true;
+  const bool video = false;
+
+  ASSERT_TRUE(Init(kReceiving));
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session =
+      GenerateFakeSession(&candidates, video);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+  // TODO: Figure out why the TransportChannel is not created.
+  // if (!CallOnInitiateMessage(local_session, candidates)) {
+  //    delete local_session;
+  //    FAIL();
+  // }
+  ASSERT_TRUE(CallConnect());
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+
+  ASSERT_TRUE(CallHasAudioChannel() &&
+              !CallHasVideoChannel());
+}
+
+TEST_F(WebRtcSessionTest, VideoReceiveCallSetUp) {
+  const bool kReceiving = true;
+  const bool video = true;
+
+  ASSERT_TRUE(Init(kReceiving));
+
+  std::vector<cricket::Candidate> candidates;
+  cricket::SessionDescription* local_session =
+      GenerateFakeSession(&candidates, video);
+  ASSERT_FALSE(candidates.empty());
+  ASSERT_FALSE(local_session == NULL);
+  // TODO: Figure out why the TransportChannel is not created.
+  // if (!CallOnInitiateMessage(local_session, candidates)) {
+  //     delete local_session;
+  //     FAIL();
+  // }
+  ASSERT_TRUE(CallConnect());
+  ASSERT_FALSE(CallbackReceived(this, 1000));
+  ASSERT_TRUE(!CallHasAudioChannel() &&
+              CallHasVideoChannel());
+}