Add unit tests and MUC room config.
git-svn-id: http://libjingle.googlecode.com/svn/trunk@81 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/CHANGELOG b/CHANGELOG
index 81b3f09..96ab6f4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,7 @@
0.5.9 - Aug 31, 2011
- Add app/webrtc
+ - Add webrtcvoiceengine/webrtcvideoengine
- Update STUN support some more (RFC 5389)
- Add video output scaling
- Refactoring and bug fixes
diff --git a/README b/README
index ccf22ea..f40d4ea 100644
--- a/README
+++ b/README
@@ -56,6 +56,13 @@
set PATH_TO_SWTOOLKIT=c:\src\swtoolkit
set PATH=%PATH_TO_SWTOOLKIT%;%PATH%
+ * Finally, download and install the unit test framework from
+ http://code.google.com/p/googletest/downloads/list
+ To install it, just unzip the package and put the file folder under
+ /talk/third_party/ directory and rename it from "gtest-x.x.x" to "gtest",
+ so that your folder structure looks like:
+ /talk/third_party/gtest/...
+
2.2 libjingle
Libjingle needs to be downloaded and patched
diff --git a/talk/app/webrtc/webrtc.scons b/talk/app/webrtc/webrtc.scons
index 66f73af..2394e69 100644
--- a/talk/app/webrtc/webrtc.scons
+++ b/talk/app/webrtc/webrtc.scons
@@ -21,6 +21,8 @@
env,
name = 'webrtc',
srcs = [
+ 'peerconnection_unittest.cc',
+ 'unittest_utilities.cc',
'webrtcsession_unittest.cc',
],
libs = [
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
index 72610b9..648a5d6 100644
--- a/talk/app/webrtc/webrtcsession_unittest.cc
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -31,6 +31,7 @@
#include "base/gunit.h"
#include "base/helpers.h"
+#include "talk/app/webrtc/unittest_utilities.h"
#include "talk/app/webrtc/webrtcsession.h"
#include "talk/base/fakenetwork.h"
#include "talk/base/scoped_ptr.h"
@@ -41,131 +42,6 @@
#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 {
@@ -219,9 +95,7 @@
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());
+ CopyCandidates(candidates, &last_candidates_);
}
cricket::SessionDescription* GetLocalDescription(
std::vector<cricket::Candidate>* candidates) {
@@ -231,8 +105,7 @@
if (!last_description_ptr_.get()) {
return NULL;
}
- candidates->insert(candidates->end(), last_candidates_.begin(),
- last_candidates_.end());
+ CopyCandidates(last_candidates_, candidates);
return CopySessionDescription(last_description_ptr_.get());
}
@@ -315,7 +188,6 @@
session_->SignalLocalDescription.connect(this,
&WebRtcSessionTest::OnLocalDescription);
session_->SignalFailedCall.connect(this, &WebRtcSessionTest::OnFailedCall);
-
return true;
}
@@ -512,14 +384,14 @@
std::vector<cricket::Candidate> candidates;
cricket::SessionDescription* local_session =
- GenerateFakeSession(&candidates, video);
+ GenerateFakeSession(video, &candidates);
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(CallInitiate());
+ if (!CallOnInitiateMessage(local_session, candidates)) {
+ delete local_session;
+ FAIL();
+ }
ASSERT_TRUE(CallConnect());
ASSERT_FALSE(CallbackReceived(this, 1000));
@@ -535,14 +407,14 @@
std::vector<cricket::Candidate> candidates;
cricket::SessionDescription* local_session =
- GenerateFakeSession(&candidates, video);
+ GenerateFakeSession(video, &candidates);
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(CallInitiate());
+ if (!CallOnInitiateMessage(local_session, candidates)) {
+ delete local_session;
+ FAIL();
+ }
ASSERT_TRUE(CallConnect());
ASSERT_FALSE(CallbackReceived(this, 1000));
ASSERT_TRUE(!CallHasAudioChannel() &&
diff --git a/talk/base/gunit.h b/talk/base/gunit.h
new file mode 100644
index 0000000..9d04595
--- /dev/null
+++ b/talk/base/gunit.h
@@ -0,0 +1,129 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, 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_BASE_GUNIT_H_
+#define TALK_BASE_GUNIT_H_
+
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#ifdef ANDROID
+#include <gtest/gtest.h>
+#elif LIBJINGLE_UNITTEST
+#include "third_party/gtest/include/gtest/gtest.h"
+#else
+#include "testing/base/public/gunit.h"
+#endif
+
+// forward declarations
+namespace talk_base {
+class Pathname;
+}
+
+// Wait until "ex" is true, or "timeout" expires.
+#define WAIT(ex, timeout) \
+ for (uint32 start = talk_base::Time(); \
+ !(ex) && talk_base::Time() < start + timeout;) \
+ talk_base::Thread::Current()->ProcessMessages(1);
+
+// This returns the result of the test in res, so that we don't re-evaluate
+// the expression in the XXXX_WAIT macros below, since that causes problems
+// when the expression is only true the first time you check it.
+#define WAIT_(ex, timeout, res) \
+ do { \
+ uint32 start = talk_base::Time(); \
+ res = (ex); \
+ while (!res && talk_base::Time() < start + timeout) { \
+ talk_base::Thread::Current()->ProcessMessages(1); \
+ res = (ex); \
+ } \
+ } while (0);
+
+// The typical EXPECT_XXXX and ASSERT_XXXXs, but done until true or a timeout.
+#define EXPECT_TRUE_WAIT(ex, timeout) \
+ do { \
+ bool res; \
+ WAIT_(ex, timeout, res); \
+ if (!res) EXPECT_TRUE(ex); \
+ } while (0);
+
+#define EXPECT_EQ_WAIT(v1, v2, timeout) \
+ do { \
+ bool res; \
+ WAIT_(v1 == v2, timeout, res); \
+ if (!res) EXPECT_EQ(v1, v2); \
+ } while (0);
+
+#define ASSERT_TRUE_WAIT(ex, timeout) \
+ do { \
+ bool res; \
+ WAIT_(ex, timeout, res); \
+ if (!res) ASSERT_TRUE(ex); \
+ } while (0);
+
+#define ASSERT_EQ_WAIT(v1, v2, timeout) \
+ do { \
+ bool res; \
+ WAIT_(v1 == v2, timeout, res); \
+ if (!res) ASSERT_EQ(v1, v2); \
+ } while (0);
+
+// Version with a "soft" timeout and a margin. This logs if the timeout is
+// exceeded, but it only fails if the expression still isn't true after the
+// margin time passes.
+#define EXPECT_TRUE_WAIT_MARGIN(ex, timeout, margin) \
+ do { \
+ bool res; \
+ WAIT_(ex, timeout, res); \
+ if (res) { \
+ break; \
+ } \
+ LOG(LS_WARNING) << "Expression " << #ex << " still not true after " << \
+ timeout << "ms; waiting an additional " << margin << "ms"; \
+ WAIT_(ex, margin, res); \
+ if (!res) { \
+ EXPECT_TRUE(ex); \
+ } \
+ } while (0);
+
+#ifdef __LP64__
+// When compiling in 64-bit mode we redefine the TEST and TEST_F macros to
+// append _64bit to all testsuite names to distinguish the test results from
+// those for the 32-bit cross-built binaries.
+#undef TEST
+#undef TEST_F
+// Copied and adjusted from google3/third_party/gtest/include/gtest/gtest.h
+#define TEST(test_case_name, test_name)\
+ GTEST_TEST_(test_case_name##_64bit, test_name, \
+ ::testing::Test, ::testing::internal::GetTestTypeId())
+#define TEST_F(test_fixture, test_name)\
+ GTEST_TEST_(test_fixture##_64bit, test_name, test_fixture, \
+ ::testing::internal::GetTypeId<test_fixture>())
+#endif
+
+talk_base::Pathname GetTalkDirectory();
+
+#endif // TALK_BASE_GUNIT_H_
diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc
new file mode 100644
index 0000000..6a9919d
--- /dev/null
+++ b/talk/base/network_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright 2009 Google Inc.
+// All Rights Reserved.
+//
+
+
+#include <vector>
+#include "talk/base/gunit.h"
+#include "talk/base/network.h"
+
+namespace talk_base {
+
+// A network that should not be ignored.
+static const Network kNetwork1("test1", "Test Network Adapter 1",
+ 0x12345678, 0x12345601);
+// A network that should be ignored (IP is 0.1.0.4).
+static const Network kNetwork2("test2", "Test Network Adapter 2",
+ 0x00010004, 0x01000000);
+// A network that should not be ignored (IP is valid, but gateway is 0.0.0.1).
+// Previously, we attempted to ignore networks with no default gateway,
+// but if an explicit route is set, no default gateway is needed.
+static const Network kNetwork3("test3", "Test Network Adapter 3",
+ 0x55667788, 0x01000000);
+
+class NetworkTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ NetworkTest()
+ : callback_called_(false) {
+ }
+
+ void OnNetworksChanged() {
+ callback_called_ = true;
+ }
+
+ void MergeNetworkList(BasicNetworkManager& network_manager,
+ const NetworkManager::NetworkList& list,
+ bool force_notification) {
+ network_manager.MergeNetworkList(list, force_notification);
+ }
+
+ bool IsIgnoredNetwork(const Network& network) {
+ return BasicNetworkManager::IsIgnoredNetwork(network);
+ }
+
+ protected:
+ bool callback_called_;
+};
+
+// Test that the Network ctor works properly.
+TEST_F(NetworkTest, TestNetworkConstruct) {
+ EXPECT_EQ("test1", kNetwork1.name());
+ EXPECT_EQ("Test Network Adapter 1", kNetwork1.description());
+ EXPECT_EQ(0x12345678U, kNetwork1.ip());
+ EXPECT_EQ(0x12345601U, kNetwork1.gateway_ip());
+ EXPECT_FALSE(kNetwork1.ignored());
+}
+
+// Tests that our ignore function works properly.
+TEST_F(NetworkTest, TestNetworkIgnore) {
+ EXPECT_FALSE(IsIgnoredNetwork(kNetwork1));
+ EXPECT_TRUE(IsIgnoredNetwork(kNetwork2));
+ EXPECT_FALSE(IsIgnoredNetwork(kNetwork3));
+}
+
+// Test that UpdateNetworks succeeds.
+TEST_F(NetworkTest, TestUpdateNetworks) {
+ BasicNetworkManager manager;
+ manager.SignalNetworksChanged.connect(
+ static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+ manager.StartUpdating();
+ Thread::Current()->ProcessMessages(0);
+ EXPECT_TRUE(callback_called_);
+}
+
+// Verify that MergeNetworkList() merges network lists properly.
+TEST_F(NetworkTest, TestMergeNetworkList) {
+ BasicNetworkManager manager;
+ manager.SignalNetworksChanged.connect(
+ static_cast<NetworkTest*>(this), &NetworkTest::OnNetworksChanged);
+
+ // Add kNetwork1 to the list of networks.
+ NetworkManager::NetworkList list;
+ list.push_back(new Network(kNetwork1));
+ callback_called_ = false;
+ MergeNetworkList(manager, list, false);
+ EXPECT_TRUE(callback_called_);
+ list.clear();
+
+ manager.GetNetworks(&list);
+ EXPECT_EQ(1U, list.size());
+ EXPECT_EQ(kNetwork1.ToString(), list[0]->ToString());
+ Network* net1 = list[0];
+ list.clear();
+
+ // Replace kNetwork1 with kNetwork2.
+ list.push_back(new Network(kNetwork2));
+ callback_called_ = false;
+ MergeNetworkList(manager, list, false);
+ EXPECT_TRUE(callback_called_);
+ list.clear();
+
+ manager.GetNetworks(&list);
+ EXPECT_EQ(1U, list.size());
+ EXPECT_EQ(kNetwork2.ToString(), list[0]->ToString());
+ Network* net2 = list[0];
+ list.clear();
+
+ // Add Network2 back.
+ list.push_back(new Network(kNetwork1));
+ list.push_back(new Network(kNetwork2));
+ callback_called_ = false;
+ MergeNetworkList(manager, list, false);
+ EXPECT_TRUE(callback_called_);
+ list.clear();
+
+ // Verify that we get previous instances of Network objects.
+ manager.GetNetworks(&list);
+ EXPECT_EQ(2U, list.size());
+ EXPECT_TRUE((net1 == list[0] && net2 == list[1]) ||
+ (net1 == list[1] && net2 == list[0]));
+ list.clear();
+
+ // Call MergeNetworkList() again and verify that we don't get update
+ // notification.
+ list.push_back(new Network(kNetwork2));
+ list.push_back(new Network(kNetwork1));
+ callback_called_ = false;
+ MergeNetworkList(manager, list, false);
+ EXPECT_FALSE(callback_called_);
+ list.clear();
+
+ // Verify that we get previous instances of Network objects.
+ manager.GetNetworks(&list);
+ EXPECT_EQ(2U, list.size());
+ EXPECT_TRUE((net1 == list[0] && net2 == list[1]) ||
+ (net1 == list[1] && net2 == list[0]));
+ list.clear();
+}
+
+// Test that DumpNetworks works.
+TEST_F(NetworkTest, TestDumpNetworks) {
+ BasicNetworkManager::DumpNetworks(true);
+}
+
+} // namespace talk_base
diff --git a/talk/base/unittest_main.cc b/talk/base/unittest_main.cc
new file mode 100644
index 0000000..56fbc54
--- /dev/null
+++ b/talk/base/unittest_main.cc
@@ -0,0 +1,119 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+
+// juberti@google.com (Justin Uberti)
+//
+// A reuseable entry point for gunit tests.
+
+#ifdef WIN32
+#include <crtdbg.h>
+#endif
+
+#include "talk/base/flags.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+
+DEFINE_bool(help, false, "prints this message");
+DEFINE_string(log, "", "logging options to use");
+#ifdef WIN32
+DEFINE_int(crt_break_alloc, -1, "memory allocation to break on");
+DEFINE_bool(default_error_handlers, false,
+ "leave the default exception/dbg handler functions in place");
+
+void TestInvalidParameterHandler(const wchar_t* expression,
+ const wchar_t* function,
+ const wchar_t* file,
+ unsigned int line,
+ uintptr_t pReserved) {
+ LOG(LS_ERROR) << "InvalidParameter Handler called. Exiting.";
+ LOG(LS_ERROR) << expression << std::endl << function << std::endl << file
+ << std::endl << line;
+ exit(1);
+}
+void TestPureCallHandler() {
+ LOG(LS_ERROR) << "Purecall Handler called. Exiting.";
+ exit(1);
+}
+int TestCrtReportHandler(int report_type, char* msg, int* retval) {
+ LOG(LS_ERROR) << "CrtReport Handler called...";
+ LOG(LS_ERROR) << msg;
+ if (report_type == _CRT_ASSERT) {
+ exit(1);
+ } else {
+ *retval = 0;
+ return TRUE;
+ }
+}
+#endif // WIN32
+
+talk_base::Pathname GetTalkDirectory() {
+ // Locate talk directory.
+ talk_base::Pathname path = talk_base::Filesystem::GetCurrentDirectory();
+ std::string talk_folder_name("talk");
+ talk_folder_name += path.folder_delimiter();
+ while (path.folder_name() != talk_folder_name && !path.empty()) {
+ path.SetFolder(path.parent_folder());
+ }
+
+ // If not running inside "talk" folder, then assume running in its parent
+ // folder.
+ if (path.empty()) {
+ path = talk_base::Filesystem::GetCurrentDirectory();
+ path.AppendFolder("talk");
+ // Make sure the folder exist.
+ if (!talk_base::Filesystem::IsFolder(path)) {
+ path.clear();
+ }
+ }
+ return path;
+}
+
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ FlagList::SetFlagsFromCommandLine(&argc, argv, false);
+ if (FLAG_help) {
+ FlagList::Print(NULL, false);
+ return 0;
+ }
+
+#ifdef WIN32
+ if (!FLAG_default_error_handlers) {
+ // Make sure any errors don't throw dialogs hanging the test run.
+ _set_invalid_parameter_handler(TestInvalidParameterHandler);
+ _set_purecall_handler(TestPureCallHandler);
+ _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, TestCrtReportHandler);
+ }
+
+#ifdef _DEBUG // Turn on memory leak checking on Windows.
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF |_CRTDBG_LEAK_CHECK_DF);
+ if (FLAG_crt_break_alloc >= 0) {
+ _crtBreakAlloc = FLAG_crt_break_alloc;
+ }
+#endif // _DEBUG
+#endif // WIN32
+
+ talk_base::Filesystem::SetOrganizationName("google");
+ talk_base::Filesystem::SetApplicationName("unittest");
+
+ // By default, log timestamps. Allow overrides by used of a --log flag.
+ talk_base::LogMessage::LogTimestamps();
+ if (*FLAG_log != '\0') {
+ talk_base::LogMessage::ConfigureLogging(FLAG_log, "unittest.log");
+ }
+
+ int res = RUN_ALL_TESTS();
+
+ // clean up logging so we don't appear to leak memory.
+ talk_base::LogMessage::ConfigureLogging("", "");
+
+#ifdef WIN32
+ // Unhook crt function so that we don't ever log after statics have been
+ // uninitialized.
+ if (!FLAG_default_error_handlers)
+ _CrtSetReportHook2(_CRT_RPTHOOK_REMOVE, TestCrtReportHandler);
+#endif
+
+ return res;
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index 8b9a834..fca401e 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -54,6 +54,7 @@
#include "talk/session/phone/mediasessionclient.h"
#include "talk/session/phone/videorendererfactory.h"
#include "talk/xmpp/constants.h"
+#include "talk/xmpp/mucroomconfigtask.h"
#include "talk/xmpp/mucroomlookuptask.h"
namespace {
@@ -696,7 +697,8 @@
}
buzz::MucRoomLookupTask* lookup_query_task =
- new buzz::MucRoomLookupTask(xmpp_client_, room, domain);
+ new buzz::MucRoomLookupTask(
+ xmpp_client_, buzz::JID_GOOGLE_MUC_LOOKUP, room, domain);
lookup_query_task->SignalResult.connect(this,
&CallClient::OnRoomLookupResponse);
lookup_query_task->SignalError.connect(this,
@@ -739,11 +741,35 @@
presence_out_->SendDirected(muc->local_jid(), my_status_);
}
-void CallClient::OnRoomLookupResponse(const buzz::MucRoomInfo& room_info) {
- JoinMuc(room_info.room_jid);
+void CallClient::OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
+ const buzz::MucRoomInfo& room) {
+ // The server requires the room be "configured" before being used.
+ // We only need to configure it if we create it, but rooms are
+ // auto-created at lookup, so there's currently no way to know if we
+ // created it. So, we configure it every time, just in case.
+ // Luckily, it appears to be safe to configure a room that's already
+ // configured. Our current flow is:
+ // 1. Lookup/auto-create
+ // 2. Configure
+ // 3. Join
+ // TODO: In the future, once the server supports it, we
+ // should:
+ // 1. Lookup
+ // 2. Create and Configure if necessary
+ // 3. Join
+ std::vector<std::string> room_features;
+ room_features.push_back(buzz::STR_MUC_ROOM_FEATURE_ENTERPRISE);
+ buzz::MucRoomConfigTask* room_config_task = new buzz::MucRoomConfigTask(
+ xmpp_client_, room.jid, room.full_name(), room_features);
+ room_config_task->SignalResult.connect(this,
+ &CallClient::OnRoomConfigResult);
+ room_config_task->SignalError.connect(this,
+ &CallClient::OnRoomConfigError);
+ room_config_task->Start();
}
-void CallClient::OnRoomLookupError(const buzz::XmlElement* stanza) {
+void CallClient::OnRoomLookupError(buzz::IqTask* task,
+ const buzz::XmlElement* stanza) {
if (stanza == NULL) {
console_->PrintLine("Room lookup failed.");
} else {
@@ -751,6 +777,19 @@
}
}
+void CallClient::OnRoomConfigResult(buzz::MucRoomConfigTask* task) {
+ JoinMuc(task->room_jid());
+}
+
+void CallClient::OnRoomConfigError(buzz::IqTask* task,
+ const buzz::XmlElement* stanza) {
+ if (stanza == NULL) {
+ console_->PrintLine("Room config failed.");
+ } else {
+ console_->PrintLine("Room config error: ", stanza->Str().c_str());
+ }
+}
+
void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
const buzz::Jid& room,
const std::vector<buzz::AvailableMediaEntry>& avail) {
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index fb7878e..18296be 100644
--- a/talk/examples/call/callclient.h
+++ b/talk/examples/call/callclient.h
@@ -50,6 +50,8 @@
class DiscoInfoQueryTask;
class Muc;
class Status;
+class IqTask;
+class MucRoomConfigTask;
class MucRoomLookupTask;
class MucStatus;
class XmlElement;
@@ -174,8 +176,13 @@
void OnSpeakerChanged(cricket::Call* call,
cricket::Session* session,
const cricket::NamedSource& speaker_source);
- void OnRoomLookupResponse(const buzz::MucRoomInfo& room_info);
- void OnRoomLookupError(const buzz::XmlElement* stanza);
+ void OnRoomLookupResponse(buzz::MucRoomLookupTask* task,
+ const buzz::MucRoomInfo& room_info);
+ void OnRoomLookupError(buzz::IqTask* task,
+ const buzz::XmlElement* stanza);
+ void OnRoomConfigResult(buzz::MucRoomConfigTask* task);
+ void OnRoomConfigError(buzz::IqTask* task,
+ const buzz::XmlElement* stanza);
buzz::Jid GenerateRandomMucJid();
void AddStaticRenderedView(
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index 03239d4..e053a8f 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -20,6 +20,18 @@
"HAVE_EXPAT_CONFIG_H",
],
)
+talk.Library(env, name = "gunit",
+ srcs = [
+ "third_party/gtest/src/gtest-all.cc",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/gtest",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ ],
+)
talk.Library(env, name = "srtp",
srcs = [
"third_party/srtp/crypto/cipher/aes.c",
@@ -63,6 +75,8 @@
"sound/alsasoundsystem.cc",
"sound/alsasymboltable.cc",
"sound/linuxsoundsystem.cc",
+ "sound/nullsoundsystem.cc",
+ "sound/nullsoundsystemfactory.cc",
"sound/pulseaudiosoundsystem.cc",
"sound/pulseaudiosymboltable.cc",
"sound/platformsoundsystem.cc",
@@ -212,6 +226,7 @@
"xmpp/constants.cc",
"xmpp/iqtask.cc",
"xmpp/jid.cc",
+ "xmpp/mucroomconfigtask.cc",
"xmpp/mucroomlookuptask.cc",
"xmpp/ratelimitmanager.cc",
"xmpp/saslmechanism.cc",
@@ -262,6 +277,21 @@
"gtk+-2.0",
],
)
+talk.Library(env, name = "unittest_main",
+ libs = [
+ "gunit",
+ ],
+ srcs = [
+ "base/unittest_main.cc",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/gtest",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ ],
+)
talk.App(env, name = "login",
libs = [
"jingle",
@@ -343,3 +373,18 @@
"p2p/base/stunserver_main.cc",
],
)
+talk.Unittest(env, name = "network",
+ libs = [
+ "jingle",
+ ],
+ srcs = [
+ "base/network_unittest.cc",
+ ],
+ includedirs = [
+ "third_party/gtest/include",
+ "third_party/gtest",
+ ],
+ cppdefines = [
+ "LIBJINGLE_UNITTEST",
+ ],
+)
diff --git a/talk/session/phone/channelmanager.cc b/talk/session/phone/channelmanager.cc
index 0d11b53..97419cb 100644
--- a/talk/session/phone/channelmanager.cc
+++ b/talk/session/phone/channelmanager.cc
@@ -654,6 +654,8 @@
void ChannelManager::OnVideoCaptureResult(VideoCapturer* capturer,
CaptureResult result) {
+ // TODO: Check capturer and signal failure only for camera video, not
+ // screencast.
capturing_ = result == CR_SUCCESS;
main_thread_->Post(this, MSG_CAMERASTARTED,
new talk_base::TypedMessageData<CaptureResult>(result));
diff --git a/talk/session/phone/mediacommon.h b/talk/session/phone/mediacommon.h
new file mode 100644
index 0000000..7a606e3
--- /dev/null
+++ b/talk/session/phone/mediacommon.h
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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_SESSION_PHONE_MEDIACOMMON_H_
+#define TALK_SESSION_PHONE_MEDIACOMMON_H_
+
+namespace cricket {
+
+enum MediaCapabilities {
+ AUDIO_RECV = 1 << 0,
+ AUDIO_SEND = 1 << 1,
+ VIDEO_RECV = 1 << 2,
+ VIDEO_SEND = 1 << 3,
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MEDIACOMMON_H_
diff --git a/talk/session/phone/webrtccommon.h b/talk/session/phone/webrtccommon.h
new file mode 100644
index 0000000..60fee27
--- /dev/null
+++ b/talk/session/phone/webrtccommon.h
@@ -0,0 +1,84 @@
+/*
+ * 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_SESSION_PHONE_WEBRTCCOMMON_H_
+#define TALK_SESSION_PHONE_WEBRTCCOMMON_H_
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "common_types.h"
+#include "video_engine/main/interface/vie_base.h"
+#include "voice_engine/main/interface/voe_base.h"
+#else
+#include "third_party/webrtc/files/include/common_types.h"
+#include "third_party/webrtc/files/include/voe_base.h"
+#include "third_party/webrtc/files/include/vie_base.h"
+#endif // WEBRTC_RELATIVE_PATH
+
+namespace cricket {
+
+// Tracing helpers, for easy logging when WebRTC calls fail.
+// Example: "LOG_RTCERR1(StartSend, channel);" produces the trace
+// "StartSend(1) failed, err=XXXX"
+// The method GetLastEngineError must be defined in the calling scope.
+#define LOG_RTCERR0(func) \
+ LOG_RTCERR0_EX(func, GetLastEngineError())
+#define LOG_RTCERR1(func, a1) \
+ LOG_RTCERR1_EX(func, a1, GetLastEngineError())
+#define LOG_RTCERR2(func, a1, a2) \
+ LOG_RTCERR2_EX(func, a1, a2, GetLastEngineError())
+#define LOG_RTCERR3(func, a1, a2, a3) \
+ LOG_RTCERR3_EX(func, a1, a2, a3, GetLastEngineError())
+#define LOG_RTCERR4(func, a1, a2, a3, a4) \
+ LOG_RTCERR4_EX(func, a1, a2, a3, a4, GetLastEngineError())
+#define LOG_RTCERR5(func, a1, a2, a3, a4, a5) \
+ LOG_RTCERR5_EX(func, a1, a2, a3, a4, a5, GetLastEngineError())
+#define LOG_RTCERR6(func, a1, a2, a3, a4, a5, a6) \
+ LOG_RTCERR6_EX(func, a1, a2, a3, a4, a5, a6, GetLastEngineError())
+#define LOG_RTCERR0_EX(func, err) LOG(LS_WARNING) \
+ << "" << #func << "() failed, err=" << err
+#define LOG_RTCERR1_EX(func, a1, err) LOG(LS_WARNING) \
+ << "" << #func << "(" << a1 << ") failed, err=" << err
+#define LOG_RTCERR2_EX(func, a1, a2, err) LOG(LS_WARNING) \
+ << "" << #func << "(" << a1 << ", " << a2 << ") failed, err=" \
+ << err
+#define LOG_RTCERR3_EX(func, a1, a2, a3, err) LOG(LS_WARNING) \
+ << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+ << ") failed, err=" << err
+#define LOG_RTCERR4_EX(func, a1, a2, a3, a4, err) LOG(LS_WARNING) \
+ << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+ << ", " << a4 << ") failed, err=" << err
+#define LOG_RTCERR5_EX(func, a1, a2, a3, a4, a5, err) LOG(LS_WARNING) \
+ << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+ << ", " << a4 << ", " << a5 << ") failed, err=" << err
+#define LOG_RTCERR6_EX(func, a1, a2, a3, a4, a5, a6, err) LOG(LS_WARNING) \
+ << "" << #func << "(" << a1 << ", " << a2 << ", " << a3 \
+ << ", " << a4 << ", " << a5 << ", " << a6 << ") failed, err=" << err
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_WEBRTCCOMMON_H_
diff --git a/talk/session/phone/webrtcpassthroughrender.cc b/talk/session/phone/webrtcpassthroughrender.cc
new file mode 100644
index 0000000..cecaa42
--- /dev/null
+++ b/talk/session/phone/webrtcpassthroughrender.cc
@@ -0,0 +1,135 @@
+/*
+ * 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/session/phone/webrtcpassthroughrender.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+class PassthroughStream: public webrtc::VideoRenderCallback {
+ public:
+ explicit PassthroughStream(const WebRtc_UWord32 stream_id)
+ : stream_id_(stream_id) {
+ }
+ virtual ~PassthroughStream() {
+ }
+ virtual WebRtc_Word32 RenderFrame(const WebRtc_UWord32 stream_id,
+ webrtc::VideoFrame& videoFrame) {
+ talk_base::CritScope cs(&stream_critical_);
+ // Send frame for rendering directly
+ if (renderer_) {
+ renderer_->RenderFrame(stream_id, videoFrame);
+ }
+ return 0;
+ }
+ WebRtc_Word32 SetRenderer(VideoRenderCallback* renderer) {
+ talk_base::CritScope cs(&stream_critical_);
+ renderer_ = renderer;
+ return 0;
+ }
+
+ private:
+ WebRtc_UWord32 stream_id_;
+ VideoRenderCallback* renderer_;
+ talk_base::CriticalSection stream_critical_;
+};
+
+WebRtcPassthroughRender::WebRtcPassthroughRender()
+ : window_(NULL) {
+}
+
+WebRtcPassthroughRender::~WebRtcPassthroughRender() {
+ while (!stream_render_map_.empty()) {
+ PassthroughStream* stream = stream_render_map_.begin()->second;
+ stream_render_map_.erase(stream_render_map_.begin());
+ delete stream;
+ }
+}
+
+webrtc::VideoRenderCallback* WebRtcPassthroughRender::AddIncomingRenderStream(
+ const WebRtc_UWord32 stream_id,
+ const WebRtc_UWord32 zOrder,
+ const float left, const float top,
+ const float right, const float bottom) {
+ talk_base::CritScope cs(&render_critical_);
+ StreamMap::iterator it;
+ it = stream_render_map_.find(stream_id);
+ if (it != stream_render_map_.end())
+ return NULL;
+
+ PassthroughStream* stream = new PassthroughStream(stream_id);
+ // Store the stream
+ stream_render_map_[stream_id] = stream;
+ return stream;
+}
+
+WebRtc_Word32 WebRtcPassthroughRender::DeleteIncomingRenderStream(
+ const WebRtc_UWord32 stream_id) {
+ talk_base::CritScope cs(&render_critical_);
+ StreamMap::iterator it;
+ it = stream_render_map_.find(stream_id);
+ if (it == stream_render_map_.end()) {
+ LOG(LS_WARNING) << "DeleteIncomingRenderStream failed to find stream_id: "
+ << stream_id;
+ return -1;
+ }
+ PassthroughStream* stream = it->second;
+ delete stream;
+ stream_render_map_.erase(it);
+ return 0;
+}
+
+WebRtc_Word32 WebRtcPassthroughRender::AddExternalRenderCallback(
+ const WebRtc_UWord32 stream_id,
+ webrtc::VideoRenderCallback* render_object) {
+ talk_base::CritScope cs(&render_critical_);
+ StreamMap::iterator it;
+ it = stream_render_map_.find(stream_id);
+ if (it == stream_render_map_.end()) {
+ LOG(LS_WARNING) << "AddExternalRenderCallback failed to find stream_id: "
+ << stream_id;
+ return -1;
+ }
+ PassthroughStream* stream = it->second;
+ ASSERT(stream != NULL);
+ return stream->SetRenderer(render_object);
+}
+
+bool WebRtcPassthroughRender::HasIncomingRenderStream(
+ const WebRtc_UWord32 stream_id) const {
+ StreamMap::const_iterator it;
+ it = stream_render_map_.find(stream_id);
+ return (it != stream_render_map_.end());
+}
+
+webrtc::RawVideoType WebRtcPassthroughRender::PreferredVideoType() const {
+ return webrtc::kVideoI420;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/webrtcpassthroughrender.h b/talk/session/phone/webrtcpassthroughrender.h
new file mode 100644
index 0000000..8d2ae90
--- /dev/null
+++ b/talk/session/phone/webrtcpassthroughrender.h
@@ -0,0 +1,211 @@
+/*
+ * 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_SESSION_PHONE_WEBRTCPASSTHROUGHRENDER_H_
+#define TALK_SESSION_PHONE_WEBRTCPASSTHROUGHRENDER_H_
+
+#include <map>
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "modules/video_render/main/interface/video_render.h"
+#else
+#include "third_party/webrtc/files/include/video_render.h"
+#endif
+#include "talk/base/criticalsection.h"
+
+namespace cricket {
+class PassthroughStream;
+
+class WebRtcPassthroughRender : public webrtc::VideoRender {
+ public:
+ WebRtcPassthroughRender();
+ virtual ~WebRtcPassthroughRender();
+
+ virtual WebRtc_Word32 Version(WebRtc_Word8* version,
+ WebRtc_UWord32& remainingBufferInBytes,
+ WebRtc_UWord32& position) const {
+ return 0;
+ }
+
+ virtual WebRtc_Word32 ChangeUniqueId(const WebRtc_Word32 id) {
+ return 0;
+ }
+
+ virtual WebRtc_Word32 TimeUntilNextProcess() { return 0; }
+
+ virtual WebRtc_Word32 Process() { return 0; }
+
+ virtual void* Window() {
+ talk_base::CritScope cs(&render_critical_);
+ return window_;
+ }
+
+ virtual WebRtc_Word32 ChangeWindow(void* window) {
+ talk_base::CritScope cs(&render_critical_);
+ window_ = window;
+ return 0;
+ }
+
+ virtual webrtc::VideoRenderCallback* AddIncomingRenderStream(
+ const WebRtc_UWord32 stream_id,
+ const WebRtc_UWord32 zOrder,
+ const float left, const float top,
+ const float right, const float bottom);
+
+ virtual WebRtc_Word32 DeleteIncomingRenderStream(
+ const WebRtc_UWord32 stream_id);
+
+ virtual WebRtc_Word32 AddExternalRenderCallback(
+ const WebRtc_UWord32 stream_id,
+ webrtc::VideoRenderCallback* render_object);
+
+ virtual WebRtc_Word32 GetIncomingRenderStreamProperties(
+ const WebRtc_UWord32 stream_id,
+ WebRtc_UWord32& zOrder,
+ float& left, float& top,
+ float& right, float& bottom) const {
+ return -1;
+ }
+
+ virtual WebRtc_UWord32 GetIncomingFrameRate(
+ const WebRtc_UWord32 stream_id) {
+ return 0;
+ }
+
+ virtual WebRtc_UWord32 GetNumIncomingRenderStreams() const {
+ return stream_render_map_.size();
+ }
+
+ virtual bool HasIncomingRenderStream(const WebRtc_UWord32 stream_id) const;
+
+ virtual WebRtc_Word32 RegisterRawFrameCallback(
+ const WebRtc_UWord32 stream_id,
+ webrtc::VideoRenderCallback* callback_obj) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 GetLastRenderedFrame(
+ const WebRtc_UWord32 stream_id,
+ webrtc::VideoFrame &frame) const {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 StartRender(
+ const WebRtc_UWord32 stream_id) { return 0; }
+
+ virtual WebRtc_Word32 StopRender(
+ const WebRtc_UWord32 stream_id) { return 0; }
+
+ virtual WebRtc_Word32 ResetRender() { return 0; }
+
+ virtual webrtc::RawVideoType PreferredVideoType() const;
+
+ virtual bool IsFullScreen() { return false; }
+
+ virtual WebRtc_Word32 GetScreenResolution(
+ WebRtc_UWord32& screenWidth,
+ WebRtc_UWord32& screenHeight) const {
+ return -1;
+ }
+
+ virtual WebRtc_UWord32 RenderFrameRate(
+ const WebRtc_UWord32 stream_id) {
+ return 0;
+ }
+
+ virtual WebRtc_Word32 SetStreamCropping(
+ const WebRtc_UWord32 stream_id,
+ const float left, const float top,
+ const float right,
+ const float bottom) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 ConfigureRenderer(
+ const WebRtc_UWord32 stream_id,
+ const unsigned int zOrder,
+ const float left, const float top,
+ const float right,
+ const float bottom) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 SetTransparentBackground(const bool enable) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 FullScreenRender(void* window, const bool enable) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 SetBitmap(const void* bitMap,
+ const WebRtc_UWord8 pictureId, const void* colorKey,
+ const float left, const float top,
+ const float right, const float bottom) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 SetText(const WebRtc_UWord8 textId,
+ const WebRtc_UWord8* text,
+ const WebRtc_Word32 textLength,
+ const WebRtc_UWord32 textColorRef,
+ const WebRtc_UWord32 backgroundColorRef,
+ const float left, const float top,
+ const float right, const float bottom) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 SetStartImage(
+ const WebRtc_UWord32 stream_id,
+ const webrtc::VideoFrame& videoFrame) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 SetTimeoutImage(
+ const WebRtc_UWord32 stream_id,
+ const webrtc::VideoFrame& videoFrame,
+ const WebRtc_UWord32 timeout) {
+ return -1;
+ }
+
+ virtual WebRtc_Word32 MirrorRenderStream(const int renderId,
+ const bool enable,
+ const bool mirrorXAxis,
+ const bool mirrorYAxis) {
+ return -1;
+ }
+
+ private:
+ typedef std::map<WebRtc_UWord32, PassthroughStream*> StreamMap;
+
+ void* window_;
+ StreamMap stream_render_map_;
+ talk_base::CriticalSection render_critical_;
+};
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_WEBRTCPASSTHROUGHRENDER_H_
diff --git a/talk/session/phone/webrtcpassthroughrender_unittest.cc b/talk/session/phone/webrtcpassthroughrender_unittest.cc
new file mode 100644
index 0000000..b33d38b
--- /dev/null
+++ b/talk/session/phone/webrtcpassthroughrender_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright 2008 Google Inc. All Rights Reserved,
+//
+// Author: Ronghua Wu (ronghuawu@google.com)
+
+#include <string>
+
+#include "talk/base/gunit.h"
+#include "talk/session/phone/webrtcpassthroughrender.h"
+#include "talk/session/phone/testutils.h"
+
+class WebRtcPassthroughRenderTest : public testing::Test {
+ public:
+ class ExternalRenderer : public webrtc::VideoRenderCallback {
+ public:
+ ExternalRenderer() : frame_num_(0) {
+ }
+
+ virtual ~ExternalRenderer() {
+ }
+
+ virtual WebRtc_Word32 RenderFrame(
+ const WebRtc_UWord32 stream_id,
+ webrtc::VideoFrame& videoFrame) {
+ ++frame_num_;
+ LOG(INFO) << "RenderFrame stream_id: " << stream_id
+ << " frame_num: " << frame_num_;
+ return 0;
+ }
+
+ int frame_num() const {
+ return frame_num_;
+ }
+
+ private:
+ int frame_num_;
+ };
+
+ WebRtcPassthroughRenderTest()
+ : renderer_(new cricket::WebRtcPassthroughRender()) {
+ }
+
+ ~WebRtcPassthroughRenderTest() {
+ }
+
+ webrtc::VideoRenderCallback* AddIncomingRenderStream(int stream_id) {
+ return renderer_->AddIncomingRenderStream(stream_id, 0, 0, 0, 0, 0);
+ }
+
+ bool HasIncomingRenderStream(int stream_id) {
+ return renderer_->HasIncomingRenderStream(stream_id);
+ }
+
+ bool DeleteIncomingRenderStream(int stream_id) {
+ return (renderer_->DeleteIncomingRenderStream(stream_id) == 0);
+ }
+
+ bool AddExternalRenderCallback(int stream_id,
+ webrtc::VideoRenderCallback* renderer) {
+ return (renderer_->AddExternalRenderCallback(stream_id, renderer) == 0);
+ }
+
+ private:
+ talk_base::scoped_ptr<cricket::WebRtcPassthroughRender> renderer_;
+};
+
+TEST_F(WebRtcPassthroughRenderTest, Streams) {
+ const int stream_id1 = 1234;
+ const int stream_id2 = 5678;
+ webrtc::VideoRenderCallback* stream = NULL;
+ // Add a new stream
+ stream = AddIncomingRenderStream(stream_id1);
+ EXPECT_TRUE(stream != NULL);
+ EXPECT_TRUE(HasIncomingRenderStream(stream_id1));
+ // Tried to add a already existed stream should return null
+ stream =AddIncomingRenderStream(stream_id1);
+ EXPECT_TRUE(stream == NULL);
+ stream = AddIncomingRenderStream(stream_id2);
+ EXPECT_TRUE(stream != NULL);
+ EXPECT_TRUE(HasIncomingRenderStream(stream_id2));
+ // Remove the stream
+ EXPECT_TRUE(DeleteIncomingRenderStream(stream_id2));
+ EXPECT_TRUE(!HasIncomingRenderStream(stream_id2));
+ // Add back the removed stream
+ stream = AddIncomingRenderStream(stream_id2);
+ EXPECT_TRUE(stream != NULL);
+ EXPECT_TRUE(HasIncomingRenderStream(stream_id2));
+}
+
+TEST_F(WebRtcPassthroughRenderTest, Renderer) {
+ webrtc::VideoFrame frame;
+ const int stream_id1 = 1234;
+ const int stream_id2 = 5678;
+ webrtc::VideoRenderCallback* stream1 = NULL;
+ webrtc::VideoRenderCallback* stream2 = NULL;
+ // Add two new stream
+ stream1 = AddIncomingRenderStream(stream_id1);
+ EXPECT_TRUE(stream1 != NULL);
+ EXPECT_TRUE(HasIncomingRenderStream(stream_id1));
+ stream2 = AddIncomingRenderStream(stream_id2);
+ EXPECT_TRUE(stream2 != NULL);
+ EXPECT_TRUE(HasIncomingRenderStream(stream_id2));
+ // Register the external renderer
+ WebRtcPassthroughRenderTest::ExternalRenderer renderer1;
+ WebRtcPassthroughRenderTest::ExternalRenderer renderer2;
+ AddExternalRenderCallback(stream_id1, &renderer1);
+ AddExternalRenderCallback(stream_id2, &renderer2);
+ int test_frame_num = 10;
+ for (int i = 0; i < test_frame_num; ++i) {
+ stream1->RenderFrame(stream_id1, frame);
+ }
+ EXPECT_EQ(test_frame_num, renderer1.frame_num());
+ test_frame_num = 30;
+ for (int i = 0; i < test_frame_num; ++i) {
+ stream2->RenderFrame(stream_id2, frame);
+ }
+ EXPECT_EQ(test_frame_num, renderer2.frame_num());
+}
diff --git a/talk/session/phone/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
new file mode 100644
index 0000000..6f9ff6a
--- /dev/null
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -0,0 +1,1003 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_WEBRTC_VIDEO
+
+#include "talk/session/phone/webrtcvideoengine.h"
+
+#include "talk/base/common.h"
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/videorenderer.h"
+#include "talk/session/phone/webrtcpassthroughrender.h"
+#include "talk/session/phone/webrtcvoiceengine.h"
+#include "talk/session/phone/webrtcvideoframe.h"
+#include "talk/session/phone/webrtcvie.h"
+#include "talk/session/phone/webrtcvoe.h"
+
+namespace cricket {
+
+static const int kDefaultLogSeverity = talk_base::LS_WARNING;
+static const int kStartVideoBitrate = 300;
+static const int kMaxVideoBitrate = 1000;
+
+class WebRtcRenderAdapter : public webrtc::ExternalRenderer {
+ public:
+ explicit WebRtcRenderAdapter(VideoRenderer* renderer)
+ : renderer_(renderer) {
+ }
+
+ virtual int FrameSizeChange(unsigned int width, unsigned int height,
+ unsigned int /*number_of_streams*/) {
+ if (renderer_ == NULL)
+ return 0;
+ width_ = width;
+ height_ = height;
+ return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
+ }
+
+ virtual int DeliverFrame(unsigned char* buffer, int buffer_size) {
+ if (renderer_ == NULL)
+ return 0;
+ WebRtcVideoFrame video_frame;
+ // TODO: Currently by the time DeliverFrame got called,
+ // ViE expects the frame will be rendered ASAP. However, the libjingle
+ // renderer may have its own internal delays. Can you disable the buffering
+ // inside ViE and surface the timing information to this callback?
+ video_frame.Attach(buffer, buffer_size, width_, height_, 0, 0);
+ int ret = renderer_->RenderFrame(&video_frame) ? 0 : -1;
+ uint8* buffer_temp;
+ size_t buffer_size_temp;
+ video_frame.Detach(&buffer_temp, &buffer_size_temp);
+ return ret;
+ }
+
+ virtual ~WebRtcRenderAdapter() {}
+
+ private:
+ VideoRenderer* renderer_;
+ unsigned int width_;
+ unsigned int height_;
+};
+
+const WebRtcVideoEngine::VideoCodecPref
+ WebRtcVideoEngine::kVideoCodecPrefs[] = {
+ {"VP8", 120, 0},
+};
+
+// The formats are sorted by the descending order of width. We use the order to
+// find the next format for CPU and bandwidth adaptation.
+const VideoFormat WebRtcVideoEngine::kVideoFormats[] = {
+ // TODO: Understand why we have problem with 16:9 formats.
+ VideoFormat(1280, 800, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+//VideoFormat(1280, 720, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+ VideoFormat(960, 600, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+//VideoFormat(960, 540, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+ VideoFormat(640, 400, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+//VideoFormat(640, 360, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+ VideoFormat(480, 300, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+//VideoFormat(480, 270, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+ VideoFormat(320, 200, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+//VideoFormat(320, 180, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+ VideoFormat(240, 150, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+//VideoFormat(240, 135, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+ VideoFormat(160, 100, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+//VideoFormat(160, 90, VideoFormat::FpsToInterval(30), FOURCC_ANY),
+};
+
+// TODO: Understand why 640x400 is not working.
+const VideoFormat WebRtcVideoEngine::kDefaultVideoFormat =
+ VideoFormat(320, 200, VideoFormat::FpsToInterval(30), FOURCC_ANY);
+
+WebRtcVideoEngine::WebRtcVideoEngine()
+ : vie_wrapper_(new ViEWrapper()),
+ capture_module_(NULL),
+ external_capture_(false),
+ render_module_(new WebRtcPassthroughRender()),
+ voice_engine_(NULL) {
+ Construct();
+}
+
+WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+ webrtc::VideoCaptureModule* capture)
+ : vie_wrapper_(new ViEWrapper()),
+ capture_module_(capture),
+ external_capture_(true),
+ render_module_(webrtc::VideoRender::CreateVideoRender(0, NULL, false,
+ webrtc::kRenderExternal)),
+ voice_engine_(voice_engine) {
+ Construct();
+}
+
+WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+ ViEWrapper* vie_wrapper)
+ : vie_wrapper_(vie_wrapper),
+ capture_module_(NULL),
+ external_capture_(false),
+ render_module_(new WebRtcPassthroughRender()),
+ voice_engine_(voice_engine) {
+ Construct();
+}
+
+void WebRtcVideoEngine::Construct() {
+ capture_id_ = -1;
+ log_level_ = kDefaultLogSeverity;
+ capture_started_ = false;
+
+ ApplyLogging();
+ if (vie_wrapper_->engine()->SetTraceCallback(this) != 0) {
+ LOG_RTCERR1(SetTraceCallback, this);
+ }
+
+ // Set default quality levels for our supported codecs. We override them here
+ // if we know your cpu performance is low, and they can be updated explicitly
+ // by calling SetDefaultCodec. For example by a flute preference setting, or
+ // by the server with a jec in response to our reported system info.
+ VideoCodec max_codec(kVideoCodecPrefs[0].payload_type,
+ kVideoCodecPrefs[0].name,
+ kDefaultVideoFormat.width,
+ kDefaultVideoFormat.height,
+ kDefaultVideoFormat.framerate(), 0);
+ if (!SetDefaultCodec(max_codec)) {
+ LOG(LS_ERROR) << "Failed to initialize list of supported codec types";
+ }
+}
+
+WebRtcVideoEngine::~WebRtcVideoEngine() {
+ LOG(LS_INFO) << " WebRtcVideoEngine::~WebRtcVideoEngine";
+ vie_wrapper_->engine()->SetTraceCallback(NULL);
+ Terminate();
+ vie_wrapper_.reset();
+ if (capture_module_) {
+ webrtc::VideoCaptureModule::Destroy(capture_module_);
+ }
+}
+
+bool WebRtcVideoEngine::Init() {
+ LOG(LS_INFO) << "WebRtcVideoEngine::Init";
+ bool result = InitVideoEngine();
+ if (result) {
+ LOG(LS_INFO) << "VideoEngine Init done";
+ } else {
+ LOG(LS_ERROR) << "VideoEngine Init failed, releasing";
+ Terminate();
+ }
+ return result;
+}
+
+bool WebRtcVideoEngine::InitVideoEngine() {
+ LOG(LS_INFO) << "WebRtcVideoEngine::InitVideoEngine";
+
+ if (vie_wrapper_->base()->Init() != 0) {
+ LOG_RTCERR0(Init);
+ return false;
+ }
+
+ if (!voice_engine_) {
+ LOG(LS_WARNING) << "NULL voice engine";
+ } else if ((vie_wrapper_->base()->SetVoiceEngine(
+ voice_engine_->voe()->engine())) != 0) {
+ LOG_RTCERR0(SetVoiceEngine);
+ return false;
+ }
+
+ if ((vie_wrapper_->base()->RegisterObserver(*this)) != 0) {
+ LOG_RTCERR0(RegisterObserver);
+ return false;
+ }
+
+ if (vie_wrapper_->render()->RegisterVideoRenderModule(
+ *render_module_.get()) != 0) {
+ LOG_RTCERR0(RegisterVideoRenderModule);
+ return false;
+ }
+
+ std::sort(video_codecs_.begin(), video_codecs_.end(),
+ &VideoCodec::Preferable);
+ return true;
+}
+
+void WebRtcVideoEngine::PerformanceAlarm(const unsigned int cpu_load) {
+ LOG(LS_INFO) << "WebRtcVideoEngine::PerformanceAlarm";
+}
+
+// Ignore spammy trace messages, mostly from the stats API when we haven't
+// gotten RTCP info yet from the remote side.
+static bool ShouldIgnoreTrace(const std::string& trace) {
+ static const char* kTracesToIgnore[] = {
+ "\tfailed to GetReportBlockInformation",
+ NULL
+ };
+ for (const char* const* p = kTracesToIgnore; *p; ++p) {
+ if (trace.find(*p) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void WebRtcVideoEngine::Print(const webrtc::TraceLevel level,
+ const char* trace, const int length) {
+ talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
+ if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
+ sev = talk_base::LS_ERROR;
+ else if (level == webrtc::kTraceWarning)
+ sev = talk_base::LS_WARNING;
+ else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
+ sev = talk_base::LS_INFO;
+
+ if (sev >= log_level_) {
+ // Skip past boilerplate prefix text
+ if (length < 72) {
+ std::string msg(trace, length);
+ LOG(LS_ERROR) << "Malformed webrtc log message: ";
+ LOG_V(sev) << msg;
+ } else {
+ std::string msg(trace + 71, length - 72);
+ if (!ShouldIgnoreTrace(msg)) {
+ LOG_V(sev) << "WebRtc ViE:" << msg;
+ }
+ }
+ }
+}
+
+void WebRtcVideoEngine::ApplyLogging() {
+ int filter = 0;
+ switch (log_level_) {
+ case talk_base::LS_VERBOSE: filter |= webrtc::kTraceAll;
+ case talk_base::LS_INFO: filter |= webrtc::kTraceStateInfo;
+ case talk_base::LS_WARNING: filter |= webrtc::kTraceWarning;
+ case talk_base::LS_ERROR: filter |=
+ webrtc::kTraceError | webrtc::kTraceCritical;
+ }
+}
+
+// Rebuilds the codec list to be only those that are less intensive
+// than the specified codec.
+bool WebRtcVideoEngine::RebuildCodecList(const VideoCodec& in_codec) {
+ if (!FindCodec(in_codec))
+ return false;
+
+ video_codecs_.clear();
+
+ bool found = false;
+ for (size_t i = 0; i < ARRAY_SIZE(kVideoCodecPrefs); ++i) {
+ const VideoCodecPref& pref(kVideoCodecPrefs[i]);
+ if (!found)
+ found = (in_codec.name == pref.name);
+ if (found) {
+ VideoCodec codec(pref.payload_type, pref.name,
+ in_codec.width, in_codec.height, in_codec.framerate,
+ ARRAY_SIZE(kVideoCodecPrefs) - i);
+ video_codecs_.push_back(codec);
+ }
+ }
+ ASSERT(found);
+ return true;
+}
+
+void WebRtcVideoEngine::Terminate() {
+ LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
+ SetCapture(false);
+ if (local_renderer_.get()) {
+ // If the renderer already set, stop it first
+ if (vie_wrapper_->render()->StopRender(capture_id_) != 0)
+ LOG_RTCERR1(StopRender, capture_id_);
+ }
+
+ if (vie_wrapper_->render()->DeRegisterVideoRenderModule(
+ *render_module_.get()) != 0)
+ LOG_RTCERR0(DeRegisterVideoRenderModule);
+
+ if ((vie_wrapper_->base()->DeregisterObserver()) != 0)
+ LOG_RTCERR0(DeregisterObserver);
+
+ if ((vie_wrapper_->base()->SetVoiceEngine(NULL)) != 0)
+ LOG_RTCERR0(SetVoiceEngine);
+
+ if (vie_wrapper_->engine()->SetTraceCallback(NULL) != 0)
+ LOG_RTCERR0(SetTraceCallback);
+}
+
+int WebRtcVideoEngine::GetCapabilities() {
+ return VIDEO_RECV | VIDEO_SEND;
+}
+
+bool WebRtcVideoEngine::SetOptions(int options) {
+ return true;
+}
+
+bool WebRtcVideoEngine::ReleaseCaptureDevice() {
+ if (capture_id_ != -1) {
+ // Stop capture
+ SetCapture(false);
+ // DisconnectCaptureDevice
+ WebRtcVideoMediaChannel* channel;
+ for (VideoChannels::const_iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ ASSERT(*it != NULL);
+ channel = *it;
+ // Ignore the return value here as the channel may not have connected to
+ // the capturer yet.
+ vie_wrapper_->capture()->DisconnectCaptureDevice(
+ channel->video_channel());
+ channel->set_connected(false);
+ }
+ // ReleaseCaptureDevice
+ vie_wrapper_->capture()->ReleaseCaptureDevice(capture_id_);
+ capture_id_ = -1;
+ }
+
+ return true;
+}
+
+bool WebRtcVideoEngine::SetCaptureDevice(const Device* cam) {
+ ASSERT(cam != NULL);
+
+ ReleaseCaptureDevice();
+
+ webrtc::ViECapture* vie_capture = vie_wrapper_->capture();
+
+ // There's an external VCM
+ if (capture_module_) {
+ if (vie_capture->AllocateCaptureDevice(*capture_module_, capture_id_) != 0)
+ ASSERT(capture_id_ == -1);
+ } else if (!external_capture_) {
+ char device_name[256], device_id[256];
+ bool found = false;
+ for (int i = 0; i < vie_capture->NumberOfCaptureDevices(); ++i) {
+ if (vie_capture->GetCaptureDevice(i, device_name, sizeof(device_name),
+ device_id, sizeof(device_id)) == 0) {
+ // TODO: We should only compare the device_id here,
+ // however the devicemanager and webrtc use different format for th v4l2
+ // device id. So here we also compare the device_name for now.
+ // For example "usb-0000:00:1d.7-6" vs "/dev/video0".
+ if (cam->name.compare(device_name) == 0 ||
+ cam->id.compare(device_id) == 0) {
+ LOG(INFO) << "Found video capture device: " << device_name;
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ if (vie_capture->AllocateCaptureDevice(device_id, strlen(device_id),
+ capture_id_) != 0) {
+ ASSERT(capture_id_ == -1);
+ }
+ }
+
+ if (capture_id_ != -1) {
+ // Connect to all the channels if there is any.
+ WebRtcVideoMediaChannel* channel;
+ for (VideoChannels::const_iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ ASSERT(*it != NULL);
+ channel = *it;
+
+ // No channel should have been connected yet.
+ // In case of switching device, all channel connections should have been
+ // disconnected in ReleaseCaptureDevice() first.
+ ASSERT(!channel->connected());
+
+ if (vie_capture->ConnectCaptureDevice(capture_id_,
+ channel->video_channel()) == 0) {
+ channel->set_connected(true);
+ }
+ }
+ SetCapture(true);
+ }
+
+ return (capture_id_ != -1);
+}
+
+bool WebRtcVideoEngine::SetCaptureModule(webrtc::VideoCaptureModule* vcm) {
+ ReleaseCaptureDevice();
+ if (capture_module_) {
+ webrtc::VideoCaptureModule::Destroy(capture_module_);
+ }
+ capture_module_ = vcm;
+ external_capture_ = true;
+ return true;
+}
+
+bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) {
+ if (local_renderer_.get()) {
+ // If the renderer already set, stop and remove it first
+ if (vie_wrapper_->render()->StopRender(capture_id_) != 0) {
+ LOG_RTCERR1(StopRender, capture_id_);
+ }
+ if (vie_wrapper_->render()->RemoveRenderer(capture_id_) != 0) {
+ LOG_RTCERR1(RemoveRenderer, capture_id_);
+ }
+ }
+ local_renderer_.reset(new WebRtcRenderAdapter(renderer));
+
+ int ret;
+ ret = vie_wrapper_->render()->AddRenderer(capture_id_,
+ webrtc::kVideoI420,
+ local_renderer_.get());
+ if (ret != 0)
+ return false;
+ ret = vie_wrapper_->render()->StartRender(capture_id_);
+ return (ret == 0);
+}
+
+CaptureResult WebRtcVideoEngine::SetCapture(bool capture) {
+ if ((capture_started_ != capture) && (capture_id_ != -1)) {
+ int ret;
+ if (capture)
+ ret = vie_wrapper_->capture()->StartCapture(capture_id_);
+ else
+ ret = vie_wrapper_->capture()->StopCapture(capture_id_);
+ if (ret != 0)
+ return CR_NO_DEVICE;
+ capture_started_ = capture;
+ }
+ return CR_SUCCESS;
+}
+
+const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const {
+ return video_codecs_;
+}
+
+void WebRtcVideoEngine::SetLogging(int min_sev, const char* filter) {
+ log_level_ = min_sev;
+ ApplyLogging();
+}
+
+int WebRtcVideoEngine::GetLastEngineError() {
+ return vie_wrapper_->error();
+}
+
+bool WebRtcVideoEngine::SetDefaultEncoderConfig(
+ const VideoEncoderConfig& config) {
+ default_encoder_config_ = config;
+ return true;
+}
+
+WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel(
+ VoiceMediaChannel* voice_channel) {
+ WebRtcVideoMediaChannel* channel =
+ new WebRtcVideoMediaChannel(this, voice_channel);
+ if (channel) {
+ if (!channel->Init()) {
+ delete channel;
+ channel = NULL;
+ }
+ }
+ return channel;
+}
+
+// Checks to see whether we comprehend and could receive a particular codec
+bool WebRtcVideoEngine::FindCodec(const VideoCodec& in) {
+ for (int i = 0; i < ARRAY_SIZE(kVideoFormats); ++i) {
+ const VideoFormat& fmt = kVideoFormats[i];
+ if (fmt.width == in.width && fmt.height == in.height) {
+ for (int j = 0; j < ARRAY_SIZE(kVideoCodecPrefs); ++j) {
+ VideoCodec codec(kVideoCodecPrefs[j].payload_type,
+ kVideoCodecPrefs[j].name, 0, 0, 0, 0);
+ if (codec.Matches(in)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// SetDefaultCodec may be called while the capturer is running. For example, a
+// test call is started in a page with QVGA default codec, and then a real call
+// is started in another page with VGA default codec. This is the corner case
+// and happens only when a session is started. We ignore this case currently.
+bool WebRtcVideoEngine::SetDefaultCodec(const VideoCodec& codec) {
+ if (!RebuildCodecList(codec)) {
+ LOG(LS_WARNING) << "Failed to RebuildCodecList";
+ return false;
+ }
+ return true;
+}
+
+void WebRtcVideoEngine::ConvertToCricketVideoCodec(
+ const webrtc::VideoCodec& in_codec, VideoCodec& out_codec) {
+ out_codec.id = in_codec.plType;
+ out_codec.name = in_codec.plName;
+ out_codec.width = in_codec.width;
+ out_codec.height = in_codec.height;
+ out_codec.framerate = in_codec.maxFramerate;
+}
+
+bool WebRtcVideoEngine::ConvertFromCricketVideoCodec(
+ const VideoCodec& in_codec, webrtc::VideoCodec& out_codec) {
+ bool found = false;
+ int ncodecs = vie_wrapper_->codec()->NumberOfCodecs();
+ for (int i = 0; i < ncodecs; ++i) {
+ if ((vie_wrapper_->codec()->GetCodec(i, out_codec) == 0) &&
+ (strncmp(out_codec.plName,
+ in_codec.name.c_str(),
+ webrtc::kPayloadNameSize - 1) == 0)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ LOG(LS_ERROR) << "invalid codec type";
+ return false;
+ }
+
+ if (in_codec.id != 0)
+ out_codec.plType = in_codec.id;
+
+ if (in_codec.width != 0)
+ out_codec.width = in_codec.width;
+
+ if (in_codec.height != 0)
+ out_codec.height = in_codec.height;
+
+ if (in_codec.framerate != 0)
+ out_codec.maxFramerate = in_codec.framerate;
+
+ out_codec.maxBitrate = kMaxVideoBitrate;
+ out_codec.startBitrate = kStartVideoBitrate;
+ out_codec.minBitrate = kStartVideoBitrate;
+
+ return true;
+}
+
+int WebRtcVideoEngine::GetLastVideoEngineError() {
+ return vie_wrapper_->base()->LastError();
+}
+
+void WebRtcVideoEngine::RegisterChannel(WebRtcVideoMediaChannel *channel) {
+ channels_.push_back(channel);
+}
+
+void WebRtcVideoEngine::UnregisterChannel(WebRtcVideoMediaChannel *channel) {
+ VideoChannels::iterator i = std::find(channels_.begin(),
+ channels_.end(),
+ channel);
+ if (i != channels_.end()) {
+ channels_.erase(i);
+ }
+}
+
+// WebRtcVideoMediaChannel
+
+WebRtcVideoMediaChannel::WebRtcVideoMediaChannel(
+ WebRtcVideoEngine* engine, VoiceMediaChannel* channel)
+ : engine_(engine),
+ voice_channel_(channel),
+ vie_channel_(-1),
+ sending_(false),
+ connected_(false),
+ render_started_(false),
+ send_codec_(NULL) {
+ engine->RegisterChannel(this);
+}
+
+bool WebRtcVideoMediaChannel::Init() {
+ bool ret = true;
+ if (engine_->video_engine()->base()->CreateChannel(vie_channel_) != 0) {
+ LOG_RTCERR1(CreateChannel, vie_channel_);
+ return false;
+ }
+
+ LOG(LS_INFO) << "WebRtcVideoMediaChannel::Init "
+ << "video_channel " << vie_channel_ << " created";
+
+ // connect audio channel
+ if (voice_channel_) {
+ WebRtcVoiceMediaChannel* channel =
+ static_cast<WebRtcVoiceMediaChannel*> (voice_channel_);
+ if (engine_->video_engine()->base()->ConnectAudioChannel(
+ vie_channel_, channel->voe_channel()) != 0) {
+ LOG(LS_WARNING) << "ViE ConnectAudioChannel failed"
+ << "A/V not synchronized";
+ // Don't set ret to false;
+ }
+ }
+
+ // Register external transport
+ if (engine_->video_engine()->network()->RegisterSendTransport(
+ vie_channel_, *this) != 0) {
+ ret = false;
+ } else {
+ EnableRtcp();
+ EnablePLI();
+ }
+ return ret;
+}
+
+WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() {
+ // Stop and remote renderer
+ SetRender(false);
+ if (engine()->video_engine()->render()->RemoveRenderer(vie_channel_)
+ == -1) {
+ LOG_RTCERR1(RemoveRenderer, vie_channel_);
+ }
+
+ // DeRegister external transport
+ if (engine()->video_engine()->network()->DeregisterSendTransport(
+ vie_channel_) == -1) {
+ LOG_RTCERR1(DeregisterSendTransport, vie_channel_);
+ }
+
+ // Unregister RtcChannel with the engine.
+ engine()->UnregisterChannel(this);
+
+ // Delete VideoChannel
+ if (engine()->video_engine()->base()->DeleteChannel(vie_channel_) == -1) {
+ LOG_RTCERR1(DeleteChannel, vie_channel_);
+ }
+}
+
+bool WebRtcVideoMediaChannel::SetRecvCodecs(
+ const std::vector<VideoCodec>& codecs) {
+ bool ret = true;
+ for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
+ iter != codecs.end(); ++iter) {
+ if (engine()->FindCodec(*iter)) {
+ webrtc::VideoCodec wcodec;
+ if (engine()->ConvertFromCricketVideoCodec(*iter, wcodec)) {
+ if (engine()->video_engine()->codec()->SetReceiveCodec(
+ vie_channel_, wcodec) != 0) {
+ LOG_RTCERR2(SetReceiveCodec, vie_channel_, wcodec.plName);
+ ret = false;
+ }
+ }
+ } else {
+ LOG(LS_INFO) << "Unknown codec" << iter->name;
+ ret = false;
+ }
+ }
+
+ // make channel ready to receive packets
+ if (ret) {
+ if (engine()->video_engine()->base()->StartReceive(vie_channel_) != 0) {
+ LOG_RTCERR1(StartReceive, vie_channel_);
+ ret = false;
+ }
+ }
+ return ret;
+}
+
+bool WebRtcVideoMediaChannel::SetSendCodecs(
+ const std::vector<VideoCodec>& codecs) {
+ if (sending_) {
+ LOG(LS_ERROR) << "channel is alredy sending";
+ return false;
+ }
+
+ // match with local video codec list
+ std::vector<webrtc::VideoCodec> send_codecs;
+ for (std::vector<VideoCodec>::const_iterator iter = codecs.begin();
+ iter != codecs.end(); ++iter) {
+ if (engine()->FindCodec(*iter)) {
+ webrtc::VideoCodec wcodec;
+ if (engine()->ConvertFromCricketVideoCodec(*iter, wcodec))
+ send_codecs.push_back(wcodec);
+ }
+ }
+
+ // if none matches, return with set
+ if (send_codecs.empty()) {
+ LOG(LS_ERROR) << "No matching codecs avilable";
+ return false;
+ }
+
+ // select the first matched codec
+ const webrtc::VideoCodec& codec(send_codecs[0]);
+ send_codec_.reset(new webrtc::VideoCodec(codec));
+ if (engine()->video_engine()->codec()->SetSendCodec(
+ vie_channel_, codec) != 0) {
+ LOG_RTCERR2(SetSendCodec, vie_channel_, codec.plName);
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SetRender(bool render) {
+ if (render != render_started_) {
+ int ret;
+ if (render) {
+ ret = engine()->video_engine()->render()->StartRender(vie_channel_);
+ } else {
+ ret = engine()->video_engine()->render()->StopRender(vie_channel_);
+ }
+ if (ret != 0) {
+ return false;
+ }
+ render_started_ = render;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SetSend(bool send) {
+ if (send == sending()) {
+ return true; // no action required
+ }
+
+ bool ret = true;
+ if (send) { // enable
+ if (engine()->video_engine()->base()->StartSend(vie_channel_) != 0) {
+ LOG_RTCERR1(StartSend, vie_channel_);
+ ret = false;
+ }
+
+ // If the channel has not been connected to the capturer yet,
+ // connect it now.
+ if (!connected()) {
+ if (engine()->video_engine()->capture()->ConnectCaptureDevice(
+ engine()->capture_id(), vie_channel_) != 0) {
+ LOG_RTCERR2(ConnectCaptureDevice, engine()->capture_id(), vie_channel_);
+ ret = false;
+ } else {
+ set_connected(true);
+ }
+ }
+ } else { // disable
+ if (engine()->video_engine()->base()->StopSend(vie_channel_) != 0) {
+ LOG_RTCERR1(StopSend, vie_channel_);
+ ret = false;
+ }
+ }
+ if (ret) {
+ sending_ = send;
+ }
+
+ return ret;
+}
+
+bool WebRtcVideoMediaChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
+ return false;
+}
+
+bool WebRtcVideoMediaChannel::RemoveStream(uint32 ssrc) {
+ return false;
+}
+
+bool WebRtcVideoMediaChannel::SetRenderer(
+ uint32 ssrc, VideoRenderer* renderer) {
+ ASSERT(vie_channel_ != -1);
+ if (ssrc != 0)
+ return false;
+ if (remote_renderer_.get()) {
+ // If the renderer already set, stop and remove it first
+ if (engine_->video_engine()->render()->StopRender(vie_channel_) != 0) {
+ LOG_RTCERR1(StopRender, vie_channel_);
+ }
+ if (engine_->video_engine()->render()->RemoveRenderer(vie_channel_) != 0) {
+ LOG_RTCERR1(RemoveRenderer, vie_channel_);
+ }
+ }
+ remote_renderer_.reset(new WebRtcRenderAdapter(renderer));
+
+ if (engine_->video_engine()->render()->AddRenderer(vie_channel_,
+ webrtc::kVideoI420, remote_renderer_.get()) != 0) {
+ LOG_RTCERR3(AddRenderer, vie_channel_, webrtc::kVideoI420,
+ remote_renderer_.get());
+ remote_renderer_.reset();
+ return false;
+ }
+
+ if (engine_->video_engine()->render()->StartRender(vie_channel_) != 0) {
+ LOG_RTCERR1(StartRender, vie_channel_);
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::GetStats(VideoMediaInfo* info) {
+ VideoSenderInfo sinfo;
+ memset(&sinfo, 0, sizeof(sinfo));
+
+ unsigned int ssrc;
+ if (engine_->video_engine()->rtp()->GetLocalSSRC(vie_channel_,
+ ssrc) != 0) {
+ LOG_RTCERR2(GetLocalSSRC, vie_channel_, ssrc);
+ return false;
+ }
+ sinfo.ssrc = ssrc;
+
+ unsigned int cumulative_lost, extended_max, jitter;
+ int rtt_ms;
+ uint16 fraction_lost;
+
+ if (engine_->video_engine()->rtp()->GetReceivedRTCPStatistics(vie_channel_,
+ fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms) != 0) {
+ LOG_RTCERR6(GetReceivedRTCPStatistics, vie_channel_,
+ fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms);
+ return false;
+ }
+
+ sinfo.fraction_lost = fraction_lost;
+ sinfo.packets_lost = cumulative_lost;
+ sinfo.rtt_ms = rtt_ms;
+
+ unsigned int bytes_sent, packets_sent, bytes_recv, packets_recv;
+ if (engine_->video_engine()->rtp()->GetRTPStatistics(vie_channel_,
+ bytes_sent, packets_sent, bytes_recv, packets_recv) != 0) {
+ LOG_RTCERR5(GetRTPStatistics, vie_channel_,
+ bytes_sent, packets_sent, bytes_recv, packets_recv);
+ return false;
+ }
+ sinfo.packets_sent = packets_sent;
+ sinfo.bytes_sent = bytes_sent;
+ sinfo.packets_lost = -1;
+ sinfo.packets_cached = -1;
+
+ info->senders.push_back(sinfo);
+
+ // build receiver info.
+ // reusing the above local variables
+ VideoReceiverInfo rinfo;
+ memset(&rinfo, 0, sizeof(rinfo));
+ if (engine_->video_engine()->rtp()->GetSentRTCPStatistics(vie_channel_,
+ fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms) != 0) {
+ LOG_RTCERR6(GetSentRTCPStatistics, vie_channel_,
+ fraction_lost, cumulative_lost, extended_max, jitter, rtt_ms);
+ return false;
+ }
+ rinfo.bytes_rcvd = bytes_recv;
+ rinfo.packets_rcvd = packets_recv;
+ rinfo.fraction_lost = fraction_lost;
+ rinfo.packets_lost = cumulative_lost;
+
+ if (engine_->video_engine()->rtp()->GetRemoteSSRC(vie_channel_,
+ ssrc) != 0) {
+ return false;
+ }
+ rinfo.ssrc = ssrc;
+
+ // Get codec for wxh
+ info->receivers.push_back(rinfo);
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SendIntraFrame() {
+ bool ret = true;
+ if (engine()->video_engine()->codec()->SendKeyFrame(vie_channel_) != 0) {
+ LOG_RTCERR1(SendKeyFrame, vie_channel_);
+ ret = false;
+ }
+
+ return ret;
+}
+
+bool WebRtcVideoMediaChannel::RequestIntraFrame() {
+ // There is no API exposed to application to request a key frame
+ // ViE does this internally when there are errors from decoder
+ return false;
+}
+
+void WebRtcVideoMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
+ engine()->video_engine()->network()->ReceivedRTPPacket(vie_channel_,
+ packet->data(),
+ packet->length());
+}
+
+void WebRtcVideoMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
+ engine_->video_engine()->network()->ReceivedRTCPPacket(vie_channel_,
+ packet->data(),
+ packet->length());
+}
+
+void WebRtcVideoMediaChannel::SetSendSsrc(uint32 id) {
+ if (!sending_) {
+ if (engine()->video_engine()->rtp()->SetLocalSSRC(vie_channel_,
+ id) != 0) {
+ LOG_RTCERR1(SetLocalSSRC, vie_channel_);
+ }
+ } else {
+ LOG(LS_ERROR) << "Channel already in send state";
+ }
+}
+
+bool WebRtcVideoMediaChannel::SetRtcpCName(const std::string& cname) {
+ if (engine()->video_engine()->rtp()->SetRTCPCName(vie_channel_,
+ cname.c_str()) != 0) {
+ LOG_RTCERR2(SetRTCPCName, vie_channel_, cname.c_str());
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::Mute(bool on) {
+ // stop send??
+ return false;
+}
+
+bool WebRtcVideoMediaChannel::SetSendBandwidth(bool autobw, int bps) {
+ LOG(LS_INFO) << "RtcVideoMediaChanne::SetSendBandwidth";
+
+ if (!send_codec_.get()) {
+ LOG(LS_INFO) << "The send codec has not been set up yet.";
+ return true;
+ }
+
+ if (!autobw) {
+ send_codec_->startBitrate = bps;
+ send_codec_->minBitrate = bps;
+ }
+ send_codec_->maxBitrate = bps;
+
+ if (engine()->video_engine()->codec()->SetSendCodec(vie_channel_,
+ *send_codec_.get()) != 0) {
+ LOG_RTCERR2(SetSendCodec, vie_channel_, send_codec_->plName);
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcVideoMediaChannel::SetOptions(int options) {
+ return true;
+}
+
+void WebRtcVideoMediaChannel::EnableRtcp() {
+ engine()->video_engine()->rtp()->SetRTCPStatus(
+ vie_channel_, webrtc::kRtcpCompound_RFC4585);
+}
+
+void WebRtcVideoMediaChannel::EnablePLI() {
+ engine_->video_engine()->rtp()->SetKeyFrameRequestMethod(
+ vie_channel_, webrtc::kViEKeyFrameRequestPliRtcp);
+}
+
+void WebRtcVideoMediaChannel::EnableTMMBR() {
+ engine_->video_engine()->rtp()->SetTMMBRStatus(vie_channel_, true);
+}
+
+int WebRtcVideoMediaChannel::SendPacket(int channel, const void* data,
+ int len) {
+ if (!network_interface_) {
+ return -1;
+ }
+ talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+ return network_interface_->SendPacket(&packet) ? len : -1;
+}
+
+int WebRtcVideoMediaChannel::SendRTCPPacket(int channel,
+ const void* data,
+ int len) {
+ if (!network_interface_) {
+ return -1;
+ }
+ talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+ return network_interface_->SendRtcp(&packet) ? len : -1;
+}
+
+} // namespace cricket
+
+#endif // HAVE_WEBRTC_VIDEO
+
diff --git a/talk/session/phone/webrtcvideoengine.h b/talk/session/phone/webrtcvideoengine.h
new file mode 100644
index 0000000..8daa81c
--- /dev/null
+++ b/talk/session/phone/webrtcvideoengine.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_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
+#define TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
+
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/videocommon.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/webrtccommon.h"
+
+namespace webrtc {
+class VideoCaptureModule;
+class VideoRender;
+}
+
+namespace cricket {
+struct Device;
+class VideoCapturer;
+class VideoRenderer;
+class ViEWrapper;
+class VoiceMediaChannel;
+class WebRtcRenderAdapter;
+class WebRtcVideoMediaChannel;
+class WebRtcVoiceEngine;
+
+class WebRtcVideoEngine : public webrtc::ViEBaseObserver,
+ public webrtc::TraceCallback {
+ public:
+ // Creates the WebRtcVideoEngine with internal VideoCaptureModule.
+ WebRtcVideoEngine();
+ // Creates the WebRtcVideoEngine, and specifies the WebRtcVoiceEngine and
+ // external VideoCaptureModule to use.
+ WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
+ webrtc::VideoCaptureModule* capture);
+ // For testing purposes. Allows the WebRtcVoiceEngine and
+ // ViEWrapper to be mocks.
+ WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine, ViEWrapper* vie_wrapper);
+ ~WebRtcVideoEngine();
+
+ bool Init();
+ void Terminate();
+
+ WebRtcVideoMediaChannel* CreateChannel(
+ VoiceMediaChannel* voice_channel);
+ bool FindCodec(const VideoCodec& in);
+ bool SetDefaultEncoderConfig(const VideoEncoderConfig& config);
+
+ void RegisterChannel(WebRtcVideoMediaChannel* channel);
+ void UnregisterChannel(WebRtcVideoMediaChannel* channel);
+
+ ViEWrapper* video_engine() { return vie_wrapper_.get(); }
+ int GetLastVideoEngineError();
+ int GetCapabilities();
+ bool SetOptions(int options);
+ bool SetCaptureDevice(const Device* device);
+ bool SetCaptureModule(webrtc::VideoCaptureModule* vcm);
+ int capture_id() const { return capture_id_; }
+ bool SetLocalRenderer(VideoRenderer* renderer);
+ CaptureResult SetCapture(bool capture);
+ const std::vector<VideoCodec>& codecs() const;
+ void SetLogging(int min_sev, const char* filter);
+
+ int GetLastEngineError();
+
+ VideoEncoderConfig& default_encoder_config() {
+ return default_encoder_config_;
+ }
+
+ void ConvertToCricketVideoCodec(const webrtc::VideoCodec& in_codec,
+ VideoCodec& out_codec);
+
+ bool ConvertFromCricketVideoCodec(const VideoCodec& in_codec,
+ webrtc::VideoCodec& out_codec);
+
+ sigslot::signal2<VideoCapturer*, CaptureResult> SignalCaptureResult;
+
+ private:
+ struct VideoCodecPref {
+ const char* name;
+ int payload_type;
+ int pref;
+ };
+
+ static const VideoCodecPref kVideoCodecPrefs[];
+ static const VideoFormat kVideoFormats[];
+ static const VideoFormat kDefaultVideoFormat;
+
+ void Construct();
+ bool SetDefaultCodec(const VideoCodec& codec);
+ bool RebuildCodecList(const VideoCodec& max_codec);
+
+ void ApplyLogging();
+ bool InitVideoEngine();
+ void PerformanceAlarm(const unsigned int cpu_load);
+ bool ReleaseCaptureDevice();
+ virtual void Print(const webrtc::TraceLevel level, const char* trace_string,
+ const int length);
+
+ typedef std::vector<WebRtcVideoMediaChannel*> VideoChannels;
+
+ talk_base::scoped_ptr<ViEWrapper> vie_wrapper_;
+ webrtc::VideoCaptureModule* capture_module_;
+ bool external_capture_;
+ int capture_id_;
+ talk_base::scoped_ptr<webrtc::VideoRender> render_module_;
+ WebRtcVoiceEngine* voice_engine_;
+ std::vector<VideoCodec> video_codecs_;
+ VideoChannels channels_;
+ int log_level_;
+ VideoEncoderConfig default_encoder_config_;
+ bool capture_started_;
+ talk_base::scoped_ptr<WebRtcRenderAdapter> local_renderer_;
+};
+
+class WebRtcVideoMediaChannel : public VideoMediaChannel,
+ public webrtc::Transport {
+ public:
+ WebRtcVideoMediaChannel(
+ WebRtcVideoEngine* engine, VoiceMediaChannel* voice_channel);
+ ~WebRtcVideoMediaChannel();
+
+ bool Init();
+ virtual bool SetRecvCodecs(const std::vector<VideoCodec> &codecs);
+ virtual bool SetSendCodecs(const std::vector<VideoCodec> &codecs);
+ virtual bool SetRender(bool render);
+ virtual bool SetSend(bool send);
+ virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc);
+ virtual bool RemoveStream(uint32 ssrc);
+ virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+ virtual bool GetStats(VideoMediaInfo* info);
+ virtual bool SendIntraFrame();
+ virtual bool RequestIntraFrame();
+
+ virtual void OnPacketReceived(talk_base::Buffer* packet);
+ virtual void OnRtcpReceived(talk_base::Buffer* packet);
+ virtual void SetSendSsrc(uint32 id);
+ virtual bool SetRtcpCName(const std::string& cname);
+ virtual bool Mute(bool on);
+ virtual bool SetRecvRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions) {
+ return false;
+ }
+ virtual bool SetSendRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions) {
+ return false;
+ }
+ virtual bool SetSendBandwidth(bool autobw, int bps);
+ virtual bool SetOptions(int options);
+
+ WebRtcVideoEngine* engine() const { return engine_; }
+ VoiceMediaChannel* voice_channel() const { return voice_channel_; }
+ int video_channel() const { return vie_channel_; }
+ bool sending() const { return sending_; }
+ void set_connected(bool connected) { connected_ = connected; }
+ bool connected() const { return connected_; }
+
+ protected:
+ int GetLastEngineError() { return engine()->GetLastEngineError(); }
+ virtual int SendPacket(int channel, const void* data, int len);
+ virtual int SendRTCPPacket(int channel, const void* data, int len);
+
+ private:
+ void EnableRtcp();
+ void EnablePLI();
+ void EnableTMMBR();
+
+ WebRtcVideoEngine* engine_;
+ VoiceMediaChannel* voice_channel_;
+ int vie_channel_;
+ bool sending_;
+ // connected to the capture device or not.
+ bool connected_;
+ bool render_started_;
+ talk_base::scoped_ptr<webrtc::VideoCodec> send_codec_;
+ talk_base::scoped_ptr<WebRtcRenderAdapter> remote_renderer_;
+};
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_WEBRTCVIDEOENGINE_H_
diff --git a/talk/session/phone/webrtcvideoengine_unittest.cc b/talk/session/phone/webrtcvideoengine_unittest.cc
new file mode 100644
index 0000000..8e985d7
--- /dev/null
+++ b/talk/session/phone/webrtcvideoengine_unittest.cc
@@ -0,0 +1,183 @@
+// Copyright 2008 Google Inc. All Rights Reserved.
+//
+// Author: Ronghua Wu (ronghuawu@google.com)
+// Zhurun Zhang (zhurunz@google.com)
+
+#include "talk/base/byteorder.h"
+#include "talk/base/gunit.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/fakesession.h"
+#include "talk/session/phone/fakewebrtcvideoengine.h"
+#include "talk/session/phone/fakewebrtcvoiceengine.h"
+#include "talk/session/phone/webrtcvideoengine.h"
+#include "talk/session/phone/webrtcvoiceengine.h"
+
+// Tests for the WebRtcVideoEngine/VideoChannel code.
+
+static const cricket::VideoCodec kVP8Codec(104, "VP8", 320, 200, 30, 0);
+static const cricket::VideoCodec* const kVideoCodecs[] = {
+ &kVP8Codec,
+};
+
+class FakeViEWrapper : public cricket::ViEWrapper {
+ public:
+ explicit FakeViEWrapper(cricket::FakeWebRtcVideoEngine* engine)
+ : cricket::ViEWrapper(engine, engine, engine, engine,
+ engine, engine, engine) {
+ }
+};
+
+class WebRtcVideoEngineTest : public testing::Test {
+ public:
+ class ChannelErrorListener : public sigslot::has_slots<> {
+ public:
+ explicit ChannelErrorListener(cricket::WebRtcVideoMediaChannel* channel)
+ : ssrc_(0), error_(cricket::WebRtcVideoMediaChannel::ERROR_NONE) {
+ ASSERT(channel != NULL);
+ channel->SignalMediaError.connect(
+ this, &ChannelErrorListener::OnVideoChannelError);
+ }
+ void OnVideoChannelError(uint32 ssrc,
+ cricket::WebRtcVideoMediaChannel::Error error) {
+ ssrc_ = ssrc;
+ error_ = error;
+ }
+ void Reset() {
+ ssrc_ = 0;
+ error_ = cricket::WebRtcVideoMediaChannel::ERROR_NONE;
+ }
+ uint32 ssrc() const {
+ return ssrc_;
+ }
+ cricket::WebRtcVideoMediaChannel::Error error() const {
+ return error_;
+ }
+
+ private:
+ uint32 ssrc_;
+ cricket::WebRtcVideoMediaChannel::Error error_;
+ };
+
+ WebRtcVideoEngineTest()
+ : vie_(kVideoCodecs, ARRAY_SIZE(kVideoCodecs)),
+ engine_(NULL, // cricket::WebRtcVoiceEngine
+ new FakeViEWrapper(&vie_)),
+ channel_(NULL),
+ voice_channel_(NULL) {
+ }
+ bool SetupEngine() {
+ bool result = engine_.Init();
+ if (result) {
+ channel_ = engine_.CreateChannel(voice_channel_);
+ result = (channel_ != NULL);
+ }
+ return result;
+ }
+ void DeliverPacket(const void* data, int len) {
+ talk_base::Buffer packet(data, len);
+ channel_->OnPacketReceived(&packet);
+ }
+ virtual void TearDown() {
+ delete channel_;
+ engine_.Terminate();
+ }
+
+ protected:
+ cricket::FakeWebRtcVideoEngine vie_;
+ cricket::WebRtcVideoEngine engine_;
+ cricket::WebRtcVideoMediaChannel* channel_;
+ cricket::WebRtcVoiceMediaChannel* voice_channel_;
+};
+
+// Tests that our stub library "works".
+TEST_F(WebRtcVideoEngineTest, StartupShutdown) {
+ EXPECT_FALSE(vie_.IsInited());
+ EXPECT_TRUE(engine_.Init());
+ EXPECT_TRUE(vie_.IsInited());
+ engine_.Terminate();
+ // TODO: what to expect after Terminate
+ // EXPECT_FALSE(vie_.IsInited());
+}
+
+// Tests that we can create and destroy a channel.
+TEST_F(WebRtcVideoEngineTest, CreateChannel) {
+ EXPECT_TRUE(engine_.Init());
+ channel_ = engine_.CreateChannel(voice_channel_);
+ EXPECT_TRUE(channel_ != NULL);
+}
+
+// Tests that we properly handle failures in CreateChannel.
+TEST_F(WebRtcVideoEngineTest, CreateChannelFail) {
+ vie_.set_fail_create_channel(true);
+ EXPECT_TRUE(engine_.Init());
+ channel_ = engine_.CreateChannel(voice_channel_);
+ EXPECT_TRUE(channel_ == NULL);
+}
+
+// Tests that we can find codecs by name or id
+TEST_F(WebRtcVideoEngineTest, FindCodec) {
+ // We should not need to init engine in order to get codecs.
+ const std::vector<cricket::VideoCodec>& c = engine_.codecs();
+ EXPECT_EQ(1U, c.size());
+
+ cricket::VideoCodec vp8(104, "VP8", 320, 200, 30, 0);
+ EXPECT_TRUE(engine_.FindCodec(vp8));
+
+ cricket::VideoCodec vp8_diff_fr_diff_pref(104, "VP8", 320, 200, 50, 50);
+ EXPECT_TRUE(engine_.FindCodec(vp8_diff_fr_diff_pref));
+
+ cricket::VideoCodec vp8_diff_id(95, "VP8", 320, 200, 30, 0);
+ EXPECT_FALSE(engine_.FindCodec(vp8_diff_id));
+ vp8_diff_id.id = 97;
+ EXPECT_TRUE(engine_.FindCodec(vp8_diff_id));
+
+ cricket::VideoCodec vp8_diff_res(104, "VP8", 320, 111, 30, 0);
+ EXPECT_FALSE(engine_.FindCodec(vp8_diff_res));
+}
+
+// Test that we set our inbound codecs properly
+TEST_F(WebRtcVideoEngineTest, SetRecvCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ std::vector<cricket::VideoCodec> codecs;
+ codecs.push_back(kVP8Codec);
+ EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+}
+
+// Test that we apply codecs properly.
+TEST_F(WebRtcVideoEngineTest, SetSendCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = vie_.GetLastChannel();
+ std::vector<cricket::VideoCodec> codecs;
+ codecs.push_back(kVP8Codec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::VideoCodec gcodec;
+ EXPECT_EQ(0, vie_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(kVP8Codec.id, gcodec.plType);
+ EXPECT_EQ(kVP8Codec.width, gcodec.width);
+ EXPECT_EQ(kVP8Codec.height, gcodec.height);
+ EXPECT_STREQ(kVP8Codec.name.c_str(), gcodec.plName);
+}
+
+// TODO: add tests for below interfaces
+// bool SetOptions(int options);
+// bool SetCaptureDevice(const Device* device);
+// bool SetLocalRenderer(VideoRenderer* renderer);
+// CaptureResult SetCapture(bool capture);
+// virtual bool SetRender(bool render);
+// virtual bool SetSend(bool send);
+// virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc);
+// virtual bool RemoveStream(uint32 ssrc);
+// virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+// virtual bool GetStats(VideoMediaInfo* info);
+// virtual bool SendIntraFrame();
+// virtual bool RequestIntraFrame();
+// virtual void OnPacketReceived(talk_base::Buffer* packet);
+// virtual void OnRtcpReceived(talk_base::Buffer* packet);
+// virtual void SetSendSsrc(uint32 id);
+// virtual bool SetRtcpCName(const std::string& cname);
+// virtual bool Mute(bool on);
+// virtual bool SetSendBandwidth(bool autobw, int bps);
+// virtual bool SetOptions(int options);
+
diff --git a/talk/session/phone/webrtcvideoframe.cc b/talk/session/phone/webrtcvideoframe.cc
new file mode 100644
index 0000000..c2a3470
--- /dev/null
+++ b/talk/session/phone/webrtcvideoframe.cc
@@ -0,0 +1,244 @@
+/*
+ * 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/session/phone/webrtcvideoframe.h"
+
+#include "talk/base/logging.h"
+#include "talk/session/phone/videocommon.h"
+#ifdef WEBRTC_RELATIVE_PATH
+#include "common_video/vplib/main/interface/vplib.h"
+#else
+#include "third_party/webrtc/files/include/vplib.h"
+#endif
+
+namespace cricket {
+WebRtcVideoFrame::WebRtcVideoFrame() {
+}
+
+WebRtcVideoFrame::~WebRtcVideoFrame() {
+}
+
+void WebRtcVideoFrame::Attach(uint8* buffer, size_t buffer_size, size_t w,
+ size_t h, int64 elapsed_time, int64 time_stamp) {
+ video_frame_.Free();
+ WebRtc_UWord8* new_memory = buffer;
+ WebRtc_UWord32 new_length = buffer_size;
+ WebRtc_UWord32 new_size = buffer_size;
+ video_frame_.Swap(new_memory, new_length, new_size);
+ video_frame_.SetWidth(w);
+ video_frame_.SetHeight(h);
+ elapsed_time_ = elapsed_time;
+ video_frame_.SetTimeStamp(static_cast<WebRtc_UWord32>(time_stamp));
+}
+
+void WebRtcVideoFrame::Detach(uint8** buffer, size_t* buffer_size) {
+ WebRtc_UWord8* new_memory = NULL;
+ WebRtc_UWord32 new_length = 0;
+ WebRtc_UWord32 new_size = 0;
+ video_frame_.Swap(new_memory, new_length, new_size);
+ *buffer = new_memory;
+ *buffer_size = new_size;
+}
+
+bool WebRtcVideoFrame::InitToBlack(size_t w, size_t h,
+ int64 elapsed_time, int64 time_stamp) {
+ size_t buffer_size = w * h * 3 / 2;
+ uint8* buffer = new uint8[buffer_size];
+ Attach(buffer, buffer_size, w, h, elapsed_time, time_stamp);
+ memset(GetYPlane(), 16, w * h);
+ memset(GetUPlane(), 128, w * h / 4);
+ memset(GetVPlane(), 128, w * h / 4);
+ return true;
+}
+
+size_t WebRtcVideoFrame::GetWidth() const {
+ return video_frame_.Width();
+}
+
+size_t WebRtcVideoFrame::GetHeight() const {
+ return video_frame_.Height();
+}
+
+const uint8* WebRtcVideoFrame::GetYPlane() const {
+ WebRtc_UWord8* buffer = video_frame_.Buffer();
+ return buffer;
+}
+
+const uint8* WebRtcVideoFrame::GetUPlane() const {
+ WebRtc_UWord8* buffer = video_frame_.Buffer();
+ if (buffer)
+ buffer += (video_frame_.Width() * video_frame_.Height());
+ return buffer;
+}
+
+const uint8* WebRtcVideoFrame::GetVPlane() const {
+ WebRtc_UWord8* buffer = video_frame_.Buffer();
+ if (buffer)
+ buffer += (video_frame_.Width() * video_frame_.Height() * 5 / 4);
+ return buffer;
+}
+
+uint8* WebRtcVideoFrame::GetYPlane() {
+ WebRtc_UWord8* buffer = video_frame_.Buffer();
+ return buffer;
+}
+
+uint8* WebRtcVideoFrame::GetUPlane() {
+ WebRtc_UWord8* buffer = video_frame_.Buffer();
+ if (buffer)
+ buffer += (video_frame_.Width() * video_frame_.Height());
+ return buffer;
+}
+
+uint8* WebRtcVideoFrame::GetVPlane() {
+ WebRtc_UWord8* buffer = video_frame_.Buffer();
+ if (buffer)
+ buffer += (video_frame_.Width() * video_frame_.Height() * 5 / 4);
+ return buffer;
+}
+
+VideoFrame* WebRtcVideoFrame::Copy() const {
+ WebRtc_UWord8* buffer = video_frame_.Buffer();
+ if (!buffer)
+ return NULL;
+
+ size_t new_buffer_size = video_frame_.Length();
+ uint8* new_buffer = new uint8[new_buffer_size];
+ memcpy(new_buffer, buffer, new_buffer_size);
+ WebRtcVideoFrame* copy = new WebRtcVideoFrame();
+ copy->Attach(new_buffer, new_buffer_size,
+ video_frame_.Width(), video_frame_.Height(),
+ elapsed_time_, video_frame_.TimeStamp());
+ return copy;
+}
+
+bool WebRtcVideoFrame::MakeExclusive() {
+ // WebRtcVideoFrame::Copy makes a deep copy of the frame buffer. No action
+ // is needed for MakeExclusive.
+ return true;
+}
+
+size_t WebRtcVideoFrame::CopyToBuffer(
+ uint8* buffer, size_t size) const {
+ if (!video_frame_.Buffer()) {
+ return 0;
+ }
+
+ size_t needed = video_frame_.Length();
+ if (needed <= size) {
+ memcpy(buffer, video_frame_.Buffer(), needed);
+ }
+ return needed;
+}
+
+size_t WebRtcVideoFrame::ConvertToRgbBuffer(uint32 to_fourcc,
+ uint8* buffer,
+ size_t size,
+ size_t pitch_rgb) const {
+ if (!video_frame_.Buffer()) {
+ return 0;
+ }
+
+ size_t width = video_frame_.Width();
+ size_t height = video_frame_.Height();
+ // See http://www.virtualdub.org/blog/pivot/entry.php?id=190 for a good
+ // explanation of pitch and why this is the amount of space we need.
+ size_t needed = pitch_rgb * (height - 1) + 4 * width;
+
+ if (needed > size) {
+ LOG(LS_WARNING) << "RGB buffer is not large enough";
+ return 0;
+ }
+
+ webrtc::VideoType outgoingVideoType = webrtc::kUnknown;
+ switch (to_fourcc) {
+ case FOURCC_ARGB:
+ outgoingVideoType = webrtc::kARGB;
+ break;
+ default:
+ LOG(LS_WARNING) << "RGB type not supported: " << to_fourcc;
+ return 0;
+ break;
+ }
+
+ if (outgoingVideoType != webrtc::kUnknown)
+ webrtc::ConvertFromI420(outgoingVideoType, video_frame_.Buffer(),
+ width, height, buffer);
+
+ return needed;
+}
+
+void WebRtcVideoFrame::StretchToPlanes(
+ uint8* y, uint8* u, uint8* v,
+ int32 dst_pitch_y, int32 dst_pitch_u, int32 dst_pitch_v,
+ size_t width, size_t height, bool interpolate, bool crop) const {
+ // TODO: Implement StretchToPlanes
+}
+
+size_t WebRtcVideoFrame::StretchToBuffer(size_t w, size_t h,
+ uint8* buffer, size_t size,
+ bool interpolate,
+ bool crop) const {
+ if (!video_frame_.Buffer()) {
+ return 0;
+ }
+
+ size_t needed = video_frame_.Length();
+
+ if (needed <= size) {
+ uint8* bufy = buffer;
+ uint8* bufu = bufy + w * h;
+ uint8* bufv = bufu + ((w + 1) >> 1) * ((h + 1) >> 1);
+ StretchToPlanes(bufy, bufu, bufv, w, (w + 1) >> 1, (w + 1) >> 1, w, h,
+ interpolate, crop);
+ }
+ return needed;
+}
+
+void WebRtcVideoFrame::StretchToFrame(VideoFrame* target,
+ bool interpolate, bool crop) const {
+ if (!target) return;
+
+ StretchToPlanes(target->GetYPlane(),
+ target->GetUPlane(),
+ target->GetVPlane(),
+ target->GetYPitch(),
+ target->GetUPitch(),
+ target->GetVPitch(),
+ target->GetWidth(),
+ target->GetHeight(),
+ interpolate, crop);
+ target->SetElapsedTime(GetElapsedTime());
+ target->SetTimeStamp(GetTimeStamp());
+}
+
+VideoFrame* WebRtcVideoFrame::Stretch(size_t w, size_t h,
+ bool interpolate, bool crop) const {
+ // TODO: implement
+ return NULL;
+}
+} // namespace cricket
diff --git a/talk/session/phone/webrtcvideoframe.h b/talk/session/phone/webrtcvideoframe.h
new file mode 100644
index 0000000..6538fbc
--- /dev/null
+++ b/talk/session/phone/webrtcvideoframe.h
@@ -0,0 +1,98 @@
+/*
+ * 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_SESSION_PHONE_WEBRTCVIDEOFRAME_H_
+#define TALK_SESSION_PHONE_WEBRTCVIDEOFRAME_H_
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "common_types.h"
+#include "modules/interface/module_common_types.h"
+#else
+#include "third_party/webrtc/files/include/common_types.h"
+#include "third_party/webrtc/files/include/module_common_types.h"
+#endif
+#include "talk/session/phone/videoframe.h"
+
+namespace cricket {
+// WebRtcVideoFrame only supports I420
+class WebRtcVideoFrame : public VideoFrame {
+ public:
+ WebRtcVideoFrame();
+ ~WebRtcVideoFrame();
+
+ void Attach(uint8* buffer, size_t buffer_size,
+ size_t w, size_t h, int64 elapsed_time, int64 time_stamp);
+ void Detach(uint8** buffer, size_t* buffer_size);
+ bool InitToBlack(size_t w, size_t h, int64 elapsed_time, int64 time_stamp);
+ bool HasImage() const { return video_frame_.Buffer() != NULL; }
+
+ virtual size_t GetWidth() const;
+ virtual size_t GetHeight() const;
+ virtual const uint8* GetYPlane() const;
+ virtual const uint8* GetUPlane() const;
+ virtual const uint8* GetVPlane() const;
+ virtual uint8* GetYPlane();
+ virtual uint8* GetUPlane();
+ virtual uint8* GetVPlane();
+ virtual int32 GetYPitch() const { return video_frame_.Width(); }
+ virtual int32 GetUPitch() const { return video_frame_.Width() / 2; }
+ virtual int32 GetVPitch() const { return video_frame_.Width() / 2; }
+
+ virtual size_t GetPixelWidth() const { return 1; }
+ virtual size_t GetPixelHeight() const { return 1; }
+ virtual int64 GetElapsedTime() const { return elapsed_time_; }
+ virtual int64 GetTimeStamp() const { return video_frame_.TimeStamp(); }
+ virtual void SetElapsedTime(int64 elapsed_time) {
+ elapsed_time_ = elapsed_time;
+ }
+ virtual void SetTimeStamp(int64 time_stamp) {
+ video_frame_.SetTimeStamp(static_cast<WebRtc_UWord32>(time_stamp));
+ }
+
+ virtual VideoFrame* Copy() const;
+ virtual bool MakeExclusive();
+ virtual size_t CopyToBuffer(uint8* buffer, size_t size) const;
+ virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8* buffer,
+ size_t size, size_t pitch_rgb) const;
+ virtual void StretchToPlanes(uint8* y, uint8* u, uint8* v,
+ int32 pitchY, int32 pitchU, int32 pitchV,
+ size_t width, size_t height,
+ bool interpolate, bool crop) const;
+ virtual size_t StretchToBuffer(size_t w, size_t h, uint8* buffer, size_t size,
+ bool interpolate, bool crop) const;
+ virtual void StretchToFrame(VideoFrame* target, bool interpolate,
+ bool crop) const;
+ virtual VideoFrame* Stretch(size_t w, size_t h, bool interpolate,
+ bool crop) const;
+
+ private:
+ webrtc::VideoFrame video_frame_;
+ int64 elapsed_time_;
+};
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_WEBRTCVIDEOFRAME_H_
diff --git a/talk/session/phone/webrtcvideoframe_unittest.cc b/talk/session/phone/webrtcvideoframe_unittest.cc
new file mode 100644
index 0000000..4bd9244
--- /dev/null
+++ b/talk/session/phone/webrtcvideoframe_unittest.cc
@@ -0,0 +1,443 @@
+// Copyright 2008 Google Inc. All Rights Reserved,
+//
+// Author: Justin Uberti (juberti@google.com)
+// Frank Barchard (fbarchard@google.com)
+#include <string>
+
+#include "talk/base/flags.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/formatconversion.h"
+#include "talk/session/phone/webrtcvideoframe.h"
+#include "talk/session/phone/testutils.h"
+#include "talk/session/phone/videocommon.h"
+
+enum {
+ ROTATION_0 = 0,
+ ROTATION_90 = 90,
+ ROTATION_180 = 180,
+ ROTATION_270 = 270
+};
+
+using cricket::WebRtcVideoFrame;
+using cricket::FOURCC_I420;
+
+static const int kWidth = 1280;
+static const int kHeight = 720;
+static const int kAlignment = 16;
+static const std::string kImageFilename = "faces.1280x720_P420.yuv";
+
+DEFINE_int(yuvconverter_repeat, 1,
+ "how many times to perform each conversion operation (for perf testing)");
+
+class WebRtcVideoFrameTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ repeat_ = FLAG_yuvconverter_repeat;
+ }
+
+ public:
+ // Load a video frame from disk or a buffer.
+ bool LoadFrame(const std::string& filename, uint32 format,
+ int32 width, int32 height, WebRtcVideoFrame* frame,
+ int rotation) {
+ talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(filename));
+ return LoadFrame(ms.get(), format, width, height, frame, rotation);
+ }
+
+ bool LoadFrame(talk_base::MemoryStream* ms, uint32 format,
+ int32 width, int32 height, WebRtcVideoFrame* frame,
+ int rotation) {
+ if (!ms) {
+ return false;
+ }
+ size_t data_size;
+ bool ret = ms->GetSize(&data_size);
+ EXPECT_TRUE(ret);
+ if (ret) {
+ ret = LoadFrame(reinterpret_cast<uint8*>(ms->GetBuffer()), data_size,
+ format, width, height, frame, rotation);
+ }
+ return ret;
+ }
+
+ bool LoadFrame(uint8* sample, size_t sample_size, uint32 format,
+ int32 width, int32 height, WebRtcVideoFrame* frame,
+ int rotation) {
+ // WebRtcVideoFrame only supporst I420 for now
+ if (format != FOURCC_I420)
+ return false;
+ for (int i = 0; i < repeat_; ++i) {
+ uint8* new_buffer = new uint8[sample_size];
+ memcpy(new_buffer, sample, sample_size);
+ frame->Attach(new_buffer, sample_size, width, height, 0, 0);
+ }
+ return true;
+ }
+
+ talk_base::MemoryStream* LoadSample(const std::string& filename) {
+ talk_base::Pathname path(cricket::GetTestFilePath(filename));
+ talk_base::scoped_ptr<talk_base::FileStream> fs(
+ talk_base::Filesystem::OpenFile(path, "rb"));
+ if (!fs.get()) {
+ return NULL;
+ }
+
+ char buf[4096];
+ talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+ new talk_base::MemoryStream());
+ talk_base::StreamResult res = Flow(fs.get(), buf, sizeof(buf), ms.get());
+ if (res != talk_base::SR_SUCCESS) {
+ return NULL;
+ }
+
+ return ms.release();
+ }
+
+ // Write an I420 frame out to disk.
+ bool DumpFrame(const std::string& prefix,
+ const WebRtcVideoFrame& frame) {
+ char filename[256];
+ talk_base::sprintfn(filename, sizeof(filename), "%s.%dx%d_P420.yuv",
+ prefix.c_str(), frame.GetWidth(), frame.GetHeight());
+ size_t out_size = cricket::VideoFrame::SizeOf(frame.GetWidth(),
+ frame.GetHeight());
+ talk_base::scoped_array<uint8> out(new uint8[out_size]);
+ frame.CopyToBuffer(out.get(), out_size);
+ return DumpSample(filename, out.get(), out_size);
+ }
+
+ bool DumpSample(const std::string& filename, const void* buffer, int size) {
+ talk_base::Pathname path(filename);
+ talk_base::scoped_ptr<talk_base::FileStream> fs(
+ talk_base::Filesystem::OpenFile(path, "wb"));
+ if (!fs.get()) {
+ return false;
+ }
+
+ return (fs->Write(buffer, size, NULL, NULL) == talk_base::SR_SUCCESS);
+ }
+
+ // Create a test image for YUV 420 formats with 12 bits per pixel.
+ talk_base::MemoryStream* CreateYuv420Sample(uint32 width, uint32 height) {
+ talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+ new talk_base::MemoryStream);
+ if (!ms->ReserveSize(width * height * 12 / 8)) {
+ return NULL;
+ }
+
+ for (uint32 i = 0; i < width * height * 12 / 8; ++i) {
+ char value = ((i / 63) & 1) ? 192 : 64;
+ ms->Write(&value, sizeof(value), NULL, NULL);
+ }
+ return ms.release();
+ }
+
+ talk_base::MemoryStream* CreateRgbSample(uint32 fourcc,
+ uint32 width, uint32 height) {
+ int r_pos, g_pos, b_pos, bytes;
+ if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
+ return NULL;
+ }
+
+ talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+ new talk_base::MemoryStream);
+ if (!ms->ReserveSize(width * height * bytes)) {
+ return NULL;
+ }
+
+ for (uint32 y = 0; y < height; ++y) {
+ for (uint32 x = 0; x < width; ++x) {
+ uint8 rgb[4] = { 255, 255, 255, 255 };
+ rgb[r_pos] = ((x / 63) & 1) ? 224 : 32;
+ rgb[g_pos] = (x % 63 + y % 63) + 96;
+ rgb[b_pos] = ((y / 63) & 1) ? 224 : 32;
+ ms->Write(rgb, bytes, NULL, NULL);
+ }
+ }
+ return ms.release();
+ }
+
+ // Convert RGB to 420.
+ // A negative height inverts the image.
+ bool ConvertRgb(const talk_base::MemoryStream* ms,
+ uint32 fourcc, int32 width, int32 height,
+ WebRtcVideoFrame* frame) {
+ int r_pos, g_pos, b_pos, bytes;
+ if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
+ return false;
+ }
+ int stride = width * bytes;
+ const uint8* start = reinterpret_cast<const uint8*>(ms->GetBuffer());
+ if (height < 0) {
+ height = -height;
+ start = start + stride * (height - 1);
+ stride = -stride;
+ }
+ frame->InitToBlack(width, height, 0, 0);
+ for (int32 y = 0; y < height; y += 2) {
+ for (int32 x = 0; x < width; x += 2) {
+ const uint8* rgb[4];
+ uint8 yuv[4][3];
+ rgb[0] = start + y * stride + x * bytes;
+ rgb[1] = rgb[0] + bytes;
+ rgb[2] = rgb[0] + stride;
+ rgb[3] = rgb[2] + bytes;
+ for (size_t i = 0; i < 4; ++i) {
+ ConvertRgbPixel(rgb[i][r_pos], rgb[i][g_pos], rgb[i][b_pos],
+ &yuv[i][0], &yuv[i][1], &yuv[i][2]);
+ }
+ frame->GetYPlane()[width * y + x] = yuv[0][0];
+ frame->GetYPlane()[width * y + x + 1] = yuv[1][0];
+ frame->GetYPlane()[width * (y + 1) + x] = yuv[2][0];
+ frame->GetYPlane()[width * (y + 1) + x + 1] = yuv[3][0];
+ frame->GetUPlane()[width / 2 * (y / 2) + x / 2] =
+ (yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1] + 2) / 4;
+ frame->GetVPlane()[width / 2 * (y / 2) + x / 2] =
+ (yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2] + 2) / 4;
+ }
+ }
+ return true;
+ }
+
+ // Simple and slow RGB->YUV conversion. From NTSC standard, c/o Wikipedia.
+ void ConvertRgbPixel(uint8 r, uint8 g, uint8 b,
+ uint8* y, uint8* u, uint8* v) {
+ *y = static_cast<int>(.257 * r + .504 * g + .098 * b) + 16;
+ *u = static_cast<int>(-.148 * r - .291 * g + .439 * b) + 128;
+ *v = static_cast<int>(.439 * r - .368 * g - .071 * b) + 128;
+ }
+
+ bool GetRgbPacking(uint32 fourcc,
+ int* r_pos, int* g_pos, int* b_pos, int* bytes) {
+ if (fourcc == cricket::FOURCC_RAW) {
+ *r_pos = 0;
+ *g_pos = 1;
+ *b_pos = 2;
+ *bytes = 3; // RGB in memory
+ } else if (fourcc == cricket::FOURCC_24BG) {
+ *r_pos = 2;
+ *g_pos = 1;
+ *b_pos = 0;
+ *bytes = 3; // BGR in memory
+ } else if (fourcc == cricket::FOURCC_ABGR) {
+ *r_pos = 0;
+ *g_pos = 1;
+ *b_pos = 2;
+ *bytes = 4; // RGBA in memory
+ } else if (fourcc == cricket::FOURCC_BGRA) {
+ *r_pos = 1;
+ *g_pos = 2;
+ *b_pos = 3;
+ *bytes = 4; // ARGB in memory
+ } else if (fourcc == cricket::FOURCC_ARGB) {
+ *r_pos = 2;
+ *g_pos = 1;
+ *b_pos = 0;
+ *bytes = 4; // BGRA in memory
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ // Comparison functions for testing.
+ static bool IsNull(const WebRtcVideoFrame& frame) {
+ return !frame.HasImage();
+ }
+
+ static bool IsSize(const WebRtcVideoFrame& frame,
+ uint32 width, uint32 height) {
+ return frame.HasImage() &&
+ frame.GetYPitch() >= static_cast<int32>(width) &&
+ frame.GetUPitch() >= static_cast<int32>(width) / 2 &&
+ frame.GetVPitch() >= static_cast<int32>(width) / 2 &&
+ frame.GetWidth() == width && frame.GetHeight() == height;
+ }
+
+ static bool IsPlaneEqual(const std::string& name,
+ const uint8* plane1, uint32 pitch1,
+ const uint8* plane2, uint32 pitch2,
+ uint32 width, uint32 height,
+ int max_error) {
+ const uint8* r1 = plane1;
+ const uint8* r2 = plane2;
+ for (uint32 y = 0; y < height; ++y) {
+ for (uint32 x = 0; x < width; ++x) {
+ if (abs(static_cast<int>(r1[x] - r2[x])) > max_error) {
+ LOG(LS_INFO) << "IsPlaneEqual(" << name << "): pixel["
+ << x << "," << y << "] differs: "
+ << static_cast<int>(r1[x]) << " vs "
+ << static_cast<int>(r2[x]);
+ return false;
+ }
+ }
+ r1 += pitch1;
+ r2 += pitch2;
+ }
+ return true;
+ }
+
+ static bool IsFrameContiguous(const WebRtcVideoFrame& frame) {
+ int width = frame.GetWidth();
+ int height = frame.GetHeight();
+ const uint8* y = frame.GetYPlane();
+ const uint8* u = frame.GetUPlane();
+ const uint8* v = frame.GetVPlane();
+ int size = width * height * 3 / 2;
+ bool u_near = (u - y) < size;
+ bool v_near = (v - y) < size;
+ return u_near && v_near;
+ }
+
+ static bool IsEqual(const WebRtcVideoFrame& frame,
+ size_t width, size_t height,
+ size_t pixel_width, size_t pixel_height,
+ int64 elapsed_time, int64 time_stamp,
+ const uint8* y, uint32 ypitch,
+ const uint8* u, uint32 upitch,
+ const uint8* v, uint32 vpitch,
+ int max_error) {
+ if (!IsFrameContiguous(frame)) {
+ LOG(LS_INFO) << "lmi frame is not contiguous";
+ }
+ return IsSize(frame, width, height) &&
+ frame.GetPixelWidth() == pixel_width &&
+ frame.GetPixelHeight() == pixel_height &&
+ frame.GetElapsedTime() == elapsed_time &&
+ frame.GetTimeStamp() == time_stamp &&
+ IsPlaneEqual("y", frame.GetYPlane(), frame.GetYPitch(), y, ypitch,
+ width, height, max_error) &&
+ IsPlaneEqual("u", frame.GetUPlane(), frame.GetUPitch(), u, upitch,
+ width / 2, height / 2, max_error) &&
+ IsPlaneEqual("v", frame.GetVPlane(), frame.GetVPitch(), v, vpitch,
+ width / 2, height / 2, max_error);
+ }
+
+ static bool IsEqual(const WebRtcVideoFrame& frame1,
+ const WebRtcVideoFrame& frame2,
+ int max_error) {
+ return IsEqual(frame1, frame2.GetWidth(), frame2.GetHeight(),
+ frame2.GetPixelWidth(), frame2.GetPixelHeight(),
+ frame2.GetElapsedTime(), frame2.GetTimeStamp(),
+ frame2.GetYPlane(), frame2.GetYPitch(),
+ frame2.GetUPlane(), frame2.GetUPitch(),
+ frame2.GetVPlane(), frame2.GetVPitch(),
+ max_error);
+ }
+
+ protected:
+ int repeat_;
+};
+
+TEST_F(WebRtcVideoFrameTest, ConvertToARGBBuffer) {
+ size_t out_size = kWidth * kHeight * 4;
+ talk_base::scoped_array<uint8> outbuf(new uint8[out_size + kAlignment]);
+ uint8 *out = ALIGNP(outbuf.get(), kAlignment);
+ WebRtcVideoFrame frame;
+ ASSERT_TRUE(LoadFrame(kImageFilename, FOURCC_I420, kWidth, kHeight,
+ &frame, ROTATION_0));
+
+ // TODO: Add test to convert these back to I420, to ensure the
+ // conversion is done correctly.
+ for (int i = 0; i < repeat_; ++i) {
+ EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB,
+ out,
+ out_size, kWidth * 4));
+ }
+}
+
+// Test basic contruction of an image from an I420 buffer.
+TEST_F(WebRtcVideoFrameTest, InitI420) {
+ WebRtcVideoFrame frame;
+ EXPECT_TRUE(IsNull(frame));
+ talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(kImageFilename));
+ ASSERT_TRUE(ms.get() != NULL);
+ size_t data_size;
+ ASSERT_TRUE(ms->GetSize(&data_size));
+ uint8* buf = reinterpret_cast<uint8*>(ms->GetBuffer());
+ EXPECT_TRUE(LoadFrame(buf, data_size, FOURCC_I420,
+ kWidth, kHeight, &frame, ROTATION_0));
+
+ const uint8* y = reinterpret_cast<uint8*>(ms->GetBuffer());
+ const uint8* u = y + kWidth * kHeight;
+ const uint8* v = u + kWidth * kHeight / 4;
+ EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, 0,
+ y, kWidth, u, kWidth / 2, v, kWidth / 2, 0));
+}
+
+// Test constructing an image from a I420 buffer
+TEST_F(WebRtcVideoFrameTest, ConstructI420) {
+ WebRtcVideoFrame frame;
+ talk_base::scoped_ptr<talk_base::MemoryStream> ms(
+ CreateYuv420Sample(kWidth, kHeight));
+ EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420,
+ kWidth, kHeight, &frame, ROTATION_0));
+
+ const uint8* y = reinterpret_cast<uint8*>(ms.get()->GetBuffer());
+ const uint8* u = y + kWidth * kHeight;
+ const uint8* v = u + kWidth * kHeight / 4;
+ EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, 0,
+ y, kWidth, u, kWidth / 2, v, kWidth / 2, 0));
+}
+
+// Test creating an empty image and initing it to black.
+TEST_F(WebRtcVideoFrameTest, ConstructBlack) {
+ WebRtcVideoFrame frame;
+ for (int i = 0; i < repeat_; ++i) {
+ EXPECT_TRUE(frame.InitToBlack(kWidth, kHeight, 0, 0));
+ }
+ EXPECT_TRUE(IsSize(frame, kWidth, kHeight));
+ EXPECT_EQ(16, *frame.GetYPlane());
+ EXPECT_EQ(128, *frame.GetUPlane());
+ EXPECT_EQ(128, *frame.GetVPlane());
+}
+
+TEST_F(WebRtcVideoFrameTest, Copy) {
+ WebRtcVideoFrame frame1;
+ talk_base::scoped_ptr<cricket::WebRtcVideoFrame> frame2;
+ ASSERT_TRUE(LoadFrame(kImageFilename, FOURCC_I420, kWidth, kHeight,
+ &frame1, ROTATION_0));
+ frame2.reset(static_cast<WebRtcVideoFrame*>(frame1.Copy()));
+ EXPECT_TRUE(IsEqual(frame1, *frame2.get(), 0));
+}
+
+TEST_F(WebRtcVideoFrameTest, CopyToBuffer) {
+ size_t out_size = kWidth * kHeight * 3 / 2;
+ talk_base::scoped_array<uint8> out(new uint8[out_size]);
+ WebRtcVideoFrame frame;
+ talk_base::scoped_ptr<talk_base::MemoryStream> ms(LoadSample(kImageFilename));
+ ASSERT_TRUE(ms.get() != NULL);
+ size_t data_size;
+ ASSERT_TRUE(ms->GetSize(&data_size));
+ EXPECT_TRUE(LoadFrame(reinterpret_cast<uint8*>(ms->GetBuffer()),
+ data_size, FOURCC_I420,
+ kWidth, kHeight, &frame, ROTATION_0));
+ for (int i = 0; i < repeat_; ++i) {
+ EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
+ }
+ EXPECT_EQ(0, memcmp(out.get(), ms->GetBuffer(), out_size));
+}
+
+TEST_F(WebRtcVideoFrameTest, CopyToBuffer1Pixel) {
+ size_t out_size = 3;
+ talk_base::scoped_array<uint8> out(new uint8[out_size + 1]);
+ memset(out.get(), 0xfb, out_size + 1); // Fill buffer
+ uint8 pixel[3] = { 1, 2, 3 };
+ WebRtcVideoFrame frame;
+ EXPECT_TRUE(LoadFrame(pixel, sizeof(pixel), FOURCC_I420,
+ 1, 1, &frame, ROTATION_0));
+ for (int i = 0; i < repeat_; ++i) {
+ EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
+ }
+ EXPECT_EQ(1, out.get()[0]); // Check Y. Should be 1.
+ EXPECT_EQ(2, out.get()[1]); // Check U. Should be 2.
+ EXPECT_EQ(3, out.get()[2]); // Check V. Should be 3.
+ EXPECT_EQ(0xfb, out.get()[3]); // Check sentinel is still intact.
+}
+
+// TODO: Merge this with the LmiVideoFrame test for more test cases
+// when they are supported.
diff --git a/talk/session/phone/webrtcvie.h b/talk/session/phone/webrtcvie.h
new file mode 100644
index 0000000..d6ef007
--- /dev/null
+++ b/talk/session/phone/webrtcvie.h
@@ -0,0 +1,144 @@
+/*
+ * 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_SESSION_PHONE_WEBRTCVIE_H_
+#define TALK_SESSION_PHONE_WEBRTCVIE_H_
+
+#include "talk/base/common.h"
+#include "talk/session/phone/webrtccommon.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "common_types.h"
+#include "modules/interface/module_common_types.h"
+#include "modules/video_capture/main/interface/video_capture.h"
+#include "modules/video_render/main/interface/video_render.h"
+#include "video_engine/main/interface/vie_base.h"
+#include "video_engine/main/interface/vie_capture.h"
+#include "video_engine/main/interface/vie_codec.h"
+#include "video_engine/main/interface/vie_errors.h"
+#include "video_engine/main/interface/vie_image_process.h"
+#include "video_engine/main/interface/vie_network.h"
+#include "video_engine/main/interface/vie_render.h"
+#include "video_engine/main/interface/vie_rtp_rtcp.h"
+#else
+#include "third_party/webrtc/files/include/common_types.h"
+#include "third_party/webrtc/files/include/module_common_types.h"
+#include "third_party/webrtc/files/include/video_capture.h"
+#include "third_party/webrtc/files/include/video_render.h"
+#include "third_party/webrtc/files/include/vie_base.h"
+#include "third_party/webrtc/files/include/vie_capture.h"
+#include "third_party/webrtc/files/include/vie_codec.h"
+#include "third_party/webrtc/files/include/vie_errors.h"
+#include "third_party/webrtc/files/include/vie_image_process.h"
+#include "third_party/webrtc/files/include/vie_network.h"
+#include "third_party/webrtc/files/include/vie_render.h"
+#include "third_party/webrtc/files/include/vie_rtp_rtcp.h"
+#endif // WEBRTC_RELATIVE_PATH
+
+namespace cricket {
+
+// all tracing macros should go to a common file
+
+// automatically handles lifetime of VideoEngine
+class scoped_vie_engine {
+ public:
+ explicit scoped_vie_engine(webrtc::VideoEngine* e) : ptr(e) {}
+ // VERIFY, to ensure that there are no leaks at shutdown
+ ~scoped_vie_engine() {
+ if (ptr) {
+ webrtc::VideoEngine::Delete(ptr);
+ }
+ }
+ webrtc::VideoEngine* get() const { return ptr; }
+ private:
+ webrtc::VideoEngine* ptr;
+};
+
+// scoped_ptr class to handle obtaining and releasing VideoEngine
+// interface pointers
+template<class T> class scoped_vie_ptr {
+ public:
+ explicit scoped_vie_ptr(const scoped_vie_engine& e)
+ : ptr(T::GetInterface(e.get())) {}
+ explicit scoped_vie_ptr(T* p) : ptr(p) {}
+ ~scoped_vie_ptr() { if (ptr) ptr->Release(); }
+ T* operator->() const { return ptr; }
+ T* get() const { return ptr; }
+ private:
+ T* ptr;
+};
+
+// Utility class for aggregating the various WebRTC interface.
+// Fake implementations can also be injected for testing.
+class ViEWrapper {
+ public:
+ ViEWrapper()
+ : engine_(webrtc::VideoEngine::Create()),
+ base_(engine_), codec_(engine_), capture_(engine_),
+ network_(engine_), render_(engine_), rtp_(engine_),
+ image_(engine_) {
+ }
+
+ ViEWrapper(webrtc::ViEBase* base, webrtc::ViECodec* codec,
+ webrtc::ViECapture* capture, webrtc::ViENetwork* network,
+ webrtc::ViERender* render, webrtc::ViERTP_RTCP* rtp,
+ webrtc::ViEImageProcess* image)
+ : engine_(NULL),
+ base_(base),
+ codec_(codec),
+ capture_(capture),
+ network_(network),
+ render_(render),
+ rtp_(rtp),
+ image_(image) {
+ }
+
+ virtual ~ViEWrapper() {}
+ webrtc::VideoEngine* engine() { return engine_.get(); }
+ webrtc::ViEBase* base() { return base_.get(); }
+ webrtc::ViECodec* codec() { return codec_.get(); }
+ webrtc::ViECapture* capture() { return capture_.get(); }
+ webrtc::ViENetwork* network() { return network_.get(); }
+ webrtc::ViERender* render() { return render_.get(); }
+ webrtc::ViERTP_RTCP* rtp() { return rtp_.get(); }
+ webrtc::ViEImageProcess* sync() { return image_.get(); }
+ int error() { return base_->LastError(); }
+
+ private:
+ scoped_vie_engine engine_;
+ scoped_vie_ptr<webrtc::ViEBase> base_;
+ scoped_vie_ptr<webrtc::ViECodec> codec_;
+ scoped_vie_ptr<webrtc::ViECapture> capture_;
+ scoped_vie_ptr<webrtc::ViENetwork> network_;
+ scoped_vie_ptr<webrtc::ViERender> render_;
+ scoped_vie_ptr<webrtc::ViERTP_RTCP> rtp_;
+ scoped_vie_ptr<webrtc::ViEImageProcess> image_;
+};
+}
+
+#endif // TALK_SESSION_PHONE_WEBRTCVIE_H_
diff --git a/talk/session/phone/webrtcvoe.h b/talk/session/phone/webrtcvoe.h
new file mode 100644
index 0000000..398928e
--- /dev/null
+++ b/talk/session/phone/webrtcvoe.h
@@ -0,0 +1,196 @@
+/*
+ * 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_SESSION_PHONE_WEBRTCVOE_H_
+#define TALK_SESSION_PHONE_WEBRTCVOE_H_
+
+#include "talk/base/common.h"
+#include "talk/session/phone/webrtccommon.h"
+
+#ifdef WEBRTC_RELATIVE_PATH
+#include "common_types.h"
+#include "modules/audio_device/main/interface/audio_device.h"
+#include "voice_engine/main/interface/voe_audio_processing.h"
+#include "voice_engine/main/interface/voe_base.h"
+#include "voice_engine/main/interface/voe_codec.h"
+#include "voice_engine/main/interface/voe_dtmf.h"
+#include "voice_engine/main/interface/voe_errors.h"
+#include "voice_engine/main/interface/voe_external_media.h"
+#include "voice_engine/main/interface/voe_file.h"
+#include "voice_engine/main/interface/voe_hardware.h"
+#include "voice_engine/main/interface/voe_neteq_stats.h"
+#include "voice_engine/main/interface/voe_network.h"
+#include "voice_engine/main/interface/voe_rtp_rtcp.h"
+#include "voice_engine/main/interface/voe_video_sync.h"
+#include "voice_engine/main/interface/voe_volume_control.h"
+#else
+#include "third_party/webrtc/files/include/audio_device.h"
+#include "third_party/webrtc/files/include/common_types.h"
+#include "third_party/webrtc/files/include/voe_audio_processing.h"
+#include "third_party/webrtc/files/include/voe_base.h"
+#include "third_party/webrtc/files/include/voe_codec.h"
+#include "third_party/webrtc/files/include/voe_dtmf.h"
+#include "third_party/webrtc/files/include/voe_errors.h"
+#include "third_party/webrtc/files/include/voe_external_media.h"
+#include "third_party/webrtc/files/include/voe_file.h"
+#include "third_party/webrtc/files/include/voe_hardware.h"
+#include "third_party/webrtc/files/include/voe_neteq_stats.h"
+#include "third_party/webrtc/files/include/voe_network.h"
+#include "third_party/webrtc/files/include/voe_rtp_rtcp.h"
+#include "third_party/webrtc/files/include/voe_video_sync.h"
+#include "third_party/webrtc/files/include/voe_volume_control.h"
+#endif // WEBRTC_RELATIVE_PATH
+
+namespace cricket {
+// automatically handles lifetime of WebRtc VoiceEngine
+class scoped_voe_engine {
+ public:
+ explicit scoped_voe_engine(webrtc::VoiceEngine* e) : ptr(e) {}
+ // VERIFY, to ensure that there are no leaks at shutdown
+ ~scoped_voe_engine() { if (ptr) VERIFY(webrtc::VoiceEngine::Delete(ptr)); }
+ // Releases the current pointer.
+ void reset() {
+ if (ptr) {
+ VERIFY(webrtc::VoiceEngine::Delete(ptr));
+ ptr = NULL;
+ }
+ }
+ webrtc::VoiceEngine* get() const { return ptr; }
+ private:
+ webrtc::VoiceEngine* ptr;
+};
+
+// scoped_ptr class to handle obtaining and releasing WebRTC interface pointers
+template<class T>
+class scoped_voe_ptr {
+ public:
+ explicit scoped_voe_ptr(const scoped_voe_engine& e)
+ : ptr(T::GetInterface(e.get())) {}
+ explicit scoped_voe_ptr(T* p) : ptr(p) {}
+ ~scoped_voe_ptr() { if (ptr) ptr->Release(); }
+ T* operator->() const { return ptr; }
+ T* get() const { return ptr; }
+
+ // Releases the current pointer.
+ void reset() {
+ if (ptr) {
+ ptr->Release();
+ ptr = NULL;
+ }
+ }
+
+ private:
+ T* ptr;
+};
+
+// Utility class for aggregating the various WebRTC interface.
+// Fake implementations can also be injected for testing.
+class VoEWrapper {
+ public:
+ VoEWrapper()
+ : engine_(webrtc::VoiceEngine::Create()), processing_(engine_),
+ base_(engine_), codec_(engine_), dtmf_(engine_), file_(engine_),
+ hw_(engine_), media_(engine_), neteq_(engine_), network_(engine_),
+ rtp_(engine_), sync_(engine_), volume_(engine_) {
+ }
+ VoEWrapper(webrtc::VoEAudioProcessing* processing,
+ webrtc::VoEBase* base,
+ webrtc::VoECodec* codec,
+ webrtc::VoEDtmf* dtmf,
+ webrtc::VoEFile* file,
+ webrtc::VoEHardware* hw,
+ webrtc::VoEExternalMedia* media,
+ webrtc::VoENetEqStats* neteq,
+ webrtc::VoENetwork* network,
+ webrtc::VoERTP_RTCP* rtp,
+ webrtc::VoEVideoSync* sync,
+ webrtc::VoEVolumeControl* volume)
+ : engine_(NULL),
+ processing_(processing),
+ base_(base),
+ codec_(codec),
+ dtmf_(dtmf),
+ file_(file),
+ hw_(hw),
+ media_(media),
+ neteq_(neteq),
+ network_(network),
+ rtp_(rtp),
+ sync_(sync),
+ volume_(volume) {
+ }
+ ~VoEWrapper() {}
+ webrtc::VoiceEngine* engine() const { return engine_.get(); }
+ webrtc::VoEAudioProcessing* processing() const { return processing_.get(); }
+ webrtc::VoEBase* base() const { return base_.get(); }
+ webrtc::VoECodec* codec() const { return codec_.get(); }
+ webrtc::VoEDtmf* dtmf() const { return dtmf_.get(); }
+ webrtc::VoEFile* file() const { return file_.get(); }
+ webrtc::VoEHardware* hw() const { return hw_.get(); }
+ webrtc::VoEExternalMedia* media() const { return media_.get(); }
+ webrtc::VoENetEqStats* neteq() const { return neteq_.get(); }
+ webrtc::VoENetwork* network() const { return network_.get(); }
+ webrtc::VoERTP_RTCP* rtp() const { return rtp_.get(); }
+ webrtc::VoEVideoSync* sync() const { return sync_.get(); }
+ webrtc::VoEVolumeControl* volume() const { return volume_.get(); }
+ int error() { return base_->LastError(); }
+
+ private:
+ scoped_voe_engine engine_;
+ scoped_voe_ptr<webrtc::VoEAudioProcessing> processing_;
+ scoped_voe_ptr<webrtc::VoEBase> base_;
+ scoped_voe_ptr<webrtc::VoECodec> codec_;
+ scoped_voe_ptr<webrtc::VoEDtmf> dtmf_;
+ scoped_voe_ptr<webrtc::VoEFile> file_;
+ scoped_voe_ptr<webrtc::VoEHardware> hw_;
+ scoped_voe_ptr<webrtc::VoEExternalMedia> media_;
+ scoped_voe_ptr<webrtc::VoENetEqStats> neteq_;
+ scoped_voe_ptr<webrtc::VoENetwork> network_;
+ scoped_voe_ptr<webrtc::VoERTP_RTCP> rtp_;
+ scoped_voe_ptr<webrtc::VoEVideoSync> sync_;
+ scoped_voe_ptr<webrtc::VoEVolumeControl> volume_;
+};
+
+// Adds indirection to static WebRtc functions, allowing them to be mocked.
+class VoETraceWrapper {
+ public:
+ virtual ~VoETraceWrapper() {}
+
+ virtual int SetTraceFilter(const unsigned int filter) {
+ return webrtc::VoiceEngine::SetTraceFilter(filter);
+ }
+ virtual int SetTraceFile(const char* fileNameUTF8) {
+ return webrtc::VoiceEngine::SetTraceFile(fileNameUTF8);
+ }
+ virtual int SetTraceCallback(webrtc::TraceCallback* callback) {
+ return webrtc::VoiceEngine::SetTraceCallback(callback);
+ }
+};
+}
+
+#endif // TALK_SESSION_PHONE_WEBRTCVOE_H_
diff --git a/talk/session/phone/webrtcvoiceengine.cc b/talk/session/phone/webrtcvoiceengine.cc
new file mode 100644
index 0000000..a53dfc1
--- /dev/null
+++ b/talk/session/phone/webrtcvoiceengine.cc
@@ -0,0 +1,1988 @@
+/*
+ * 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.
+ */
+
+
+#ifdef HAVE_WEBRTC_VOICE
+
+#include "talk/session/phone/webrtcvoiceengine.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <string>
+#include <vector>
+
+#include "talk/base/base64.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/webrtcvoe.h"
+
+#ifdef WIN32
+#include <objbase.h> // NOLINT
+#endif
+
+namespace cricket {
+
+// For Linux/Mac, using the default device is done by specifying index 0 for
+// VoE 4.0 and not -1 (which was the case for VoE 3.5).
+//
+// On Windows Vista and newer, Microsoft introduced the concept of "Default
+// Communications Device". This means that there are two types of default
+// devices (old Wave Audio style default and Default Communications Device).
+//
+// On Windows systems which only support Wave Audio style default, uses either
+// -1 or 0 to select the default device.
+//
+// On Windows systems which support both "Default Communication Device" and
+// old Wave Audio style default, use -1 for Default Communications Device and
+// -2 for Wave Audio style default, which is what we want to use for clips.
+// It's not clear yet whether the -2 index is handled properly on other OSes.
+
+#ifdef WIN32
+static const int kDefaultAudioDeviceId = -1;
+static const int kDefaultSoundclipDeviceId = -2;
+#else
+static const int kDefaultAudioDeviceId = 0;
+#endif
+
+// extension header for audio levels, as defined in
+// http://tools.ietf.org/html/draft-ietf-avtext-client-to-mixer-audio-level-03
+static const char kRtpAudioLevelHeaderExtension[] =
+ "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
+
+static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
+ const char* delim = "\r\n";
+ for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
+ LOG_V(sev) << tok;
+ }
+}
+
+static const char kL16CodecName[] = "L16";
+
+// WebRtcVoiceEngine
+const WebRtcVoiceEngine::CodecPref WebRtcVoiceEngine::kCodecPrefs[] = {
+ { "ISAC", 16000 },
+ { "ISAC", 32000 },
+ { "speex", 16000 },
+ { "G722", 16000 },
+ { "iLBC", 8000 },
+ { "speex", 8000 },
+ { "PCMU", 8000 },
+ { "PCMA", 8000 },
+ { "CN", 32000 },
+ { "CN", 16000 },
+ { "CN", 8000 },
+ { "red", 8000 },
+ { "telephone-event", 8000 },
+};
+
+class WebRtcSoundclipMedia : public SoundclipMedia {
+ public:
+ explicit WebRtcSoundclipMedia(WebRtcVoiceEngine *engine)
+ : engine_(engine), webrtc_channel_(-1) {
+ engine_->RegisterSoundclip(this);
+ }
+
+ virtual ~WebRtcSoundclipMedia() {
+ engine_->UnregisterSoundclip(this);
+ if (webrtc_channel_ != -1) {
+ if (engine_->voe_sc()->base()->DeleteChannel(webrtc_channel_)
+ == -1) {
+ LOG_RTCERR1(DeleteChannel, webrtc_channel_);
+ }
+ }
+ }
+
+ bool Init() {
+ webrtc_channel_ = engine_->voe_sc()->base()->CreateChannel();
+ if (webrtc_channel_ == -1) {
+ LOG_RTCERR0(CreateChannel);
+ return false;
+ }
+ return true;
+ }
+
+ bool Enable() {
+ if (engine_->voe_sc()->base()->StartPlayout(webrtc_channel_) == -1) {
+ LOG_RTCERR1(StartPlayout, webrtc_channel_);
+ return false;
+ }
+ return true;
+ }
+
+ bool Disable() {
+ if (engine_->voe_sc()->base()->StopPlayout(webrtc_channel_) == -1) {
+ LOG_RTCERR1(StopPlayout, webrtc_channel_);
+ return false;
+ }
+ return true;
+ }
+
+ virtual bool PlaySound(const char *buf, int len, int flags) {
+ // Must stop playing the current sound (if any), because we are about to
+ // modify the stream.
+ if (engine_->voe_sc()->file()->StopPlayingFileLocally(webrtc_channel_)
+ == -1) {
+ LOG_RTCERR1(StopPlayingFileLocally, webrtc_channel_);
+ return false;
+ }
+
+ if (buf) {
+ stream_.reset(new WebRtcSoundclipStream(buf, len));
+ stream_->set_loop((flags & SF_LOOP) != 0);
+ stream_->Rewind();
+
+ // Play it.
+ if (engine_->voe_sc()->file()->StartPlayingFileLocally(
+ webrtc_channel_, stream_.get()) == -1) {
+ LOG_RTCERR2(StartPlayingFileLocally, webrtc_channel_, stream_.get());
+ LOG(LS_ERROR) << "Unable to start soundclip";
+ return false;
+ }
+ } else {
+ stream_.reset();
+ }
+ return true;
+ }
+
+ int GetLastEngineError() const { return engine_->voe_sc()->error(); }
+
+ private:
+ WebRtcVoiceEngine *engine_;
+ int webrtc_channel_;
+ talk_base::scoped_ptr<WebRtcSoundclipStream> stream_;
+};
+
+WebRtcVoiceEngine::WebRtcVoiceEngine()
+ : voe_wrapper_(new VoEWrapper()),
+ voe_wrapper_sc_(new VoEWrapper()),
+ tracing_(new VoETraceWrapper()),
+ adm_(NULL),
+ adm_sc_(NULL),
+ log_level_(kDefaultLogSeverity),
+ is_dumping_aec_(false),
+ desired_local_monitor_enable_(false) {
+ Construct();
+}
+
+WebRtcVoiceEngine::WebRtcVoiceEngine(webrtc::AudioDeviceModule* adm,
+ webrtc::AudioDeviceModule* adm_sc)
+ : voe_wrapper_(new VoEWrapper()),
+ voe_wrapper_sc_(new VoEWrapper()),
+ tracing_(new VoETraceWrapper()),
+ adm_(adm),
+ adm_sc_(adm_sc),
+ log_level_(kDefaultLogSeverity),
+ is_dumping_aec_(false),
+ desired_local_monitor_enable_(false) {
+ Construct();
+}
+
+WebRtcVoiceEngine::WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
+ VoEWrapper* voe_wrapper_sc,
+ VoETraceWrapper* tracing)
+ : voe_wrapper_(voe_wrapper),
+ voe_wrapper_sc_(voe_wrapper_sc),
+ tracing_(tracing),
+ adm_(NULL),
+ adm_sc_(NULL),
+ log_level_(kDefaultLogSeverity),
+ is_dumping_aec_(false),
+ desired_local_monitor_enable_(false) {
+ Construct();
+}
+
+void WebRtcVoiceEngine::Construct() {
+ initialized_ = false;
+ LOG(LS_VERBOSE) << "WebRtcVoiceEngine::WebRtcVoiceEngine";
+ ApplyLogging("");
+ if (tracing_->SetTraceCallback(this) == -1) {
+ LOG_RTCERR0(SetTraceCallback);
+ }
+ if (voe_wrapper_->base()->RegisterVoiceEngineObserver(*this) == -1) {
+ LOG_RTCERR0(RegisterVoiceEngineObserver);
+ }
+ // Clear the default agc state.
+ memset(&default_agc_config_, 0, sizeof(default_agc_config_));
+
+ // Load our audio codec list
+ LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
+ int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
+ for (int i = 0; i < ncodecs; ++i) {
+ webrtc::CodecInst gcodec;
+ if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
+ // Skip the codecs that we don't support.
+ if (strcmp(gcodec.plname, kL16CodecName) == 0) {
+ continue;
+ }
+ int pref = GetCodecPreference(gcodec.plname, gcodec.plfreq);
+ if (pref != -1) {
+ if (gcodec.rate == -1) gcodec.rate = 0;
+ AudioCodec codec(gcodec.pltype, gcodec.plname, gcodec.plfreq,
+ gcodec.rate, gcodec.channels, pref);
+ LOG(LS_INFO) << gcodec.plname << "/" << gcodec.plfreq << "/" \
+ << gcodec.channels << " " << gcodec.pltype;
+ codecs_.push_back(codec);
+ }
+ }
+ }
+ // Make sure they are in local preference order
+ std::sort(codecs_.begin(), codecs_.end(), &AudioCodec::Preferable);
+}
+
+WebRtcVoiceEngine::~WebRtcVoiceEngine() {
+ LOG(LS_VERBOSE) << "WebRtcVoiceEngine::~WebRtcVoiceEngine";
+ if (voe_wrapper_->base()->DeRegisterVoiceEngineObserver() == -1) {
+ LOG_RTCERR0(DeRegisterVoiceEngineObserver);
+ }
+ if (adm_) {
+ voe_wrapper_.reset();
+ webrtc::AudioDeviceModule::Destroy(adm_);
+ adm_ = NULL;
+ }
+ if (adm_sc_) {
+ voe_wrapper_sc_.reset();
+ webrtc::AudioDeviceModule::Destroy(adm_sc_);
+ adm_sc_ = NULL;
+ }
+
+ tracing_->SetTraceCallback(NULL);
+}
+
+bool WebRtcVoiceEngine::Init() {
+ LOG(LS_INFO) << "WebRtcVoiceEngine::Init";
+ bool res = InitInternal();
+ if (res) {
+ LOG(LS_INFO) << "WebRtcVoiceEngine::Init Done!";
+ } else {
+ LOG(LS_ERROR) << "WebRtcVoiceEngine::Init failed";
+ Terminate();
+ }
+ return res;
+}
+
+bool WebRtcVoiceEngine::InitInternal() {
+ // Temporarily turn logging level up for the Init call
+ int old_level = log_level_;
+ log_level_ = talk_base::_min(log_level_,
+ static_cast<int>(talk_base::LS_INFO));
+ ApplyLogging("");
+
+ if (adm_) {
+ if (voe_wrapper_->base()->RegisterAudioDeviceModule(*adm_) == -1) {
+ LOG_RTCERR0_EX(Init, voe_wrapper_->error());
+ return false;
+ }
+ }
+ if (adm_sc_) {
+ if (voe_wrapper_sc_->base()->RegisterAudioDeviceModule(*adm_sc_) == -1) {
+ LOG_RTCERR0_EX(Init, voe_wrapper_sc_->error());
+ return false;
+ }
+ }
+
+ // Init WebRtc VoiceEngine, enabling AEC logging if specified in SetLogging.
+ if (voe_wrapper_->base()->Init() == -1) {
+ LOG_RTCERR0_EX(Init, voe_wrapper_->error());
+ return false;
+ }
+
+ // Restore the previous log level and apply the log filter.
+ log_level_ = old_level;
+ ApplyLogging(log_filter_);
+
+ // Log the VoiceEngine version info
+ char buffer[1024] = "";
+ voe_wrapper_->base()->GetVersion(buffer);
+ LOG(LS_INFO) << "WebRtc VoiceEngine Version:";
+ LogMultiline(talk_base::LS_INFO, buffer);
+
+ // Turn on AEC and AGC by default.
+ if (!SetOptions(
+ MediaEngineInterface::ECHO_CANCELLATION |
+ MediaEngineInterface::AUTO_GAIN_CONTROL)) {
+ return false;
+ }
+
+ // Save the default AGC configuration settings.
+ if (voe_wrapper_->processing()->SetAgcConfig(default_agc_config_) == -1) {
+ LOG_RTCERR0(GetAGCConfig);
+ return false;
+ }
+
+ // Print our codec list again for the call diagnostic log
+ LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
+ for (std::vector<AudioCodec>::const_iterator it = codecs_.begin();
+ it != codecs_.end(); ++it) {
+ LOG(LS_INFO) << it->name << "/" << it->clockrate << "/"
+ << it->channels << " " << it->id;
+ }
+
+#if defined(LINUX) && !defined(HAVE_LIBPULSE)
+ voe_wrapper_sc_->hw()->SetAudioDeviceLayer(webrtc::kAudioLinuxAlsa);
+#endif
+
+ // Initialize the VoiceEngine instance that we'll use to play out sound clips.
+ if (voe_wrapper_sc_->base()->Init() == -1) {
+ LOG_RTCERR0_EX(Init, voe_wrapper_sc_->error());
+ return false;
+ }
+
+ // On Windows, tell it to use the default sound (not communication) devices.
+ // First check whether there is a valid sound device for playback.
+ // TODO: Clean this up when we support setting the soundclip device.
+#ifdef WIN32
+ int num_of_devices = 0;
+ if (voe_wrapper_sc_->hw()->GetNumOfPlayoutDevices(num_of_devices) != -1 &&
+ num_of_devices > 0) {
+ if (voe_wrapper_sc_->hw()->SetPlayoutDevice(kDefaultSoundclipDeviceId)
+ == -1) {
+ LOG_RTCERR1_EX(SetPlayoutDevice, kDefaultSoundclipDeviceId,
+ voe_wrapper_sc_->error());
+ return false;
+ }
+ } else {
+ LOG(LS_WARNING) << "No valid sound playout device found.";
+ }
+#endif
+
+ initialized_ = true;
+ return true;
+}
+
+void WebRtcVoiceEngine::Terminate() {
+ LOG(LS_INFO) << "WebRtcVoiceEngine::Terminate";
+ initialized_ = false;
+
+ if (is_dumping_aec_) {
+ if (voe_wrapper_->processing()->StopDebugRecording() == -1) {
+ LOG_RTCERR0(StopDebugRecording);
+ }
+ is_dumping_aec_ = false;
+ }
+
+ voe_wrapper_sc_->base()->Terminate();
+ voe_wrapper_->base()->Terminate();
+ desired_local_monitor_enable_ = false;
+}
+
+int WebRtcVoiceEngine::GetCapabilities() {
+ return AUDIO_SEND | AUDIO_RECV;
+}
+
+VoiceMediaChannel *WebRtcVoiceEngine::CreateChannel() {
+ WebRtcVoiceMediaChannel* ch = new WebRtcVoiceMediaChannel(this);
+ if (!ch->valid()) {
+ delete ch;
+ ch = NULL;
+ }
+ return ch;
+}
+
+SoundclipMedia *WebRtcVoiceEngine::CreateSoundclip() {
+ WebRtcSoundclipMedia *soundclip = new WebRtcSoundclipMedia(this);
+ if (!soundclip->Init() || !soundclip->Enable()) {
+ delete soundclip;
+ return NULL;
+ }
+ return soundclip;
+}
+
+bool WebRtcVoiceEngine::SetOptions(int options) {
+ // NS and typing detection are always on, if supported.
+ bool aec = (options & MediaEngineInterface::ECHO_CANCELLATION) ? true : false;
+ bool agc = (options & MediaEngineInterface::AUTO_GAIN_CONTROL) ? true : false;
+#if !defined(IOS) && !defined(ANDROID)
+ if (voe_wrapper_->processing()->SetEcStatus(aec) == -1) {
+ LOG_RTCERR1(SetEcStatus, aec);
+ return false;
+ }
+
+ if (voe_wrapper_->processing()->SetAgcStatus(agc) == -1) {
+ LOG_RTCERR1(SetAgcStatus, agc);
+ return false;
+ }
+
+ if (voe_wrapper_->processing()->SetNsStatus(true) == -1) {
+ LOG_RTCERR1(SetNsStatus, true);
+ return false;
+ }
+
+ if (voe_wrapper_->processing()->SetTypingDetectionStatus(true) == -1) {
+ // In case of error, log the info and continue
+ LOG_RTCERR1(SetTypingDetectionStatus, true);
+ }
+#else
+ if (voe_wrapper_->processing()->SetEcStatus(aec, kEcAecm) == -1) {
+ LOG_RTCERR2(SetEcStatus, aec, kEcAecm);
+ return false;
+ }
+
+ if (aec) {
+ // Use speakerphone mode with comfort noise generation for mobile.
+ if (voe_wrapper_->processing()->SetAecmMode(kAecmSpeakerphone, true) != 0) {
+ LOG_RTCERR2(SetAecmMode, kAecmSpeakerphone, true);
+ }
+ }
+
+ // On mobile, GIPS recommends fixed AGC (not adaptive)
+ if (voe_wrapper_->processing()->SetAgcStatus(agc, kAgcFixedDigital) == -1) {
+ LOG_RTCERR2(SetAgcStatus, agc, kAgcFixedDigital);
+ return false;
+ }
+
+ // On mobile, GIPS recommends moderate aggressiveness.
+ if (voe_wrapper_->processing()->SetNsStatus(true,
+ kNsModerateSuppression) == -1) {
+ LOG_RTCERR2(SetNsStatus, ns, kNsModerateSuppression);
+ return false;
+ }
+
+ // No typing detection support on iOS or Android.
+#endif // !IOS && !ANDROID
+
+ return true;
+}
+
+struct ResumeEntry {
+ ResumeEntry(WebRtcVoiceMediaChannel *c, bool p, SendFlags s)
+ : channel(c),
+ playout(p),
+ send(s) {
+ }
+
+ WebRtcVoiceMediaChannel *channel;
+ bool playout;
+ SendFlags send;
+};
+
+// TODO: Refactor this so that the core logic can be used to set the
+// soundclip device. At that time, reinstate the soundclip pause/resume code.
+bool WebRtcVoiceEngine::SetDevices(const Device* in_device,
+ const Device* out_device) {
+#if !defined(IOS) && !defined(ANDROID)
+ int in_id = in_device ? talk_base::FromString<int>(in_device->id) :
+ kDefaultAudioDeviceId;
+ int out_id = out_device ? talk_base::FromString<int>(out_device->id) :
+ kDefaultAudioDeviceId;
+ // The device manager uses -1 as the default device, which was the case for
+ // VoE 3.5. VoE 4.0, however, uses 0 as the default in Linux and Mac.
+#ifndef WIN32
+ if (-1 == in_id) {
+ in_id = kDefaultAudioDeviceId;
+ }
+ if (-1 == out_id) {
+ out_id = kDefaultAudioDeviceId;
+ }
+#endif
+
+ std::string in_name = (in_id != kDefaultAudioDeviceId) ?
+ in_device->name : "Default device";
+ std::string out_name = (out_id != kDefaultAudioDeviceId) ?
+ out_device->name : "Default device";
+ LOG(LS_INFO) << "Setting microphone to (id=" << in_id << ", name=" << in_name
+ << ") and speaker to (id=" << out_id << ", name=" << out_name
+ << ")";
+
+ // If we're running the local monitor, we need to stop it first.
+ bool ret = true;
+ if (!PauseLocalMonitor()) {
+ LOG(LS_WARNING) << "Failed to pause local monitor";
+ ret = false;
+ }
+
+ // Must also pause all audio playback and capture.
+ for (ChannelList::const_iterator i = channels_.begin();
+ i != channels_.end(); ++i) {
+ WebRtcVoiceMediaChannel *channel = *i;
+ if (!channel->PausePlayout()) {
+ LOG(LS_WARNING) << "Failed to pause playout";
+ ret = false;
+ }
+ if (!channel->PauseSend()) {
+ LOG(LS_WARNING) << "Failed to pause send";
+ ret = false;
+ }
+ }
+
+ // Find the recording device id in VoiceEngine and set recording device.
+ if (!FindWebRtcAudioDeviceId(true, in_name, in_id, &in_id)) {
+ ret = false;
+ }
+ if (ret) {
+ if (voe_wrapper_->hw()->SetRecordingDevice(in_id) == -1) {
+ LOG_RTCERR2(SetRecordingDevice, in_device->name, in_id);
+ ret = false;
+ }
+ }
+
+ // Find the playout device id in VoiceEngine and set playout device.
+ if (!FindWebRtcAudioDeviceId(false, out_name, out_id, &out_id)) {
+ LOG(LS_WARNING) << "Failed to find VoiceEngine device id for " << out_name;
+ ret = false;
+ }
+ if (ret) {
+ if (voe_wrapper_->hw()->SetPlayoutDevice(out_id) == -1) {
+ LOG_RTCERR2(SetPlayoutDevice, out_device->name, out_id);
+ ret = false;
+ }
+ }
+
+ // Resume all audio playback and capture.
+ for (ChannelList::const_iterator i = channels_.begin();
+ i != channels_.end(); ++i) {
+ WebRtcVoiceMediaChannel *channel = *i;
+ if (!channel->ResumePlayout()) {
+ LOG(LS_WARNING) << "Failed to resume playout";
+ ret = false;
+ }
+ if (!channel->ResumeSend()) {
+ LOG(LS_WARNING) << "Failed to resume send";
+ ret = false;
+ }
+ }
+
+ // Resume local monitor.
+ if (!ResumeLocalMonitor()) {
+ LOG(LS_WARNING) << "Failed to resume local monitor";
+ ret = false;
+ }
+
+ if (ret) {
+ LOG(LS_INFO) << "Set microphone to (id=" << in_id <<" name=" << in_name
+ << ") and speaker to (id="<< out_id << " name=" << out_name
+ << ")";
+ }
+
+ return ret;
+#else
+ return true;
+#endif // !IOS && !ANDROID
+}
+
+bool WebRtcVoiceEngine::FindWebRtcAudioDeviceId(
+ bool is_input, const std::string& dev_name, int dev_id, int* rtc_id) {
+ // In Linux, VoiceEngine uses the same device dev_id as the device manager.
+#ifdef LINUX
+ *rtc_id = dev_id;
+ return true;
+#else
+ // In Windows and Mac, we need to find the VoiceEngine device id by name
+ // unless the input dev_id is the default device id.
+ if (kDefaultAudioDeviceId == dev_id) {
+ *rtc_id = dev_id;
+ return true;
+ }
+
+ // Get the number of VoiceEngine audio devices.
+ int count = 0;
+ if (is_input) {
+ if (-1 == voe_wrapper_->hw()->GetNumOfRecordingDevices(count)) {
+ LOG_RTCERR0(GetNumOfRecordingDevices);
+ return false;
+ }
+ } else {
+ if (-1 == voe_wrapper_->hw()->GetNumOfPlayoutDevices(count)) {
+ LOG_RTCERR0(GetNumOfPlayoutDevices);
+ return false;
+ }
+ }
+
+ for (int i = 0; i < count; ++i) {
+ char name[128];
+ char guid[128];
+ if (is_input) {
+ voe_wrapper_->hw()->GetRecordingDeviceName(i, name, guid);
+ LOG(LS_VERBOSE) << "VoiceEngine microphone " << i << ": " << name;
+ } else {
+ voe_wrapper_->hw()->GetPlayoutDeviceName(i, name, guid);
+ LOG(LS_VERBOSE) << "VoiceEngine speaker " << i << ": " << name;
+ }
+
+ std::string webrtc_name(name);
+ if (dev_name.compare(0, webrtc_name.size(), webrtc_name) == 0) {
+ *rtc_id = i;
+ return true;
+ }
+ }
+ LOG(LS_WARNING) << "VoiceEngine cannot find device: " << dev_name;
+ return false;
+#endif
+}
+
+bool WebRtcVoiceEngine::GetOutputVolume(int* level) {
+ unsigned int ulevel;
+ if (voe_wrapper_->volume()->GetSpeakerVolume(ulevel) == -1) {
+ LOG_RTCERR1(GetSpeakerVolume, level);
+ return false;
+ }
+ *level = ulevel;
+ return true;
+}
+
+bool WebRtcVoiceEngine::SetOutputVolume(int level) {
+ ASSERT(level >= 0 && level <= 255);
+ if (voe_wrapper_->volume()->SetSpeakerVolume(level) == -1) {
+ LOG_RTCERR1(SetSpeakerVolume, level);
+ return false;
+ }
+ return true;
+}
+
+int WebRtcVoiceEngine::GetInputLevel() {
+ unsigned int ulevel;
+ return (voe_wrapper_->volume()->GetSpeechInputLevel(ulevel) != -1) ?
+ static_cast<int>(ulevel) : -1;
+}
+
+bool WebRtcVoiceEngine::SetLocalMonitor(bool enable) {
+ desired_local_monitor_enable_ = enable;
+ return ChangeLocalMonitor(desired_local_monitor_enable_);
+}
+
+bool WebRtcVoiceEngine::ChangeLocalMonitor(bool enable) {
+ if (enable && !monitor_.get()) {
+ monitor_.reset(new WebRtcMonitorStream);
+ if (voe_wrapper_->file()->StartRecordingMicrophone(monitor_.get()) == -1) {
+ LOG_RTCERR1(StartRecordingMicrophone, monitor_.get());
+ // Must call Stop() because there are some cases where Start will report
+ // failure but still change the state, and if we leave VE in the on state
+ // then it could crash later when trying to invoke methods on our monitor.
+ voe_wrapper_->file()->StopRecordingMicrophone();
+ monitor_.reset();
+ return false;
+ }
+ } else if (!enable && monitor_.get()) {
+ voe_wrapper_->file()->StopRecordingMicrophone();
+ monitor_.reset();
+ }
+ return true;
+}
+
+bool WebRtcVoiceEngine::PauseLocalMonitor() {
+ return ChangeLocalMonitor(false);
+}
+
+bool WebRtcVoiceEngine::ResumeLocalMonitor() {
+ return ChangeLocalMonitor(desired_local_monitor_enable_);
+}
+
+const std::vector<AudioCodec>& WebRtcVoiceEngine::codecs() {
+ return codecs_;
+}
+
+bool WebRtcVoiceEngine::FindCodec(const AudioCodec& in) {
+ return FindWebRtcCodec(in, NULL);
+}
+
+bool WebRtcVoiceEngine::FindWebRtcCodec(const AudioCodec& in,
+ webrtc::CodecInst* out) {
+ int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
+ for (int i = 0; i < ncodecs; ++i) {
+ webrtc::CodecInst gcodec;
+ if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
+ AudioCodec codec(gcodec.pltype, gcodec.plname,
+ gcodec.plfreq, gcodec.rate, gcodec.channels, 0);
+ if (codec.Matches(in)) {
+ if (out) {
+ // If the codec is VBR and an explicit rate is specified, use it.
+ if (in.bitrate != 0 && gcodec.rate == -1) {
+ gcodec.rate = in.bitrate;
+ }
+ *out = gcodec;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void WebRtcVoiceEngine::SetLogging(int min_sev, const char* filter) {
+ // if min_sev == -1, we keep the current log level.
+ if (min_sev >= 0) {
+ log_level_ = min_sev;
+ }
+ log_filter_ = filter;
+ ApplyLogging(initialized_ ? log_filter_ : "");
+}
+
+int WebRtcVoiceEngine::GetLastEngineError() {
+ return voe_wrapper_->error();
+}
+
+// We suppport three different logging settings for VoiceEngine:
+// 1. Observer callback that goes into talk diagnostic logfile.
+// Use --logfile and --loglevel
+//
+// 2. Encrypted VoiceEngine log for debugging VoiceEngine.
+// Use --voice_loglevel --voice_logfilter "tracefile file_name"
+//
+// 3. EC log and dump for debugging QualityEngine.
+// Use --voice_loglevel --voice_logfilter "recordEC file_name"
+//
+// For more details see: "https://sites.google.com/a/google.com/wavelet/Home/
+// Magic-Flute--RTC-Engine-/Magic-Flute-Command-Line-Parameters"
+void WebRtcVoiceEngine::ApplyLogging(const std::string& log_filter) {
+ // Set log level.
+ int filter = 0;
+ switch (log_level_) {
+ case talk_base::LS_VERBOSE:
+ filter |= webrtc::kTraceAll; // fall through
+ case talk_base::LS_INFO:
+ filter |= webrtc::kTraceStateInfo; // fall through
+ case talk_base::LS_WARNING:
+ filter |= (webrtc::kTraceInfo | webrtc::kTraceWarning); // fall through
+ case talk_base::LS_ERROR:
+ filter |= (webrtc::kTraceError | webrtc::kTraceCritical);
+ }
+ tracing_->SetTraceFilter(filter);
+
+ // Set encrypted trace file.
+ std::vector<std::string> opts;
+ talk_base::tokenize(log_filter, ' ', '"', '"', &opts);
+ std::vector<std::string>::iterator tracefile =
+ std::find(opts.begin(), opts.end(), "tracefile");
+ if (tracefile != opts.end() && ++tracefile != opts.end()) {
+ // Write encrypted debug output (at same loglevel) to file
+ // EncryptedTraceFile no longer supported.
+ if (tracing_->SetTraceFile(tracefile->c_str()) == -1) {
+ LOG_RTCERR1(SetTraceFile, *tracefile);
+ }
+ }
+
+ // Set AEC dump file
+ std::vector<std::string>::iterator recordEC =
+ std::find(opts.begin(), opts.end(), "recordEC");
+ if (recordEC != opts.end()) {
+ ++recordEC;
+ if (recordEC != opts.end() && !is_dumping_aec_) {
+ // Start dumping AEC when we are not dumping and recordEC has a filename.
+ if (voe_wrapper_->processing()->StartDebugRecording(
+ recordEC->c_str()) == -1) {
+ LOG_RTCERR0(StartDebugRecording);
+ } else {
+ is_dumping_aec_ = true;
+ }
+ } else if (recordEC == opts.end() && is_dumping_aec_) {
+ // Stop dumping EC when we are dumping and recordEC has no filename.
+ if (voe_wrapper_->processing()->StopDebugRecording() == -1) {
+ LOG_RTCERR0(StopDebugRecording);
+ }
+ is_dumping_aec_ = false;
+ }
+ }
+}
+
+// Ignore spammy trace messages, mostly from the stats API when we haven't
+// gotten RTCP info yet from the remote side.
+static bool ShouldIgnoreTrace(const std::string& trace) {
+ static const char* kTracesToIgnore[] = {
+ "\tfailed to GetReportBlockInformation",
+ "GetRecCodec() failed to get received codec",
+ "GetRemoteRTCPData() failed to retrieve sender info for remote side",
+ "GetRTPStatistics() failed to measure RTT since no RTP packets have been received yet", // NOLINT
+ "GetRTPStatistics() failed to read RTP statistics from the RTP/RTCP module",
+ "GetRTPStatistics() failed to retrieve RTT from the RTP/RTCP module",
+ "RTCPReceiver::SenderInfoReceived No received SR",
+ "StatisticsRTP() no statisitics availble",
+ NULL
+ };
+ for (const char* const* p = kTracesToIgnore; *p; ++p) {
+ if (trace.find(*p) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void WebRtcVoiceEngine::Print(const webrtc::TraceLevel level,
+ const char* trace, const int length) {
+ talk_base::LoggingSeverity sev = talk_base::LS_VERBOSE;
+ if (level == webrtc::kTraceError || level == webrtc::kTraceCritical)
+ sev = talk_base::LS_ERROR;
+ else if (level == webrtc::kTraceWarning)
+ sev = talk_base::LS_WARNING;
+ else if (level == webrtc::kTraceStateInfo || level == webrtc::kTraceInfo)
+ sev = talk_base::LS_INFO;
+
+ if (sev >= log_level_) {
+ // Skip past boilerplate prefix text
+ if (length < 72) {
+ std::string msg(trace, length);
+ LOG(LS_ERROR) << "Malformed webrtc log message: ";
+ LOG_V(sev) << msg;
+ } else {
+ std::string msg(trace + 71, length - 72);
+ if (!ShouldIgnoreTrace(msg)) {
+ LOG_V(sev) << "WebRtc VoE:" << msg;
+ }
+ }
+ }
+}
+
+void WebRtcVoiceEngine::CallbackOnError(const int channel_num,
+ const int err_code) {
+ talk_base::CritScope lock(&channels_cs_);
+ WebRtcVoiceMediaChannel* channel = NULL;
+ uint32 ssrc = 0;
+ LOG(LS_WARNING) << "VoiceEngine error " << err_code << " reported on channel "
+ << channel_num << ".";
+ if (FindChannelAndSsrc(channel_num, &channel, &ssrc)) {
+ ASSERT(channel != NULL);
+ channel->OnError(ssrc, err_code);
+ } else {
+ LOG(LS_ERROR) << "VoiceEngine channel " << channel_num
+ << " could not be found in the channel list when error reported.";
+ }
+}
+
+int WebRtcVoiceEngine::GetCodecPreference(const char *name, int clockrate) {
+ for (size_t i = 0; i < ARRAY_SIZE(kCodecPrefs); ++i) {
+ if ((strcmp(kCodecPrefs[i].name, name) == 0) &&
+ (kCodecPrefs[i].clockrate == clockrate))
+ return ARRAY_SIZE(kCodecPrefs) - i;
+ }
+ LOG(LS_WARNING) << "Unexpected codec \"" << name << "/" << clockrate << "\"";
+ return -1;
+}
+
+bool WebRtcVoiceEngine::FindChannelAndSsrc(
+ int channel_num, WebRtcVoiceMediaChannel** channel, uint32* ssrc) const {
+ ASSERT(channel != NULL && ssrc != NULL);
+
+ *channel = NULL;
+ *ssrc = 0;
+ // Find corresponding channel and ssrc
+ for (ChannelList::const_iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ ASSERT(*it != NULL);
+ if ((*it)->FindSsrc(channel_num, ssrc)) {
+ *channel = *it;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void WebRtcVoiceEngine::RegisterChannel(WebRtcVoiceMediaChannel *channel) {
+ talk_base::CritScope lock(&channels_cs_);
+ channels_.push_back(channel);
+}
+
+void WebRtcVoiceEngine::UnregisterChannel(WebRtcVoiceMediaChannel *channel) {
+ talk_base::CritScope lock(&channels_cs_);
+ ChannelList::iterator i = std::find(channels_.begin(),
+ channels_.end(),
+ channel);
+ if (i != channels_.end()) {
+ channels_.erase(i);
+ }
+}
+
+void WebRtcVoiceEngine::RegisterSoundclip(WebRtcSoundclipMedia *soundclip) {
+ soundclips_.push_back(soundclip);
+}
+
+void WebRtcVoiceEngine::UnregisterSoundclip(WebRtcSoundclipMedia *soundclip) {
+ SoundclipList::iterator i = std::find(soundclips_.begin(),
+ soundclips_.end(),
+ soundclip);
+ if (i != soundclips_.end()) {
+ soundclips_.erase(i);
+ }
+}
+
+// Adjusts the default AGC target level by the specified delta.
+// NB: If we start messing with other config fields, we'll want
+// to save the current webrtc::AgcConfig as well.
+bool WebRtcVoiceEngine::AdjustAgcLevel(int delta) {
+ webrtc::AgcConfig config = default_agc_config_;
+ config.targetLeveldBOv += delta;
+
+ LOG(LS_INFO) << "Adjusting AGC level from default -"
+ << default_agc_config_.targetLeveldBOv << "dB to -"
+ << config.targetLeveldBOv << "dB";
+
+ if (voe_wrapper_->processing()->SetAgcConfig(config) == -1) {
+ LOG_RTCERR1(SetAgcConfig, config.targetLeveldBOv);
+ return false;
+ }
+ return true;
+}
+
+// Configures echo cancellation and noise suppression modes according to
+// whether or not we are in a multi-point conference.
+bool WebRtcVoiceEngine::SetConferenceMode(bool enable) {
+// Only use EC_AECM for mobile.
+#if defined(IOS) || defined(ANDROID)
+ return true;
+#endif
+
+ LOG(LS_INFO) << (enable ? "Enabling" : "Disabling")
+ << " Conference Mode noise reduction";
+
+ // We always configure noise suppression on, so just toggle the mode.
+ const webrtc::NsModes ns_mode = enable ? webrtc::kNsConference
+ : webrtc::kNsDefault;
+ if (voe_wrapper_->processing()->SetNsStatus(true, ns_mode) == -1) {
+ LOG_RTCERR2(SetNsStatus, true, ns_mode);
+ return false;
+ }
+
+ // Echo-cancellation is a user-option, so preserve the enable state and
+ // just toggle the mode.
+ bool aec;
+ webrtc::EcModes ec_mode;
+ if (voe_wrapper_->processing()->GetEcStatus(aec, ec_mode) == -1) {
+ LOG_RTCERR0(GetEcStatus);
+ return false;
+ }
+ ec_mode = enable ? webrtc::kEcConference : webrtc::kEcDefault;
+ if (voe_wrapper_->processing()->SetEcStatus(aec, ec_mode) == -1) {
+ LOG_RTCERR2(SetEcStatus, aec, ec_mode);
+ return false;
+ }
+ return true;
+}
+
+// WebRtcVoiceMediaChannel
+WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine)
+ : WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine>(
+ engine,
+ engine->voe()->base()->CreateChannel()),
+ channel_options_(0),
+ agc_adjusted_(false),
+ dtmf_allowed_(false),
+ desired_playout_(false),
+ playout_(false),
+ desired_send_(SEND_NOTHING),
+ send_(SEND_NOTHING) {
+ engine->RegisterChannel(this);
+ LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel "
+ << voe_channel();
+
+ // Register external transport
+ if (engine->voe()->network()->RegisterExternalTransport(
+ voe_channel(), *static_cast<Transport*>(this)) == -1) {
+ LOG_RTCERR2(RegisterExternalTransport, voe_channel(), this);
+ }
+
+ // Enable RTCP (for quality stats and feedback messages)
+ EnableRtcp(voe_channel());
+
+ // Create a random but nonzero send SSRC
+ SetSendSsrc(talk_base::CreateRandomNonZeroId());
+}
+
+WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() {
+ LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel "
+ << voe_channel();
+
+ // DeRegister external transport
+ if (engine()->voe()->network()->DeRegisterExternalTransport(
+ voe_channel()) == -1) {
+ LOG_RTCERR1(DeRegisterExternalTransport, voe_channel());
+ }
+
+ // Unregister ourselves from the engine.
+ engine()->UnregisterChannel(this);
+ // Remove any remaining streams.
+ while (!mux_channels_.empty()) {
+ RemoveStream(mux_channels_.begin()->first);
+ }
+ // Delete the primary channel.
+ if (engine()->voe()->base()->DeleteChannel(voe_channel()) == -1) {
+ LOG_RTCERR1(DeleteChannel, voe_channel());
+ }
+}
+
+bool WebRtcVoiceMediaChannel::SetOptions(int flags) {
+ // Always accept flags that are unchanged.
+ if (channel_options_ == flags) {
+ return true;
+ }
+
+ // Reject new options if we're already sending.
+ if (send_ != SEND_NOTHING) {
+ return false;
+ }
+
+ // Save the options, to be interpreted where appropriate.
+ channel_options_ = flags;
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetRecvCodecs(
+ const std::vector<AudioCodec>& codecs) {
+ // Update our receive payload types to match what we offered. This only is
+ // an issue when a different entity (i.e. a server) is generating the offer
+ // for us.
+ bool ret = true;
+ for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
+ i != codecs.end() && ret; ++i) {
+ webrtc::CodecInst gcodec;
+ if (engine()->FindWebRtcCodec(*i, &gcodec)) {
+ if (gcodec.pltype != i->id) {
+ LOG(LS_INFO) << "Updating payload type for " << gcodec.plname
+ << " from " << gcodec.pltype << " to " << i->id;
+ gcodec.pltype = i->id;
+ if (engine()->voe()->codec()->SetRecPayloadType(
+ voe_channel(), gcodec) == -1) {
+ LOG_RTCERR1(SetRecPayloadType, voe_channel());
+ ret = false;
+ }
+ }
+ } else {
+ LOG(LS_WARNING) << "Unknown codec " << i->name;
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+bool WebRtcVoiceMediaChannel::SetSendCodecs(
+ const std::vector<AudioCodec>& codecs) {
+ // Disable DTMF, VAD, and FEC unless we know the other side wants them.
+ dtmf_allowed_ = false;
+ engine()->voe()->codec()->SetVADStatus(voe_channel(), false);
+ engine()->voe()->rtp()->SetFECStatus(voe_channel(), false);
+
+ // Scan through the list to figure out the codec to use for sending, along
+ // with the proper configuration for VAD and DTMF.
+ bool first = true;
+ webrtc::CodecInst send_codec;
+ memset(&send_codec, 0, sizeof(send_codec));
+
+ for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
+ i != codecs.end(); ++i) {
+ // Ignore codecs we don't know about. The negotiation step should prevent
+ // this, but double-check to be sure.
+ webrtc::CodecInst gcodec;
+ if (!engine()->FindWebRtcCodec(*i, &gcodec)) {
+ LOG(LS_WARNING) << "Unknown codec " << i->name;
+ continue;
+ }
+
+ // Find the DTMF telephone event "codec" and tell VoiceEngine about it.
+ if (i->name == "telephone-event" || i->name == "audio/telephone-event") {
+ engine()->voe()->dtmf()->SetSendTelephoneEventPayloadType(
+ voe_channel(), i->id);
+ dtmf_allowed_ = true;
+ }
+
+ // Turn voice activity detection/comfort noise on if supported.
+ // Set the wideband CN payload type appropriately.
+ // (narrowband always uses the static payload type 13).
+ if (i->name == "CN") {
+ webrtc::PayloadFrequencies cn_freq;
+ switch (i->clockrate) {
+ case 8000:
+ cn_freq = webrtc::kFreq8000Hz;
+ break;
+ case 16000:
+ cn_freq = webrtc::kFreq16000Hz;
+ break;
+ case 32000:
+ cn_freq = webrtc::kFreq32000Hz;
+ break;
+ default:
+ LOG(LS_WARNING) << "CN frequency " << i->clockrate
+ << " not supported.";
+ continue;
+ }
+ engine()->voe()->codec()->SetVADStatus(voe_channel(), true);
+ if (cn_freq != webrtc::kFreq8000Hz) {
+ engine()->voe()->codec()->SetSendCNPayloadType(voe_channel(),
+ i->id, cn_freq);
+ }
+ }
+
+ // We'll use the first codec in the list to actually send audio data.
+ // Be sure to use the payload type requested by the remote side.
+ // "red", for FEC audio, is a special case where the actual codec to be
+ // used is specified in params.
+ if (first) {
+ if (i->name == "red") {
+ // Parse out the RED parameters. If we fail, just ignore RED;
+ // we don't support all possible params/usage scenarios.
+ if (!GetRedSendCodec(*i, codecs, &send_codec)) {
+ continue;
+ }
+
+ // Enable redundant encoding of the specified codec. Treat any
+ // failure as a fatal internal error.
+ LOG(LS_INFO) << "Enabling RED";
+ if (engine()->voe()->rtp()->SetFECStatus(voe_channel(),
+ true, i->id) == -1) {
+ LOG_RTCERR3(SetFECStatus, voe_channel(), true, i->id);
+ return false;
+ }
+ } else {
+ send_codec = gcodec;
+ send_codec.pltype = i->id;
+ }
+ first = false;
+ }
+ }
+
+ // If we're being asked to set an empty list of codecs, due to a buggy client,
+ // choose the most common format: PCMU
+ if (first) {
+ LOG(LS_WARNING) << "Received empty list of codecs; using PCMU/8000";
+ AudioCodec codec(0, "PCMU", 8000, 0, 1, 0);
+ engine()->FindWebRtcCodec(codec, &send_codec);
+ }
+
+ // Set the codec.
+ LOG(LS_INFO) << "Selected voice codec " << send_codec.plname
+ << "/" << send_codec.plfreq;
+ if (engine()->voe()->codec()->SetSendCodec(voe_channel(),
+ send_codec) == -1) {
+ LOG_RTCERR1(SetSendCodec, voe_channel());
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetRecvRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions) {
+ // We don't support any incoming extensions headers right now.
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetSendRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions) {
+ // Enable the audio level extension header if requested.
+ std::vector<RtpHeaderExtension>::const_iterator it;
+ for (it = extensions.begin(); it != extensions.end(); ++it) {
+ if (it->uri == kRtpAudioLevelHeaderExtension) {
+ break;
+ }
+ }
+
+ bool enable = (it != extensions.end());
+ int id = 0;
+
+ if (enable) {
+ id = it->id;
+ if (id < kMinRtpHeaderExtensionId ||
+ id > kMaxRtpHeaderExtensionId) {
+ LOG(LS_WARNING) << "Invalid RTP header extension id " << id;
+ return false;
+ }
+ }
+
+// This api call is not available in iOS version of VoiceEngine currently.
+#if !defined(IOS) && !defined(ANDROID)
+ if (engine()->voe()->rtp()->SetRTPAudioLevelIndicationStatus(
+ voe_channel(), enable, id) == -1) {
+ LOG_RTCERR3(SetRTPAudioLevelIndicationStatus, voe_channel(), enable, id);
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetPlayout(bool playout) {
+ desired_playout_ = playout;
+ return ChangePlayout(desired_playout_);
+}
+
+bool WebRtcVoiceMediaChannel::PausePlayout() {
+ return ChangePlayout(false);
+}
+
+bool WebRtcVoiceMediaChannel::ResumePlayout() {
+ return ChangePlayout(desired_playout_);
+}
+
+bool WebRtcVoiceMediaChannel::ChangePlayout(bool playout) {
+ if (playout_ == playout) {
+ return true;
+ }
+
+ bool result = true;
+ if (mux_channels_.empty()) {
+ // Only toggle the default channel if we don't have any other channels.
+ result = SetPlayout(voe_channel(), playout);
+ }
+ for (ChannelMap::iterator it = mux_channels_.begin();
+ it != mux_channels_.end() && result; ++it) {
+ if (!SetPlayout(it->second, playout)) {
+ LOG(LS_ERROR) << "SetPlayout " << playout << " on channel " << it->second
+ << " failed";
+ result = false;
+ }
+ }
+
+ if (result) {
+ playout_ = playout;
+ }
+ return result;
+}
+
+bool WebRtcVoiceMediaChannel::SetSend(SendFlags send) {
+ desired_send_ = send;
+ return ChangeSend(desired_send_);
+}
+
+bool WebRtcVoiceMediaChannel::PauseSend() {
+ return ChangeSend(SEND_NOTHING);
+}
+
+bool WebRtcVoiceMediaChannel::ResumeSend() {
+ return ChangeSend(desired_send_);
+}
+
+bool WebRtcVoiceMediaChannel::ChangeSend(SendFlags send) {
+ if (send_ == send) {
+ return true;
+ }
+
+ if (send == SEND_MICROPHONE) {
+#ifdef CHROMEOS
+ // Conference mode doesn't work well on ChromeOS.
+ if (!engine()->SetConferenceMode(false)) {
+ LOG_RTCERR1(SetConferenceMode, voe_channel());
+ return false;
+ }
+#else
+ // Multi-point conferences use conference-mode noise filtering.
+ if (!engine()->SetConferenceMode(
+ 0 != (channel_options_ & OPT_CONFERENCE))) {
+ LOG_RTCERR1(SetConferenceMode, voe_channel());
+ return false;
+ }
+#endif // CHROMEOS
+
+ // Tandberg-bridged conferences have an AGC target that is lower than
+ // GTV-only levels.
+ if ((channel_options_ & OPT_AGC_TANDBERG_LEVELS) && !agc_adjusted_) {
+ if (engine()->AdjustAgcLevel(kTandbergDbAdjustment)) {
+ agc_adjusted_ = true;
+ }
+ }
+
+ // VoiceEngine resets sequence number when StopSend is called. This
+ // sometimes causes libSRTP to complain about packets being
+ // replayed. To get around this we store the last sent sequence
+ // number and initializes the channel with the next to continue on
+ // the same sequence.
+ if (sequence_number() != -1) {
+ LOG(LS_INFO) << "WebRtcVoiceMediaChannel restores seqnum="
+ << sequence_number() + 1;
+ if (engine()->voe()->sync()->SetInitSequenceNumber(
+ voe_channel(), sequence_number() + 1) == -1) {
+ LOG_RTCERR2(SetInitSequenceNumber, voe_channel(),
+ sequence_number() + 1);
+ }
+ }
+ if (engine()->voe()->base()->StartSend(voe_channel()) == -1) {
+ LOG_RTCERR1(StartSend, voe_channel());
+ return false;
+ }
+ if (engine()->voe()->file()->StopPlayingFileAsMicrophone(
+ voe_channel()) == -1) {
+ LOG_RTCERR1(StopPlayingFileAsMicrophone, voe_channel());
+ return false;
+ }
+ } else if (send == SEND_RINGBACKTONE) {
+ ASSERT(ringback_tone_.get() != NULL);
+ if (!ringback_tone_.get()) {
+ return false;
+ }
+ if (engine()->voe()->file()->StartPlayingFileAsMicrophone(
+ voe_channel(), ringback_tone_.get(), false) == -1) {
+ LOG_RTCERR3(StartPlayingFileAsMicrophone, voe_channel(),
+ ringback_tone_.get(), false);
+ return false;
+ }
+ // VoiceEngine resets sequence number when StopSend is called. This
+ // sometimes causes libSRTP to complain about packets being
+ // replayed. To get around this we store the last sent sequence
+ // number and initializes the channel with the next to continue on
+ // the same sequence.
+ if (sequence_number() != -1) {
+ LOG(LS_INFO) << "WebRtcVoiceMediaChannel restores seqnum="
+ << sequence_number() + 1;
+ if (engine()->voe()->sync()->SetInitSequenceNumber(
+ voe_channel(), sequence_number() + 1) == -1) {
+ LOG_RTCERR2(SetInitSequenceNumber, voe_channel(),
+ sequence_number() + 1);
+ }
+ }
+ if (engine()->voe()->base()->StartSend(voe_channel()) == -1) {
+ LOG_RTCERR1(StartSend, voe_channel());
+ return false;
+ }
+ } else { // SEND_NOTHING
+ if (engine()->voe()->base()->StopSend(voe_channel()) == -1) {
+ LOG_RTCERR1(StopSend, voe_channel());
+ }
+
+ // Reset the AGC level, if it was set.
+ if (agc_adjusted_) {
+ if (engine()->AdjustAgcLevel(0)) {
+ agc_adjusted_ = false;
+ }
+ }
+
+ // Disable conference-mode noise filtering.
+ if (!engine()->SetConferenceMode(false)) {
+ LOG_RTCERR1(SetConferenceMode, voe_channel());
+ }
+ }
+ send_ = send;
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::AddStream(uint32 ssrc) {
+ talk_base::CritScope lock(&mux_channels_cs_);
+
+ if (mux_channels_.find(ssrc) != mux_channels_.end()) {
+ return false;
+ }
+
+ // Create a new channel for receiving audio data.
+ int channel = engine()->voe()->base()->CreateChannel();
+ if (channel == -1) {
+ LOG_RTCERR0(CreateChannel);
+ return false;
+ }
+
+ // Configure to use external transport, like our default channel.
+ if (engine()->voe()->network()->RegisterExternalTransport(
+ channel, *this) == -1) {
+ LOG_RTCERR2(SetExternalTransport, channel, this);
+ return false;
+ }
+
+ // Use the same SSRC as our default channel (so the RTCP reports are correct).
+ unsigned int send_ssrc;
+ webrtc::VoERTP_RTCP* rtp = engine()->voe()->rtp();
+ if (rtp->GetLocalSSRC(voe_channel(), send_ssrc) == -1) {
+ LOG_RTCERR2(GetSendSSRC, channel, send_ssrc);
+ return false;
+ }
+ if (rtp->SetLocalSSRC(channel, send_ssrc) == -1) {
+ LOG_RTCERR2(SetSendSSRC, channel, send_ssrc);
+ return false;
+ }
+
+ if (mux_channels_.empty() && playout_) {
+ // This is the first stream in a multi user meeting. We can now
+ // disable playback of the default stream. This since the default
+ // stream will probably have received some initial packets before
+ // the new stream was added. This will mean that the CN state from
+ // the default channel will be mixed in with the other streams
+ // throughout the whole meeting, which might be disturbing.
+ LOG(LS_INFO) << "Disabling playback on the default voice channel";
+ SetPlayout(voe_channel(), false);
+ }
+
+ mux_channels_[ssrc] = channel;
+
+ // TODO: We should rollback the add if SetPlayout fails.
+ LOG(LS_INFO) << "New audio stream " << ssrc
+ << " registered to VoiceEngine channel #"
+ << channel << ".";
+ return SetPlayout(channel, playout_);
+}
+
+bool WebRtcVoiceMediaChannel::RemoveStream(uint32 ssrc) {
+ talk_base::CritScope lock(&mux_channels_cs_);
+ ChannelMap::iterator it = mux_channels_.find(ssrc);
+
+ if (it != mux_channels_.end()) {
+ if (engine()->voe()->network()->DeRegisterExternalTransport(
+ it->second) == -1) {
+ LOG_RTCERR1(DeRegisterExternalTransport, it->second);
+ }
+
+ LOG(LS_INFO) << "Removing audio stream " << ssrc
+ << " with VoiceEngine channel #"
+ << it->second << ".";
+ if (engine()->voe()->base()->DeleteChannel(it->second) == -1) {
+ LOG_RTCERR1(DeleteChannel, voe_channel());
+ return false;
+ }
+
+ mux_channels_.erase(it);
+ if (mux_channels_.empty() && playout_) {
+ // The last stream was removed. We can now enable the default
+ // channel for new channels to be played out immediately without
+ // waiting for AddStream messages.
+ // TODO: Does the default channel still have it's CN state?
+ LOG(LS_INFO) << "Enabling playback on the default voice channel";
+ SetPlayout(voe_channel(), true);
+ }
+ }
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::GetActiveStreams(
+ AudioInfo::StreamList* actives) {
+ actives->clear();
+ for (ChannelMap::iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ int level = GetOutputLevel(it->second);
+ if (level > 0) {
+ actives->push_back(std::make_pair(it->first, level));
+ }
+ }
+ return true;
+}
+
+int WebRtcVoiceMediaChannel::GetOutputLevel() {
+ // return the highest output level of all streams
+ int highest = GetOutputLevel(voe_channel());
+ for (ChannelMap::iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ int level = GetOutputLevel(it->second);
+ highest = talk_base::_max(level, highest);
+ }
+ return highest;
+}
+
+
+bool WebRtcVoiceMediaChannel::SetOutputScaling(
+ uint32 ssrc, double left, double right) {
+ talk_base::CritScope lock(&mux_channels_cs_);
+ // Collect the channels to scale the output volume.
+ std::vector<int> channels;
+ if (0 == ssrc) { // Collect all channels, including the default one.
+ channels.push_back(voe_channel());
+ for (ChannelMap::const_iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ channels.push_back(it->second);
+ }
+ } else { // Collect only the channel of the specified ssrc.
+ int channel = GetChannel(ssrc);
+ if (-1 == channel) {
+ LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
+ return false;
+ }
+ channels.push_back(channel);
+ }
+
+ // Scale the output volume for the collected channels. We first normalize to
+ // scale the volume and then set the left and right pan.
+ float scale = static_cast<float>(talk_base::_max(left, right));
+ if (scale > 0.0001f) {
+ left /= scale;
+ right /= scale;
+ }
+ for (std::vector<int>::const_iterator it = channels.begin();
+ it != channels.end(); ++it) {
+ if (-1 == engine()->voe()->volume()->SetChannelOutputVolumeScaling(
+ *it, scale)) {
+ LOG_RTCERR2(SetChannelOutputVolumeScaling, *it, scale);
+ return false;
+ }
+ if (-1 == engine()->voe()->volume()->SetOutputVolumePan(
+ *it, static_cast<float>(left), static_cast<float>(right))) {
+ LOG_RTCERR3(SetOutputVolumePan, *it, left, right);
+ // Do not return if fails. SetOutputVolumePan is not available for all
+ // pltforms.
+ }
+ LOG(LS_INFO) << "SetOutputScaling to left=" << left * scale
+ << " right=" << right * scale
+ << " for channel " << *it << " and ssrc " << ssrc;
+ }
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::GetOutputScaling(
+ uint32 ssrc, double* left, double* right) {
+ if (!left || !right) return false;
+
+ talk_base::CritScope lock(&mux_channels_cs_);
+ // Determine which channel based on ssrc.
+ int channel = (0 == ssrc) ? voe_channel() : GetChannel(ssrc);
+ if (channel == -1) {
+ LOG(LS_WARNING) << "Cannot find channel for ssrc:" << ssrc;
+ return false;
+ }
+
+ float scaling;
+ if (-1 == engine()->voe()->volume()->GetChannelOutputVolumeScaling(
+ channel, scaling)) {
+ LOG_RTCERR2(GetChannelOutputVolumeScaling, channel, scaling);
+ return false;
+ }
+
+ float left_pan;
+ float right_pan;
+ if (-1 == engine()->voe()->volume()->GetOutputVolumePan(
+ channel, left_pan, right_pan)) {
+ LOG_RTCERR3(GetOutputVolumePan, channel, left_pan, right_pan);
+ // If GetOutputVolumePan fails, we use the default left and right pan.
+ left_pan = 1.0f;
+ right_pan = 1.0f;
+ }
+
+ *left = scaling * left_pan;
+ *right = scaling * right_pan;
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetRingbackTone(const char *buf, int len) {
+ ringback_tone_.reset(new WebRtcSoundclipStream(buf, len));
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::PlayRingbackTone(uint32 ssrc,
+ bool play, bool loop) {
+ if (!ringback_tone_.get()) {
+ return false;
+ }
+
+ // Determine which VoiceEngine channel to play on.
+ int channel = (ssrc == 0) ? voe_channel() : GetChannel(ssrc);
+ if (channel == -1) {
+ return false;
+ }
+
+ // Make sure the ringtone is cued properly, and play it out.
+ if (play) {
+ ringback_tone_->set_loop(loop);
+ ringback_tone_->Rewind();
+ if (engine()->voe()->file()->StartPlayingFileLocally(channel,
+ ringback_tone_.get()) == -1) {
+ LOG_RTCERR2(StartPlayingFileLocally, channel, ringback_tone_.get());
+ LOG(LS_ERROR) << "Unable to start ringback tone";
+ return false;
+ }
+ ringback_channels_.insert(channel);
+ LOG(LS_INFO) << "Started ringback on channel " << channel;
+ } else {
+ if (engine()->voe()->file()->StopPlayingFileLocally(channel)
+ == -1) {
+ LOG_RTCERR1(StopPlayingFileLocally, channel);
+ return false;
+ }
+ LOG(LS_INFO) << "Stopped ringback on channel " << channel;
+ ringback_channels_.erase(channel);
+ }
+
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::PressDTMF(int event, bool playout) {
+ if (!dtmf_allowed_) {
+ return false;
+ }
+
+ // Enable or disable DTMF playout of this tone as requested. This will linger
+ // until the next call to this method, but that's OK.
+ if (engine()->voe()->dtmf()->SetDtmfFeedbackStatus(playout) == -1) {
+ LOG_RTCERR2(SendDTMF, voe_channel(), playout);
+ return false;
+ }
+
+ // Send DTMF using out-of-band DTMF. ("true", as 3rd arg)
+ if (engine()->voe()->dtmf()->SendTelephoneEvent(voe_channel(), event,
+ true) == -1) {
+ LOG_RTCERR3(SendDTMF, voe_channel(), event, true);
+ return false;
+ }
+
+ return true;
+}
+
+void WebRtcVoiceMediaChannel::OnPacketReceived(talk_base::Buffer* packet) {
+ // Pick which channel to send this packet to. If this packet doesn't match
+ // any multiplexed streams, just send it to the default channel. Otherwise,
+ // send it to the specific decoder instance for that stream.
+ int which_channel = GetChannel(
+ ParseSsrc(packet->data(), packet->length(), false));
+ if (which_channel == -1) {
+ which_channel = voe_channel();
+ }
+
+ // Stop any ringback that might be playing on the channel.
+ // It's possible the ringback has already stopped, ih which case we'll just
+ // use the opportunity to remove the channel from ringback_channels_.
+ const std::set<int>::iterator it = ringback_channels_.find(which_channel);
+ if (it != ringback_channels_.end()) {
+ if (engine()->voe()->file()->IsPlayingFileLocally(
+ which_channel) == 1) {
+ engine()->voe()->file()->StopPlayingFileLocally(which_channel);
+ LOG(LS_INFO) << "Stopped ringback on channel " << which_channel
+ << " due to incoming media";
+ }
+ ringback_channels_.erase(which_channel);
+ }
+
+ // Pass it off to the decoder.
+ engine()->voe()->network()->ReceivedRTPPacket(which_channel,
+ packet->data(),
+ packet->length());
+}
+
+void WebRtcVoiceMediaChannel::OnRtcpReceived(talk_base::Buffer* packet) {
+ // See above.
+ int which_channel = GetChannel(
+ ParseSsrc(packet->data(), packet->length(), true));
+ if (which_channel == -1) {
+ which_channel = voe_channel();
+ }
+
+ engine()->voe()->network()->ReceivedRTCPPacket(which_channel,
+ packet->data(),
+ packet->length());
+}
+
+void WebRtcVoiceMediaChannel::SetSendSsrc(uint32 ssrc) {
+ if (engine()->voe()->rtp()->SetLocalSSRC(voe_channel(), ssrc)
+ == -1) {
+ LOG_RTCERR2(SetSendSSRC, voe_channel(), ssrc);
+ }
+}
+
+bool WebRtcVoiceMediaChannel::SetRtcpCName(const std::string& cname) {
+ if (engine()->voe()->rtp()->SetRTCP_CNAME(voe_channel(),
+ cname.c_str()) == -1) {
+ LOG_RTCERR2(SetRTCP_CNAME, voe_channel(), cname);
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::Mute(bool muted) {
+ if (engine()->voe()->volume()->SetInputMute(voe_channel(),
+ muted) == -1) {
+ LOG_RTCERR2(SetInputMute, voe_channel(), muted);
+ return false;
+ }
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) {
+ // In VoiceEngine 3.5, GetRTCPStatistics will return 0 even when it fails,
+ // causing the stats to contain garbage information. To prevent this, we
+ // zero the stats structure before calling this API.
+ // TODO: Remove this workaround.
+ webrtc::CallStatistics cs;
+ unsigned int ssrc;
+ webrtc::CodecInst codec;
+ unsigned int level;
+
+ // Fill in the sender info, based on what we know, and what the
+ // remote side told us it got from its RTCP report.
+ VoiceSenderInfo sinfo;
+ memset(&sinfo, 0, sizeof(sinfo));
+
+ // Data we obtain locally.
+ memset(&cs, 0, sizeof(cs));
+ if (engine()->voe()->rtp()->GetRTCPStatistics(voe_channel(), cs) == -1 ||
+ engine()->voe()->rtp()->GetLocalSSRC(voe_channel(), ssrc) == -1) {
+ return false;
+ }
+
+ sinfo.ssrc = ssrc;
+ sinfo.bytes_sent = cs.bytesSent;
+ sinfo.packets_sent = cs.packetsSent;
+ // RTT isn't known until a RTCP report is received. Until then, VoiceEngine
+ // returns 0 to indicate an error value.
+ sinfo.rtt_ms = (cs.rttMs > 0) ? cs.rttMs : -1;
+
+ // Data from the last remote RTCP report.
+ unsigned int ntp_high, ntp_low, timestamp, ptimestamp, jitter;
+ unsigned short loss; // NOLINT
+ if (engine()->voe()->rtp()->GetRemoteRTCPData(voe_channel(),
+ ntp_high, ntp_low, timestamp, ptimestamp, &jitter, &loss) != -1 &&
+ engine()->voe()->codec()->GetSendCodec(voe_channel(),
+ codec) != -1) {
+ // Convert Q8 to floating point.
+ sinfo.fraction_lost = static_cast<float>(loss) / (1 << 8);
+ // Convert samples to milliseconds.
+ if (codec.plfreq / 1000 > 0) {
+ sinfo.jitter_ms = jitter / (codec.plfreq / 1000);
+ }
+ } else {
+ sinfo.fraction_lost = -1;
+ sinfo.jitter_ms = -1;
+ }
+ // TODO: Figure out how to get remote packets_lost, ext_seqnum
+ sinfo.packets_lost = -1;
+ sinfo.ext_seqnum = -1;
+
+ // Local speech level.
+ sinfo.audio_level = (engine()->voe()->volume()->
+ GetSpeechInputLevelFullRange(level) != -1) ? level : -1;
+ info->senders.push_back(sinfo);
+
+ // Build the list of receivers, one for each mux channel, or 1 in a 1:1 call.
+ std::vector<int> channels;
+ for (ChannelMap::const_iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ channels.push_back(it->second);
+ }
+ if (channels.empty()) {
+ channels.push_back(voe_channel());
+ }
+
+ // Get the SSRC and stats for each receiver, based on our own calculations.
+ for (std::vector<int>::const_iterator it = channels.begin();
+ it != channels.end(); ++it) {
+ memset(&cs, 0, sizeof(cs));
+ if (engine()->voe()->rtp()->GetRemoteSSRC(*it, ssrc) != -1 &&
+ engine()->voe()->rtp()->GetRTCPStatistics(*it, cs) != -1 &&
+ engine()->voe()->codec()->GetRecCodec(*it, codec) != -1) {
+ VoiceReceiverInfo rinfo;
+ memset(&rinfo, 0, sizeof(rinfo));
+ rinfo.ssrc = ssrc;
+ rinfo.bytes_rcvd = cs.bytesReceived;
+ rinfo.packets_rcvd = cs.packetsReceived;
+ // The next four fields are from the most recently sent RTCP report.
+ // Convert Q8 to floating point.
+ rinfo.fraction_lost = static_cast<float>(cs.fractionLost) / (1 << 8);
+ rinfo.packets_lost = cs.cumulativeLost;
+ rinfo.ext_seqnum = cs.extendedMax;
+ // Convert samples to milliseconds.
+ if (codec.plfreq / 1000 > 0) {
+ rinfo.jitter_ms = cs.jitterSamples / (codec.plfreq / 1000);
+ }
+
+ // Get jitter buffer and total delay (alg + jitter + playout) stats.
+ webrtc::NetworkStatistics ns;
+ if (engine()->voe()->neteq() &&
+ engine()->voe()->neteq()->GetNetworkStatistics(
+ *it, ns) != -1) {
+ rinfo.jitter_buffer_ms = ns.currentBufferSize;
+ rinfo.jitter_buffer_preferred_ms = ns.preferredBufferSize;
+ }
+ if (engine()->voe()->sync()) {
+ engine()->voe()->sync()->GetDelayEstimate(*it,
+ rinfo.delay_estimate_ms);
+ }
+
+ // Get speech level.
+ rinfo.audio_level = (engine()->voe()->volume()->
+ GetSpeechOutputLevelFullRange(*it, level) != -1) ? level : -1;
+ info->receivers.push_back(rinfo);
+ }
+ }
+
+ return true;
+}
+
+void WebRtcVoiceMediaChannel::GetLastMediaError(
+ uint32* ssrc, VoiceMediaChannel::Error* error) {
+ ASSERT(ssrc != NULL);
+ ASSERT(error != NULL);
+ FindSsrc(voe_channel(), ssrc);
+ *error = WebRtcErrorToChannelError(GetLastEngineError());
+}
+
+bool WebRtcVoiceMediaChannel::FindSsrc(int channel_num, uint32* ssrc) {
+ talk_base::CritScope lock(&mux_channels_cs_);
+ ASSERT(ssrc != NULL);
+ if (channel_num == voe_channel()) {
+ unsigned local_ssrc = 0;
+ // This is a sending channel.
+ if (engine()->voe()->rtp()->GetLocalSSRC(
+ channel_num, local_ssrc) != -1) {
+ *ssrc = local_ssrc;
+ }
+ return true;
+ } else if (channel_num == -1 && send_ != SEND_NOTHING) {
+ // Sometimes the VoiceEngine core will throw error with channel_num = -1.
+ // This means the error is not limited to a specific channel. Signal the
+ // message using ssrc=0. If the current channel is sending, use this
+ // channel for sending the message.
+ *ssrc = 0;
+ return true;
+ } else {
+ // Check whether this is a receiving channel.
+ for (ChannelMap::const_iterator it = mux_channels_.begin();
+ it != mux_channels_.end(); ++it) {
+ if (it->second == channel_num) {
+ *ssrc = it->first;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void WebRtcVoiceMediaChannel::OnError(uint32 ssrc, int error) {
+ SignalMediaError(ssrc, WebRtcErrorToChannelError(error));
+}
+
+int WebRtcVoiceMediaChannel::GetOutputLevel(int channel) {
+ unsigned int ulevel;
+ int ret =
+ engine()->voe()->volume()->GetSpeechOutputLevel(channel, ulevel);
+ return (ret == 0) ? static_cast<int>(ulevel) : -1;
+}
+
+int WebRtcVoiceMediaChannel::GetChannel(uint32 ssrc) {
+ ChannelMap::iterator it = mux_channels_.find(ssrc);
+ return (it != mux_channels_.end()) ? it->second : -1;
+}
+
+bool WebRtcVoiceMediaChannel::GetRedSendCodec(const AudioCodec& red_codec,
+ const std::vector<AudioCodec>& all_codecs, webrtc::CodecInst* send_codec) {
+ // Get the RED encodings from the parameter with no name. This may
+ // change based on what is discussed on the Jingle list.
+ // The encoding parameter is of the form "a/b"; we only support where
+ // a == b. Verify this and parse out the value into red_pt.
+ // If the parameter value is absent (as it will be until we wire up the
+ // signaling of this message), use the second codec specified (i.e. the
+ // one after "red") as the encoding parameter.
+ int red_pt = -1;
+ std::string red_params;
+ CodecParameterMap::const_iterator it = red_codec.params.find("");
+ if (it != red_codec.params.end()) {
+ red_params = it->second;
+ std::vector<std::string> red_pts;
+ if (talk_base::split(red_params, '/', &red_pts) != 2 ||
+ red_pts[0] != red_pts[1] ||
+ !talk_base::FromString(red_pts[0], &red_pt)) {
+ LOG(LS_WARNING) << "RED params " << red_params << " not supported.";
+ return false;
+ }
+ } else if (red_codec.params.empty()) {
+ LOG(LS_WARNING) << "RED params not present, using defaults";
+ if (all_codecs.size() > 1) {
+ red_pt = all_codecs[1].id;
+ }
+ }
+
+ // Try to find red_pt in |codecs|.
+ std::vector<AudioCodec>::const_iterator codec;
+ for (codec = all_codecs.begin(); codec != all_codecs.end(); ++codec) {
+ if (codec->id == red_pt)
+ break;
+ }
+
+ // If we find the right codec, that will be the codec we pass to
+ // SetSendCodec, with the desired payload type.
+ if (codec != all_codecs.end() &&
+ engine()->FindWebRtcCodec(*codec, send_codec)) {
+ send_codec->pltype = red_pt;
+ } else {
+ LOG(LS_WARNING) << "RED params " << red_params << " are invalid.";
+ return false;
+ }
+
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::EnableRtcp(int channel) {
+ if (engine()->voe()->rtp()->SetRTCPStatus(channel, true) == -1) {
+ LOG_RTCERR2(SetRTCPStatus, voe_channel(), 1);
+ return false;
+ }
+ // TODO: Enable VQMon and RTCP XR reports, once we know what
+ // what we want to do with them.
+ // engine()->voe().EnableVQMon(voe_channel(), true);
+ // engine()->voe().EnableRTCP_XR(voe_channel(), true);
+ return true;
+}
+
+bool WebRtcVoiceMediaChannel::SetPlayout(int channel, bool playout) {
+ if (playout) {
+ LOG(LS_INFO) << "Starting playout for channel #" << channel;
+ if (engine()->voe()->base()->StartPlayout(channel) == -1) {
+ LOG_RTCERR1(StartPlayout, channel);
+ return false;
+ }
+ } else {
+ LOG(LS_INFO) << "Stopping playout for channel #" << channel;
+ engine()->voe()->base()->StopPlayout(channel);
+ }
+ return true;
+}
+
+uint32 WebRtcVoiceMediaChannel::ParseSsrc(const void* data, size_t len,
+ bool rtcp) {
+ size_t ssrc_pos = (!rtcp) ? 8 : 4;
+ uint32 ssrc = 0;
+ if (len >= (ssrc_pos + sizeof(ssrc))) {
+ ssrc = talk_base::GetBE32(static_cast<const char*>(data) + ssrc_pos);
+ }
+ return ssrc;
+}
+
+// Convert VoiceEngine error code into VoiceMediaChannel::Error enum.
+VoiceMediaChannel::Error
+ WebRtcVoiceMediaChannel::WebRtcErrorToChannelError(int err_code) {
+ switch (err_code) {
+ case 0:
+ return ERROR_NONE;
+ case VE_CANNOT_START_RECORDING:
+ case VE_MIC_VOL_ERROR:
+ case VE_GET_MIC_VOL_ERROR:
+ case VE_CANNOT_ACCESS_MIC_VOL:
+ return ERROR_REC_DEVICE_OPEN_FAILED;
+ case VE_SATURATION_WARNING:
+ return ERROR_REC_DEVICE_SATURATION;
+ case VE_REC_DEVICE_REMOVED:
+ return ERROR_REC_DEVICE_REMOVED;
+ case VE_RUNTIME_REC_WARNING:
+ case VE_RUNTIME_REC_ERROR:
+ return ERROR_REC_RUNTIME_ERROR;
+ case VE_CANNOT_START_PLAYOUT:
+ case VE_SPEAKER_VOL_ERROR:
+ case VE_GET_SPEAKER_VOL_ERROR:
+ case VE_CANNOT_ACCESS_SPEAKER_VOL:
+ return ERROR_PLAY_DEVICE_OPEN_FAILED;
+ case VE_RUNTIME_PLAY_WARNING:
+ case VE_RUNTIME_PLAY_ERROR:
+ return ERROR_PLAY_RUNTIME_ERROR;
+ case VE_TYPING_NOISE_WARNING:
+ return ERROR_REC_TYPING_NOISE_DETECTED;
+ default:
+ return VoiceMediaChannel::ERROR_OTHER;
+ }
+}
+
+int WebRtcSoundclipStream::Read(void *buf, int len) {
+ size_t res = 0;
+ mem_.Read(buf, len, &res, NULL);
+ return res;
+}
+
+int WebRtcSoundclipStream::Rewind() {
+ mem_.Rewind();
+ // Return -1 to keep VoiceEngine from looping.
+ return (loop_) ? 0 : -1;
+}
+
+} // namespace cricket
+
+#endif // HAVE_WEBRTC_VOICE
diff --git a/talk/session/phone/webrtcvoiceengine.h b/talk/session/phone/webrtcvoiceengine.h
new file mode 100644
index 0000000..8234b9b
--- /dev/null
+++ b/talk/session/phone/webrtcvoiceengine.h
@@ -0,0 +1,323 @@
+/*
+ * 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_SESSION_PHONE_WEBRTCVOICEENGINE_H_
+#define TALK_SESSION_PHONE_WEBRTCVOICEENGINE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/rtputils.h"
+#include "talk/session/phone/webrtccommon.h"
+
+namespace cricket {
+
+// WebRtcSoundclipStream is an adapter object that allows a memory stream to be
+// passed into WebRtc, and support looping.
+class WebRtcSoundclipStream : public webrtc::InStream {
+ public:
+ WebRtcSoundclipStream(const char* buf, size_t len)
+ : mem_(buf, len), loop_(true) {
+ }
+ void set_loop(bool loop) { loop_ = loop; }
+ virtual int Read(void* buf, int len);
+ virtual int Rewind();
+
+ private:
+ talk_base::MemoryStream mem_;
+ bool loop_;
+};
+
+// WebRtcMonitorStream is used to monitor a stream coming from WebRtc.
+// For now we just dump the data.
+class WebRtcMonitorStream : public webrtc::OutStream {
+ virtual bool Write(const void *buf, int len) {
+ return true;
+ }
+};
+
+class AudioDeviceModule;
+class VoETraceWrapper;
+class VoEWrapper;
+class WebRtcSoundclipMedia;
+class WebRtcVoiceMediaChannel;
+
+// WebRtcVoiceEngine is a class to be used with CompositeMediaEngine.
+// It uses the WebRtc VoiceEngine library for audio handling.
+class WebRtcVoiceEngine
+ : public webrtc::VoiceEngineObserver,
+ public webrtc::TraceCallback {
+ public:
+ WebRtcVoiceEngine();
+ WebRtcVoiceEngine(webrtc::AudioDeviceModule* adm,
+ webrtc::AudioDeviceModule* adm_sc);
+ // Dependency injection for testing.
+ WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
+ VoEWrapper* voe_wrapper_sc,
+ VoETraceWrapper* tracing);
+ ~WebRtcVoiceEngine();
+ bool Init();
+ void Terminate();
+
+ int GetCapabilities();
+ VoiceMediaChannel* CreateChannel();
+
+ SoundclipMedia* CreateSoundclip();
+
+ bool SetOptions(int options);
+ bool SetDevices(const Device* in_device, const Device* out_device);
+ bool GetOutputVolume(int* level);
+ bool SetOutputVolume(int level);
+ int GetInputLevel();
+ bool SetLocalMonitor(bool enable);
+
+ const std::vector<AudioCodec>& codecs();
+ bool FindCodec(const AudioCodec& codec);
+ bool FindWebRtcCodec(const AudioCodec& codec, webrtc::CodecInst* gcodec);
+
+ void SetLogging(int min_sev, const char* filter);
+
+ // For tracking WebRtc channels. Needed because we have to pause them
+ // all when switching devices.
+ // May only be called by WebRtcVoiceMediaChannel.
+ void RegisterChannel(WebRtcVoiceMediaChannel *channel);
+ void UnregisterChannel(WebRtcVoiceMediaChannel *channel);
+
+ // May only be called by WebRtcSoundclipMedia.
+ void RegisterSoundclip(WebRtcSoundclipMedia *channel);
+ void UnregisterSoundclip(WebRtcSoundclipMedia *channel);
+
+ // Called by WebRtcVoiceMediaChannel to set a gain offset from
+ // the default AGC target level.
+ bool AdjustAgcLevel(int delta);
+
+ // Called by WebRtcVoiceMediaChannel to configure echo cancellation
+ // and noise suppression modes.
+ bool SetConferenceMode(bool enable);
+
+ VoEWrapper* voe() { return voe_wrapper_.get(); }
+ VoEWrapper* voe_sc() { return voe_wrapper_sc_.get(); }
+ int GetLastEngineError();
+
+ private:
+ typedef std::vector<WebRtcSoundclipMedia *> SoundclipList;
+ typedef std::vector<WebRtcVoiceMediaChannel *> ChannelList;
+
+ struct CodecPref {
+ const char* name;
+ int clockrate;
+ };
+
+ void Construct();
+ bool InitInternal();
+ void ApplyLogging(const std::string& log_filter);
+ virtual void Print(const webrtc::TraceLevel level,
+ const char* trace_string, const int length);
+ virtual void CallbackOnError(const int channel, const int errCode);
+ static int GetCodecPreference(const char *name, int clockrate);
+ // Given the device type, name, and id, find device id. Return true and
+ // set the output parameter rtc_id if successful.
+ bool FindWebRtcAudioDeviceId(
+ bool is_input, const std::string& dev_name, int dev_id, int* rtc_id);
+ bool FindChannelAndSsrc(int channel_num,
+ WebRtcVoiceMediaChannel** channel,
+ uint32* ssrc) const;
+ bool ChangeLocalMonitor(bool enable);
+ bool PauseLocalMonitor();
+ bool ResumeLocalMonitor();
+
+ static const int kDefaultLogSeverity = talk_base::LS_WARNING;
+ static const CodecPref kCodecPrefs[];
+
+ // The primary instance of WebRtc VoiceEngine.
+ talk_base::scoped_ptr<VoEWrapper> voe_wrapper_;
+ // A secondary instance, for playing out soundclips (on the 'ring' device).
+ talk_base::scoped_ptr<VoEWrapper> voe_wrapper_sc_;
+ talk_base::scoped_ptr<VoETraceWrapper> tracing_;
+ // The external audio device manager
+ webrtc::AudioDeviceModule* adm_;
+ webrtc::AudioDeviceModule* adm_sc_;
+ int log_level_;
+ std::string log_filter_;
+ bool is_dumping_aec_;
+ std::vector<AudioCodec> codecs_;
+ bool desired_local_monitor_enable_;
+ talk_base::scoped_ptr<WebRtcMonitorStream> monitor_;
+ SoundclipList soundclips_;
+ ChannelList channels_;
+ // channels_ can be read from WebRtc callback thread. We need a lock on that
+ // callback as well as the RegisterChannel/UnregisterChannel.
+ talk_base::CriticalSection channels_cs_;
+ webrtc::AgcConfig default_agc_config_;
+ bool initialized_;
+};
+
+// WebRtcMediaChannel is a class that implements the common WebRtc channel
+// functionality.
+template <class T, class E>
+class WebRtcMediaChannel : public T, public webrtc::Transport {
+ public:
+ WebRtcMediaChannel(E *engine, int channel)
+ : engine_(engine), voe_channel_(channel), sequence_number_(-1) {}
+ E *engine() { return engine_; }
+ int voe_channel() const { return voe_channel_; }
+ bool valid() const { return voe_channel_ != -1; }
+
+ protected:
+ // implements Transport interface
+ virtual int SendPacket(int channel, const void *data, int len) {
+ if (!T::network_interface_) {
+ return -1;
+ }
+
+ // We need to store the sequence number to be able to pick up
+ // the same sequence when the device is restarted.
+ // TODO: Remove when WebRtc has fixed the problem.
+ int seq_num;
+ if (!GetRtpSeqNum(data, len, &seq_num)) {
+ return -1;
+ }
+ if (sequence_number() == -1) {
+ LOG(INFO) << "WebRtcVoiceMediaChannel sends first packet seqnum="
+ << seq_num;
+ }
+ sequence_number_ = seq_num;
+
+ talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+ return T::network_interface_->SendPacket(&packet) ? len : -1;
+ }
+ virtual int SendRTCPPacket(int channel, const void *data, int len) {
+ if (!T::network_interface_) {
+ return -1;
+ }
+
+ talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+ return T::network_interface_->SendRtcp(&packet) ? len : -1;
+ }
+ int sequence_number() const {
+ return sequence_number_;
+ }
+
+ private:
+ E *engine_;
+ int voe_channel_;
+ int sequence_number_;
+};
+
+// WebRtcVoiceMediaChannel is an implementation of VoiceMediaChannel that uses
+// WebRtc Voice Engine.
+class WebRtcVoiceMediaChannel
+ : public WebRtcMediaChannel<VoiceMediaChannel,
+ WebRtcVoiceEngine> {
+ public:
+ explicit WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine);
+ virtual ~WebRtcVoiceMediaChannel();
+ virtual bool SetOptions(int options);
+ virtual bool SetRecvCodecs(const std::vector<AudioCodec> &codecs);
+ virtual bool SetSendCodecs(const std::vector<AudioCodec> &codecs);
+ virtual bool SetRecvRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions);
+ virtual bool SetSendRtpHeaderExtensions(
+ const std::vector<RtpHeaderExtension>& extensions);
+ virtual bool SetPlayout(bool playout);
+ bool PausePlayout();
+ bool ResumePlayout();
+ virtual bool SetSend(SendFlags send);
+ bool PauseSend();
+ bool ResumeSend();
+ virtual bool AddStream(uint32 ssrc);
+ virtual bool RemoveStream(uint32 ssrc);
+ virtual bool GetActiveStreams(AudioInfo::StreamList* actives);
+ virtual int GetOutputLevel();
+ virtual bool SetOutputScaling(uint32 ssrc, double left, double right);
+ virtual bool GetOutputScaling(uint32 ssrc, double* left, double* right);
+
+ virtual bool SetRingbackTone(const char *buf, int len);
+ virtual bool PlayRingbackTone(uint32 ssrc, bool play, bool loop);
+ virtual bool PressDTMF(int event, bool playout);
+
+ virtual void OnPacketReceived(talk_base::Buffer* packet);
+ virtual void OnRtcpReceived(talk_base::Buffer* packet);
+ virtual void SetSendSsrc(uint32 id);
+ virtual bool SetRtcpCName(const std::string& cname);
+ virtual bool Mute(bool mute);
+ virtual bool SetSendBandwidth(bool autobw, int bps) { return false; }
+ virtual bool GetStats(VoiceMediaInfo* info);
+ // Gets last reported error from WebRtc voice engine. This should be only
+ // called in response a failure.
+ virtual void GetLastMediaError(uint32* ssrc,
+ VoiceMediaChannel::Error* error);
+ bool FindSsrc(int channel_num, uint32* ssrc);
+ void OnError(uint32 ssrc, int error);
+
+ protected:
+ int GetLastEngineError() { return engine()->GetLastEngineError(); }
+ int GetChannel(uint32 ssrc);
+ int GetOutputLevel(int channel);
+ bool GetRedSendCodec(const AudioCodec& red_codec,
+ const std::vector<AudioCodec>& all_codecs,
+ webrtc::CodecInst* send_codec);
+ bool EnableRtcp(int channel);
+ bool SetPlayout(int channel, bool playout);
+ static uint32 ParseSsrc(const void* data, size_t len, bool rtcp);
+ static Error WebRtcErrorToChannelError(int err_code);
+
+ private:
+ // Tandberg-bridged conferences require a -10dB gain adjustment,
+ // which is actually +10 in AgcConfig.targetLeveldBOv
+ static const int kTandbergDbAdjustment = 10;
+
+ bool ChangePlayout(bool playout);
+ bool ChangeSend(SendFlags send);
+
+ typedef std::map<uint32, int> ChannelMap;
+ talk_base::scoped_ptr<WebRtcSoundclipStream> ringback_tone_;
+ std::set<int> ringback_channels_; // channels playing ringback
+ int channel_options_;
+ bool agc_adjusted_;
+ bool dtmf_allowed_;
+ bool desired_playout_;
+ bool playout_;
+ SendFlags desired_send_;
+ SendFlags send_;
+ ChannelMap mux_channels_; // for multiple sources
+ // mux_channels_ can be read from WebRtc callback thread. Accesses off the
+ // WebRtc thread must be synchronized with edits on the worker thread. Reads
+ // on the worker thread are ok.
+ mutable talk_base::CriticalSection mux_channels_cs_;
+};
+}
+
+#endif // TALK_SESSION_PHONE_WEBRTCVOICEENGINE_H_
diff --git a/talk/session/phone/webrtcvoiceengine_unittest.cc b/talk/session/phone/webrtcvoiceengine_unittest.cc
new file mode 100644
index 0000000..0660630
--- /dev/null
+++ b/talk/session/phone/webrtcvoiceengine_unittest.cc
@@ -0,0 +1,1148 @@
+// Copyright 2008 Google Inc. All Rights Reserved.
+//
+// Author: Justin Uberti (juberti@google.com)
+
+#include "talk/base/byteorder.h"
+#include "talk/base/gunit.h"
+#include "talk/session/phone/channel.h"
+#include "talk/session/phone/fakemediaengine.h"
+#include "talk/session/phone/fakertp.h"
+#include "talk/session/phone/fakesession.h"
+#include "talk/session/phone/fakewebrtcvoiceengine.h"
+#include "talk/session/phone/webrtcvoiceengine.h"
+
+// Tests for the WebRtcVoiceEngine/VoiceChannel code.
+
+static const cricket::AudioCodec kPcmuCodec(0, "PCMU", 8000, 64000, 1, 0);
+static const cricket::AudioCodec kIsacCodec(103, "ISAC", 16000, -1, 1, 0);
+static const cricket::AudioCodec kRedCodec(117, "red", 8000, 0, 1, 0);
+static const cricket::AudioCodec kCn8000Codec(13, "CN", 8000, 0, 1, 0);
+static const cricket::AudioCodec kCn16000Codec(105, "CN", 16000, 0, 1, 0);
+static const cricket::AudioCodec
+ kTelephoneEventCodec(106, "telephone-event", 8000, 0, 1, 0);
+static const cricket::AudioCodec* const kAudioCodecs[] = {
+ &kPcmuCodec, &kIsacCodec, &kRedCodec, &kCn8000Codec, &kCn16000Codec,
+ &kTelephoneEventCodec,
+};
+const char kRingbackTone[] = "RIFF____WAVE____ABCD1234";
+
+class FakeVoEWrapper : public cricket::VoEWrapper {
+ public:
+ explicit FakeVoEWrapper(cricket::FakeWebRtcVoiceEngine* engine)
+ : cricket::VoEWrapper(engine, // processing
+ engine, // base
+ engine, // codec
+ engine, // dtmf
+ engine, // file
+ engine, // hw
+ engine, // media
+ engine, // neteq
+ engine, // network
+ engine, // rtp
+ engine, // sync
+ engine) { // volume
+ }
+};
+
+class NullVoETraceWrapper : public cricket::VoETraceWrapper {
+ public:
+ virtual int SetTraceFilter(const unsigned int filter) {
+ return 0;
+ }
+ virtual int SetTraceFile(const char* fileNameUTF8) {
+ return 0;
+ }
+ virtual int SetTraceCallback(webrtc::TraceCallback* callback) {
+ return 0;
+ }
+};
+
+class WebRtcVoiceEngineTest : public testing::Test {
+ public:
+ class ChannelErrorListener : public sigslot::has_slots<> {
+ public:
+ explicit ChannelErrorListener(cricket::VoiceMediaChannel* channel)
+ : ssrc_(0), error_(cricket::VoiceMediaChannel::ERROR_NONE) {
+ ASSERT(channel != NULL);
+ channel->SignalMediaError.connect(
+ this, &ChannelErrorListener::OnVoiceChannelError);
+ }
+ void OnVoiceChannelError(uint32 ssrc,
+ cricket::VoiceMediaChannel::Error error) {
+ ssrc_ = ssrc;
+ error_ = error;
+ }
+ void Reset() {
+ ssrc_ = 0;
+ error_ = cricket::VoiceMediaChannel::ERROR_NONE;
+ }
+ uint32 ssrc() const {
+ return ssrc_;
+ }
+ cricket::VoiceMediaChannel::Error error() const {
+ return error_;
+ }
+
+ private:
+ uint32 ssrc_;
+ cricket::VoiceMediaChannel::Error error_;
+ };
+ // TODO: Implement other stub interfaces (VQE)
+ WebRtcVoiceEngineTest()
+ : voe_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
+ voe_sc_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
+ engine_(new FakeVoEWrapper(&voe_),
+ new FakeVoEWrapper(&voe_sc_),
+ new NullVoETraceWrapper()),
+ channel_(NULL), soundclip_(NULL) {
+ }
+ bool SetupEngine() {
+ bool result = engine_.Init();
+ if (result) {
+ channel_ = engine_.CreateChannel();
+ result = (channel_ != NULL);
+ }
+ return result;
+ }
+ void DeliverPacket(const void* data, int len) {
+ talk_base::Buffer packet(data, len);
+ channel_->OnPacketReceived(&packet);
+ }
+ virtual void TearDown() {
+ delete soundclip_;
+ delete channel_;
+ engine_.Terminate();
+ }
+
+ protected:
+ cricket::FakeWebRtcVoiceEngine voe_;
+ cricket::FakeWebRtcVoiceEngine voe_sc_;
+ cricket::WebRtcVoiceEngine engine_;
+ cricket::VoiceMediaChannel* channel_;
+ cricket::SoundclipMedia* soundclip_;
+};
+
+// Tests that our stub library "works".
+TEST_F(WebRtcVoiceEngineTest, StartupShutdown) {
+ EXPECT_FALSE(voe_.IsInited());
+ EXPECT_FALSE(voe_sc_.IsInited());
+ EXPECT_TRUE(engine_.Init());
+ EXPECT_TRUE(voe_.IsInited());
+ EXPECT_TRUE(voe_sc_.IsInited());
+ engine_.Terminate();
+ EXPECT_FALSE(voe_.IsInited());
+ EXPECT_FALSE(voe_sc_.IsInited());
+}
+
+// Tests that we can create and destroy a channel.
+TEST_F(WebRtcVoiceEngineTest, CreateChannel) {
+ EXPECT_TRUE(engine_.Init());
+ channel_ = engine_.CreateChannel();
+ EXPECT_TRUE(channel_ != NULL);
+}
+
+// Tests that we properly handle failures in CreateChannel.
+TEST_F(WebRtcVoiceEngineTest, CreateChannelFail) {
+ voe_.set_fail_create_channel(true);
+ EXPECT_TRUE(engine_.Init());
+ channel_ = engine_.CreateChannel();
+ EXPECT_TRUE(channel_ == NULL);
+}
+
+// Tests that we can find codecs by name or id, and that we interpret the
+// clockrate and bitrate fields properly.
+TEST_F(WebRtcVoiceEngineTest, FindCodec) {
+ cricket::AudioCodec codec;
+ webrtc::CodecInst codec_inst;
+ // Find PCMU with explicit clockrate and bitrate.
+ EXPECT_TRUE(engine_.FindWebRtcCodec(kPcmuCodec, &codec_inst));
+ // Find ISAC with explicit clockrate and 0 bitrate.
+ EXPECT_TRUE(engine_.FindWebRtcCodec(kIsacCodec, &codec_inst));
+ // Find telephone-event with explicit clockrate and 0 bitrate.
+ EXPECT_TRUE(engine_.FindWebRtcCodec(kTelephoneEventCodec, &codec_inst));
+ // Find ISAC with a different payload id.
+ codec = kIsacCodec;
+ codec.id = 127;
+ EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ // Find PCMU with a 0 clockrate.
+ codec = kPcmuCodec;
+ codec.clockrate = 0;
+ EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ EXPECT_EQ(8000, codec_inst.plfreq);
+ // Find PCMU with a 0 bitrate.
+ codec = kPcmuCodec;
+ codec.bitrate = 0;
+ EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ EXPECT_EQ(64000, codec_inst.rate);
+ // Find ISAC with an explicit bitrate.
+ codec = kIsacCodec;
+ codec.bitrate = 32000;
+ EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ EXPECT_EQ(32000, codec_inst.rate);
+}
+
+// Test that we set our inbound codecs properly, including changing PT.
+TEST_F(WebRtcVoiceEngineTest, SetRecvCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 96;
+ EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
+ gcodec.plfreq = 16000;
+ EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+}
+
+// Test that we fail to set an unknown inbound codec.
+TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsUnsupportedCodec) {
+ EXPECT_TRUE(SetupEngine());
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(cricket::AudioCodec(127, "XYZ", 32000, 0, 1, 0));
+ EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+}
+
+// Test that we apply codecs properly.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs.push_back(kRedCodec);
+ codecs[0].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_FALSE(voe_.GetVAD(channel_num));
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+ EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+ EXPECT_EQ(105, voe_.GetSendCNPayloadType(channel_num, true));
+ EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
+// Test that we fall back to PCMU if no codecs are specified.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsNoCodecs) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(0, gcodec.pltype);
+ EXPECT_STREQ("PCMU", gcodec.plname);
+ EXPECT_FALSE(voe_.GetVAD(channel_num));
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+ EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+ EXPECT_EQ(105, voe_.GetSendCNPayloadType(channel_num, true));
+ EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
+// Test that we set VAD and DTMF types correctly.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsCNandDTMF) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ // TODO: cn 32000
+ codecs.push_back(kCn16000Codec);
+ codecs.push_back(kCn8000Codec);
+ codecs.push_back(kTelephoneEventCodec);
+ codecs.push_back(kRedCodec);
+ codecs[0].id = 96;
+ codecs[2].id = 97; // wideband CN
+ codecs[4].id = 98; // DTMF
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_TRUE(voe_.GetVAD(channel_num));
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+ EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+ EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
+ EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
+// Test that we set up FEC correctly.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsRED) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kRedCodec);
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 127;
+ codecs[0].params[""] = "96/96";
+ codecs[1].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_TRUE(voe_.GetFEC(channel_num));
+ EXPECT_EQ(127, voe_.GetSendFECPayloadType(channel_num));
+}
+
+// Test that we set up FEC correctly if params are omitted.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsREDNoParams) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kRedCodec);
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 127;
+ codecs[1].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_TRUE(voe_.GetFEC(channel_num));
+ EXPECT_EQ(127, voe_.GetSendFECPayloadType(channel_num));
+}
+
+// Test that we ignore RED if the parameters aren't named the way we expect.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED1) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kRedCodec);
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 127;
+ codecs[0].params["ABC"] = "96/96";
+ codecs[1].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it uses different primary/secondary encoding.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED2) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kRedCodec);
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 127;
+ codecs[0].params[""] = "96/0";
+ codecs[1].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it uses more than 2 encodings.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED3) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kRedCodec);
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 127;
+ codecs[0].params[""] = "96/96/96";
+ codecs[1].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it has bogus codec ids.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED4) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kRedCodec);
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 127;
+ codecs[0].params[""] = "ABC/ABC";
+ codecs[1].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we ignore RED if it refers to a codec that is not present.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBadRED5) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kRedCodec);
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs[0].id = 127;
+ codecs[0].params[""] = "97/97";
+ codecs[1].id = 96;
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+}
+
+// Test that we support setting an empty list of recv header extensions.
+TEST_F(WebRtcVoiceEngineTest, SetRecvRtpHeaderExtensions) {
+ EXPECT_TRUE(SetupEngine());
+ std::vector<cricket::RtpHeaderExtension> extensions;
+ int channel_num = voe_.GetLastChannel();
+ bool enable = false;
+ unsigned char id = 0;
+
+ // An empty list shouldn't cause audio-level headers to be enabled.
+ EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions));
+ EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+ channel_num, enable, id));
+ EXPECT_FALSE(enable);
+
+ // Nor should indicating we can receive the audio-level header.
+ extensions.push_back(cricket::RtpHeaderExtension(
+ "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8));
+ EXPECT_TRUE(channel_->SetRecvRtpHeaderExtensions(extensions));
+ EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+ channel_num, enable, id));
+ EXPECT_FALSE(enable);
+}
+
+// Test that we support setting certain send header extensions.
+TEST_F(WebRtcVoiceEngineTest, SetSendRtpHeaderExtensions) {
+ EXPECT_TRUE(SetupEngine());
+ std::vector<cricket::RtpHeaderExtension> extensions;
+ int channel_num = voe_.GetLastChannel();
+ bool enable = false;
+ unsigned char id = 0;
+
+ // Ensure audio levels are off by default.
+ EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+ channel_num, enable, id));
+ EXPECT_FALSE(enable);
+
+ // Ensure audio levels stay off with an empty list of headers.
+ EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+ EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+ channel_num, enable, id));
+ EXPECT_FALSE(enable);
+
+ // Ensure audio levels are enabled if the audio-level header is specified.
+ extensions.push_back(cricket::RtpHeaderExtension(
+ "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8));
+ EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+ EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+ channel_num, enable, id));
+ EXPECT_TRUE(enable);
+ EXPECT_EQ(8, id);
+
+ // Ensure audio levels go back off with an empty list.
+ extensions.clear();
+ EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions));
+ EXPECT_EQ(0, voe_.GetRTPAudioLevelIndicationStatus(
+ channel_num, enable, id));
+ EXPECT_FALSE(enable);
+}
+
+// Test that we can create a channel and start sending/playing out on it.
+TEST_F(WebRtcVoiceEngineTest, SendAndPlayout) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_TRUE(voe_.GetSend(channel_num));
+ EXPECT_TRUE(channel_->SetPlayout(true));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_NOTHING));
+ EXPECT_FALSE(voe_.GetSend(channel_num));
+ EXPECT_TRUE(channel_->SetPlayout(false));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can set the devices to use.
+TEST_F(WebRtcVoiceEngineTest, SetDevices) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+ cricket::Device default_dev(cricket::kFakeDefaultDeviceName,
+ cricket::kFakeDefaultDeviceId);
+ cricket::Device dev(cricket::kFakeDeviceName,
+ cricket::kFakeDeviceId);
+
+ // Test SetDevices() while not sending or playing.
+ EXPECT_TRUE(engine_.SetDevices(&default_dev, &default_dev));
+
+ // Test SetDevices() while sending and playing.
+ EXPECT_TRUE(engine_.SetLocalMonitor(true));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_TRUE(channel_->SetPlayout(true));
+ EXPECT_TRUE(voe_.GetRecordingMicrophone());
+ EXPECT_TRUE(voe_.GetSend(channel_num));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num));
+
+ EXPECT_TRUE(engine_.SetDevices(&dev, &dev));
+
+ EXPECT_TRUE(voe_.GetRecordingMicrophone());
+ EXPECT_TRUE(voe_.GetSend(channel_num));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num));
+
+ // Test that failure to open newly selected devices does not prevent opening
+ // ones after that.
+ voe_.set_fail_start_recording_microphone(true);
+ voe_.set_playout_fail_channel(channel_num);
+ voe_.set_send_fail_channel(channel_num);
+
+ EXPECT_FALSE(engine_.SetDevices(&default_dev, &default_dev));
+
+ EXPECT_FALSE(voe_.GetRecordingMicrophone());
+ EXPECT_FALSE(voe_.GetSend(channel_num));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num));
+
+ voe_.set_fail_start_recording_microphone(false);
+ voe_.set_playout_fail_channel(-1);
+ voe_.set_send_fail_channel(-1);
+
+ EXPECT_TRUE(engine_.SetDevices(&dev, &dev));
+
+ EXPECT_TRUE(voe_.GetRecordingMicrophone());
+ EXPECT_TRUE(voe_.GetSend(channel_num));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can set the devices to use even if we failed to
+// open the initial ones.
+TEST_F(WebRtcVoiceEngineTest, SetDevicesWithInitiallyBadDevices) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+
+ cricket::Device default_dev(cricket::kFakeDefaultDeviceName,
+ cricket::kFakeDefaultDeviceId);
+ cricket::Device dev(cricket::kFakeDeviceName,
+ cricket::kFakeDeviceId);
+
+ // Test that failure to open devices selected before starting
+ // send/play does not prevent opening newly selected ones after that.
+ voe_.set_fail_start_recording_microphone(true);
+ voe_.set_playout_fail_channel(channel_num);
+ voe_.set_send_fail_channel(channel_num);
+
+ EXPECT_TRUE(engine_.SetDevices(&default_dev, &default_dev));
+
+ EXPECT_FALSE(engine_.SetLocalMonitor(true));
+ EXPECT_FALSE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_FALSE(channel_->SetPlayout(true));
+ EXPECT_FALSE(voe_.GetRecordingMicrophone());
+ EXPECT_FALSE(voe_.GetSend(channel_num));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num));
+
+ voe_.set_fail_start_recording_microphone(false);
+ voe_.set_playout_fail_channel(-1);
+ voe_.set_send_fail_channel(-1);
+
+ EXPECT_TRUE(engine_.SetDevices(&dev, &dev));
+
+ EXPECT_TRUE(voe_.GetRecordingMicrophone());
+ EXPECT_TRUE(voe_.GetSend(channel_num));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can create a channel configured for multi-point conferences,
+// and start sending/playing out on it.
+TEST_F(WebRtcVoiceEngineTest, ConferenceSendAndPlayout) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_CONFERENCE));
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_TRUE(voe_.GetSend(channel_num));
+
+ bool enabled;
+ webrtc::EcModes ec_mode;
+ webrtc::NsModes ns_mode;
+ EXPECT_EQ(0, voe_.GetEcStatus(enabled, ec_mode));
+#ifdef CHROMEOS
+ EXPECT_EQ(webrtc::kEcDefault, ec_mode);
+#else
+ EXPECT_EQ(webrtc::kEcConference, ec_mode);
+#endif
+ EXPECT_EQ(0, voe_.GetNsStatus(enabled, ns_mode));
+ EXPECT_TRUE(enabled);
+#ifdef CHROMEOS
+ EXPECT_EQ(webrtc::kNsDefault, ns_mode);
+#else
+ EXPECT_EQ(webrtc::kNsConference, ns_mode);
+#endif
+
+ EXPECT_TRUE(channel_->SetPlayout(true));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_NOTHING));
+ EXPECT_FALSE(voe_.GetSend(channel_num));
+
+ EXPECT_EQ(0, voe_.GetEcStatus(enabled, ec_mode));
+ EXPECT_EQ(webrtc::kEcDefault, ec_mode);
+ EXPECT_EQ(0, voe_.GetNsStatus(enabled, ns_mode));
+ EXPECT_EQ(webrtc::kNsDefault, ns_mode);
+
+ EXPECT_TRUE(channel_->SetPlayout(false));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can create a channel configured for Codian bridges,
+// and start sending/playing out on it.
+TEST_F(WebRtcVoiceEngineTest, CodianSendAndPlayout) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ webrtc::AgcConfig agc_config;
+ EXPECT_EQ(0, voe_.GetAgcConfig(agc_config));
+ EXPECT_EQ(0, agc_config.targetLeveldBOv);
+ EXPECT_TRUE(channel_->SetOptions(cricket::OPT_AGC_TANDBERG_LEVELS));
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_TRUE(voe_.GetSend(channel_num));
+ EXPECT_EQ(0, voe_.GetAgcConfig(agc_config));
+ EXPECT_GT(agc_config.targetLeveldBOv, 0); // level was attenuated
+ EXPECT_TRUE(channel_->SetPlayout(true));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_NOTHING));
+ EXPECT_FALSE(voe_.GetSend(channel_num));
+ EXPECT_EQ(0, voe_.GetAgcConfig(agc_config));
+ EXPECT_EQ(0, agc_config.targetLeveldBOv); // level was restored
+ EXPECT_TRUE(channel_->SetPlayout(false));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num));
+}
+
+// Test that we can set the outgoing SSRC properly.
+TEST_F(WebRtcVoiceEngineTest, SetSendSsrc) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ unsigned int send_ssrc;
+ EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
+ EXPECT_NE(0U, send_ssrc);
+ channel_->SetSendSsrc(0x99);
+ EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num, send_ssrc));
+ EXPECT_EQ(0x99U, send_ssrc);
+}
+
+// Test that we can properly receive packets.
+TEST_F(WebRtcVoiceEngineTest, Recv) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+ EXPECT_TRUE(voe_.CheckPacket(channel_num, kPcmuFrame,
+ sizeof(kPcmuFrame)));
+}
+
+// Test that we can add and remove streams, and do proper send/playout.
+// We can receive on multiple streams, but will only send on one.
+TEST_F(WebRtcVoiceEngineTest, SendAndPlayoutWithMultipleStreams) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num1 = voe_.GetLastChannel();
+
+ // Start playout on the default channel.
+ EXPECT_TRUE(channel_->SetPlayout(true));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num1));
+
+ // Adding another stream should disable playout on the default channel.
+ EXPECT_TRUE(channel_->AddStream(2));
+ int channel_num2 = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_TRUE(voe_.GetSend(channel_num1));
+ EXPECT_FALSE(voe_.GetSend(channel_num2));
+
+ // Make sure only the new channel is played out.
+ EXPECT_FALSE(voe_.GetPlayout(channel_num1));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num2));
+
+ // Adding yet another stream should have stream 2 and 3 enabled for playout.
+ EXPECT_TRUE(channel_->AddStream(3));
+ int channel_num3 = voe_.GetLastChannel();
+ EXPECT_FALSE(voe_.GetPlayout(channel_num1));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num2));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num3));
+ EXPECT_FALSE(voe_.GetSend(channel_num3));
+
+ // Stop sending.
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_NOTHING));
+ EXPECT_FALSE(voe_.GetSend(channel_num1));
+ EXPECT_FALSE(voe_.GetSend(channel_num2));
+ EXPECT_FALSE(voe_.GetSend(channel_num3));
+
+ // Stop playout.
+ EXPECT_TRUE(channel_->SetPlayout(false));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num1));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num2));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num3));
+
+ // Restart playout and make sure the default channel still is not played out.
+ EXPECT_TRUE(channel_->SetPlayout(true));
+ EXPECT_FALSE(voe_.GetPlayout(channel_num1));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num2));
+ EXPECT_TRUE(voe_.GetPlayout(channel_num3));
+
+ // Now remove the new streams and verify that the default channel is
+ // played out again.
+ EXPECT_TRUE(channel_->RemoveStream(3));
+ EXPECT_TRUE(channel_->RemoveStream(2));
+
+ EXPECT_TRUE(voe_.GetPlayout(channel_num1));
+}
+
+// Test that we can set the outgoing SSRC properly with multiple streams.
+TEST_F(WebRtcVoiceEngineTest, SetSendSsrcWithMultipleStreams) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num1 = voe_.GetLastChannel();
+ unsigned int send_ssrc;
+ channel_->SetSendSsrc(0x99);
+ EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num1, send_ssrc));
+ EXPECT_EQ(0x99U, send_ssrc);
+ EXPECT_TRUE(channel_->AddStream(2));
+ int channel_num2 = voe_.GetLastChannel();
+ EXPECT_EQ(0, voe_.GetLocalSSRC(channel_num2, send_ssrc));
+ EXPECT_EQ(0x99U, send_ssrc);
+}
+
+// Test that we properly handle failures to add a stream.
+TEST_F(WebRtcVoiceEngineTest, AddStreamFail) {
+ EXPECT_TRUE(SetupEngine());
+ voe_.set_fail_create_channel(true);
+ EXPECT_FALSE(channel_->AddStream(2));
+}
+
+// Test that we can properly receive packets on multiple streams.
+TEST_F(WebRtcVoiceEngineTest, RecvWithMultipleStreams) {
+ EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->AddStream(1));
+ int channel_num1 = voe_.GetLastChannel();
+ EXPECT_TRUE(channel_->AddStream(2));
+ int channel_num2 = voe_.GetLastChannel();
+ EXPECT_TRUE(channel_->AddStream(3));
+ int channel_num3 = voe_.GetLastChannel();
+ // Create packets with the right SSRCs.
+ char packets[4][sizeof(kPcmuFrame)];
+ for (size_t i = 0; i < ARRAY_SIZE(packets); ++i) {
+ memcpy(packets[i], kPcmuFrame, sizeof(kPcmuFrame));
+ talk_base::SetBE32(packets[i] + 8, i);
+ }
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
+ DeliverPacket(packets[0], sizeof(packets[0]));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
+ DeliverPacket(packets[1], sizeof(packets[1]));
+ EXPECT_TRUE(voe_.CheckPacket(channel_num1, packets[1],
+ sizeof(packets[1])));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
+ DeliverPacket(packets[2], sizeof(packets[2]));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
+ EXPECT_TRUE(voe_.CheckPacket(channel_num2, packets[2],
+ sizeof(packets[2])));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num3));
+ DeliverPacket(packets[3], sizeof(packets[3]));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num1));
+ EXPECT_TRUE(voe_.CheckNoPacket(channel_num2));
+ EXPECT_TRUE(voe_.CheckPacket(channel_num3, packets[3],
+ sizeof(packets[3])));
+ EXPECT_TRUE(channel_->RemoveStream(3));
+ EXPECT_TRUE(channel_->RemoveStream(2));
+ EXPECT_TRUE(channel_->RemoveStream(1));
+}
+
+// Test that we properly clean up any streams that were added, even if
+// not explicitly removed.
+TEST_F(WebRtcVoiceEngineTest, StreamCleanup) {
+ EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->AddStream(1));
+ EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_EQ(3, voe_.GetNumChannels()); // default channel + 2 added
+ delete channel_;
+ channel_ = NULL;
+ EXPECT_EQ(0, voe_.GetNumChannels());
+}
+
+// Test that we can send DTMF properly, but only if the other side supports
+// telephone-event.
+TEST_F(WebRtcVoiceEngineTest, SendDtmf) {
+ EXPECT_TRUE(SetupEngine());
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_FALSE(channel_->PressDTMF(1, true));
+ codecs.push_back(kTelephoneEventCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->PressDTMF(1, true));
+}
+
+// Test that we can play a ringback tone properly in a single-stream call.
+TEST_F(WebRtcVoiceEngineTest, PlayRingback) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+ // Check we fail if no ringback tone specified.
+ EXPECT_FALSE(channel_->PlayRingbackTone(0, true, true));
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+ // Check we can set and play a ringback tone.
+ EXPECT_TRUE(channel_->SetRingbackTone(kRingbackTone, strlen(kRingbackTone)));
+ EXPECT_TRUE(channel_->PlayRingbackTone(0, true, true));
+ EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+ // Check we can stop the tone manually.
+ EXPECT_TRUE(channel_->PlayRingbackTone(0, false, false));
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+ // Check we stop the tone if a packet arrives.
+ EXPECT_TRUE(channel_->PlayRingbackTone(0, true, true));
+ EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+ DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+}
+
+// Test that we can play a ringback tone properly in a multi-stream call.
+TEST_F(WebRtcVoiceEngineTest, PlayRingbackWithMultipleStreams) {
+ EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->AddStream(1));
+ EXPECT_TRUE(channel_->AddStream(2));
+ int channel_num = voe_.GetLastChannel();
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+ // Check we fail if no ringback tone specified.
+ EXPECT_FALSE(channel_->PlayRingbackTone(2, true, true));
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+ // Check we can set and play a ringback tone on the correct ssrc.
+ EXPECT_TRUE(channel_->SetRingbackTone(kRingbackTone, strlen(kRingbackTone)));
+ EXPECT_FALSE(channel_->PlayRingbackTone(77, true, true));
+ EXPECT_TRUE(channel_->PlayRingbackTone(2, true, true));
+ EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+ // Check we can stop the tone manually.
+ EXPECT_TRUE(channel_->PlayRingbackTone(2, false, false));
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+ // Check we stop the tone if a packet arrives, but only with the right SSRC.
+ EXPECT_TRUE(channel_->PlayRingbackTone(2, true, true));
+ EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+ // Send a packet with SSRC 1; the tone should not stop.
+ DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
+ EXPECT_EQ(1, voe_.IsPlayingFileLocally(channel_num));
+ // Send a packet with SSRC 2; the tone should stop.
+ char packet[sizeof(kPcmuFrame)];
+ memcpy(packet, kPcmuFrame, sizeof(kPcmuFrame));
+ talk_base::SetBE32(packet + 8, 2);
+ DeliverPacket(packet, sizeof(packet));
+ EXPECT_EQ(0, voe_.IsPlayingFileLocally(channel_num));
+}
+
+// Tests creating soundclips, and make sure they come from the right engine.
+TEST_F(WebRtcVoiceEngineTest, CreateSoundclip) {
+ EXPECT_TRUE(engine_.Init());
+ soundclip_ = engine_.CreateSoundclip();
+ ASSERT_TRUE(soundclip_ != NULL);
+ EXPECT_EQ(0, voe_.GetNumChannels());
+ EXPECT_EQ(1, voe_sc_.GetNumChannels());
+ int channel_num = voe_sc_.GetLastChannel();
+ EXPECT_TRUE(voe_sc_.GetPlayout(channel_num));
+ delete soundclip_;
+ soundclip_ = NULL;
+ EXPECT_EQ(0, voe_sc_.GetNumChannels());
+}
+
+// Tests playing out a fake sound.
+TEST_F(WebRtcVoiceEngineTest, PlaySoundclip) {
+ static const char kZeroes[16000] = {};
+ EXPECT_TRUE(engine_.Init());
+ soundclip_ = engine_.CreateSoundclip();
+ ASSERT_TRUE(soundclip_ != NULL);
+ EXPECT_TRUE(soundclip_->PlaySound(kZeroes, sizeof(kZeroes), 0));
+}
+
+TEST_F(WebRtcVoiceEngineTest, MediaEngineCallbackOnError) {
+ talk_base::scoped_ptr<ChannelErrorListener> listener;
+ cricket::WebRtcVoiceMediaChannel* media_channel;
+ unsigned int ssrc = 0;
+
+ EXPECT_TRUE(SetupEngine());
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+
+ media_channel = reinterpret_cast<cricket::WebRtcVoiceMediaChannel*>(channel_);
+ listener.reset(new ChannelErrorListener(channel_));
+
+ // Test on WebRtc VoE channel.
+ voe_.TriggerCallbackOnError(media_channel->voe_channel(),
+ VE_SATURATION_WARNING);
+ EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
+ listener->error());
+ EXPECT_NE(-1, voe_.GetLocalSSRC(voe_.GetLastChannel(), ssrc));
+ EXPECT_EQ(ssrc, listener->ssrc());
+
+ listener->Reset();
+ voe_.TriggerCallbackOnError(-1, VE_TYPING_NOISE_WARNING);
+ EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_TYPING_NOISE_DETECTED,
+ listener->error());
+ EXPECT_EQ(0U, listener->ssrc());
+
+ // Add another stream and test on that.
+ ++ssrc;
+ EXPECT_TRUE(channel_->AddStream(ssrc));
+ listener->Reset();
+ voe_.TriggerCallbackOnError(voe_.GetLastChannel(),
+ VE_SATURATION_WARNING);
+ EXPECT_EQ(cricket::VoiceMediaChannel::ERROR_REC_DEVICE_SATURATION,
+ listener->error());
+ EXPECT_EQ(ssrc, listener->ssrc());
+
+ // Testing a non-existing channel.
+ listener->Reset();
+ voe_.TriggerCallbackOnError(voe_.GetLastChannel() + 2,
+ VE_SATURATION_WARNING);
+ EXPECT_EQ(0, listener->error());
+}
+
+TEST_F(WebRtcVoiceEngineTest, TestSetPlayoutError) {
+ EXPECT_TRUE(SetupEngine());
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kPcmuCodec);
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_TRUE(channel_->SetSend(cricket::SEND_MICROPHONE));
+ EXPECT_TRUE(channel_->AddStream(2));
+ EXPECT_TRUE(channel_->AddStream(3));
+ EXPECT_TRUE(channel_->SetPlayout(true));
+ voe_.set_playout_fail_channel(voe_.GetLastChannel() - 1);
+ EXPECT_TRUE(channel_->SetPlayout(false));
+ EXPECT_FALSE(channel_->SetPlayout(true));
+}
+
+// Tests for the actual WebRtc VoE library.
+
+// Tests that the library initializes and shuts down properly.
+TEST(WebRtcVoiceEngineLibTest, StartupShutdown) {
+ cricket::WebRtcVoiceEngine engine;
+ EXPECT_TRUE(engine.Init());
+ cricket::VoiceMediaChannel* channel = engine.CreateChannel();
+ EXPECT_TRUE(channel != NULL);
+ delete channel;
+ engine.Terminate();
+
+ // Reinit to catch regression where VoiceEngineObserver reference is lost
+ EXPECT_TRUE(engine.Init());
+ engine.Terminate();
+}
+
+// Tests that the logging from the library is cleartext.
+// TODO: This test case is disabled due to a known bug in VoE4.0
+// which sends out truncated log message. Will be fixed in next VoE release.
+TEST(WebRtcVoiceEngineLibTest, DISABLED_HasUnencryptedLogging) {
+ cricket::WebRtcVoiceEngine engine;
+ talk_base::scoped_ptr<talk_base::MemoryStream> stream(
+ new talk_base::MemoryStream);
+ size_t size = 0;
+ bool cleartext = true;
+ talk_base::LogMessage::AddLogToStream(stream.get(), talk_base::LS_VERBOSE);
+ engine.SetLogging(talk_base::LS_VERBOSE, "");
+ EXPECT_TRUE(engine.Init());
+ EXPECT_TRUE(stream->GetSize(&size));
+ EXPECT_GT(size, 0U);
+ engine.Terminate();
+ talk_base::LogMessage::RemoveLogToStream(stream.get());
+ const char* buf = stream->GetBuffer();
+ for (size_t i = 0; i < size && cleartext; ++i) {
+ int ch = static_cast<int>(buf[i]);
+ ASSERT_GE(ch, 0) << "Out of bounds character in WebRtc VoE log: "
+ << std::hex << ch;
+ cleartext = (isprint(ch) || isspace(ch));
+ }
+ EXPECT_TRUE(cleartext);
+}
+
+// Tests we do not see any references to a monitor thread being spun up
+// when initiating the engine.
+TEST(WebRtcVoiceEngineLibTest, HasNoMonitorThread) {
+ cricket::WebRtcVoiceEngine engine;
+ talk_base::scoped_ptr<talk_base::MemoryStream> stream(
+ new talk_base::MemoryStream);
+ talk_base::LogMessage::AddLogToStream(stream.get(), talk_base::LS_VERBOSE);
+ engine.SetLogging(talk_base::LS_VERBOSE, "");
+ EXPECT_TRUE(engine.Init());
+ engine.Terminate();
+ talk_base::LogMessage::RemoveLogToStream(stream.get());
+
+ size_t size = 0;
+ EXPECT_TRUE(stream->GetSize(&size));
+ EXPECT_GT(size, 0U);
+ const std::string logs(stream->GetBuffer());
+ EXPECT_NE(std::string::npos, logs.find("ProcessThread"));
+}
+
+// Tests that the library is configured with the codecs we want.
+TEST(WebRtcVoiceEngineLibTest, HasCorrectCodecs) {
+ cricket::WebRtcVoiceEngine engine;
+ // Check codecs by name.
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "ISAC", 16000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "ISAC", 32000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "iLBC", 8000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "PCMU", 8000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "PCMA", 8000, 0, 1, 0)));
+// TODO: Add speex back, once webrtc supports it.
+// EXPECT_TRUE(engine.FindCodec(
+// cricket::AudioCodec(96, "speex", 16000, 0, 1, 0)));
+// EXPECT_TRUE(engine.FindCodec(
+// cricket::AudioCodec(96, "speex", 8000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "G722", 16000, 0, 1, 0)));
+// EXPECT_TRUE(engine.FindCodec(
+// cricket::AudioCodec(96, "GSM", 8000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "red", 8000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "CN", 32000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "CN", 16000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "CN", 8000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "telephone-event", 8000, 0, 1, 0)));
+ // Check codecs with an id by id.
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(0, "", 8000, 0, 1, 0))); // PCMU
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(8, "", 8000, 0, 1, 0))); // PCMA
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(9, "", 16000, 0, 1, 0))); // G722
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(13, "", 8000, 0, 1, 0))); // CN
+ // Check sample/bitrate matching.
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(0, "PCMU", 8000, 64000, 1, 0)));
+ // Check that bad codecs fail.
+ EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(99, "ABCD", 0, 0, 1, 0)));
+ EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(88, "", 0, 0, 1, 0)));
+ EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 0, 0, 2, 0)));
+ EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 5000, 0, 1, 0)));
+ EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 0, 5000, 1, 0)));
+ // Check that there aren't any extra codecs lying around.
+ EXPECT_EQ(11U, engine.codecs().size());
+ // Verify the payload id of common audio codecs, including CN, ISAC, and G722.
+ // TODO: WebRtc team may change the payload id.
+ for (std::vector<cricket::AudioCodec>::const_iterator it =
+ engine.codecs().begin(); it != engine.codecs().end(); ++it) {
+ if (it->name == "CN" && it->clockrate == 16000) {
+ EXPECT_EQ(98, it->id);
+ } else if (it->name == "CN" && it->clockrate == 32000) {
+ EXPECT_EQ(99, it->id);
+ } else if (it->name == "ISAC" && it->clockrate == 16000) {
+ EXPECT_EQ(103, it->id);
+ } else if (it->name == "ISAC" && it->clockrate == 32000) {
+ EXPECT_EQ(104, it->id);
+ } else if (it->name == "G722" && it->clockrate == 16000) {
+ EXPECT_EQ(9, it->id);
+ }
+ }
+
+ engine.Terminate();
+}
+
+// Tests that the list of supported codecs is created properly and ordered
+// correctly
+TEST_F(WebRtcVoiceEngineTest, CodecPreference) {
+ cricket::WebRtcVoiceEngine engine;
+ const std::vector<cricket::AudioCodec>& codecs = engine.codecs();
+ ASSERT_FALSE(codecs.empty());
+ EXPECT_EQ("ISAC", codecs[0].name);
+ EXPECT_EQ(16000, codecs[0].clockrate);
+ EXPECT_EQ(32000, codecs[0].bitrate);
+ int pref = codecs[0].preference;
+ for (size_t i = 1; i < codecs.size(); ++i) {
+ EXPECT_GT(pref, codecs[i].preference);
+ pref = codecs[i].preference;
+ }
+}
+
+TEST(WebRtcVoiceEngineLibTest, Has32Channels) {
+ cricket::WebRtcVoiceEngine engine;
+ EXPECT_TRUE(engine.Init());
+
+ cricket::VoiceMediaChannel* channels[32];
+ int num_channels = 0;
+
+ while (num_channels < ARRAY_SIZE(channels)) {
+ cricket::VoiceMediaChannel* channel = engine.CreateChannel();
+ if (!channel)
+ break;
+
+ channels[num_channels++] = channel;
+ }
+
+ int expected = ARRAY_SIZE(channels);
+ EXPECT_EQ(expected, num_channels);
+
+ while (num_channels > 0) {
+ delete channels[--num_channels];
+ }
+
+ engine.Terminate();
+}
+
+#ifdef WIN32
+// Test our workarounds to WebRtc VoE' munging of the coinit count
+TEST(WebRtcVoiceEngineLibTest, CoInitialize) {
+ cricket::WebRtcVoiceEngine* engine = new cricket::WebRtcVoiceEngine();
+
+ // Initial refcount should be 0.
+ EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+
+ // Engine should start even with COM already inited.
+ EXPECT_TRUE(engine->Init());
+ engine->Terminate();
+ EXPECT_TRUE(engine->Init());
+ engine->Terminate();
+
+ // Refcount after terminate should be 1 (in reality 3); test if it is nonzero.
+ EXPECT_EQ(S_FALSE, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+ // Decrement refcount to (hopefully) 0.
+ CoUninitialize();
+ CoUninitialize();
+ delete engine;
+
+ // Ensure refcount is 0.
+ EXPECT_EQ(S_OK, CoInitializeEx(NULL, COINIT_MULTITHREADED));
+ CoUninitialize();
+}
+#endif
diff --git a/talk/sound/nullsoundsystem.cc b/talk/sound/nullsoundsystem.cc
new file mode 100644
index 0000000..2920008
--- /dev/null
+++ b/talk/sound/nullsoundsystem.cc
@@ -0,0 +1,174 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/sound/nullsoundsystem.h"
+
+#include "talk/base/logging.h"
+#include "talk/sound/sounddevicelocator.h"
+#include "talk/sound/soundinputstreaminterface.h"
+#include "talk/sound/soundoutputstreaminterface.h"
+
+namespace talk_base {
+
+class Thread;
+
+}
+
+namespace cricket {
+
+// Name used for the single device and the sound system itself.
+static const char kNullName[] = "null";
+
+class NullSoundDeviceLocator : public SoundDeviceLocator {
+ public:
+ NullSoundDeviceLocator() : SoundDeviceLocator(kNullName, kNullName) {}
+
+ virtual SoundDeviceLocator *Copy() const {
+ return new NullSoundDeviceLocator();
+ }
+};
+
+class NullSoundInputStream : public SoundInputStreamInterface {
+ public:
+ virtual bool StartReading() {
+ return true;
+ }
+
+ virtual bool StopReading() {
+ return true;
+ }
+
+ virtual bool GetVolume(int *volume) {
+ *volume = SoundSystemInterface::kMinVolume;
+ return true;
+ }
+
+ virtual bool SetVolume(int volume) {
+ return false;
+ }
+
+ virtual bool Close() {
+ return true;
+ }
+
+ virtual int LatencyUsecs() {
+ return 0;
+ }
+};
+
+class NullSoundOutputStream : public SoundOutputStreamInterface {
+ public:
+ virtual bool EnableBufferMonitoring() {
+ return true;
+ }
+
+ virtual bool DisableBufferMonitoring() {
+ return true;
+ }
+
+ virtual bool WriteSamples(const void *sample_data,
+ size_t size) {
+ LOG(LS_VERBOSE) << "Got " << size << " bytes of playback samples";
+ return true;
+ }
+
+ virtual bool GetVolume(int *volume) {
+ *volume = SoundSystemInterface::kMinVolume;
+ return true;
+ }
+
+ virtual bool SetVolume(int volume) {
+ return false;
+ }
+
+ virtual bool Close() {
+ return true;
+ }
+
+ virtual int LatencyUsecs() {
+ return 0;
+ }
+};
+
+NullSoundSystem::~NullSoundSystem() {
+}
+
+bool NullSoundSystem::Init() {
+ return true;
+}
+
+void NullSoundSystem::Terminate() {
+ // Nothing to do.
+}
+
+bool NullSoundSystem::EnumeratePlaybackDevices(
+ SoundSystemInterface::SoundDeviceLocatorList *devices) {
+ ClearSoundDeviceLocatorList(devices);
+ SoundDeviceLocator *device;
+ GetDefaultPlaybackDevice(&device);
+ devices->push_back(device);
+ return true;
+}
+
+bool NullSoundSystem::EnumerateCaptureDevices(
+ SoundSystemInterface::SoundDeviceLocatorList *devices) {
+ ClearSoundDeviceLocatorList(devices);
+ SoundDeviceLocator *device;
+ GetDefaultCaptureDevice(&device);
+ devices->push_back(device);
+ return true;
+}
+
+bool NullSoundSystem::GetDefaultPlaybackDevice(
+ SoundDeviceLocator **device) {
+ *device = new NullSoundDeviceLocator();
+ return true;
+}
+
+bool NullSoundSystem::GetDefaultCaptureDevice(
+ SoundDeviceLocator **device) {
+ *device = new NullSoundDeviceLocator();
+ return true;
+}
+
+SoundOutputStreamInterface *NullSoundSystem::OpenPlaybackDevice(
+ const SoundDeviceLocator *device,
+ const OpenParams ¶ms) {
+ return new NullSoundOutputStream();
+}
+
+SoundInputStreamInterface *NullSoundSystem::OpenCaptureDevice(
+ const SoundDeviceLocator *device,
+ const OpenParams ¶ms) {
+ return new NullSoundInputStream();
+}
+
+const char *NullSoundSystem::GetName() const {
+ return kNullName;
+}
+
+} // namespace cricket
diff --git a/talk/sound/nullsoundsystem.h b/talk/sound/nullsoundsystem.h
new file mode 100644
index 0000000..3edb4f9
--- /dev/null
+++ b/talk/sound/nullsoundsystem.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_SOUND_NULLSOUNDSYSTEM_H_
+#define TALK_SOUND_NULLSOUNDSYSTEM_H_
+
+#include "talk/sound/soundsysteminterface.h"
+
+namespace cricket {
+
+class SoundDeviceLocator;
+class SoundInputStreamInterface;
+class SoundOutputStreamInterface;
+
+// A simple reference sound system that drops output samples and generates
+// no input samples.
+class NullSoundSystem : public SoundSystemInterface {
+ public:
+ static SoundSystemInterface *Create() {
+ return new NullSoundSystem();
+ }
+
+ virtual ~NullSoundSystem();
+
+ virtual bool Init();
+ virtual void Terminate();
+
+ virtual bool EnumeratePlaybackDevices(SoundDeviceLocatorList *devices);
+ virtual bool EnumerateCaptureDevices(SoundDeviceLocatorList *devices);
+
+ virtual SoundOutputStreamInterface *OpenPlaybackDevice(
+ const SoundDeviceLocator *device,
+ const OpenParams ¶ms);
+ virtual SoundInputStreamInterface *OpenCaptureDevice(
+ const SoundDeviceLocator *device,
+ const OpenParams ¶ms);
+
+ virtual bool GetDefaultPlaybackDevice(SoundDeviceLocator **device);
+ virtual bool GetDefaultCaptureDevice(SoundDeviceLocator **device);
+
+ virtual const char *GetName() const;
+};
+
+} // namespace cricket
+
+#endif // TALK_SOUND_NULLSOUNDSYSTEM_H_
diff --git a/talk/sound/nullsoundsystemfactory.cc b/talk/sound/nullsoundsystemfactory.cc
new file mode 100644
index 0000000..089d51f
--- /dev/null
+++ b/talk/sound/nullsoundsystemfactory.cc
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/sound/nullsoundsystemfactory.h"
+
+#include "talk/sound/nullsoundsystem.h"
+
+namespace cricket {
+
+NullSoundSystemFactory::NullSoundSystemFactory() {
+}
+
+NullSoundSystemFactory::~NullSoundSystemFactory() {
+}
+
+bool NullSoundSystemFactory::SetupInstance() {
+ instance_.reset(new NullSoundSystem());
+ return true;
+}
+
+void NullSoundSystemFactory::CleanupInstance() {
+ instance_.reset();
+}
+
+} // namespace cricket
diff --git a/talk/sound/nullsoundsystemfactory.h b/talk/sound/nullsoundsystemfactory.h
new file mode 100644
index 0000000..71ae980
--- /dev/null
+++ b/talk/sound/nullsoundsystemfactory.h
@@ -0,0 +1,50 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_SOUND_NULLSOUNDSYSTEMFACTORY_H_
+#define TALK_SOUND_NULLSOUNDSYSTEMFACTORY_H_
+
+#include "talk/sound/soundsystemfactory.h"
+
+namespace cricket {
+
+// A SoundSystemFactory that always returns a NullSoundSystem. Intended for
+// testing.
+class NullSoundSystemFactory : public SoundSystemFactory {
+ public:
+ NullSoundSystemFactory();
+ virtual ~NullSoundSystemFactory();
+
+ protected:
+ // Inherited from SoundSystemFactory.
+ virtual bool SetupInstance();
+ virtual void CleanupInstance();
+};
+
+} // namespace cricket
+
+#endif // TALK_SOUND_NULLSOUNDSYSTEMFACTORY_H_
diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc
index a1aaa63..cf63e79 100644
--- a/talk/xmpp/constants.cc
+++ b/talk/xmpp/constants.cc
@@ -131,6 +131,13 @@
const std::string STR_RESULT("result");
const std::string STR_ERROR("error");
+const std::string STR_FORM("form");
+const std::string STR_SUBMIT("submit");
+const std::string STR_TEXT_SINGLE("text-single");
+const std::string STR_LIST_SINGLE("list-single");
+const std::string STR_LIST_MULTI("list-multi");
+const std::string STR_HIDDEN("hidden");
+const std::string STR_FORM_TYPE("FORM_TYPE");
const std::string STR_FROM("from");
const std::string STR_TO("to");
@@ -160,7 +167,11 @@
const std::string STR_UNAVAILABLE("unavailable");
-const std::string STR_MUC_LOOKUP_DOMAIN("lookup.groupchat.google.com");
+const Jid JID_GOOGLE_MUC_LOOKUP("lookup.groupchat.google.com");
+const std::string STR_MUC_ROOMCONFIG_ROOMNAME("muc#roomconfig_roomname");
+const std::string STR_MUC_ROOMCONFIG_FEATURES("muc#roomconfig_features");
+const std::string STR_MUC_ROOM_FEATURE_ENTERPRISE("muc_enterprise");
+const std::string STR_MUC_ROOMCONFIG("http://jabber.org/protocol/muc#roomconfig");
const QName QN_STREAM_STREAM(true, NS_STREAM, STR_STREAM);
const QName QN_STREAM_FEATURES(true, NS_STREAM, "features");
@@ -379,6 +390,22 @@
const QName QN_DISCO_ITEMS_QUERY(true, NS_DISCO_ITEMS, "query");
const QName QN_DISCO_ITEM(true, NS_DISCO_ITEMS, "item");
+// JEP 0020
+const std::string NS_FEATURE("http://jabber.org/protocol/feature-neg");
+const QName QN_FEATURE_FEATURE(true, NS_FEATURE, "feature");
+
+// JEP 0004
+const std::string NS_XDATA("jabber:x:data");
+const QName QN_XDATA_X(true, NS_XDATA, "x");
+const QName QN_XDATA_INSTRUCTIONS(true, NS_XDATA, "instructions");
+const QName QN_XDATA_TITLE(true, NS_XDATA, "title");
+const QName QN_XDATA_FIELD(true, NS_XDATA, "field");
+const QName QN_XDATA_REPORTED(true, NS_XDATA, "reported");
+const QName QN_XDATA_ITEM(true, NS_XDATA, "item");
+const QName QN_XDATA_DESC(true, NS_XDATA, "desc");
+const QName QN_XDATA_REQUIRED(true, NS_XDATA, "required");
+const QName QN_XDATA_VALUE(true, NS_XDATA, "value");
+const QName QN_XDATA_OPTION(true, NS_XDATA, "option");
// JEP 0045
const std::string NS_MUC("http://jabber.org/protocol/muc");
@@ -404,7 +431,7 @@
const QName QN_SEARCH_QUERY(true, NS_SEARCH, "query");
const QName QN_SEARCH_ITEM(true, NS_SEARCH, "item");
const QName QN_SEARCH_ROOM_NAME(true, NS_SEARCH, "room-name");
-const QName QN_SEARCH_ORGANIZERS_DOMAIN(true, NS_SEARCH, "organizers-domain");
+const QName QN_SEARCH_ROOM_DOMAIN(true, NS_SEARCH, "room-domain");
const QName QN_SEARCH_ROOM_JID(true, NS_SEARCH, "room-jid");
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
index ecc8e00..ba9c9e1 100644
--- a/talk/xmpp/constants.h
+++ b/talk/xmpp/constants.h
@@ -85,6 +85,13 @@
extern const std::string STR_RESULT;
extern const std::string STR_ERROR;
+extern const std::string STR_FORM;
+extern const std::string STR_SUBMIT;
+extern const std::string STR_TEXT_SINGLE;
+extern const std::string STR_LIST_SINGLE;
+extern const std::string STR_LIST_MULTI;
+extern const std::string STR_HIDDEN;
+extern const std::string STR_FORM_TYPE;
extern const std::string STR_FROM;
extern const std::string STR_TO;
@@ -114,7 +121,10 @@
extern const std::string STR_UNAVAILABLE;
-extern const std::string STR_MUC_LOOKUP_DOMAIN;
+extern const Jid JID_GOOGLE_MUC_LOOKUP;
+extern const std::string STR_MUC_ROOMCONFIG_ROOMNAME;
+extern const std::string STR_MUC_ROOMCONFIG_FEATURES;
+extern const std::string STR_MUC_ROOM_FEATURE_ENTERPRISE;
extern const QName QN_STREAM_STREAM;
extern const QName QN_STREAM_FEATURES;
@@ -345,6 +355,22 @@
extern const QName QN_DISCO_ITEMS_QUERY;
extern const QName QN_DISCO_ITEM;
+// JEP 0020
+extern const std::string NS_FEATURE;
+extern const QName QN_FEATURE_FEATURE;
+
+// JEP 0004
+extern const std::string NS_XDATA;
+extern const QName QN_XDATA_X;
+extern const QName QN_XDATA_INSTRUCTIONS;
+extern const QName QN_XDATA_TITLE;
+extern const QName QN_XDATA_FIELD;
+extern const QName QN_XDATA_REPORTED;
+extern const QName QN_XDATA_ITEM;
+extern const QName QN_XDATA_DESC;
+extern const QName QN_XDATA_REQUIRED;
+extern const QName QN_XDATA_VALUE;
+extern const QName QN_XDATA_OPTION;
// JEP 0045
extern const std::string NS_MUC;
@@ -371,7 +397,7 @@
extern const QName QN_SEARCH_ITEM;
extern const QName QN_SEARCH_ROOM_NAME;
extern const QName QN_SEARCH_ROOM_JID;
-extern const QName QN_SEARCH_ORGANIZERS_DOMAIN;
+extern const QName QN_SEARCH_ROOM_DOMAIN;
// JEP 0115
diff --git a/talk/xmpp/iqtask.cc b/talk/xmpp/iqtask.cc
index f319990..f54f630 100644
--- a/talk/xmpp/iqtask.cc
+++ b/talk/xmpp/iqtask.cc
@@ -73,13 +73,13 @@
if (success) {
HandleResult(stanza);
} else {
- SignalError(stanza->FirstNamed(QN_ERROR));
+ SignalError(this, stanza->FirstNamed(QN_ERROR));
}
return STATE_DONE;
}
int IqTask::OnTimeout() {
- SignalError(NULL);
+ SignalError(this, NULL);
return XmppTask::OnTimeout();
}
diff --git a/talk/xmpp/iqtask.h b/talk/xmpp/iqtask.h
index 7d9d621..589616b 100644
--- a/talk/xmpp/iqtask.h
+++ b/talk/xmpp/iqtask.h
@@ -42,7 +42,8 @@
buzz::XmlElement* el);
virtual ~IqTask() {}
- sigslot::signal1<const XmlElement*> SignalError;
+ sigslot::signal2<IqTask*,
+ const XmlElement*> SignalError;
protected:
virtual void HandleResult(const buzz::XmlElement* element) = 0;
diff --git a/talk/xmpp/mucroomconfigtask.cc b/talk/xmpp/mucroomconfigtask.cc
new file mode 100644
index 0000000..272bd44
--- /dev/null
+++ b/talk/xmpp/mucroomconfigtask.cc
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 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 <string>
+#include <vector>
+
+#include "talk/xmpp/mucroomconfigtask.h"
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+MucRoomConfigTask::MucRoomConfigTask(
+ XmppTaskParentInterface* parent,
+ const Jid& room_jid,
+ const std::string& room_name,
+ const std::vector<std::string>& room_features)
+ : IqTask(parent, STR_SET, room_jid,
+ MakeRequest(room_name, room_features)),
+ room_jid_(room_jid) {
+}
+
+XmlElement* MucRoomConfigTask::MakeRequest(
+ const std::string& room_name,
+ const std::vector<std::string>& room_features) {
+ buzz::XmlElement* owner_query = new
+ buzz::XmlElement(buzz::QN_MUC_OWNER_QUERY, true);
+
+ buzz::XmlElement* x_form = new buzz::XmlElement(buzz::QN_XDATA_X, true);
+ x_form->SetAttr(buzz::QN_TYPE, buzz::STR_FORM);
+
+ buzz::XmlElement* roomname_field =
+ new buzz::XmlElement(buzz::QN_XDATA_FIELD, false);
+ roomname_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_ROOMNAME);
+ roomname_field->SetAttr(buzz::QN_TYPE, buzz::STR_TEXT_SINGLE);
+
+ buzz::XmlElement* roomname_value =
+ new buzz::XmlElement(buzz::QN_XDATA_VALUE, false);
+ roomname_value->SetBodyText(room_name);
+
+ roomname_field->AddElement(roomname_value);
+ x_form->AddElement(roomname_field);
+
+ buzz::XmlElement* features_field =
+ new buzz::XmlElement(buzz::QN_XDATA_FIELD, false);
+ features_field->SetAttr(buzz::QN_VAR, buzz::STR_MUC_ROOMCONFIG_FEATURES);
+ features_field->SetAttr(buzz::QN_TYPE, buzz::STR_LIST_MULTI);
+
+ for (std::vector<std::string>::const_iterator feature = room_features.begin();
+ feature != room_features.end(); ++feature) {
+ buzz::XmlElement* features_value =
+ new buzz::XmlElement(buzz::QN_XDATA_VALUE, false);
+ features_value->SetBodyText(*feature);
+ features_field->AddElement(features_value);
+ }
+
+ x_form->AddElement(features_field);
+ owner_query->AddElement(x_form);
+ return owner_query;
+}
+
+void MucRoomConfigTask::HandleResult(const XmlElement* element) {
+ SignalResult(this);
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/mucroomconfigtask.h b/talk/xmpp/mucroomconfigtask.h
new file mode 100644
index 0000000..ba0dbaa
--- /dev/null
+++ b/talk/xmpp/mucroomconfigtask.h
@@ -0,0 +1,64 @@
+/*
+ * libjingle
+ * Copyright 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_XMPP_MUCROOMCONFIGTASK_H_
+#define TALK_XMPP_MUCROOMCONFIGTASK_H_
+
+#include <string>
+#include "talk/xmpp/iqtask.h"
+
+namespace buzz {
+
+// This task configures the muc room for document sharing and other enterprise
+// specific goodies.
+class MucRoomConfigTask : public IqTask {
+ public:
+ MucRoomConfigTask(XmppTaskParentInterface* parent,
+ const Jid& room_jid,
+ const std::string& room_name,
+ const std::vector<std::string>& room_features);
+
+ // Room configuration does not return any reasonable error
+ // values. The First config request configures the room, subseqent
+ // ones are just ignored by server and server returns empty
+ // response.
+ sigslot::signal1<MucRoomConfigTask*> SignalResult;
+
+ const Jid& room_jid() const { return room_jid_; }
+
+ protected:
+ virtual void HandleResult(const XmlElement* stanza);
+
+ private:
+ static XmlElement* MakeRequest(const std::string& room_name,
+ const std::vector<std::string>& room_features);
+ Jid room_jid_;
+};
+
+} // namespace buzz
+
+#endif // TALK_XMPP_MUCROOMCONFIGTASK_H_
diff --git a/talk/xmpp/mucroomlookuptask.cc b/talk/xmpp/mucroomlookuptask.cc
index e6a204a..278dc38 100644
--- a/talk/xmpp/mucroomlookuptask.cc
+++ b/talk/xmpp/mucroomlookuptask.cc
@@ -35,28 +35,30 @@
namespace buzz {
MucRoomLookupTask::MucRoomLookupTask(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
const std::string& room_name,
- const std::string& organizer_domain)
- : IqTask(parent, STR_SET, Jid(STR_MUC_LOOKUP_DOMAIN),
- MakeRoomQuery(room_name, organizer_domain)) {
+ const std::string& room_domain)
+ : IqTask(parent, STR_SET, lookup_server_jid,
+ MakeNameQuery(room_name, room_domain)) {
}
MucRoomLookupTask::MucRoomLookupTask(XmppTaskParentInterface* parent,
+ const Jid& lookup_server_jid,
const Jid& room_jid)
- : IqTask(parent, STR_SET, Jid(STR_MUC_LOOKUP_DOMAIN),
+ : IqTask(parent, STR_SET, lookup_server_jid,
MakeJidQuery(room_jid)) {
}
-XmlElement* MucRoomLookupTask::MakeRoomQuery(const std::string& room_name,
- const std::string& org_domain) {
- XmlElement* room_elem = new XmlElement(QN_SEARCH_ROOM_NAME, false);
- room_elem->SetBodyText(room_name);
+XmlElement* MucRoomLookupTask::MakeNameQuery(
+ const std::string& room_name, const std::string& room_domain) {
+ XmlElement* name_elem = new XmlElement(QN_SEARCH_ROOM_NAME, false);
+ name_elem->SetBodyText(room_name);
- XmlElement* domain_elem = new XmlElement(QN_SEARCH_ORGANIZERS_DOMAIN, false);
- domain_elem->SetBodyText(org_domain);
+ XmlElement* domain_elem = new XmlElement(QN_SEARCH_ROOM_DOMAIN, false);
+ domain_elem->SetBodyText(room_domain);
XmlElement* query = new XmlElement(QN_SEARCH_QUERY, true);
- query->AddElement(room_elem);
+ query->AddElement(name_elem);
query->AddElement(domain_elem);
return query;
}
@@ -72,37 +74,37 @@
void MucRoomLookupTask::HandleResult(const XmlElement* stanza) {
const XmlElement* query_elem = stanza->FirstNamed(QN_SEARCH_QUERY);
- if (query_elem != NULL) {
- const XmlElement* item_elem =
- query_elem->FirstNamed(QN_SEARCH_ITEM);
- if (item_elem != NULL && item_elem->HasAttr(QN_JID)) {
- MucRoomInfo room_info;
- if (GetRoomInfoFromResponse(item_elem, &room_info)) {
- SignalResult(room_info);
- return;
- }
- }
+ if (query_elem == NULL) {
+ SignalError(this, NULL);
+ return;
}
- SignalError(NULL);
-}
+ const XmlElement* item_elem = query_elem->FirstNamed(QN_SEARCH_ITEM);
+ if (item_elem == NULL) {
+ SignalError(this, NULL);
+ return;
+ }
-bool MucRoomLookupTask::GetRoomInfoFromResponse(
- const XmlElement* stanza, MucRoomInfo* info) {
+ MucRoomInfo room;
+ room.jid = Jid(item_elem->Attr(buzz::QN_JID));
+ if (!room.jid.IsValid()) {
+ SignalError(this, NULL);
+ return;
+ }
- info->room_jid = Jid(stanza->Attr(buzz::QN_JID));
- if (!info->room_jid.IsValid()) return false;
+ const XmlElement* room_name_elem =
+ item_elem->FirstNamed(QN_SEARCH_ROOM_NAME);
+ if (room_name_elem != NULL) {
+ room.name = room_name_elem->BodyText();
+ }
- const XmlElement* room_name_elem = stanza->FirstNamed(QN_SEARCH_ROOM_NAME);
- const XmlElement* org_domain_elem =
- stanza->FirstNamed(QN_SEARCH_ORGANIZERS_DOMAIN);
+ const XmlElement* room_domain_elem =
+ item_elem->FirstNamed(QN_SEARCH_ROOM_DOMAIN);
+ if (room_domain_elem != NULL) {
+ room.domain = room_domain_elem->BodyText();
+ }
- if (room_name_elem != NULL)
- info->room_name = room_name_elem->BodyText();
- if (org_domain_elem != NULL)
- info->organizer_domain = org_domain_elem->BodyText();
-
- return true;
+ SignalResult(this, room);
}
} // namespace buzz
diff --git a/talk/xmpp/mucroomlookuptask.h b/talk/xmpp/mucroomlookuptask.h
index e8e6c76..ec5873c 100644
--- a/talk/xmpp/mucroomlookuptask.h
+++ b/talk/xmpp/mucroomlookuptask.h
@@ -34,28 +34,35 @@
namespace buzz {
struct MucRoomInfo {
- Jid room_jid;
- std::string room_name;
- std::string organizer_domain;
+ Jid jid;
+ std::string name;
+ std::string domain;
+
+ std::string full_name() const {
+ return name + "@" + domain;
+ }
};
class MucRoomLookupTask : public IqTask {
public:
MucRoomLookupTask(XmppTaskParentInterface* parent,
+ const Jid& lookup_jid,
const std::string& room_name,
- const std::string& organizer_domain);
+ const std::string& room_domain);
MucRoomLookupTask(XmppTaskParentInterface* parent,
+ const Jid& lookup_jid,
const Jid& room_jid);
- sigslot::signal1<const MucRoomInfo&> SignalResult;
+ sigslot::signal2<MucRoomLookupTask*,
+ const MucRoomInfo&> SignalResult;
+
+ protected:
+ virtual void HandleResult(const XmlElement* element);
private:
- static XmlElement* MakeRoomQuery(const std::string& room_name,
- const std::string& org_domain);
+ static XmlElement* MakeNameQuery(const std::string& room_name,
+ const std::string& room_domain);
static XmlElement* MakeJidQuery(const Jid& room_jid);
- virtual void HandleResult(const XmlElement* element);
- static bool GetRoomInfoFromResponse(const XmlElement* stanza,
- MucRoomInfo* info);
};
} // namespace buzz