aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorJorge E. Moreira <jemoreira@google.com>2020-07-23 13:07:40 -0700
committerJorge E. Moreira <jemoreira@google.com>2020-07-23 13:08:51 -0700
commit206ccd0b36df69a0d0d0d26ddf7c4ead20202f91 (patch)
tree9ba6a46a7e4cd59e1018b94136f46578efe31f2e /examples
parent889b21a2e1486d6c9df1b458a356ce6423bcdf72 (diff)
parent708b22cd0384ba5ceafd153c47e28f541c215ea6 (diff)
downloadwebrtc-206ccd0b36df69a0d0d0d26ddf7c4ead20202f91.tar.gz
Merge remote tracking branch 'upstream-master'
Bug: 153469641 Test: run cuttlefish locally Change-Id: Ida3bfe62ef5c6549278f4c155a1f690b008e9b9d
Diffstat (limited to 'examples')
-rw-r--r--examples/BUILD.gn40
-rw-r--r--examples/aarproject/app/build.gradle2
-rw-r--r--examples/androidapp/AndroidManifest.xml2
-rw-r--r--examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java4
-rw-r--r--examples/androidnativeapi/AndroidManifest.xml2
-rw-r--r--examples/androidnativeapi/BUILD.gn4
-rw-r--r--examples/androidnativeapi/jni/android_call_client.cc12
-rw-r--r--examples/androidnativeapi/jni/android_call_client.h4
-rw-r--r--examples/androidtests/AndroidManifest.xml2
-rw-r--r--examples/androidvoip/AndroidManifest.xml38
-rw-r--r--examples/androidvoip/BUILD.gn88
-rw-r--r--examples/androidvoip/DEPS3
-rw-r--r--examples/androidvoip/OWNERS2
-rw-r--r--examples/androidvoip/java/org/webrtc/examples/androidvoip/MainActivity.java339
-rw-r--r--examples/androidvoip/java/org/webrtc/examples/androidvoip/OnVoipClientTaskCompleted.java26
-rw-r--r--examples/androidvoip/java/org/webrtc/examples/androidvoip/VoipClient.java188
-rw-r--r--examples/androidvoip/jni/android_voip_client.cc405
-rw-r--r--examples/androidvoip/jni/android_voip_client.h156
-rw-r--r--examples/androidvoip/jni/onload.cc28
-rw-r--r--examples/androidvoip/res/layout/activity_main.xml303
-rw-r--r--examples/androidvoip/res/values/colors.xml5
-rw-r--r--examples/androidvoip/res/values/strings.xml19
-rw-r--r--examples/objc/AppRTCMobile/ios/ARDMainViewController.m1
-rw-r--r--examples/objcnativeapi/objc/objc_call_client.h6
-rw-r--r--examples/objcnativeapi/objc/objc_call_client.mm10
25 files changed, 1652 insertions, 37 deletions
diff --git a/examples/BUILD.gn b/examples/BUILD.gn
index 4d6d14d0d9..f0c5fa8be1 100644
--- a/examples/BUILD.gn
+++ b/examples/BUILD.gn
@@ -27,6 +27,7 @@ group("examples") {
":AppRTCMobile",
":AppRTCMobile_test_apk",
":libwebrtc_unity",
+ "androidvoip",
]
# TODO(sakal): We include some code from the tests. Remove this dependency
@@ -87,7 +88,7 @@ if (is_android) {
testonly = true
apk_name = "AppRTCMobile"
android_manifest = "androidapp/AndroidManifest.xml"
- min_sdk_version = 16
+ min_sdk_version = 21
target_sdk_version = 29
deps = [
@@ -101,7 +102,7 @@ if (is_android) {
rtc_android_library("AppRTCMobile_javalib") {
testonly = true
- android_manifest_for_lint = "androidapp/AndroidManifest.xml"
+ android_manifest = "androidapp/AndroidManifest.xml"
sources = [
"androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java",
@@ -180,10 +181,10 @@ if (is_android) {
"androidapp/res/layout/fragment_call.xml",
"androidapp/res/layout/fragment_hud.xml",
"androidapp/res/menu/connect_menu.xml",
- "androidapp/res/values/arrays.xml",
- "androidapp/res/values/strings.xml",
"androidapp/res/values-v17/styles.xml",
"androidapp/res/values-v21/styles.xml",
+ "androidapp/res/values/arrays.xml",
+ "androidapp/res/values/strings.xml",
"androidapp/res/xml/preferences.xml",
]
custom_package = "org.appspot.apprtc"
@@ -196,7 +197,7 @@ if (is_android) {
rtc_instrumentation_test_apk("AppRTCMobile_test_apk") {
apk_name = "AppRTCMobileTest"
android_manifest = "androidtests/AndroidManifest.xml"
- min_sdk_version = 16
+ min_sdk_version = 21
target_sdk_version = 21
sources = [
@@ -207,7 +208,11 @@ if (is_android) {
deps = [
":AppRTCMobile_javalib",
+ "../sdk/android:base_java",
+ "../sdk/android:camera_java",
"../sdk/android:libjingle_peerconnection_java",
+ "../sdk/android:peerconnection_java",
+ "../sdk/android:video_api_java",
"../sdk/android:video_java",
"//third_party/android_support_test_runner:runner_java",
"//third_party/junit",
@@ -303,7 +308,7 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
} else {
deps += [ "../sdk:mac_framework_objc+link" ]
}
- libs = [
+ frameworks = [
"CoreMedia.framework",
"QuartzCore.framework",
]
@@ -345,7 +350,7 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
"../sdk:ios_framework_bundle",
]
- libs = [ "AVFoundation.framework" ]
+ frameworks = [ "AVFoundation.framework" ]
}
ios_app_bundle("AppRTCMobile") {
@@ -379,14 +384,18 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
if (rtc_apprtcmobile_broadcast_extension) {
bundle_data("AppRTCMobileBroadcastUpload_extension_bundle") {
testonly = true
- public_deps = [ ":AppRTCMobileBroadcastUpload" ] # no-presubmit-check TODO(webrtc:8603)
+ public_deps = [ # no-presubmit-check TODO(webrtc:8603)
+ ":AppRTCMobileBroadcastUpload", # prevent code format
+ ]
sources = [ "$root_out_dir/AppRTCMobileBroadcastUpload.appex" ]
outputs = [ "{{bundle_contents_dir}}/Plugins/{{source_file_part}}" ]
}
bundle_data("AppRTCMobileBroadcastSetupUI_extension_bundle") {
testonly = true
- public_deps = [ ":AppRTCMobileBroadcastSetupUI" ] # no-presubmit-check TODO(webrtc:8603)
+ public_deps = [ # no-presubmit-check TODO(webrtc:8603)
+ ":AppRTCMobileBroadcastSetupUI", # prevent code format
+ ]
sources = [ "$root_out_dir/AppRTCMobileBroadcastSetupUI.appex" ]
outputs = [ "{{bundle_contents_dir}}/Plugins/{{source_file_part}}" ]
}
@@ -404,7 +413,7 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
"../sdk:ios_framework_bundle",
]
- libs = [ "ReplayKit.framework" ]
+ frameworks = [ "ReplayKit.framework" ]
}
ios_appex_bundle("AppRTCMobileBroadcastUpload") {
@@ -428,7 +437,7 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
info_plist = "objc/AppRTCMobile/ios/broadcast_extension/BroadcastSetupUIInfo.plist"
- libs = [ "ReplayKit.framework" ]
+ frameworks = [ "ReplayKit.framework" ]
deps = [ ":AppRTCMobile_ios_bundle_data" ]
}
@@ -484,6 +493,7 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
"../modules/audio_processing:api",
"../pc:libjingle_peerconnection",
"../rtc_base",
+ "../rtc_base/synchronization:mutex",
"../sdk:base_objc",
"../sdk:default_codec_factory_objc",
"../sdk:helpers_objc",
@@ -542,7 +552,7 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
info_plist = "objc/AppRTCMobile/mac/Info.plist"
- libs = [ "AppKit.framework" ]
+ frameworks = [ "AppKit.framework" ]
ldflags = [
"-rpath",
@@ -587,10 +597,10 @@ if (is_ios || (is_mac && target_cpu != "x86")) {
configs += [ ":socketrocket_warning_config" ]
public_configs = [ ":socketrocket_include_config" ]
- libs = [
+ libs = [ "icucore" ]
+ frameworks = [
"CFNetwork.framework",
"Security.framework",
- "icucore",
]
}
@@ -829,6 +839,7 @@ if (is_android) {
"../sdk/android:camera_java",
"../sdk/android:libjingle_peerconnection_java",
"../sdk/android:peerconnection_java",
+ "../sdk/android:video_api_java",
"../sdk/android:video_java",
"//third_party/android_deps:com_android_support_support_annotations_java",
]
@@ -859,6 +870,7 @@ if (is_android) {
deps = [
":AppRTCMobile_javalib",
+ "../sdk/android:peerconnection_java",
"//base:base_java_test_support",
"//third_party/google-truth:google_truth_java",
]
diff --git a/examples/aarproject/app/build.gradle b/examples/aarproject/app/build.gradle
index dde0707ace..37499d468b 100644
--- a/examples/aarproject/app/build.gradle
+++ b/examples/aarproject/app/build.gradle
@@ -5,7 +5,7 @@ android {
buildToolsVersion "27.0.1"
defaultConfig {
applicationId "org.appspot.apprtc"
- minSdkVersion 16
+ minSdkVersion 21
targetSdkVersion 21
versionCode 1
versionName "1.0"
diff --git a/examples/androidapp/AndroidManifest.xml b/examples/androidapp/AndroidManifest.xml
index 8a9035e782..c4e1e797d0 100644
--- a/examples/androidapp/AndroidManifest.xml
+++ b/examples/androidapp/AndroidManifest.xml
@@ -8,7 +8,7 @@
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
- <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="29" />
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
diff --git a/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java b/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java
index 7ae3d838dd..c32ab964ad 100644
--- a/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java
+++ b/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java
@@ -185,8 +185,8 @@ public class AppRTCAudioManager {
// Note that, the sensor will not be active until start() has been called.
proximitySensor = AppRTCProximitySensor.create(context,
// This method will be called each time a state change is detected.
- // Example: user holds his hand over the device (closer than ~5 cm),
- // or removes his hand from the device.
+ // Example: user holds their hand over the device (closer than ~5 cm),
+ // or removes their hand from the device.
this ::onProximitySensorChangedState);
Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice);
diff --git a/examples/androidnativeapi/AndroidManifest.xml b/examples/androidnativeapi/AndroidManifest.xml
index f10f55a1b6..9257c4132e 100644
--- a/examples/androidnativeapi/AndroidManifest.xml
+++ b/examples/androidnativeapi/AndroidManifest.xml
@@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.webrtc.examples.androidnativeapi">
- <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="27" />
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
diff --git a/examples/androidnativeapi/BUILD.gn b/examples/androidnativeapi/BUILD.gn
index 9c114e859c..9253c0bcd9 100644
--- a/examples/androidnativeapi/BUILD.gn
+++ b/examples/androidnativeapi/BUILD.gn
@@ -5,7 +5,7 @@ if (is_android) {
testonly = true
apk_name = "androidnativeapi"
android_manifest = "AndroidManifest.xml"
- min_sdk_version = 19
+ min_sdk_version = 21
target_sdk_version = 27
sources = [
@@ -16,6 +16,7 @@ if (is_android) {
deps = [
":resources",
"//modules/audio_device:audio_device_java",
+ "//rtc_base:base_java",
"//sdk/android:camera_java",
"//sdk/android:surfaceviewrenderer_java",
"//sdk/android:video_api_java",
@@ -47,6 +48,7 @@ if (is_android) {
deps = [
":generated_jni",
"../../api:scoped_refptr",
+ "../../rtc_base/synchronization:mutex",
"//api:libjingle_peerconnection_api",
"//api/rtc_event_log:rtc_event_log_factory",
"//api/task_queue:default_task_queue_factory",
diff --git a/examples/androidnativeapi/jni/android_call_client.cc b/examples/androidnativeapi/jni/android_call_client.cc
index 03968335d9..f0b060632d 100644
--- a/examples/androidnativeapi/jni/android_call_client.cc
+++ b/examples/androidnativeapi/jni/android_call_client.cc
@@ -43,7 +43,7 @@ class AndroidCallClient::PCObserver : public webrtc::PeerConnectionObserver {
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
private:
- const AndroidCallClient* client_;
+ AndroidCallClient* const client_;
};
namespace {
@@ -88,7 +88,7 @@ void AndroidCallClient::Call(JNIEnv* env,
const webrtc::JavaRef<jobject>& remote_sink) {
RTC_DCHECK_RUN_ON(&thread_checker_);
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
if (call_started_) {
RTC_LOG(LS_WARNING) << "Call already started.";
return;
@@ -112,7 +112,7 @@ void AndroidCallClient::Hangup(JNIEnv* env) {
call_started_ = false;
{
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
if (pc_ != nullptr) {
pc_->Close();
pc_ = nullptr;
@@ -174,7 +174,7 @@ void AndroidCallClient::CreatePeerConnectionFactory() {
}
void AndroidCallClient::CreatePeerConnection() {
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
webrtc::PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
// DTLS SRTP has to be disabled for loopback to work.
@@ -205,7 +205,7 @@ void AndroidCallClient::CreatePeerConnection() {
}
void AndroidCallClient::Connect() {
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
pc_->CreateOffer(new rtc::RefCountedObject<CreateOfferObserver>(pc_),
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
}
@@ -240,7 +240,7 @@ void AndroidCallClient::PCObserver::OnIceGatheringChange(
void AndroidCallClient::PCObserver::OnIceCandidate(
const webrtc::IceCandidateInterface* candidate) {
RTC_LOG(LS_INFO) << "OnIceCandidate: " << candidate->server_url();
- rtc::CritScope lock(&client_->pc_mutex_);
+ webrtc::MutexLock lock(&client_->pc_mutex_);
RTC_DCHECK(client_->pc_ != nullptr);
client_->pc_->AddIceCandidate(candidate);
}
diff --git a/examples/androidnativeapi/jni/android_call_client.h b/examples/androidnativeapi/jni/android_call_client.h
index 13992f5960..f3f61a4695 100644
--- a/examples/androidnativeapi/jni/android_call_client.h
+++ b/examples/androidnativeapi/jni/android_call_client.h
@@ -18,7 +18,7 @@
#include "api/peer_connection_interface.h"
#include "api/scoped_refptr.h"
-#include "rtc_base/critical_section.h"
+#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_checker.h"
#include "sdk/android/native_api/jni/scoped_java_ref.h"
#include "sdk/android/native_api/video/video_source.h"
@@ -66,7 +66,7 @@ class AndroidCallClient {
rtc::scoped_refptr<webrtc::JavaVideoTrackSourceInterface> video_source_
RTC_GUARDED_BY(thread_checker_);
- rtc::CriticalSection pc_mutex_;
+ webrtc::Mutex pc_mutex_;
rtc::scoped_refptr<webrtc::PeerConnectionInterface> pc_
RTC_GUARDED_BY(pc_mutex_);
};
diff --git a/examples/androidtests/AndroidManifest.xml b/examples/androidtests/AndroidManifest.xml
index dae2e980a6..8e995366dc 100644
--- a/examples/androidtests/AndroidManifest.xml
+++ b/examples/androidtests/AndroidManifest.xml
@@ -14,7 +14,7 @@
package="org.appspot.apprtc.test">
<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
- <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/examples/androidvoip/AndroidManifest.xml b/examples/androidvoip/AndroidManifest.xml
new file mode 100644
index 0000000000..106f71171d
--- /dev/null
+++ b/examples/androidvoip/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright 2020 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.webrtc.examples.androidvoip">
+
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
+ <uses-feature android:name="android.hardware.microphone" android:required="true" />
+ <uses-feature android:name="android.hardware.telephony" android:required="false" />
+
+ <application
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:supportsRtl="true">
+ <activity android:name=".MainActivity"
+ android:windowSoftInputMode="stateHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/examples/androidvoip/BUILD.gn b/examples/androidvoip/BUILD.gn
new file mode 100644
index 0000000000..74341a78ac
--- /dev/null
+++ b/examples/androidvoip/BUILD.gn
@@ -0,0 +1,88 @@
+# Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+
+import("//webrtc.gni")
+
+if (is_android) {
+ rtc_android_apk("androidvoip") {
+ testonly = true
+ apk_name = "androidvoip"
+ android_manifest = "AndroidManifest.xml"
+ min_sdk_version = 21
+ target_sdk_version = 27
+
+ sources = [
+ "java/org/webrtc/examples/androidvoip/MainActivity.java",
+ "java/org/webrtc/examples/androidvoip/OnVoipClientTaskCompleted.java",
+ "java/org/webrtc/examples/androidvoip/VoipClient.java",
+ ]
+
+ deps = [
+ ":resources",
+ "//modules/audio_device:audio_device_java",
+ "//rtc_base:base_java",
+ "//sdk/android:java_audio_device_module_java",
+ "//sdk/android:video_java",
+ "//third_party/android_deps:androidx_core_core_java",
+ "//third_party/android_deps:androidx_legacy_legacy_support_v4_java",
+ ]
+
+ shared_libraries = [ ":examples_androidvoip_jni" ]
+ }
+
+ generate_jni("generated_jni") {
+ testonly = true
+ sources = [ "java/org/webrtc/examples/androidvoip/VoipClient.java" ]
+ namespace = "webrtc_examples"
+ jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h"
+ }
+
+ rtc_shared_library("examples_androidvoip_jni") {
+ testonly = true
+ sources = [
+ "jni/android_voip_client.cc",
+ "jni/android_voip_client.h",
+ "jni/onload.cc",
+ ]
+
+ suppressed_configs += [ "//build/config/android:hide_all_but_jni_onload" ]
+ configs += [ "//build/config/android:hide_all_but_jni" ]
+
+ deps = [
+ ":generated_jni",
+ "//api:transport_api",
+ "//api/audio_codecs:audio_codecs_api",
+ "//api/audio_codecs:builtin_audio_decoder_factory",
+ "//api/audio_codecs:builtin_audio_encoder_factory",
+ "//api/task_queue:default_task_queue_factory",
+ "//api/voip:voip_api",
+ "//api/voip:voip_engine_factory",
+ "//modules/utility:utility",
+ "//rtc_base",
+ "//rtc_base/third_party/sigslot:sigslot",
+ "//sdk/android:native_api_audio_device_module",
+ "//sdk/android:native_api_base",
+ "//sdk/android:native_api_jni",
+ "//third_party/abseil-cpp/absl/memory:memory",
+ ]
+ }
+
+ android_resources("resources") {
+ testonly = true
+ custom_package = "org.webrtc.examples.androidvoip"
+ sources = [
+ "res/layout/activity_main.xml",
+ "res/values/colors.xml",
+ "res/values/strings.xml",
+ ]
+
+ # Needed for Bazel converter.
+ resource_dirs = [ "res" ]
+ assert(resource_dirs != []) # Mark as used.
+ }
+}
diff --git a/examples/androidvoip/DEPS b/examples/androidvoip/DEPS
new file mode 100644
index 0000000000..edb714dd44
--- /dev/null
+++ b/examples/androidvoip/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+sdk/android/native_api",
+]
diff --git a/examples/androidvoip/OWNERS b/examples/androidvoip/OWNERS
new file mode 100644
index 0000000000..0fe5182450
--- /dev/null
+++ b/examples/androidvoip/OWNERS
@@ -0,0 +1,2 @@
+natim@webrtc.org
+sakal@webrtc.org
diff --git a/examples/androidvoip/java/org/webrtc/examples/androidvoip/MainActivity.java b/examples/androidvoip/java/org/webrtc/examples/androidvoip/MainActivity.java
new file mode 100644
index 0000000000..d787de59a0
--- /dev/null
+++ b/examples/androidvoip/java/org/webrtc/examples/androidvoip/MainActivity.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc.examples.androidvoip;
+
+import android.Manifest.permission;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+import android.widget.ScrollView;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ToggleButton;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.webrtc.ContextUtils;
+
+public class MainActivity extends Activity implements OnVoipClientTaskCompleted {
+ private static final int NUM_SUPPORTED_CODECS = 6;
+
+ private VoipClient voipClient;
+ private List<String> supportedCodecs;
+ private boolean[] isDecoderSelected;
+ private Set<Integer> selectedDecoders;
+
+ private Toast toast;
+ private ScrollView scrollView;
+ private TextView localIPAddressTextView;
+ private EditText localPortNumberEditText;
+ private EditText remoteIPAddressEditText;
+ private EditText remotePortNumberEditText;
+ private Spinner encoderSpinner;
+ private Button decoderSelectionButton;
+ private TextView decodersTextView;
+ private ToggleButton sessionButton;
+ private RelativeLayout switchLayout;
+ private Switch sendSwitch;
+ private Switch playoutSwitch;
+
+ @Override
+ protected void onCreate(Bundle savedInstance) {
+ ContextUtils.initialize(getApplicationContext());
+
+ super.onCreate(savedInstance);
+ setContentView(R.layout.activity_main);
+
+ System.loadLibrary("examples_androidvoip_jni");
+
+ voipClient = new VoipClient(getApplicationContext(), this);
+ voipClient.getAndSetUpLocalIPAddress();
+ voipClient.getAndSetUpSupportedCodecs();
+
+ isDecoderSelected = new boolean[NUM_SUPPORTED_CODECS];
+ selectedDecoders = new HashSet<>();
+
+ toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
+
+ scrollView = (ScrollView) findViewById(R.id.scroll_view);
+ localIPAddressTextView = (TextView) findViewById(R.id.local_ip_address_text_view);
+ localPortNumberEditText = (EditText) findViewById(R.id.local_port_number_edit_text);
+ remoteIPAddressEditText = (EditText) findViewById(R.id.remote_ip_address_edit_text);
+ remotePortNumberEditText = (EditText) findViewById(R.id.remote_port_number_edit_text);
+ encoderSpinner = (Spinner) findViewById(R.id.encoder_spinner);
+ decoderSelectionButton = (Button) findViewById(R.id.decoder_selection_button);
+ decodersTextView = (TextView) findViewById(R.id.decoders_text_view);
+ sessionButton = (ToggleButton) findViewById(R.id.session_button);
+ switchLayout = (RelativeLayout) findViewById(R.id.switch_layout);
+ sendSwitch = (Switch) findViewById(R.id.start_send_switch);
+ playoutSwitch = (Switch) findViewById(R.id.start_playout_switch);
+
+ setUpSessionButton();
+ setUpSendAndPlayoutSwitch();
+ }
+
+ private void setUpEncoderSpinner(List<String> supportedCodecs) {
+ ArrayAdapter<String> encoderAdapter =
+ new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, supportedCodecs);
+ encoderAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ encoderSpinner.setAdapter(encoderAdapter);
+ encoderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ voipClient.setEncoder((String) parent.getSelectedItem());
+ }
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {}
+ });
+ }
+
+ private List<String> getSelectedDecoders() {
+ List<String> decoders = new ArrayList<>();
+ for (int i = 0; i < supportedCodecs.size(); i++) {
+ if (selectedDecoders.contains(i)) {
+ decoders.add(supportedCodecs.get(i));
+ }
+ }
+ return decoders;
+ }
+
+ private void setUpDecoderSelectionButton(List<String> supportedCodecs) {
+ decoderSelectionButton.setOnClickListener((view) -> {
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
+ dialogBuilder.setTitle(R.string.dialog_title);
+
+ // Populate multi choice items with supported decoders.
+ String[] supportedCodecsArray = supportedCodecs.toArray(new String[0]);
+ dialogBuilder.setMultiChoiceItems(
+ supportedCodecsArray, isDecoderSelected, (dialog, position, isChecked) -> {
+ if (isChecked) {
+ selectedDecoders.add(position);
+ } else if (!isChecked) {
+ selectedDecoders.remove(position);
+ }
+ });
+
+ // "Ok" button.
+ dialogBuilder.setPositiveButton(R.string.ok_label, (dialog, position) -> {
+ List<String> decoders = getSelectedDecoders();
+ String result = decoders.stream().collect(Collectors.joining(", "));
+ if (result.isEmpty()) {
+ decodersTextView.setText(R.string.decoders_text_view_default);
+ } else {
+ decodersTextView.setText(result);
+ }
+ voipClient.setDecoders(decoders);
+ });
+
+ // "Dismiss" button.
+ dialogBuilder.setNegativeButton(
+ R.string.dismiss_label, (dialog, position) -> { dialog.dismiss(); });
+
+ // "Clear All" button.
+ dialogBuilder.setNeutralButton(R.string.clear_all_label, (dialog, position) -> {
+ Arrays.fill(isDecoderSelected, false);
+ selectedDecoders.clear();
+ decodersTextView.setText(R.string.decoders_text_view_default);
+ });
+
+ AlertDialog dialog = dialogBuilder.create();
+ dialog.show();
+ });
+ }
+
+ private void setUpSessionButton() {
+ sessionButton.setOnCheckedChangeListener((button, isChecked) -> {
+ // Ask for permission on RECORD_AUDIO if not granted.
+ if (ContextCompat.checkSelfPermission(this, permission.RECORD_AUDIO)
+ != PackageManager.PERMISSION_GRANTED) {
+ String[] sList = {permission.RECORD_AUDIO};
+ ActivityCompat.requestPermissions(this, sList, 1);
+ }
+
+ if (isChecked) {
+ // Order matters here, addresses have to be set before starting session
+ // before setting codec.
+ voipClient.setLocalAddress(localIPAddressTextView.getText().toString(),
+ Integer.parseInt(localPortNumberEditText.getText().toString()));
+ voipClient.setRemoteAddress(remoteIPAddressEditText.getText().toString(),
+ Integer.parseInt(remotePortNumberEditText.getText().toString()));
+ voipClient.startSession();
+ voipClient.setEncoder((String) encoderSpinner.getSelectedItem());
+ voipClient.setDecoders(getSelectedDecoders());
+ } else {
+ voipClient.stopSession();
+ }
+ });
+ }
+
+ private void setUpSendAndPlayoutSwitch() {
+ sendSwitch.setOnCheckedChangeListener((button, isChecked) -> {
+ if (isChecked) {
+ voipClient.startSend();
+ } else {
+ voipClient.stopSend();
+ }
+ });
+
+ playoutSwitch.setOnCheckedChangeListener((button, isChecked) -> {
+ if (isChecked) {
+ voipClient.startPlayout();
+ } else {
+ voipClient.stopPlayout();
+ }
+ });
+ }
+
+ private void setUpIPAddressEditTexts(String localIPAddress) {
+ if (localIPAddress.isEmpty()) {
+ showToast("Please check your network configuration");
+ } else {
+ localIPAddressTextView.setText(localIPAddress);
+ // By default remote IP address is the same as local IP address.
+ remoteIPAddressEditText.setText(localIPAddress);
+ }
+ }
+
+ private void showToast(String message) {
+ toast.cancel();
+ toast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
+ toast.setGravity(Gravity.TOP, 0, 200);
+ toast.show();
+ }
+
+ @Override
+ protected void onDestroy() {
+ voipClient.close();
+ voipClient = null;
+
+ super.onDestroy();
+ }
+
+ @Override
+ public void onGetLocalIPAddressCompleted(String localIPAddress) {
+ runOnUiThread(() -> { setUpIPAddressEditTexts(localIPAddress); });
+ }
+
+ @Override
+ public void onGetSupportedCodecsCompleted(List<String> supportedCodecs) {
+ runOnUiThread(() -> {
+ this.supportedCodecs = supportedCodecs;
+ setUpEncoderSpinner(supportedCodecs);
+ setUpDecoderSelectionButton(supportedCodecs);
+ });
+ }
+
+ @Override
+ public void onVoipClientInitializationCompleted(boolean isSuccessful) {
+ runOnUiThread(() -> {
+ if (!isSuccessful) {
+ showToast("Error initializing audio device");
+ }
+ });
+ }
+
+ @Override
+ public void onStartSessionCompleted(boolean isSuccessful) {
+ runOnUiThread(() -> {
+ if (isSuccessful) {
+ showToast("Session started");
+ switchLayout.setVisibility(View.VISIBLE);
+ scrollView.post(() -> { scrollView.fullScroll(ScrollView.FOCUS_DOWN); });
+ } else {
+ showToast("Failed to start session");
+ }
+ });
+ }
+
+ @Override
+ public void onStopSessionCompleted(boolean isSuccessful) {
+ runOnUiThread(() -> {
+ if (isSuccessful) {
+ showToast("Session stopped");
+ // Set listeners to null so the checked state can be changed programmatically.
+ sendSwitch.setOnCheckedChangeListener(null);
+ playoutSwitch.setOnCheckedChangeListener(null);
+ sendSwitch.setChecked(false);
+ playoutSwitch.setChecked(false);
+ // Redo the switch listener setup.
+ setUpSendAndPlayoutSwitch();
+ switchLayout.setVisibility(View.GONE);
+ } else {
+ showToast("Failed to stop session");
+ }
+ });
+ }
+
+ @Override
+ public void onStartSendCompleted(boolean isSuccessful) {
+ runOnUiThread(() -> {
+ if (isSuccessful) {
+ showToast("Started sending");
+ } else {
+ showToast("Error initializing microphone");
+ }
+ });
+ }
+
+ @Override
+ public void onStopSendCompleted(boolean isSuccessful) {
+ runOnUiThread(() -> {
+ if (isSuccessful) {
+ showToast("Stopped sending");
+ } else {
+ showToast("Microphone termination failed");
+ }
+ });
+ }
+
+ @Override
+ public void onStartPlayoutCompleted(boolean isSuccessful) {
+ runOnUiThread(() -> {
+ if (isSuccessful) {
+ showToast("Started playout");
+ } else {
+ showToast("Error initializing speaker");
+ }
+ });
+ }
+
+ @Override
+ public void onStopPlayoutCompleted(boolean isSuccessful) {
+ runOnUiThread(() -> {
+ if (isSuccessful) {
+ showToast("Stopped playout");
+ } else {
+ showToast("Speaker termination failed");
+ }
+ });
+ }
+
+ @Override
+ public void onUninitializedVoipClient() {
+ runOnUiThread(() -> { showToast("Voip client is uninitialized"); });
+ }
+}
diff --git a/examples/androidvoip/java/org/webrtc/examples/androidvoip/OnVoipClientTaskCompleted.java b/examples/androidvoip/java/org/webrtc/examples/androidvoip/OnVoipClientTaskCompleted.java
new file mode 100644
index 0000000000..bb85e048bb
--- /dev/null
+++ b/examples/androidvoip/java/org/webrtc/examples/androidvoip/OnVoipClientTaskCompleted.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc.examples.androidvoip;
+
+import java.util.List;
+
+public interface OnVoipClientTaskCompleted {
+ void onGetLocalIPAddressCompleted(String localIPAddress);
+ void onGetSupportedCodecsCompleted(List<String> supportedCodecs);
+ void onVoipClientInitializationCompleted(boolean isSuccessful);
+ void onStartSessionCompleted(boolean isSuccessful);
+ void onStopSessionCompleted(boolean isSuccessful);
+ void onStartSendCompleted(boolean isSuccessful);
+ void onStopSendCompleted(boolean isSuccessful);
+ void onStartPlayoutCompleted(boolean isSuccessful);
+ void onStopPlayoutCompleted(boolean isSuccessful);
+ void onUninitializedVoipClient();
+}
diff --git a/examples/androidvoip/java/org/webrtc/examples/androidvoip/VoipClient.java b/examples/androidvoip/java/org/webrtc/examples/androidvoip/VoipClient.java
new file mode 100644
index 0000000000..2dcbd99b1d
--- /dev/null
+++ b/examples/androidvoip/java/org/webrtc/examples/androidvoip/VoipClient.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc.examples.androidvoip;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import java.util.ArrayList;
+import java.util.List;
+
+public class VoipClient {
+ private static final String TAG = "VoipClient";
+
+ private final HandlerThread thread;
+ private final Handler handler;
+
+ private long nativeClient;
+ private OnVoipClientTaskCompleted listener;
+
+ public VoipClient(Context applicationContext, OnVoipClientTaskCompleted listener) {
+ this.listener = listener;
+ thread = new HandlerThread(TAG + "Thread");
+ thread.start();
+ handler = new Handler(thread.getLooper());
+
+ handler.post(() -> {
+ nativeClient = nativeCreateClient(applicationContext);
+ listener.onVoipClientInitializationCompleted(/* isSuccessful */ nativeClient != 0);
+ });
+ }
+
+ private boolean isInitialized() {
+ return nativeClient != 0;
+ }
+
+ public void getAndSetUpSupportedCodecs() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onGetSupportedCodecsCompleted(nativeGetSupportedCodecs(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void getAndSetUpLocalIPAddress() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onGetLocalIPAddressCompleted(nativeGetLocalIPAddress(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void setEncoder(String encoder) {
+ handler.post(() -> {
+ if (isInitialized()) {
+ nativeSetEncoder(nativeClient, encoder);
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void setDecoders(List<String> decoders) {
+ handler.post(() -> {
+ if (isInitialized()) {
+ nativeSetDecoders(nativeClient, decoders);
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void setLocalAddress(String ipAddress, int portNumber) {
+ handler.post(() -> {
+ if (isInitialized()) {
+ nativeSetLocalAddress(nativeClient, ipAddress, portNumber);
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void setRemoteAddress(String ipAddress, int portNumber) {
+ handler.post(() -> {
+ if (isInitialized()) {
+ nativeSetRemoteAddress(nativeClient, ipAddress, portNumber);
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void startSession() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onStartSessionCompleted(nativeStartSession(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void stopSession() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onStopSessionCompleted(nativeStopSession(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void startSend() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onStartSendCompleted(nativeStartSend(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void stopSend() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onStopSendCompleted(nativeStopSend(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void startPlayout() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onStartPlayoutCompleted(nativeStartPlayout(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void stopPlayout() {
+ handler.post(() -> {
+ if (isInitialized()) {
+ listener.onStopPlayoutCompleted(nativeStopPlayout(nativeClient));
+ } else {
+ listener.onUninitializedVoipClient();
+ }
+ });
+ }
+
+ public void close() {
+ handler.post(() -> {
+ nativeDelete(nativeClient);
+ nativeClient = 0;
+ });
+ thread.quitSafely();
+ }
+
+ private static native long nativeCreateClient(Context applicationContext);
+ private static native List<String> nativeGetSupportedCodecs(long nativeAndroidVoipClient);
+ private static native String nativeGetLocalIPAddress(long nativeAndroidVoipClient);
+ private static native void nativeSetEncoder(long nativeAndroidVoipClient, String encoder);
+ private static native void nativeSetDecoders(long nativeAndroidVoipClient, List<String> decoders);
+ private static native void nativeSetLocalAddress(
+ long nativeAndroidVoipClient, String ipAddress, int portNumber);
+ private static native void nativeSetRemoteAddress(
+ long nativeAndroidVoipClient, String ipAddress, int portNumber);
+ private static native boolean nativeStartSession(long nativeAndroidVoipClient);
+ private static native boolean nativeStopSession(long nativeAndroidVoipClient);
+ private static native boolean nativeStartSend(long nativeAndroidVoipClient);
+ private static native boolean nativeStopSend(long nativeAndroidVoipClient);
+ private static native boolean nativeStartPlayout(long nativeAndroidVoipClient);
+ private static native boolean nativeStopPlayout(long nativeAndroidVoipClient);
+ private static native void nativeDelete(long nativeAndroidVoipClient);
+}
diff --git a/examples/androidvoip/jni/android_voip_client.cc b/examples/androidvoip/jni/android_voip_client.cc
new file mode 100644
index 0000000000..13cadf2f3d
--- /dev/null
+++ b/examples/androidvoip/jni/android_voip_client.cc
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2020 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "examples/androidvoip/jni/android_voip_client.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "api/audio_codecs/builtin_audio_decoder_factory.h"
+#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "api/task_queue/default_task_queue_factory.h"
+#include "api/voip/voip_codec.h"
+#include "api/voip/voip_engine_factory.h"
+#include "api/voip/voip_network.h"
+#include "examples/androidvoip/generated_jni/VoipClient_jni.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/network.h"
+#include "rtc_base/socket_server.h"
+#include "sdk/android/native_api/audio_device_module/audio_device_android.h"
+#include "sdk/android/native_api/jni/java_types.h"
+
+namespace {
+
+// Connects a UDP socket to a public address and returns the local
+// address associated with it. Since it binds to the "any" address
+// internally, it returns the default local address on a multi-homed
+// endpoint. Implementation copied from
+// BasicNetworkManager::QueryDefaultLocalAddress.
+rtc::IPAddress QueryDefaultLocalAddress(int family) {
+ const char kPublicIPv4Host[] = "8.8.8.8";
+ const char kPublicIPv6Host[] = "2001:4860:4860::8888";
+ const int kPublicPort = 53;
+ std::unique_ptr<rtc::Thread> thread = rtc::Thread::CreateWithSocketServer();
+
+ RTC_DCHECK(thread->socketserver() != nullptr);
+ RTC_DCHECK(family == AF_INET || family == AF_INET6);
+
+ std::unique_ptr<rtc::AsyncSocket> socket(
+ thread->socketserver()->CreateAsyncSocket(family, SOCK_DGRAM));
+ if (!socket) {
+ RTC_LOG_ERR(LERROR) << "Socket creation failed";
+ return rtc::IPAddress();
+ }
+
+ auto host = family == AF_INET ? kPublicIPv4Host : kPublicIPv6Host;
+ if (socket->Connect(rtc::SocketAddress(host, kPublicPort)) < 0) {
+ if (socket->GetError() != ENETUNREACH &&
+ socket->GetError() != EHOSTUNREACH) {
+ RTC_LOG(LS_INFO) << "Connect failed with " << socket->GetError();
+ }
+ return rtc::IPAddress();
+ }
+ return socket->GetLocalAddress().ipaddr();
+}
+
+// Assigned payload type for supported built-in codecs. PCMU, PCMA,
+// and G722 have set payload types. Whereas opus, ISAC, and ILBC
+// have dynamic payload types.
+enum class PayloadType : int {
+ kPcmu = 0,
+ kPcma = 8,
+ kG722 = 9,
+ kOpus = 96,
+ kIsac = 97,
+ kIlbc = 98,
+};
+
+// Returns the payload type corresponding to codec_name. Only
+// supports the built-in codecs.
+int GetPayloadType(const std::string& codec_name) {
+ RTC_DCHECK(codec_name == "PCMU" || codec_name == "PCMA" ||
+ codec_name == "G722" || codec_name == "opus" ||
+ codec_name == "ISAC" || codec_name == "ILBC");
+
+ if (codec_name == "PCMU") {
+ return static_cast<int>(PayloadType::kPcmu);
+ } else if (codec_name == "PCMA") {
+ return static_cast<int>(PayloadType::kPcma);
+ } else if (codec_name == "G722") {
+ return static_cast<int>(PayloadType::kG722);
+ } else if (codec_name == "opus") {
+ return static_cast<int>(PayloadType::kOpus);
+ } else if (codec_name == "ISAC") {
+ return static_cast<int>(PayloadType::kIsac);
+ } else if (codec_name == "ILBC") {
+ return static_cast<int>(PayloadType::kIlbc);
+ }
+
+ RTC_NOTREACHED();
+ return -1;
+}
+
+} // namespace
+
+namespace webrtc_examples {
+
+AndroidVoipClient::AndroidVoipClient(
+ JNIEnv* env,
+ const webrtc::JavaParamRef<jobject>& application_context) {
+ voip_thread_ = rtc::Thread::CreateWithSocketServer();
+ voip_thread_->Start();
+
+ webrtc::VoipEngineConfig config;
+ config.encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory();
+ config.decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory();
+ config.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
+ config.audio_device_module =
+ webrtc::CreateJavaAudioDeviceModule(env, application_context.obj());
+ config.audio_processing = webrtc::AudioProcessingBuilder().Create();
+
+ supported_codecs_ = config.encoder_factory->GetSupportedEncoders();
+
+ // Due to consistent thread requirement on
+ // modules/audio_device/android/audio_device_template.h,
+ // code is invoked in the context of voip_thread_.
+ voip_thread_->Invoke<void>(RTC_FROM_HERE, [&] {
+ voip_engine_ = webrtc::CreateVoipEngine(std::move(config));
+ if (!voip_engine_) {
+ RTC_LOG(LS_ERROR) << "VoipEngine creation failed";
+ }
+ });
+}
+
+AndroidVoipClient::~AndroidVoipClient() {
+ voip_thread_->Stop();
+}
+
+AndroidVoipClient* AndroidVoipClient::Create(
+ JNIEnv* env,
+ const webrtc::JavaParamRef<jobject>& application_context) {
+ // Using `new` to access a non-public constructor.
+ auto voip_client =
+ absl::WrapUnique(new AndroidVoipClient(env, application_context));
+ if (!voip_client->voip_engine_) {
+ return nullptr;
+ }
+ return voip_client.release();
+}
+
+webrtc::ScopedJavaLocalRef<jobject> AndroidVoipClient::GetSupportedCodecs(
+ JNIEnv* env) {
+ std::vector<std::string> names;
+ for (const webrtc::AudioCodecSpec& spec : supported_codecs_) {
+ names.push_back(spec.format.name);
+ }
+ webrtc::ScopedJavaLocalRef<jstring> (*convert_function)(
+ JNIEnv*, const std::string&) = &webrtc::NativeToJavaString;
+ return NativeToJavaList(env, names, convert_function);
+}
+
+webrtc::ScopedJavaLocalRef<jstring> AndroidVoipClient::GetLocalIPAddress(
+ JNIEnv* env) {
+ rtc::IPAddress ipv4_address = QueryDefaultLocalAddress(AF_INET);
+ if (!ipv4_address.IsNil()) {
+ return webrtc::NativeToJavaString(env, ipv4_address.ToString());
+ }
+ rtc::IPAddress ipv6_address = QueryDefaultLocalAddress(AF_INET6);
+ if (!ipv6_address.IsNil()) {
+ return webrtc::NativeToJavaString(env, ipv6_address.ToString());
+ }
+ return webrtc::NativeToJavaString(env, "");
+}
+
+void AndroidVoipClient::SetEncoder(
+ JNIEnv* env,
+ const webrtc::JavaRef<jstring>& j_encoder_string) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return;
+ }
+ const std::string& chosen_encoder =
+ webrtc::JavaToNativeString(env, j_encoder_string);
+ for (const webrtc::AudioCodecSpec& encoder : supported_codecs_) {
+ if (encoder.format.name == chosen_encoder) {
+ voip_engine_->Codec().SetSendCodec(
+ *channel_, GetPayloadType(encoder.format.name), encoder.format);
+ break;
+ }
+ }
+}
+
+void AndroidVoipClient::SetDecoders(
+ JNIEnv* env,
+ const webrtc::JavaParamRef<jobject>& j_decoder_strings) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return;
+ }
+ std::vector<std::string> chosen_decoders =
+ webrtc::JavaListToNativeVector<std::string, jstring>(
+ env, j_decoder_strings, &webrtc::JavaToNativeString);
+ std::map<int, webrtc::SdpAudioFormat> decoder_specs;
+
+ for (const webrtc::AudioCodecSpec& decoder : supported_codecs_) {
+ if (std::find(chosen_decoders.begin(), chosen_decoders.end(),
+ decoder.format.name) != chosen_decoders.end()) {
+ decoder_specs.insert(
+ {GetPayloadType(decoder.format.name), decoder.format});
+ }
+ }
+
+ voip_engine_->Codec().SetReceiveCodecs(*channel_, decoder_specs);
+}
+
+void AndroidVoipClient::SetLocalAddress(
+ JNIEnv* env,
+ const webrtc::JavaRef<jstring>& j_ip_address_string,
+ jint j_port_number_int) {
+ const std::string& ip_address =
+ webrtc::JavaToNativeString(env, j_ip_address_string);
+ rtp_local_address_ = rtc::SocketAddress(ip_address, j_port_number_int);
+ rtcp_local_address_ = rtc::SocketAddress(ip_address, j_port_number_int + 1);
+}
+
+void AndroidVoipClient::SetRemoteAddress(
+ JNIEnv* env,
+ const webrtc::JavaRef<jstring>& j_ip_address_string,
+ jint j_port_number_int) {
+ const std::string& ip_address =
+ webrtc::JavaToNativeString(env, j_ip_address_string);
+ rtp_remote_address_ = rtc::SocketAddress(ip_address, j_port_number_int);
+ rtcp_remote_address_ = rtc::SocketAddress(ip_address, j_port_number_int + 1);
+}
+
+jboolean AndroidVoipClient::StartSession(JNIEnv* env) {
+ // Due to consistent thread requirement on
+ // modules/utility/source/process_thread_impl.cc,
+ // code is invoked in the context of voip_thread_.
+ channel_ = voip_thread_->Invoke<absl::optional<webrtc::ChannelId>>(
+ RTC_FROM_HERE,
+ [this] { return voip_engine_->Base().CreateChannel(this, 0); });
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel creation failed";
+ return false;
+ }
+
+ rtp_socket_.reset(rtc::AsyncUDPSocket::Create(voip_thread_->socketserver(),
+ rtp_local_address_));
+ if (!rtp_socket_) {
+ RTC_LOG_ERR(LERROR) << "Socket creation failed";
+ return false;
+ }
+ rtp_socket_->SignalReadPacket.connect(
+ this, &AndroidVoipClient::OnSignalReadRTPPacket);
+
+ rtcp_socket_.reset(rtc::AsyncUDPSocket::Create(voip_thread_->socketserver(),
+ rtcp_local_address_));
+ if (!rtcp_socket_) {
+ RTC_LOG_ERR(LERROR) << "Socket creation failed";
+ return false;
+ }
+ rtcp_socket_->SignalReadPacket.connect(
+ this, &AndroidVoipClient::OnSignalReadRTCPPacket);
+
+ return true;
+}
+
+jboolean AndroidVoipClient::StopSession(JNIEnv* env) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return false;
+ }
+ if (!StopSend(env) || !StopPlayout(env)) {
+ return false;
+ }
+
+ rtp_socket_->Close();
+ rtcp_socket_->Close();
+ // Due to consistent thread requirement on
+ // modules/utility/source/process_thread_impl.cc,
+ // code is invoked in the context of voip_thread_.
+ voip_thread_->Invoke<void>(RTC_FROM_HERE, [this] {
+ voip_engine_->Base().ReleaseChannel(*channel_);
+ });
+ channel_ = absl::nullopt;
+ return true;
+}
+
+jboolean AndroidVoipClient::StartSend(JNIEnv* env) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return false;
+ }
+ // Due to consistent thread requirement on
+ // modules/audio_device/android/opensles_recorder.cc,
+ // code is invoked in the context of voip_thread_.
+ return voip_thread_->Invoke<bool>(RTC_FROM_HERE, [this] {
+ return voip_engine_->Base().StartSend(*channel_);
+ });
+}
+
+jboolean AndroidVoipClient::StopSend(JNIEnv* env) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return false;
+ }
+ // Due to consistent thread requirement on
+ // modules/audio_device/android/opensles_recorder.cc,
+ // code is invoked in the context of voip_thread_.
+ return voip_thread_->Invoke<bool>(RTC_FROM_HERE, [this] {
+ return voip_engine_->Base().StopSend(*channel_);
+ });
+}
+
+jboolean AndroidVoipClient::StartPlayout(JNIEnv* env) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return false;
+ }
+ // Due to consistent thread requirement on
+ // modules/audio_device/android/opensles_player.cc,
+ // code is invoked in the context of voip_thread_.
+ return voip_thread_->Invoke<bool>(RTC_FROM_HERE, [this] {
+ return voip_engine_->Base().StartPlayout(*channel_);
+ });
+}
+
+jboolean AndroidVoipClient::StopPlayout(JNIEnv* env) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return false;
+ }
+ // Due to consistent thread requirement on
+ // modules/audio_device/android/opensles_player.cc,
+ // code is invoked in the context of voip_thread_.
+ return voip_thread_->Invoke<bool>(RTC_FROM_HERE, [this] {
+ return voip_engine_->Base().StopPlayout(*channel_);
+ });
+}
+
+void AndroidVoipClient::Delete(JNIEnv* env) {
+ delete this;
+}
+
+bool AndroidVoipClient::SendRtp(const uint8_t* packet,
+ size_t length,
+ const webrtc::PacketOptions& options) {
+ if (!rtp_socket_->SendTo(packet, length, rtp_remote_address_,
+ rtc::PacketOptions())) {
+ RTC_LOG(LS_ERROR) << "Failed to send RTP packet";
+ return false;
+ }
+ return true;
+}
+
+bool AndroidVoipClient::SendRtcp(const uint8_t* packet, size_t length) {
+ if (!rtcp_socket_->SendTo(packet, length, rtcp_remote_address_,
+ rtc::PacketOptions())) {
+ RTC_LOG(LS_ERROR) << "Failed to send RTCP packet";
+ return false;
+ }
+ return true;
+}
+
+void AndroidVoipClient::OnSignalReadRTPPacket(rtc::AsyncPacketSocket* socket,
+ const char* rtp_packet,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const int64_t& timestamp) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return;
+ }
+ voip_engine_->Network().ReceivedRTPPacket(
+ *channel_, rtc::ArrayView<const uint8_t>(
+ reinterpret_cast<const uint8_t*>(rtp_packet), size));
+}
+
+void AndroidVoipClient::OnSignalReadRTCPPacket(rtc::AsyncPacketSocket* socket,
+ const char* rtcp_packet,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const int64_t& timestamp) {
+ if (!channel_) {
+ RTC_LOG(LS_ERROR) << "Channel has not been created";
+ return;
+ }
+ voip_engine_->Network().ReceivedRTCPPacket(
+ *channel_, rtc::ArrayView<const uint8_t>(
+ reinterpret_cast<const uint8_t*>(rtcp_packet), size));
+}
+
+static jlong JNI_VoipClient_CreateClient(
+ JNIEnv* env,
+ const webrtc::JavaParamRef<jobject>& application_context) {
+ return webrtc::NativeToJavaPointer(
+ AndroidVoipClient::Create(env, application_context));
+}
+
+} // namespace webrtc_examples
diff --git a/examples/androidvoip/jni/android_voip_client.h b/examples/androidvoip/jni/android_voip_client.h
new file mode 100644
index 0000000000..aed652e281
--- /dev/null
+++ b/examples/androidvoip/jni/android_voip_client.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020 The WebRTC Project Authors. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef EXAMPLES_ANDROIDVOIP_JNI_ANDROID_VOIP_CLIENT_H_
+#define EXAMPLES_ANDROIDVOIP_JNI_ANDROID_VOIP_CLIENT_H_
+
+#include <jni.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "api/audio_codecs/audio_format.h"
+#include "api/call/transport.h"
+#include "api/voip/voip_base.h"
+#include "api/voip/voip_engine.h"
+#include "rtc_base/async_packet_socket.h"
+#include "rtc_base/async_udp_socket.h"
+#include "rtc_base/socket_address.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "rtc_base/thread.h"
+#include "sdk/android/native_api/jni/scoped_java_ref.h"
+
+namespace webrtc_examples {
+
+// AndroidVoipClient facilitates the use of the VoIP API defined in
+// api/voip/voip_engine.h. One instance of AndroidVoipClient should
+// suffice for most VoIP applications. AndroidVoipClient implements
+// webrtc::Transport to send RTP/RTCP packets to the remote endpoint.
+// It also creates methods (slots) for sockets to connect to in
+// order to receive RTP/RTCP packets. AndroidVoipClient does all
+// VoipBase related operations with rtc::Thread (voip_thread_), this
+// is to comply with consistent thread usage requirement with
+// ProcessThread used within VoipEngine. AndroidVoipClient is meant
+// to be used by Java through JNI.
+class AndroidVoipClient : public webrtc::Transport,
+ public sigslot::has_slots<> {
+ public:
+ // Returns a pointer to an AndroidVoipClient object. Clients should
+ // use this factory method to create AndroidVoipClient objects. The
+ // method will return a nullptr in case of initialization errors.
+ // It is the client's responsibility to delete the pointer when
+ // they are done with it (this class provides a Delete() method).
+ static AndroidVoipClient* Create(
+ JNIEnv* env,
+ const webrtc::JavaParamRef<jobject>& application_context);
+
+ ~AndroidVoipClient() override;
+
+ // Returns a Java List of Strings containing names of the built-in
+ // supported codecs.
+ webrtc::ScopedJavaLocalRef<jobject> GetSupportedCodecs(JNIEnv* env);
+
+ // Returns a Java String of the default local IPv4 address. If IPv4
+ // address is not found, returns the default local IPv6 address. If
+ // IPv6 address is not found, returns an empty string.
+ webrtc::ScopedJavaLocalRef<jstring> GetLocalIPAddress(JNIEnv* env);
+
+ // Sets the encoder used by the VoIP API.
+ void SetEncoder(JNIEnv* env,
+ const webrtc::JavaRef<jstring>& j_encoder_string);
+
+ // Sets the decoders used by the VoIP API.
+ void SetDecoders(JNIEnv* env,
+ const webrtc::JavaParamRef<jobject>& j_decoder_strings);
+
+ // Sets two local/remote addresses, one for RTP packets, and another for
+ // RTCP packets. The RTP address will have IP address j_ip_address_string
+ // and port number j_port_number_int, the RTCP address will have IP address
+ // j_ip_address_string and port number j_port_number_int+1.
+ void SetLocalAddress(JNIEnv* env,
+ const webrtc::JavaRef<jstring>& j_ip_address_string,
+ jint j_port_number_int);
+ void SetRemoteAddress(JNIEnv* env,
+ const webrtc::JavaRef<jstring>& j_ip_address_string,
+ jint j_port_number_int);
+
+ // Starts a VoIP session. The VoIP operations below can only be
+ // used after a session has already started. Returns true if session
+ // started successfully and false otherwise.
+ jboolean StartSession(JNIEnv* env);
+
+ // Stops the current session. Returns true if session stopped
+ // successfully and false otherwise.
+ jboolean StopSession(JNIEnv* env);
+
+ // Starts sending RTP/RTCP packets to the remote endpoint. Returns
+ // the return value of StartSend in api/voip/voip_base.h.
+ jboolean StartSend(JNIEnv* env);
+
+ // Stops sending RTP/RTCP packets to the remote endpoint. Returns
+ // the return value of StopSend in api/voip/voip_base.h.
+ jboolean StopSend(JNIEnv* env);
+
+ // Starts playing out the voice data received from the remote endpoint.
+ // Returns the return value of StartPlayout in api/voip/voip_base.h.
+ jboolean StartPlayout(JNIEnv* env);
+
+ // Stops playing out the voice data received from the remote endpoint.
+ // Returns the return value of StopPlayout in api/voip/voip_base.h.
+ jboolean StopPlayout(JNIEnv* env);
+
+ // Deletes this object. Used by client when they are done.
+ void Delete(JNIEnv* env);
+
+ // Implementation for Transport.
+ bool SendRtp(const uint8_t* packet,
+ size_t length,
+ const webrtc::PacketOptions& options) override;
+ bool SendRtcp(const uint8_t* packet, size_t length) override;
+
+ // Slots for sockets to connect to.
+ void OnSignalReadRTPPacket(rtc::AsyncPacketSocket* socket,
+ const char* rtp_packet,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const int64_t& timestamp);
+ void OnSignalReadRTCPPacket(rtc::AsyncPacketSocket* socket,
+ const char* rtcp_packet,
+ size_t size,
+ const rtc::SocketAddress& addr,
+ const int64_t& timestamp);
+
+ private:
+ AndroidVoipClient(JNIEnv* env,
+ const webrtc::JavaParamRef<jobject>& application_context);
+
+ // Used to invoke VoipBase operations and send/receive
+ // RTP/RTCP packets.
+ std::unique_ptr<rtc::Thread> voip_thread_;
+ // A list of AudioCodecSpec supported by the built-in
+ // encoder/decoder factories.
+ std::vector<webrtc::AudioCodecSpec> supported_codecs_;
+ // The entry point to all VoIP APIs.
+ std::unique_ptr<webrtc::VoipEngine> voip_engine_;
+ // Used by the VoIP API to facilitate a VoIP session.
+ absl::optional<webrtc::ChannelId> channel_;
+ // Members below are used for network related operations.
+ std::unique_ptr<rtc::AsyncUDPSocket> rtp_socket_;
+ std::unique_ptr<rtc::AsyncUDPSocket> rtcp_socket_;
+ rtc::SocketAddress rtp_local_address_;
+ rtc::SocketAddress rtcp_local_address_;
+ rtc::SocketAddress rtp_remote_address_;
+ rtc::SocketAddress rtcp_remote_address_;
+};
+
+} // namespace webrtc_examples
+
+#endif // EXAMPLES_ANDROIDVOIP_JNI_ANDROID_VOIP_CLIENT_H_
diff --git a/examples/androidvoip/jni/onload.cc b/examples/androidvoip/jni/onload.cc
new file mode 100644
index 0000000000..b952de348b
--- /dev/null
+++ b/examples/androidvoip/jni/onload.cc
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <jni.h>
+
+#include "rtc_base/ssl_adapter.h"
+#include "sdk/android/native_api/base/init.h"
+
+namespace webrtc_examples {
+
+extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM* jvm, void* reserved) {
+ webrtc::InitAndroid(jvm);
+ RTC_CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()";
+ return JNI_VERSION_1_6;
+}
+
+extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM* jvm, void* reserved) {
+ RTC_CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()";
+}
+
+} // namespace webrtc_examples
diff --git a/examples/androidvoip/res/layout/activity_main.xml b/examples/androidvoip/res/layout/activity_main.xml
new file mode 100644
index 0000000000..c7fa5a9b31
--- /dev/null
+++ b/examples/androidvoip/res/layout/activity_main.xml
@@ -0,0 +1,303 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ tools:context="org.webrtc.examples.androidvoip.MainActivity">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginTop="15dp"
+ android:text="@string/local_endpoint_text_view"
+ android:textSize="19dp"
+ android:textStyle="bold"
+ android:textColor="@color/almost_black" />
+
+ <!--Local IP Adress-->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginRight="15dp"
+ android:text="@string/ip_address_text_view"
+ android:textSize="16dp" />
+
+ <TextView
+ android:id="@+id/local_ip_address_text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:layout_marginRight="15dp"
+ android:textSize="16dp" />
+
+ </LinearLayout>
+
+ <!--Local Port Number-->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginRight="15dp"
+ android:text="@string/port_number_text_view"
+ android:textSize="16dp" />
+
+ <EditText
+ android:id="@+id/local_port_number_edit_text"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginRight="15dp"
+ android:layout_weight="1"
+ android:text="10000"
+ android:inputType="number"
+ android:textSize="16dp" />
+
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginTop="30dp"
+ android:text="@string/remote_endpoint_text_view"
+ android:textSize="19dp"
+ android:textStyle="bold"
+ android:textColor="@color/almost_black" />
+
+ <!--Remote IP Adress-->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginRight="15dp"
+ android:text="@string/ip_address_text_view"
+ android:textSize="16dp" />
+
+ <EditText
+ android:id="@+id/remote_ip_address_edit_text"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="15dp"
+ android:layout_weight="1"
+ android:inputType="number"
+ android:digits="0123456789."
+ android:textSize="16dp" />
+
+ </LinearLayout>
+
+ <!--Remote Port Number-->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginRight="15dp"
+ android:text="@string/port_number_text_view"
+ android:textSize="16dp" />
+
+ <EditText
+ android:id="@+id/remote_port_number_edit_text"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginRight="15dp"
+ android:layout_weight="1"
+ android:text="10000"
+ android:inputType="number"
+ android:textSize="16dp" />
+
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginTop="30dp"
+ android:text="@string/encoder_text_view"
+ android:textSize="19dp"
+ android:textStyle="bold"
+ android:textColor="@color/almost_black" />
+
+ <Spinner
+ android:id="@+id/encoder_spinner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginTop="10dp"/>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_marginTop="20dp"
+ android:layout_gravity="center_vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="15dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginRight="25dp"
+ android:text="@string/decoder_text_view"
+ android:textSize="19dp"
+ android:textStyle="bold"
+ android:textColor="@color/almost_black" />
+
+ <Button
+ android:id="@+id/decoder_selection_button"
+ android:text="@string/decoder_selection_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginRight="15dp"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+
+
+ <TextView
+ android:id="@+id/decoders_text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="15dp"
+ android:layout_marginBottom="30dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginRight="15dp"
+ android:text="@string/decoders_text_view_default"
+ android:textSize="16dp" />
+
+
+ <RelativeLayout
+ android:id="@+id/switch_layout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="15dp"
+ android:visibility="gone" >
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginLeft="15dp"
+ android:layout_marginRight="15dp"
+ android:layout_marginBottom="45dp"
+ android:background="@color/light_gray" />
+
+ <LinearLayout
+ android:id="@+id/start_send_switch_layout"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:layout_below="@id/divider" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="15dp"
+ android:gravity="left"
+ android:layout_weight="1"
+ android:text="@string/start_send_text_view"
+ android:textSize="16dp" />
+
+ <Switch
+ android:id="@+id/start_send_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginRight="15dp"
+ android:gravity="right"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:layout_below="@id/start_send_switch_layout">
+
+ <TextView
+ android:id="@+id/start_playout_text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="15dp"
+ android:gravity="left"
+ android:layout_weight="1"
+ android:text="@string/start_playout_text_view"
+ android:textSize="16dp" />
+
+ <Switch
+ android:id="@+id/start_playout_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginRight="15dp"
+ android:gravity="right"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical" >
+
+ <ToggleButton
+ android:id="@+id/session_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:textOff="@string/session_button_text_off"
+ android:textOn="@string/session_button_text_on"
+ style="?android:attr/buttonStyle" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/examples/androidvoip/res/values/colors.xml b/examples/androidvoip/res/values/colors.xml
new file mode 100644
index 0000000000..4dadaa9941
--- /dev/null
+++ b/examples/androidvoip/res/values/colors.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="almost_black">#484848</color>
+ <color name="light_gray">#D3D3D3</color>
+</resources> \ No newline at end of file
diff --git a/examples/androidvoip/res/values/strings.xml b/examples/androidvoip/res/values/strings.xml
new file mode 100644
index 0000000000..d519bfbbb6
--- /dev/null
+++ b/examples/androidvoip/res/values/strings.xml
@@ -0,0 +1,19 @@
+<resources>
+ <string name="app_name">androidvoip</string>
+ <string name="local_endpoint_text_view">Local Endpoint</string>
+ <string name="remote_endpoint_text_view">Remote Endpoint</string>
+ <string name="ip_address_text_view">IP Address:</string>
+ <string name="port_number_text_view">Port Number:</string>
+ <string name="encoder_text_view">Select Encoder</string>
+ <string name="decoder_text_view">Select Decoder</string>
+ <string name="decoder_selection_button">Configure Selection</string>
+ <string name="decoders_text_view_default">No decoders selected</string>
+ <string name="dialog_title">Choose Decoders</string>
+ <string name="ok_label">Ok</string>
+ <string name="dismiss_label">Dismiss</string>
+ <string name="clear_all_label">Clear All</string>
+ <string name="start_send_text_view">Start Sending</string>
+ <string name="start_playout_text_view">Start Playout</string>
+ <string name="session_button_text_off">Start Session</string>
+ <string name="session_button_text_on">Stop Session</string>
+</resources>
diff --git a/examples/objc/AppRTCMobile/ios/ARDMainViewController.m b/examples/objc/AppRTCMobile/ios/ARDMainViewController.m
index dea7742a1b..a3ede07f3b 100644
--- a/examples/objc/AppRTCMobile/ios/ARDMainViewController.m
+++ b/examples/objc/AppRTCMobile/ios/ARDMainViewController.m
@@ -134,6 +134,7 @@ static NSString *const loopbackLaunchProcessArgument = @"loopback";
delegate:self];
videoCallViewController.modalTransitionStyle =
UIModalTransitionStyleCrossDissolve;
+ videoCallViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:videoCallViewController
animated:YES
completion:nil];
diff --git a/examples/objcnativeapi/objc/objc_call_client.h b/examples/objcnativeapi/objc/objc_call_client.h
index 90ac20ac01..b952402bc0 100644
--- a/examples/objcnativeapi/objc/objc_call_client.h
+++ b/examples/objcnativeapi/objc/objc_call_client.h
@@ -18,7 +18,7 @@
#include "api/peer_connection_interface.h"
#include "api/scoped_refptr.h"
-#include "rtc_base/critical_section.h"
+#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_checker.h"
@class RTC_OBJC_TYPE(RTCVideoCapturer);
@@ -50,7 +50,7 @@ class ObjCCallClient {
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
private:
- const ObjCCallClient* client_;
+ ObjCCallClient* const client_;
};
void CreatePeerConnectionFactory() RTC_RUN_ON(thread_checker_);
@@ -73,7 +73,7 @@ class ObjCCallClient {
rtc::scoped_refptr<webrtc::VideoTrackSourceInterface> video_source_
RTC_GUARDED_BY(thread_checker_);
- rtc::CriticalSection pc_mutex_;
+ webrtc::Mutex pc_mutex_;
rtc::scoped_refptr<webrtc::PeerConnectionInterface> pc_ RTC_GUARDED_BY(pc_mutex_);
};
diff --git a/examples/objcnativeapi/objc/objc_call_client.mm b/examples/objcnativeapi/objc/objc_call_client.mm
index 52ee2b5f95..5ce7eb7804 100644
--- a/examples/objcnativeapi/objc/objc_call_client.mm
+++ b/examples/objcnativeapi/objc/objc_call_client.mm
@@ -68,7 +68,7 @@ void ObjCCallClient::Call(RTC_OBJC_TYPE(RTCVideoCapturer) * capturer,
id<RTC_OBJC_TYPE(RTCVideoRenderer)> remote_renderer) {
RTC_DCHECK_RUN_ON(&thread_checker_);
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
if (call_started_) {
RTC_LOG(LS_WARNING) << "Call already started.";
return;
@@ -90,7 +90,7 @@ void ObjCCallClient::Hangup() {
call_started_ = false;
{
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
if (pc_ != nullptr) {
pc_->Close();
pc_ = nullptr;
@@ -138,7 +138,7 @@ void ObjCCallClient::CreatePeerConnectionFactory() {
}
void ObjCCallClient::CreatePeerConnection() {
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
webrtc::PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
// DTLS SRTP has to be disabled for loopback to work.
@@ -165,7 +165,7 @@ void ObjCCallClient::CreatePeerConnection() {
}
void ObjCCallClient::Connect() {
- rtc::CritScope lock(&pc_mutex_);
+ webrtc::MutexLock lock(&pc_mutex_);
pc_->CreateOffer(new rtc::RefCountedObject<CreateOfferObserver>(pc_),
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
}
@@ -198,7 +198,7 @@ void ObjCCallClient::PCObserver::OnIceGatheringChange(
void ObjCCallClient::PCObserver::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
RTC_LOG(LS_INFO) << "OnIceCandidate: " << candidate->server_url();
- rtc::CritScope lock(&client_->pc_mutex_);
+ webrtc::MutexLock lock(&client_->pc_mutex_);
RTC_DCHECK(client_->pc_ != nullptr);
client_->pc_->AddIceCandidate(candidate);
}