git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,172 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AAUDIO_EXTENSIONS_H
#define OBOE_AAUDIO_EXTENSIONS_H
#include <dlfcn.h>
#include <stdint.h>
#include <sys/system_properties.h>
#include "common/OboeDebug.h"
#include "oboe/Oboe.h"
#include "AAudioLoader.h"
namespace oboe {
#define LIB_AAUDIO_NAME "libaaudio.so"
#define FUNCTION_IS_MMAP "AAudioStream_isMMapUsed"
#define FUNCTION_SET_MMAP_POLICY "AAudio_setMMapPolicy"
#define FUNCTION_GET_MMAP_POLICY "AAudio_getMMapPolicy"
#define AAUDIO_ERROR_UNAVAILABLE static_cast<aaudio_result_t>(Result::ErrorUnavailable)
typedef struct AAudioStreamStruct AAudioStream;
/**
* Call some AAudio test routines that are not part of the normal API.
*/
class AAudioExtensions {
public:
AAudioExtensions() {
int32_t policy = getIntegerProperty("aaudio.mmap_policy", 0);
mMMapSupported = isPolicyEnabled(policy);
policy = getIntegerProperty("aaudio.mmap_exclusive_policy", 0);
mMMapExclusiveSupported = isPolicyEnabled(policy);
}
static bool isPolicyEnabled(int32_t policy) {
return (policy == AAUDIO_POLICY_AUTO || policy == AAUDIO_POLICY_ALWAYS);
}
static AAudioExtensions &getInstance() {
static AAudioExtensions instance;
return instance;
}
bool isMMapUsed(oboe::AudioStream *oboeStream) {
AAudioStream *aaudioStream = (AAudioStream *) oboeStream->getUnderlyingStream();
return isMMapUsed(aaudioStream);
}
bool isMMapUsed(AAudioStream *aaudioStream) {
if (loadSymbols()) return false;
if (mAAudioStream_isMMap == nullptr) return false;
return mAAudioStream_isMMap(aaudioStream);
}
/**
* Controls whether the MMAP data path can be selected when opening a stream.
* It has no effect after the stream has been opened.
* It only affects the application that calls it. Other apps are not affected.
*
* @param enabled
* @return 0 or a negative error code
*/
int32_t setMMapEnabled(bool enabled) {
if (loadSymbols()) return AAUDIO_ERROR_UNAVAILABLE;
if (mAAudio_setMMapPolicy == nullptr) return false;
return mAAudio_setMMapPolicy(enabled ? AAUDIO_POLICY_AUTO : AAUDIO_POLICY_NEVER);
}
bool isMMapEnabled() {
if (loadSymbols()) return false;
if (mAAudio_getMMapPolicy == nullptr) return false;
int32_t policy = mAAudio_getMMapPolicy();
return isPolicyEnabled(policy);
}
bool isMMapSupported() {
return mMMapSupported;
}
bool isMMapExclusiveSupported() {
return mMMapExclusiveSupported;
}
private:
enum {
AAUDIO_POLICY_NEVER = 1,
AAUDIO_POLICY_AUTO,
AAUDIO_POLICY_ALWAYS
};
typedef int32_t aaudio_policy_t;
int getIntegerProperty(const char *name, int defaultValue) {
int result = defaultValue;
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = atoi(valueText);
}
return result;
}
/**
* Load the function pointers.
* This can be called multiple times.
* It should only be called from one thread.
*
* @return 0 if successful or negative error.
*/
aaudio_result_t loadSymbols() {
if (mAAudio_getMMapPolicy != nullptr) {
return 0;
}
void *libHandle = AAudioLoader::getInstance()->getLibHandle();
if (libHandle == nullptr) {
LOGI("%s() could not find " LIB_AAUDIO_NAME, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudioStream_isMMap = (bool (*)(AAudioStream *stream))
dlsym(libHandle, FUNCTION_IS_MMAP);
if (mAAudioStream_isMMap == nullptr) {
LOGI("%s() could not find " FUNCTION_IS_MMAP, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudio_setMMapPolicy = (int32_t (*)(aaudio_policy_t policy))
dlsym(libHandle, FUNCTION_SET_MMAP_POLICY);
if (mAAudio_setMMapPolicy == nullptr) {
LOGI("%s() could not find " FUNCTION_SET_MMAP_POLICY, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudio_getMMapPolicy = (aaudio_policy_t (*)())
dlsym(libHandle, FUNCTION_GET_MMAP_POLICY);
if (mAAudio_getMMapPolicy == nullptr) {
LOGI("%s() could not find " FUNCTION_GET_MMAP_POLICY, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
return 0;
}
bool mMMapSupported = false;
bool mMMapExclusiveSupported = false;
bool (*mAAudioStream_isMMap)(AAudioStream *stream) = nullptr;
int32_t (*mAAudio_setMMapPolicy)(aaudio_policy_t policy) = nullptr;
aaudio_policy_t (*mAAudio_getMMapPolicy)() = nullptr;
};
} // namespace oboe
#endif //OBOE_AAUDIO_EXTENSIONS_H

View File

@ -0,0 +1,366 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <dlfcn.h>
#include <oboe/Utilities.h>
#include "common/OboeDebug.h"
#include "AAudioLoader.h"
#define LIB_AAUDIO_NAME "libaaudio.so"
namespace oboe {
AAudioLoader::~AAudioLoader() {
// Issue 360: thread_local variables with non-trivial destructors
// will cause segfaults if the containing library is dlclose()ed on
// devices running M or newer, or devices before M when using a static STL.
// The simple workaround is to not call dlclose.
// https://github.com/android/ndk/wiki/Changelog-r22#known-issues
//
// The libaaudio and libaaudioclient do not use thread_local.
// But, to be safe, we should avoid dlclose() if possible.
// Because AAudioLoader is a static Singleton, we can safely skip
// calling dlclose() without causing a resource leak.
LOGI("%s() dlclose(%s) not called, OK", __func__, LIB_AAUDIO_NAME);
}
AAudioLoader* AAudioLoader::getInstance() {
static AAudioLoader instance;
return &instance;
}
int AAudioLoader::open() {
if (mLibHandle != nullptr) {
return 0;
}
// Use RTLD_NOW to avoid the unpredictable behavior that RTLD_LAZY can cause.
// Also resolving all the links now will prevent a run-time penalty later.
mLibHandle = dlopen(LIB_AAUDIO_NAME, RTLD_NOW);
if (mLibHandle == nullptr) {
LOGI("AAudioLoader::open() could not find " LIB_AAUDIO_NAME);
return -1; // TODO review return code
} else {
LOGD("AAudioLoader(): dlopen(%s) returned %p", LIB_AAUDIO_NAME, mLibHandle);
}
// Load all the function pointers.
createStreamBuilder = load_I_PPB("AAudio_createStreamBuilder");
builder_openStream = load_I_PBPPS("AAudioStreamBuilder_openStream");
builder_setChannelCount = load_V_PBI("AAudioStreamBuilder_setChannelCount");
if (builder_setChannelCount == nullptr) {
// Use old deprecated alias if needed.
builder_setChannelCount = load_V_PBI("AAudioStreamBuilder_setSamplesPerFrame");
}
builder_setBufferCapacityInFrames = load_V_PBI("AAudioStreamBuilder_setBufferCapacityInFrames");
builder_setDeviceId = load_V_PBI("AAudioStreamBuilder_setDeviceId");
builder_setDirection = load_V_PBI("AAudioStreamBuilder_setDirection");
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_setSampleRate = load_V_PBI("AAudioStreamBuilder_setSampleRate");
if (getSdkVersion() >= __ANDROID_API_P__){
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");
}
if (getSdkVersion() >= __ANDROID_API_S__){
builder_setPackageName = load_V_PBCPH("AAudioStreamBuilder_setPackageName");
builder_setAttributionTag = load_V_PBCPH("AAudioStreamBuilder_setAttributionTag");
}
builder_delete = load_I_PB("AAudioStreamBuilder_delete");
builder_setDataCallback = load_V_PBPDPV("AAudioStreamBuilder_setDataCallback");
builder_setErrorCallback = load_V_PBPEPV("AAudioStreamBuilder_setErrorCallback");
stream_read = load_I_PSPVIL("AAudioStream_read");
stream_write = load_I_PSCPVIL("AAudioStream_write");
stream_waitForStateChange = load_I_PSTPTL("AAudioStream_waitForStateChange");
stream_getTimestamp = load_I_PSKPLPL("AAudioStream_getTimestamp");
stream_getChannelCount = load_I_PS("AAudioStream_getChannelCount");
if (stream_getChannelCount == nullptr) {
// Use old alias if needed.
stream_getChannelCount = load_I_PS("AAudioStream_getSamplesPerFrame");
}
stream_close = load_I_PS("AAudioStream_close");
stream_getBufferSize = load_I_PS("AAudioStream_getBufferSizeInFrames");
stream_getDeviceId = load_I_PS("AAudioStream_getDeviceId");
stream_getBufferCapacity = load_I_PS("AAudioStream_getBufferCapacityInFrames");
stream_getFormat = load_F_PS("AAudioStream_getFormat");
stream_getFramesPerBurst = load_I_PS("AAudioStream_getFramesPerBurst");
stream_getFramesRead = load_L_PS("AAudioStream_getFramesRead");
stream_getFramesWritten = load_L_PS("AAudioStream_getFramesWritten");
stream_getPerformanceMode = load_I_PS("AAudioStream_getPerformanceMode");
stream_getSampleRate = load_I_PS("AAudioStream_getSampleRate");
stream_getSharingMode = load_I_PS("AAudioStream_getSharingMode");
stream_getState = load_I_PS("AAudioStream_getState");
stream_getXRunCount = load_I_PS("AAudioStream_getXRunCount");
stream_requestStart = load_I_PS("AAudioStream_requestStart");
stream_requestPause = load_I_PS("AAudioStream_requestPause");
stream_requestFlush = load_I_PS("AAudioStream_requestFlush");
stream_requestStop = load_I_PS("AAudioStream_requestStop");
stream_setBufferSize = load_I_PSI("AAudioStream_setBufferSizeInFrames");
convertResultToText = load_CPH_I("AAudio_convertResultToText");
if (getSdkVersion() >= __ANDROID_API_P__){
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;
}
static void AAudioLoader_check(void *proc, const char *functionName) {
if (proc == nullptr) {
LOGW("AAudioLoader could not find %s", functionName);
}
}
AAudioLoader::signature_I_PPB AAudioLoader::load_I_PPB(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PPB>(proc);
}
AAudioLoader::signature_CPH_I AAudioLoader::load_CPH_I(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_CPH_I>(proc);
}
AAudioLoader::signature_V_PBI AAudioLoader::load_V_PBI(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBI>(proc);
}
AAudioLoader::signature_V_PBCPH AAudioLoader::load_V_PBCPH(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBCPH>(proc);
}
AAudioLoader::signature_V_PBPDPV AAudioLoader::load_V_PBPDPV(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBPDPV>(proc);
}
AAudioLoader::signature_V_PBPEPV AAudioLoader::load_V_PBPEPV(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBPEPV>(proc);
}
AAudioLoader::signature_I_PSI AAudioLoader::load_I_PSI(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSI>(proc);
}
AAudioLoader::signature_I_PS AAudioLoader::load_I_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PS>(proc);
}
AAudioLoader::signature_L_PS AAudioLoader::load_L_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_L_PS>(proc);
}
AAudioLoader::signature_F_PS AAudioLoader::load_F_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_F_PS>(proc);
}
AAudioLoader::signature_B_PS AAudioLoader::load_B_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_B_PS>(proc);
}
AAudioLoader::signature_I_PB AAudioLoader::load_I_PB(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PB>(proc);
}
AAudioLoader::signature_I_PBPPS AAudioLoader::load_I_PBPPS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PBPPS>(proc);
}
AAudioLoader::signature_I_PSCPVIL AAudioLoader::load_I_PSCPVIL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSCPVIL>(proc);
}
AAudioLoader::signature_I_PSPVIL AAudioLoader::load_I_PSPVIL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSPVIL>(proc);
}
AAudioLoader::signature_I_PSTPTL AAudioLoader::load_I_PSTPTL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSTPTL>(proc);
}
AAudioLoader::signature_I_PSKPLPL AAudioLoader::load_I_PSKPLPL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSKPLPL>(proc);
}
// Ensure that all AAudio primitive data types are int32_t
#define ASSERT_INT32(type) static_assert(std::is_same<int32_t, type>::value, \
#type" must be int32_t")
#define ERRMSG "Oboe constants must match AAudio constants."
// These asserts help verify that the Oboe definitions match the equivalent AAudio definitions.
// This code is in this .cpp file so it only gets tested once.
#ifdef AAUDIO_AAUDIO_H
ASSERT_INT32(aaudio_stream_state_t);
ASSERT_INT32(aaudio_direction_t);
ASSERT_INT32(aaudio_format_t);
ASSERT_INT32(aaudio_data_callback_result_t);
ASSERT_INT32(aaudio_result_t);
ASSERT_INT32(aaudio_sharing_mode_t);
ASSERT_INT32(aaudio_performance_mode_t);
static_assert((int32_t)StreamState::Uninitialized == AAUDIO_STREAM_STATE_UNINITIALIZED, ERRMSG);
static_assert((int32_t)StreamState::Unknown == AAUDIO_STREAM_STATE_UNKNOWN, ERRMSG);
static_assert((int32_t)StreamState::Open == AAUDIO_STREAM_STATE_OPEN, ERRMSG);
static_assert((int32_t)StreamState::Starting == AAUDIO_STREAM_STATE_STARTING, ERRMSG);
static_assert((int32_t)StreamState::Started == AAUDIO_STREAM_STATE_STARTED, ERRMSG);
static_assert((int32_t)StreamState::Pausing == AAUDIO_STREAM_STATE_PAUSING, ERRMSG);
static_assert((int32_t)StreamState::Paused == AAUDIO_STREAM_STATE_PAUSED, ERRMSG);
static_assert((int32_t)StreamState::Flushing == AAUDIO_STREAM_STATE_FLUSHING, ERRMSG);
static_assert((int32_t)StreamState::Flushed == AAUDIO_STREAM_STATE_FLUSHED, ERRMSG);
static_assert((int32_t)StreamState::Stopping == AAUDIO_STREAM_STATE_STOPPING, ERRMSG);
static_assert((int32_t)StreamState::Stopped == AAUDIO_STREAM_STATE_STOPPED, ERRMSG);
static_assert((int32_t)StreamState::Closing == AAUDIO_STREAM_STATE_CLOSING, ERRMSG);
static_assert((int32_t)StreamState::Closed == AAUDIO_STREAM_STATE_CLOSED, ERRMSG);
static_assert((int32_t)StreamState::Disconnected == AAUDIO_STREAM_STATE_DISCONNECTED, ERRMSG);
static_assert((int32_t)Direction::Output == AAUDIO_DIRECTION_OUTPUT, ERRMSG);
static_assert((int32_t)Direction::Input == AAUDIO_DIRECTION_INPUT, ERRMSG);
static_assert((int32_t)AudioFormat::Invalid == AAUDIO_FORMAT_INVALID, ERRMSG);
static_assert((int32_t)AudioFormat::Unspecified == AAUDIO_FORMAT_UNSPECIFIED, ERRMSG);
static_assert((int32_t)AudioFormat::I16 == AAUDIO_FORMAT_PCM_I16, ERRMSG);
static_assert((int32_t)AudioFormat::Float == AAUDIO_FORMAT_PCM_FLOAT, ERRMSG);
static_assert((int32_t)DataCallbackResult::Continue == AAUDIO_CALLBACK_RESULT_CONTINUE, ERRMSG);
static_assert((int32_t)DataCallbackResult::Stop == AAUDIO_CALLBACK_RESULT_STOP, ERRMSG);
static_assert((int32_t)Result::OK == AAUDIO_OK, ERRMSG);
static_assert((int32_t)Result::ErrorBase == AAUDIO_ERROR_BASE, ERRMSG);
static_assert((int32_t)Result::ErrorDisconnected == AAUDIO_ERROR_DISCONNECTED, ERRMSG);
static_assert((int32_t)Result::ErrorIllegalArgument == AAUDIO_ERROR_ILLEGAL_ARGUMENT, ERRMSG);
static_assert((int32_t)Result::ErrorInternal == AAUDIO_ERROR_INTERNAL, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidState == AAUDIO_ERROR_INVALID_STATE, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidHandle == AAUDIO_ERROR_INVALID_HANDLE, ERRMSG);
static_assert((int32_t)Result::ErrorUnimplemented == AAUDIO_ERROR_UNIMPLEMENTED, ERRMSG);
static_assert((int32_t)Result::ErrorUnavailable == AAUDIO_ERROR_UNAVAILABLE, ERRMSG);
static_assert((int32_t)Result::ErrorNoFreeHandles == AAUDIO_ERROR_NO_FREE_HANDLES, ERRMSG);
static_assert((int32_t)Result::ErrorNoMemory == AAUDIO_ERROR_NO_MEMORY, ERRMSG);
static_assert((int32_t)Result::ErrorNull == AAUDIO_ERROR_NULL, ERRMSG);
static_assert((int32_t)Result::ErrorTimeout == AAUDIO_ERROR_TIMEOUT, ERRMSG);
static_assert((int32_t)Result::ErrorWouldBlock == AAUDIO_ERROR_WOULD_BLOCK, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidFormat == AAUDIO_ERROR_INVALID_FORMAT, ERRMSG);
static_assert((int32_t)Result::ErrorOutOfRange == AAUDIO_ERROR_OUT_OF_RANGE, ERRMSG);
static_assert((int32_t)Result::ErrorNoService == AAUDIO_ERROR_NO_SERVICE, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidRate == AAUDIO_ERROR_INVALID_RATE, ERRMSG);
static_assert((int32_t)SharingMode::Exclusive == AAUDIO_SHARING_MODE_EXCLUSIVE, ERRMSG);
static_assert((int32_t)SharingMode::Shared == AAUDIO_SHARING_MODE_SHARED, ERRMSG);
static_assert((int32_t)PerformanceMode::None == AAUDIO_PERFORMANCE_MODE_NONE, ERRMSG);
static_assert((int32_t)PerformanceMode::PowerSaving
== AAUDIO_PERFORMANCE_MODE_POWER_SAVING, ERRMSG);
static_assert((int32_t)PerformanceMode::LowLatency
== AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, ERRMSG);
// The aaudio_ usage, content and input_preset types were added in NDK 17,
// which is the first version to support Android Pie (API 28).
#if __NDK_MAJOR__ >= 17
ASSERT_INT32(aaudio_usage_t);
ASSERT_INT32(aaudio_content_type_t);
ASSERT_INT32(aaudio_input_preset_t);
static_assert((int32_t)Usage::Media == AAUDIO_USAGE_MEDIA, ERRMSG);
static_assert((int32_t)Usage::VoiceCommunication == AAUDIO_USAGE_VOICE_COMMUNICATION, ERRMSG);
static_assert((int32_t)Usage::VoiceCommunicationSignalling
== AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, ERRMSG);
static_assert((int32_t)Usage::Alarm == AAUDIO_USAGE_ALARM, ERRMSG);
static_assert((int32_t)Usage::Notification == AAUDIO_USAGE_NOTIFICATION, ERRMSG);
static_assert((int32_t)Usage::NotificationRingtone == AAUDIO_USAGE_NOTIFICATION_RINGTONE, ERRMSG);
static_assert((int32_t)Usage::NotificationEvent == AAUDIO_USAGE_NOTIFICATION_EVENT, ERRMSG);
static_assert((int32_t)Usage::AssistanceAccessibility == AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY, ERRMSG);
static_assert((int32_t)Usage::AssistanceNavigationGuidance
== AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, ERRMSG);
static_assert((int32_t)Usage::AssistanceSonification == AAUDIO_USAGE_ASSISTANCE_SONIFICATION, ERRMSG);
static_assert((int32_t)Usage::Game == AAUDIO_USAGE_GAME, ERRMSG);
static_assert((int32_t)Usage::Assistant == AAUDIO_USAGE_ASSISTANT, ERRMSG);
static_assert((int32_t)ContentType::Speech == AAUDIO_CONTENT_TYPE_SPEECH, ERRMSG);
static_assert((int32_t)ContentType::Music == AAUDIO_CONTENT_TYPE_MUSIC, ERRMSG);
static_assert((int32_t)ContentType::Movie == AAUDIO_CONTENT_TYPE_MOVIE, ERRMSG);
static_assert((int32_t)ContentType::Sonification == AAUDIO_CONTENT_TYPE_SONIFICATION, ERRMSG);
static_assert((int32_t)InputPreset::Generic == AAUDIO_INPUT_PRESET_GENERIC, ERRMSG);
static_assert((int32_t)InputPreset::Camcorder == AAUDIO_INPUT_PRESET_CAMCORDER, ERRMSG);
static_assert((int32_t)InputPreset::VoiceRecognition == AAUDIO_INPUT_PRESET_VOICE_RECOGNITION, ERRMSG);
static_assert((int32_t)InputPreset::VoiceCommunication
== AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION, ERRMSG);
static_assert((int32_t)InputPreset::Unprocessed == AAUDIO_INPUT_PRESET_UNPROCESSED, ERRMSG);
static_assert((int32_t)SessionId::None == AAUDIO_SESSION_ID_NONE, ERRMSG);
static_assert((int32_t)SessionId::Allocate == AAUDIO_SESSION_ID_ALLOCATE, ERRMSG);
#endif // __NDK_MAJOR__ >= 17
#endif // AAUDIO_AAUDIO_H
} // namespace oboe

View File

@ -0,0 +1,243 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AAUDIO_LOADER_H_
#define OBOE_AAUDIO_LOADER_H_
#include <unistd.h>
#include "oboe/Definitions.h"
// If the NDK is before O then define this in your build
// so that AAudio.h will not be included.
#ifdef OBOE_NO_INCLUDE_AAUDIO
// Define missing types from AAudio.h
typedef int32_t aaudio_stream_state_t;
typedef int32_t aaudio_direction_t;
typedef int32_t aaudio_format_t;
typedef int32_t aaudio_data_callback_result_t;
typedef int32_t aaudio_result_t;
typedef int32_t aaudio_sharing_mode_t;
typedef int32_t aaudio_performance_mode_t;
typedef struct AAudioStreamStruct AAudioStream;
typedef struct AAudioStreamBuilderStruct AAudioStreamBuilder;
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames);
typedef void (*AAudioStream_errorCallback)(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
// These were defined in P
typedef int32_t aaudio_usage_t;
typedef int32_t aaudio_content_type_t;
typedef int32_t aaudio_input_preset_t;
typedef int32_t aaudio_session_id_t;
// There are a few definitions used by Oboe.
#define AAUDIO_OK static_cast<aaudio_result_t>(Result::OK)
#define AAUDIO_ERROR_TIMEOUT static_cast<aaudio_result_t>(Result::ErrorTimeout)
#define AAUDIO_STREAM_STATE_STARTING static_cast<aaudio_stream_state_t>(StreamState::Starting)
#define AAUDIO_STREAM_STATE_STARTED static_cast<aaudio_stream_state_t>(StreamState::Started)
#else
#include <aaudio/AAudio.h>
#endif
#ifndef __NDK_MAJOR__
#define __NDK_MAJOR__ 0
#endif
#ifndef __ANDROID_API_S__
#define __ANDROID_API_S__ 31
#endif
namespace oboe {
/**
* The AAudio API was not available in early versions of Android.
* To avoid linker errors, we dynamically link with the functions by name using dlsym().
* On older versions this linkage will safely fail.
*/
class AAudioLoader {
public:
// Use signatures for common functions.
// Key to letter abbreviations.
// S = Stream
// B = Builder
// I = int32_t
// L = int64_t
// T = sTate
// K = clocKid_t
// P = Pointer to following data type
// C = Const prefix
// H = cHar
typedef int32_t (*signature_I_PPB)(AAudioStreamBuilder **builder);
typedef const char * (*signature_CPH_I)(int32_t);
typedef int32_t (*signature_I_PBPPS)(AAudioStreamBuilder *,
AAudioStream **stream); // AAudioStreamBuilder_open()
typedef int32_t (*signature_I_PB)(AAudioStreamBuilder *); // AAudioStreamBuilder_delete()
// AAudioStreamBuilder_setSampleRate()
typedef void (*signature_V_PBI)(AAudioStreamBuilder *, int32_t);
typedef void (*signature_V_PBCPH)(AAudioStreamBuilder *, const char *);
typedef int32_t (*signature_I_PS)(AAudioStream *); // AAudioStream_getSampleRate()
typedef int64_t (*signature_L_PS)(AAudioStream *); // AAudioStream_getFramesRead()
// AAudioStream_setBufferSizeInFrames()
typedef int32_t (*signature_I_PSI)(AAudioStream *, int32_t);
typedef void (*signature_V_PBPDPV)(AAudioStreamBuilder *,
AAudioStream_dataCallback,
void *);
typedef void (*signature_V_PBPEPV)(AAudioStreamBuilder *,
AAudioStream_errorCallback,
void *);
typedef aaudio_format_t (*signature_F_PS)(AAudioStream *stream);
typedef int32_t (*signature_I_PSPVIL)(AAudioStream *, void *, int32_t, int64_t);
typedef int32_t (*signature_I_PSCPVIL)(AAudioStream *, const void *, int32_t, int64_t);
typedef int32_t (*signature_I_PSTPTL)(AAudioStream *,
aaudio_stream_state_t,
aaudio_stream_state_t *,
int64_t);
typedef int32_t (*signature_I_PSKPLPL)(AAudioStream *, clockid_t, int64_t *, int64_t *);
typedef bool (*signature_B_PS)(AAudioStream *);
static AAudioLoader* getInstance(); // singleton
/**
* Open the AAudio shared library and load the function pointers.
* This can be called multiple times.
* It should only be called from one thread.
*
* The destructor will clean up after the open.
*
* @return 0 if successful or negative error.
*/
int open();
void *getLibHandle() const { return mLibHandle; }
// Function pointers into the AAudio shared library.
signature_I_PPB createStreamBuilder = nullptr;
signature_I_PBPPS builder_openStream = nullptr;
signature_V_PBI builder_setBufferCapacityInFrames = nullptr;
signature_V_PBI builder_setChannelCount = nullptr;
signature_V_PBI builder_setDeviceId = nullptr;
signature_V_PBI builder_setDirection = nullptr;
signature_V_PBI builder_setFormat = nullptr;
signature_V_PBI builder_setFramesPerDataCallback = nullptr;
signature_V_PBI builder_setPerformanceMode = nullptr;
signature_V_PBI builder_setSampleRate = nullptr;
signature_V_PBI builder_setSharingMode = nullptr;
signature_V_PBI builder_setUsage = nullptr;
signature_V_PBI builder_setContentType = nullptr;
signature_V_PBI builder_setInputPreset = nullptr;
signature_V_PBI builder_setSessionId = nullptr;
signature_V_PBCPH builder_setPackageName = nullptr;
signature_V_PBCPH builder_setAttributionTag = nullptr;
signature_V_PBPDPV builder_setDataCallback = nullptr;
signature_V_PBPEPV builder_setErrorCallback = nullptr;
signature_I_PB builder_delete = nullptr;
signature_F_PS stream_getFormat = nullptr;
signature_I_PSPVIL stream_read = nullptr;
signature_I_PSCPVIL stream_write = nullptr;
signature_I_PSTPTL stream_waitForStateChange = nullptr;
signature_I_PSKPLPL stream_getTimestamp = nullptr;
signature_I_PS stream_close = nullptr;
signature_I_PS stream_getChannelCount = nullptr;
signature_I_PS stream_getDeviceId = nullptr;
signature_I_PS stream_getBufferSize = nullptr;
signature_I_PS stream_getBufferCapacity = nullptr;
signature_I_PS stream_getFramesPerBurst = nullptr;
signature_I_PS stream_getState = nullptr;
signature_I_PS stream_getPerformanceMode = nullptr;
signature_I_PS stream_getSampleRate = nullptr;
signature_I_PS stream_getSharingMode = nullptr;
signature_I_PS stream_getXRunCount = nullptr;
signature_I_PSI stream_setBufferSize = nullptr;
signature_I_PS stream_requestStart = nullptr;
signature_I_PS stream_requestPause = nullptr;
signature_I_PS stream_requestFlush = nullptr;
signature_I_PS stream_requestStop = nullptr;
signature_L_PS stream_getFramesRead = nullptr;
signature_L_PS stream_getFramesWritten = nullptr;
signature_CPH_I convertResultToText = nullptr;
signature_I_PS stream_getUsage = nullptr;
signature_I_PS stream_getContentType = nullptr;
signature_I_PS stream_getInputPreset = nullptr;
signature_I_PS stream_getSessionId = nullptr;
private:
AAudioLoader() {}
~AAudioLoader();
// Load function pointers for specific signatures.
signature_I_PPB load_I_PPB(const char *name);
signature_CPH_I load_CPH_I(const char *name);
signature_V_PBI load_V_PBI(const char *name);
signature_V_PBCPH load_V_PBCPH(const char *name);
signature_V_PBPDPV load_V_PBPDPV(const char *name);
signature_V_PBPEPV load_V_PBPEPV(const char *name);
signature_I_PB load_I_PB(const char *name);
signature_I_PBPPS load_I_PBPPS(const char *name);
signature_I_PS load_I_PS(const char *name);
signature_L_PS load_L_PS(const char *name);
signature_F_PS load_F_PS(const char *name);
signature_B_PS load_B_PS(const char *name);
signature_I_PSI load_I_PSI(const char *name);
signature_I_PSPVIL load_I_PSPVIL(const char *name);
signature_I_PSCPVIL load_I_PSCPVIL(const char *name);
signature_I_PSTPTL load_I_PSTPTL(const char *name);
signature_I_PSKPLPL load_I_PSKPLPL(const char *name);
void *mLibHandle = nullptr;
};
} // namespace oboe
#endif //OBOE_AAUDIO_LOADER_H_

View File

@ -0,0 +1,715 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <stdint.h>
#include <stdlib.h>
#include "aaudio/AAudioLoader.h"
#include "aaudio/AudioStreamAAudio.h"
#include "common/AudioClock.h"
#include "common/OboeDebug.h"
#include "oboe/Utilities.h"
#include "AAudioExtensions.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#include <common/QuirksManager.h>
#endif
#ifndef OBOE_FIX_FORCE_STARTING_TO_STARTED
// Workaround state problems in AAudio
// TODO Which versions does this occur in? Verify fixed in Q.
#define OBOE_FIX_FORCE_STARTING_TO_STARTED 1
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
using namespace oboe;
AAudioLoader *AudioStreamAAudio::mLibLoader = nullptr;
// 'C' wrapper for the data callback method
static aaudio_data_callback_result_t oboe_aaudio_data_callback_proc(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames) {
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
if (oboeStream != nullptr) {
return static_cast<aaudio_data_callback_result_t>(
oboeStream->callOnAudioReady(stream, audioData, numFrames));
} else {
return static_cast<aaudio_data_callback_result_t>(DataCallbackResult::Stop);
}
}
// This runs in its own thread.
// Only one of these threads will be launched from internalErrorCallback().
// It calls app error callbacks from a static function in case the stream gets deleted.
static void oboe_aaudio_error_thread_proc(AudioStreamAAudio *oboeStream,
Result error) {
LOGD("%s(,%d) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__, error);
AudioStreamErrorCallback *errorCallback = oboeStream->getErrorCallback();
if (errorCallback == nullptr) return; // should be impossible
bool isErrorHandled = errorCallback->onError(oboeStream, error);
if (!isErrorHandled) {
oboeStream->requestStop();
errorCallback->onErrorBeforeClose(oboeStream, error);
oboeStream->close();
// Warning, oboeStream may get deleted by this callback.
errorCallback->onErrorAfterClose(oboeStream, error);
}
LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__);
}
// This runs in its own thread.
// Only one of these threads will be launched from internalErrorCallback().
// Prevents deletion of the stream if the app is using AudioStreamBuilder::openSharedStream()
static void oboe_aaudio_error_thread_proc_shared(std::shared_ptr<AudioStream> sharedStream,
Result error) {
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(sharedStream.get());
oboe_aaudio_error_thread_proc(oboeStream, error);
}
namespace oboe {
/*
* Create a stream that uses Oboe Audio API.
*/
AudioStreamAAudio::AudioStreamAAudio(const AudioStreamBuilder &builder)
: AudioStream(builder)
, mAAudioStream(nullptr) {
mCallbackThreadEnabled.store(false);
mLibLoader = AAudioLoader::getInstance();
}
bool AudioStreamAAudio::isSupported() {
mLibLoader = AAudioLoader::getInstance();
int openResult = mLibLoader->open();
return openResult == 0;
}
// Static method for the error callback.
// We use a method so we can access protected methods on the stream.
// Launch a thread to handle the error.
// That other thread can safely stop, close and delete the stream.
void AudioStreamAAudio::internalErrorCallback(
AAudioStream *stream,
void *userData,
aaudio_result_t error) {
oboe::Result oboeResult = static_cast<Result>(error);
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
// Coerce the error code if needed to workaround a regression in RQ1A that caused
// the wrong code to be passed when headsets plugged in. See b/173928197.
if (OboeGlobals::areWorkaroundsEnabled()
&& getSdkVersion() == __ANDROID_API_R__
&& oboeResult == oboe::Result::ErrorTimeout) {
oboeResult = oboe::Result::ErrorDisconnected;
LOGD("%s() ErrorTimeout changed to ErrorDisconnected to fix b/173928197", __func__);
}
oboeStream->mErrorCallbackResult = oboeResult;
// Prevents deletion of the stream if the app is using AudioStreamBuilder::openStream(shared_ptr)
std::shared_ptr<AudioStream> sharedStream = oboeStream->lockWeakThis();
// These checks should be enough because we assume that the stream close()
// will join() any active callback threads and will not allow new callbacks.
if (oboeStream->wasErrorCallbackCalled()) { // block extra error callbacks
LOGE("%s() multiple error callbacks called!", __func__);
} else if (stream != oboeStream->getUnderlyingStream()) {
LOGW("%s() stream already closed or closing", __func__); // might happen if there are bugs
} else if (sharedStream) {
// Handle error on a separate thread using shared pointer.
std::thread t(oboe_aaudio_error_thread_proc_shared, sharedStream, oboeResult);
t.detach();
} else {
// Handle error on a separate thread.
std::thread t(oboe_aaudio_error_thread_proc, oboeStream, oboeResult);
t.detach();
}
}
void AudioStreamAAudio::logUnsupportedAttributes() {
int sdkVersion = getSdkVersion();
// These attributes are not supported pre Android "P"
if (sdkVersion < __ANDROID_API_P__) {
if (mUsage != Usage::Media) {
LOGW("Usage [AudioStreamBuilder::setUsage()] "
"is not supported on AAudio streams running on pre-Android P versions.");
}
if (mContentType != ContentType::Music) {
LOGW("ContentType [AudioStreamBuilder::setContentType()] "
"is not supported on AAudio streams running on pre-Android P versions.");
}
if (mSessionId != SessionId::None) {
LOGW("SessionId [AudioStreamBuilder::setSessionId()] "
"is not supported on AAudio streams running on pre-Android P versions.");
}
}
}
Result AudioStreamAAudio::open() {
Result result = Result::OK;
if (mAAudioStream != nullptr) {
return Result::ErrorInvalidState;
}
result = AudioStream::open();
if (result != Result::OK) {
return result;
}
AAudioStreamBuilder *aaudioBuilder;
result = static_cast<Result>(mLibLoader->createStreamBuilder(&aaudioBuilder));
if (result != Result::OK) {
return result;
}
// Do not set INPUT capacity below 4096 because that prevents us from getting a FAST track
// when using the Legacy data path.
// If the app requests > 4096 then we allow it but we are less likely to get LowLatency.
// See internal bug b/80308183 for more details.
// Fixed in Q but let's still clip the capacity because high input capacity
// does not increase latency.
int32_t capacity = mBufferCapacityInFrames;
constexpr int kCapacityRequiredForFastLegacyTrack = 4096; // matches value in AudioFinger
if (OboeGlobals::areWorkaroundsEnabled()
&& mDirection == oboe::Direction::Input
&& capacity != oboe::Unspecified
&& capacity < kCapacityRequiredForFastLegacyTrack
&& mPerformanceMode == oboe::PerformanceMode::LowLatency) {
capacity = kCapacityRequiredForFastLegacyTrack;
LOGD("AudioStreamAAudio.open() capacity changed from %d to %d for lower latency",
static_cast<int>(mBufferCapacityInFrames), capacity);
}
mLibLoader->builder_setBufferCapacityInFrames(aaudioBuilder, capacity);
mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount);
mLibLoader->builder_setDeviceId(aaudioBuilder, mDeviceId);
mLibLoader->builder_setDirection(aaudioBuilder, static_cast<aaudio_direction_t>(mDirection));
mLibLoader->builder_setFormat(aaudioBuilder, static_cast<aaudio_format_t>(mFormat));
mLibLoader->builder_setSampleRate(aaudioBuilder, mSampleRate);
mLibLoader->builder_setSharingMode(aaudioBuilder,
static_cast<aaudio_sharing_mode_t>(mSharingMode));
mLibLoader->builder_setPerformanceMode(aaudioBuilder,
static_cast<aaudio_performance_mode_t>(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<aaudio_usage_t>(mUsage));
}
if (mLibLoader->builder_setContentType != nullptr) {
mLibLoader->builder_setContentType(aaudioBuilder,
static_cast<aaudio_content_type_t>(mContentType));
}
if (mLibLoader->builder_setInputPreset != nullptr) {
aaudio_input_preset_t inputPreset = mInputPreset;
if (getSdkVersion() <= __ANDROID_API_P__ && inputPreset == InputPreset::VoicePerformance) {
LOGD("InputPreset::VoicePerformance not supported before Q. Using VoiceRecognition.");
inputPreset = InputPreset::VoiceRecognition; // most similar preset
}
mLibLoader->builder_setInputPreset(aaudioBuilder,
static_cast<aaudio_input_preset_t>(inputPreset));
}
if (mLibLoader->builder_setSessionId != nullptr) {
mLibLoader->builder_setSessionId(aaudioBuilder,
static_cast<aaudio_session_id_t>(mSessionId));
}
// These were added in S so we have to check for the function pointer.
if (mLibLoader->builder_setPackageName != nullptr && !mPackageName.empty()) {
mLibLoader->builder_setPackageName(aaudioBuilder,
mPackageName.c_str());
}
if (mLibLoader->builder_setAttributionTag != nullptr && !mAttributionTag.empty()) {
mLibLoader->builder_setAttributionTag(aaudioBuilder,
mAttributionTag.c_str());
}
if (isDataCallbackSpecified()) {
mLibLoader->builder_setDataCallback(aaudioBuilder, oboe_aaudio_data_callback_proc, this);
mLibLoader->builder_setFramesPerDataCallback(aaudioBuilder, getFramesPerDataCallback());
if (!isErrorCallbackSpecified()) {
// The app did not specify a callback so we should specify
// our own so the stream gets closed and stopped.
mErrorCallback = &mDefaultErrorCallback;
}
mLibLoader->builder_setErrorCallback(aaudioBuilder, internalErrorCallback, this);
}
// Else if the data callback is not being used then the write method will return an error
// and the app can stop and close the stream.
// ============= OPEN THE STREAM ================
{
AAudioStream *stream = nullptr;
result = static_cast<Result>(mLibLoader->builder_openStream(aaudioBuilder, &stream));
mAAudioStream.store(stream);
}
if (result != Result::OK) {
// Warn developer because ErrorInternal is not very informative.
if (result == Result::ErrorInternal && mDirection == Direction::Input) {
LOGW("AudioStreamAAudio.open() may have failed due to lack of "
"audio recording permission.");
}
goto error2;
}
// Query and cache the stream properties
mDeviceId = mLibLoader->stream_getDeviceId(mAAudioStream);
mChannelCount = mLibLoader->stream_getChannelCount(mAAudioStream);
mSampleRate = mLibLoader->stream_getSampleRate(mAAudioStream);
mFormat = static_cast<AudioFormat>(mLibLoader->stream_getFormat(mAAudioStream));
mSharingMode = static_cast<SharingMode>(mLibLoader->stream_getSharingMode(mAAudioStream));
mPerformanceMode = static_cast<PerformanceMode>(
mLibLoader->stream_getPerformanceMode(mAAudioStream));
mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream);
mBufferSizeInFrames = mLibLoader->stream_getBufferSize(mAAudioStream);
mFramesPerBurst = mLibLoader->stream_getFramesPerBurst(mAAudioStream);
// These were added in P so we have to check for the function pointer.
if (mLibLoader->stream_getUsage != nullptr) {
mUsage = static_cast<Usage>(mLibLoader->stream_getUsage(mAAudioStream));
}
if (mLibLoader->stream_getContentType != nullptr) {
mContentType = static_cast<ContentType>(mLibLoader->stream_getContentType(mAAudioStream));
}
if (mLibLoader->stream_getInputPreset != nullptr) {
mInputPreset = static_cast<InputPreset>(mLibLoader->stream_getInputPreset(mAAudioStream));
}
if (mLibLoader->stream_getSessionId != nullptr) {
mSessionId = static_cast<SessionId>(mLibLoader->stream_getSessionId(mAAudioStream));
} else {
mSessionId = SessionId::None;
}
LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d",
static_cast<int>(mFormat), static_cast<int>(mSampleRate),
static_cast<int>(mBufferCapacityInFrames));
error2:
mLibLoader->builder_delete(aaudioBuilder);
LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s",
mLibLoader->convertResultToText(static_cast<aaudio_result_t>(result)));
return result;
}
Result AudioStreamAAudio::close() {
// Prevent two threads from closing the stream at the same time and crashing.
// This could occur, for example, if an application called close() at the same
// time that an onError callback was being executed because of a disconnect.
std::lock_guard<std::mutex> lock(mLock);
AudioStream::close();
AAudioStream *stream = nullptr;
{
// Wait for any methods using mAAudioStream to finish.
std::unique_lock<std::shared_mutex> lock2(mAAudioStreamLock);
// Closing will delete *mAAudioStream so we need to null out the pointer atomically.
stream = mAAudioStream.exchange(nullptr);
}
if (stream != nullptr) {
if (OboeGlobals::areWorkaroundsEnabled()) {
// Make sure we are really stopped. Do it under mLock
// so another thread cannot call requestStart() right before the close.
requestStop_l(stream);
// Sometimes a callback can occur shortly after a stream has been stopped and
// even after a close! If the stream has been closed then the callback
// can access memory that has been freed. That causes a crash.
// This seems to be more likely in Android P or earlier.
// But it can also occur in later versions.
usleep(kDelayBeforeCloseMillis * 1000);
}
return static_cast<Result>(mLibLoader->stream_close(stream));
} else {
return Result::ErrorClosed;
}
}
static void oboe_stop_thread_proc(AudioStream *oboeStream) {
if (oboeStream != nullptr) {
oboeStream->requestStop();
}
}
void AudioStreamAAudio::launchStopThread() {
// Prevent multiple stop threads from being launched.
if (mStopThreadAllowed.exchange(false)) {
// Stop this stream on a separate thread
std::thread t(oboe_stop_thread_proc, this);
t.detach();
}
}
DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream * /*stream*/,
void *audioData,
int32_t numFrames) {
DataCallbackResult result = fireDataCallback(audioData, numFrames);
if (result == DataCallbackResult::Continue) {
return result;
} else {
if (result == DataCallbackResult::Stop) {
LOGD("Oboe callback returned DataCallbackResult::Stop");
} else {
LOGE("Oboe callback returned unexpected value = %d", result);
}
// Returning Stop caused various problems before S. See #1230
if (OboeGlobals::areWorkaroundsEnabled() && getSdkVersion() <= __ANDROID_API_R__) {
launchStopThread();
return DataCallbackResult::Continue;
} else {
return DataCallbackResult::Stop; // OK >= API_S
}
}
}
Result AudioStreamAAudio::requestStart() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Starting || state == StreamState::Started) {
// WARNING: On P, AAudio is returning ErrorInvalidState for Output and OK for Input.
return Result::OK;
}
}
if (isDataCallbackSpecified()) {
setDataCallbackEnabled(true);
}
mStopThreadAllowed = true;
return static_cast<Result>(mLibLoader->stream_requestStart(stream));
} else {
return Result::ErrorClosed;
}
}
Result AudioStreamAAudio::requestPause() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Pausing || state == StreamState::Paused) {
return Result::OK;
}
}
return static_cast<Result>(mLibLoader->stream_requestPause(stream));
} else {
return Result::ErrorClosed;
}
}
Result AudioStreamAAudio::requestFlush() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Flushing || state == StreamState::Flushed) {
return Result::OK;
}
}
return static_cast<Result>(mLibLoader->stream_requestFlush(stream));
} else {
return Result::ErrorClosed;
}
}
Result AudioStreamAAudio::requestStop() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return requestStop_l(stream);
} else {
return Result::ErrorClosed;
}
}
// Call under mLock
Result AudioStreamAAudio::requestStop_l(AAudioStream *stream) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Stopping || state == StreamState::Stopped) {
return Result::OK;
}
}
return static_cast<Result>(mLibLoader->stream_requestStop(stream));
}
ResultWithValue<int32_t> AudioStreamAAudio::write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
int32_t result = mLibLoader->stream_write(mAAudioStream, buffer,
numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(result);
} else {
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
}
ResultWithValue<int32_t> AudioStreamAAudio::read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
int32_t result = mLibLoader->stream_read(mAAudioStream, buffer,
numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(result);
} else {
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
}
// AAudioStream_waitForStateChange() can crash if it is waiting on a stream and that stream
// is closed from another thread. We do not want to lock the stream for the duration of the call.
// So we call AAudioStream_waitForStateChange() with a timeout of zero so that it will not block.
// Then we can do our own sleep with the lock unlocked.
Result AudioStreamAAudio::waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) {
Result oboeResult = Result::ErrorTimeout;
int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary
aaudio_stream_state_t currentAAudioState = static_cast<aaudio_stream_state_t>(currentState);
aaudio_result_t result = AAUDIO_OK;
int64_t timeLeftNanos = timeoutNanoseconds;
mLock.lock();
while (true) {
// Do we still have an AAudio stream? If not then stream must have been closed.
AAudioStream *stream = mAAudioStream.load();
if (stream == nullptr) {
if (nextState != nullptr) {
*nextState = StreamState::Closed;
}
oboeResult = Result::ErrorClosed;
break;
}
// Update and query state change with no blocking.
aaudio_stream_state_t aaudioNextState;
result = mLibLoader->stream_waitForStateChange(
mAAudioStream,
currentAAudioState,
&aaudioNextState,
0); // timeout=0 for non-blocking
// AAudio will return AAUDIO_ERROR_TIMEOUT if timeout=0 and the state does not change.
if (result != AAUDIO_OK && result != AAUDIO_ERROR_TIMEOUT) {
oboeResult = static_cast<Result>(result);
break;
}
#if OBOE_FIX_FORCE_STARTING_TO_STARTED
if (OboeGlobals::areWorkaroundsEnabled()
&& aaudioNextState == static_cast<aaudio_stream_state_t >(StreamState::Starting)) {
aaudioNextState = static_cast<aaudio_stream_state_t >(StreamState::Started);
}
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
if (nextState != nullptr) {
*nextState = static_cast<StreamState>(aaudioNextState);
}
if (currentAAudioState != aaudioNextState) { // state changed?
oboeResult = Result::OK;
break;
}
// Did we timeout or did user ask for non-blocking?
if (timeLeftNanos <= 0) {
break;
}
// No change yet so sleep.
mLock.unlock(); // Don't sleep while locked.
if (sleepTimeNanos > timeLeftNanos) {
sleepTimeNanos = timeLeftNanos; // last little bit
}
AudioClock::sleepForNanos(sleepTimeNanos);
timeLeftNanos -= sleepTimeNanos;
mLock.lock();
}
mLock.unlock();
return oboeResult;
}
ResultWithValue<int32_t> AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) {
int32_t adjustedFrames = requestedFrames;
if (adjustedFrames > mBufferCapacityInFrames) {
adjustedFrames = mBufferCapacityInFrames;
}
// This calls getBufferSize() so avoid recursive lock.
adjustedFrames = QuirksManager::getInstance().clipBufferSize(*this, adjustedFrames);
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, adjustedFrames);
// Cache the result if it's valid
if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize;
return ResultWithValue<int32_t>::createBasedOnSign(newBufferSize);
} else {
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
}
StreamState AudioStreamAAudio::getState() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
aaudio_stream_state_t aaudioState = mLibLoader->stream_getState(stream);
#if OBOE_FIX_FORCE_STARTING_TO_STARTED
if (OboeGlobals::areWorkaroundsEnabled()
&& aaudioState == AAUDIO_STREAM_STATE_STARTING) {
aaudioState = AAUDIO_STREAM_STATE_STARTED;
}
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
return static_cast<StreamState>(aaudioState);
} else {
return StreamState::Closed;
}
}
int32_t AudioStreamAAudio::getBufferSizeInFrames() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
mBufferSizeInFrames = mLibLoader->stream_getBufferSize(stream);
}
return mBufferSizeInFrames;
}
void AudioStreamAAudio::updateFramesRead() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
// Set to 1 for debugging race condition #1180 with mAAudioStream.
// See also DEBUG_CLOSE_RACE in OboeTester.
// This was left in the code so that we could test the fix again easily in the future.
// We could not trigger the race condition without adding these get calls and the sleeps.
#define DEBUG_CLOSE_RACE 0
#if DEBUG_CLOSE_RACE
// This is used when testing race conditions with close().
// See DEBUG_CLOSE_RACE in OboeTester
AudioClock::sleepForNanos(400 * kNanosPerMillisecond);
#endif // DEBUG_CLOSE_RACE
if (stream != nullptr) {
mFramesRead = mLibLoader->stream_getFramesRead(stream);
}
}
void AudioStreamAAudio::updateFramesWritten() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
mFramesWritten = mLibLoader->stream_getFramesWritten(stream);
}
}
ResultWithValue<int32_t> AudioStreamAAudio::getXRunCount() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return ResultWithValue<int32_t>::createBasedOnSign(mLibLoader->stream_getXRunCount(stream));
} else {
return ResultWithValue<int32_t>(Result::ErrorNull);
}
}
Result AudioStreamAAudio::getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) {
if (getState() != StreamState::Started) {
return Result::ErrorInvalidState;
}
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return static_cast<Result>(mLibLoader->stream_getTimestamp(stream, clockId,
framePosition, timeNanoseconds));
} else {
return Result::ErrorNull;
}
}
ResultWithValue<double> AudioStreamAAudio::calculateLatencyMillis() {
// Get the time that a known audio frame was presented.
int64_t hardwareFrameIndex;
int64_t hardwareFrameHardwareTime;
auto result = getTimestamp(CLOCK_MONOTONIC,
&hardwareFrameIndex,
&hardwareFrameHardwareTime);
if (result != oboe::Result::OK) {
return ResultWithValue<double>(static_cast<Result>(result));
}
// Get counter closest to the app.
bool isOutput = (getDirection() == oboe::Direction::Output);
int64_t appFrameIndex = isOutput ? getFramesWritten() : getFramesRead();
// Assume that the next frame will be processed at the current time
using namespace std::chrono;
int64_t appFrameAppTime =
duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
// Calculate the number of frames between app and hardware
int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
// Calculate the time which the next frame will be or was presented
int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / getSampleRate();
int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
// The current latency is the difference in time between when the current frame is at
// the app and when it is at the hardware.
double latencyNanos = static_cast<double>(isOutput
? (appFrameHardwareTime - appFrameAppTime) // hardware is later
: (appFrameAppTime - appFrameHardwareTime)); // hardware is earlier
double latencyMillis = latencyNanos / kNanosPerMillisecond;
return ResultWithValue<double>(latencyMillis);
}
bool AudioStreamAAudio::isMMapUsed() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return AAudioExtensions::getInstance().isMMapUsed(stream);
} else {
return false;
}
}
} // namespace oboe

View File

@ -0,0 +1,139 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_STREAM_AAUDIO_H_
#define OBOE_STREAM_AAUDIO_H_
#include <atomic>
#include <shared_mutex>
#include <mutex>
#include <thread>
#include "oboe/AudioStreamBuilder.h"
#include "oboe/AudioStream.h"
#include "oboe/Definitions.h"
#include "AAudioLoader.h"
namespace oboe {
/**
* Implementation of OboeStream that uses AAudio.
*
* Do not create this class directly.
* Use an OboeStreamBuilder to create one.
*/
class AudioStreamAAudio : public AudioStream {
public:
AudioStreamAAudio();
explicit AudioStreamAAudio(const AudioStreamBuilder &builder);
virtual ~AudioStreamAAudio() = default;
/**
*
* @return true if AAudio is supported on this device.
*/
static bool isSupported();
// These functions override methods in AudioStream.
// See AudioStream for documentation.
Result open() override;
Result close() override;
Result requestStart() override;
Result requestPause() override;
Result requestFlush() override;
Result requestStop() override;
ResultWithValue<int32_t> write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
int32_t getBufferSizeInFrames() override;
ResultWithValue<int32_t> getXRunCount() override;
bool isXRunCountSupported() const override { return true; }
ResultWithValue<double> calculateLatencyMillis() override;
Result waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) override;
Result getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
StreamState getState() override;
AudioApi getAudioApi() const override {
return AudioApi::AAudio;
}
DataCallbackResult callOnAudioReady(AAudioStream *stream,
void *audioData,
int32_t numFrames);
bool isMMapUsed();
protected:
static void internalErrorCallback(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
void *getUnderlyingStream() const override {
return mAAudioStream.load();
}
void updateFramesRead() override;
void updateFramesWritten() override;
void logUnsupportedAttributes();
private:
// Must call under mLock. And stream must NOT be nullptr.
Result requestStop_l(AAudioStream *stream);
/**
* Launch a thread that will stop the stream.
*/
void launchStopThread();
// Time to sleep in order to prevent a race condition with a callback after a close().
// Two milliseconds may be enough but 10 msec is even safer.
static constexpr int kDelayBeforeCloseMillis = 10;
std::atomic<bool> mCallbackThreadEnabled;
std::atomic<bool> mStopThreadAllowed{false};
// pointer to the underlying 'C' AAudio stream, valid if open, null if closed
std::atomic<AAudioStream *> mAAudioStream{nullptr};
std::shared_mutex mAAudioStreamLock; // to protect mAAudioStream while closing
static AAudioLoader *mLibLoader;
// We may not use this but it is so small that it is not worth allocating dynamically.
AudioStreamErrorCallback mDefaultErrorCallback;
};
} // namespace oboe
#endif // OBOE_STREAM_AAUDIO_H_

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AUDIO_CLOCK_H
#define OBOE_AUDIO_CLOCK_H
#include <sys/types.h>
#include <ctime>
#include "oboe/Definitions.h"
namespace oboe {
// TODO: Move this class into the public headers because it is useful when calculating stream latency
class AudioClock {
public:
static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
int result = clock_gettime(clockId, &time);
if (result < 0) {
return result;
}
return (time.tv_sec * kNanosPerSecond) + time.tv_nsec;
}
/**
* Sleep until the specified time.
*
* @param nanoTime time to wake up
* @param clockId CLOCK_MONOTONIC is default
* @return 0 or a negative error, eg. -EINTR
*/
static int sleepUntilNanoTime(int64_t nanoTime, clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
time.tv_sec = nanoTime / kNanosPerSecond;
time.tv_nsec = nanoTime - (time.tv_sec * kNanosPerSecond);
return 0 - clock_nanosleep(clockId, TIMER_ABSTIME, &time, NULL);
}
/**
* Sleep for the specified number of nanoseconds in real-time.
* Return immediately with 0 if a negative nanoseconds is specified.
*
* @param nanoseconds time to sleep
* @param clockId CLOCK_REALTIME is default
* @return 0 or a negative error, eg. -EINTR
*/
static int sleepForNanos(int64_t nanoseconds, clockid_t clockId = CLOCK_REALTIME) {
if (nanoseconds > 0) {
struct timespec time;
time.tv_sec = nanoseconds / kNanosPerSecond;
time.tv_nsec = nanoseconds - (time.tv_sec * kNanosPerSecond);
return 0 - clock_nanosleep(clockId, 0, &time, NULL);
}
return 0;
}
};
} // namespace oboe
#endif //OBOE_AUDIO_CLOCK_H

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AudioSourceCaller.h"
using namespace oboe;
using namespace flowgraph;
int32_t AudioSourceCaller::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
AudioStreamDataCallback *callback = mStream->getDataCallback();
int32_t result = 0;
int32_t numFrames = numBytes / mStream->getBytesPerFrame();
if (callback != nullptr) {
DataCallbackResult callbackResult = callback->onAudioReady(mStream, buffer, numFrames);
// onAudioReady() does not return the number of bytes processed so we have to assume all.
result = (callbackResult == DataCallbackResult::Continue)
? numBytes
: -1;
} else {
auto readResult = mStream->read(buffer, numFrames, mTimeoutNanos);
if (!readResult) return (int32_t) readResult.error();
result = readResult.value() * mStream->getBytesPerFrame();
}
return result;
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AUDIO_SOURCE_CALLER_H
#define OBOE_AUDIO_SOURCE_CALLER_H
#include "OboeDebug.h"
#include "oboe/Oboe.h"
#include "flowgraph/FlowGraphNode.h"
#include "FixedBlockReader.h"
namespace oboe {
class AudioStreamCallback;
class AudioStream;
/**
* For output streams that use a callback, call the application for more data.
* For input streams that do not use a callback, read from the stream.
*/
class AudioSourceCaller : public flowgraph::FlowGraphSource, public FixedBlockProcessor {
public:
AudioSourceCaller(int32_t channelCount, int32_t framesPerCallback, int32_t bytesPerSample)
: FlowGraphSource(channelCount)
, mBlockReader(*this) {
mBlockReader.open(channelCount * framesPerCallback * bytesPerSample);
}
/**
* Set the stream to use as a source of data.
* @param stream
*/
void setStream(oboe::AudioStream *stream) {
mStream = stream;
}
oboe::AudioStream *getStream() {
return mStream;
}
/**
* Timeout value to use when calling audioStream->read().
* @param timeoutNanos Zero for no timeout or time in nanoseconds.
*/
void setTimeoutNanos(int64_t timeoutNanos) {
mTimeoutNanos = timeoutNanos;
}
int64_t getTimeoutNanos() const {
return mTimeoutNanos;
}
/**
* Called internally for block size adaptation.
* @param buffer
* @param numBytes
* @return
*/
int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
protected:
oboe::AudioStream *mStream = nullptr;
int64_t mTimeoutNanos = 0;
FixedBlockReader mBlockReader;
};
}
#endif //OBOE_AUDIO_SOURCE_CALLER_H

View File

@ -0,0 +1,199 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/types.h>
#include <pthread.h>
#include <thread>
#include <oboe/AudioStream.h>
#include "OboeDebug.h"
#include "AudioClock.h"
#include <oboe/Utilities.h>
namespace oboe {
/*
* AudioStream
*/
AudioStream::AudioStream(const AudioStreamBuilder &builder)
: AudioStreamBase(builder) {
}
Result AudioStream::close() {
// Update local counters so they can be read after the close.
updateFramesWritten();
updateFramesRead();
return Result::OK;
}
// Call this from fireDataCallback() if you want to monitor CPU scheduler.
void AudioStream::checkScheduler() {
int scheduler = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK; // for current thread
if (scheduler != mPreviousScheduler) {
LOGD("AudioStream::%s() scheduler = %s", __func__,
((scheduler == SCHED_FIFO) ? "SCHED_FIFO" :
((scheduler == SCHED_OTHER) ? "SCHED_OTHER" :
((scheduler == SCHED_RR) ? "SCHED_RR" : "UNKNOWN")))
);
mPreviousScheduler = scheduler;
}
}
DataCallbackResult AudioStream::fireDataCallback(void *audioData, int32_t numFrames) {
if (!isDataCallbackEnabled()) {
LOGW("AudioStream::%s() called with data callback disabled!", __func__);
return DataCallbackResult::Stop; // Should not be getting called
}
DataCallbackResult result;
if (mDataCallback) {
result = mDataCallback->onAudioReady(this, audioData, numFrames);
} else {
result = onDefaultCallback(audioData, numFrames);
}
// On Oreo, we might get called after returning stop.
// So block that here.
setDataCallbackEnabled(result == DataCallbackResult::Continue);
return result;
}
Result AudioStream::waitForStateTransition(StreamState startingState,
StreamState endingState,
int64_t timeoutNanoseconds)
{
StreamState state;
{
std::lock_guard<std::mutex> lock(mLock);
state = getState();
if (state == StreamState::Closed) {
return Result::ErrorClosed;
} else if (state == StreamState::Disconnected) {
return Result::ErrorDisconnected;
}
}
StreamState nextState = state;
// TODO Should this be a while()?!
if (state == startingState && state != endingState) {
Result result = waitForStateChange(state, &nextState, timeoutNanoseconds);
if (result != Result::OK) {
return result;
}
}
if (nextState != endingState) {
return Result::ErrorInvalidState;
} else {
return Result::OK;
}
}
Result AudioStream::start(int64_t timeoutNanoseconds)
{
Result result = requestStart();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Starting,
StreamState::Started, timeoutNanoseconds);
}
Result AudioStream::pause(int64_t timeoutNanoseconds)
{
Result result = requestPause();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Pausing,
StreamState::Paused, timeoutNanoseconds);
}
Result AudioStream::flush(int64_t timeoutNanoseconds)
{
Result result = requestFlush();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Flushing,
StreamState::Flushed, timeoutNanoseconds);
}
Result AudioStream::stop(int64_t timeoutNanoseconds)
{
Result result = requestStop();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Stopping,
StreamState::Stopped, timeoutNanoseconds);
}
int32_t AudioStream::getBytesPerSample() const {
return convertFormatToSizeInBytes(mFormat);
}
int64_t AudioStream::getFramesRead() {
updateFramesRead();
return mFramesRead;
}
int64_t AudioStream::getFramesWritten() {
updateFramesWritten();
return mFramesWritten;
}
ResultWithValue<int32_t> AudioStream::getAvailableFrames() {
int64_t readCounter = getFramesRead();
if (readCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(readCounter);
int64_t writeCounter = getFramesWritten();
if (writeCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(writeCounter);
int32_t framesAvailable = writeCounter - readCounter;
return ResultWithValue<int32_t>(framesAvailable);
}
ResultWithValue<int32_t> AudioStream::waitForAvailableFrames(int32_t numFrames,
int64_t timeoutNanoseconds) {
if (numFrames == 0) return Result::OK;
if (numFrames < 0) return Result::ErrorOutOfRange;
int64_t framesAvailable = 0;
int64_t burstInNanos = getFramesPerBurst() * kNanosPerSecond / getSampleRate();
bool ready = false;
int64_t deadline = AudioClock::getNanoseconds() + timeoutNanoseconds;
do {
ResultWithValue<int32_t> result = getAvailableFrames();
if (!result) return result;
framesAvailable = result.value();
ready = (framesAvailable >= numFrames);
if (!ready) {
int64_t now = AudioClock::getNanoseconds();
if (now > deadline) break;
AudioClock::sleepForNanos(burstInNanos);
}
} while (!ready);
return (!ready)
? ResultWithValue<int32_t>(Result::ErrorTimeout)
: ResultWithValue<int32_t>(framesAvailable);
}
ResultWithValue<FrameTimestamp> AudioStream::getTimestamp(clockid_t clockId) {
FrameTimestamp frame;
Result result = getTimestamp(clockId, &frame.position, &frame.timestamp);
if (result == Result::OK){
return ResultWithValue<FrameTimestamp>(frame);
} else {
return ResultWithValue<FrameTimestamp>(static_cast<Result>(result));
}
}
} // namespace oboe

View File

@ -0,0 +1,224 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/types.h>
#include "aaudio/AAudioExtensions.h"
#include "aaudio/AudioStreamAAudio.h"
#include "FilterAudioStream.h"
#include "OboeDebug.h"
#include "oboe/Oboe.h"
#include "oboe/AudioStreamBuilder.h"
#include "opensles/AudioInputStreamOpenSLES.h"
#include "opensles/AudioOutputStreamOpenSLES.h"
#include "opensles/AudioStreamOpenSLES.h"
#include "QuirksManager.h"
bool oboe::OboeGlobals::mWorkaroundsEnabled = true;
namespace oboe {
/**
* The following default values are used when oboe does not have any better way of determining the optimal values
* for an audio stream. This can happen when:
*
* - Client is creating a stream on API < 26 (OpenSLES) but has not supplied the optimal sample
* rate and/or frames per burst
* - Client is creating a stream on API 16 (OpenSLES) where AudioManager.PROPERTY_OUTPUT_* values
* are not available
*/
int32_t DefaultStreamValues::SampleRate = 48000; // Common rate for mobile audio and video
int32_t DefaultStreamValues::FramesPerBurst = 192; // 4 msec at 48000 Hz
int32_t DefaultStreamValues::ChannelCount = 2; // Stereo
constexpr int kBufferSizeInBurstsForLowLatencyStreams = 2;
#ifndef OBOE_ENABLE_AAUDIO
// Set OBOE_ENABLE_AAUDIO to 0 if you want to disable the AAudio API.
// This might be useful if you want to force all the unit tests to use OpenSL ES.
#define OBOE_ENABLE_AAUDIO 1
#endif
bool AudioStreamBuilder::isAAudioSupported() {
return AudioStreamAAudio::isSupported() && OBOE_ENABLE_AAUDIO;
}
bool AudioStreamBuilder::isAAudioRecommended() {
// See https://github.com/google/oboe/issues/40,
// AAudio may not be stable on Android O, depending on how it is used.
// To be safe, use AAudio only on O_MR1 and above.
return (getSdkVersion() >= __ANDROID_API_O_MR1__) && isAAudioSupported();
}
AudioStream *AudioStreamBuilder::build() {
AudioStream *stream = nullptr;
if (isAAudioRecommended() && mAudioApi != AudioApi::OpenSLES) {
stream = new AudioStreamAAudio(*this);
} else if (isAAudioSupported() && mAudioApi == AudioApi::AAudio) {
stream = new AudioStreamAAudio(*this);
LOGE("Creating AAudio stream on 8.0 because it was specified. This is error prone.");
} else {
if (getDirection() == oboe::Direction::Output) {
stream = new AudioOutputStreamOpenSLES(*this);
} else if (getDirection() == oboe::Direction::Input) {
stream = new AudioInputStreamOpenSLES(*this);
}
}
return stream;
}
bool AudioStreamBuilder::isCompatible(AudioStreamBase &other) {
return (getSampleRate() == oboe::Unspecified || getSampleRate() == other.getSampleRate())
&& (getFormat() == (AudioFormat)oboe::Unspecified || getFormat() == other.getFormat())
&& (getFramesPerDataCallback() == oboe::Unspecified || getFramesPerDataCallback() == other.getFramesPerDataCallback())
&& (getChannelCount() == oboe::Unspecified || getChannelCount() == other.getChannelCount());
}
Result AudioStreamBuilder::openStream(AudioStream **streamPP) {
auto result = isValidConfig();
if (result != Result::OK) {
LOGW("%s() invalid config %d", __func__, result);
return result;
}
LOGI("%s() %s -------- %s --------",
__func__, getDirection() == Direction::Input ? "INPUT" : "OUTPUT", getVersionText());
if (streamPP == nullptr) {
return Result::ErrorNull;
}
*streamPP = nullptr;
AudioStream *streamP = nullptr;
// Maybe make a FilterInputStream.
AudioStreamBuilder childBuilder(*this);
// Check need for conversion and modify childBuilder for optimal stream.
bool conversionNeeded = QuirksManager::getInstance().isConversionNeeded(*this, childBuilder);
// Do we need to make a child stream and convert.
if (conversionNeeded) {
AudioStream *tempStream;
result = childBuilder.openStream(&tempStream);
if (result != Result::OK) {
return result;
}
if (isCompatible(*tempStream)) {
// The child stream would work as the requested stream so we can just use it directly.
*streamPP = tempStream;
return result;
} else {
AudioStreamBuilder parentBuilder = *this;
// Build a stream that is as close as possible to the childStream.
if (getFormat() == oboe::AudioFormat::Unspecified) {
parentBuilder.setFormat(tempStream->getFormat());
}
if (getChannelCount() == oboe::Unspecified) {
parentBuilder.setChannelCount(tempStream->getChannelCount());
}
if (getSampleRate() == oboe::Unspecified) {
parentBuilder.setSampleRate(tempStream->getSampleRate());
}
if (getFramesPerDataCallback() == oboe::Unspecified) {
parentBuilder.setFramesPerCallback(tempStream->getFramesPerDataCallback());
}
// Use childStream in a FilterAudioStream.
LOGI("%s() create a FilterAudioStream for data conversion.", __func__);
FilterAudioStream *filterStream = new FilterAudioStream(parentBuilder, tempStream);
result = filterStream->configureFlowGraph();
if (result != Result::OK) {
filterStream->close();
delete filterStream;
// Just open streamP the old way.
} else {
streamP = static_cast<AudioStream *>(filterStream);
}
}
}
if (streamP == nullptr) {
streamP = build();
if (streamP == nullptr) {
return Result::ErrorNull;
}
}
// If MMAP has a problem in this case then disable it temporarily.
bool wasMMapOriginallyEnabled = AAudioExtensions::getInstance().isMMapEnabled();
bool wasMMapTemporarilyDisabled = false;
if (wasMMapOriginallyEnabled) {
bool isMMapSafe = QuirksManager::getInstance().isMMapSafe(childBuilder);
if (!isMMapSafe) {
AAudioExtensions::getInstance().setMMapEnabled(false);
wasMMapTemporarilyDisabled = true;
}
}
result = streamP->open();
if (wasMMapTemporarilyDisabled) {
AAudioExtensions::getInstance().setMMapEnabled(wasMMapOriginallyEnabled); // restore original
}
if (result == Result::OK) {
int32_t optimalBufferSize = -1;
// Use a reasonable default buffer size.
if (streamP->getDirection() == Direction::Input) {
// For input, small size does not improve latency because the stream is usually
// run close to empty. And a low size can result in XRuns so always use the maximum.
optimalBufferSize = streamP->getBufferCapacityInFrames();
} else if (streamP->getPerformanceMode() == PerformanceMode::LowLatency
&& streamP->getDirection() == Direction::Output) { // Output check is redundant.
optimalBufferSize = streamP->getFramesPerBurst() *
kBufferSizeInBurstsForLowLatencyStreams;
}
if (optimalBufferSize >= 0) {
auto setBufferResult = streamP->setBufferSizeInFrames(optimalBufferSize);
if (!setBufferResult) {
LOGW("Failed to setBufferSizeInFrames(%d). Error was %s",
optimalBufferSize,
convertToText(setBufferResult.error()));
}
}
*streamPP = streamP;
} else {
delete streamP;
}
return result;
}
Result AudioStreamBuilder::openManagedStream(oboe::ManagedStream &stream) {
stream.reset();
AudioStream *streamptr;
auto result = openStream(&streamptr);
stream.reset(streamptr);
return result;
}
Result AudioStreamBuilder::openStream(std::shared_ptr<AudioStream> &sharedStream) {
sharedStream.reset();
AudioStream *streamptr;
auto result = openStream(&streamptr);
if (result == Result::OK) {
sharedStream.reset(streamptr);
// Save a weak_ptr in the stream for use with callbacks.
streamptr->setWeakThis(sharedStream);
}
return result;
}
} // namespace oboe

View File

@ -0,0 +1,267 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include "OboeDebug.h"
#include "DataConversionFlowGraph.h"
#include "SourceFloatCaller.h"
#include "SourceI16Caller.h"
#include "SourceI24Caller.h"
#include "SourceI32Caller.h"
#include <flowgraph/ClipToRange.h>
#include <flowgraph/MonoToMultiConverter.h>
#include <flowgraph/MultiToMonoConverter.h>
#include <flowgraph/RampLinear.h>
#include <flowgraph/SinkFloat.h>
#include <flowgraph/SinkI16.h>
#include <flowgraph/SinkI24.h>
#include <flowgraph/SinkI32.h>
#include <flowgraph/SourceFloat.h>
#include <flowgraph/SourceI16.h>
#include <flowgraph/SourceI24.h>
#include <flowgraph/SourceI32.h>
#include <flowgraph/SampleRateConverter.h>
using namespace oboe;
using namespace flowgraph;
using namespace resampler;
void DataConversionFlowGraph::setSource(const void *buffer, int32_t numFrames) {
mSource->setData(buffer, numFrames);
}
static MultiChannelResampler::Quality convertOboeSRQualityToMCR(SampleRateConversionQuality quality) {
switch (quality) {
case SampleRateConversionQuality::Fastest:
return MultiChannelResampler::Quality::Fastest;
case SampleRateConversionQuality::Low:
return MultiChannelResampler::Quality::Low;
default:
case SampleRateConversionQuality::Medium:
return MultiChannelResampler::Quality::Medium;
case SampleRateConversionQuality::High:
return MultiChannelResampler::Quality::High;
case SampleRateConversionQuality::Best:
return MultiChannelResampler::Quality::Best;
}
}
// Chain together multiple processors.
// Callback Output
// Use SourceCaller that calls original app callback from the flowgraph.
// The child callback from FilteredAudioStream read()s from the flowgraph.
// Callback Input
// Child callback from FilteredAudioStream writes()s to the flowgraph.
// The output of the flowgraph goes through a BlockWriter to the app callback.
// Blocking Write
// Write buffer is set on an AudioSource.
// Data is pulled through the graph and written to the child stream.
// Blocking Read
// Reads in a loop from the flowgraph Sink to fill the read buffer.
// A SourceCaller then does a blocking read from the child Stream.
//
Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream *sinkStream) {
FlowGraphPortFloatOutput *lastOutput = nullptr;
bool isOutput = sourceStream->getDirection() == Direction::Output;
bool isInput = !isOutput;
mFilterStream = isOutput ? sourceStream : sinkStream;
AudioFormat sourceFormat = sourceStream->getFormat();
int32_t sourceChannelCount = sourceStream->getChannelCount();
int32_t sourceSampleRate = sourceStream->getSampleRate();
int32_t sourceFramesPerCallback = sourceStream->getFramesPerDataCallback();
AudioFormat sinkFormat = sinkStream->getFormat();
int32_t sinkChannelCount = sinkStream->getChannelCount();
int32_t sinkSampleRate = sinkStream->getSampleRate();
int32_t sinkFramesPerCallback = sinkStream->getFramesPerDataCallback();
LOGI("%s() flowgraph converts channels: %d to %d, format: %d to %d"
", rate: %d to %d, cbsize: %d to %d, qual = %d",
__func__,
sourceChannelCount, sinkChannelCount,
sourceFormat, sinkFormat,
sourceSampleRate, sinkSampleRate,
sourceFramesPerCallback, sinkFramesPerCallback,
sourceStream->getSampleRateConversionQuality());
// Source
// IF OUTPUT and using a callback then call back to the app using a SourceCaller.
// OR IF INPUT and NOT using a callback then read from the child stream using a SourceCaller.
bool isDataCallbackSpecified = sourceStream->isDataCallbackSpecified();
if ((isDataCallbackSpecified && isOutput)
|| (!isDataCallbackSpecified && isInput)) {
int32_t actualSourceFramesPerCallback = (sourceFramesPerCallback == kUnspecified)
? sourceStream->getFramesPerBurst()
: sourceFramesPerCallback;
switch (sourceFormat) {
case AudioFormat::Float:
mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I16:
mSourceCaller = std::make_unique<SourceI16Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I24:
mSourceCaller = std::make_unique<SourceI24Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I32:
mSourceCaller = std::make_unique<SourceI32Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
default:
LOGE("%s() Unsupported source caller format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
mSourceCaller->setStream(sourceStream);
lastOutput = &mSourceCaller->output;
} else {
// IF OUTPUT and NOT using a callback then write to the child stream using a BlockWriter.
// OR IF INPUT and using a callback then write to the app using a BlockWriter.
switch (sourceFormat) {
case AudioFormat::Float:
mSource = std::make_unique<SourceFloat>(sourceChannelCount);
break;
case AudioFormat::I16:
mSource = std::make_unique<SourceI16>(sourceChannelCount);
break;
case AudioFormat::I24:
mSource = std::make_unique<SourceI24>(sourceChannelCount);
break;
case AudioFormat::I32:
mSource = std::make_unique<SourceI32>(sourceChannelCount);
break;
default:
LOGE("%s() Unsupported source format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
if (isInput) {
int32_t actualSinkFramesPerCallback = (sinkFramesPerCallback == kUnspecified)
? sinkStream->getFramesPerBurst()
: sinkFramesPerCallback;
// The BlockWriter is after the Sink so use the SinkStream size.
mBlockWriter.open(actualSinkFramesPerCallback * sinkStream->getBytesPerFrame());
mAppBuffer = std::make_unique<uint8_t[]>(
kDefaultBufferSize * sinkStream->getBytesPerFrame());
}
lastOutput = &mSource->output;
}
// If we are going to reduce the number of channels then do it before the
// sample rate converter.
if (sourceChannelCount > sinkChannelCount) {
if (sinkChannelCount == 1) {
mMultiToMonoConverter = std::make_unique<MultiToMonoConverter>(sourceChannelCount);
lastOutput->connect(&mMultiToMonoConverter->input);
lastOutput = &mMultiToMonoConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sample Rate conversion
if (sourceSampleRate != sinkSampleRate) {
// Create a resampler to do the math.
mResampler.reset(MultiChannelResampler::make(lastOutput->getSamplesPerFrame(),
sourceSampleRate,
sinkSampleRate,
convertOboeSRQualityToMCR(
sourceStream->getSampleRateConversionQuality())));
// Make a flowgraph node that uses the resampler.
mRateConverter = std::make_unique<SampleRateConverter>(lastOutput->getSamplesPerFrame(),
*mResampler.get());
lastOutput->connect(&mRateConverter->input);
lastOutput = &mRateConverter->output;
}
// Expand the number of channels if required.
if (sourceChannelCount < sinkChannelCount) {
if (sourceChannelCount == 1) {
mMonoToMultiConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
lastOutput->connect(&mMonoToMultiConverter->input);
lastOutput = &mMonoToMultiConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sink
switch (sinkFormat) {
case AudioFormat::Float:
mSink = std::make_unique<SinkFloat>(sinkChannelCount);
break;
case AudioFormat::I16:
mSink = std::make_unique<SinkI16>(sinkChannelCount);
break;
case AudioFormat::I24:
mSink = std::make_unique<SinkI24>(sinkChannelCount);
break;
case AudioFormat::I32:
mSink = std::make_unique<SinkI32>(sinkChannelCount);
break;
default:
LOGE("%s() Unsupported sink format = %d", __func__, sinkFormat);
return Result::ErrorIllegalArgument;;
}
lastOutput->connect(&mSink->input);
return Result::OK;
}
int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames, int64_t timeoutNanos) {
if (mSourceCaller) {
mSourceCaller->setTimeoutNanos(timeoutNanos);
}
int32_t numRead = mSink->read(buffer, numFrames);
return numRead;
}
// This is similar to pushing data through the flowgraph.
int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) {
// Put the data from the input at the head of the flowgraph.
mSource->setData(inputBuffer, numFrames);
while (true) {
// Pull and read some data in app format into a small buffer.
int32_t framesRead = mSink->read(mAppBuffer.get(), flowgraph::kDefaultBufferSize);
if (framesRead <= 0) break;
// Write to a block adapter, which will call the destination whenever it has enough data.
int32_t bytesRead = mBlockWriter.write(mAppBuffer.get(),
framesRead * mFilterStream->getBytesPerFrame());
if (bytesRead < 0) return bytesRead; // TODO review
}
return numFrames;
}
int32_t DataConversionFlowGraph::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
int32_t numFrames = numBytes / mFilterStream->getBytesPerFrame();
mCallbackResult = mFilterStream->getDataCallback()->onAudioReady(mFilterStream, buffer, numFrames);
// TODO handle STOP from callback, process data remaining in the block adapter
return numBytes;
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_OBOE_FLOW_GRAPH_H
#define OBOE_OBOE_FLOW_GRAPH_H
#include <memory>
#include <stdint.h>
#include <sys/types.h>
#include <flowgraph/ChannelCountConverter.h>
#include <flowgraph/MonoToMultiConverter.h>
#include <flowgraph/MultiToMonoConverter.h>
#include <flowgraph/SampleRateConverter.h>
#include <oboe/Definitions.h>
#include "AudioSourceCaller.h"
#include "FixedBlockWriter.h"
namespace oboe {
class AudioStream;
class AudioSourceCaller;
/**
* Convert PCM channels, format and sample rate for optimal latency.
*/
class DataConversionFlowGraph : public FixedBlockProcessor {
public:
DataConversionFlowGraph()
: mBlockWriter(*this) {}
void setSource(const void *buffer, int32_t numFrames);
/** Connect several modules together to convert from source to sink.
* This should only be called once for each instance.
*
* @param sourceFormat
* @param sourceChannelCount
* @param sinkFormat
* @param sinkChannelCount
* @return
*/
oboe::Result configure(oboe::AudioStream *sourceStream, oboe::AudioStream *sinkStream);
int32_t read(void *buffer, int32_t numFrames, int64_t timeoutNanos);
int32_t write(void *buffer, int32_t numFrames);
int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
DataCallbackResult getDataCallbackResult() {
return mCallbackResult;
}
private:
std::unique_ptr<flowgraph::FlowGraphSourceBuffered> mSource;
std::unique_ptr<AudioSourceCaller> mSourceCaller;
std::unique_ptr<flowgraph::MonoToMultiConverter> mMonoToMultiConverter;
std::unique_ptr<flowgraph::MultiToMonoConverter> mMultiToMonoConverter;
std::unique_ptr<flowgraph::ChannelCountConverter> mChannelCountConverter;
std::unique_ptr<resampler::MultiChannelResampler> mResampler;
std::unique_ptr<flowgraph::SampleRateConverter> mRateConverter;
std::unique_ptr<flowgraph::FlowGraphSink> mSink;
FixedBlockWriter mBlockWriter;
DataCallbackResult mCallbackResult = DataCallbackResult::Continue;
AudioStream *mFilterStream = nullptr;
std::unique_ptr<uint8_t[]> mAppBuffer;
};
}
#endif //OBOE_OBOE_FLOW_GRAPH_H

View File

@ -0,0 +1,106 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include "OboeDebug.h"
#include "FilterAudioStream.h"
using namespace oboe;
using namespace flowgraph;
// Output callback uses FixedBlockReader::read()
// <= SourceFloatCaller::onProcess()
// <=== DataConversionFlowGraph::read()
// <== FilterAudioStream::onAudioReady()
//
// Output blocking uses no block adapter because AAudio can accept
// writes of any size. It uses DataConversionFlowGraph::read() <== FilterAudioStream::write() <= app
//
// Input callback uses FixedBlockWriter::write()
// <= DataConversionFlowGraph::write()
// <= FilterAudioStream::onAudioReady()
//
// Input blocking uses FixedBlockReader::read() // TODO may not need block adapter
// <= SourceFloatCaller::onProcess()
// <=== SinkFloat::read()
// <= DataConversionFlowGraph::read()
// <== FilterAudioStream::read()
// <= app
Result FilterAudioStream::configureFlowGraph() {
mFlowGraph = std::make_unique<DataConversionFlowGraph>();
bool isOutput = getDirection() == Direction::Output;
AudioStream *sourceStream = isOutput ? this : mChildStream.get();
AudioStream *sinkStream = isOutput ? mChildStream.get() : this;
mRateScaler = ((double) getSampleRate()) / mChildStream->getSampleRate();
return mFlowGraph->configure(sourceStream, sinkStream);
}
// Put the data to be written at the source end of the flowgraph.
// Then read (pull) the data from the flowgraph and write it to the
// child stream.
ResultWithValue<int32_t> FilterAudioStream::write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
int32_t framesWritten = 0;
mFlowGraph->setSource(buffer, numFrames);
while (true) {
int32_t numRead = mFlowGraph->read(mBlockingBuffer.get(),
getFramesPerBurst(),
timeoutNanoseconds);
if (numRead < 0) {
return ResultWithValue<int32_t>::createBasedOnSign(numRead);
}
if (numRead == 0) {
break; // finished processing the source buffer
}
auto writeResult = mChildStream->write(mBlockingBuffer.get(),
numRead,
timeoutNanoseconds);
if (!writeResult) {
return writeResult;
}
framesWritten += writeResult.value();
}
return ResultWithValue<int32_t>::createBasedOnSign(framesWritten);
}
// Read (pull) the data we want from the sink end of the flowgraph.
// The necessary data will be read from the child stream using a flowgraph callback.
ResultWithValue<int32_t> FilterAudioStream::read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
int32_t framesRead = mFlowGraph->read(buffer, numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(framesRead);
}
DataCallbackResult FilterAudioStream::onAudioReady(AudioStream *oboeStream,
void *audioData,
int32_t numFrames) {
int32_t framesProcessed;
if (oboeStream->getDirection() == Direction::Output) {
framesProcessed = mFlowGraph->read(audioData, numFrames, 0 /* timeout */);
} else {
framesProcessed = mFlowGraph->write(audioData, numFrames);
}
return (framesProcessed < numFrames)
? DataCallbackResult::Stop
: mFlowGraph->getDataCallbackResult();
}

View File

@ -0,0 +1,223 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_FILTER_AUDIO_STREAM_H
#define OBOE_FILTER_AUDIO_STREAM_H
#include <memory>
#include <oboe/AudioStream.h>
#include "DataConversionFlowGraph.h"
namespace oboe {
/**
* An AudioStream that wraps another AudioStream and provides audio data conversion.
* Operations may include channel conversion, data format conversion and/or sample rate conversion.
*/
class FilterAudioStream : public AudioStream, AudioStreamCallback {
public:
/**
* Construct an `AudioStream` using the given `AudioStreamBuilder` and a child AudioStream.
*
* This should only be called internally by AudioStreamBuilder.
* Ownership of childStream will be passed to this object.
*
* @param builder containing all the stream's attributes
*/
FilterAudioStream(const AudioStreamBuilder &builder, AudioStream *childStream)
: AudioStream(builder)
, mChildStream(childStream) {
// Intercept the callback if used.
if (builder.isErrorCallbackSpecified()) {
mErrorCallback = mChildStream->swapErrorCallback(this);
}
if (builder.isDataCallbackSpecified()) {
mDataCallback = mChildStream->swapDataCallback(this);
} else {
const int size = childStream->getFramesPerBurst() * childStream->getBytesPerFrame();
mBlockingBuffer = std::make_unique<uint8_t[]>(size);
}
// Copy parameters that may not match builder.
mBufferCapacityInFrames = mChildStream->getBufferCapacityInFrames();
mPerformanceMode = mChildStream->getPerformanceMode();
mInputPreset = mChildStream->getInputPreset();
mFramesPerBurst = mChildStream->getFramesPerBurst();
mDeviceId = mChildStream->getDeviceId();
}
virtual ~FilterAudioStream() = default;
AudioStream *getChildStream() const {
return mChildStream.get();
}
Result configureFlowGraph();
// Close child and parent.
Result close() override {
const Result result1 = mChildStream->close();
const Result result2 = AudioStream::close();
return (result1 != Result::OK ? result1 : result2);
}
/**
* Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `start(0)`.
*/
Result requestStart() override {
return mChildStream->requestStart();
}
/**
* Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `pause(0)`.
*/
Result requestPause() override {
return mChildStream->requestPause();
}
/**
* Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `flush(0)`.
*/
Result requestFlush() override {
return mChildStream->requestFlush();
}
/**
* Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `stop(0)`.
*/
Result requestStop() override {
return mChildStream->requestStop();
}
ResultWithValue<int32_t> read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
StreamState getState() override {
return mChildStream->getState();
}
Result waitForStateChange(
StreamState inputState,
StreamState *nextState,
int64_t timeoutNanoseconds) override {
return mChildStream->waitForStateChange(inputState, nextState, timeoutNanoseconds);
}
bool isXRunCountSupported() const override {
return mChildStream->isXRunCountSupported();
}
AudioApi getAudioApi() const override {
return mChildStream->getAudioApi();
}
void updateFramesWritten() override {
// TODO for output, just count local writes?
mFramesWritten = static_cast<int64_t>(mChildStream->getFramesWritten() * mRateScaler);
}
void updateFramesRead() override {
// TODO for input, just count local reads?
mFramesRead = static_cast<int64_t>(mChildStream->getFramesRead() * mRateScaler);
}
void *getUnderlyingStream() const override {
return mChildStream->getUnderlyingStream();
}
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override {
return mChildStream->setBufferSizeInFrames(requestedFrames);
}
int32_t getBufferSizeInFrames() override {
mBufferSizeInFrames = mChildStream->getBufferSizeInFrames();
return mBufferSizeInFrames;
}
ResultWithValue<int32_t> getXRunCount() override {
return mChildStream->getXRunCount();
}
ResultWithValue<double> calculateLatencyMillis() override {
// This will automatically include the latency of the flowgraph?
return mChildStream->calculateLatencyMillis();
}
Result getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override {
int64_t childPosition = 0;
Result result = mChildStream->getTimestamp(clockId, &childPosition, timeNanoseconds);
// It is OK if framePosition is null.
if (framePosition) {
*framePosition = childPosition * mRateScaler;
}
return result;
}
DataCallbackResult onAudioReady(AudioStream *oboeStream,
void *audioData,
int32_t numFrames) override;
bool onError(AudioStream * /*audioStream*/, Result error) override {
if (mErrorCallback != nullptr) {
return mErrorCallback->onError(this, error);
}
return false;
}
void onErrorBeforeClose(AudioStream * /*oboeStream*/, Result error) override {
if (mErrorCallback != nullptr) {
mErrorCallback->onErrorBeforeClose(this, error);
}
}
void onErrorAfterClose(AudioStream * /*oboeStream*/, Result error) override {
// Close this parent stream because the callback will only close the child.
AudioStream::close();
if (mErrorCallback != nullptr) {
mErrorCallback->onErrorAfterClose(this, error);
}
}
/**
* @return last result passed from an error callback
*/
oboe::Result getLastErrorCallbackResult() const override {
return mChildStream->getLastErrorCallbackResult();
}
private:
std::unique_ptr<AudioStream> mChildStream; // this stream wraps the child stream
std::unique_ptr<DataConversionFlowGraph> mFlowGraph; // for converting data
std::unique_ptr<uint8_t[]> mBlockingBuffer; // temp buffer for write()
double mRateScaler = 1.0; // ratio parent/child sample rates
};
} // oboe
#endif //OBOE_FILTER_AUDIO_STREAM_H

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include "FixedBlockAdapter.h"
FixedBlockAdapter::~FixedBlockAdapter() {
}
int32_t FixedBlockAdapter::open(int32_t bytesPerFixedBlock)
{
mSize = bytesPerFixedBlock;
mStorage = std::make_unique<uint8_t[]>(bytesPerFixedBlock);
mPosition = 0;
return 0;
}
int32_t FixedBlockAdapter::close()
{
mStorage.reset(nullptr);
mSize = 0;
mPosition = 0;
return 0;
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AAUDIO_FIXED_BLOCK_ADAPTER_H
#define AAUDIO_FIXED_BLOCK_ADAPTER_H
#include <memory>
#include <stdint.h>
#include <sys/types.h>
/**
* Interface for a class that needs fixed-size blocks.
*/
class FixedBlockProcessor {
public:
virtual ~FixedBlockProcessor() = default;
/**
*
* @param buffer Pointer to first byte of data.
* @param numBytes This will be a fixed size specified in FixedBlockAdapter::open().
* @return Number of bytes processed or a negative error code.
*/
virtual int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) = 0;
};
/**
* Base class for a variable-to-fixed-size block adapter.
*/
class FixedBlockAdapter
{
public:
FixedBlockAdapter(FixedBlockProcessor &fixedBlockProcessor)
: mFixedBlockProcessor(fixedBlockProcessor) {}
virtual ~FixedBlockAdapter();
/**
* Allocate internal resources needed for buffering data.
*/
virtual int32_t open(int32_t bytesPerFixedBlock);
/**
* Free internal resources.
*/
int32_t close();
protected:
FixedBlockProcessor &mFixedBlockProcessor;
std::unique_ptr<uint8_t[]> mStorage; // Store data here while assembling buffers.
int32_t mSize = 0; // Size in bytes of the fixed size buffer.
int32_t mPosition = 0; // Offset of the last byte read or written.
};
#endif /* AAUDIO_FIXED_BLOCK_ADAPTER_H */

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <memory.h>
#include "FixedBlockAdapter.h"
#include "FixedBlockReader.h"
FixedBlockReader::FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor)
: FixedBlockAdapter(fixedBlockProcessor) {
mPosition = mSize;
}
int32_t FixedBlockReader::open(int32_t bytesPerFixedBlock) {
int32_t result = FixedBlockAdapter::open(bytesPerFixedBlock);
mPosition = 0;
mValid = 0;
return result;
}
int32_t FixedBlockReader::readFromStorage(uint8_t *buffer, int32_t numBytes) {
int32_t bytesToRead = numBytes;
int32_t dataAvailable = mValid - mPosition;
if (bytesToRead > dataAvailable) {
bytesToRead = dataAvailable;
}
memcpy(buffer, mStorage.get() + mPosition, bytesToRead);
mPosition += bytesToRead;
return bytesToRead;
}
int32_t FixedBlockReader::read(uint8_t *buffer, int32_t numBytes) {
int32_t bytesRead;
int32_t bytesLeft = numBytes;
while(bytesLeft > 0) {
if (mPosition < mValid) {
// Use up bytes currently in storage.
bytesRead = readFromStorage(buffer, bytesLeft);
buffer += bytesRead;
bytesLeft -= bytesRead;
} else if (bytesLeft >= mSize) {
// Nothing in storage. Read through if enough for a complete block.
bytesRead = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
if (bytesRead < 0) return bytesRead;
buffer += bytesRead;
bytesLeft -= bytesRead;
} else {
// Just need a partial block so we have to reload storage.
bytesRead = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize);
if (bytesRead < 0) return bytesRead;
mPosition = 0;
mValid = bytesRead;
if (bytesRead == 0) break;
}
}
return numBytes - bytesLeft;
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AAUDIO_FIXED_BLOCK_READER_H
#define AAUDIO_FIXED_BLOCK_READER_H
#include <stdint.h>
#include "FixedBlockAdapter.h"
/**
* Read from a fixed-size block to a variable sized block.
*
* This can be used to convert a pull data flow from fixed sized buffers to variable sized buffers.
* An example would be an audio output callback that reads from the app.
*/
class FixedBlockReader : public FixedBlockAdapter
{
public:
FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor);
virtual ~FixedBlockReader() = default;
int32_t open(int32_t bytesPerFixedBlock) override;
/**
* Read into a variable sized block.
*
* Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
* must have the same alignment.
* For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
* blocks must also be a multiple of 8.
*
* @param buffer
* @param numBytes
* @return Number of bytes read or a negative error code.
*/
int32_t read(uint8_t *buffer, int32_t numBytes);
private:
int32_t readFromStorage(uint8_t *buffer, int32_t numBytes);
int32_t mValid = 0; // Number of valid bytes in mStorage.
};
#endif /* AAUDIO_FIXED_BLOCK_READER_H */

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <memory.h>
#include "FixedBlockAdapter.h"
#include "FixedBlockWriter.h"
FixedBlockWriter::FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor)
: FixedBlockAdapter(fixedBlockProcessor) {}
int32_t FixedBlockWriter::writeToStorage(uint8_t *buffer, int32_t numBytes) {
int32_t bytesToStore = numBytes;
int32_t roomAvailable = mSize - mPosition;
if (bytesToStore > roomAvailable) {
bytesToStore = roomAvailable;
}
memcpy(mStorage.get() + mPosition, buffer, bytesToStore);
mPosition += bytesToStore;
return bytesToStore;
}
int32_t FixedBlockWriter::write(uint8_t *buffer, int32_t numBytes) {
int32_t bytesLeft = numBytes;
// If we already have data in storage then add to it.
if (mPosition > 0) {
int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
buffer += bytesWritten;
bytesLeft -= bytesWritten;
// If storage full then flush it out
if (mPosition == mSize) {
bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize);
if (bytesWritten < 0) return bytesWritten;
mPosition = 0;
if (bytesWritten < mSize) {
// Only some of the data was written! This should not happen.
return -1;
}
}
}
// Write through if enough for a complete block.
while(bytesLeft > mSize) {
int32_t bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
if (bytesWritten < 0) return bytesWritten;
buffer += bytesWritten;
bytesLeft -= bytesWritten;
}
// Save any remaining partial blocks for next time.
if (bytesLeft > 0) {
int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
bytesLeft -= bytesWritten;
}
return numBytes - bytesLeft;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AAUDIO_FIXED_BLOCK_WRITER_H
#define AAUDIO_FIXED_BLOCK_WRITER_H
#include <stdint.h>
#include "FixedBlockAdapter.h"
/**
* This can be used to convert a push data flow from variable sized buffers to fixed sized buffers.
* An example would be an audio input callback.
*/
class FixedBlockWriter : public FixedBlockAdapter
{
public:
FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor);
virtual ~FixedBlockWriter() = default;
/**
* Write from a variable sized block.
*
* Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
* must have the same alignment.
* For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
* blocks must also be a multiple of 8.
*
* @param buffer
* @param numBytes
* @return Number of bytes written or a negative error code.
*/
int32_t write(uint8_t *buffer, int32_t numBytes);
private:
int32_t writeToStorage(uint8_t *buffer, int32_t numBytes);
};
#endif /* AAUDIO_FIXED_BLOCK_WRITER_H */

View File

@ -0,0 +1,108 @@
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oboe/LatencyTuner.h"
using namespace oboe;
LatencyTuner::LatencyTuner(AudioStream &stream)
: LatencyTuner(stream, stream.getBufferCapacityInFrames()) {
}
LatencyTuner::LatencyTuner(oboe::AudioStream &stream, int32_t maximumBufferSize)
: mStream(stream)
, mMaxBufferSize(maximumBufferSize) {
int32_t burstSize = stream.getFramesPerBurst();
setMinimumBufferSize(kDefaultNumBursts * burstSize);
setBufferSizeIncrement(burstSize);
reset();
}
Result LatencyTuner::tune() {
if (mState == State::Unsupported) {
return Result::ErrorUnimplemented;
}
Result result = Result::OK;
// Process reset requests.
int32_t numRequests = mLatencyTriggerRequests.load();
if (numRequests != mLatencyTriggerResponses.load()) {
mLatencyTriggerResponses.store(numRequests);
reset();
}
// Set state to Active if the idle countdown has reached zero.
if (mState == State::Idle && --mIdleCountDown <= 0) {
mState = State::Active;
}
// When state is Active attempt to change the buffer size if the number of xRuns has increased.
if (mState == State::Active) {
auto xRunCountResult = mStream.getXRunCount();
if (xRunCountResult == Result::OK) {
if ((xRunCountResult.value() - mPreviousXRuns) > 0) {
mPreviousXRuns = xRunCountResult.value();
int32_t oldBufferSize = mStream.getBufferSizeInFrames();
int32_t requestedBufferSize = oldBufferSize + getBufferSizeIncrement();
// Do not request more than the maximum buffer size (which was either user-specified
// or was from stream->getBufferCapacityInFrames())
if (requestedBufferSize > mMaxBufferSize) requestedBufferSize = mMaxBufferSize;
// Note that this will not allocate more memory. It simply determines
// how much of the existing buffer capacity will be used. The size will be
// clipped to the bufferCapacity by AAudio.
auto setBufferResult = mStream.setBufferSizeInFrames(requestedBufferSize);
if (setBufferResult != Result::OK) {
result = setBufferResult;
mState = State::Unsupported;
} else if (setBufferResult.value() == oldBufferSize) {
mState = State::AtMax;
}
}
} else {
mState = State::Unsupported;
}
}
if (mState == State::Unsupported) {
result = Result::ErrorUnimplemented;
}
if (mState == State::AtMax) {
result = Result::OK;
}
return result;
}
void LatencyTuner::requestReset() {
if (mState != State::Unsupported) {
mLatencyTriggerRequests++;
}
}
void LatencyTuner::reset() {
mState = State::Idle;
mIdleCountDown = kIdleCount;
// Set to minimal latency
mStream.setBufferSizeInFrames(getMinimumBufferSize());
}
bool LatencyTuner::isAtMaximumBufferSize() {
return mState == State::AtMax;
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COMMON_MONOTONIC_COUNTER_H
#define COMMON_MONOTONIC_COUNTER_H
#include <cstdint>
/**
* Maintain a 64-bit monotonic counter.
* Can be used to track a 32-bit counter that wraps or gets reset.
*
* Note that this is not atomic and has no interior locks.
* A caller will need to provide their own exterior locking
* if they need to use it from multiple threads.
*/
class MonotonicCounter {
public:
MonotonicCounter() {}
virtual ~MonotonicCounter() {}
/**
* @return current value of the counter
*/
int64_t get() const {
return mCounter64;
}
/**
* set the current value of the counter
*/
void set(int64_t counter) {
mCounter64 = counter;
}
/**
* Advance the counter if delta is positive.
* @return current value of the counter
*/
int64_t increment(int64_t delta) {
if (delta > 0) {
mCounter64 += delta;
}
return mCounter64;
}
/**
* Advance the 64-bit counter if (current32 - previousCurrent32) > 0.
* This can be used to convert a 32-bit counter that may be wrapping into
* a monotonic 64-bit counter.
*
* This counter32 should NOT be allowed to advance by more than 0x7FFFFFFF between calls.
* Think of the wrapping counter like a sine wave. If the frequency of the signal
* is more than half the sampling rate (Nyquist rate) then you cannot measure it properly.
* If the counter wraps around every 24 hours then we should measure it with a period
* of less than 12 hours.
*
* @return current value of the 64-bit counter
*/
int64_t update32(int32_t counter32) {
int32_t delta = counter32 - mCounter32;
// protect against the mCounter64 going backwards
if (delta > 0) {
mCounter64 += delta;
mCounter32 = counter32;
}
return mCounter64;
}
/**
* Reset the stored value of the 32-bit counter.
* This is used if your counter32 has been reset to zero.
*/
void reset32() {
mCounter32 = 0;
}
/**
* Round 64-bit counter up to a multiple of the period.
*
* The period must be positive.
*
* @param period might be, for example, a buffer capacity
*/
void roundUp64(int32_t period) {
if (period > 0) {
int64_t numPeriods = (mCounter64 + period - 1) / period;
mCounter64 = numPeriods * period;
}
}
private:
int64_t mCounter64 = 0;
int32_t mCounter32 = 0;
};
#endif //COMMON_MONOTONIC_COUNTER_H

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef OBOE_DEBUG_H
#define OBOE_DEBUG_H
#include <android/log.h>
#ifndef MODULE_NAME
#define MODULE_NAME "OboeAudio"
#endif
// Always log INFO and errors.
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)
#if OBOE_ENABLE_LOGGING
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
#else
#define LOGV(...)
#define LOGD(...)
#endif
#endif //OBOE_DEBUG_H

View File

@ -0,0 +1,251 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <oboe/AudioStreamBuilder.h>
#include <oboe/Oboe.h>
#include "OboeDebug.h"
#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;
}
bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
bool isSampleRateCompatible =
builder.getSampleRate() == oboe::Unspecified
|| builder.getSampleRate() == kCommonNativeRate
|| builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
return builder.getPerformanceMode() == PerformanceMode::LowLatency
&& isSampleRateCompatible
&& builder.getChannelCount() <= kChannelCountStereo;
}
class SamsungDeviceQuirks : public QuirksManager::DeviceQuirks {
public:
SamsungDeviceQuirks() {
std::string arch = getPropertyString("ro.arch");
isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
std::string chipname = getPropertyString("ro.hardware.chipname");
isExynos9810 = (chipname == "exynos9810");
isExynos990 = (chipname == "exynos990");
isExynos850 = (chipname == "exynos850");
mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
}
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;
}
// See Oboe issues #824 and #1247 for more information.
bool isMonoMMapActuallyStereo() const override {
return isExynos9810 || isExynos850; // TODO We can make this version specific if it gets fixed.
}
bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
return DeviceQuirks::isAAudioMMapPossible(builder)
// Samsung says they use Legacy for Camcorder
&& builder.getInputPreset() != oboe::InputPreset::Camcorder;
}
bool isMMapSafe(const AudioStreamBuilder &builder) override {
const bool isInput = builder.getDirection() == Direction::Input;
// This detects b/159066712 , S20 LSI has corrupt low latency audio recording
// and turns off MMAP.
// See also https://github.com/google/oboe/issues/892
bool isRecordingCorrupted = isInput
&& isExynos990
&& mBuildChangelist < 19350896;
// Certain S9+ builds record silence when using MMAP and not using the VoiceCommunication
// preset.
// See https://github.com/google/oboe/issues/1110
bool wouldRecordSilence = isInput
&& isExynos9810
&& mBuildChangelist <= 18847185
&& (builder.getInputPreset() != InputPreset::VoiceCommunication);
if (wouldRecordSilence){
LOGI("QuirksManager::%s() Requested stream configuration would result in silence on "
"this device. Switching off MMAP.", __func__);
}
return !isRecordingCorrupted && !wouldRecordSilence;
}
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;
bool isExynos9810 = false;
bool isExynos990 = false;
bool isExynos850 = false;
int mBuildChangelist = 0;
};
QuirksManager::QuirksManager() {
std::string manufacturer = getPropertyString("ro.product.manufacturer");
if (manufacturer == "samsung") {
mDeviceQuirks = std::make_unique<SamsungDeviceQuirks>();
} else {
mDeviceQuirks = std::make_unique<DeviceQuirks>();
}
}
bool QuirksManager::isConversionNeeded(
const AudioStreamBuilder &builder,
AudioStreamBuilder &childBuilder) {
bool conversionNeeded = false;
const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
const bool isInput = builder.getDirection() == Direction::Input;
const bool isFloat = builder.getFormat() == AudioFormat::Float;
// There are multiple bugs involving using callback with a specified callback size.
// Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
// and a specified callback size. It would assert because of a bad buffer size.
//
// Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
// An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
// Internally b/161914201#comment25
//
// Issue #983: O to R would glitch if the framesPerCallback was too small.
//
// Most of these problems were related to Legacy stream. MMAP was OK. But we don't
// know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
if (OboeGlobals::areWorkaroundsEnabled()
&& builder.willUseAAudio()
&& builder.isDataCallbackSpecified()
&& builder.getFramesPerDataCallback() != 0
&& getSdkVersion() <= __ANDROID_API_R__) {
LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
childBuilder.setFramesPerCallback(oboe::Unspecified);
conversionNeeded = true;
}
// If a SAMPLE RATE is specified for low latency then let the native code choose an optimal rate.
// TODO There may be a problem if the devices supports low latency
// at a higher rate than the default.
if (builder.getSampleRate() != oboe::Unspecified
&& builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
&& isLowLatency
) {
childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
conversionNeeded = true;
}
// Data Format
// OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
if (isFloat
&& isInput
&& builder.isFormatConversionAllowed()
&& isLowLatency
&& (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
) {
childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
conversionNeeded = true;
LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
}
// Add quirk for float output on API <21
if (isFloat
&& !isInput
&& getSdkVersion() < __ANDROID_API_L__
&& builder.isFormatConversionAllowed()
) {
childBuilder.setFormat(AudioFormat::I16);
conversionNeeded = true;
LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices, "
"creating an underlying I16 stream and using format conversion to provide a float "
"stream", __func__);
}
// Channel Count conversions
if (OboeGlobals::areWorkaroundsEnabled()
&& builder.isChannelConversionAllowed()
&& builder.getChannelCount() == kChannelCountStereo
&& isInput
&& isLowLatency
&& (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
) {
// Workaround for heap size regression in O.
// b/66967812 AudioRecord does not allow FAST track for stereo capture in O
childBuilder.setChannelCount(kChannelCountMono);
conversionNeeded = true;
LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
} else if (OboeGlobals::areWorkaroundsEnabled()
&& builder.getChannelCount() == kChannelCountMono
&& isInput
&& mDeviceQuirks->isMonoMMapActuallyStereo()
&& builder.willUseAAudio()
// Note: we might use this workaround on a device that supports
// MMAP but will use Legacy for this stream. But this will only happen
// on devices that have the broken mono.
&& mDeviceQuirks->isAAudioMMapPossible(builder)
) {
// Workaround for mono actually running in stereo mode.
childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
conversionNeeded = true;
LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
}
// Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
// phones and they have almost all been updated to 9.0.
return conversionNeeded;
}
bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
if (!OboeGlobals::areWorkaroundsEnabled()) return true;
return mDeviceQuirks->isMMapSafe(builder);
}

View File

@ -0,0 +1,131 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_QUIRKS_MANAGER_H
#define OBOE_QUIRKS_MANAGER_H
#include <memory>
#include <oboe/AudioStreamBuilder.h>
#include <aaudio/AudioStreamAAudio.h>
#ifndef __ANDROID_API_R__
#define __ANDROID_API_R__ 30
#endif
namespace oboe {
/**
* INTERNAL USE ONLY.
*
* Based on manufacturer, model and Android version number
* decide whether data conversion needs to occur.
*
* This also manages device and version specific workarounds.
*/
class QuirksManager {
public:
static QuirksManager &getInstance() {
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
* @return true if conversion is needed
*/
bool isConversionNeeded(const AudioStreamBuilder &builder, AudioStreamBuilder &childBuilder);
static bool isMMapUsed(AudioStream &stream) {
bool answer = false;
if (stream.getAudioApi() == AudioApi::AAudio) {
AudioStreamAAudio *streamAAudio =
reinterpret_cast<AudioStreamAAudio *>(&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;
}
// On some devices, you can open a mono stream but it is actually running in stereo!
virtual bool isMonoMMapActuallyStereo() const {
return false;
}
virtual bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const;
virtual bool isMMapSafe(const AudioStreamBuilder & /* builder */ ) {
return true;
}
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;
static constexpr int32_t kCommonNativeRate = 48000; // very typical native sample rate
};
bool isMMapSafe(AudioStreamBuilder &builder);
private:
static constexpr int32_t kChannelCountMono = 1;
static constexpr int32_t kChannelCountStereo = 2;
std::unique_ptr<DeviceQuirks> mDeviceQuirks{};
};
}
#endif //OBOE_QUIRKS_MANAGER_H

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceFloatCaller.h"
using namespace oboe;
using namespace flowgraph;
int32_t SourceFloatCaller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) output.getBuffer(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
return framesRead;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_FLOAT_CALLER_H
#define OBOE_SOURCE_FLOAT_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more float data.
*/
class SourceFloatCaller : public AudioSourceCaller {
public:
SourceFloatCaller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, (int32_t)sizeof(float)) {}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceFloatCaller";
}
};
}
#endif //OBOE_SOURCE_FLOAT_CALLER_H

View File

@ -0,0 +1,47 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceI16Caller.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI16Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const int16_t *shortData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i16(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *shortData++ * (1.0f / 32768);
}
#endif
return framesRead;
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_I16_CALLER_H
#define OBOE_SOURCE_I16_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI16Caller : public AudioSourceCaller {
public:
SourceI16Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, sizeof(int16_t)) {
mConversionBuffer = std::make_unique<int16_t[]>(channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI16Caller";
}
private:
std::unique_ptr<int16_t[]> mConversionBuffer;
};
}
#endif //OBOE_SOURCE_I16_CALLER_H

View File

@ -0,0 +1,56 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceI24Caller.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI24Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const uint8_t *byteData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_p24(floatData, byteData, numSamples);
#else
static const float scale = 1. / (float)(1UL << 31);
for (int i = 0; i < numSamples; i++) {
// Assemble the data assuming Little Endian format.
int32_t pad = byteData[2];
pad <<= 8;
pad |= byteData[1];
pad <<= 8;
pad |= byteData[0];
pad <<= 8; // Shift to 32 bit data so the sign is correct.
byteData += kBytesPerI24Packed;
*floatData++ = pad * scale; // scale to range -1.0 to 1.0
}
#endif
return framesRead;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_I24_CALLER_H
#define OBOE_SOURCE_I24_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI24Caller : public AudioSourceCaller {
public:
SourceI24Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, kBytesPerI24Packed) {
mConversionBuffer = std::make_unique<uint8_t[]>(
kBytesPerI24Packed * channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI24Caller";
}
private:
std::unique_ptr<uint8_t[]> mConversionBuffer;
static constexpr int kBytesPerI24Packed = 3;
};
}
#endif //OBOE_SOURCE_I16_CALLER_H

View File

@ -0,0 +1,47 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceI32Caller.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI32Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const int32_t *intData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i32(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
return framesRead;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_I32_CALLER_H
#define OBOE_SOURCE_I32_CALLER_H
#include <memory.h>
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI32Caller : public AudioSourceCaller {
public:
SourceI32Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, sizeof(int32_t)) {
mConversionBuffer = std::make_unique<int32_t[]>(channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI32Caller";
}
private:
std::unique_ptr<int32_t[]> mConversionBuffer;
static constexpr float kScale = 1.0 / (1UL << 31);
};
}
#endif //OBOE_SOURCE_I32_CALLER_H

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oboe/StabilizedCallback.h"
#include "common/AudioClock.h"
#include "common/Trace.h"
constexpr int32_t kLoadGenerationStepSizeNanos = 20000;
constexpr float kPercentageOfCallbackToUse = 0.8;
using namespace oboe;
StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback){
Trace::initialize();
}
/**
* An audio callback which attempts to do work for a fixed amount of time.
*
* @param oboeStream
* @param audioData
* @param numFrames
* @return
*/
DataCallbackResult
StabilizedCallback::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
int64_t startTimeNanos = AudioClock::getNanoseconds();
if (mFrameCount == 0){
mEpochTimeNanos = startTimeNanos;
}
int64_t durationSinceEpochNanos = startTimeNanos - mEpochTimeNanos;
// In an ideal world the callback start time will be exactly the same as the duration of the
// frames already read/written into the stream. In reality the callback can start early
// or late. By finding the delta we can calculate the target duration for our stabilized
// callback.
int64_t idealStartTimeNanos = (mFrameCount * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t lateStartNanos = durationSinceEpochNanos - idealStartTimeNanos;
if (lateStartNanos < 0){
// This was an early start which indicates that our previous epoch was a late callback.
// Update our epoch to this more accurate time.
mEpochTimeNanos = startTimeNanos;
mFrameCount = 0;
}
int64_t numFramesAsNanos = (numFrames * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t targetDurationNanos = static_cast<int64_t>(
(numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos);
Trace::beginSection("Actual load");
DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames);
Trace::endSection();
int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos;
int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos;
Trace::beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos);
generateLoad(stabilizingLoadDurationNanos);
Trace::endSection();
// Wraparound: At 48000 frames per second mFrameCount wraparound will occur after 6m years,
// significantly longer than the average lifetime of an Android phone.
mFrameCount += numFrames;
return result;
}
void StabilizedCallback::generateLoad(int64_t durationNanos) {
int64_t currentTimeNanos = AudioClock::getNanoseconds();
int64_t deadlineTimeNanos = currentTimeNanos + durationNanos;
// opsPerStep gives us an estimated number of operations which need to be run to fully utilize
// the CPU for a fixed amount of time (specified by kLoadGenerationStepSizeNanos).
// After each step the opsPerStep value is re-calculated based on the actual time taken to
// execute those operations.
auto opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos);
int64_t stepDurationNanos = 0;
int64_t previousTimeNanos = 0;
while (currentTimeNanos <= deadlineTimeNanos){
for (int i = 0; i < opsPerStep; i++) cpu_relax();
previousTimeNanos = currentTimeNanos;
currentTimeNanos = AudioClock::getNanoseconds();
stepDurationNanos = currentTimeNanos - previousTimeNanos;
// Calculate exponential moving average to smooth out values, this acts as a low pass filter.
// @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
static const float kFilterCoefficient = 0.1;
auto measuredOpsPerNano = (double) opsPerStep / stepDurationNanos;
mOpsPerNano = kFilterCoefficient * measuredOpsPerNano + (1.0 - kFilterCoefficient) * mOpsPerNano;
opsPerStep = (int) (mOpsPerNano * kLoadGenerationStepSizeNanos);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <dlfcn.h>
#include <cstdio>
#include "Trace.h"
#include "OboeDebug.h"
static char buffer[256];
// Tracing functions
static void *(*ATrace_beginSection)(const char *sectionName);
static void *(*ATrace_endSection)();
typedef void *(*fp_ATrace_beginSection)(const char *sectionName);
typedef void *(*fp_ATrace_endSection)();
bool Trace::mIsTracingSupported = false;
void Trace::beginSection(const char *format, ...){
if (mIsTracingSupported) {
va_list va;
va_start(va, format);
vsprintf(buffer, format, va);
ATrace_beginSection(buffer);
va_end(va);
} else {
LOGE("Tracing is either not initialized (call Trace::initialize()) "
"or not supported on this device");
}
}
void Trace::endSection() {
if (mIsTracingSupported) {
ATrace_endSection();
}
}
void Trace::initialize() {
// Using dlsym allows us to use tracing on API 21+ without needing android/trace.h which wasn't
// published until API 23
void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
if (lib == nullptr) {
LOGE("Could not open libandroid.so to dynamically load tracing symbols");
} else {
ATrace_beginSection =
reinterpret_cast<fp_ATrace_beginSection >(
dlsym(lib, "ATrace_beginSection"));
ATrace_endSection =
reinterpret_cast<fp_ATrace_endSection >(
dlsym(lib, "ATrace_endSection"));
if (ATrace_beginSection != nullptr && ATrace_endSection != nullptr){
mIsTracingSupported = true;
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_TRACE_H
#define OBOE_TRACE_H
class Trace {
public:
static void beginSection(const char *format, ...);
static void endSection();
static void initialize();
private:
static bool mIsTracingSupported;
};
#endif //OBOE_TRACE_H

View File

@ -0,0 +1,313 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include <unistd.h>
#include <sstream>
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif
#include <oboe/AudioStream.h>
#include "oboe/Definitions.h"
#include "oboe/Utilities.h"
namespace oboe {
constexpr float kScaleI16ToFloat = (1.0f / 32768.0f);
void convertFloatToPcm16(const float *source, int16_t *destination, int32_t numSamples) {
for (int i = 0; i < numSamples; i++) {
float fval = source[i];
fval += 1.0; // to avoid discontinuity at 0.0 caused by truncation
fval *= 32768.0f;
auto sample = static_cast<int32_t>(fval);
// clip to 16-bit range
if (sample < 0) sample = 0;
else if (sample > 0x0FFFF) sample = 0x0FFFF;
sample -= 32768; // center at zero
destination[i] = static_cast<int16_t>(sample);
}
}
void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples) {
for (int i = 0; i < numSamples; i++) {
destination[i] = source[i] * kScaleI16ToFloat;
}
}
int32_t convertFormatToSizeInBytes(AudioFormat format) {
int32_t size = 0;
switch (format) {
case AudioFormat::I16:
size = sizeof(int16_t);
break;
case AudioFormat::Float:
size = sizeof(float);
break;
case AudioFormat::I24:
size = 3; // packed 24-bit data
break;
case AudioFormat::I32:
size = sizeof(int32_t);
break;
default:
break;
}
return size;
}
template<>
const char *convertToText<Result>(Result returnCode) {
switch (returnCode) {
case Result::OK: return "OK";
case Result::ErrorDisconnected: return "ErrorDisconnected";
case Result::ErrorIllegalArgument: return "ErrorIllegalArgument";
case Result::ErrorInternal: return "ErrorInternal";
case Result::ErrorInvalidState: return "ErrorInvalidState";
case Result::ErrorInvalidHandle: return "ErrorInvalidHandle";
case Result::ErrorUnimplemented: return "ErrorUnimplemented";
case Result::ErrorUnavailable: return "ErrorUnavailable";
case Result::ErrorNoFreeHandles: return "ErrorNoFreeHandles";
case Result::ErrorNoMemory: return "ErrorNoMemory";
case Result::ErrorNull: return "ErrorNull";
case Result::ErrorTimeout: return "ErrorTimeout";
case Result::ErrorWouldBlock: return "ErrorWouldBlock";
case Result::ErrorInvalidFormat: return "ErrorInvalidFormat";
case Result::ErrorOutOfRange: return "ErrorOutOfRange";
case Result::ErrorNoService: return "ErrorNoService";
case Result::ErrorInvalidRate: return "ErrorInvalidRate";
case Result::ErrorClosed: return "ErrorClosed";
default: return "Unrecognized result";
}
}
template<>
const char *convertToText<AudioFormat>(AudioFormat format) {
switch (format) {
case AudioFormat::Invalid: return "Invalid";
case AudioFormat::Unspecified: return "Unspecified";
case AudioFormat::I16: return "I16";
case AudioFormat::Float: return "Float";
case AudioFormat::I24: return "I24";
case AudioFormat::I32: return "I32";
default: return "Unrecognized format";
}
}
template<>
const char *convertToText<PerformanceMode>(PerformanceMode mode) {
switch (mode) {
case PerformanceMode::LowLatency: return "LowLatency";
case PerformanceMode::None: return "None";
case PerformanceMode::PowerSaving: return "PowerSaving";
default: return "Unrecognized performance mode";
}
}
template<>
const char *convertToText<SharingMode>(SharingMode mode) {
switch (mode) {
case SharingMode::Exclusive: return "Exclusive";
case SharingMode::Shared: return "Shared";
default: return "Unrecognized sharing mode";
}
}
template<>
const char *convertToText<DataCallbackResult>(DataCallbackResult result) {
switch (result) {
case DataCallbackResult::Continue: return "Continue";
case DataCallbackResult::Stop: return "Stop";
default: return "Unrecognized data callback result";
}
}
template<>
const char *convertToText<Direction>(Direction direction) {
switch (direction) {
case Direction::Input: return "Input";
case Direction::Output: return "Output";
default: return "Unrecognized direction";
}
}
template<>
const char *convertToText<StreamState>(StreamState state) {
switch (state) {
case StreamState::Closed: return "Closed";
case StreamState::Closing: return "Closing";
case StreamState::Disconnected: return "Disconnected";
case StreamState::Flushed: return "Flushed";
case StreamState::Flushing: return "Flushing";
case StreamState::Open: return "Open";
case StreamState::Paused: return "Paused";
case StreamState::Pausing: return "Pausing";
case StreamState::Started: return "Started";
case StreamState::Starting: return "Starting";
case StreamState::Stopped: return "Stopped";
case StreamState::Stopping: return "Stopping";
case StreamState::Uninitialized: return "Uninitialized";
case StreamState::Unknown: return "Unknown";
default: return "Unrecognized stream state";
}
}
template<>
const char *convertToText<AudioApi>(AudioApi audioApi) {
switch (audioApi) {
case AudioApi::Unspecified: return "Unspecified";
case AudioApi::OpenSLES: return "OpenSLES";
case AudioApi::AAudio: return "AAudio";
default: return "Unrecognized audio API";
}
}
template<>
const char *convertToText<AudioStream*>(AudioStream* stream) {
static std::string streamText;
std::stringstream s;
s<<"StreamID: "<< static_cast<void*>(stream)<<std::endl
<<"DeviceId: "<<stream->getDeviceId()<<std::endl
<<"Direction: "<<oboe::convertToText(stream->getDirection())<<std::endl
<<"API type: "<<oboe::convertToText(stream->getAudioApi())<<std::endl
<<"BufferCapacity: "<<stream->getBufferCapacityInFrames()<<std::endl
<<"BufferSize: "<<stream->getBufferSizeInFrames()<<std::endl
<<"FramesPerBurst: "<< stream->getFramesPerBurst()<<std::endl
<<"FramesPerDataCallback: "<<stream->getFramesPerDataCallback()<<std::endl
<<"SampleRate: "<<stream->getSampleRate()<<std::endl
<<"ChannelCount: "<<stream->getChannelCount()<<std::endl
<<"Format: "<<oboe::convertToText(stream->getFormat())<<std::endl
<<"SharingMode: "<<oboe::convertToText(stream->getSharingMode())<<std::endl
<<"PerformanceMode: "<<oboe::convertToText(stream->getPerformanceMode())
<<std::endl
<<"CurrentState: "<<oboe::convertToText(stream->getState())<<std::endl
<<"XRunCount: "<<stream->getXRunCount()<<std::endl
<<"FramesRead: "<<stream->getFramesRead()<<std::endl
<<"FramesWritten: "<<stream->getFramesWritten()<<std::endl;
streamText = s.str();
return streamText.c_str();
}
template<>
const char *convertToText<Usage>(Usage usage) {
switch (usage) {
case Usage::Media: return "Media";
case Usage::VoiceCommunication: return "VoiceCommunication";
case Usage::VoiceCommunicationSignalling: return "VoiceCommunicationSignalling";
case Usage::Alarm: return "Alarm";
case Usage::Notification: return "Notification";
case Usage::NotificationRingtone: return "NotificationRingtone";
case Usage::NotificationEvent: return "NotificationEvent";
case Usage::AssistanceAccessibility: return "AssistanceAccessibility";
case Usage::AssistanceNavigationGuidance: return "AssistanceNavigationGuidance";
case Usage::AssistanceSonification: return "AssistanceSonification";
case Usage::Game: return "Game";
case Usage::Assistant: return "Assistant";
default: return "Unrecognized usage";
}
}
template<>
const char *convertToText<ContentType>(ContentType contentType) {
switch (contentType) {
case ContentType::Speech: return "Speech";
case ContentType::Music: return "Music";
case ContentType::Movie: return "Movie";
case ContentType::Sonification: return "Sonification";
default: return "Unrecognized content type";
}
}
template<>
const char *convertToText<InputPreset>(InputPreset inputPreset) {
switch (inputPreset) {
case InputPreset::Generic: return "Generic";
case InputPreset::Camcorder: return "Camcorder";
case InputPreset::VoiceRecognition: return "VoiceRecognition";
case InputPreset::VoiceCommunication: return "VoiceCommunication";
case InputPreset::Unprocessed: return "Unprocessed";
case InputPreset::VoicePerformance: return "VoicePerformance";
default: return "Unrecognized input preset";
}
}
template<>
const char *convertToText<SessionId>(SessionId sessionId) {
switch (sessionId) {
case SessionId::None: return "None";
case SessionId::Allocate: return "Allocate";
default: return "Unrecognized session id";
}
}
template<>
const char *convertToText<ChannelCount>(ChannelCount channelCount) {
switch (channelCount) {
case ChannelCount::Unspecified: return "Unspecified";
case ChannelCount::Mono: return "Mono";
case ChannelCount::Stereo: return "Stereo";
default: return "Unrecognized channel count";
}
}
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) {
sCachedSdkVersion = getPropertyInteger("ro.build.version.sdk", -1);
}
#endif
return sCachedSdkVersion;
}
}// namespace oboe

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oboe/Version.h"
namespace oboe {
// This variable enables the version information to be read from the resulting binary e.g.
// by running `objdump -s --section=.data <binary>`
// Please do not optimize or change in any way.
char kVersionText[] = "OboeVersion" OBOE_VERSION_TEXT;
const char * getVersionText(){
return kVersionText;
}
} // namespace oboe

View File

@ -0,0 +1,178 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <memory.h>
#include <stdint.h>
#include "fifo/FifoControllerBase.h"
#include "fifo/FifoController.h"
#include "fifo/FifoControllerIndirect.h"
#include "fifo/FifoBuffer.h"
namespace oboe {
FifoBuffer::FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames)
: mBytesPerFrame(bytesPerFrame)
, mStorage(nullptr)
, mFramesReadCount(0)
, mFramesUnderrunCount(0)
{
mFifo = std::make_unique<FifoController>(capacityInFrames);
// allocate buffer
int32_t bytesPerBuffer = bytesPerFrame * capacityInFrames;
mStorage = new uint8_t[bytesPerBuffer];
mStorageOwned = true;
}
FifoBuffer::FifoBuffer( uint32_t bytesPerFrame,
uint32_t capacityInFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress,
uint8_t *dataStorageAddress
)
: mBytesPerFrame(bytesPerFrame)
, mStorage(dataStorageAddress)
, mFramesReadCount(0)
, mFramesUnderrunCount(0)
{
mFifo = std::make_unique<FifoControllerIndirect>(capacityInFrames,
readCounterAddress,
writeCounterAddress);
mStorage = dataStorageAddress;
mStorageOwned = false;
}
FifoBuffer::~FifoBuffer() {
if (mStorageOwned) {
delete[] mStorage;
}
}
int32_t FifoBuffer::convertFramesToBytes(int32_t frames) {
return frames * mBytesPerFrame;
}
int32_t FifoBuffer::read(void *buffer, int32_t numFrames) {
if (numFrames <= 0) {
return 0;
}
// safe because numFrames is guaranteed positive
uint32_t framesToRead = static_cast<uint32_t>(numFrames);
uint32_t framesAvailable = mFifo->getFullFramesAvailable();
framesToRead = std::min(framesToRead, framesAvailable);
uint32_t readIndex = mFifo->getReadIndex(); // ranges 0 to capacity
uint8_t *destination = reinterpret_cast<uint8_t *>(buffer);
uint8_t *source = &mStorage[convertFramesToBytes(readIndex)];
if ((readIndex + framesToRead) > mFifo->getFrameCapacity()) {
// read in two parts, first part here is at the end of the mStorage buffer
int32_t frames1 = static_cast<int32_t>(mFifo->getFrameCapacity() - readIndex);
int32_t numBytes = convertFramesToBytes(frames1);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
destination += numBytes;
// read second part, which is at the beginning of mStorage
source = &mStorage[0];
int32_t frames2 = static_cast<uint32_t>(framesToRead - frames1);
numBytes = convertFramesToBytes(frames2);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
} else {
// just read in one shot
int32_t numBytes = convertFramesToBytes(framesToRead);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
}
mFifo->advanceReadIndex(framesToRead);
return framesToRead;
}
int32_t FifoBuffer::write(const void *buffer, int32_t numFrames) {
if (numFrames <= 0) {
return 0;
}
// Guaranteed positive.
uint32_t framesToWrite = static_cast<uint32_t>(numFrames);
uint32_t framesAvailable = mFifo->getEmptyFramesAvailable();
framesToWrite = std::min(framesToWrite, framesAvailable);
uint32_t writeIndex = mFifo->getWriteIndex();
int byteIndex = convertFramesToBytes(writeIndex);
const uint8_t *source = reinterpret_cast<const uint8_t *>(buffer);
uint8_t *destination = &mStorage[byteIndex];
if ((writeIndex + framesToWrite) > mFifo->getFrameCapacity()) {
// write in two parts, first part here
int32_t frames1 = static_cast<uint32_t>(mFifo->getFrameCapacity() - writeIndex);
int32_t numBytes = convertFramesToBytes(frames1);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
// read second part
source += convertFramesToBytes(frames1);
destination = &mStorage[0];
int frames2 = static_cast<uint32_t>(framesToWrite - frames1);
numBytes = convertFramesToBytes(frames2);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
} else {
// just write in one shot
int32_t numBytes = convertFramesToBytes(framesToWrite);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
}
mFifo->advanceWriteIndex(framesToWrite);
return framesToWrite;
}
int32_t FifoBuffer::readNow(void *buffer, int32_t numFrames) {
int32_t framesRead = read(buffer, numFrames);
if (framesRead < 0) {
return framesRead;
}
int32_t framesLeft = numFrames - framesRead;
mFramesReadCount += framesRead;
mFramesUnderrunCount += framesLeft;
// Zero out any samples we could not set.
if (framesLeft > 0) {
uint8_t *destination = reinterpret_cast<uint8_t *>(buffer);
destination += convertFramesToBytes(framesRead); // point to first byte not set
int32_t bytesToZero = convertFramesToBytes(framesLeft);
memset(destination, 0, static_cast<size_t>(bytesToZero));
}
return framesRead;
}
uint32_t FifoBuffer::getBufferCapacityInFrames() const {
return mFifo->getFrameCapacity();
}
} // namespace oboe

View File

@ -0,0 +1,99 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_FIFOPROCESSOR_H
#define OBOE_FIFOPROCESSOR_H
#include <memory>
#include <stdint.h>
#include "oboe/Definitions.h"
#include "FifoControllerBase.h"
namespace oboe {
class FifoBuffer {
public:
FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames);
FifoBuffer(uint32_t bytesPerFrame,
uint32_t capacityInFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress,
uint8_t *dataStorageAddress);
~FifoBuffer();
int32_t convertFramesToBytes(int32_t frames);
/**
* Read framesToRead or, if not enough, then read as many as are available.
* @param destination
* @param framesToRead number of frames requested
* @return number of frames actually read
*/
int32_t read(void *destination, int32_t framesToRead);
int32_t write(const void *source, int32_t framesToWrite);
uint32_t getBufferCapacityInFrames() const;
/**
* Calls read(). If all of the frames cannot be read then the remainder of the buffer
* is set to zero.
*
* @param destination
* @param framesToRead number of frames requested
* @return number of frames actually read
*/
int32_t readNow(void *destination, int32_t numFrames);
uint32_t getFullFramesAvailable() {
return mFifo->getFullFramesAvailable();
}
uint32_t getBytesPerFrame() const {
return mBytesPerFrame;
}
uint64_t getReadCounter() const {
return mFifo->getReadCounter();
}
void setReadCounter(uint64_t n) {
mFifo->setReadCounter(n);
}
uint64_t getWriteCounter() {
return mFifo->getWriteCounter();
}
void setWriteCounter(uint64_t n) {
mFifo->setWriteCounter(n);
}
private:
uint32_t mBytesPerFrame;
uint8_t* mStorage;
bool mStorageOwned; // did this object allocate the storage?
std::unique_ptr<FifoControllerBase> mFifo;
uint64_t mFramesReadCount;
uint64_t mFramesUnderrunCount;
};
} // namespace oboe
#endif //OBOE_FIFOPROCESSOR_H

View File

@ -0,0 +1,31 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include "FifoControllerBase.h"
#include "FifoController.h"
namespace oboe {
FifoController::FifoController(uint32_t numFrames)
: FifoControllerBase(numFrames)
{
setReadCounter(0);
setWriteCounter(0);
}
} // namespace oboe

View File

@ -0,0 +1,62 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLER_H
#define NATIVEOBOE_FIFOCONTROLLER_H
#include <atomic>
#include <stdint.h>
#include "FifoControllerBase.h"
namespace oboe {
/**
* A FifoControllerBase with counters contained in the class.
*/
class FifoController : public FifoControllerBase
{
public:
FifoController(uint32_t bufferSize);
virtual ~FifoController() = default;
virtual uint64_t getReadCounter() const override {
return mReadCounter.load(std::memory_order_acquire);
}
virtual void setReadCounter(uint64_t n) override {
mReadCounter.store(n, std::memory_order_release);
}
virtual void incrementReadCounter(uint64_t n) override {
mReadCounter.fetch_add(n, std::memory_order_acq_rel);
}
virtual uint64_t getWriteCounter() const override {
return mWriteCounter.load(std::memory_order_acquire);
}
virtual void setWriteCounter(uint64_t n) override {
mWriteCounter.store(n, std::memory_order_release);
}
virtual void incrementWriteCounter(uint64_t n) override {
mWriteCounter.fetch_add(n, std::memory_order_acq_rel);
}
private:
std::atomic<uint64_t> mReadCounter{};
std::atomic<uint64_t> mWriteCounter{};
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLER_H

View File

@ -0,0 +1,68 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <cassert>
#include <stdint.h>
#include "FifoControllerBase.h"
namespace oboe {
FifoControllerBase::FifoControllerBase(uint32_t capacityInFrames)
: mTotalFrames(capacityInFrames)
{
// Avoid ridiculously large buffers and the arithmetic wraparound issues that can follow.
assert(capacityInFrames <= (UINT32_MAX / 4));
}
uint32_t FifoControllerBase::getFullFramesAvailable() const {
uint64_t writeCounter = getWriteCounter();
uint64_t readCounter = getReadCounter();
if (readCounter > writeCounter) {
return 0;
}
uint64_t delta = writeCounter - readCounter;
if (delta >= mTotalFrames) {
return mTotalFrames;
}
// delta is now guaranteed to fit within the range of a uint32_t
return static_cast<uint32_t>(delta);
}
uint32_t FifoControllerBase::getReadIndex() const {
// % works with non-power of two sizes
return static_cast<uint32_t>(getReadCounter() % mTotalFrames);
}
void FifoControllerBase::advanceReadIndex(uint32_t numFrames) {
incrementReadCounter(numFrames);
}
uint32_t FifoControllerBase::getEmptyFramesAvailable() const {
return static_cast<uint32_t>(mTotalFrames - getFullFramesAvailable());
}
uint32_t FifoControllerBase::getWriteIndex() const {
// % works with non-power of two sizes
return static_cast<uint32_t>(getWriteCounter() % mTotalFrames);
}
void FifoControllerBase::advanceWriteIndex(uint32_t numFrames) {
incrementWriteCounter(numFrames);
}
} // namespace oboe

View File

@ -0,0 +1,92 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLERBASE_H
#define NATIVEOBOE_FIFOCONTROLLERBASE_H
#include <stdint.h>
namespace oboe {
/**
* Manage the read/write indices of a circular buffer.
*
* The caller is responsible for reading and writing the actual data.
* Note that the span of available frames may not be contiguous. They
* may wrap around from the end to the beginning of the buffer. In that
* case the data must be read or written in at least two blocks of frames.
*
*/
class FifoControllerBase {
public:
/**
* @param totalFrames capacity of the circular buffer in frames.
*/
FifoControllerBase(uint32_t totalFrames);
virtual ~FifoControllerBase() = default;
/**
* The frames available to read will be calculated from the read and write counters.
* The result will be clipped to the capacity of the buffer.
* If the buffer has underflowed then this will return zero.
* @return number of valid frames available to read.
*/
uint32_t getFullFramesAvailable() const;
/**
* The index in a circular buffer of the next frame to read.
*/
uint32_t getReadIndex() const;
/**
* @param numFrames number of frames to advance the read index
*/
void advanceReadIndex(uint32_t numFrames);
/**
* @return maximum number of frames that can be written without exceeding the threshold.
*/
uint32_t getEmptyFramesAvailable() const;
/**
* The index in a circular buffer of the next frame to write.
*/
uint32_t getWriteIndex() const;
/**
* @param numFrames number of frames to advance the write index
*/
void advanceWriteIndex(uint32_t numFrames);
uint32_t getFrameCapacity() const { return mTotalFrames; }
virtual uint64_t getReadCounter() const = 0;
virtual void setReadCounter(uint64_t n) = 0;
virtual void incrementReadCounter(uint64_t n) = 0;
virtual uint64_t getWriteCounter() const = 0;
virtual void setWriteCounter(uint64_t n) = 0;
virtual void incrementWriteCounter(uint64_t n) = 0;
private:
uint32_t mTotalFrames;
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLERBASE_H

View File

@ -0,0 +1,32 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include "FifoControllerIndirect.h"
namespace oboe {
FifoControllerIndirect::FifoControllerIndirect(uint32_t numFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress)
: FifoControllerBase(numFrames)
, mReadCounterAddress(readCounterAddress)
, mWriteCounterAddress(writeCounterAddress)
{
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLERINDIRECT_H
#define NATIVEOBOE_FIFOCONTROLLERINDIRECT_H
#include <atomic>
#include <stdint.h>
#include "FifoControllerBase.h"
namespace oboe {
/**
* A FifoControllerBase with counters external to the class.
*/
class FifoControllerIndirect : public FifoControllerBase {
public:
FifoControllerIndirect(uint32_t bufferSize,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress);
virtual ~FifoControllerIndirect() = default;
virtual uint64_t getReadCounter() const override {
return mReadCounterAddress->load(std::memory_order_acquire);
}
virtual void setReadCounter(uint64_t n) override {
mReadCounterAddress->store(n, std::memory_order_release);
}
virtual void incrementReadCounter(uint64_t n) override {
mReadCounterAddress->fetch_add(n, std::memory_order_acq_rel);
}
virtual uint64_t getWriteCounter() const override {
return mWriteCounterAddress->load(std::memory_order_acquire);
}
virtual void setWriteCounter(uint64_t n) override {
mWriteCounterAddress->store(n, std::memory_order_release);
}
virtual void incrementWriteCounter(uint64_t n) override {
mWriteCounterAddress->fetch_add(n, std::memory_order_acq_rel);
}
private:
std::atomic<uint64_t> *mReadCounterAddress;
std::atomic<uint64_t> *mWriteCounterAddress;
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLERINDIRECT_H

View File

@ -0,0 +1,52 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <unistd.h>
#include "FlowGraphNode.h"
#include "ChannelCountConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ChannelCountConverter::ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount)
: input(*this, inputChannelCount)
, output(*this, outputChannelCount) {
}
ChannelCountConverter::~ChannelCountConverter() { }
int32_t ChannelCountConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t inputChannelCount = input.getSamplesPerFrame();
int32_t outputChannelCount = output.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
int inputChannel = 0;
for (int outputChannel = 0; outputChannel < outputChannelCount; outputChannel++) {
// Copy input channels to output channels.
// Wrap if we run out of inputs.
// Discard if we run out of outputs.
outputBuffer[outputChannel] = inputBuffer[inputChannel];
inputChannel = (inputChannel == inputChannelCount)
? 0 : inputChannel + 1;
}
inputBuffer += inputChannelCount;
outputBuffer += outputChannelCount;
}
return numFrames;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
#define FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Change the number of number of channels without mixing.
* When increasing the channel count, duplicate input channels.
* When decreasing the channel count, drop input channels.
*/
class ChannelCountConverter : public FlowGraphNode {
public:
explicit ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount);
virtual ~ChannelCountConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "ChannelCountConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H

View File

@ -0,0 +1,38 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "ClipToRange.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ClipToRange::ClipToRange(int32_t channelCount)
: FlowGraphFilter(channelCount) {
}
int32_t ClipToRange::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t numSamples = numFrames * output.getSamplesPerFrame();
for (int32_t i = 0; i < numSamples; i++) {
*outputBuffer++ = std::min(mMaximum, std::max(mMinimum, *inputBuffer++));
}
return numFrames;
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_CLIP_TO_RANGE_H
#define FLOWGRAPH_CLIP_TO_RANGE_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
// This is 3 dB, (10^(3/20)), to match the maximum headroom in AudioTrack for float data.
// It is designed to allow occasional transient peaks.
constexpr float kDefaultMaxHeadroom = 1.41253754f;
constexpr float kDefaultMinHeadroom = -kDefaultMaxHeadroom;
class ClipToRange : public FlowGraphFilter {
public:
explicit ClipToRange(int32_t channelCount);
virtual ~ClipToRange() = default;
int32_t onProcess(int32_t numFrames) override;
void setMinimum(float min) {
mMinimum = min;
}
float getMinimum() const {
return mMinimum;
}
void setMaximum(float min) {
mMaximum = min;
}
float getMaximum() const {
return mMaximum;
}
const char *getName() override {
return "ClipToRange";
}
private:
float mMinimum = kDefaultMinHeadroom;
float mMaximum = kDefaultMaxHeadroom;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_CLIP_TO_RANGE_H

View File

@ -0,0 +1,114 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "stdio.h"
#include <algorithm>
#include <sys/types.h>
#include "FlowGraphNode.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
/***************************************************************************/
int32_t FlowGraphNode::pullData(int32_t numFrames, int64_t callCount) {
int32_t frameCount = numFrames;
// Prevent recursion and multiple execution of nodes.
if (callCount > mLastCallCount) {
mLastCallCount = callCount;
if (mDataPulledAutomatically) {
// Pull from all the upstream nodes.
for (auto &port : mInputPorts) {
// TODO fix bug of leaving unused data in some ports if using multiple AudioSource
frameCount = port.get().pullData(callCount, frameCount);
}
}
if (frameCount > 0) {
frameCount = onProcess(frameCount);
}
mLastFrameCount = frameCount;
} else {
frameCount = mLastFrameCount;
}
return frameCount;
}
void FlowGraphNode::pullReset() {
if (!mBlockRecursion) {
mBlockRecursion = true; // for cyclic graphs
// Pull reset from all the upstream nodes.
for (auto &port : mInputPorts) {
port.get().pullReset();
}
mBlockRecursion = false;
reset();
}
}
void FlowGraphNode::reset() {
mLastFrameCount = 0;
mLastCallCount = kInitialCallCount;
}
/***************************************************************************/
FlowGraphPortFloat::FlowGraphPortFloat(FlowGraphNode &parent,
int32_t samplesPerFrame,
int32_t framesPerBuffer)
: FlowGraphPort(parent, samplesPerFrame)
, mFramesPerBuffer(framesPerBuffer)
, mBuffer(nullptr) {
size_t numFloats = static_cast<size_t>(framesPerBuffer * getSamplesPerFrame());
mBuffer = std::make_unique<float[]>(numFloats);
}
/***************************************************************************/
int32_t FlowGraphPortFloatOutput::pullData(int64_t callCount, int32_t numFrames) {
numFrames = std::min(getFramesPerBuffer(), numFrames);
return mContainingNode.pullData(numFrames, callCount);
}
void FlowGraphPortFloatOutput::pullReset() {
mContainingNode.pullReset();
}
// These need to be in the .cpp file because of forward cross references.
void FlowGraphPortFloatOutput::connect(FlowGraphPortFloatInput *port) {
port->connect(this);
}
void FlowGraphPortFloatOutput::disconnect(FlowGraphPortFloatInput *port) {
port->disconnect(this);
}
/***************************************************************************/
int32_t FlowGraphPortFloatInput::pullData(int64_t callCount, int32_t numFrames) {
return (mConnected == nullptr)
? std::min(getFramesPerBuffer(), numFrames)
: mConnected->pullData(callCount, numFrames);
}
void FlowGraphPortFloatInput::pullReset() {
if (mConnected != nullptr) mConnected->pullReset();
}
float *FlowGraphPortFloatInput::getBuffer() {
if (mConnected == nullptr) {
return FlowGraphPortFloat::getBuffer(); // loaded using setValue()
} else {
return mConnected->getBuffer();
}
}
int32_t FlowGraphSink::pullData(int32_t numFrames) {
return FlowGraphNode::pullData(numFrames, getLastCallCount() + 1);
}

View File

@ -0,0 +1,448 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* FlowGraph.h
*
* Processing node and ports that can be used in a simple data flow graph.
* This was designed to work with audio but could be used for other
* types of data.
*/
#ifndef FLOWGRAPH_FLOW_GRAPH_NODE_H
#define FLOWGRAPH_FLOW_GRAPH_NODE_H
#include <cassert>
#include <cstring>
#include <math.h>
#include <memory>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <vector>
// TODO Move these classes into separate files.
// TODO Review use of raw pointers for connect(). Maybe use smart pointers but need to avoid
// run-time deallocation in audio thread.
// Set this to 1 if using it inside the Android framework.
// This code is kept here so that it can be moved easily between Oboe and AAudio.
#ifndef FLOWGRAPH_ANDROID_INTERNAL
#define FLOWGRAPH_ANDROID_INTERNAL 0
#endif
// Set this to a name that will prevent AAudio from calling into Oboe.
// AAudio and Oboe both use a version of this flowgraph package.
// There was a problem in the unit tests where AAudio would call a constructor
// in AAudio and then call a destructor in Oboe! That caused memory corruption.
// For more details, see Issue #930.
#ifndef FLOWGRAPH_OUTER_NAMESPACE
#define FLOWGRAPH_OUTER_NAMESPACE oboe
#endif
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
// Default block size that can be overridden when the FlowGraphPortFloat is created.
// If it is too small then we will have too much overhead from switching between nodes.
// If it is too high then we will thrash the caches.
constexpr int kDefaultBufferSize = 8; // arbitrary
class FlowGraphPort;
class FlowGraphPortFloatInput;
/***************************************************************************/
/**
* Base class for all nodes in the flowgraph.
*/
class FlowGraphNode {
public:
FlowGraphNode() {}
virtual ~FlowGraphNode() = default;
/**
* Read from the input ports,
* generate multiple frames of data then write the results to the output ports.
*
* @param numFrames maximum number of frames requested for processing
* @return number of frames actually processed
*/
virtual int32_t onProcess(int32_t numFrames) = 0;
/**
* If the callCount is at or after the previous callCount then call
* pullData on all of the upstreamNodes.
* Then call onProcess().
* This prevents infinite recursion in case of cyclic graphs.
* It also prevents nodes upstream from a branch from being executed twice.
*
* @param callCount
* @param numFrames
* @return number of frames valid
*/
int32_t pullData(int32_t numFrames, int64_t callCount);
/**
* Recursively reset all the nodes in the graph, starting from a Sink.
*
* This must not be called at the same time as pullData!
*/
void pullReset();
/**
* Reset framePosition counters.
*/
virtual void reset();
void addInputPort(FlowGraphPort &port) {
mInputPorts.push_back(port);
}
bool isDataPulledAutomatically() const {
return mDataPulledAutomatically;
}
/**
* Set true if you want the data pulled through the graph automatically.
* This is the default.
*
* Set false if you want to pull the data from the input ports in the onProcess() method.
* You might do this, for example, in a sample rate converting node.
*
* @param automatic
*/
void setDataPulledAutomatically(bool automatic) {
mDataPulledAutomatically = automatic;
}
virtual const char *getName() {
return "FlowGraph";
}
int64_t getLastCallCount() {
return mLastCallCount;
}
protected:
static constexpr int64_t kInitialCallCount = -1;
int64_t mLastCallCount = kInitialCallCount;
std::vector<std::reference_wrapper<FlowGraphPort>> mInputPorts;
private:
bool mDataPulledAutomatically = true;
bool mBlockRecursion = false;
int32_t mLastFrameCount = 0;
};
/***************************************************************************/
/**
* This is a connector that allows data to flow between modules.
*
* The ports are the primary means of interacting with a module.
* So they are generally declared as public.
*
*/
class FlowGraphPort {
public:
FlowGraphPort(FlowGraphNode &parent, int32_t samplesPerFrame)
: mContainingNode(parent)
, mSamplesPerFrame(samplesPerFrame) {
}
virtual ~FlowGraphPort() = default;
// Ports are often declared public. So let's make them non-copyable.
FlowGraphPort(const FlowGraphPort&) = delete;
FlowGraphPort& operator=(const FlowGraphPort&) = delete;
int32_t getSamplesPerFrame() const {
return mSamplesPerFrame;
}
virtual int32_t pullData(int64_t framePosition, int32_t numFrames) = 0;
virtual void pullReset() {}
protected:
FlowGraphNode &mContainingNode;
private:
const int32_t mSamplesPerFrame = 1;
};
/***************************************************************************/
/**
* This port contains a 32-bit float buffer that can contain several frames of data.
* Processing the data in a block improves performance.
*
* The size is framesPerBuffer * samplesPerFrame).
*/
class FlowGraphPortFloat : public FlowGraphPort {
public:
FlowGraphPortFloat(FlowGraphNode &parent,
int32_t samplesPerFrame,
int32_t framesPerBuffer = kDefaultBufferSize
);
virtual ~FlowGraphPortFloat() = default;
int32_t getFramesPerBuffer() const {
return mFramesPerBuffer;
}
protected:
/**
* @return buffer internal to the port or from a connected port
*/
virtual float *getBuffer() {
return mBuffer.get();
}
private:
const int32_t mFramesPerBuffer = 1;
std::unique_ptr<float[]> mBuffer; // allocated in constructor
};
/***************************************************************************/
/**
* The results of a node's processing are stored in the buffers of the output ports.
*/
class FlowGraphPortFloatOutput : public FlowGraphPortFloat {
public:
FlowGraphPortFloatOutput(FlowGraphNode &parent, int32_t samplesPerFrame)
: FlowGraphPortFloat(parent, samplesPerFrame) {
}
virtual ~FlowGraphPortFloatOutput() = default;
using FlowGraphPortFloat::getBuffer;
/**
* Connect to the input of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* If you connect a second output port to an input port
* then it overwrites the previous connection.
*
* This not thread safe. Do not modify the graph topology from another thread while running.
* Also do not delete a module while it is connected to another port if the graph is running.
*/
void connect(FlowGraphPortFloatInput *port);
/**
* Disconnect from the input of another module.
* This not thread safe.
*/
void disconnect(FlowGraphPortFloatInput *port);
/**
* Call the parent module's onProcess() method.
* That may pull data from its inputs and recursively
* process the entire graph.
* @return number of frames actually pulled
*/
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
void pullReset() override;
};
/***************************************************************************/
/**
* An input port for streaming audio data.
* You can set a value that will be used for processing.
* If you connect an output port to this port then its value will be used instead.
*/
class FlowGraphPortFloatInput : public FlowGraphPortFloat {
public:
FlowGraphPortFloatInput(FlowGraphNode &parent, int32_t samplesPerFrame)
: FlowGraphPortFloat(parent, samplesPerFrame) {
// Add to parent so it can pull data from each input.
parent.addInputPort(*this);
}
virtual ~FlowGraphPortFloatInput() = default;
/**
* If connected to an output port then this will return
* that output ports buffers.
* If not connected then it returns the input ports own buffer
* which can be loaded using setValue().
*/
float *getBuffer() override;
/**
* Write every value of the float buffer.
* This value will be ignored if an output port is connected
* to this port.
*/
void setValue(float value) {
int numFloats = kDefaultBufferSize * getSamplesPerFrame();
float *buffer = getBuffer();
for (int i = 0; i < numFloats; i++) {
*buffer++ = value;
}
}
/**
* Connect to the output of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* This not thread safe.
*/
void connect(FlowGraphPortFloatOutput *port) {
assert(getSamplesPerFrame() == port->getSamplesPerFrame());
mConnected = port;
}
void disconnect(FlowGraphPortFloatOutput *port) {
assert(mConnected == port);
(void) port;
mConnected = nullptr;
}
void disconnect() {
mConnected = nullptr;
}
/**
* Pull data from any output port that is connected.
*/
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
void pullReset() override;
private:
FlowGraphPortFloatOutput *mConnected = nullptr;
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no upstream nodes.
* It outputs data but does not consume data.
* By default, it will read its data from an external buffer.
*/
class FlowGraphSource : public FlowGraphNode {
public:
explicit FlowGraphSource(int32_t channelCount)
: output(*this, channelCount) {
}
virtual ~FlowGraphSource() = default;
FlowGraphPortFloatOutput output;
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no upstream nodes.
* It outputs data but does not consume data.
* By default, it will read its data from an external buffer.
*/
class FlowGraphSourceBuffered : public FlowGraphSource {
public:
explicit FlowGraphSourceBuffered(int32_t channelCount)
: FlowGraphSource(channelCount) {}
virtual ~FlowGraphSourceBuffered() = default;
/**
* Specify buffer that the node will read from.
*
* @param data TODO Consider using std::shared_ptr.
* @param numFrames
*/
void setData(const void *data, int32_t numFrames) {
mData = data;
mSizeInFrames = numFrames;
mFrameIndex = 0;
}
protected:
const void *mData = nullptr;
int32_t mSizeInFrames = 0; // number of frames in mData
int32_t mFrameIndex = 0; // index of next frame to be processed
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no downstream nodes.
* It consumes data but does not output data.
* This graph will be executed when data is read() from this node
* by pulling data from upstream nodes.
*/
class FlowGraphSink : public FlowGraphNode {
public:
explicit FlowGraphSink(int32_t channelCount)
: input(*this, channelCount) {
}
virtual ~FlowGraphSink() = default;
FlowGraphPortFloatInput input;
/**
* Dummy processor. The work happens in the read() method.
*
* @param numFrames
* @return number of frames actually processed
*/
int32_t onProcess(int32_t numFrames) override {
return numFrames;
}
virtual int32_t read(void *data, int32_t numFrames) = 0;
protected:
/**
* Pull data through the graph using this nodes last callCount.
* @param numFrames
* @return
*/
int32_t pullData(int32_t numFrames);
};
/***************************************************************************/
/**
* Base class for a node that has an input and an output with the same number of channels.
* This may include traditional filters, eg. FIR, but also include
* any processing node that converts input to output.
*/
class FlowGraphFilter : public FlowGraphNode {
public:
explicit FlowGraphFilter(int32_t channelCount)
: input(*this, channelCount)
, output(*this, channelCount) {
}
virtual ~FlowGraphFilter() = default;
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif /* FLOWGRAPH_FLOW_GRAPH_NODE_H */

View File

@ -0,0 +1,55 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_UTILITIES_H
#define FLOWGRAPH_UTILITIES_H
#include <unistd.h>
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
class FlowgraphUtilities {
public:
// This was copied from audio_utils/primitives.h
/**
* Convert a single-precision floating point value to a Q0.31 integer value.
* Rounds to nearest, ties away from 0.
*
* Values outside the range [-1.0, 1.0) are properly clamped to -2147483648 and 2147483647,
* including -Inf and +Inf. NaN values are considered undefined, and behavior may change
* depending on hardware and future implementation of this function.
*/
static int32_t clamp32FromFloat(float f)
{
static const float scale = (float)(1UL << 31);
static const float limpos = 1.;
static const float limneg = -1.;
if (f <= limneg) {
return -0x80000000; /* or 0x80000000 */
} else if (f >= limpos) {
return 0x7fffffff;
}
f *= scale;
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f > 0 ? f + 0.5 : f - 0.5;
}
};
#endif // FLOWGRAPH_UTILITIES_H

View File

@ -0,0 +1,47 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <unistd.h>
#include "ManyToMultiConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ManyToMultiConverter::ManyToMultiConverter(int32_t channelCount)
: inputs(channelCount)
, output(*this, channelCount) {
for (int i = 0; i < channelCount; i++) {
inputs[i] = std::make_unique<FlowGraphPortFloatInput>(*this, 1);
}
}
int32_t ManyToMultiConverter::onProcess(int32_t numFrames) {
int32_t channelCount = output.getSamplesPerFrame();
for (int ch = 0; ch < channelCount; ch++) {
const float *inputBuffer = inputs[ch]->getBuffer();
float *outputBuffer = output.getBuffer() + ch;
for (int i = 0; i < numFrames; i++) {
// read one, write into the proper interleaved output channel
float sample = *inputBuffer++;
*outputBuffer = sample;
outputBuffer += channelCount; // advance to next multichannel frame
}
}
return numFrames;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
#define FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include <vector>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Combine multiple mono inputs into one interleaved multi-channel output.
*/
class ManyToMultiConverter : public flowgraph::FlowGraphNode {
public:
explicit ManyToMultiConverter(int32_t channelCount);
virtual ~ManyToMultiConverter() = default;
int32_t onProcess(int numFrames) override;
void setEnabled(bool /*enabled*/) {}
std::vector<std::unique_ptr<flowgraph::FlowGraphPortFloatInput>> inputs;
flowgraph::FlowGraphPortFloatOutput output;
const char *getName() override {
return "ManyToMultiConverter";
}
private:
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H

View File

@ -0,0 +1,43 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <unistd.h>
#include "FlowGraphNode.h"
#include "MonoToMultiConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MonoToMultiConverter::MonoToMultiConverter(int32_t outputChannelCount)
: input(*this, 1)
, output(*this, outputChannelCount) {
}
MonoToMultiConverter::~MonoToMultiConverter() { }
int32_t MonoToMultiConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
// read one, write many
float sample = *inputBuffer++;
for (int channel = 0; channel < channelCount; channel++) {
*outputBuffer++ = sample;
}
}
return numFrames;
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
#define FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Convert a monophonic stream to a multi-channel interleaved stream
* with the same signal on each channel.
*/
class MonoToMultiConverter : public FlowGraphNode {
public:
explicit MonoToMultiConverter(int32_t outputChannelCount);
virtual ~MonoToMultiConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MonoToMultiConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H

View File

@ -0,0 +1,41 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <unistd.h>
#include "FlowGraphNode.h"
#include "MultiToMonoConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MultiToMonoConverter::MultiToMonoConverter(int32_t inputChannelCount)
: input(*this, inputChannelCount)
, output(*this, 1) {
}
MultiToMonoConverter::~MultiToMonoConverter() { }
int32_t MultiToMonoConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = input.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
// read first channel of multi stream, write many
*outputBuffer++ = *inputBuffer;
inputBuffer += channelCount;
}
return numFrames;
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H
#define FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Convert a multi-channel interleaved stream to a monophonic stream
* by extracting channel[0].
*/
class MultiToMonoConverter : public FlowGraphNode {
public:
explicit MultiToMonoConverter(int32_t inputChannelCount);
virtual ~MultiToMonoConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MultiToMonoConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H

View File

@ -0,0 +1,81 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "RampLinear.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
RampLinear::RampLinear(int32_t channelCount)
: FlowGraphFilter(channelCount) {
mTarget.store(1.0f);
}
void RampLinear::setLengthInFrames(int32_t frames) {
mLengthInFrames = frames;
}
void RampLinear::setTarget(float target) {
mTarget.store(target);
// If the ramp has not been used then start immediately at this level.
if (mLastCallCount == kInitialCallCount) {
forceCurrent(target);
}
}
float RampLinear::interpolateCurrent() {
return mLevelTo - (mRemaining * mScaler);
}
int32_t RampLinear::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
float target = getTarget();
if (target != mLevelTo) {
// Start new ramp. Continue from previous level.
mLevelFrom = interpolateCurrent();
mLevelTo = target;
mRemaining = mLengthInFrames;
mScaler = (mLevelTo - mLevelFrom) / mLengthInFrames; // for interpolation
}
int32_t framesLeft = numFrames;
if (mRemaining > 0) { // Ramping? This doesn't happen very often.
int32_t framesToRamp = std::min(framesLeft, mRemaining);
framesLeft -= framesToRamp;
while (framesToRamp > 0) {
float currentLevel = interpolateCurrent();
for (int ch = 0; ch < channelCount; ch++) {
*outputBuffer++ = *inputBuffer++ * currentLevel;
}
mRemaining--;
framesToRamp--;
}
}
// Process any frames after the ramp.
int32_t samplesLeft = framesLeft * channelCount;
for (int i = 0; i < samplesLeft; i++) {
*outputBuffer++ = *inputBuffer++ * mLevelTo;
}
return numFrames;
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_RAMP_LINEAR_H
#define FLOWGRAPH_RAMP_LINEAR_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* When the target is modified then the output will ramp smoothly
* between the original and the new target value.
* This can be used to smooth out control values and reduce pops.
*
* The target may be updated while a ramp is in progress, which will trigger
* a new ramp from the current value.
*/
class RampLinear : public FlowGraphFilter {
public:
explicit RampLinear(int32_t channelCount);
virtual ~RampLinear() = default;
int32_t onProcess(int32_t numFrames) override;
/**
* This is used for the next ramp.
* Calling this does not affect a ramp that is in progress.
*/
void setLengthInFrames(int32_t frames);
int32_t getLengthInFrames() const {
return mLengthInFrames;
}
/**
* This may be safely called by another thread.
* @param target
*/
void setTarget(float target);
float getTarget() const {
return mTarget.load();
}
/**
* Force the nextSegment to start from this level.
*
* WARNING: this can cause a discontinuity if called while the ramp is being used.
* Only call this when setting the initial ramp.
*
* @param level
*/
void forceCurrent(float level) {
mLevelFrom = level;
mLevelTo = level;
}
const char *getName() override {
return "RampLinear";
}
private:
float interpolateCurrent();
std::atomic<float> mTarget;
int32_t mLengthInFrames = 48000.0f / 100.0f ; // 10 msec at 48000 Hz;
int32_t mRemaining = 0;
float mScaler = 0.0f;
float mLevelFrom = 0.0f;
float mLevelTo = 0.0f;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_RAMP_LINEAR_H

View File

@ -0,0 +1,70 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SampleRateConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
using namespace resampler;
SampleRateConverter::SampleRateConverter(int32_t channelCount, MultiChannelResampler &resampler)
: FlowGraphFilter(channelCount)
, mResampler(resampler) {
setDataPulledAutomatically(false);
}
void SampleRateConverter::reset() {
FlowGraphNode::reset();
mInputCursor = kInitialCallCount;
}
// Return true if there is a sample available.
bool SampleRateConverter::isInputAvailable() {
// If we have consumed all of the input data then go out and get some more.
if (mInputCursor >= mNumValidInputFrames) {
mInputCallCount++;
mNumValidInputFrames = input.pullData(mInputCallCount, input.getFramesPerBuffer());
mInputCursor = 0;
}
return (mInputCursor < mNumValidInputFrames);
}
const float *SampleRateConverter::getNextInputFrame() {
const float *inputBuffer = input.getBuffer();
return &inputBuffer[mInputCursor++ * input.getSamplesPerFrame()];
}
int32_t SampleRateConverter::onProcess(int32_t numFrames) {
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int framesLeft = numFrames;
while (framesLeft > 0) {
// Gather input samples as needed.
if(mResampler.isWriteNeeded()) {
if (isInputAvailable()) {
const float *frame = getNextInputFrame();
mResampler.writeNextFrame(frame);
} else {
break;
}
} else {
// Output frame is interpolated from input samples.
mResampler.readNextFrame(outputBuffer);
outputBuffer += channelCount;
framesLeft--;
}
}
return numFrames - framesLeft;
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SAMPLE_RATE_CONVERTER_H
#define OBOE_SAMPLE_RATE_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
#include "resampler/MultiChannelResampler.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
class SampleRateConverter : public FlowGraphFilter {
public:
explicit SampleRateConverter(int32_t channelCount, resampler::MultiChannelResampler &mResampler);
virtual ~SampleRateConverter() = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SampleRateConverter";
}
void reset() override;
private:
// Return true if there is a sample available.
bool isInputAvailable();
// This assumes data is available. Only call after calling isInputAvailable().
const float *getNextInputFrame();
resampler::MultiChannelResampler &mResampler;
int32_t mInputCursor = 0; // offset into the input port buffer
int32_t mNumValidInputFrames = 0; // number of valid frames currently in the input port buffer
// We need our own callCount for upstream calls because calls occur at a different rate.
// This means we cannot have cyclic graphs or merges that contain an SRC.
int64_t mInputCallCount = 0;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //OBOE_SAMPLE_RATE_CONVERTER_H

View File

@ -0,0 +1,49 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SinkFloat.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkFloat::SinkFloat(int32_t channelCount)
: FlowGraphSink(channelCount) {
}
int32_t SinkFloat::read(void *data, int32_t numFrames) {
// printf("SinkFloat::read(,,%d)\n", numFrames);
float *floatData = (float *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesPulled = pullData(framesLeft);
// printf("SinkFloat::read: framesLeft = %d, framesPulled = %d\n", framesLeft, framesPulled);
if (framesPulled <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesPulled * channelCount;
memcpy(floatData, signal, numSamples * sizeof(float));
floatData += numSamples;
framesLeft -= framesPulled;
}
// printf("SinkFloat returning %d\n", numFrames - framesLeft);
return numFrames - framesLeft;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SINK_FLOAT_H
#define FLOWGRAPH_SINK_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSink that lets you read data as 32-bit floats.
*/
class SinkFloat : public FlowGraphSink {
public:
explicit SinkFloat(int32_t channelCount);
~SinkFloat() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkFloat";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_FLOAT_H

View File

@ -0,0 +1,57 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "SinkI16.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI16::SinkI16(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI16::read(void *data, int32_t numFrames) {
int16_t *shortData = (int16_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_i16_from_float(shortData, signal, numSamples);
shortData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
int32_t n = (int32_t) (*signal++ * 32768.0f);
*shortData++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SINK_I16_H
#define FLOWGRAPH_SINK_I16_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSink that lets you read data as 16-bit signed integers.
*/
class SinkI16 : public FlowGraphSink {
public:
explicit SinkI16(int32_t channelCount);
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI16";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_I16_H

View File

@ -0,0 +1,66 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SinkI24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI24::SinkI24(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI24::read(void *data, int32_t numFrames) {
uint8_t *byteData = (uint8_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *floatData = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_p24_from_float(byteData, floatData, numSamples);
static const int kBytesPerI24Packed = 3;
byteData += numSamples * kBytesPerI24Packed;
floatData += numSamples;
#else
const int32_t kI24PackedMax = 0x007FFFFF;
const int32_t kI24PackedMin = 0xFF800000;
for (int i = 0; i < numSamples; i++) {
int32_t n = (int32_t) (*floatData++ * 0x00800000);
n = std::min(kI24PackedMax, std::max(kI24PackedMin, n)); // clip
// Write as a packed 24-bit integer in Little Endian format.
*byteData++ = (uint8_t) n;
*byteData++ = (uint8_t) (n >> 8);
*byteData++ = (uint8_t) (n >> 16);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SINK_I24_H
#define FLOWGRAPH_SINK_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSink that lets you read data as packed 24-bit signed integers.
* The sample size is 3 bytes.
*/
class SinkI24 : public FlowGraphSink {
public:
explicit SinkI24(int32_t channelCount);
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI24";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_I24_H

View File

@ -0,0 +1,55 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
#include "FlowGraphNode.h"
#include "FlowgraphUtilities.h"
#include "SinkI32.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI32::SinkI32(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI32::read(void *data, int32_t numFrames) {
int32_t *intData = (int32_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_i32_from_float(intData, signal, numSamples);
intData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
*intData++ = FlowgraphUtilities::clamp32FromFloat(*signal++);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SINK_I32_H
#define FLOWGRAPH_SINK_I32_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
class SinkI32 : public FlowGraphSink {
public:
explicit SinkI32(int32_t channelCount);
~SinkI32() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI32";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_I32_H

View File

@ -0,0 +1,43 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "common/OboeDebug.h"
#include <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SourceFloat.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceFloat::SourceFloat(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceFloat::onProcess(int32_t numFrames) {
float *outputBuffer = output.getBuffer();
const int32_t channelCount = output.getSamplesPerFrame();
const int32_t framesLeft = mSizeInFrames - mFrameIndex;
const int32_t framesToProcess = std::min(numFrames, framesLeft);
const int32_t numSamples = framesToProcess * channelCount;
const float *floatBase = (float *) mData;
const float *floatData = &floatBase[mFrameIndex * channelCount];
memcpy(outputBuffer, floatData, numSamples * sizeof(float));
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SOURCE_FLOAT_H
#define FLOWGRAPH_SOURCE_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSource that reads a block of pre-defined float data.
*/
class SourceFloat : public FlowGraphSourceBuffered {
public:
explicit SourceFloat(int32_t channelCount);
~SourceFloat() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceFloat";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_FLOAT_H

View File

@ -0,0 +1,54 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "SourceI16.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI16::SourceI16(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI16::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int32_t framesLeft = mSizeInFrames - mFrameIndex;
int32_t framesToProcess = std::min(numFrames, framesLeft);
int32_t numSamples = framesToProcess * channelCount;
const int16_t *shortBase = static_cast<const int16_t *>(mData);
const int16_t *shortData = &shortBase[mFrameIndex * channelCount];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i16(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *shortData++ * (1.0f / 32768);
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SOURCE_I16_H
#define FLOWGRAPH_SOURCE_I16_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSource that reads a block of pre-defined 16-bit integer data.
*/
class SourceI16 : public FlowGraphSourceBuffered {
public:
explicit SourceI16(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI16";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_I16_H

View File

@ -0,0 +1,65 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
#include "FlowGraphNode.h"
#include "SourceI24.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
constexpr int kBytesPerI24Packed = 3;
SourceI24::SourceI24(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI24::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int32_t framesLeft = mSizeInFrames - mFrameIndex;
int32_t framesToProcess = std::min(numFrames, framesLeft);
int32_t numSamples = framesToProcess * channelCount;
const uint8_t *byteBase = (uint8_t *) mData;
const uint8_t *byteData = &byteBase[mFrameIndex * channelCount * kBytesPerI24Packed];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_p24(floatData, byteData, numSamples);
#else
static const float scale = 1. / (float)(1UL << 31);
for (int i = 0; i < numSamples; i++) {
// Assemble the data assuming Little Endian format.
int32_t pad = byteData[2];
pad <<= 8;
pad |= byteData[1];
pad <<= 8;
pad |= byteData[0];
pad <<= 8; // Shift to 32 bit data so the sign is correct.
byteData += kBytesPerI24Packed;
*floatData++ = pad * scale; // scale to range -1.0 to 1.0
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SOURCE_I24_H
#define FLOWGRAPH_SOURCE_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSource that reads a block of pre-defined 24-bit packed integer data.
*/
class SourceI24 : public FlowGraphSourceBuffered {
public:
explicit SourceI24(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI24";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_I24_H

View File

@ -0,0 +1,54 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
#include "FlowGraphNode.h"
#include "SourceI32.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI32::SourceI32(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI32::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
const int32_t channelCount = output.getSamplesPerFrame();
const int32_t framesLeft = mSizeInFrames - mFrameIndex;
const int32_t framesToProcess = std::min(numFrames, framesLeft);
const int32_t numSamples = framesToProcess * channelCount;
const int32_t *intBase = static_cast<const int32_t *>(mData);
const int32_t *intData = &intBase[mFrameIndex * channelCount];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i32(floatData, intData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_SOURCE_I32_H
#define FLOWGRAPH_SOURCE_I32_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
class SourceI32 : public FlowGraphSourceBuffered {
public:
explicit SourceI32(int32_t channelCount);
~SourceI32() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI32";
}
private:
static constexpr float kScale = 1.0 / (1UL << 31);
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_I32_H

View File

@ -0,0 +1,68 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H
#define RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H
#include <math.h>
namespace resampler {
/**
* Calculate a HyperbolicCosineWindow window centered at 0.
* This can be used in place of a Kaiser window.
*
* The code is based on an anonymous contribution by "a concerned citizen":
* https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation
*/
class HyperbolicCosineWindow {
public:
HyperbolicCosineWindow() {
setStopBandAttenuation(60);
}
/**
* @param attenuation typical values range from 30 to 90 dB
* @return beta
*/
double setStopBandAttenuation(double attenuation) {
double alpha = ((-325.1e-6 * attenuation + 0.1677) * attenuation) - 3.149;
setAlpha(alpha);
return alpha;
}
void setAlpha(double alpha) {
mAlpha = alpha;
mInverseCoshAlpha = 1.0 / cosh(alpha);
}
/**
* @param x ranges from -1.0 to +1.0
*/
double operator()(double x) {
double x2 = x * x;
if (x2 >= 1.0) return 0.0;
double w = mAlpha * sqrt(1.0 - x2);
return cosh(w) * mInverseCoshAlpha;
}
private:
double mAlpha = 0.0;
double mInverseCoshAlpha = 1.0;
};
} // namespace resampler
#endif //RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H

View File

@ -0,0 +1,50 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "IntegerRatio.h"
using namespace resampler;
// Enough primes to cover the common sample rates.
static const int kPrimes[] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131, 137, 139, 149,
151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199};
void IntegerRatio::reduce() {
for (int prime : kPrimes) {
if (mNumerator < prime || mDenominator < prime) {
break;
}
// Find biggest prime factor for numerator.
while (true) {
int top = mNumerator / prime;
int bottom = mDenominator / prime;
if ((top >= 1)
&& (bottom >= 1)
&& (top * prime == mNumerator) // divided evenly?
&& (bottom * prime == mDenominator)) {
mNumerator = top;
mDenominator = bottom;
} else {
break;
}
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_INTEGER_RATIO_H
#define OBOE_INTEGER_RATIO_H
#include <sys/types.h>
namespace resampler {
/**
* Represent the ratio of two integers.
*/
class IntegerRatio {
public:
IntegerRatio(int32_t numerator, int32_t denominator)
: mNumerator(numerator), mDenominator(denominator) {}
/**
* Reduce by removing common prime factors.
*/
void reduce();
int32_t getNumerator() {
return mNumerator;
}
int32_t getDenominator() {
return mDenominator;
}
private:
int32_t mNumerator;
int32_t mDenominator;
};
}
#endif //OBOE_INTEGER_RATIO_H

View File

@ -0,0 +1,87 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef RESAMPLER_KAISER_WINDOW_H
#define RESAMPLER_KAISER_WINDOW_H
#include <math.h>
namespace resampler {
/**
* Calculate a Kaiser window centered at 0.
*/
class KaiserWindow {
public:
KaiserWindow() {
setStopBandAttenuation(60);
}
/**
* @param attenuation typical values range from 30 to 90 dB
* @return beta
*/
double setStopBandAttenuation(double attenuation) {
double beta = 0.0;
if (attenuation > 50) {
beta = 0.1102 * (attenuation - 8.7);
} else if (attenuation >= 21) {
double a21 = attenuation - 21;
beta = 0.5842 * pow(a21, 0.4) + (0.07886 * a21);
}
setBeta(beta);
return beta;
}
void setBeta(double beta) {
mBeta = beta;
mInverseBesselBeta = 1.0 / bessel(beta);
}
/**
* @param x ranges from -1.0 to +1.0
*/
double operator()(double x) {
double x2 = x * x;
if (x2 >= 1.0) return 0.0;
double w = mBeta * sqrt(1.0 - x2);
return bessel(w) * mInverseBesselBeta;
}
// Approximation of a
// modified zero order Bessel function of the first kind.
// Based on a discussion at:
// https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation
static double bessel(double x) {
double y = cosh(0.970941817426052 * x);
y += cosh(0.8854560256532099 * x);
y += cosh(0.7485107481711011 * x);
y += cosh(0.5680647467311558 * x);
y += cosh(0.3546048870425356 * x);
y += cosh(0.120536680255323 * x);
y *= 2;
y += cosh(x);
y /= 13;
return y;
}
private:
double mBeta = 0.0;
double mInverseBesselBeta = 1.0;
};
} // namespace resampler
#endif //RESAMPLER_KAISER_WINDOW_H

View File

@ -0,0 +1,42 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "LinearResampler.h"
using namespace resampler;
LinearResampler::LinearResampler(const MultiChannelResampler::Builder &builder)
: MultiChannelResampler(builder) {
mPreviousFrame = std::make_unique<float[]>(getChannelCount());
mCurrentFrame = std::make_unique<float[]>(getChannelCount());
}
void LinearResampler::writeFrame(const float *frame) {
memcpy(mPreviousFrame.get(), mCurrentFrame.get(), sizeof(float) * getChannelCount());
memcpy(mCurrentFrame.get(), frame, sizeof(float) * getChannelCount());
}
void LinearResampler::readFrame(float *frame) {
float *previous = mPreviousFrame.get();
float *current = mCurrentFrame.get();
float phase = (float) getIntegerPhase() / mDenominator;
// iterate across samples in the frame
for (int channel = 0; channel < getChannelCount(); channel++) {
float f0 = *previous++;
float f1 = *current++;
*frame++ = f0 + (phase * (f1 - f0));
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_LINEAR_RESAMPLER_H
#define OBOE_LINEAR_RESAMPLER_H
#include <memory>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
namespace resampler {
/**
* Simple resampler that uses bi-linear interpolation.
*/
class LinearResampler : public MultiChannelResampler {
public:
LinearResampler(const MultiChannelResampler::Builder &builder);
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
private:
std::unique_ptr<float[]> mPreviousFrame;
std::unique_ptr<float[]> mCurrentFrame;
};
} // namespace resampler
#endif //OBOE_LINEAR_RESAMPLER_H

View File

@ -0,0 +1,171 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <math.h>
#include "IntegerRatio.h"
#include "LinearResampler.h"
#include "MultiChannelResampler.h"
#include "PolyphaseResampler.h"
#include "PolyphaseResamplerMono.h"
#include "PolyphaseResamplerStereo.h"
#include "SincResampler.h"
#include "SincResamplerStereo.h"
using namespace resampler;
MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
: mNumTaps(builder.getNumTaps())
, mX(builder.getChannelCount() * builder.getNumTaps() * 2)
, mSingleFrame(builder.getChannelCount())
, mChannelCount(builder.getChannelCount())
{
// Reduce sample rates to the smallest ratio.
// For example 44100/48000 would become 147/160.
IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate());
ratio.reduce();
mNumerator = ratio.getNumerator();
mDenominator = ratio.getDenominator();
mIntegerPhase = mDenominator;
}
// static factory method
MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
int32_t inputRate,
int32_t outputRate,
Quality quality) {
Builder builder;
builder.setInputRate(inputRate);
builder.setOutputRate(outputRate);
builder.setChannelCount(channelCount);
switch (quality) {
case Quality::Fastest:
builder.setNumTaps(2);
break;
case Quality::Low:
builder.setNumTaps(4);
break;
case Quality::Medium:
default:
builder.setNumTaps(8);
break;
case Quality::High:
builder.setNumTaps(16);
break;
case Quality::Best:
builder.setNumTaps(32);
break;
}
// Set the cutoff frequency so that we do not get aliasing when down-sampling.
if (inputRate > outputRate) {
builder.setNormalizedCutoff(kDefaultNormalizedCutoff);
}
return builder.build();
}
MultiChannelResampler *MultiChannelResampler::Builder::build() {
if (getNumTaps() == 2) {
// Note that this does not do low pass filteringh.
return new LinearResampler(*this);
}
IntegerRatio ratio(getInputRate(), getOutputRate());
ratio.reduce();
bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients;
if (usePolyphase) {
if (getChannelCount() == 1) {
return new PolyphaseResamplerMono(*this);
} else if (getChannelCount() == 2) {
return new PolyphaseResamplerStereo(*this);
} else {
return new PolyphaseResampler(*this);
}
} else {
// Use less optimized resampler that uses a float phaseIncrement.
// TODO mono resampler
if (getChannelCount() == 2) {
return new SincResamplerStereo(*this);
} else {
return new SincResampler(*this);
}
}
}
void MultiChannelResampler::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * getChannelCount()];
int offset = getNumTaps() * getChannelCount();
for (int channel = 0; channel < getChannelCount(); channel++) {
// Write twice so we avoid having to wrap when reading.
dest[channel] = dest[channel + offset] = frame[channel];
}
}
float MultiChannelResampler::sinc(float radians) {
if (abs(radians) < 1.0e-9) return 1.0f; // avoid divide by zero
return sinf(radians) / radians; // Sinc function
}
// Generate coefficients in the order they will be used by readFrame().
// This is more complicated but readFrame() is called repeatedly and should be optimized.
void MultiChannelResampler::generateCoefficients(int32_t inputRate,
int32_t outputRate,
int32_t numRows,
double phaseIncrement,
float normalizedCutoff) {
mCoefficients.resize(getNumTaps() * numRows);
int coefficientIndex = 0;
double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples
// Stretch the sinc function for low pass filtering.
const float cutoffScaler = normalizedCutoff *
((outputRate < inputRate)
? ((float)outputRate / inputRate)
: ((float)inputRate / outputRate));
const int numTapsHalf = getNumTaps() / 2; // numTaps must be even.
const float numTapsHalfInverse = 1.0f / numTapsHalf;
for (int i = 0; i < numRows; i++) {
float tapPhase = phase - numTapsHalf;
float gain = 0.0; // sum of raw coefficients
int gainCursor = coefficientIndex;
for (int tap = 0; tap < getNumTaps(); tap++) {
float radians = tapPhase * M_PI;
#if MCR_USE_KAISER
float window = mKaiserWindow(tapPhase * numTapsHalfInverse);
#else
float window = mCoshWindow(tapPhase * numTapsHalfInverse);
#endif
float coefficient = sinc(radians * cutoffScaler) * window;
mCoefficients.at(coefficientIndex++) = coefficient;
gain += coefficient;
tapPhase += 1.0;
}
phase += phaseIncrement;
while (phase >= 1.0) {
phase -= 1.0;
}
// Correct for gain variations.
float gainCorrection = 1.0 / gain; // normalize the gain
for (int tap = 0; tap < getNumTaps(); tap++) {
mCoefficients.at(gainCursor + tap) *= gainCorrection;
}
}
}

View File

@ -0,0 +1,272 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_MULTICHANNEL_RESAMPLER_H
#define OBOE_MULTICHANNEL_RESAMPLER_H
#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#ifndef MCR_USE_KAISER
// It appears from the spectrogram that the HyperbolicCosine window leads to fewer artifacts.
// And it is faster to calculate.
#define MCR_USE_KAISER 0
#endif
#if MCR_USE_KAISER
#include "KaiserWindow.h"
#else
#include "HyperbolicCosineWindow.h"
#endif
namespace resampler {
class MultiChannelResampler {
public:
enum class Quality : int32_t {
Fastest,
Low,
Medium,
High,
Best,
};
class Builder {
public:
/**
* Construct an optimal resampler based on the specified parameters.
* @return address of a resampler
*/
MultiChannelResampler *build();
/**
* The number of taps in the resampling filter.
* More taps gives better quality but uses more CPU time.
* This typically ranges from 4 to 64. Default is 16.
*
* For polyphase filters, numTaps must be a multiple of four for loop unrolling.
* @param numTaps number of taps for the filter
* @return address of this builder for chaining calls
*/
Builder *setNumTaps(int32_t numTaps) {
mNumTaps = numTaps;
return this;
}
/**
* Use 1 for mono, 2 for stereo, etc. Default is 1.
*
* @param channelCount number of channels
* @return address of this builder for chaining calls
*/
Builder *setChannelCount(int32_t channelCount) {
mChannelCount = channelCount;
return this;
}
/**
* Default is 48000.
*
* @param inputRate sample rate of the input stream
* @return address of this builder for chaining calls
*/
Builder *setInputRate(int32_t inputRate) {
mInputRate = inputRate;
return this;
}
/**
* Default is 48000.
*
* @param outputRate sample rate of the output stream
* @return address of this builder for chaining calls
*/
Builder *setOutputRate(int32_t outputRate) {
mOutputRate = outputRate;
return this;
}
/**
* Set cutoff frequency relative to the Nyquist rate of the output sample rate.
* Set to 1.0 to match the Nyquist frequency.
* Set lower to reduce aliasing.
* Default is 0.70.
*
* @param normalizedCutoff anti-aliasing filter cutoff
* @return address of this builder for chaining calls
*/
Builder *setNormalizedCutoff(float normalizedCutoff) {
mNormalizedCutoff = normalizedCutoff;
return this;
}
int32_t getNumTaps() const {
return mNumTaps;
}
int32_t getChannelCount() const {
return mChannelCount;
}
int32_t getInputRate() const {
return mInputRate;
}
int32_t getOutputRate() const {
return mOutputRate;
}
float getNormalizedCutoff() const {
return mNormalizedCutoff;
}
protected:
int32_t mChannelCount = 1;
int32_t mNumTaps = 16;
int32_t mInputRate = 48000;
int32_t mOutputRate = 48000;
float mNormalizedCutoff = kDefaultNormalizedCutoff;
};
virtual ~MultiChannelResampler() = default;
/**
* Factory method for making a resampler that is optimal for the given inputs.
*
* @param channelCount number of channels, 2 for stereo
* @param inputRate sample rate of the input stream
* @param outputRate sample rate of the output stream
* @param quality higher quality sounds better but uses more CPU
* @return an optimal resampler
*/
static MultiChannelResampler *make(int32_t channelCount,
int32_t inputRate,
int32_t outputRate,
Quality quality);
bool isWriteNeeded() const {
return mIntegerPhase >= mDenominator;
}
/**
* Write a frame containing N samples.
*
* @param frame pointer to the first sample in a frame
*/
void writeNextFrame(const float *frame) {
writeFrame(frame);
advanceWrite();
}
/**
* Read a frame containing N samples.
*
* @param frame pointer to the first sample in a frame
*/
void readNextFrame(float *frame) {
readFrame(frame);
advanceRead();
}
int getNumTaps() const {
return mNumTaps;
}
int getChannelCount() const {
return mChannelCount;
}
static float hammingWindow(float radians, float spread);
static float sinc(float radians);
protected:
explicit MultiChannelResampler(const MultiChannelResampler::Builder &builder);
/**
* Write a frame containing N samples.
* Call advanceWrite() after calling this.
* @param frame pointer to the first sample in a frame
*/
virtual void writeFrame(const float *frame);
/**
* Read a frame containing N samples using interpolation.
* Call advanceRead() after calling this.
* @param frame pointer to the first sample in a frame
*/
virtual void readFrame(float *frame) = 0;
void advanceWrite() {
mIntegerPhase -= mDenominator;
}
void advanceRead() {
mIntegerPhase += mNumerator;
}
/**
* Generate the filter coefficients in optimal order.
* @param inputRate sample rate of the input stream
* @param outputRate sample rate of the output stream
* @param numRows number of rows in the array that contain a set of tap coefficients
* @param phaseIncrement how much to increment the phase between rows
* @param normalizedCutoff filter cutoff frequency normalized to Nyquist rate of output
*/
void generateCoefficients(int32_t inputRate,
int32_t outputRate,
int32_t numRows,
double phaseIncrement,
float normalizedCutoff);
int32_t getIntegerPhase() {
return mIntegerPhase;
}
static constexpr int kMaxCoefficients = 8 * 1024;
std::vector<float> mCoefficients;
const int mNumTaps;
int mCursor = 0;
std::vector<float> mX; // delayed input values for the FIR
std::vector<float> mSingleFrame; // one frame for temporary use
int32_t mIntegerPhase = 0;
int32_t mNumerator = 0;
int32_t mDenominator = 0;
private:
#if MCR_USE_KAISER
KaiserWindow mKaiserWindow;
#else
HyperbolicCosineWindow mCoshWindow;
#endif
static constexpr float kDefaultNormalizedCutoff = 0.70f;
const int mChannelCount;
};
}
#endif //OBOE_MULTICHANNEL_RESAMPLER_H

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <math.h>
#include "IntegerRatio.h"
#include "PolyphaseResampler.h"
using namespace resampler;
PolyphaseResampler::PolyphaseResampler(const MultiChannelResampler::Builder &builder)
: MultiChannelResampler(builder)
{
assert((getNumTaps() % 4) == 0); // Required for loop unrolling.
int32_t inputRate = builder.getInputRate();
int32_t outputRate = builder.getOutputRate();
int32_t numRows = mDenominator;
double phaseIncrement = (double) inputRate / (double) outputRate;
generateCoefficients(inputRate, outputRate,
numRows, phaseIncrement,
builder.getNormalizedCutoff());
}
void PolyphaseResampler::readFrame(float *frame) {
// Clear accumulator for mixing.
std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0);
// printf("PolyphaseResampler: mCoefficientCursor = %4d\n", mCoefficientCursor);
// Multiply input times windowed sinc function.
float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[mCursor * getChannelCount()];
for (int i = 0; i < mNumTaps; i++) {
float coefficient = *coefficients++;
// printf("PolyphaseResampler: coeff = %10.6f, xFrame[0] = %10.6f\n", coefficient, xFrame[0]);
for (int channel = 0; channel < getChannelCount(); channel++) {
mSingleFrame[channel] += *xFrame++ * coefficient;
}
}
// Advance and wrap through coefficients.
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulator to output.
for (int channel = 0; channel < getChannelCount(); channel++) {
frame[channel] = mSingleFrame[channel];
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_POLYPHASE_RESAMPLER_H
#define OBOE_POLYPHASE_RESAMPLER_H
#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
namespace resampler {
/**
* Resampler that is optimized for a reduced ratio of sample rates.
* All of the coefficients for each possible phase value are pre-calculated.
*/
class PolyphaseResampler : public MultiChannelResampler {
public:
/**
*
* @param builder containing lots of parameters
*/
explicit PolyphaseResampler(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResampler() = default;
void readFrame(float *frame) override;
protected:
int32_t mCoefficientCursor = 0;
};
}
#endif //OBOE_POLYPHASE_RESAMPLER_H

View File

@ -0,0 +1,63 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include "PolyphaseResamplerMono.h"
using namespace resampler;
#define MONO 1
PolyphaseResamplerMono::PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder)
: PolyphaseResampler(builder) {
assert(builder.getChannelCount() == MONO);
}
void PolyphaseResamplerMono::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * MONO];
const int offset = mNumTaps * MONO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float sample = frame[0];
// Put ordered writes together.
dest[0] = sample;
dest[offset] = sample;
}
void PolyphaseResamplerMono::readFrame(float *frame) {
// Clear accumulator.
float sum = 0.0;
// Multiply input times precomputed windowed sinc function.
const float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[mCursor * MONO];
const int numLoops = mNumTaps >> 2; // n/4
for (int i = 0; i < numLoops; i++) {
// Manual loop unrolling, might get converted to SIMD.
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
}
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulator to output.
frame[0] = sum;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_POLYPHASE_RESAMPLER_MONO_H
#define OBOE_POLYPHASE_RESAMPLER_MONO_H
#include <sys/types.h>
#include <unistd.h>
#include "PolyphaseResampler.h"
namespace resampler {
class PolyphaseResamplerMono : public PolyphaseResampler {
public:
explicit PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResamplerMono() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
}
#endif //OBOE_POLYPHASE_RESAMPLER_MONO_H

View File

@ -0,0 +1,79 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include "PolyphaseResamplerStereo.h"
using namespace resampler;
#define STEREO 2
PolyphaseResamplerStereo::PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder)
: PolyphaseResampler(builder) {
assert(builder.getChannelCount() == STEREO);
}
void PolyphaseResamplerStereo::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * STEREO];
const int offset = mNumTaps * STEREO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float left = frame[0];
const float right = frame[1];
// Put ordered writes together.
dest[0] = left;
dest[1] = right;
dest[offset] = left;
dest[1 + offset] = right;
}
void PolyphaseResamplerStereo::readFrame(float *frame) {
// Clear accumulators.
float left = 0.0;
float right = 0.0;
// Multiply input times precomputed windowed sinc function.
const float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[mCursor * STEREO];
const int numLoops = mNumTaps >> 2; // n/4
for (int i = 0; i < numLoops; i++) {
// Manual loop unrolling, might get converted to SIMD.
float coefficient = *coefficients++;
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
}
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulators to output.
frame[0] = left;
frame[1] = right;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_POLYPHASE_RESAMPLER_STEREO_H
#define OBOE_POLYPHASE_RESAMPLER_STEREO_H
#include <sys/types.h>
#include <unistd.h>
#include "PolyphaseResampler.h"
namespace resampler {
class PolyphaseResamplerStereo : public PolyphaseResampler {
public:
explicit PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResamplerStereo() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
}
#endif //OBOE_POLYPHASE_RESAMPLER_STEREO_H

View File

@ -0,0 +1,77 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <math.h>
#include "SincResampler.h"
using namespace resampler;
SincResampler::SincResampler(const MultiChannelResampler::Builder &builder)
: MultiChannelResampler(builder)
, mSingleFrame2(builder.getChannelCount()) {
assert((getNumTaps() % 4) == 0); // Required for loop unrolling.
mNumRows = kMaxCoefficients / getNumTaps(); // no guard row needed
// printf("SincResampler: numRows = %d\n", mNumRows);
mPhaseScaler = (double) mNumRows / mDenominator;
double phaseIncrement = 1.0 / mNumRows;
generateCoefficients(builder.getInputRate(),
builder.getOutputRate(),
mNumRows,
phaseIncrement,
builder.getNormalizedCutoff());
}
void SincResampler::readFrame(float *frame) {
// Clear accumulator for mixing.
std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0);
std::fill(mSingleFrame2.begin(), mSingleFrame2.end(), 0.0);
// Determine indices into coefficients table.
double tablePhase = getIntegerPhase() * mPhaseScaler;
int index1 = static_cast<int>(floor(tablePhase));
if (index1 >= mNumRows) { // no guard row needed because we wrap the indices
tablePhase -= mNumRows;
index1 -= mNumRows;
}
int index2 = index1 + 1;
if (index2 >= mNumRows) { // no guard row needed because we wrap the indices
index2 -= mNumRows;
}
float *coefficients1 = &mCoefficients[index1 * getNumTaps()];
float *coefficients2 = &mCoefficients[index2 * getNumTaps()];
float *xFrame = &mX[mCursor * getChannelCount()];
for (int i = 0; i < mNumTaps; i++) {
float coefficient1 = *coefficients1++;
float coefficient2 = *coefficients2++;
for (int channel = 0; channel < getChannelCount(); channel++) {
float sample = *xFrame++;
mSingleFrame[channel] += sample * coefficient1;
mSingleFrame2[channel] += sample * coefficient2;
}
}
// Interpolate and copy to output.
float fraction = tablePhase - index1;
for (int channel = 0; channel < getChannelCount(); channel++) {
float low = mSingleFrame[channel];
float high = mSingleFrame2[channel];
frame[channel] = low + (fraction * (high - low));
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SINC_RESAMPLER_H
#define OBOE_SINC_RESAMPLER_H
#include <memory>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
namespace resampler {
/**
* Resampler that can interpolate between coefficients.
* This can be used to support arbitrary ratios.
*/
class SincResampler : public MultiChannelResampler {
public:
explicit SincResampler(const MultiChannelResampler::Builder &builder);
virtual ~SincResampler() = default;
void readFrame(float *frame) override;
protected:
std::vector<float> mSingleFrame2; // for interpolation
int32_t mNumRows = 0;
double mPhaseScaler = 1.0;
};
}
#endif //OBOE_SINC_RESAMPLER_H

View File

@ -0,0 +1,81 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <math.h>
#include "SincResamplerStereo.h"
using namespace resampler;
#define STEREO 2
SincResamplerStereo::SincResamplerStereo(const MultiChannelResampler::Builder &builder)
: SincResampler(builder) {
assert(builder.getChannelCount() == STEREO);
}
void SincResamplerStereo::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * STEREO];
const int offset = mNumTaps * STEREO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float left = frame[0];
const float right = frame[1];
// Put ordered writes together.
dest[0] = left;
dest[1] = right;
dest[offset] = left;
dest[1 + offset] = right;
}
// Multiply input times windowed sinc function.
void SincResamplerStereo::readFrame(float *frame) {
// Clear accumulator for mixing.
std::fill(mSingleFrame.begin(), mSingleFrame.end(), 0.0);
std::fill(mSingleFrame2.begin(), mSingleFrame2.end(), 0.0);
// Determine indices into coefficients table.
double tablePhase = getIntegerPhase() * mPhaseScaler;
int index1 = static_cast<int>(floor(tablePhase));
float *coefficients1 = &mCoefficients[index1 * getNumTaps()];
int index2 = (index1 + 1);
if (index2 >= mNumRows) { // no guard row needed because we wrap the indices
index2 = 0;
}
float *coefficients2 = &mCoefficients[index2 * getNumTaps()];
float *xFrame = &mX[mCursor * getChannelCount()];
for (int i = 0; i < mNumTaps; i++) {
float coefficient1 = *coefficients1++;
float coefficient2 = *coefficients2++;
for (int channel = 0; channel < getChannelCount(); channel++) {
float sample = *xFrame++;
mSingleFrame[channel] += sample * coefficient1;
mSingleFrame2[channel] += sample * coefficient2;
}
}
// Interpolate and copy to output.
float fraction = tablePhase - index1;
for (int channel = 0; channel < getChannelCount(); channel++) {
float low = mSingleFrame[channel];
float high = mSingleFrame2[channel];
frame[channel] = low + (fraction * (high - low));
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SINC_RESAMPLER_STEREO_H
#define OBOE_SINC_RESAMPLER_STEREO_H
#include <sys/types.h>
#include <unistd.h>
#include "SincResampler.h"
namespace resampler {
class SincResamplerStereo : public SincResampler {
public:
explicit SincResamplerStereo(const MultiChannelResampler::Builder &builder);
virtual ~SincResamplerStereo() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
}
#endif //OBOE_SINC_RESAMPLER_STEREO_H

View File

@ -0,0 +1,359 @@
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include "oboe/AudioStreamBuilder.h"
#include "AudioInputStreamOpenSLES.h"
#include "AudioStreamOpenSLES.h"
#include "OpenSLESUtilities.h"
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:
case InputPreset::VoicePerformance:
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) {
}
AudioInputStreamOpenSLES::~AudioInputStreamOpenSLES() {
}
// Calculate masks specific to INPUT streams.
SLuint32 AudioInputStreamOpenSLES::channelCountToChannelMask(int channelCount) const {
// Derived from internal sles_channel_in_mask_from_count(chanCount);
// in "frameworks/wilhelm/src/android/channels.cpp".
// Yes, it seems strange to use SPEAKER constants to describe inputs.
// But that is how OpenSL ES does it internally.
switch (channelCount) {
case 1:
return SL_SPEAKER_FRONT_LEFT;
case 2:
return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
default:
return channelCountToChannelMaskDefault(channelCount);
}
}
Result AudioInputStreamOpenSLES::open() {
logUnsupportedAttributes();
SLAndroidConfigurationItf configItf = nullptr;
if (getSdkVersion() < __ANDROID_API_M__ && mFormat == AudioFormat::Float){
// TODO: Allow floating point format on API <23 using float->int16 converter
return Result::ErrorInvalidFormat;
}
// If audio format is unspecified then choose a suitable default.
// API 23+: FLOAT
// API <23: INT16
if (mFormat == AudioFormat::Unspecified){
mFormat = (getSdkVersion() < __ANDROID_API_M__) ?
AudioFormat::I16 : AudioFormat::Float;
}
Result oboeResult = AudioStreamOpenSLES::open();
if (Result::OK != oboeResult) return oboeResult;
SLuint32 bitsPerSample = static_cast<SLuint32>(getBytesPerSample() * kBitsPerByte);
// configure audio sink
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType
static_cast<SLuint32>(kBufferQueueLength)}; // numBuffers
// Define the audio data format.
SLDataFormat_PCM format_pcm = {
SL_DATAFORMAT_PCM, // formatType
static_cast<SLuint32>(mChannelCount), // numChannels
static_cast<SLuint32>(mSampleRate * kMillisPerSecond), // milliSamplesPerSec
bitsPerSample, // bitsPerSample
bitsPerSample, // containerSize;
channelCountToChannelMask(mChannelCount), // channelMask
getDefaultByteOrder(),
};
SLDataSink audioSink = {&loc_bufq, &format_pcm};
/**
* API 23 (Marshmallow) introduced support for floating-point data representation and an
* extended data format type: SLAndroidDataFormat_PCM_EX for recording streams (playback streams
* got this in API 21). If running on API 23+ use this newer format type, creating it from our
* original format.
*/
SLAndroidDataFormat_PCM_EX format_pcm_ex;
if (getSdkVersion() >= __ANDROID_API_M__) {
SLuint32 representation = OpenSLES_ConvertFormatToRepresentation(getFormat());
// Fill in the format structure.
format_pcm_ex = OpenSLES_createExtendedFormat(format_pcm, representation);
// Use in place of the previous format.
audioSink.pFormat = &format_pcm_ex;
}
// configure audio source
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,
SL_IODEVICE_AUDIOINPUT,
SL_DEFAULTDEVICEID_AUDIOINPUT,
NULL};
SLDataSource audioSrc = {&loc_dev, NULL};
SLresult result = EngineOpenSLES::getInstance().createAudioRecorder(&mObjectInterface,
&audioSrc,
&audioSink);
if (SL_RESULT_SUCCESS != result) {
LOGE("createAudioRecorder() result:%s", getSLErrStr(result));
goto error;
}
// Configure the stream.
result = (*mObjectInterface)->GetInterface(mObjectInterface,
SL_IID_ANDROIDCONFIGURATION,
&configItf);
if (SL_RESULT_SUCCESS != result) {
LOGW("%s() GetInterface(SL_IID_ANDROIDCONFIGURATION) failed with %s",
__func__, getSLErrStr(result));
} else {
if (getInputPreset() == InputPreset::VoicePerformance) {
LOGD("OpenSL ES does not support InputPreset::VoicePerformance. Use VoiceRecognition.");
mInputPreset = InputPreset::VoiceRecognition;
}
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;
LOGD("Setting InputPreset %d failed. Using VoiceRecognition instead.", getInputPreset());
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);
if (SL_RESULT_SUCCESS != result) {
LOGE("Realize recorder object result:%s", getSLErrStr(result));
goto error;
}
result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_RECORD, &mRecordInterface);
if (SL_RESULT_SUCCESS != result) {
LOGE("GetInterface RECORD result:%s", getSLErrStr(result));
goto error;
}
result = AudioStreamOpenSLES::registerBufferQueueCallback();
if (SL_RESULT_SUCCESS != result) {
goto error;
}
result = updateStreamParameters(configItf);
if (SL_RESULT_SUCCESS != result) {
goto error;
}
oboeResult = configureBufferSizes(mSampleRate);
if (Result::OK != oboeResult) {
goto error;
}
allocateFifo();
setState(StreamState::Open);
return Result::OK;
error:
return Result::ErrorInternal; // TODO convert error from SLES to OBOE
}
Result AudioInputStreamOpenSLES::close() {
LOGD("AudioInputStreamOpenSLES::%s()", __func__);
std::lock_guard<std::mutex> lock(mLock);
Result result = Result::OK;
if (getState() == StreamState::Closed){
result = Result::ErrorClosed;
} else {
requestStop_l();
// invalidate any interfaces
mRecordInterface = nullptr;
result = AudioStreamOpenSLES::close_l();
}
return result;
}
Result AudioInputStreamOpenSLES::setRecordState_l(SLuint32 newState) {
LOGD("AudioInputStreamOpenSLES::%s(%u)", __func__, newState);
Result result = Result::OK;
if (mRecordInterface == nullptr) {
LOGE("AudioInputStreamOpenSLES::%s() mRecordInterface is null", __func__);
return Result::ErrorInvalidState;
}
SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState);
//LOGD("AudioInputStreamOpenSLES::%s(%u) returned %u", __func__, newState, slResult);
if (SL_RESULT_SUCCESS != slResult) {
LOGE("AudioInputStreamOpenSLES::%s(%u) returned error %s",
__func__, newState, getSLErrStr(slResult));
result = Result::ErrorInternal; // TODO review
}
return result;
}
Result AudioInputStreamOpenSLES::requestStart() {
LOGD("AudioInputStreamOpenSLES(): %s() called", __func__);
std::lock_guard<std::mutex> lock(mLock);
StreamState initialState = getState();
switch (initialState) {
case StreamState::Starting:
case StreamState::Started:
return Result::OK;
case StreamState::Closed:
return Result::ErrorClosed;
default:
break;
}
// We use a callback if the user requests one
// OR if we have an internal callback to fill the blocking IO buffer.
setDataCallbackEnabled(true);
setState(StreamState::Starting);
Result result = setRecordState_l(SL_RECORDSTATE_RECORDING);
if (result == Result::OK) {
setState(StreamState::Started);
// Enqueue the first buffer to start the streaming.
// This does not call the callback function.
enqueueCallbackBuffer(mSimpleBufferQueueInterface);
} else {
setState(initialState);
}
return result;
}
Result AudioInputStreamOpenSLES::requestPause() {
LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input "
"streams", __func__);
return Result::ErrorUnimplemented; // Matches AAudio behavior.
}
Result AudioInputStreamOpenSLES::requestFlush() {
LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input "
"streams", __func__);
return Result::ErrorUnimplemented; // Matches AAudio behavior.
}
Result AudioInputStreamOpenSLES::requestStop() {
LOGD("AudioInputStreamOpenSLES(): %s() called", __func__);
std::lock_guard<std::mutex> lock(mLock);
return requestStop_l();
}
// Call under mLock
Result AudioInputStreamOpenSLES::requestStop_l() {
StreamState initialState = getState();
switch (initialState) {
case StreamState::Stopping:
case StreamState::Stopped:
return Result::OK;
case StreamState::Closed:
return Result::ErrorClosed;
default:
break;
}
setState(StreamState::Stopping);
Result result = setRecordState_l(SL_RECORDSTATE_STOPPED);
if (result == Result::OK) {
mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
setState(StreamState::Stopped);
} else {
setState(initialState);
}
return result;
}
void AudioInputStreamOpenSLES::updateFramesWritten() {
if (usingFIFO()) {
AudioStreamBuffered::updateFramesWritten();
} else {
mFramesWritten = getFramesProcessedByServer();
}
}
Result AudioInputStreamOpenSLES::updateServiceFrameCounter() {
Result result = Result::OK;
// Avoid deadlock if another thread is trying to stop or close this stream
// and this is being called from a callback.
if (mLock.try_lock()) {
if (mRecordInterface == nullptr) {
mLock.unlock();
return Result::ErrorNull;
}
SLmillisecond msec = 0;
SLresult slResult = (*mRecordInterface)->GetPosition(mRecordInterface, &msec);
if (SL_RESULT_SUCCESS != slResult) {
LOGW("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult));
// set result based on SLresult
result = Result::ErrorInternal;
} else {
mPositionMillis.update32(msec);
}
mLock.unlock();
}
return result;
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AUDIO_INPUT_STREAM_OPENSL_ES_H_
#define AUDIO_INPUT_STREAM_OPENSL_ES_H_
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include "oboe/Oboe.h"
#include "AudioStreamOpenSLES.h"
namespace oboe {
/**
* INTERNAL USE ONLY
*/
class AudioInputStreamOpenSLES : public AudioStreamOpenSLES {
public:
AudioInputStreamOpenSLES();
explicit AudioInputStreamOpenSLES(const AudioStreamBuilder &builder);
virtual ~AudioInputStreamOpenSLES();
Result open() override;
Result close() override;
Result requestStart() override;
Result requestPause() override;
Result requestFlush() override;
Result requestStop() override;
protected:
Result requestStop_l();
Result updateServiceFrameCounter() override;
void updateFramesWritten() override;
private:
SLuint32 channelCountToChannelMask(int chanCount) const;
Result setRecordState_l(SLuint32 newState);
SLRecordItf mRecordInterface = nullptr;
};
} // namespace oboe
#endif //AUDIO_INPUT_STREAM_OPENSL_ES_H_

View File

@ -0,0 +1,450 @@
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <common/AudioClock.h>
#include "oboe/AudioStreamBuilder.h"
#include "AudioOutputStreamOpenSLES.h"
#include "AudioStreamOpenSLES.h"
#include "OpenSLESUtilities.h"
#include "OutputMixerOpenSLES.h"
using namespace oboe;
static SLuint32 OpenSLES_convertOutputUsage(Usage oboeUsage) {
SLuint32 openslStream = SL_ANDROID_STREAM_MEDIA;
switch(oboeUsage) {
case Usage::Media:
openslStream = SL_ANDROID_STREAM_MEDIA;
break;
case Usage::VoiceCommunication:
case Usage::VoiceCommunicationSignalling:
openslStream = SL_ANDROID_STREAM_VOICE;
break;
case Usage::Alarm:
openslStream = SL_ANDROID_STREAM_ALARM;
break;
case Usage::Notification:
case Usage::NotificationRingtone:
case Usage::NotificationEvent:
openslStream = SL_ANDROID_STREAM_NOTIFICATION;
break;
case Usage::AssistanceAccessibility:
case Usage::AssistanceNavigationGuidance:
case Usage::AssistanceSonification:
openslStream = SL_ANDROID_STREAM_SYSTEM;
break;
case Usage::Game:
openslStream = SL_ANDROID_STREAM_MEDIA;
break;
case Usage::Assistant:
default:
openslStream = SL_ANDROID_STREAM_SYSTEM;
break;
}
return openslStream;
}
AudioOutputStreamOpenSLES::AudioOutputStreamOpenSLES(const AudioStreamBuilder &builder)
: AudioStreamOpenSLES(builder) {
}
// These will wind up in <SLES/OpenSLES_Android.h>
constexpr int SL_ANDROID_SPEAKER_STEREO = (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
constexpr int SL_ANDROID_SPEAKER_QUAD = (SL_ANDROID_SPEAKER_STEREO
| SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT);
constexpr int SL_ANDROID_SPEAKER_5DOT1 = (SL_ANDROID_SPEAKER_QUAD
| SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY);
constexpr int SL_ANDROID_SPEAKER_7DOT1 = (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT
| SL_SPEAKER_SIDE_RIGHT);
SLuint32 AudioOutputStreamOpenSLES::channelCountToChannelMask(int channelCount) const {
SLuint32 channelMask = 0;
switch (channelCount) {
case 1:
channelMask = SL_SPEAKER_FRONT_CENTER;
break;
case 2:
channelMask = SL_ANDROID_SPEAKER_STEREO;
break;
case 4: // Quad
channelMask = SL_ANDROID_SPEAKER_QUAD;
break;
case 6: // 5.1
channelMask = SL_ANDROID_SPEAKER_5DOT1;
break;
case 8: // 7.1
channelMask = SL_ANDROID_SPEAKER_7DOT1;
break;
default:
channelMask = channelCountToChannelMaskDefault(channelCount);
break;
}
return channelMask;
}
Result AudioOutputStreamOpenSLES::open() {
logUnsupportedAttributes();
SLAndroidConfigurationItf configItf = nullptr;
if (getSdkVersion() < __ANDROID_API_L__ && mFormat == AudioFormat::Float){
// TODO: Allow floating point format on API <21 using float->int16 converter
return Result::ErrorInvalidFormat;
}
// If audio format is unspecified then choose a suitable default.
// API 21+: FLOAT
// API <21: INT16
if (mFormat == AudioFormat::Unspecified){
mFormat = (getSdkVersion() < __ANDROID_API_L__) ?
AudioFormat::I16 : AudioFormat::Float;
}
Result oboeResult = AudioStreamOpenSLES::open();
if (Result::OK != oboeResult) return oboeResult;
SLresult result = OutputMixerOpenSL::getInstance().open();
if (SL_RESULT_SUCCESS != result) {
AudioStreamOpenSLES::close();
return Result::ErrorInternal;
}
SLuint32 bitsPerSample = static_cast<SLuint32>(getBytesPerSample() * kBitsPerByte);
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType
static_cast<SLuint32>(kBufferQueueLength)}; // numBuffers
// Define the audio data format.
SLDataFormat_PCM format_pcm = {
SL_DATAFORMAT_PCM, // formatType
static_cast<SLuint32>(mChannelCount), // numChannels
static_cast<SLuint32>(mSampleRate * kMillisPerSecond), // milliSamplesPerSec
bitsPerSample, // bitsPerSample
bitsPerSample, // containerSize;
channelCountToChannelMask(mChannelCount), // channelMask
getDefaultByteOrder(),
};
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
/**
* API 21 (Lollipop) introduced support for floating-point data representation and an extended
* data format type: SLAndroidDataFormat_PCM_EX. If running on API 21+ use this newer format
* type, creating it from our original format.
*/
SLAndroidDataFormat_PCM_EX format_pcm_ex;
if (getSdkVersion() >= __ANDROID_API_L__) {
SLuint32 representation = OpenSLES_ConvertFormatToRepresentation(getFormat());
// Fill in the format structure.
format_pcm_ex = OpenSLES_createExtendedFormat(format_pcm, representation);
// Use in place of the previous format.
audioSrc.pFormat = &format_pcm_ex;
}
result = OutputMixerOpenSL::getInstance().createAudioPlayer(&mObjectInterface,
&audioSrc);
if (SL_RESULT_SUCCESS != result) {
LOGE("createAudioPlayer() result:%s", getSLErrStr(result));
goto error;
}
// Configure the stream.
result = (*mObjectInterface)->GetInterface(mObjectInterface,
SL_IID_ANDROIDCONFIGURATION,
(void *)&configItf);
if (SL_RESULT_SUCCESS != result) {
LOGW("%s() GetInterface(SL_IID_ANDROIDCONFIGURATION) failed with %s",
__func__, getSLErrStr(result));
} else {
result = configurePerformanceMode(configItf);
if (SL_RESULT_SUCCESS != result) {
goto error;
}
SLuint32 presetValue = OpenSLES_convertOutputUsage(getUsage());
result = (*configItf)->SetConfiguration(configItf,
SL_ANDROID_KEY_STREAM_TYPE,
&presetValue,
sizeof(presetValue));
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));
goto error;
}
result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_PLAY, &mPlayInterface);
if (SL_RESULT_SUCCESS != result) {
LOGE("GetInterface PLAY result:%s", getSLErrStr(result));
goto error;
}
result = AudioStreamOpenSLES::registerBufferQueueCallback();
if (SL_RESULT_SUCCESS != result) {
goto error;
}
result = updateStreamParameters(configItf);
if (SL_RESULT_SUCCESS != result) {
goto error;
}
oboeResult = configureBufferSizes(mSampleRate);
if (Result::OK != oboeResult) {
goto error;
}
allocateFifo();
setState(StreamState::Open);
return Result::OK;
error:
return Result::ErrorInternal; // TODO convert error from SLES to OBOE
}
Result AudioOutputStreamOpenSLES::onAfterDestroy() {
OutputMixerOpenSL::getInstance().close();
return Result::OK;
}
Result AudioOutputStreamOpenSLES::close() {
LOGD("AudioOutputStreamOpenSLES::%s()", __func__);
std::lock_guard<std::mutex> lock(mLock);
Result result = Result::OK;
if (getState() == StreamState::Closed){
result = Result::ErrorClosed;
} else {
requestPause_l();
// invalidate any interfaces
mPlayInterface = nullptr;
result = AudioStreamOpenSLES::close_l();
}
return result;
}
Result AudioOutputStreamOpenSLES::setPlayState_l(SLuint32 newState) {
LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
Result result = Result::OK;
if (mPlayInterface == nullptr){
LOGE("AudioOutputStreamOpenSLES::%s() mPlayInterface is null", __func__);
return Result::ErrorInvalidState;
}
SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
if (SL_RESULT_SUCCESS != slResult) {
LOGW("AudioOutputStreamOpenSLES(): %s() returned %s", __func__, getSLErrStr(slResult));
result = Result::ErrorInternal; // TODO convert slResult to Result::Error
}
return result;
}
Result AudioOutputStreamOpenSLES::requestStart() {
LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
mLock.lock();
StreamState initialState = getState();
switch (initialState) {
case StreamState::Starting:
case StreamState::Started:
mLock.unlock();
return Result::OK;
case StreamState::Closed:
mLock.unlock();
return Result::ErrorClosed;
default:
break;
}
// We use a callback if the user requests one
// OR if we have an internal callback to read the blocking IO buffer.
setDataCallbackEnabled(true);
setState(StreamState::Starting);
Result result = setPlayState_l(SL_PLAYSTATE_PLAYING);
if (result == Result::OK) {
setState(StreamState::Started);
mLock.unlock();
if (getBufferDepth(mSimpleBufferQueueInterface) == 0) {
// Enqueue the first buffer if needed to start the streaming.
// This might call requestStop() so try to avoid a recursive lock.
processBufferCallback(mSimpleBufferQueueInterface);
}
} else {
setState(initialState);
mLock.unlock();
}
return result;
}
Result AudioOutputStreamOpenSLES::requestPause() {
LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
std::lock_guard<std::mutex> lock(mLock);
return requestPause_l();
}
// Call under mLock
Result AudioOutputStreamOpenSLES::requestPause_l() {
StreamState initialState = getState();
switch (initialState) {
case StreamState::Pausing:
case StreamState::Paused:
return Result::OK;
case StreamState::Closed:
return Result::ErrorClosed;
default:
break;
}
setState(StreamState::Pausing);
Result result = setPlayState_l(SL_PLAYSTATE_PAUSED);
if (result == Result::OK) {
// Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused.
int64_t framesWritten = getFramesWritten();
if (framesWritten >= 0) {
setFramesRead(framesWritten);
}
setState(StreamState::Paused);
} else {
setState(initialState);
}
return result;
}
/**
* Flush/clear the queue buffers
*/
Result AudioOutputStreamOpenSLES::requestFlush() {
std::lock_guard<std::mutex> lock(mLock);
return requestFlush_l();
}
Result AudioOutputStreamOpenSLES::requestFlush_l() {
LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
if (getState() == StreamState::Closed) {
return Result::ErrorClosed;
}
Result result = Result::OK;
if (mPlayInterface == nullptr || mSimpleBufferQueueInterface == nullptr) {
result = Result::ErrorInvalidState;
} else {
SLresult slResult = (*mSimpleBufferQueueInterface)->Clear(mSimpleBufferQueueInterface);
if (slResult != SL_RESULT_SUCCESS){
LOGW("Failed to clear buffer queue. OpenSLES error: %d", result);
result = Result::ErrorInternal;
}
}
return result;
}
Result AudioOutputStreamOpenSLES::requestStop() {
LOGD("AudioOutputStreamOpenSLES(): %s() called", __func__);
std::lock_guard<std::mutex> lock(mLock);
StreamState initialState = getState();
switch (initialState) {
case StreamState::Stopping:
case StreamState::Stopped:
return Result::OK;
case StreamState::Closed:
return Result::ErrorClosed;
default:
break;
}
setState(StreamState::Stopping);
Result result = setPlayState_l(SL_PLAYSTATE_STOPPED);
if (result == Result::OK) {
// Also clear the buffer queue so the old data won't be played if the stream is restarted.
// Call the _l function that expects to already be under a lock.
if (requestFlush_l() != Result::OK) {
LOGW("Failed to flush the stream. Error %s", convertToText(flush()));
}
mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
int64_t framesWritten = getFramesWritten();
if (framesWritten >= 0) {
setFramesRead(framesWritten);
}
setState(StreamState::Stopped);
} else {
setState(initialState);
}
return result;
}
void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) {
int64_t millisWritten = framesRead * kMillisPerSecond / getSampleRate();
mPositionMillis.set(millisWritten);
}
void AudioOutputStreamOpenSLES::updateFramesRead() {
if (usingFIFO()) {
AudioStreamBuffered::updateFramesRead();
} else {
mFramesRead = getFramesProcessedByServer();
}
}
Result AudioOutputStreamOpenSLES::updateServiceFrameCounter() {
Result result = Result::OK;
// Avoid deadlock if another thread is trying to stop or close this stream
// and this is being called from a callback.
if (mLock.try_lock()) {
if (mPlayInterface == nullptr) {
mLock.unlock();
return Result::ErrorNull;
}
SLmillisecond msec = 0;
SLresult slResult = (*mPlayInterface)->GetPosition(mPlayInterface, &msec);
if (SL_RESULT_SUCCESS != slResult) {
LOGW("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult));
// set result based on SLresult
result = Result::ErrorInternal;
} else {
mPositionMillis.update32(msec);
}
mLock.unlock();
}
return result;
}

Some files were not shown because too many files have changed in this diff Show More