diff --git a/examples/BUILD.gn b/examples/BUILD.gn index 561c245257..e3d1079d83 100644 --- a/examples/BUILD.gn +++ b/examples/BUILD.gn @@ -29,6 +29,12 @@ group("examples") { ":AppRTCMobileTestStubbedVideoIO", ":libwebrtc_unity", ] + + # TODO(sakal): We include some code from the tests. Remove this dependency + # and remove this if-clause. + if (rtc_include_tests) { + deps += [ "androidnativeapi:androidnativeapi" ] + } } if (!build_with_chromium) { diff --git a/examples/androidnativeapi/AndroidManifest.xml b/examples/androidnativeapi/AndroidManifest.xml new file mode 100644 index 0000000000..19e4dc0274 --- /dev/null +++ b/examples/androidnativeapi/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/androidnativeapi/BUILD.gn b/examples/androidnativeapi/BUILD.gn new file mode 100644 index 0000000000..a98d63d089 --- /dev/null +++ b/examples/androidnativeapi/BUILD.gn @@ -0,0 +1,76 @@ +import("//webrtc.gni") + +rtc_android_apk("androidnativeapi") { + testonly = true + apk_name = "androidnativeapi" + android_manifest = "AndroidManifest.xml" + + java_files = [ + "java/org/webrtc/examples/androidnativeapi/MainActivity.java", + "java/org/webrtc/examples/androidnativeapi/CallClient.java", + ] + + deps = [ + ":resources", + "//sdk/android:libjingle_peerconnection_java", + ] + + shared_libraries = [ ":examples_androidnativeapi_jni" ] +} + +generate_jni("generated_jni") { + testonly = true + sources = [ + "java/org/webrtc/examples/androidnativeapi/CallClient.java", + ] + jni_package = "" + jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h" +} + +rtc_shared_library("examples_androidnativeapi_jni") { + testonly = true + sources = [ + "jni/androidcallclient.cc", + "jni/androidcallclient.h", + "jni/onload.cc", + ] + + suppressed_configs += [ "//build/config/android:hide_all_but_jni_onload" ] + configs += [ "//build/config/android:hide_all_but_jni" ] + + if (is_clang) { + # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163). + suppressed_configs += [ + "//build/config/clang:extra_warnings", + "//build/config/clang:find_bad_constructs", + ] + } + + deps = [ + ":generated_jni", + "//api:libjingle_peerconnection_api", + "//api/audio_codecs:builtin_audio_decoder_factory", + "//api/audio_codecs:builtin_audio_encoder_factory", + "//logging:rtc_event_log_impl_base", + "//media:rtc_audio_video", + "//media:rtc_internal_video_codecs", + "//modules/audio_processing", + "//modules/utility:utility", + "//pc:libjingle_peerconnection", + "//pc:pc_test_utils", + "//rtc_base:rtc_base", + "//rtc_base:rtc_base_approved", + "//sdk/android:native_api_base", + "//sdk/android:native_api_jni", + "//sdk/android:native_api_video", + "//system_wrappers:field_trial_default", + "//system_wrappers:metrics_default", + "//system_wrappers:runtime_enabled_features_default", + ] +} + +android_resources("resources") { + testonly = true + resource_dirs = [ "res" ] + custom_package = "org.webrtc.examples.androidnativeapi" +} diff --git a/examples/androidnativeapi/DEPS b/examples/androidnativeapi/DEPS new file mode 100644 index 0000000000..2d4c0d8bfb --- /dev/null +++ b/examples/androidnativeapi/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+modules/utility/include", + "+sdk/android/native_api", +] diff --git a/examples/androidnativeapi/java/org/webrtc/examples/androidnativeapi/CallClient.java b/examples/androidnativeapi/java/org/webrtc/examples/androidnativeapi/CallClient.java new file mode 100644 index 0000000000..5c18cb7c42 --- /dev/null +++ b/examples/androidnativeapi/java/org/webrtc/examples/androidnativeapi/CallClient.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 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.androidnativeapi; + +import android.os.Handler; +import android.os.HandlerThread; +import org.webrtc.NativeClassQualifiedName; +import org.webrtc.VideoSink; + +public class CallClient { + private static final String TAG = "CallClient"; + + private final HandlerThread thread; + private final Handler handler; + + private long nativeClient; + + public CallClient() { + thread = new HandlerThread(TAG + "Thread"); + thread.start(); + handler = new Handler(thread.getLooper()); + handler.post(() -> { nativeClient = nativeCreateClient(); }); + } + + public void call(VideoSink localSink, VideoSink remoteSink) { + handler.post(() -> { nativeCall(nativeClient, localSink, remoteSink); }); + } + + public void hangup() { + handler.post(() -> { nativeHangup(nativeClient); }); + } + + public void close() { + handler.post(() -> { + nativeDelete(nativeClient); + nativeClient = 0; + }); + thread.quitSafely(); + } + + private static native long nativeCreateClient(); + @NativeClassQualifiedName("webrtc_examples::AndroidCallClient") + private static native void nativeCall(long nativePtr, VideoSink localSink, VideoSink remoteSink); + @NativeClassQualifiedName("webrtc_examples::AndroidCallClient") + private static native void nativeHangup(long nativePtr); + @NativeClassQualifiedName("webrtc_examples::AndroidCallClient") + private static native void nativeDelete(long nativePtr); +} diff --git a/examples/androidnativeapi/java/org/webrtc/examples/androidnativeapi/MainActivity.java b/examples/androidnativeapi/java/org/webrtc/examples/androidnativeapi/MainActivity.java new file mode 100644 index 0000000000..cb68c55470 --- /dev/null +++ b/examples/androidnativeapi/java/org/webrtc/examples/androidnativeapi/MainActivity.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 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.androidnativeapi; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.Button; +import org.webrtc.ContextUtils; +import org.webrtc.EglBase; +import org.webrtc.GlRectDrawer; +import org.webrtc.SurfaceViewRenderer; + +public class MainActivity extends Activity { + private CallClient callClient; + private EglBase eglBase; + private SurfaceViewRenderer localRenderer; + private SurfaceViewRenderer remoteRenderer; + + @Override + protected void onCreate(Bundle savedInstance) { + ContextUtils.initialize(getApplicationContext()); + + super.onCreate(savedInstance); + setContentView(R.layout.activity_main); + + System.loadLibrary("examples_androidnativeapi_jni"); + callClient = new CallClient(); + + Button callButton = (Button) findViewById(R.id.call_button); + callButton.setOnClickListener((view) -> { callClient.call(localRenderer, remoteRenderer); }); + + Button hangupButton = (Button) findViewById(R.id.hangup_button); + hangupButton.setOnClickListener((view) -> { callClient.hangup(); }); + } + + @Override + protected void onStart() { + super.onStart(); + + eglBase = EglBase.create(null /* sharedContext */, EglBase.CONFIG_PLAIN); + localRenderer = (SurfaceViewRenderer) findViewById(R.id.local_renderer); + remoteRenderer = (SurfaceViewRenderer) findViewById(R.id.remote_renderer); + + localRenderer.init(eglBase.getEglBaseContext(), null /* rendererEvents */, EglBase.CONFIG_PLAIN, + new GlRectDrawer()); + remoteRenderer.init(eglBase.getEglBaseContext(), null /* rendererEvents */, + EglBase.CONFIG_PLAIN, new GlRectDrawer()); + } + + @Override + protected void onStop() { + callClient.hangup(); + + localRenderer.release(); + remoteRenderer.release(); + eglBase.release(); + + localRenderer = null; + remoteRenderer = null; + eglBase = null; + + super.onStop(); + } + + @Override + protected void onDestroy() { + callClient.close(); + callClient = null; + + super.onDestroy(); + } +} diff --git a/examples/androidnativeapi/jni/androidcallclient.cc b/examples/androidnativeapi/jni/androidcallclient.cc new file mode 100644 index 0000000000..657bce2df6 --- /dev/null +++ b/examples/androidnativeapi/jni/androidcallclient.cc @@ -0,0 +1,286 @@ +/* + * Copyright 2018 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/androidnativeapi/jni/androidcallclient.h" + +#include + +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/peerconnectioninterface.h" +#include "examples/androidnativeapi/generated_jni/jni/CallClient_jni.h" +#include "media/engine/internaldecoderfactory.h" +#include "media/engine/internalencoderfactory.h" +#include "media/engine/webrtcmediaengine.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "pc/test/fakeperiodicvideocapturer.h" +#include "rtc_base/ptr_util.h" +#include "sdk/android/native_api/jni/java_types.h" +#include "sdk/android/native_api/video/wrapper.h" + +namespace webrtc_examples { + +class AndroidCallClient::PCObserver : public webrtc::PeerConnectionObserver { + public: + explicit PCObserver(AndroidCallClient* client); + + void OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) override; + void OnDataChannel( + rtc::scoped_refptr data_channel) override; + void OnRenegotiationNeeded() override; + void OnIceConnectionChange( + webrtc::PeerConnectionInterface::IceConnectionState new_state) override; + void OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) override; + void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override; + + private: + const AndroidCallClient* client_; +}; + +namespace { + +class CreateOfferObserver : public webrtc::CreateSessionDescriptionObserver { + public: + explicit CreateOfferObserver( + rtc::scoped_refptr pc); + + void OnSuccess(webrtc::SessionDescriptionInterface* desc) override; + void OnFailure(const std::string& error) override; + + private: + const rtc::scoped_refptr pc_; +}; + +class SetRemoteSessionDescriptionObserver + : public webrtc::SetRemoteDescriptionObserverInterface { + public: + void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override; +}; + +class SetLocalSessionDescriptionObserver + : public webrtc::SetSessionDescriptionObserver { + public: + void OnSuccess() override; + void OnFailure(const std::string& error) override; +}; + +} // namespace + +AndroidCallClient::AndroidCallClient() + : call_started_(false), pc_observer_(rtc::MakeUnique(this)) { + thread_checker_.DetachFromThread(); + CreatePeerConnectionFactory(); +} + +void AndroidCallClient::Call(JNIEnv* env, + const webrtc::JavaRef& cls, + const webrtc::JavaRef& local_sink, + const webrtc::JavaRef& remote_sink) { + RTC_DCHECK_RUN_ON(&thread_checker_); + + rtc::CritScope lock(&pc_mutex_); + if (call_started_) { + RTC_LOG(LS_WARNING) << "Call already started."; + return; + } + call_started_ = true; + + local_sink_ = webrtc::JavaToNativeVideoSink(env, local_sink.obj()); + remote_sink_ = webrtc::JavaToNativeVideoSink(env, remote_sink.obj()); + + // The fake video source wants to be created on the same thread as it is + // destroyed. It is destroyed on the signaling thread so we have to invoke + // here. + // TODO(sakal): Get picture from camera? + video_source_ = pcf_->CreateVideoSource( + signaling_thread_ + ->Invoke>( + RTC_FROM_HERE, [&] { + return rtc::MakeUnique(); + })); + + CreatePeerConnection(); + Connect(); +} + +void AndroidCallClient::Hangup(JNIEnv* env, + const webrtc::JavaRef& cls) { + RTC_DCHECK_RUN_ON(&thread_checker_); + + call_started_ = false; + + { + rtc::CritScope lock(&pc_mutex_); + if (pc_ != nullptr) { + pc_->Close(); + pc_ = nullptr; + } + } + + local_sink_ = nullptr; + remote_sink_ = nullptr; + video_source_ = nullptr; +} + +void AndroidCallClient::Delete(JNIEnv* env, + const webrtc::JavaRef& cls) { + RTC_DCHECK_RUN_ON(&thread_checker_); + + delete this; +} + +void AndroidCallClient::CreatePeerConnectionFactory() { + network_thread_ = rtc::Thread::CreateWithSocketServer(); + network_thread_->SetName("network_thread", nullptr); + RTC_CHECK(network_thread_->Start()) << "Failed to start thread"; + + worker_thread_ = rtc::Thread::Create(); + worker_thread_->SetName("worker_thread", nullptr); + RTC_CHECK(worker_thread_->Start()) << "Failed to start thread"; + + signaling_thread_ = rtc::Thread::Create(); + signaling_thread_->SetName("signaling_thread", nullptr); + RTC_CHECK(signaling_thread_->Start()) << "Failed to start thread"; + + std::unique_ptr media_engine = + cricket::WebRtcMediaEngineFactory::Create( + nullptr /* adm */, webrtc::CreateBuiltinAudioEncoderFactory(), + webrtc::CreateBuiltinAudioDecoderFactory(), + rtc::MakeUnique(), + rtc::MakeUnique(), + nullptr /* audio_mixer */, webrtc::AudioProcessingBuilder().Create()); + RTC_LOG(LS_INFO) << "Media engine created: " << media_engine.get(); + + pcf_ = CreateModularPeerConnectionFactory( + network_thread_.get(), worker_thread_.get(), signaling_thread_.get(), + std::move(media_engine), webrtc::CreateCallFactory(), + webrtc::CreateRtcEventLogFactory()); + RTC_LOG(LS_INFO) << "PeerConnectionFactory created: " << pcf_; +} + +void AndroidCallClient::CreatePeerConnection() { + rtc::CritScope lock(&pc_mutex_); + webrtc::PeerConnectionInterface::RTCConfiguration config; + config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; + // DTLS SRTP has to be disabled for loopback to work. + config.enable_dtls_srtp = false; + pc_ = pcf_->CreatePeerConnection(config, nullptr /* port_allocator */, + nullptr /* cert_generator */, + pc_observer_.get()); + RTC_LOG(LS_INFO) << "PeerConnection created: " << pc_; + + rtc::scoped_refptr local_video_track = + pcf_->CreateVideoTrack("video", video_source_); + local_video_track->AddOrUpdateSink(local_sink_.get(), rtc::VideoSinkWants()); + pc_->AddTransceiver(local_video_track); + RTC_LOG(LS_INFO) << "Local video sink set up: " << local_video_track; + + for (const rtc::scoped_refptr& tranceiver : + pc_->GetTransceivers()) { + rtc::scoped_refptr track = + tranceiver->receiver()->track(); + if (track && + track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) { + static_cast(track.get()) + ->AddOrUpdateSink(remote_sink_.get(), rtc::VideoSinkWants()); + RTC_LOG(LS_INFO) << "Remote video sink set up: " << track; + break; + } + } +} + +void AndroidCallClient::Connect() { + rtc::CritScope lock(&pc_mutex_); + pc_->CreateOffer(new rtc::RefCountedObject(pc_), + webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); +} + +AndroidCallClient::PCObserver::PCObserver(AndroidCallClient* client) + : client_(client) {} + +void AndroidCallClient::PCObserver::OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) { + RTC_LOG(LS_INFO) << "OnSignalingChange: " << new_state; +} + +void AndroidCallClient::PCObserver::OnDataChannel( + rtc::scoped_refptr data_channel) { + RTC_LOG(LS_INFO) << "OnDataChannel"; +} + +void AndroidCallClient::PCObserver::OnRenegotiationNeeded() { + RTC_LOG(LS_INFO) << "OnRenegotiationNeeded"; +} + +void AndroidCallClient::PCObserver::OnIceConnectionChange( + webrtc::PeerConnectionInterface::IceConnectionState new_state) { + RTC_LOG(LS_INFO) << "OnIceConnectionChange: " << new_state; +} + +void AndroidCallClient::PCObserver::OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) { + RTC_LOG(LS_INFO) << "OnIceGatheringChange: " << new_state; +} + +void AndroidCallClient::PCObserver::OnIceCandidate( + const webrtc::IceCandidateInterface* candidate) { + RTC_LOG(LS_INFO) << "OnIceCandidate: " << candidate->server_url(); + rtc::CritScope lock(&client_->pc_mutex_); + RTC_DCHECK(client_->pc_ != nullptr); + client_->pc_->AddIceCandidate(candidate); +} + +CreateOfferObserver::CreateOfferObserver( + rtc::scoped_refptr pc) + : pc_(pc) {} + +void CreateOfferObserver::OnSuccess(webrtc::SessionDescriptionInterface* desc) { + std::string sdp; + desc->ToString(&sdp); + RTC_LOG(LS_INFO) << "Created offer: " << sdp; + + // Ownership of desc was transferred to us, now we transfer it forward. + pc_->SetLocalDescription( + new rtc::RefCountedObject(), desc); + + // Generate a fake answer. + std::unique_ptr answer( + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp)); + pc_->SetRemoteDescription( + std::move(answer), + new rtc::RefCountedObject()); +} + +void CreateOfferObserver::OnFailure(const std::string& error) { + RTC_LOG(LS_INFO) << "Failed to create offer: " << error; +} + +void SetRemoteSessionDescriptionObserver::OnSetRemoteDescriptionComplete( + webrtc::RTCError error) { + RTC_LOG(LS_INFO) << "Set remote description: " << error.message(); +} + +void SetLocalSessionDescriptionObserver::OnSuccess() { + RTC_LOG(LS_INFO) << "Set local description success!"; +} + +void SetLocalSessionDescriptionObserver::OnFailure(const std::string& error) { + RTC_LOG(LS_INFO) << "Set local description failure: " << error; +} + +} // namespace webrtc_examples + +static jlong JNI_CallClient_CreateClient( + JNIEnv* env, + const webrtc::JavaParamRef& cls) { + return webrtc::NativeToJavaPointer(new webrtc_examples::AndroidCallClient()); +} diff --git a/examples/androidnativeapi/jni/androidcallclient.h b/examples/androidnativeapi/jni/androidcallclient.h new file mode 100644 index 0000000000..2815b9d95f --- /dev/null +++ b/examples/androidnativeapi/jni/androidcallclient.h @@ -0,0 +1,73 @@ +/* + * Copyright 2018 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_ANDROIDNATIVEAPI_JNI_ANDROIDCALLCLIENT_H_ +#define EXAMPLES_ANDROIDNATIVEAPI_JNI_ANDROIDCALLCLIENT_H_ + +#include + +#include +#include + +#include "api/peerconnectioninterface.h" +#include "rtc_base/criticalsection.h" +#include "rtc_base/scoped_ref_ptr.h" +#include "rtc_base/thread_checker.h" +#include "sdk/android/native_api/jni/scoped_java_ref.h" + +namespace webrtc_examples { + +class AndroidCallClient { + public: + AndroidCallClient(); + + void Call(JNIEnv* env, + const webrtc::JavaRef& cls, + const webrtc::JavaRef& local_sink, + const webrtc::JavaRef& remote_sink); + void Hangup(JNIEnv* env, const webrtc::JavaRef& cls); + // A helper method for Java code to delete this object. Calls delete this. + void Delete(JNIEnv* env, const webrtc::JavaRef& cls); + + private: + class PCObserver; + + void CreatePeerConnectionFactory() RTC_RUN_ON(thread_checker_); + void CreatePeerConnection() RTC_RUN_ON(thread_checker_); + void Connect() RTC_RUN_ON(thread_checker_); + + rtc::ThreadChecker thread_checker_; + + bool call_started_ RTC_GUARDED_BY(thread_checker_); + + const std::unique_ptr pc_observer_; + + rtc::scoped_refptr pcf_ + RTC_GUARDED_BY(thread_checker_); + std::unique_ptr network_thread_ RTC_GUARDED_BY(thread_checker_); + std::unique_ptr worker_thread_ RTC_GUARDED_BY(thread_checker_); + std::unique_ptr signaling_thread_ + RTC_GUARDED_BY(thread_checker_); + + std::unique_ptr> local_sink_ + RTC_GUARDED_BY(thread_checker_); + std::unique_ptr> remote_sink_ + RTC_GUARDED_BY(thread_checker_); + rtc::scoped_refptr video_source_ + RTC_GUARDED_BY(thread_checker_); + + rtc::CriticalSection pc_mutex_; + rtc::scoped_refptr pc_ + RTC_GUARDED_BY(pc_mutex_); +}; + +} // namespace webrtc_examples + +#endif // EXAMPLES_ANDROIDNATIVEAPI_JNI_ANDROIDCALLCLIENT_H_ diff --git a/examples/androidnativeapi/jni/onload.cc b/examples/androidnativeapi/jni/onload.cc new file mode 100644 index 0000000000..4b4b5d960a --- /dev/null +++ b/examples/androidnativeapi/jni/onload.cc @@ -0,0 +1,30 @@ +/* + * Copyright 2018 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 + +#include "modules/utility/include/jvm_android.h" +#include "rtc_base/ssladapter.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); + webrtc::JVM::Initialize(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/androidnativeapi/res/layout/activity_main.xml b/examples/androidnativeapi/res/layout/activity_main.xml new file mode 100644 index 0000000000..ac8037320f --- /dev/null +++ b/examples/androidnativeapi/res/layout/activity_main.xml @@ -0,0 +1,52 @@ + + + + + + + + + + +