Add audio device module for android based on Oboe

This commit is contained in:
Jim Gustafson 2024-07-03 12:28:41 -07:00 committed by GitHub
parent 47b7b9d95f
commit a322dda556
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1318 additions and 2 deletions

6
DEPS
View file

@ -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',

1
ringrtc/oboe/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/src

99
ringrtc/oboe/BUILD.gn Normal file
View file

@ -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",
]
}

View file

@ -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"
}

View file

@ -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);
}

View file

@ -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 <oboe/Oboe.h>
#include <memory>
#include <utility>
#include <atomic>
// 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<Stats> 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<int16_t*>(audioData), static_cast<int16_t*>(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<int16_t*>(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<int16_t*>(audioData), static_cast<int16_t*>(audioData) + numFrames, 0);
}
// Update the delay of the playout.
auto latencyResult = output_stream_->calculateLatencyMillis();
if (latencyResult) {
playout_delay_ms_ = static_cast<uint16_t>(
std::clamp(latencyResult.value(), static_cast<double>(0), static_cast<double>(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<int16_t*>(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<int16_t*>(audioData), static_cast<int16_t*>(audioData) + numFrames, 0);
}
} else {
RTC_LOG(LS_ERROR) << "onAudioReady on unknown stream!";
std::fill(static_cast<int16_t*>(audioData), static_cast<int16_t*>(audioData) + numFrames, 0);
}
return oboe::DataCallbackResult::Continue;
}
private:
void LogStreamConfiguration(const std::shared_ptr<oboe::AudioStream>& 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<oboe::AudioStreamDataCallback>(
this,
[](oboe::AudioStreamDataCallback*) {}
));
builder.setErrorCallback(std::shared_ptr<oboe::AudioStreamErrorCallback>(
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<oboe::AudioStream> input_stream_;
std::shared_ptr<oboe::AudioStream> output_stream_;
webrtc::AudioTransport* audio_callback_ = nullptr;
std::atomic<bool> initialized_ { false };
std::atomic<bool> playout_initialized_ { false };
std::atomic<bool> recording_initialized_ { false };
std::atomic<bool> is_playing_ { false };
std::atomic<bool> is_recording_ { false };
std::atomic<uint16_t> playout_delay_ms_ { kDefaultPlayoutDelayMs };
std::atomic<int32_t> playout_underrun_count_ { 0 };
mutable std::atomic<uint32_t> delay_log_counter_ { 0 };
mutable std::atomic<uint32_t> underrun_log_counter_ { 0 };
};
} // namespace
rtc::scoped_refptr<AudioDeviceModule> 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<AndroidAudioDeviceModuleOboe>(
use_software_acoustic_echo_canceler,
use_software_noise_suppressor,
use_exclusive_sharing_mode,
audio_session_id);
}
} // namespace jni
} // namespace webrtc

View file

@ -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<AudioDeviceModule> 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_

View file

@ -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