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, ¶ms);
+}
+
+bool PeerConnectionProxy::Init() {
+ ResultParams params;
+ return (Send(MSG_WEBRTC_INIT, ¶ms) && params.result);
+}
+
+void PeerConnectionProxy::RegisterObserver(PeerConnectionObserver* observer) {
+ RegisterObserverParams params(observer);
+ Send(MSG_WEBRTC_REGISTEROBSERVER, ¶ms);
+}
+
+bool PeerConnectionProxy::SignalingMessage(
+ const std::string& signaling_message) {
+ SignalingMsgParams params(signaling_message);
+ return (Send(MSG_WEBRTC_SIGNALINGMESSAGE, ¶ms) && params.result);
+}
+
+bool PeerConnectionProxy::AddStream(const std::string& stream_id, bool video) {
+ AddStreamParams params(stream_id, video);
+ return (Send(MSG_WEBRTC_ADDSTREAM, ¶ms) && params.result);
+}
+
+bool PeerConnectionProxy::RemoveStream(const std::string& stream_id) {
+ RemoveStreamParams params(stream_id);
+ return (Send(MSG_WEBRTC_REMOVESTREAM, ¶ms) && 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, ¶ms) && params.result);
+}
+
+bool PeerConnectionProxy::SetLocalVideoRenderer(
+ cricket::VideoRenderer* renderer) {
+ SetLocalRendererParams params(renderer);
+ return (Send(MSG_WEBRTC_SETLOCALRENDERER, ¶ms) && params.result);
+}
+
+bool PeerConnectionProxy::SetVideoRenderer(const std::string& stream_id,
+ cricket::VideoRenderer* renderer) {
+ SetVideoRendererParams params(stream_id, renderer);
+ return (Send(MSG_WEBRTC_SETVIDEORENDERER, ¶ms) && params.result);
+}
+
+bool PeerConnectionProxy::SetVideoCapture(const std::string& cam_device) {
+ SetVideoCaptureParams params(cam_device);
+ return (Send(MSG_WEBRTC_SETVIDEOCAPTURE, ¶ms) && 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, ¶ms) && params.result);
+}
+
+bool PeerConnectionProxy::Close() {
+ ResultParams params;
+ return (Send(MSG_WEBRTC_CLOSE, ¶ms) && 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());
+}