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