diff --git a/apps/OboeTester/app/build.gradle b/apps/OboeTester/app/build.gradle index 5d4058d21..41787a625 100644 --- a/apps/OboeTester/app/build.gradle +++ b/apps/OboeTester/app/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 28 defaultConfig { applicationId = "com.google.sample.oboe.manualtest" - minSdkVersion 25 + minSdkVersion 23 targetSdkVersion 28 // Also update the version in the AndroidManifest.xml file. versionCode 30 diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp index d0a6b46dd..de82a8fd2 100644 --- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp +++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp @@ -97,6 +97,12 @@ Java_com_google_sample_oboe_manualtest_NativeEngine_isMMapExclusiveSupported(JNI return AAudioExtensions::getInstance().isMMapExclusiveSupported(); } +JNIEXPORT void JNICALL +Java_com_google_sample_oboe_manualtest_NativeEngine_setWorkaroundsEnabled(JNIEnv *env, jclass type, + jboolean enabled) { + oboe::OboeGlobals::setWorkaroundsEnabled(enabled); +} + JNIEXPORT jint JNICALL Java_com_google_sample_oboe_manualtest_OboeAudioStream_openNative( JNIEnv *env, jobject synth, diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java index 4f09599cf..6a7371c5e 100644 --- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java +++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java @@ -100,6 +100,7 @@ public void onNothingSelected(AdapterView adapterView) { mBuildTextView = (TextView) findViewById(R.id.text_build_info); mBuildTextView.setText(Build.DISPLAY); + saveIntentBundleForLaterProcessing(getIntent()); } @@ -145,6 +146,7 @@ private void processBundleFromIntent() { @Override public void onResume(){ super.onResume(); + NativeEngine.setWorkaroundsEnabled(false); processBundleFromIntent(); } @@ -250,4 +252,9 @@ public void onSetSpeakerphoneOn(View view) { myAudioMgr.setSpeakerphoneOn(enabled); } + public void onEnableWorkarounds(View view) { + CheckBox checkBox = (CheckBox) view; + boolean enabled = checkBox.isChecked(); + NativeEngine.setWorkaroundsEnabled(enabled); + } } diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/NativeEngine.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/NativeEngine.java index 03bf26c14..9917c6621 100644 --- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/NativeEngine.java +++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/NativeEngine.java @@ -5,4 +5,6 @@ public class NativeEngine { static native boolean isMMapSupported(); static native boolean isMMapExclusiveSupported(); + + static native void setWorkaroundsEnabled(boolean enabled); } diff --git a/apps/OboeTester/app/src/main/res/layout/activity_main.xml b/apps/OboeTester/app/src/main/res/layout/activity_main.xml index fa724702b..6c13bbcfe 100644 --- a/apps/OboeTester/app/src/main/res/layout/activity_main.xml +++ b/apps/OboeTester/app/src/main/res/layout/activity_main.xml @@ -154,6 +154,17 @@ app:layout_constraintStart_toStartOf="@+id/useCallback" app:layout_constraintTop_toBottomOf="@+id/useCallback" /> + + + app:layout_constraintStart_toStartOf="@+id/boxEnableWorkarounds" /> + app:layout_constraintTop_toBottomOf="@+id/boxEnableWorkarounds" /> #include +#include #include "oboe/Definitions.h" namespace oboe { @@ -58,6 +59,19 @@ int32_t convertFormatToSizeInBytes(AudioFormat format); template const char * convertToText(FromType input); +/** + * @param name + * @return the value of a named system property in a string or empty string + */ +std::string getPropertyString(const char * name); + +/** + * @param name + * @param defaultValue + * @return integer value associated with a property or the default value + */ +int getPropertyInteger(const char * name, int defaultValue); + /** * Return the version of the SDK that is currently running. * diff --git a/include/oboe/Version.h b/include/oboe/Version.h index fcd4c5b79..69d7f46b9 100644 --- a/include/oboe/Version.h +++ b/include/oboe/Version.h @@ -37,7 +37,7 @@ #define OBOE_VERSION_MINOR 3 // Type: 16-bit unsigned int. Min value: 0 Max value: 65535. See below for description. -#define OBOE_VERSION_PATCH 2 +#define OBOE_VERSION_PATCH 3 #define OBOE_STRINGIFY(x) #x #define OBOE_TOSTRING(x) OBOE_STRINGIFY(x) diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp index 33091ad5a..b19ec915d 100644 --- a/src/aaudio/AudioStreamAAudio.cpp +++ b/src/aaudio/AudioStreamAAudio.cpp @@ -26,6 +26,8 @@ #ifdef __ANDROID__ #include +#include + #endif #ifndef OBOE_FIX_FORCE_STARTING_TO_STARTED @@ -161,7 +163,8 @@ Result AudioStreamAAudio::open() { // does not increase latency. int32_t capacity = mBufferCapacityInFrames; constexpr int kCapacityRequiredForFastLegacyTrack = 4096; // matches value in AudioFinger - if (mDirection == oboe::Direction::Input + if (OboeGlobals::areWorkaroundsEnabled() + && mDirection == oboe::Direction::Input && capacity != oboe::Unspecified && capacity < kCapacityRequiredForFastLegacyTrack && mPerformanceMode == oboe::PerformanceMode::LowLatency) { @@ -445,7 +448,8 @@ Result AudioStreamAAudio::waitForStateChange(StreamState currentState, break; } #if OBOE_FIX_FORCE_STARTING_TO_STARTED - if (aaudioNextState == static_cast(StreamState::Starting)) { + if (OboeGlobals::areWorkaroundsEnabled() + && aaudioNextState == static_cast(StreamState::Starting)) { aaudioNextState = static_cast(StreamState::Started); } #endif // OBOE_FIX_FORCE_STARTING_TO_STARTED @@ -481,11 +485,13 @@ ResultWithValue AudioStreamAAudio::setBufferSizeInFrames(int32_t reques AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { - - if (requestedFrames > mBufferCapacityInFrames) { - requestedFrames = mBufferCapacityInFrames; + int32_t adjustedFrames = requestedFrames; + if (adjustedFrames > mBufferCapacityInFrames) { + adjustedFrames = mBufferCapacityInFrames; } - int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, requestedFrames); + adjustedFrames = QuirksManager::getInstance().clipBufferSize(*this, adjustedFrames); + + int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, adjustedFrames); // Cache the result if it's valid if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize; @@ -502,7 +508,8 @@ StreamState AudioStreamAAudio::getState() const { if (stream != nullptr) { aaudio_stream_state_t aaudioState = mLibLoader->stream_getState(stream); #if OBOE_FIX_FORCE_STARTING_TO_STARTED - if (aaudioState == AAUDIO_STREAM_STATE_STARTING) { + if (OboeGlobals::areWorkaroundsEnabled() + && aaudioState == AAUDIO_STREAM_STATE_STARTING) { aaudioState = AAUDIO_STREAM_STATE_STARTED; } #endif // OBOE_FIX_FORCE_STARTING_TO_STARTED diff --git a/src/aaudio/AudioStreamAAudio.h b/src/aaudio/AudioStreamAAudio.h index 2a0439627..6267328ba 100644 --- a/src/aaudio/AudioStreamAAudio.h +++ b/src/aaudio/AudioStreamAAudio.h @@ -91,6 +91,8 @@ class AudioStreamAAudio : public AudioStream { void *audioData, int32_t numFrames); + bool isMMapUsed(); + protected: static void internalErrorCallback( AAudioStream *stream, @@ -108,8 +110,6 @@ class AudioStreamAAudio : public AudioStream { private: - bool isMMapUsed(); - std::atomic mCallbackThreadEnabled; // pointer to the underlying AAudio stream, valid if open, null if closed diff --git a/src/common/AudioStreamBuilder.cpp b/src/common/AudioStreamBuilder.cpp index 401beea27..9d446258a 100644 --- a/src/common/AudioStreamBuilder.cpp +++ b/src/common/AudioStreamBuilder.cpp @@ -26,6 +26,8 @@ #include "opensles/AudioStreamOpenSLES.h" #include "QuirksManager.h" +bool oboe::OboeGlobals::mWorkaroundsEnabled = true; + namespace oboe { /** diff --git a/src/common/QuirksManager.cpp b/src/common/QuirksManager.cpp index 2d5f644f7..3df1bc4ac 100644 --- a/src/common/QuirksManager.cpp +++ b/src/common/QuirksManager.cpp @@ -16,10 +16,76 @@ #include #include + #include "QuirksManager.h" using namespace oboe; +int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream, + int32_t requestedSize) { + if (!OboeGlobals::areWorkaroundsEnabled()) { + return requestedSize; + } + int bottomMargin = kDefaultBottomMarginInBursts; + int topMargin = kDefaultTopMarginInBursts; + if (isMMapUsed(stream)) { + if (stream.getSharingMode() == SharingMode::Exclusive) { + bottomMargin = getExclusiveBottomMarginInBursts(); + topMargin = getExclusiveTopMarginInBursts(); + } + } else { + bottomMargin = kLegacyBottomMarginInBursts; + } + + int32_t burst = stream.getFramesPerBurst(); + int32_t minSize = bottomMargin * burst; + int32_t adjustedSize = requestedSize; + if (adjustedSize < minSize ) { + adjustedSize = minSize; + } else { + int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst); + if (adjustedSize > maxSize ) { + adjustedSize = maxSize; + } + } + return adjustedSize; +} + +class SamsungDeviceQuirks : public QuirksManager::DeviceQuirks { +public: + SamsungDeviceQuirks() { + std::string arch = getPropertyString("ro.arch"); + isExynos = (arch.rfind("exynos", 0) == 0); // starts with? + } + + virtual ~SamsungDeviceQuirks() = default; + + int32_t getExclusiveBottomMarginInBursts() const override { + // TODO Make this conditional on build version when MMAP timing improves. + return isExynos ? kBottomMarginExynos : kBottomMarginOther; + } + + int32_t getExclusiveTopMarginInBursts() const override { + return kTopMargin; + } + +private: + // Stay farther away from DSP position on Exynos devices. + static constexpr int32_t kBottomMarginExynos = 2; + static constexpr int32_t kBottomMarginOther = 1; + static constexpr int32_t kTopMargin = 1; + bool isExynos = false; +}; + +QuirksManager::QuirksManager() { + std::string manufacturer = getPropertyString("ro.product.manufacturer"); + if (manufacturer == "samsung") { + mDeviceQuirks = std::make_unique(); + } else { + mDeviceQuirks = std::make_unique(); + } +} + bool QuirksManager::isConversionNeeded( const AudioStreamBuilder &builder, AudioStreamBuilder &childBuilder) { @@ -54,11 +120,13 @@ bool QuirksManager::isConversionNeeded( // Channel Count if (builder.getChannelCount() != oboe::Unspecified && builder.isChannelConversionAllowed()) { - if (builder.getChannelCount() == 2 // stereo? + if (OboeGlobals::areWorkaroundsEnabled() + && builder.getChannelCount() == 2 // stereo? && isInput && isLowLatency && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))) { - // workaround for temporary heap size regression, b/66967812 + // Workaround for heap size regression in O. + // b/66967812 AudioRecord does not allow FAST track for stereo capture in O childBuilder.setChannelCount(1); conversionNeeded = true; } diff --git a/src/common/QuirksManager.h b/src/common/QuirksManager.h index d14f1d3d4..a177764cf 100644 --- a/src/common/QuirksManager.h +++ b/src/common/QuirksManager.h @@ -19,10 +19,13 @@ #include #include +#include namespace oboe { /** + * INTERNAL USE ONLY. + * * Based on manufacturer, model and Android version number * decide whether data conversion needs to occur. * @@ -33,11 +36,17 @@ class QuirksManager { public: static QuirksManager &getInstance() { - static QuirksManager instance; + static QuirksManager instance; // singleton return instance; } + QuirksManager(); + virtual ~QuirksManager() = default; + /** + * Do we need to do channel, format or rate conversion to provide a low latency + * stream for this builder? If so then provide a builder for the native child stream + * that will be used to get low latency. * * @param builder builder provided by application * @param childBuilder modified builder appropriate for the underlying device @@ -45,7 +54,56 @@ class QuirksManager { */ bool isConversionNeeded(const AudioStreamBuilder &builder, AudioStreamBuilder &childBuilder); + static bool isMMapUsed(AudioStream &stream) { + bool answer = false; + if (stream.getAudioApi() == AudioApi::AAudio) { + AudioStreamAAudio *streamAAudio = + reinterpret_cast(&stream); + answer = streamAAudio->isMMapUsed(); + } + return answer; + } + + virtual int32_t clipBufferSize(AudioStream &stream, int32_t bufferSize) { + return mDeviceQuirks->clipBufferSize(stream, bufferSize); + } + + class DeviceQuirks { + public: + virtual ~DeviceQuirks() = default; + + /** + * Restrict buffer size. This is mainly to avoid glitches caused by MMAP + * timestamp inaccuracies. + * @param stream + * @param requestedSize + * @return + */ + int32_t clipBufferSize(AudioStream &stream, int32_t requestedSize); + + // Exclusive MMAP streams can have glitches because they are using a timing + // model of the DSP to control IO instead of direct synchronization. + virtual int32_t getExclusiveBottomMarginInBursts() const { + return kDefaultBottomMarginInBursts; + } + + virtual int32_t getExclusiveTopMarginInBursts() const { + return kDefaultTopMarginInBursts; + } + + static constexpr int32_t kDefaultBottomMarginInBursts = 0; + static constexpr int32_t kDefaultTopMarginInBursts = 0; + + // For Legacy streams, do not let the buffer go below one burst. + // b/129545119 | AAudio Legacy allows setBufferSizeInFrames too low + // Fixed in Q + static constexpr int32_t kLegacyBottomMarginInBursts = 1; + }; + private: + + std::unique_ptr mDeviceQuirks{}; + }; } diff --git a/src/common/Utilities.cpp b/src/common/Utilities.cpp index f940d1bf6..c3acf4763 100644 --- a/src/common/Utilities.cpp +++ b/src/common/Utilities.cpp @@ -266,18 +266,40 @@ const char *convertToText(ChannelCount channelCount) { } } -int getSdkVersion() { +std::string getPropertyString(const char * name) { + std::string result; #ifdef __ANDROID__ + char valueText[PROP_VALUE_MAX] = {0}; + if (__system_property_get(name, valueText) != 0) { + result = valueText; + } +#else + (void) name; +#endif + return result; +} + +int getPropertyInteger(const char * name, int defaultValue) { + int result = defaultValue; +#ifdef __ANDROID__ + char valueText[PROP_VALUE_MAX] = {0}; + if (__system_property_get(name, valueText) != 0) { + result = atoi(valueText); + } +#else + (void) name; +#endif + return result; +} + +int getSdkVersion() { static int sCachedSdkVersion = -1; +#ifdef __ANDROID__ if (sCachedSdkVersion == -1) { - char sdk[PROP_VALUE_MAX] = {0}; - if (__system_property_get("ro.build.version.sdk", sdk) != 0) { - sCachedSdkVersion = atoi(sdk); - } + sCachedSdkVersion = getPropertyInteger("ro.build.version.sdk", -1); } - return sCachedSdkVersion; #endif - return -1; + return sCachedSdkVersion; } }// namespace oboe