diff --git a/include/oboe/AudioStreamBase.h b/include/oboe/AudioStreamBase.h index be2d892fd..230aadba0 100644 --- a/include/oboe/AudioStreamBase.h +++ b/include/oboe/AudioStreamBase.h @@ -94,6 +94,14 @@ class AudioStreamBase { return mStreamCallback; } + Usage getUsage() const { return mUsage; } + + ContentType getContentType() const { return mContentType; } + + InputPreset getInputPreset() const { return mInputPreset; } + + SessionId getSessionId() const { return mSessionId; } + protected: AudioStreamCallback *mStreamCallback = nullptr; int32_t mFramesPerCallback = kUnspecified; @@ -106,6 +114,11 @@ class AudioStreamBase { AudioFormat mFormat = AudioFormat::Unspecified; Direction mDirection = Direction::Output; PerformanceMode mPerformanceMode = PerformanceMode::None; + // Added in API 28 + Usage mUsage = Usage::Media; + ContentType mContentType = ContentType::Music; + InputPreset mInputPreset = InputPreset::VoiceRecognition; + SessionId mSessionId = SessionId::None; }; } // namespace oboe diff --git a/include/oboe/AudioStreamBuilder.h b/include/oboe/AudioStreamBuilder.h index 50f21ff1c..948f89ea3 100644 --- a/include/oboe/AudioStreamBuilder.h +++ b/include/oboe/AudioStreamBuilder.h @@ -173,6 +173,90 @@ class AudioStreamBuilder : public AudioStreamBase { return this; } + + /** + * Set the intended use case for the stream. + * + * The system will use this information to optimize the behavior of the stream. + * This could, for example, affect how volume and focus is handled for the stream. + * + * The default, if you do not call this function, is Usage::Media. + * + * Added in API level 28. + * + * @param usage the desired usage, eg. Usage::Game + */ + AudioStreamBuilder *setUsage(Usage usage) { + mUsage = usage; + return this; + } + + /** + * Set the type of audio data that the stream will carry. + * + * The system will use this information to optimize the behavior of the stream. + * This could, for example, affect whether a stream is paused when a notification occurs. + * + * The default, if you do not call this function, is ContentType::Music. + * + * Added in API level 28. + * + * @param contentType the type of audio data, eg. ContentType::Speech + */ + AudioStreamBuilder *setContentType(ContentType contentType) { + mContentType = contentType; + return this; + } + + /** + * Set the input (capture) preset for the stream. + * + * The system will use this information to optimize the behavior of the stream. + * This could, for example, affect which microphones are used and how the + * recorded data is processed. + * + * The default, if you do not call this function, is InputPreset::VoiceRecognition. + * That is because VoiceRecognition is the preset with the lowest latency + * on many platforms. + * + * Added in API level 28. + * + * @param inputPreset the desired configuration for recording + */ + AudioStreamBuilder *setInputPreset(InputPreset inputPreset) { + mInputPreset = inputPreset; + return this; + } + + /** Set the requested session ID. + * + * The session ID can be used to associate a stream with effects processors. + * The effects are controlled using the Android AudioEffect Java API. + * + * The default, if you do not call this function, is SessionId::None. + * + * If set to SessionId::Allocate then a session ID will be allocated + * when the stream is opened. + * + * The allocated session ID can be obtained by calling AudioStream::getSessionId() + * and then used with this function when opening another stream. + * This allows effects to be shared between streams. + * + * Session IDs from Oboe can be used the Android Java APIs and vice versa. + * So a session ID from an Oboe stream can be passed to Java + * and effects applied using the Java AudioEffect API. + * + * Allocated session IDs will always be positive and nonzero. + * + * Added in API level 28. + * + * @param sessionId an allocated sessionID or SessionId::Allocate + */ + AudioStreamBuilder *setSessionId(SessionId sessionId) { + mSessionId = sessionId; + return this; + } + /** * Request an audio device identified device using an ID. * On Android, for example, the ID could be obtained from the Java AudioManager. diff --git a/include/oboe/Definitions.h b/include/oboe/Definitions.h index 4b02b3c30..f0c825878 100644 --- a/include/oboe/Definitions.h +++ b/include/oboe/Definitions.h @@ -143,6 +143,175 @@ namespace oboe { AAudio }; + /** + * The Usage attribute expresses "why" you are playing a sound, what is this sound used for. + * This information is used by certain platforms or routing policies + * to make more refined volume or routing decisions. + * + * Note that these match the equivalent values in AudioAttributes in the Android Java API. + * + * Added in API level 28. + */ + enum class Usage : aaudio_usage_t { + /** + * Use this for streaming media, music performance, video, podcasts, etcetera. + */ + Media = AAUDIO_USAGE_MEDIA, + + /** + * Use this for voice over IP, telephony, etcetera. + */ + VoiceCommunication = AAUDIO_USAGE_VOICE_COMMUNICATION, + + /** + * Use this for sounds associated with telephony such as busy tones, DTMF, etcetera. + */ + VoiceCommunicationSignalling = AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, + + /** + * Use this to demand the users attention. + */ + Alarm = AAUDIO_USAGE_ALARM, + + /** + * Use this for notifying the user when a message has arrived or some + * other background event has occured. + */ + Notification = AAUDIO_USAGE_NOTIFICATION, + + /** + * Use this when the phone rings. + */ + NotificationRingtone = AAUDIO_USAGE_NOTIFICATION_RINGTONE, + + /** + * Use this to attract the users attention when, for example, the battery is low. + */ + NotificationEvent = AAUDIO_USAGE_NOTIFICATION_EVENT, + + /** + * Use this for screen readers, etcetera. + */ + AssistanceAccessibility = AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY, + + /** + * Use this for driving or navigation directions. + */ + AssistanceNavigationGuidance = AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, + + /** + * Use this for user interface sounds, beeps, etcetera. + */ + AssistanceSonification = AAUDIO_USAGE_ASSISTANCE_SONIFICATION, + + /** + * Use this for game audio and sound effects. + */ + Game = AAUDIO_USAGE_GAME, + + /** + * Use this for audio responses to user queries, audio instructions or help utterances. + */ + Assistant = AAUDIO_USAGE_ASSISTANT, + }; + + + /** + * The CONTENT_TYPE attribute describes "what" you are playing. + * It expresses the general category of the content. This information is optional. + * But in case it is known (for instance {@link #AAUDIO_CONTENT_TYPE_MOVIE} for a + * movie streaming service or {@link #AAUDIO_CONTENT_TYPE_SPEECH} for + * an audio book application) this information might be used by the audio framework to + * enforce audio focus. + * + * Note that these match the equivalent values in AudioAttributes in the Android Java API. + * + * Added in API level 28. + */ + enum ContentType : aaudio_content_type_t { + + /** + * Use this for spoken voice, audio books, etcetera. + */ + Speech = AAUDIO_CONTENT_TYPE_SPEECH, + + /** + * Use this for pre-recorded or live music. + */ + Music = AAUDIO_CONTENT_TYPE_MUSIC, + + /** + * Use this for a movie or video soundtrack. + */ + Movie = AAUDIO_CONTENT_TYPE_MOVIE, + + /** + * Use this for sound is designed to accompany a user action, + * such as a click or beep sound made when the user presses a button. + */ + Sonification = AAUDIO_CONTENT_TYPE_SONIFICATION, + }; + + /** + * Defines the audio source. + * An audio source defines both a default physical source of audio signal, and a recording + * configuration. + * + * Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API. + * + * Added in API level 28. + */ + enum InputPreset : aaudio_input_preset_t { + /** + * Use this preset when other presets do not apply. + */ + Generic = AAUDIO_INPUT_PRESET_GENERIC, + + /** + * Use this preset when recording video. + */ + Camcorder = AAUDIO_INPUT_PRESET_CAMCORDER, + + /** + * Use this preset when doing speech recognition. + */ + VoiceRecognition = AAUDIO_INPUT_PRESET_VOICE_RECOGNITION, + + /** + * Use this preset when doing telephony or voice messaging. + */ + VoiceCommunication = AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION, + + /** + * Use this preset to obtain an input with no effects. + * Note that this input will not have automatic gain control + * so the recorded volume may be very low. + */ + Unprocessed = AAUDIO_INPUT_PRESET_UNPROCESSED, + }; + + enum SessionId { + /** + * Do not allocate a session ID. + * Effects cannot be used with this stream. + * Default. + * + * Added in API level 28. + */ + None = AAUDIO_SESSION_ID_NONE, + + /** + * Allocate a session ID that can be used to attach and control + * effects using the Java AudioEffects API. + * Note that the use of this flag may result in higher latency. + * + * Note that this matches the value of AudioManager.AUDIO_SESSION_ID_GENERATE. + * + * Added in API level 28. + */ + Allocate = AAUDIO_SESSION_ID_ALLOCATE, + }; + } // namespace oboe #endif // OBOE_DEFINITIONS_H diff --git a/src/aaudio/AAudioLoader.cpp b/src/aaudio/AAudioLoader.cpp index aeaf6f8d5..6cdfad8b5 100644 --- a/src/aaudio/AAudioLoader.cpp +++ b/src/aaudio/AAudioLoader.cpp @@ -63,9 +63,14 @@ int AAudioLoader::open() { builder_setFormat = load_V_PBI("AAudioStreamBuilder_setFormat"); builder_setFramesPerDataCallback = load_V_PBI("AAudioStreamBuilder_setFramesPerDataCallback"); builder_setSharingMode = load_V_PBI("AAudioStreamBuilder_setSharingMode"); - builder_setPerformanceMode = load_V_PBI("AAudioStreamBuilder_setPerformanceMode"); + builder_setPerformanceMode = load_V_PBI("AAudioStreamBuilder_setPerformanceMode"); builder_setSampleRate = load_V_PBI("AAudioStreamBuilder_setSampleRate"); + builder_setUsage = load_V_PBI("AAudioStreamBuilder_setUsage"); + builder_setContentType = load_V_PBI("AAudioStreamBuilder_setContentType"); + builder_setInputPreset = load_V_PBI("AAudioStreamBuilder_setinputPreset"); + builder_setSessionId = load_V_PBI("AAudioStreamBuilder_setSessionId"); + builder_delete = load_I_PB("AAudioStreamBuilder_delete"); stream_getFormat = (aaudio_format_t (*)(AAudioStream *stream)) @@ -138,6 +143,11 @@ int AAudioLoader::open() { convertResultToText = load_PC_I("AAudio_convertResultToText"); convertStreamStateToText = load_PC_I("AAudio_convertStreamStateToText"); + stream_getUsage = load_I_PS("AAudioStream_getUsage"); + stream_getContentType = load_I_PS("AAudioStream_getContentType"); + stream_getInputPreset = load_I_PS("AAudioStream_getInputPreset"); + stream_getSessionId = load_I_PS("AAudioStream_getSessionId"); + return 0; } diff --git a/src/aaudio/AAudioLoader.h b/src/aaudio/AAudioLoader.h index a4491a26a..646a383a8 100644 --- a/src/aaudio/AAudioLoader.h +++ b/src/aaudio/AAudioLoader.h @@ -86,6 +86,11 @@ class AAudioLoader { signature_V_PBI builder_setSampleRate; signature_V_PBI builder_setSharingMode; + signature_V_PBI builder_setUsage; + signature_V_PBI builder_setContentType; + signature_V_PBI builder_setInputPreset; + signature_V_PBI builder_setSessionId; + void (*builder_setDataCallback)(AAudioStreamBuilder *builder, AAudioStream_dataCallback callback, void *userData); @@ -144,7 +149,10 @@ class AAudioLoader { signature_PC_I convertResultToText; signature_PC_I convertStreamStateToText; - // TODO add any missing AAudio functions. + signature_I_PS stream_getUsage; + signature_I_PS stream_getContentType; + signature_I_PS stream_getInputPreset; + signature_I_PS stream_getSessionId; private: AAudioLoader() {} diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp index 5b59c2bc2..36a497584 100644 --- a/src/aaudio/AudioStreamAAudio.cpp +++ b/src/aaudio/AudioStreamAAudio.cpp @@ -125,6 +125,29 @@ Result AudioStreamAAudio::open() { mLibLoader->builder_setPerformanceMode(aaudioBuilder, static_cast(mPerformanceMode)); + // These were added in P so we have to check for the function pointer. + if (mLibLoader->builder_setUsage != nullptr) { + mLibLoader->builder_setUsage(aaudioBuilder, + static_cast(mUsage)); + } + + if (mLibLoader->builder_setContentType != nullptr) { + mLibLoader->builder_setContentType(aaudioBuilder, + static_cast(mContentType)); + } + + if (mLibLoader->builder_setInputPreset != nullptr) { + mLibLoader->builder_setInputPreset(aaudioBuilder, + static_cast(mInputPreset)); + } + + if (mLibLoader->builder_setSessionId != nullptr) { + mLibLoader->builder_setSessionId(aaudioBuilder, + static_cast(mSessionId)); + } else { + mSessionId = SessionId::None; + } + // TODO get more parameters from the builder? if (mStreamCallback != nullptr) { @@ -133,6 +156,7 @@ Result AudioStreamAAudio::open() { } mLibLoader->builder_setErrorCallback(aaudioBuilder, oboe_aaudio_error_callback_proc, this); + // ============= OPEN THE STREAM ================ { AAudioStream *stream = nullptr; result = static_cast(mLibLoader->builder_openStream(aaudioBuilder, &stream)); @@ -155,6 +179,20 @@ Result AudioStreamAAudio::open() { mLibLoader->stream_getPerformanceMode(mAAudioStream)); mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream); + // These were added in P so we have to check for the function pointer. + if (mLibLoader->stream_getUsage != nullptr) { + mUsage = static_cast(mLibLoader->stream_getUsage(mAAudioStream)); + } + if (mLibLoader->stream_getContentType != nullptr) { + mContentType = static_cast(mLibLoader->stream_getContentType(mAAudioStream)); + } + if (mLibLoader->stream_getInputPreset != nullptr) { + mInputPreset = static_cast(mLibLoader->stream_getInputPreset(mAAudioStream)); + } + if (mLibLoader->stream_getSessionId != nullptr) { + mSessionId = static_cast(mLibLoader->stream_getSessionId(mAAudioStream)); + } + LOGD("AudioStreamAAudio.open() app format = %d", (int) mFormat); LOGD("AudioStreamAAudio.open() native format = %d", (int) mNativeFormat); LOGD("AudioStreamAAudio.open() sample rate = %d", (int) mSampleRate); diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp index accc86ce6..1070b66e3 100644 --- a/src/opensles/AudioInputStreamOpenSLES.cpp +++ b/src/opensles/AudioInputStreamOpenSLES.cpp @@ -26,6 +26,30 @@ using namespace oboe; +static SLuint32 OpenSLES_convertInputPreset(InputPreset oboePreset) { + SLuint32 openslPreset = SL_ANDROID_RECORDING_PRESET_NONE; + switch(oboePreset) { + case InputPreset::Generic: + openslPreset = SL_ANDROID_RECORDING_PRESET_GENERIC; + break; + case InputPreset::Camcorder: + openslPreset = SL_ANDROID_RECORDING_PRESET_CAMCORDER; + break; + case InputPreset::VoiceRecognition: + openslPreset = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + break; + case InputPreset::VoiceCommunication: + openslPreset = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION; + break; + case InputPreset::Unprocessed: + openslPreset = SL_ANDROID_RECORDING_PRESET_UNPROCESSED; + break; + default: + break; + } + return openslPreset; +} + AudioInputStreamOpenSLES::AudioInputStreamOpenSLES(const AudioStreamBuilder &builder) : AudioStreamOpenSLES(builder) { } @@ -57,7 +81,7 @@ int AudioInputStreamOpenSLES::chanCountToChanMask(int channelCount) { Result AudioInputStreamOpenSLES::open() { Result oboeResult = AudioStreamOpenSLES::open(); - if (Result::OK != oboeResult) return oboeResult; + if (Result::OK != oboeResult) return oboeResult; SLuint32 bitsPerSample = getBytesPerSample() * kBitsPerByte; @@ -108,18 +132,31 @@ Result AudioInputStreamOpenSLES::open() { goto error; } - // Configure the voice recognition preset, which has no - // signal processing, for lower latency. - SLAndroidConfigurationItf inputConfig; + // Configure the stream. + SLAndroidConfigurationItf configItf; result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_ANDROIDCONFIGURATION, - &inputConfig); + &configItf); if (SL_RESULT_SUCCESS == result) { - SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; - (*inputConfig)->SetConfiguration(inputConfig, + SLuint32 presetValue = OpenSLES_convertInputPreset(getInputPreset()); + result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_RECORDING_PRESET, &presetValue, sizeof(SLuint32)); + if (SL_RESULT_SUCCESS != result + && presetValue != SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION) { + presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + mInputPreset = InputPreset::VoiceRecognition; + (*configItf)->SetConfiguration(configItf, + SL_ANDROID_KEY_RECORDING_PRESET, + &presetValue, + sizeof(SLuint32)); + } + + result = configurePerformanceMode(configItf); + if (SL_RESULT_SUCCESS != result) { + goto error; + } } result = (*mObjectInterface)->Realize(mObjectInterface, SL_BOOLEAN_FALSE); diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp index 48f5358b8..5da7777a7 100644 --- a/src/opensles/AudioOutputStreamOpenSLES.cpp +++ b/src/opensles/AudioOutputStreamOpenSLES.cpp @@ -124,6 +124,18 @@ Result AudioOutputStreamOpenSLES::open() { goto error; } + // Configure the stream. + SLAndroidConfigurationItf configItf; + result = (*mObjectInterface)->GetInterface(mObjectInterface, + SL_IID_ANDROIDCONFIGURATION, + &configItf); + if (SL_RESULT_SUCCESS == result) { + result = configurePerformanceMode(configItf); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + } + result = (*mObjectInterface)->Realize(mObjectInterface, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { LOGE("Realize player object result:%s", getSLErrStr(result)); diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp index ddea87f6c..1f8c10117 100644 --- a/src/opensles/AudioStreamOpenSLES.cpp +++ b/src/opensles/AudioStreamOpenSLES.cpp @@ -35,6 +35,7 @@ using namespace oboe; + AudioStreamOpenSLES::AudioStreamOpenSLES(const AudioStreamBuilder &builder) : AudioStreamBuffered(builder) { mSimpleBufferQueueInterface = NULL; @@ -116,6 +117,34 @@ Result AudioStreamOpenSLES::open() { return Result::OK; } +SLuint32 AudioStreamOpenSLES::convertPerformanceMode(PerformanceMode oboeMode) const { + SLuint32 openslMode = SL_ANDROID_PERFORMANCE_NONE; + switch(oboeMode) { + case PerformanceMode::None: + openslMode = SL_ANDROID_PERFORMANCE_NONE; + break; + case PerformanceMode::LowLatency: + openslMode = (getSessionId() != SessionId::None) ? SL_ANDROID_PERFORMANCE_LATENCY : SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS; + break; + case PerformanceMode::PowerSaving: + openslMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; + break; + default: + break; + } + return openslMode; +} + +SLresult AudioStreamOpenSLES::configurePerformanceMode(SLAndroidConfigurationItf configItf) { + SLuint32 performanceMode = convertPerformanceMode(getPerformanceMode()); + SLresult result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE, + &performanceMode, sizeof(performanceMode)); + if (SL_RESULT_SUCCESS != result) { + LOGE("SetConfiguration(PERFORMANCE_MODE, %u) returned %d", performanceMode, result); + } + return result; +} + Result AudioStreamOpenSLES::close() { if (mObjectInterface != nullptr) { (*mObjectInterface)->Destroy(mObjectInterface); diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h index f70ab9af7..8e89f2a74 100644 --- a/src/opensles/AudioStreamOpenSLES.h +++ b/src/opensles/AudioStreamOpenSLES.h @@ -80,6 +80,10 @@ class AudioStreamOpenSLES : public AudioStreamBuffered { SLresult enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq); + SLresult configurePerformanceMode(SLAndroidConfigurationItf configItf); + + SLuint32 convertPerformanceMode(PerformanceMode oboeMode) const; + /** * Internal use only. * Use this instead of directly setting the internal state variable. diff --git a/src/opensles/OpenSLESUtilities.h b/src/opensles/OpenSLESUtilities.h index 20f5498e5..941a527ef 100644 --- a/src/opensles/OpenSLESUtilities.h +++ b/src/opensles/OpenSLESUtilities.h @@ -38,7 +38,8 @@ SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat(SLDataFormat_PCM format SLuint32 representation); -SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format); + SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format); + } // namespace oboe