From a322dda55620b350bbb3249f0a69cebb68e6f075 Mon Sep 17 00:00:00 2001 From: Jim Gustafson Date: Wed, 3 Jul 2024 12:28:41 -0700 Subject: [PATCH] Add audio device module for android based on Oboe --- DEPS | 6 + ringrtc/oboe/.gitignore | 1 + ringrtc/oboe/BUILD.gn | 99 ++ sdk/android/BUILD.gn | 27 +- .../webrtc/audio/OboeAudioDeviceModule.java | 157 +++ .../audio_device/audio_device_module_oboe.cc | 976 ++++++++++++++++++ .../audio_device/audio_device_module_oboe.h | 24 + .../audio_device/oboe_audio_device_module.cc | 30 + 8 files changed, 1318 insertions(+), 2 deletions(-) create mode 100644 ringrtc/oboe/.gitignore create mode 100644 ringrtc/oboe/BUILD.gn create mode 100644 sdk/android/api/org/webrtc/audio/OboeAudioDeviceModule.java create mode 100644 sdk/android/src/jni/audio_device/audio_device_module_oboe.cc create mode 100644 sdk/android/src/jni/audio_device/audio_device_module_oboe.h create mode 100644 sdk/android/src/jni/audio_device/oboe_audio_device_module.cc diff --git a/DEPS b/DEPS index e060b8930b..7c54c15ee9 100644 --- a/DEPS +++ b/DEPS @@ -57,6 +57,12 @@ deps = { 'src/ringrtc/opus/src': 'https://github.com/xiph/opus.git@0e30966b198ad28943799eaf5b3b08100b6f70c3', + # RingRTC change to support Oboe for audio on Android + 'src/ringrtc/oboe/src': { + 'url': 'https://github.com/google/oboe.git@03242e9ef9495e418e6ae83954d239dc9193ec5c', + 'condition': 'checkout_android', + }, + # TODO(kjellander): Move this to be Android-only. 'src/base': 'https://chromium.googlesource.com/chromium/src/base@2f20fae2cd5d41fc2dbc912fd462796419c72ce6', diff --git a/ringrtc/oboe/.gitignore b/ringrtc/oboe/.gitignore new file mode 100644 index 0000000000..dbc8690803 --- /dev/null +++ b/ringrtc/oboe/.gitignore @@ -0,0 +1 @@ +/src \ No newline at end of file diff --git a/ringrtc/oboe/BUILD.gn b/ringrtc/oboe/BUILD.gn new file mode 100644 index 0000000000..2abadb86d0 --- /dev/null +++ b/ringrtc/oboe/BUILD.gn @@ -0,0 +1,99 @@ +# +# Copyright 2024 Signal Messenger, LLC +# SPDX-License-Identifier: AGPL-3.0-only +# + +import("//webrtc.gni") + +rtc_library("oboe") { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") + + defines = [ + "OBOE_ENABLE_LOGGING=0", + ] + + cflags_cc = [ + # These came from oboe's CMakeLists.txt file. + "-std=c++17", + "-Wall", + "-Wextra-semi", + "-Wshadow", + "-Wshadow-field", + + # These were needed to get it building. + "-Wno-exit-time-destructors", + "-Wno-header-hygiene", + "-Wno-sign-compare", + ] + + include_dirs = [ + "src/include", + "src/src", + ] + + sources = [ + "src/src/aaudio/AAudioLoader.cpp", + "src/src/aaudio/AudioStreamAAudio.cpp", + "src/src/common/AdpfWrapper.cpp", + "src/src/common/AudioSourceCaller.cpp", + "src/src/common/AudioStream.cpp", + "src/src/common/AudioStreamBuilder.cpp", + "src/src/common/DataConversionFlowGraph.cpp", + "src/src/common/FilterAudioStream.cpp", + "src/src/common/FixedBlockAdapter.cpp", + "src/src/common/FixedBlockReader.cpp", + "src/src/common/FixedBlockWriter.cpp", + "src/src/common/LatencyTuner.cpp", + "src/src/common/OboeExtensions.cpp", + "src/src/common/SourceFloatCaller.cpp", + "src/src/common/SourceI16Caller.cpp", + "src/src/common/SourceI24Caller.cpp", + "src/src/common/SourceI32Caller.cpp", + "src/src/common/Utilities.cpp", + "src/src/common/QuirksManager.cpp", + "src/src/fifo/FifoBuffer.cpp", + "src/src/fifo/FifoController.cpp", + "src/src/fifo/FifoControllerBase.cpp", + "src/src/fifo/FifoControllerIndirect.cpp", + "src/src/flowgraph/FlowGraphNode.cpp", + "src/src/flowgraph/ChannelCountConverter.cpp", + "src/src/flowgraph/ClipToRange.cpp", + "src/src/flowgraph/Limiter.cpp", + "src/src/flowgraph/ManyToMultiConverter.cpp", + "src/src/flowgraph/MonoBlend.cpp", + "src/src/flowgraph/MonoToMultiConverter.cpp", + "src/src/flowgraph/MultiToManyConverter.cpp", + "src/src/flowgraph/MultiToMonoConverter.cpp", + "src/src/flowgraph/RampLinear.cpp", + "src/src/flowgraph/SampleRateConverter.cpp", + "src/src/flowgraph/SinkFloat.cpp", + "src/src/flowgraph/SinkI16.cpp", + "src/src/flowgraph/SinkI24.cpp", + "src/src/flowgraph/SinkI32.cpp", + "src/src/flowgraph/SinkI8_24.cpp", + "src/src/flowgraph/SourceFloat.cpp", + "src/src/flowgraph/SourceI16.cpp", + "src/src/flowgraph/SourceI24.cpp", + "src/src/flowgraph/SourceI32.cpp", + "src/src/flowgraph/SourceI8_24.cpp", + "src/src/flowgraph/resampler/IntegerRatio.cpp", + "src/src/flowgraph/resampler/LinearResampler.cpp", + "src/src/flowgraph/resampler/MultiChannelResampler.cpp", + "src/src/flowgraph/resampler/PolyphaseResampler.cpp", + "src/src/flowgraph/resampler/PolyphaseResamplerMono.cpp", + "src/src/flowgraph/resampler/PolyphaseResamplerStereo.cpp", + "src/src/flowgraph/resampler/SincResampler.cpp", + "src/src/flowgraph/resampler/SincResamplerStereo.cpp", + "src/src/opensles/AudioInputStreamOpenSLES.cpp", + "src/src/opensles/AudioOutputStreamOpenSLES.cpp", + "src/src/opensles/AudioStreamBuffered.cpp", + "src/src/opensles/AudioStreamOpenSLES.cpp", + "src/src/opensles/EngineOpenSLES.cpp", + "src/src/opensles/OpenSLESUtilities.cpp", + "src/src/opensles/OutputMixerOpenSLES.cpp", + "src/src/common/StabilizedCallback.cpp", + "src/src/common/Trace.cpp", + "src/src/common/Version.cpp", + ] +} diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn index 8c04811ac4..8230392850 100644 --- a/sdk/android/BUILD.gn +++ b/sdk/android/BUILD.gn @@ -425,6 +425,8 @@ if (is_android) { visibility = [ "*" ] sources = [ "api/org/webrtc/audio/JavaAudioDeviceModule.java", + # RingRTC change to support Oboe for audio on Android + "api/org/webrtc/audio/OboeAudioDeviceModule.java", "src/java/org/webrtc/audio/LowLatencyAudioBufferManager.java", "src/java/org/webrtc/audio/VolumeLogger.java", "src/java/org/webrtc/audio/WebRtcAudioEffects.java", @@ -818,7 +820,11 @@ if (current_os == "linux" || is_android) { # JNI target for java_audio_device_module_java rtc_library("java_audio_device_module_jni") { visibility = [ "*" ] - sources = [ "src/jni/audio_device/java_audio_device_module.cc" ] + sources = [ + "src/jni/audio_device/java_audio_device_module.cc", + # RingRTC change to support Oboe for audio on Android + "src/jni/audio_device/oboe_audio_device_module.cc", + ] deps = [ ":base_jni", @@ -1165,12 +1171,23 @@ if (current_os == "linux" || is_android) { rtc_library("audio_device_module_base") { visibility = [ "*" ] + # RingRTC change to support Oboe for audio on Android + include_dirs = [ + "../../ringrtc/oboe/src/include" + ] + sources = [ "src/jni/audio_device/audio_common.h", "src/jni/audio_device/audio_device_module.cc", "src/jni/audio_device/audio_device_module.h", + # RingRTC change to support Oboe for audio on Android + "src/jni/audio_device/audio_device_module_oboe.cc", + "src/jni/audio_device/audio_device_module_oboe.h", ] + # RingRTC change to support Oboe for audio on Android + ldflags = [ "-laaudio" ] + deps = [ ":base_jni", ":generated_audio_device_module_base_jni", @@ -1184,6 +1201,8 @@ if (current_os == "linux" || is_android) { "../../rtc_base:checks", "../../rtc_base:logging", "../../system_wrappers:metrics", + # RingRTC change to support Oboe for audio on Android + "//ringrtc/oboe:oboe", ] absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] } @@ -1435,7 +1454,11 @@ if (current_os == "linux" || is_android) { } generate_jni("generated_java_audio_jni") { - sources = [ "api/org/webrtc/audio/JavaAudioDeviceModule.java" ] + sources = [ + "api/org/webrtc/audio/JavaAudioDeviceModule.java", + # RingRTC change to support Oboe for audio on Android + "api/org/webrtc/audio/OboeAudioDeviceModule.java" + ] namespace = "webrtc::jni" jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h" } diff --git a/sdk/android/api/org/webrtc/audio/OboeAudioDeviceModule.java b/sdk/android/api/org/webrtc/audio/OboeAudioDeviceModule.java new file mode 100644 index 0000000000..ef54a42b41 --- /dev/null +++ b/sdk/android/api/org/webrtc/audio/OboeAudioDeviceModule.java @@ -0,0 +1,157 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.webrtc.audio; + +import org.webrtc.JniCommon; +import org.webrtc.Logging; + +/** + * AudioDeviceModule implemented using Oboe. + */ +public class OboeAudioDeviceModule implements AudioDeviceModule { + private static final String TAG = "OboeAudioDeviceModule"; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + // By default, hardware AEC and NS will be used. These are *usually* available to + // streams by configuration for VOICE_COMMUNICATION usage. The Client will force + // WebRTC's software AEC3 and NS on a case-by-case basis. + private boolean useSoftwareAcousticEchoCanceler; + private boolean useSoftwareNoiseSuppressor; + private boolean useExclusiveSharingMode; + private int audioSessionId; + + private Builder() { + Logging.w(TAG, "Builder()"); + this.useSoftwareAcousticEchoCanceler = false; + this.useSoftwareNoiseSuppressor = false; + this.useExclusiveSharingMode = true; + this.audioSessionId = -1; + } + + /** + * Control if the software noise suppressor should be used or not. + */ + public Builder setUseSoftwareNoiseSuppressor(boolean useSoftwareNoiseSuppressor) { + Logging.w(TAG, "setUseSoftwareNoiseSuppressor: " + useSoftwareNoiseSuppressor); + this.useSoftwareNoiseSuppressor = useSoftwareNoiseSuppressor; + return this; + } + + /** + * Control if the software acoustic echo canceler should be used or not. + */ + public Builder setUseSoftwareAcousticEchoCanceler(boolean useSoftwareAcousticEchoCanceler) { + Logging.w(TAG, "setUseSoftwareAcousticEchoCanceler: " + useSoftwareAcousticEchoCanceler); + this.useSoftwareAcousticEchoCanceler = useSoftwareAcousticEchoCanceler; + return this; + } + + /** + * Control if the streams should be opened with exclusive sharing mode enabled or not. The + * default is on, but if exclusive mode can't be obtained, then shared mode will be used. + */ + public Builder setExclusiveSharingMode(boolean useExclusiveSharingMode) { + Logging.w(TAG, "setExclusiveSharingMode: " + useExclusiveSharingMode); + this.useExclusiveSharingMode = useExclusiveSharingMode; + return this; + } + + /** + * Provide an audio session ID generated from application's AudioManager. + */ + public Builder setAudioSessionId(int audioSessionId) { + Logging.w(TAG, "setAudioSessionId: " + audioSessionId); + this.audioSessionId = audioSessionId; + return this; + } + + /** + * Construct an AudioDeviceModule based on the supplied arguments. The caller takes ownership + * and is responsible for calling release(). + */ + public OboeAudioDeviceModule createAudioDeviceModule() { + Logging.w(TAG, "createAudioDeviceModule"); + return new OboeAudioDeviceModule( + useSoftwareAcousticEchoCanceler, + useSoftwareNoiseSuppressor, + useExclusiveSharingMode, + audioSessionId); + } + } + + private boolean useSoftwareAcousticEchoCanceler; + private boolean useSoftwareNoiseSuppressor; + private boolean useExclusiveSharingMode; + private int audioSessionId; + + private final Object nativeLock = new Object(); + private long nativeAudioDeviceModule; + + private OboeAudioDeviceModule( + boolean useSoftwareAcousticEchoCanceler, + boolean useSoftwareNoiseSuppressor, + boolean useExclusiveSharingMode, + int audioSessionId) { + Logging.w(TAG, "OboeAudioDeviceModule"); + this.useSoftwareAcousticEchoCanceler = useSoftwareAcousticEchoCanceler; + this.useSoftwareNoiseSuppressor = useSoftwareNoiseSuppressor; + this.useExclusiveSharingMode = useExclusiveSharingMode; + this.audioSessionId = audioSessionId; + } + + @Override + public long getNativeAudioDeviceModulePointer() { + Logging.w(TAG, "getNativeAudioDeviceModulePointer"); + synchronized (nativeLock) { + if (nativeAudioDeviceModule == 0) { + Logging.w(TAG, "calling nativeCreateAudioDeviceModule"); + nativeAudioDeviceModule = nativeCreateAudioDeviceModule( + useSoftwareAcousticEchoCanceler, + useSoftwareNoiseSuppressor, + useExclusiveSharingMode, + audioSessionId); + } + return nativeAudioDeviceModule; + } + } + + @Override + public void release() { + Logging.w(TAG, "release"); + synchronized (nativeLock) { + if (nativeAudioDeviceModule != 0) { + JniCommon.nativeReleaseRef(nativeAudioDeviceModule); + nativeAudioDeviceModule = 0; + } + } + } + + @Override + public void setSpeakerMute(boolean mute) { + Logging.e(TAG, "Not supported; setSpeakerMute: " + mute); + } + + @Override + public void setMicrophoneMute(boolean mute) { + Logging.e(TAG, "Not supported; setMicrophoneMute: " + mute); + } + + @Override + public boolean setNoiseSuppressorEnabled(boolean enabled) { + Logging.e(TAG, "Not supported; setNoiseSuppressorEnabled: " + enabled); + return false; + } + + private static native long nativeCreateAudioDeviceModule( + boolean useSoftwareAcousticEchoCanceler, + boolean useSoftwareNoiseSuppressor, + boolean useExclusiveSharingMode, + int audioSessionId); +} diff --git a/sdk/android/src/jni/audio_device/audio_device_module_oboe.cc b/sdk/android/src/jni/audio_device/audio_device_module_oboe.cc new file mode 100644 index 0000000000..be6981e009 --- /dev/null +++ b/sdk/android/src/jni/audio_device/audio_device_module_oboe.cc @@ -0,0 +1,976 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include "sdk/android/src/jni/audio_device/audio_device_module.h" + +#include "api/make_ref_counted.h" +#include "api/sequence_checker.h" +#include "rtc_base/logging.h" + +#include + +#include +#include +#include + +// We expect Oboe to use our expected configuration: +// WebRTC is looking for signed 16-bit PCM data at 48000Hz. +constexpr oboe::AudioFormat kSampleFormat = oboe::AudioFormat::I16; +constexpr int32_t kSampleRate = 48000; +// WebRTC audio callbacks handle 10ms chunks, or 480 frames at 48000Hz per channel. +constexpr oboe::ChannelCount kChannelCount = oboe::ChannelCount::Mono; +constexpr size_t kMaxFramesPerCallback = (kSampleRate / 100); + +// When delay can't be obtained, use a fairly high latency delay value by default. +constexpr uint16_t kDefaultPlayoutDelayMs = 150; + +constexpr int kInvalidAudioSessionId = -1; + +// Limit logging values that get updated frequently. +constexpr int kLogEveryN = 100; + +namespace webrtc { +namespace jni { + +namespace { + +// Implements an Audio Device Manager using the Oboe C++ audio library (https://github.com/google/oboe). +class AndroidAudioDeviceModuleOboe : + public AudioDeviceModule, + public oboe::AudioStreamDataCallback, + public oboe::AudioStreamErrorCallback { + public: + AndroidAudioDeviceModuleOboe( + bool use_software_acoustic_echo_canceler, + bool use_software_noise_suppressor, + bool use_exclusive_sharing_mode, + int audio_session_id) + : use_software_acoustic_echo_canceler_(use_software_acoustic_echo_canceler), + use_software_noise_suppressor_(use_software_noise_suppressor), + use_exclusive_sharing_mode_(use_exclusive_sharing_mode), + audio_session_id_(audio_session_id) { + RTC_LOG(LS_WARNING) << "AndroidAudioDeviceModuleOboe::AndroidAudioDeviceModuleOboe"; + thread_checker_.Detach(); + } + + ~AndroidAudioDeviceModuleOboe() override { + RTC_LOG(LS_WARNING) << "AndroidAudioDeviceModuleOboe::~AndroidAudioDeviceModuleOboe"; + } + + // Retrieve the currently utilized audio layer + // There is only one audio layer as far as this implementation is concerned. Use + // kAndroidJavaAudio to make sure the default implementation isn't used. + int32_t ActiveAudioLayer(AudioDeviceModule::AudioLayer* audioLayer) const override { + RTC_LOG(LS_WARNING) << "ActiveAudioLayer always kAndroidJavaAudio"; + *audioLayer = kAndroidJavaAudio; + return 0; + } + + // Full-duplex transportation of PCM audio + // Invoked when the VoIP Engine is initialized. + int32_t RegisterAudioCallback(AudioTransport* audioCallback) override { + RTC_LOG(LS_WARNING) << __FUNCTION__; + if (is_playing_ || is_recording_) { + RTC_LOG(LS_ERROR) << "Failed to set audio transport since media was active"; + return -1; + } + + audio_callback_ = audioCallback; + return 0; + } + + // Main initialization and termination + + // Perform general initialization, when a call is going to be established, but + // before the user should start sending and receiving audio. + int32_t Init() override { + RTC_LOG(LS_WARNING) << "Init, using Oboe version: " << oboe::Version::Text; + RTC_DCHECK_RUN_ON(&thread_checker_); + + initialized_ = true; + + return 0; + } + + int32_t Terminate() override { + RTC_LOG(LS_WARNING) << __FUNCTION__; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return 0; + } + + if (input_stream_) { + input_stream_->stop(); + input_stream_->close(); + input_stream_ = nullptr; + } + if (output_stream_) { + output_stream_->stop(); + output_stream_->close(); + output_stream_ = nullptr; + } + + playout_initialized_ = false; + recording_initialized_ = false; + + is_playing_ = false; + is_recording_ = false; + + initialized_ = false; + thread_checker_.Detach(); + + return 0; + } + + bool Initialized() const override { + RTC_LOG(LS_WARNING) << "Initialized " << initialized_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_; + } + + // Device enumeration + + // This implementation only supports one playout device. + int16_t PlayoutDevices() override { + RTC_LOG(LS_WARNING) << "PlayoutDevices always 1"; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 1; + } + + // This implementation only supports one playout device. + int16_t RecordingDevices() override { + RTC_LOG(LS_WARNING) << "RecordingDevices always 1"; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 1; + } + + int32_t PlayoutDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) override { + RTC_LOG(LS_ERROR) << "PlayoutDeviceName (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t RecordingDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) override { + RTC_LOG(LS_ERROR) << "RecordingDeviceName (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + // Device selection + + int32_t SetPlayoutDevice(uint16_t index) override { + // OK to use but it has no effect currently since device selection is done + // using Android APIs instead. + RTC_LOG(LS_WARNING) << "SetPlayoutDevice " << index << ", (no effect!)"; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 0; + } + + int32_t SetPlayoutDevice(AudioDeviceModule::WindowsDeviceType device) override { + RTC_LOG(LS_ERROR) << "SetPlayoutDevice (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t SetRecordingDevice(uint16_t index) override { + // OK to use but it has no effect currently since device selection is done + // using Android APIs instead. + RTC_LOG(LS_WARNING) << "SetRecordingDevice " << index << ", (no effect!)"; + RTC_DCHECK_RUN_ON(&thread_checker_); + return 0; + } + + int32_t SetRecordingDevice(AudioDeviceModule::WindowsDeviceType device) override { + RTC_LOG(LS_ERROR) << "SetRecordingDevice (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + // Audio transport initialization + + int32_t PlayoutIsAvailable(bool* available) override { + RTC_LOG(LS_WARNING) << "PlayoutIsAvailable always true"; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = true; + return 0; + } + + int32_t InitPlayout() override { + RTC_LOG(LS_WARNING) << "InitPlayout"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + if (playout_initialized_) { + RTC_LOG(LS_WARNING) << "Playout is already initialized!"; + return 0; + } + + if (CreateOutputStream() != 0) { + return -1; + } + + playout_initialized_ = true; + + return 0; + } + + bool PlayoutIsInitialized() const override { + RTC_LOG(LS_WARNING) << "PlayoutIsInitialized " << playout_initialized_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return playout_initialized_; + } + + int32_t RecordingIsAvailable(bool* available) override { + RTC_LOG(LS_WARNING) << "RecordingIsAvailable always true"; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = true; + return 0; + } + + int32_t InitRecording() override { + RTC_LOG(LS_WARNING) << "InitRecording"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + if (recording_initialized_) { + RTC_LOG(LS_WARNING) << "Recording is already initialized!"; + return 0; + } + + if (CreateInputStream() != 0) { + return -1; + } + + recording_initialized_ = true; + + return 0; + } + + bool RecordingIsInitialized() const override { + RTC_LOG(LS_WARNING) << "RecordingIsInitialized " << recording_initialized_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return recording_initialized_; + } + + // Audio transport control + // Note: We generally choose non-blocking operations when starting/stopping streams. + // Note that the general is_playing_ and is_recording_ flags may not be in sync with + // the Oboe states, but they are good enough to use as intentions. + + int32_t StartPlayout() override { + RTC_LOG(LS_WARNING) << "StartPlayout"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + if (!playout_initialized_) { + RTC_LOG(LS_ERROR) << "Playout is not initialized!"; + return -1; + } + if (is_playing_) { + RTC_LOG(LS_WARNING) << "Playout is already started!"; + return 0; + } + + oboe::Result result = output_stream_->requestStart(); + if (result == oboe::Result::OK) { + is_playing_ = true; + return 0; + } else { + RTC_LOG(LS_ERROR) << "Failed to start the output stream: " << oboe::convertToText(result); + return -1; + } + } + + int32_t StopPlayout() override { + RTC_LOG(LS_WARNING) << "StopPlayout"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + if (!is_playing_) { + RTC_LOG(LS_WARNING) << "Playout is already stopped!"; + return 0; + } + + // Blocking call to stop the output stream. + oboe::Result result = output_stream_->stop(); + // In most error cases, playout is stopped, and we'll return -1, so clear the state flag. + is_playing_ = false; + if (result != oboe::Result::OK) { + RTC_LOG(LS_ERROR) << "Failed to stop the output stream: " << oboe::convertToText(result); + return -1; + } + + return 0; + } + + bool Playing() const override { + RTC_LOG(LS_WARNING) << "Playing " << is_playing_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return is_playing_; + } + + int32_t StartRecording() override { + RTC_LOG(LS_WARNING) << "StartRecording"; + RTC_DCHECK_RUN_ON(&thread_checker_); + + if (!initialized_) { + return -1; + } + if (!recording_initialized_) { + RTC_LOG(LS_ERROR) << "Recording is not initialized!"; + return -1; + } + if (is_recording_) { + RTC_LOG(LS_WARNING) << "Recording is already started!"; + return 0; + } + + oboe::Result result = input_stream_->requestStart(); + if (result == oboe::Result::OK) { + is_recording_ = true; + return 0; + } else { + RTC_LOG(LS_ERROR) << "Failed to start the input stream: " << oboe::convertToText(result); + return -1; + } + } + + int32_t StopRecording() override { + RTC_LOG(LS_WARNING) << "StopRecording"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + if (!is_recording_) { + RTC_LOG(LS_WARNING) << "Recording is already stopped!"; + return 0; + } + + // Blocking call to stop the output stream. + oboe::Result result = input_stream_->stop(); + // In most error cases, recording is stopped, and we'll return -1, so clear the state flag. + is_recording_ = false; + if (result != oboe::Result::OK) { + RTC_LOG(LS_ERROR) << "Failed to stop the input stream: " << oboe::convertToText(result); + return -1; + } + + return 0; + } + + bool Recording() const override { + RTC_LOG(LS_WARNING) << "Recording " << is_recording_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return is_recording_; + } + + // Audio mixer initialization + // Use the module initialization to indicate device readiness. + + int32_t InitSpeaker() override { + RTC_LOG(LS_WARNING) << "InitSpeaker " << (initialized_ ? 0 : -1); + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_ ? 0 : -1; + } + + bool SpeakerIsInitialized() const override { + RTC_LOG(LS_WARNING) << "SpeakerIsInitialized " << initialized_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_; + } + + int32_t InitMicrophone() override { + RTC_LOG(LS_WARNING) << "InitMicrophone " << (initialized_ ? 0 : -1); + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_ ? 0 : -1; + } + + bool MicrophoneIsInitialized() const override { + RTC_LOG(LS_WARNING) << "MicrophoneIsInitialized " << initialized_; + RTC_DCHECK_RUN_ON(&thread_checker_); + return initialized_; + } + + // Speaker volume controls + + int32_t SpeakerVolumeIsAvailable(bool* available) override { + RTC_LOG(LS_WARNING) << "SpeakerVolumeIsAvailable always false"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + *available = false; + return 0; + } + + int32_t SetSpeakerVolume(uint32_t volume) override { + RTC_LOG(LS_WARNING) << "SetSpeakerVolume always success"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + return 0; + } + + int32_t SpeakerVolume(uint32_t* output_volume) const override { + RTC_LOG(LS_WARNING) << "SpeakerVolume always 0"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + *output_volume = 0; + return 0; + } + + int32_t MaxSpeakerVolume(uint32_t* output_max_volume) const override { + RTC_LOG(LS_WARNING) << "MaxSpeakerVolume always 0"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + *output_max_volume = 0; + return 0; + } + + int32_t MinSpeakerVolume(uint32_t* output_min_volume) const override { + RTC_LOG(LS_WARNING) << "MinSpeakerVolume always 0"; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) { + return -1; + } + *output_min_volume = 0; + return 0; + } + + // Microphone volume controls + + int32_t MicrophoneVolumeIsAvailable(bool* available) override { + RTC_LOG(LS_WARNING) << "MicrophoneVolumeIsAvailable always false"; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = false; + return -1; + } + + int32_t SetMicrophoneVolume(uint32_t volume) override { + RTC_LOG(LS_ERROR) << "SetMicrophoneVolume (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t MicrophoneVolume(uint32_t* volume) const override { + RTC_LOG(LS_ERROR) << "MicrophoneVolume (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t MaxMicrophoneVolume(uint32_t* maxVolume) const override { + RTC_LOG(LS_ERROR) << "MaxMicrophoneVolume (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t MinMicrophoneVolume(uint32_t* minVolume) const override { + RTC_LOG(LS_ERROR) << "MinMicrophoneVolume (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + // Speaker mute control + + int32_t SpeakerMuteIsAvailable(bool* available) override { + RTC_LOG(LS_ERROR) << "SpeakerMuteIsAvailable (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t SetSpeakerMute(bool enable) override { + RTC_LOG(LS_ERROR) << "SetSpeakerMute (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t SpeakerMute(bool* enabled) const override { + RTC_LOG(LS_ERROR) << "SpeakerMute (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + // Microphone mute control + + int32_t MicrophoneMuteIsAvailable(bool* available) override { + RTC_LOG(LS_ERROR) << "MicrophoneMuteIsAvailable (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t SetMicrophoneMute(bool enable) override { + RTC_LOG(LS_ERROR) << "SetMicrophoneMute (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t MicrophoneMute(bool* enabled) const override { + RTC_LOG(LS_ERROR) << "MicrophoneMute (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + // Stereo support + + // None of our models support stereo playout for communication. Speech is always captured + // in mono and the playout device should up-mix to all applicable output emitters. + int32_t StereoPlayoutIsAvailable(bool* available) const override { + RTC_LOG(LS_WARNING) << "StereoPlayoutIsAvailable always false"; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = false; + return 0; + } + + // We don't expect stereo to be enabled, especially on-the-fly. + int32_t SetStereoPlayout(bool enable) override { + RTC_LOG(LS_WARNING) << "SetStereoPlayout " << enable; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (enable) { + return -1; + } + return 0; + } + + int32_t StereoPlayout(bool* enabled) const override { + RTC_LOG(LS_WARNING) << "StereoPlayout always false"; + RTC_DCHECK_RUN_ON(&thread_checker_); + *enabled = false; + return 0; + } + + // None of our models support stereo recording for communication. Speech is always + // captured in mono. + int32_t StereoRecordingIsAvailable(bool* available) const override { + RTC_LOG(LS_WARNING) << "StereoRecordingIsAvailable always false"; + RTC_DCHECK_RUN_ON(&thread_checker_); + *available = false; + return 0; + } + + // We don't expect stereo to be enabled, especially on-the-fly. + int32_t SetStereoRecording(bool enable) override { + RTC_LOG(LS_WARNING) << "SetStereoRecording " << enable; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (enable) { + return -1; + } + return 0; + } + + int32_t StereoRecording(bool* enabled) const override { + RTC_LOG(LS_WARNING) << "StereoRecording always false"; + RTC_DCHECK_RUN_ON(&thread_checker_); + *enabled = false; + return 0; + } + + // Playout delay calculation. + int32_t PlayoutDelay(uint16_t* delay_ms) const override { + RTC_DCHECK_RUN_ON(&thread_checker_); + + // Return the latest value set by the data callback. + *delay_ms = playout_delay_ms_; + + // Limit logging of the playout delay. + if (++delay_log_counter_ % kLogEveryN == 0) { + RTC_LOG(LS_WARNING) << "playout_delay_ms_: " << *delay_ms; + } + return 0; + } + + // Only supported on Android. + + bool BuiltInAECIsAvailable() const override { + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) + return false; + + RTC_LOG(LS_WARNING) << "BuiltInAECIsAvailable " << !use_software_acoustic_echo_canceler_; + return !use_software_acoustic_echo_canceler_; + } + + // Not implemented for any input device on Android. + bool BuiltInAGCIsAvailable() const override { + RTC_LOG(LS_WARNING) << "BuiltInAGCIsAvailable always false"; + RTC_DCHECK_RUN_ON(&thread_checker_); + return false; + } + + bool BuiltInNSIsAvailable() const override { + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) + return false; + + RTC_LOG(LS_WARNING) << "BuiltInNSIsAvailable " << !use_software_noise_suppressor_; + return !use_software_noise_suppressor_; + } + + // Enables the built-in audio effects. Only supported on Android. + + int32_t EnableBuiltInAEC(bool enable) override { + RTC_LOG(LS_WARNING) << "EnableBuiltInAEC " << enable; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) + return -1; + + // This is a noop for us. + return 0; + } + + int32_t EnableBuiltInAGC(bool enable) override { + RTC_LOG(LS_ERROR) << "EnableBuiltInAGC " << enable << ", (should not be reached)"; + RTC_DCHECK_NOTREACHED(); + return -1; + } + + int32_t EnableBuiltInNS(bool enable) override { + RTC_LOG(LS_WARNING) << "EnableBuiltInNS " << enable; + RTC_DCHECK_RUN_ON(&thread_checker_); + if (!initialized_) + return -1; + + // This is a noop for us. + return 0; + } + + // Playout underrun count. Only supported on Android. + int32_t GetPlayoutUnderrunCount() const override { + RTC_DCHECK_RUN_ON(&thread_checker_); + + // Return the latest value set by the data callback. + if (playout_underrun_count_ != 0) { + // Limit logging of the playout underrun count. + if (++underrun_log_counter_ % kLogEveryN == 0) { + RTC_LOG(LS_WARNING) << "playout_underrun_count_: " << playout_underrun_count_; + } + } + return playout_underrun_count_; + } + + // Used to generate RTC stats. + absl::optional GetStats() const override { + // Returning nullopt because stats are not supported in this implementation. + return absl::nullopt; + } + + // Oboe Callbacks + + bool onError(oboe::AudioStream *audioStream, oboe::Result error) override { + if (audioStream == output_stream_.get()) { + RTC_LOG(LS_WARNING) << "onError on output stream: " << oboe::convertToText(error); + if (error == oboe::Result::ErrorDisconnected) { + oboe::Result result = output_stream_->stop(); + if (result != oboe::Result::OK) { + RTC_LOG(LS_WARNING) << "Failed to stop the output stream: " << oboe::convertToText(result); + } + result = output_stream_->close(); + if (result != oboe::Result::OK) { + RTC_LOG(LS_WARNING) << "Failed to close the output stream: " << oboe::convertToText(result); + } + + output_stream_.reset(); + + if (playout_initialized_) { + if (CreateOutputStream() != 0) { + RTC_LOG(LS_ERROR) << "Failed to recreate the output stream!"; + playout_initialized_ = false; + is_playing_ = false; + } else { + if (is_playing_) { + result = output_stream_->requestStart(); + if (result != oboe::Result::OK) { + RTC_LOG(LS_ERROR) << "Failed to start the output stream."; + is_playing_ = false; + } + } + } + } + + return true; + } else { + RTC_LOG(LS_ERROR) << "Unhandled output stream error"; + return false; + } + } else if (audioStream == input_stream_.get()) { + RTC_LOG(LS_WARNING) << "onError on input stream: " << oboe::convertToText(error); + if (error == oboe::Result::ErrorDisconnected) { + oboe::Result result = input_stream_->stop(); + if (result != oboe::Result::OK) { + RTC_LOG(LS_WARNING) << "Failed to stop the input stream: " << oboe::convertToText(result); + } + result = input_stream_->close(); + if (result != oboe::Result::OK) { + RTC_LOG(LS_WARNING) << "Failed to close the input stream: " << oboe::convertToText(result); + } + + input_stream_.reset(); + + if (recording_initialized_) { + if (CreateInputStream() != 0) { + RTC_LOG(LS_ERROR) << "Failed to recreate the input stream!"; + recording_initialized_ = false; + is_recording_ = false; + } else { + if (is_recording_) { + result = input_stream_->requestStart(); + if (result != oboe::Result::OK) { + RTC_LOG(LS_ERROR) << "Failed to start the input stream."; + is_recording_ = false; + } + } + } + } + + return true; + } else { + RTC_LOG(LS_ERROR) << "Unhandled input stream error"; + return false; + } + } else { + RTC_LOG(LS_ERROR) << "onError for unknown stream: " << oboe::convertToText(error); + return false; + } + } + + void onErrorBeforeClose(oboe::AudioStream *audioStream, oboe::Result error) override { + RTC_LOG(LS_ERROR) << "onErrorBeforeClose invoked! " << oboe::convertToText(error); + } + + void onErrorAfterClose(oboe::AudioStream *audioStream, oboe::Result error) override { + RTC_LOG(LS_ERROR) << "onErrorAfterClose invoked! " << oboe::convertToText(error); + } + + oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) override { + if (!audio_callback_) { + RTC_LOG(LS_ERROR) << "Audio callback is not set!"; + std::fill(static_cast(audioData), static_cast(audioData) + numFrames, 0); + return oboe::DataCallbackResult::Stop; + } + + if (audioStream == output_stream_.get()) { + size_t num_samples_out = 0; + + // TODO: Hook up these extra fields if necessary. + int64_t elapsed_time_ms = -1; + int64_t ntp_time_ms = -1; + + int32_t result = audio_callback_->NeedMorePlayData( + numFrames, + sizeof(int16_t), + kChannelCount, + kSampleRate, + static_cast(audioData), + num_samples_out, + &elapsed_time_ms, + &ntp_time_ms); + if (result != 0) { + RTC_LOG(LS_ERROR) << "NeedMorePlayData failed with error: " << result; + std::fill(static_cast(audioData), static_cast(audioData) + numFrames, 0); + } + + // Update the delay of the playout. + auto latencyResult = output_stream_->calculateLatencyMillis(); + if (latencyResult) { + playout_delay_ms_ = static_cast( + std::clamp(latencyResult.value(), static_cast(0), static_cast(UINT16_MAX)) + ); + } + + // Update the playout underrun count. + auto countResult = output_stream_->getXRunCount(); + if (countResult) { + playout_underrun_count_ = countResult.value(); + } + } else if (audioStream == input_stream_.get()) { + // TODO: Hook up these extra fields if necessary. + uint32_t new_mic_level = 0; + + int32_t result = audio_callback_->RecordedDataIsAvailable( + static_cast(audioData), + numFrames, + sizeof(int16_t), + kChannelCount, + kSampleRate, + 0, + 0, + 0, + false, + new_mic_level); + if (result != 0) { + RTC_LOG(LS_ERROR) << "RecordedDataIsAvailable failed with error: " << result; + std::fill(static_cast(audioData), static_cast(audioData) + numFrames, 0); + } + } else { + RTC_LOG(LS_ERROR) << "onAudioReady on unknown stream!"; + std::fill(static_cast(audioData), static_cast(audioData) + numFrames, 0); + } + + return oboe::DataCallbackResult::Continue; + } + + private: + + void LogStreamConfiguration(const std::shared_ptr& stream) { + RTC_LOG(LS_WARNING) << " audioApi: " << oboe::convertToText(stream->getAudioApi()); + RTC_LOG(LS_WARNING) << " deviceId: " << stream->getDeviceId(); + RTC_LOG(LS_WARNING) << " format: " << oboe::convertToText(stream->getFormat()); + RTC_LOG(LS_WARNING) << " sampleRate: " << stream->getSampleRate(); + RTC_LOG(LS_WARNING) << " channelCount: " << stream->getChannelCount(); + RTC_LOG(LS_WARNING) << " sharingMode: " << oboe::convertToText(stream->getSharingMode()); + RTC_LOG(LS_WARNING) << " performanceMode: " << oboe::convertToText(stream->getPerformanceMode()); + if (stream->getDirection() == oboe::Direction::Output) { + RTC_LOG(LS_WARNING) << " usage: " << oboe::convertToText(stream->getUsage()); + RTC_LOG(LS_WARNING) << " contentType: " << oboe::convertToText(stream->getContentType()); + } else if (stream->getDirection() == oboe::Direction::Input) { + RTC_LOG(LS_WARNING) << " inputPreset: " << oboe::convertToText(stream->getInputPreset()); + } + } + + void ConfigureCommonStreamSettings(oboe::AudioStreamBuilder& builder, oboe::Direction direction) { + builder.setDirection(direction); + + // Keep using OpenSL-ES on Android 8.1 due to problems with AAudio. + if (oboe::getSdkVersion() == __ANDROID_API_O_MR1__) { + builder.setAudioApi(oboe::AudioApi::OpenSLES); + } + + // Let Oboe manage the configuration we want. + builder.setFormat(kSampleFormat); + builder.setSampleRate(kSampleRate); + builder.setChannelCount(kChannelCount); + builder.setFramesPerDataCallback(kMaxFramesPerCallback); + + // And allow Oboe to perform conversions if necessary. + builder.setFormatConversionAllowed(true); + // Use medium to balance performance and quality. + builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium); + builder.setChannelConversionAllowed(true); + + if (use_exclusive_sharing_mode_) { + // Attempt to use Exclusive sharing mode for the lowest possible latency. + builder.setSharingMode(oboe::SharingMode::Exclusive); + } + + // Set the performance mode to get the lowest possible latency. + builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); + + // Set callbacks for handling PCM data and other underlying API notifications. + // To avoid an abstraction, we'll set the shared pointer with a no-op deleter. + builder.setDataCallback(std::shared_ptr( + this, + [](oboe::AudioStreamDataCallback*) {} + )); + builder.setErrorCallback(std::shared_ptr( + this, + [](oboe::AudioStreamErrorCallback*) {} + )); + + if (audio_session_id_ != kInvalidAudioSessionId) { + RTC_LOG(LS_WARNING) << "Setting session_id: " << audio_session_id_; + builder.setSessionId((oboe::SessionId) audio_session_id_); + } + } + + int32_t CreateOutputStream() { + RTC_LOG(LS_WARNING) << "CreateOutputStream"; + + // Create a builder for an Oboe output audio stream. + oboe::AudioStreamBuilder builder; + ConfigureCommonStreamSettings(builder, oboe::Direction::Output); + + // Specifying usage and contentType might result in better volume and routing decisions. + // These settings are specific to the output stream. + builder.setUsage(oboe::Usage::VoiceCommunication); + builder.setContentType(oboe::ContentType::Speech); + + oboe::Result result = builder.openStream(output_stream_); + if (result != oboe::Result::OK) { + RTC_LOG(LS_ERROR) << "Failed to open the output stream: " << oboe::convertToText(result); + return -1; + } + + LogStreamConfiguration(output_stream_); + + return 0; + } + + int32_t CreateInputStream() { + RTC_LOG(LS_WARNING) << "CreateInputStream"; + + oboe::AudioStreamBuilder builder; + ConfigureCommonStreamSettings(builder, oboe::Direction::Input); + + // Specifying an inputPreset might result in better volume and routing decisions (and privacy). + // This setting is specific to the input stream. + builder.setInputPreset(oboe::InputPreset::VoiceCommunication); + + oboe::Result result = builder.openStream(input_stream_); + if (result != oboe::Result::OK) { + RTC_LOG(LS_ERROR) << "Failed to open the input stream: " << oboe::convertToText(result); + return -1; + } + + LogStreamConfiguration(input_stream_); + + return 0; + } + + SequenceChecker thread_checker_; + + const bool use_software_acoustic_echo_canceler_; + const bool use_software_noise_suppressor_; + const bool use_exclusive_sharing_mode_; + const int32_t audio_session_id_; + + std::shared_ptr input_stream_; + std::shared_ptr output_stream_; + + webrtc::AudioTransport* audio_callback_ = nullptr; + + std::atomic initialized_ { false }; + + std::atomic playout_initialized_ { false }; + std::atomic recording_initialized_ { false }; + + std::atomic is_playing_ { false }; + std::atomic is_recording_ { false }; + + std::atomic playout_delay_ms_ { kDefaultPlayoutDelayMs }; + std::atomic playout_underrun_count_ { 0 }; + + mutable std::atomic delay_log_counter_ { 0 }; + mutable std::atomic underrun_log_counter_ { 0 }; +}; + +} // namespace + +rtc::scoped_refptr CreateAudioDeviceModuleOboe( + bool use_software_acoustic_echo_canceler, + bool use_software_noise_suppressor, + bool use_exclusive_sharing_mode, + int audio_session_id) { + RTC_LOG(LS_WARNING) << "CreateAudioDeviceModuleOboe"; + return rtc::make_ref_counted( + use_software_acoustic_echo_canceler, + use_software_noise_suppressor, + use_exclusive_sharing_mode, + audio_session_id); +} + +} // namespace jni +} // namespace webrtc diff --git a/sdk/android/src/jni/audio_device/audio_device_module_oboe.h b/sdk/android/src/jni/audio_device/audio_device_module_oboe.h new file mode 100644 index 0000000000..b14b356b34 --- /dev/null +++ b/sdk/android/src/jni/audio_device/audio_device_module_oboe.h @@ -0,0 +1,24 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#ifndef SDK_ANDROID_SRC_JNI_AUDIO_DEVICE_AUDIO_DEVICE_MODULE_OBOE_H_ +#define SDK_ANDROID_SRC_JNI_AUDIO_DEVICE_AUDIO_DEVICE_MODULE_OBOE_H_ + +#include "modules/audio_device/include/audio_device.h" + +namespace webrtc { +namespace jni { + +// Create the Oboe-based AudioDeviceModule. +rtc::scoped_refptr CreateAudioDeviceModuleOboe( + bool use_software_acoustic_echo_canceler, + bool use_software_noise_suppressor, + bool use_exclusive_sharing_mode, + int audio_session_id); + +} // namespace jni +} // namespace webrtc + +#endif // SDK_ANDROID_SRC_JNI_AUDIO_DEVICE_AUDIO_DEVICE_MODULE_OBOE_H_ diff --git a/sdk/android/src/jni/audio_device/oboe_audio_device_module.cc b/sdk/android/src/jni/audio_device/oboe_audio_device_module.cc new file mode 100644 index 0000000000..0a0a3525cb --- /dev/null +++ b/sdk/android/src/jni/audio_device/oboe_audio_device_module.cc @@ -0,0 +1,30 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include "sdk/android/generated_java_audio_jni/OboeAudioDeviceModule_jni.h" +#include "sdk/android/src/jni/audio_device/audio_device_module_oboe.h" +#include "sdk/android/src/jni/jni_helpers.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace jni { + +static jlong JNI_OboeAudioDeviceModule_CreateAudioDeviceModule( + JNIEnv* env, + jboolean j_use_software_acoustic_echo_canceler, + jboolean j_use_software_noise_suppressor, + jboolean j_use_exclusive_sharing_mode, + int audio_session_id) { + RTC_LOG(LS_WARNING) << "JNI_OboeAudioDeviceModule_CreateAudioDeviceModule"; + return jlongFromPointer(CreateAudioDeviceModuleOboe( + j_use_software_acoustic_echo_canceler, + j_use_software_noise_suppressor, + j_use_exclusive_sharing_mode, + audio_session_id) + .release()); +} + +} // namespace jni +} // namespace webrtc