diff --git a/examples/BUILD.gn b/examples/BUILD.gn
index 805a59b40b..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
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 supportedCodecs;
+ private boolean[] isDecoderSelected;
+ private Set 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 supportedCodecs) {
+ ArrayAdapter encoderAdapter =
+ new ArrayAdapter(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 getSelectedDecoders() {
+ List 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 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 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 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 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 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 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 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
+#include
+#include
+#include