Add configuration option for performance mode

Performance mode tells the framework how to configure the audio path
for a player or recorder according to application performance and
functional requirements.
It affects the output or input latency based on acceptable tradeoffs on
battery drain and use of pre or post processing effects.

Bug: 20825626
Bug: 17981873

Change-Id: I0e79cf2da03b4bfa2080fe6d4ea9d8753ffe3497
diff --git a/include/SLES/OpenSLES_AndroidConfiguration.h b/include/SLES/OpenSLES_AndroidConfiguration.h
index 3846dcd..ae12f77 100644
--- a/include/SLES/OpenSLES_AndroidConfiguration.h
+++ b/include/SLES/OpenSLES_AndroidConfiguration.h
@@ -68,6 +68,35 @@
 #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005)
 
 
+/*---------------------------------------------------------------------------*/
+/* Android AudioPlayer and AudioRecorder configuration                       */
+/*---------------------------------------------------------------------------*/
+
+/** Audio Performance mode.
+ * Performance mode tells the framework how to configure the audio path
+ * for a player or recorder according to application performance and
+ * functional requirements.
+ * It affects the output or input latency based on acceptable tradeoffs on
+ * battery drain and use of pre or post processing effects.
+ * Performance mode should be set before realizing the object and should be
+ * read after realizing the object to check if the requested mode could be
+ * granted or not.
+ */
+/** Audio Performance mode key */
+#define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode")
+
+/** Audio performance values */
+/*      No specific performance requirement. Allows HW and SW pre/post processing. */
+#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32) 0x00000000)
+/*      Priority given to latency. No HW or software pre/post processing.
+ *      This is the default if no performance mode is specified. */
+#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32) 0x00000001)
+/*      Priority given to latency while still allowing HW pre and post processing. */
+#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32) 0x00000002)
+/*      Priority given to power saving if latency is not a concern.
+ *      Allows HW and SW pre/post processing. */
+#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32) 0x00000003)
+
 
 #ifdef __cplusplus
 }
diff --git a/src/android/AudioPlayer_to_android.cpp b/src/android/AudioPlayer_to_android.cpp
index 4492d02..dd2e895 100644
--- a/src/android/AudioPlayer_to_android.cpp
+++ b/src/android/AudioPlayer_to_android.cpp
@@ -34,6 +34,7 @@
                                     android::sp<android::AudioEffect> > ;
 
 #define KEY_STREAM_TYPE_PARAMSIZE  sizeof(SLint32)
+#define KEY_PERFORMANCE_MODE_PARAMSIZE  sizeof(SLint32)
 
 #define AUDIOTRACK_MIN_PLAYBACKRATE_PERMILLE  500
 #define AUDIOTRACK_MAX_PLAYBACKRATE_PERMILLE 2000
@@ -484,6 +485,42 @@
     return result;
 }
 
+//-----------------------------------------------------------------------------
+SLresult audioPlayer_setPerformanceMode(CAudioPlayer* ap, SLuint32 mode) {
+    SLresult result = SL_RESULT_SUCCESS;
+    SL_LOGV("performance mode set to %d", mode);
+
+    SLuint32 perfMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
+    switch (mode) {
+    case SL_ANDROID_PERFORMANCE_LATENCY:
+        perfMode = ANDROID_PERFORMANCE_MODE_LATENCY;
+        break;
+    case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:
+        perfMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
+        break;
+    case SL_ANDROID_PERFORMANCE_NONE:
+        perfMode = ANDROID_PERFORMANCE_MODE_NONE;
+        break;
+    case SL_ANDROID_PERFORMANCE_POWER_SAVING:
+        perfMode = ANDROID_PERFORMANCE_MODE_POWER_SAVING;
+        break;
+    default:
+        SL_LOGE(ERROR_CONFIG_PERF_MODE_UNKNOWN);
+        result = SL_RESULT_PARAMETER_INVALID;
+        break;
+    }
+
+    // performance mode needs to be set before the object is realized
+    // (ap->mAudioTrack is supposed to be NULL until then)
+    if (SL_OBJECT_STATE_UNREALIZED != ap->mObject.mState) {
+        SL_LOGE(ERROR_CONFIG_PERF_MODE_REALIZED);
+        result = SL_RESULT_PRECONDITIONS_VIOLATED;
+    } else {
+        ap->mPerformanceMode = perfMode;
+    }
+
+    return result;
+}
 
 //-----------------------------------------------------------------------------
 SLresult audioPlayer_getStreamType(CAudioPlayer* ap, SLint32 *pType) {
@@ -518,6 +555,31 @@
     return result;
 }
 
+//-----------------------------------------------------------------------------
+SLresult audioPlayer_getPerformanceMode(CAudioPlayer* ap, SLuint32 *pMode) {
+    SLresult result = SL_RESULT_SUCCESS;
+
+    switch (ap->mPerformanceMode) {
+    case ANDROID_PERFORMANCE_MODE_LATENCY:
+        *pMode = SL_ANDROID_PERFORMANCE_LATENCY;
+        break;
+    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
+        *pMode = SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
+        break;
+    case ANDROID_PERFORMANCE_MODE_NONE:
+        *pMode = SL_ANDROID_PERFORMANCE_NONE;
+        break;
+    case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
+        *pMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
+        break;
+    default:
+        result = SL_RESULT_INTERNAL_ERROR;
+        *pMode = SL_ANDROID_PERFORMANCE_LATENCY;
+        break;
+    }
+
+    return result;
+}
 
 //-----------------------------------------------------------------------------
 void audioPlayer_auxEffectUpdate(CAudioPlayer* ap) {
@@ -1289,6 +1351,7 @@
     // android::AudioSystem::acquireAudioSessionId(pAudioPlayer->mSessionId);
 
     pAudioPlayer->mStreamType = ANDROID_DEFAULT_OUTPUT_STREAM_TYPE;
+    pAudioPlayer->mPerformanceMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
 
     // mAudioTrack
     pAudioPlayer->mCallbackProtector = new android::CallbackProtector();
@@ -1336,6 +1399,15 @@
         } else {
             result = audioPlayer_setStreamType(ap, *(SLuint32*)pConfigValue);
         }
+    } else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {
+
+        // performance mode
+        if (KEY_PERFORMANCE_MODE_PARAMSIZE > valueSize) {
+            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
+            result = SL_RESULT_BUFFER_INSUFFICIENT;
+        } else {
+            result = audioPlayer_setPerformanceMode(ap, *(SLuint32*)pConfigValue);
+        }
 
     } else {
         SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
@@ -1366,6 +1438,19 @@
         }
         *pValueSize = KEY_STREAM_TYPE_PARAMSIZE;
 
+    } else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {
+
+        // performance mode
+        if (NULL == pConfigValue) {
+            result = SL_RESULT_SUCCESS;
+        } else if (KEY_PERFORMANCE_MODE_PARAMSIZE > *pValueSize) {
+            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
+            result = SL_RESULT_BUFFER_INSUFFICIENT;
+        } else {
+            result = audioPlayer_getPerformanceMode(ap, (SLuint32*)pConfigValue);
+        }
+        *pValueSize = KEY_PERFORMANCE_MODE_PARAMSIZE;
+
     } else {
         SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
         result = SL_RESULT_PARAMETER_INVALID;
@@ -1375,10 +1460,11 @@
 }
 
 
-// Called from android_audioPlayer_realize for a PCM buffer queue player
-// to determine if it can use a fast track.
-static bool canUseFastTrack(CAudioPlayer *pAudioPlayer)
+// Called from android_audioPlayer_realize for a PCM buffer queue player before creating the
+// AudioTrack to determine which performance modes are allowed based on effect interfaces present
+static void checkAndSetPerformanceModePre(CAudioPlayer *pAudioPlayer)
 {
+    SLuint32 allowedModes = ANDROID_PERFORMANCE_MODE_ALL;
     assert(pAudioPlayer->mAndroidObjType == AUDIOPLAYER_FROM_PCM_BUFFERQUEUE);
 
     // no need to check the buffer queue size, application side
@@ -1392,7 +1478,6 @@
     // If a blacklisted interface is added after realization using
     // DynamicInterfaceManagement::AddInterface,
     // then this won't be detected but the interface will be ineffective.
-    bool blacklistResult = true;
     static const unsigned blacklist[] = {
         MPH_BASSBOOST,
         MPH_EFFECTSEND,
@@ -1407,11 +1492,16 @@
     };
     for (unsigned i = 0; i < sizeof(blacklist)/sizeof(blacklist[0]); ++i) {
         if (IsInterfaceInitialized(&pAudioPlayer->mObject, blacklist[i])) {
-            blacklistResult = false;
+            //TODO: query effect for EFFECT_FLAG_HW_ACC_xx flag to refine mode
+            allowedModes &=
+                    ~(ANDROID_PERFORMANCE_MODE_LATENCY|ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS);
             break;
         }
     }
 #if LOG_NDEBUG == 0
+    bool blacklistResult = (
+            (allowedModes &
+                (ANDROID_PERFORMANCE_MODE_LATENCY|ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS)) != 0);
     bool whitelistResult = true;
     static const unsigned whitelist[] = {
         MPH_BUFFERQUEUE,
@@ -1440,13 +1530,48 @@
     }
     if (whitelistResult != blacklistResult) {
         SL_LOGW("whitelistResult != blacklistResult");
-        // and use blacklistResult below
     }
 #endif
-    return blacklistResult;
+    if (pAudioPlayer->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY) {
+        if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY) == 0) {
+            pAudioPlayer->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
+        }
+    }
+    if (pAudioPlayer->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) {
+        if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) == 0) {
+            pAudioPlayer->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
+        }
+    }
 }
 
-
+// Called from android_audioPlayer_realize for a PCM buffer queue player after creating the
+// AudioTrack to adjust performance mode based on actual output flags
+static void checkAndSetPerformanceModePost(CAudioPlayer *pAudioPlayer)
+{
+    audio_output_flags_t flags = pAudioPlayer->mAudioTrack->getFlags();
+    switch (pAudioPlayer->mPerformanceMode) {
+    case ANDROID_PERFORMANCE_MODE_LATENCY:
+        if ((flags & (AUDIO_OUTPUT_FLAG_FAST | AUDIO_OUTPUT_FLAG_RAW)) ==
+                (AUDIO_OUTPUT_FLAG_FAST | AUDIO_OUTPUT_FLAG_RAW)) {
+            break;
+        }
+        pAudioPlayer->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
+        /* FALL THROUGH */
+    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
+        if ((flags & AUDIO_OUTPUT_FLAG_FAST) == 0) {
+            pAudioPlayer->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
+        }
+        break;
+    case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
+        if ((flags & AUDIO_OUTPUT_FLAG_DEEP_BUFFER) == 0) {
+            pAudioPlayer->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
+        }
+        break;
+    case ANDROID_PERFORMANCE_MODE_NONE:
+    default:
+        break;
+    }
+}
 //-----------------------------------------------------------------------------
 // FIXME abstract out the diff between CMediaPlayer and CAudioPlayer
 SLresult android_audioPlayer_realize(CAudioPlayer *pAudioPlayer, SLboolean async) {
@@ -1488,16 +1613,31 @@
             df_pcm->channelMask,
             channelMask);
 
+        checkAndSetPerformanceModePre(pAudioPlayer);
+
         audio_output_flags_t policy;
-        int32_t notificationFrames;
-        if (canUseFastTrack(pAudioPlayer)) {
+        switch (pAudioPlayer->mPerformanceMode) {
+        case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
+            policy = AUDIO_OUTPUT_FLAG_DEEP_BUFFER;
+            break;
+        case ANDROID_PERFORMANCE_MODE_NONE:
+            policy = AUDIO_OUTPUT_FLAG_NONE;
+            break;
+        case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
+            policy = AUDIO_OUTPUT_FLAG_FAST;
+            break;
+        case ANDROID_PERFORMANCE_MODE_LATENCY:
+        default:
             policy = (audio_output_flags_t)(AUDIO_OUTPUT_FLAG_FAST | AUDIO_OUTPUT_FLAG_RAW);
+            break;
+        }
+
+        int32_t notificationFrames;
+        if ((policy & AUDIO_OUTPUT_FLAG_FAST) != 0) {
             // negative notificationFrames is the number of notifications (sub-buffers) per track buffer
             // for details see the explanation at frameworks/av/include/media/AudioTrack.h
             notificationFrames = -pAudioPlayer->mBufferQueue.mNumBuffers;
         } else {
-            policy = AUDIO_OUTPUT_FLAG_NONE;
-            // use default notifications
             notificationFrames = 0;
         }
 
@@ -1521,6 +1661,9 @@
             return result;
         }
 
+        // update performance mode according to actual flags granted to AudioTrack
+        checkAndSetPerformanceModePost(pAudioPlayer);
+
         // initialize platform-independent CAudioPlayer fields
 
         pAudioPlayer->mNumChannels = df_pcm->numChannels;
diff --git a/src/android/AudioRecorder_to_android.cpp b/src/android/AudioRecorder_to_android.cpp
index fbf8890..becb693 100644
--- a/src/android/AudioRecorder_to_android.cpp
+++ b/src/android/AudioRecorder_to_android.cpp
@@ -27,6 +27,7 @@
 
 #define KEY_RECORDING_SOURCE_PARAMSIZE  sizeof(SLuint32)
 #define KEY_RECORDING_PRESET_PARAMSIZE  sizeof(SLuint32)
+#define KEY_PERFORMANCE_MODE_PARAMSIZE  sizeof(SLuint32)
 
 //-----------------------------------------------------------------------------
 // Internal utility functions
@@ -72,6 +73,44 @@
 }
 
 
+//-----------------------------------------------------------------------------
+SLresult audioRecorder_setPerformanceMode(CAudioRecorder* ar, SLuint32 mode) {
+    SLresult result = SL_RESULT_SUCCESS;
+    SL_LOGV("performance mode set to %d", mode);
+
+    SLuint32 perfMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
+    switch (mode) {
+    case SL_ANDROID_PERFORMANCE_LATENCY:
+        perfMode = ANDROID_PERFORMANCE_MODE_LATENCY;
+        break;
+    case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:
+        perfMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
+        break;
+    case SL_ANDROID_PERFORMANCE_NONE:
+        perfMode = ANDROID_PERFORMANCE_MODE_NONE;
+        break;
+    case SL_ANDROID_PERFORMANCE_POWER_SAVING:
+        perfMode = ANDROID_PERFORMANCE_MODE_POWER_SAVING;
+        break;
+    default:
+        SL_LOGE(ERROR_CONFIG_PERF_MODE_UNKNOWN);
+        result = SL_RESULT_PARAMETER_INVALID;
+        break;
+    }
+
+    // performance mode needs to be set before the object is realized
+    // (ar->mAudioRecord is supposed to be NULL until then)
+    if (SL_OBJECT_STATE_UNREALIZED != ar->mObject.mState) {
+        SL_LOGE(ERROR_CONFIG_PERF_MODE_REALIZED);
+        result = SL_RESULT_PRECONDITIONS_VIOLATED;
+    } else {
+        ar->mPerformanceMode = perfMode;
+    }
+
+    return result;
+}
+
+
 SLresult audioRecorder_getPreset(CAudioRecorder* ar, SLuint32* pPreset) {
     SLresult result = SL_RESULT_SUCCESS;
 
@@ -107,6 +146,33 @@
 }
 
 
+//-----------------------------------------------------------------------------
+SLresult audioRecorder_getPerformanceMode(CAudioRecorder* ar, SLuint32 *pMode) {
+    SLresult result = SL_RESULT_SUCCESS;
+
+    switch (ar->mPerformanceMode) {
+    case ANDROID_PERFORMANCE_MODE_LATENCY:
+        *pMode = SL_ANDROID_PERFORMANCE_LATENCY;
+        break;
+    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
+        *pMode = SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
+        break;
+    case ANDROID_PERFORMANCE_MODE_NONE:
+        *pMode = SL_ANDROID_PERFORMANCE_NONE;
+        break;
+    case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
+        *pMode = ANDROID_PERFORMANCE_MODE_POWER_SAVING;
+        break;
+    default:
+        result = SL_RESULT_INTERNAL_ERROR;
+        *pMode = SL_ANDROID_PERFORMANCE_LATENCY;
+        break;
+    }
+
+    return result;
+}
+
+
 void audioRecorder_handleNewPos_lockRecord(CAudioRecorder* ar) {
     //SL_LOGV("received event EVENT_NEW_POS from AudioRecord");
     slRecordCallback callback = NULL;
@@ -353,6 +419,7 @@
         ar->mAudioRecord.clear();
         ar->mCallbackProtector = new android::CallbackProtector();
         ar->mRecordSource = AUDIO_SOURCE_DEFAULT;
+        ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
     } else {
         result = SL_RESULT_CONTENT_UNSUPPORTED;
     }
@@ -378,6 +445,15 @@
             result = audioRecorder_setPreset(ar, *(SLuint32*)pConfigValue);
         }
 
+    } else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {
+
+        // performance mode
+        if (KEY_PERFORMANCE_MODE_PARAMSIZE > valueSize) {
+            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
+            result = SL_RESULT_BUFFER_INSUFFICIENT;
+        } else {
+            result = audioRecorder_setPerformanceMode(ar, *(SLuint32*)pConfigValue);
+        }
     } else {
         SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
         result = SL_RESULT_PARAMETER_INVALID;
@@ -407,6 +483,19 @@
         }
         *pValueSize = KEY_RECORDING_PRESET_PARAMSIZE;
 
+    } else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {
+
+        // performance mode
+        if (NULL == pConfigValue) {
+            result = SL_RESULT_SUCCESS;
+        } else if (KEY_PERFORMANCE_MODE_PARAMSIZE > *pValueSize) {
+            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
+            result = SL_RESULT_BUFFER_INSUFFICIENT;
+        } else {
+            result = audioRecorder_getPerformanceMode(ar, (SLuint32*)pConfigValue);
+        }
+        *pValueSize = KEY_PERFORMANCE_MODE_PARAMSIZE;
+
     } else {
         SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
         result = SL_RESULT_PARAMETER_INVALID;
@@ -416,6 +505,103 @@
 }
 
 
+// Called from android_audioRecorder_realize for a PCM buffer queue recorder before creating the
+// AudioRecord to determine which performance modes are allowed based on effect interfaces present
+static void checkAndSetPerformanceModePre(CAudioRecorder* ar)
+{
+    SLuint32 allowedModes = ANDROID_PERFORMANCE_MODE_ALL;
+    assert(ar->mAndroidObjType == AUDIORECORDER_FROM_MIC_TO_PCM_BUFFERQUEUE);
+
+    // no need to check the buffer queue size, application side
+    // double-buffering (and more) is not a requirement for using fast tracks
+
+    // Check a blacklist of interfaces that are incompatible with fast tracks.
+    // The alternative, to check a whitelist of compatible interfaces, is
+    // more maintainable but is too slow.  As a compromise, in a debug build
+    // we use both methods and warn if they produce different results.
+    // In release builds, we only use the blacklist method.
+    // If a blacklisted interface is added after realization using
+    // DynamicInterfaceManagement::AddInterface,
+    // then this won't be detected but the interface will be ineffective.
+    static const unsigned blacklist[] = {
+        MPH_ANDROIDACOUSTICECHOCANCELLATION,
+        MPH_ANDROIDAUTOMATICGAINCONTROL,
+        MPH_ANDROIDNOISESUPPRESSION,
+        MPH_ANDROIDEFFECT,
+        // FIXME The problem with a blacklist is remembering to add new interfaces here
+    };
+    for (unsigned i = 0; i < sizeof(blacklist)/sizeof(blacklist[0]); ++i) {
+        if (IsInterfaceInitialized(&ar->mObject, blacklist[i])) {
+            //TODO: query effect for EFFECT_FLAG_HW_ACC_xx flag to refine mode
+            allowedModes &=
+                    ~(ANDROID_PERFORMANCE_MODE_LATENCY|ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS);
+            break;
+        }
+    }
+#if LOG_NDEBUG == 0
+    bool blacklistResult = (
+            (allowedModes &
+                (ANDROID_PERFORMANCE_MODE_LATENCY|ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS)) != 0);
+    bool whitelistResult = true;
+    static const unsigned whitelist[] = {
+        MPH_BUFFERQUEUE,
+        MPH_DYNAMICINTERFACEMANAGEMENT,
+        MPH_OBJECT,
+        MPH_RECORD,
+        MPH_ANDROIDCONFIGURATION,
+        MPH_ANDROIDSIMPLEBUFFERQUEUE,
+    };
+    for (unsigned mph = MPH_MIN; mph < MPH_MAX; ++mph) {
+        for (unsigned i = 0; i < sizeof(whitelist)/sizeof(whitelist[0]); ++i) {
+            if (mph == whitelist[i]) {
+                goto compatible;
+            }
+        }
+        if (IsInterfaceInitialized(&ar->mObject, mph)) {
+            whitelistResult = false;
+            break;
+        }
+compatible: ;
+    }
+    if (whitelistResult != blacklistResult) {
+        SL_LOGW("whitelistResult != blacklistResult");
+    }
+#endif
+    if (ar->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY) {
+        if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY) == 0) {
+            ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
+        }
+    }
+    if (ar->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) {
+        if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) == 0) {
+            ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
+        }
+    }
+}
+
+// Called from android_audioRecorder_realize for a PCM buffer queue recorder after creating the
+// AudioRecord to adjust performance mode based on actual input flags
+static void checkAndSetPerformanceModePost(CAudioRecorder* ar)
+{
+    audio_input_flags_t flags = ar->mAudioRecord->getFlags();
+    switch (ar->mPerformanceMode) {
+    case ANDROID_PERFORMANCE_MODE_LATENCY:
+        if ((flags & (AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)) ==
+                (AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)) {
+            break;
+        }
+        ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
+        /* FALL THROUGH */
+    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
+        if ((flags & AUDIO_INPUT_FLAG_FAST) == 0) {
+            ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
+        }
+        break;
+    case ANDROID_PERFORMANCE_MODE_NONE:
+    default:
+        break;
+    }
+}
 //-----------------------------------------------------------------------------
 SLresult android_audioRecorder_realize(CAudioRecorder* ar, SLboolean async) {
     SL_LOGV("android_audioRecorder_realize(%p) entering", ar);
@@ -433,8 +619,22 @@
 
     uint32_t sampleRate = sles_to_android_sampleRate(df_pcm->samplesPerSec);
 
-    // currently nothing analogous to canUseFastTrack() for recording
-    audio_input_flags_t policy = AUDIO_INPUT_FLAG_FAST;
+    checkAndSetPerformanceModePre(ar);
+
+    audio_input_flags_t policy;
+    switch (ar->mPerformanceMode) {
+    case ANDROID_PERFORMANCE_MODE_NONE:
+    case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
+        policy = AUDIO_INPUT_FLAG_NONE;
+        break;
+    case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
+        policy = AUDIO_INPUT_FLAG_FAST;
+        break;
+    case ANDROID_PERFORMANCE_MODE_LATENCY:
+    default:
+        policy = (audio_input_flags_t)(AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW);
+        break;
+    }
 
     SL_LOGV("Audio Record format: %dch(0x%x), %dbit, %dKHz",
             df_pcm->numChannels,
@@ -483,6 +683,9 @@
         ar->mAudioRecord.clear();
     }
 
+    // update performance mode according to actual flags granted to AudioRecord
+    checkAndSetPerformanceModePost(ar);
+
     // If there is a JavaAudioRoutingProxy associated with this recorder, hook it up...
     JNIEnv* j_env = NULL;
     jclass clsAudioRecord = NULL;
diff --git a/src/android/android_defs.h b/src/android/android_defs.h
index 5244592..fcc8377 100644
--- a/src/android/android_defs.h
+++ b/src/android/android_defs.h
@@ -207,4 +207,17 @@
     FdInfo fdi;
 };
 
+
+#define ANDROID_PERFORMANCE_MODE_NONE            ((SLuint32) 0x1 << 0)
+#define ANDROID_PERFORMANCE_MODE_LATENCY         ((SLuint32) 0x1 << 1)
+#define ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS ((SLuint32) 0x1 << 2)
+#define ANDROID_PERFORMANCE_MODE_POWER_SAVING    ((SLuint32) 0x1 << 3)
+
+#define ANDROID_PERFORMANCE_MODE_DEFAULT ANDROID_PERFORMANCE_MODE_LATENCY
+#define ANDROID_PERFORMANCE_MODE_ALL (ANDROID_PERFORMANCE_MODE_LATENCY | \
+                                      ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS | \
+                                      ANDROID_PERFORMANCE_MODE_NONE | \
+                                      ANDROID_PERFORMANCE_MODE_POWER_SAVING)
+
+
 } // namespace android
diff --git a/src/android/android_prompts.h b/src/android/android_prompts.h
index 9feeda1..dfb363d 100644
--- a/src/android/android_prompts.h
+++ b/src/android/android_prompts.h
@@ -69,3 +69,7 @@
         "Configuration error: value size too low to store valid value"
 #define ERROR_CONFIG_NULL_PARAM \
         "Configuration error: invalid NULL parameter"
+#define ERROR_CONFIG_PERF_MODE_UNKNOWN \
+        "Cannot set performance mode: unknown or invalid mode"
+#define ERROR_CONFIG_PERF_MODE_REALIZED \
+        "Cannot set performance mode in: player/recorder realized"
diff --git a/src/classes.h b/src/classes.h
index c9d0520..6de9047 100644
--- a/src/classes.h
+++ b/src/classes.h
@@ -113,6 +113,7 @@
     float mAmplFromDirectLevel;
     /** FIXME whether to call AudioTrack::start() at the next safe opportunity */
     bool mDeferredStart;
+    SLuint32 mPerformanceMode;
 #endif
 } /*CAudioPlayer*/;
 
@@ -156,6 +157,7 @@
     android::sp<android::AudioRecord> mAudioRecord;
     android::sp<android::CallbackProtector> mCallbackProtector;
     audio_source_t mRecordSource;
+    SLuint32 mPerformanceMode;
 #endif
 } /*CAudioRecorder*/;