Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
Signed by untrusted user: crueter
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -0,0 +1,304 @@
/*
* 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 <set>
#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;
// The output device type collection must be updated if there is any new added output device type
const static std::set<DeviceType> ALL_OUTPUT_DEVICE_TYPES = {
DeviceType::BuiltinEarpiece,
DeviceType::BuiltinSpeaker,
DeviceType::WiredHeadset,
DeviceType::WiredHeadphones,
DeviceType::LineAnalog,
DeviceType::LineDigital,
DeviceType::BluetoothSco,
DeviceType::BluetoothA2dp,
DeviceType::Hdmi,
DeviceType::HdmiArc,
DeviceType::HdmiEarc,
DeviceType::UsbDevice,
DeviceType::UsbHeadset,
DeviceType::UsbAccessory,
DeviceType::Dock,
DeviceType::DockAnalog,
DeviceType::FM,
DeviceType::Telephony,
DeviceType::AuxLine,
DeviceType::IP,
DeviceType::Bus,
DeviceType::HearingAid,
DeviceType::BuiltinSpeakerSafe,
DeviceType::RemoteSubmix,
DeviceType::BleHeadset,
DeviceType::BleSpeaker,
DeviceType::BleBroadcast,
};
// The input device type collection must be updated if there is any new added input device type
const static std::set<DeviceType> ALL_INPUT_DEVICE_TYPES = {
DeviceType::BuiltinMic,
DeviceType::BluetoothSco,
DeviceType::WiredHeadset,
DeviceType::Hdmi,
DeviceType::Telephony,
DeviceType::Dock,
DeviceType::DockAnalog,
DeviceType::UsbAccessory,
DeviceType::UsbDevice,
DeviceType::UsbHeadset,
DeviceType::FMTuner,
DeviceType::TVTuner,
DeviceType::LineAnalog,
DeviceType::LineDigital,
DeviceType::BluetoothA2dp,
DeviceType::IP,
DeviceType::Bus,
DeviceType::RemoteSubmix,
DeviceType::BleHeadset,
DeviceType::HdmiArc,
DeviceType::HdmiEarc,
};
/**
* Call some AAudio test routines that are not part of the normal API.
*/
class AAudioExtensions {
private: // Because it is a singleton. Call getInstance() instead.
AAudioExtensions() {
mLibLoader = AAudioLoader::getInstance();
if (!initMMapPolicy()) {
int32_t policy = getIntegerProperty("aaudio.mmap_policy", 0);
mMMapSupported = isPolicyEnabled(policy);
policy = getIntegerProperty("aaudio.mmap_exclusive_policy", 0);
mMMapExclusiveSupported = isPolicyEnabled(policy);
}
}
public:
static bool isPolicyEnabled(int32_t policy) {
const MMapPolicy mmapPolicy = static_cast<MMapPolicy>(policy);
return (mmapPolicy == MMapPolicy::Auto || mmapPolicy == MMapPolicy::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 (mLibLoader != nullptr && mLibLoader->stream_isMMapUsed != nullptr) {
return mLibLoader->stream_isMMapUsed(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) {
// The API for setting mmap policy is public after API level 36.
if (mLibLoader != nullptr && mLibLoader->aaudio_setMMapPolicy != nullptr) {
return mLibLoader->aaudio_setMMapPolicy(
static_cast<aaudio_policy_t>(enabled ? MMapPolicy::Auto : MMapPolicy::Never));
}
// When there is no public API, fallback to loading the symbol from hidden API.
if (loadSymbols()) return AAUDIO_ERROR_UNAVAILABLE;
if (mAAudio_setMMapPolicy == nullptr) return false;
return mAAudio_setMMapPolicy(
static_cast<int32_t>(enabled ? MMapPolicy::Auto : MMapPolicy::Never));
}
bool isMMapEnabled() {
// The API for getting mmap policy is public after API level 36.
// Use it when it is available.
if (mLibLoader != nullptr && mLibLoader->aaudio_getMMapPolicy != nullptr) {
MMapPolicy policy = static_cast<MMapPolicy>(mLibLoader->aaudio_getMMapPolicy());
return policy == MMapPolicy::Unspecified
? mMMapSupported : isPolicyEnabled(static_cast<int32_t>(policy));
}
// When there is no public API, fallback to loading the symbol from hidden API.
if (loadSymbols()) return false;
if (mAAudio_getMMapPolicy == nullptr) return false;
int32_t policy = mAAudio_getMMapPolicy();
return (policy == Unspecified) ? mMMapSupported : isPolicyEnabled(policy);
}
bool isMMapSupported() {
return mMMapSupported;
}
bool isMMapExclusiveSupported() {
return mMMapExclusiveSupported;
}
MMapPolicy getMMapPolicy(DeviceType deviceType, Direction direction) {
if (mLibLoader == nullptr ||
mLibLoader->aaudio_getPlatformMMapPolicy == nullptr) {
return MMapPolicy::Unspecified;
}
return static_cast<MMapPolicy>(mLibLoader->aaudio_getPlatformMMapPolicy(
static_cast<AAudio_DeviceType>(deviceType),
static_cast<aaudio_direction_t>(direction)));
}
MMapPolicy getMMapExclusivePolicy(DeviceType deviceType, Direction direction) {
if (mLibLoader == nullptr ||
mLibLoader->aaudio_getPlatformMMapExclusivePolicy == nullptr) {
return MMapPolicy::Unspecified;
}
return static_cast<MMapPolicy>(mLibLoader->aaudio_getPlatformMMapExclusivePolicy(
static_cast<AAudio_DeviceType>(deviceType),
static_cast<aaudio_direction_t>(direction)));
}
private:
bool initMMapPolicy() {
if (mLibLoader == nullptr || mLibLoader->open() != 0) {
return false;
}
if (mLibLoader->aaudio_getPlatformMMapPolicy == nullptr ||
mLibLoader->aaudio_getPlatformMMapExclusivePolicy == nullptr) {
return false;
}
mMMapSupported =
std::any_of(ALL_INPUT_DEVICE_TYPES.begin(), ALL_INPUT_DEVICE_TYPES.end(),
[this](DeviceType deviceType) {
return isPolicyEnabled(static_cast<int32_t>(
getMMapPolicy(deviceType, Direction::Input)));
}) ||
std::any_of(ALL_OUTPUT_DEVICE_TYPES.begin(), ALL_OUTPUT_DEVICE_TYPES.end(),
[this](DeviceType deviceType) {
return isPolicyEnabled(static_cast<int32_t>(
getMMapPolicy(deviceType, Direction::Output)));
});
mMMapExclusiveSupported =
std::any_of(ALL_INPUT_DEVICE_TYPES.begin(), ALL_INPUT_DEVICE_TYPES.end(),
[this](DeviceType deviceType) {
return isPolicyEnabled(static_cast<int32_t>(
getMMapExclusivePolicy(deviceType, Direction::Input)));
}) ||
std::any_of(ALL_OUTPUT_DEVICE_TYPES.begin(), ALL_OUTPUT_DEVICE_TYPES.end(),
[this](DeviceType deviceType) {
return isPolicyEnabled(static_cast<int32_t>(
getMMapExclusivePolicy(deviceType, Direction::Output)));
});
return true;
}
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;
}
if (mLibLoader == nullptr || mLibLoader->open() != 0) {
LOGD("%s() could not open " LIB_AAUDIO_NAME, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
void *libHandle = mLibLoader->getLibHandle();
if (libHandle == nullptr) {
LOGE("%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;
AAudioLoader *mLibLoader;
};
} // namespace oboe
#endif //OBOE_AAUDIO_EXTENSIONS_H

View file

@ -0,0 +1,606 @@
/*
* 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_Q__){
builder_setAllowedCapturePolicy = load_V_PBI("AAudioStreamBuilder_setAllowedCapturePolicy");
}
if (getSdkVersion() >= __ANDROID_API_R__){
builder_setPrivacySensitive = load_V_PBO("AAudioStreamBuilder_setPrivacySensitive");
}
if (getSdkVersion() >= __ANDROID_API_S__){
builder_setPackageName = load_V_PBCPH("AAudioStreamBuilder_setPackageName");
builder_setAttributionTag = load_V_PBCPH("AAudioStreamBuilder_setAttributionTag");
}
if (getSdkVersion() >= __ANDROID_API_S_V2__) {
builder_setChannelMask = load_V_PBU("AAudioStreamBuilder_setChannelMask");
builder_setIsContentSpatialized = load_V_PBO("AAudioStreamBuilder_setIsContentSpatialized");
builder_setSpatializationBehavior = load_V_PBI("AAudioStreamBuilder_setSpatializationBehavior");
}
if (getSdkVersion() >= __ANDROID_API_B__) {
builder_setPresentationEndCallback = load_V_PBPRPV("AAudioStreamBuilder_setPresentationEndCallback");
}
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");
}
if (getSdkVersion() >= __ANDROID_API_R__) {
stream_release = load_I_PS("AAudioStream_release");
}
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");
}
if (getSdkVersion() >= __ANDROID_API_Q__){
stream_getAllowedCapturePolicy = load_I_PS("AAudioStream_getAllowedCapturePolicy");
}
if (getSdkVersion() >= __ANDROID_API_R__){
stream_isPrivacySensitive = load_O_PS("AAudioStream_isPrivacySensitive");
}
if (getSdkVersion() >= __ANDROID_API_S_V2__) {
stream_getChannelMask = load_U_PS("AAudioStream_getChannelMask");
stream_isContentSpatialized = load_O_PS("AAudioStream_isContentSpatialized");
stream_getSpatializationBehavior = load_I_PS("AAudioStream_getSpatializationBehavior");
}
if (getSdkVersion() >= __ANDROID_API_U__) {
stream_getHardwareChannelCount = load_I_PS("AAudioStream_getHardwareChannelCount");
stream_getHardwareSampleRate = load_I_PS("AAudioStream_getHardwareSampleRate");
stream_getHardwareFormat = load_F_PS("AAudioStream_getHardwareFormat");
}
// TODO: Remove pre-release check after Android B release
if (getSdkVersion() >= __ANDROID_API_B__ || isAtLeastPreReleaseCodename("Baklava")) {
aaudio_getPlatformMMapPolicy = load_I_II("AAudio_getPlatformMMapPolicy");
aaudio_getPlatformMMapExclusivePolicy = load_I_II("AAudio_getPlatformMMapExclusivePolicy");
aaudio_setMMapPolicy = load_I_I("AAudio_setMMapPolicy");
aaudio_getMMapPolicy = load_I("AAudio_getMMapPolicy");
stream_isMMapUsed = load_O_PS("AAudioStream_isMMapUsed");
stream_setOffloadDelayPadding = load_I_PSII("AAudioStream_setOffloadDelayPadding");
stream_getOffloadDelay = load_I_PS("AAudioStream_getOffloadDelay");
stream_getOffloadPadding = load_I_PS("AAudioStream_getOffloadPadding");
stream_setOffloadEndOfStream = load_I_PS("AAudioStream_setOffloadEndOfStream");
stream_getDeviceIds = load_I_PSPIPI("AAudioStream_getDeviceIds");
}
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_O_PS AAudioLoader::load_O_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_O_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);
}
AAudioLoader::signature_V_PBU AAudioLoader::load_V_PBU(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBU>(proc);
}
AAudioLoader::signature_U_PS AAudioLoader::load_U_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_U_PS>(proc);
}
AAudioLoader::signature_V_PBO AAudioLoader::load_V_PBO(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBO>(proc);
}
AAudioLoader::signature_I_II AAudioLoader::load_I_II(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_II>(proc);
}
AAudioLoader::signature_I_I AAudioLoader::load_I_I(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_I>(proc);
}
AAudioLoader::signature_I AAudioLoader::load_I(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I>(proc);
}
AAudioLoader::signature_V_PBPRPV AAudioLoader::load_V_PBPRPV(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBPRPV>(proc);
}
AAudioLoader::signature_I_PSII AAudioLoader::load_I_PSII(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSII>(proc);
}
AAudioLoader::signature_I_PSPIPI AAudioLoader::load_I_PSPIPI(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSPIPI>(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")
// Ensure that all AAudio primitive data types are uint32_t
#define ASSERT_UINT32(type) static_assert(std::is_same<uint32_t, type>::value, \
#type" must be uint32_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
// aaudio_allowed_capture_policy_t was added in NDK 20,
// which is the first version to support Android Q (API 29).
#if __NDK_MAJOR__ >= 20
ASSERT_INT32(aaudio_allowed_capture_policy_t);
static_assert((int32_t)AllowedCapturePolicy::Unspecified == AAUDIO_UNSPECIFIED, ERRMSG);
static_assert((int32_t)AllowedCapturePolicy::All == AAUDIO_ALLOW_CAPTURE_BY_ALL, ERRMSG);
static_assert((int32_t)AllowedCapturePolicy::System == AAUDIO_ALLOW_CAPTURE_BY_SYSTEM, ERRMSG);
static_assert((int32_t)AllowedCapturePolicy::None == AAUDIO_ALLOW_CAPTURE_BY_NONE, ERRMSG);
#endif // __NDK_MAJOR__ >= 20
// The aaudio channel masks and spatialization behavior were added in NDK 24,
// which is the first version to support Android SC_V2 (API 32).
#if __NDK_MAJOR__ >= 24
ASSERT_UINT32(aaudio_channel_mask_t);
static_assert((uint32_t)ChannelMask::FrontLeft == AAUDIO_CHANNEL_FRONT_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::FrontRight == AAUDIO_CHANNEL_FRONT_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::FrontCenter == AAUDIO_CHANNEL_FRONT_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::LowFrequency == AAUDIO_CHANNEL_LOW_FREQUENCY, ERRMSG);
static_assert((uint32_t)ChannelMask::BackLeft == AAUDIO_CHANNEL_BACK_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::BackRight == AAUDIO_CHANNEL_BACK_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::FrontLeftOfCenter == AAUDIO_CHANNEL_FRONT_LEFT_OF_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::FrontRightOfCenter == AAUDIO_CHANNEL_FRONT_RIGHT_OF_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::BackCenter == AAUDIO_CHANNEL_BACK_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::SideLeft == AAUDIO_CHANNEL_SIDE_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::SideRight == AAUDIO_CHANNEL_SIDE_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::TopCenter == AAUDIO_CHANNEL_TOP_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::TopFrontLeft == AAUDIO_CHANNEL_TOP_FRONT_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::TopFrontCenter == AAUDIO_CHANNEL_TOP_FRONT_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::TopFrontRight == AAUDIO_CHANNEL_TOP_FRONT_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::TopBackLeft == AAUDIO_CHANNEL_TOP_BACK_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::TopBackCenter == AAUDIO_CHANNEL_TOP_BACK_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::TopBackRight == AAUDIO_CHANNEL_TOP_BACK_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::TopSideLeft == AAUDIO_CHANNEL_TOP_SIDE_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::TopSideRight == AAUDIO_CHANNEL_TOP_SIDE_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::BottomFrontLeft == AAUDIO_CHANNEL_BOTTOM_FRONT_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::BottomFrontCenter == AAUDIO_CHANNEL_BOTTOM_FRONT_CENTER, ERRMSG);
static_assert((uint32_t)ChannelMask::BottomFrontRight == AAUDIO_CHANNEL_BOTTOM_FRONT_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::LowFrequency2 == AAUDIO_CHANNEL_LOW_FREQUENCY_2, ERRMSG);
static_assert((uint32_t)ChannelMask::FrontWideLeft == AAUDIO_CHANNEL_FRONT_WIDE_LEFT, ERRMSG);
static_assert((uint32_t)ChannelMask::FrontWideRight == AAUDIO_CHANNEL_FRONT_WIDE_RIGHT, ERRMSG);
static_assert((uint32_t)ChannelMask::Mono == AAUDIO_CHANNEL_MONO, ERRMSG);
static_assert((uint32_t)ChannelMask::Stereo == AAUDIO_CHANNEL_STEREO, ERRMSG);
static_assert((uint32_t)ChannelMask::CM2Point1 == AAUDIO_CHANNEL_2POINT1, ERRMSG);
static_assert((uint32_t)ChannelMask::Tri == AAUDIO_CHANNEL_TRI, ERRMSG);
static_assert((uint32_t)ChannelMask::TriBack == AAUDIO_CHANNEL_TRI_BACK, ERRMSG);
static_assert((uint32_t)ChannelMask::CM3Point1 == AAUDIO_CHANNEL_3POINT1, ERRMSG);
static_assert((uint32_t)ChannelMask::CM2Point0Point2 == AAUDIO_CHANNEL_2POINT0POINT2, ERRMSG);
static_assert((uint32_t)ChannelMask::CM2Point1Point2 == AAUDIO_CHANNEL_2POINT1POINT2, ERRMSG);
static_assert((uint32_t)ChannelMask::CM3Point0Point2 == AAUDIO_CHANNEL_3POINT0POINT2, ERRMSG);
static_assert((uint32_t)ChannelMask::CM3Point1Point2 == AAUDIO_CHANNEL_3POINT1POINT2, ERRMSG);
static_assert((uint32_t)ChannelMask::Quad == AAUDIO_CHANNEL_QUAD, ERRMSG);
static_assert((uint32_t)ChannelMask::QuadSide == AAUDIO_CHANNEL_QUAD_SIDE, ERRMSG);
static_assert((uint32_t)ChannelMask::Surround == AAUDIO_CHANNEL_SURROUND, ERRMSG);
static_assert((uint32_t)ChannelMask::Penta == AAUDIO_CHANNEL_PENTA, ERRMSG);
static_assert((uint32_t)ChannelMask::CM5Point1 == AAUDIO_CHANNEL_5POINT1, ERRMSG);
static_assert((uint32_t)ChannelMask::CM5Point1Side == AAUDIO_CHANNEL_5POINT1_SIDE, ERRMSG);
static_assert((uint32_t)ChannelMask::CM6Point1 == AAUDIO_CHANNEL_6POINT1, ERRMSG);
static_assert((uint32_t)ChannelMask::CM7Point1 == AAUDIO_CHANNEL_7POINT1, ERRMSG);
static_assert((uint32_t)ChannelMask::CM5Point1Point2 == AAUDIO_CHANNEL_5POINT1POINT2, ERRMSG);
static_assert((uint32_t)ChannelMask::CM5Point1Point4 == AAUDIO_CHANNEL_5POINT1POINT4, ERRMSG);
static_assert((uint32_t)ChannelMask::CM7Point1Point2 == AAUDIO_CHANNEL_7POINT1POINT2, ERRMSG);
static_assert((uint32_t)ChannelMask::CM7Point1Point4 == AAUDIO_CHANNEL_7POINT1POINT4, ERRMSG);
static_assert((uint32_t)ChannelMask::CM9Point1Point4 == AAUDIO_CHANNEL_9POINT1POINT4, ERRMSG);
static_assert((uint32_t)ChannelMask::CM9Point1Point6 == AAUDIO_CHANNEL_9POINT1POINT6, ERRMSG);
static_assert((uint32_t)ChannelMask::FrontBack == AAUDIO_CHANNEL_FRONT_BACK, ERRMSG);
ASSERT_INT32(aaudio_spatialization_behavior_t);
static_assert((int32_t)SpatializationBehavior::Unspecified == AAUDIO_UNSPECIFIED, ERRMSG);
static_assert((int32_t)SpatializationBehavior::Auto == AAUDIO_SPATIALIZATION_BEHAVIOR_AUTO, ERRMSG);
static_assert((int32_t)SpatializationBehavior::Never == AAUDIO_SPATIALIZATION_BEHAVIOR_NEVER, ERRMSG);
#endif
// The aaudio device type and aaudio policy were added in NDK 28,
// which is the first version to support Android W (API 36).
#if __NDK_MAJOR__ >= 29
ASSERT_INT32(AAudio_DeviceType);
static_assert((int32_t)DeviceType::BuiltinEarpiece == AAUDIO_DEVICE_BUILTIN_EARPIECE, ERRMSG);
static_assert((int32_t)DeviceType::BuiltinSpeaker == AAUDIO_DEVICE_BUILTIN_SPEAKER, ERRMSG);
static_assert((int32_t)DeviceType::WiredHeadset == AAUDIO_DEVICE_WIRED_HEADSET, ERRMSG);
static_assert((int32_t)DeviceType::WiredHeadphones == AAUDIO_DEVICE_WIRED_HEADPHONES, ERRMSG);
static_assert((int32_t)DeviceType::LineAnalog == AAUDIO_DEVICE_LINE_ANALOG, ERRMSG);
static_assert((int32_t)DeviceType::LineDigital == AAUDIO_DEVICE_LINE_DIGITAL, ERRMSG);
static_assert((int32_t)DeviceType::BluetoothSco == AAUDIO_DEVICE_BLUETOOTH_SCO, ERRMSG);
static_assert((int32_t)DeviceType::BluetoothA2dp == AAUDIO_DEVICE_BLUETOOTH_A2DP, ERRMSG);
static_assert((int32_t)DeviceType::Hdmi == AAUDIO_DEVICE_HDMI, ERRMSG);
static_assert((int32_t)DeviceType::HdmiArc == AAUDIO_DEVICE_HDMI_ARC, ERRMSG);
static_assert((int32_t)DeviceType::UsbDevice == AAUDIO_DEVICE_USB_DEVICE, ERRMSG);
static_assert((int32_t)DeviceType::UsbAccessory == AAUDIO_DEVICE_USB_ACCESSORY, ERRMSG);
static_assert((int32_t)DeviceType::Dock == AAUDIO_DEVICE_DOCK, ERRMSG);
static_assert((int32_t)DeviceType::FM == AAUDIO_DEVICE_FM, ERRMSG);
static_assert((int32_t)DeviceType::BuiltinMic == AAUDIO_DEVICE_BUILTIN_MIC, ERRMSG);
static_assert((int32_t)DeviceType::FMTuner == AAUDIO_DEVICE_FM_TUNER, ERRMSG);
static_assert((int32_t)DeviceType::TVTuner == AAUDIO_DEVICE_TV_TUNER, ERRMSG);
static_assert((int32_t)DeviceType::Telephony == AAUDIO_DEVICE_TELEPHONY, ERRMSG);
static_assert((int32_t)DeviceType::AuxLine == AAUDIO_DEVICE_AUX_LINE, ERRMSG);
static_assert((int32_t)DeviceType::IP == AAUDIO_DEVICE_IP, ERRMSG);
static_assert((int32_t)DeviceType::Bus == AAUDIO_DEVICE_BUS, ERRMSG);
static_assert((int32_t)DeviceType::UsbHeadset == AAUDIO_DEVICE_USB_HEADSET, ERRMSG);
static_assert((int32_t)DeviceType::HearingAid == AAUDIO_DEVICE_HEARING_AID, ERRMSG);
static_assert((int32_t)DeviceType::BuiltinSpeakerSafe == AAUDIO_DEVICE_BUILTIN_SPEAKER_SAFE, ERRMSG);
static_assert((int32_t)DeviceType::RemoteSubmix == AAUDIO_DEVICE_REMOTE_SUBMIX, ERRMSG);
static_assert((int32_t)DeviceType::BleHeadset == AAUDIO_DEVICE_BLE_HEADSET, ERRMSG);
static_assert((int32_t)DeviceType::BleSpeaker == AAUDIO_DEVICE_BLE_SPEAKER, ERRMSG);
static_assert((int32_t)DeviceType::HdmiEarc == AAUDIO_DEVICE_HDMI_EARC, ERRMSG);
static_assert((int32_t)DeviceType::BleBroadcast == AAUDIO_DEVICE_BLE_BROADCAST, ERRMSG);
static_assert((int32_t)DeviceType::DockAnalog == AAUDIO_DEVICE_DOCK_ANALOG, ERRMSG);
ASSERT_INT32(aaudio_policy_t);
static_assert((int32_t)MMapPolicy::Unspecified == AAUDIO_UNSPECIFIED, ERRMSG);
static_assert((int32_t)MMapPolicy::Never == AAUDIO_POLICY_NEVER, ERRMSG);
static_assert((int32_t)MMapPolicy::Auto == AAUDIO_POLICY_AUTO, ERRMSG);
static_assert((int32_t)MMapPolicy::Always == AAUDIO_POLICY_ALWAYS, ERRMSG);
#endif // __NDK_MAJOR__ >= 28
#endif // AAUDIO_AAUDIO_H
} // namespace oboe

353
externals/oboe/src/aaudio/AAudioLoader.h vendored Normal file
View file

@ -0,0 +1,353 @@
/*
* 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
#ifdef __NDK_MAJOR__
#define OBOE_USING_NDK 1
#else
#define __NDK_MAJOR__ 0
#define OBOE_USING_NDK 0
#endif
#if __NDK_MAJOR__ < 24
// Defined in SC_V2
typedef uint32_t aaudio_channel_mask_t;
typedef int32_t aaudio_spatialization_behavior_t;
#endif
#if OBOE_USING_NDK && __NDK_MAJOR__ < 29
// Defined in Android B
typedef void (*AAudioStream_presentationEndCallback)(
AAudioStream* stream,
void* userData);
#endif
#ifndef __ANDROID_API_Q__
#define __ANDROID_API_Q__ 29
#endif
#ifndef __ANDROID_API_R__
#define __ANDROID_API_R__ 30
#endif
#ifndef __ANDROID_API_S__
#define __ANDROID_API_S__ 31
#endif
#ifndef __ANDROID_API_S_V2__
#define __ANDROID_API_S_V2__ 32
#endif
#ifndef __ANDROID_API_U__
#define __ANDROID_API_U__ 34
#endif
#ifndef __ANDROID_API_B__
#define __ANDROID_API_B__ 36
#endif
#if OBOE_USING_NDK && __NDK_MAJOR__ < 29
// These were defined in Android B
typedef int32_t AAudio_DeviceType;
typedef int32_t aaudio_policy_t;
#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
// U = uint32_t
// O = bOol
// R = pResentation end callback
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);
// AAudioStreamBuilder_setChannelMask()
typedef void (*signature_V_PBU)(AAudioStreamBuilder *, uint32_t);
typedef void (*signature_V_PBCPH)(AAudioStreamBuilder *, const char *);
// AAudioStreamBuilder_setPrivacySensitive
typedef void (*signature_V_PBO)(AAudioStreamBuilder *, bool);
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 void (*signature_V_PBPRPV)(AAudioStreamBuilder *,
AAudioStream_presentationEndCallback,
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_O_PS)(AAudioStream *);
typedef uint32_t (*signature_U_PS)(AAudioStream *);
typedef int32_t (*signature_I_II)(int32_t, int32_t);
typedef int32_t (*signature_I_I)(int32_t);
typedef int32_t (*signature_I)();
typedef int32_t (*signature_I_PSII)(AAudioStream *, int32_t, int32_t);
// AAudioStream_getDeviceIds()
typedef int32_t (*signature_I_PSPIPI)(AAudioStream *, int32_t *, int32_t *);
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_PBU builder_setChannelMask = 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_PBO builder_setPrivacySensitive = nullptr;
signature_V_PBI builder_setAllowedCapturePolicy = nullptr;
signature_V_PBCPH builder_setPackageName = nullptr;
signature_V_PBCPH builder_setAttributionTag = nullptr;
signature_V_PBO builder_setIsContentSpatialized = nullptr;
signature_V_PBI builder_setSpatializationBehavior = nullptr;
signature_V_PBPDPV builder_setDataCallback = nullptr;
signature_V_PBPEPV builder_setErrorCallback = nullptr;
signature_V_PBPRPV builder_setPresentationEndCallback = 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_PSPIPI stream_getDeviceIds = nullptr;
signature_I_PS stream_release = 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;
signature_O_PS stream_isPrivacySensitive = nullptr;
signature_I_PS stream_getAllowedCapturePolicy = nullptr;
signature_U_PS stream_getChannelMask = nullptr;
signature_O_PS stream_isContentSpatialized = nullptr;
signature_I_PS stream_getSpatializationBehavior = nullptr;
signature_I_PS stream_getHardwareChannelCount = nullptr;
signature_I_PS stream_getHardwareSampleRate = nullptr;
signature_F_PS stream_getHardwareFormat = nullptr;
signature_I_II aaudio_getPlatformMMapPolicy = nullptr;
signature_I_II aaudio_getPlatformMMapExclusivePolicy = nullptr;
signature_I_I aaudio_setMMapPolicy = nullptr;
signature_I aaudio_getMMapPolicy = nullptr;
signature_O_PS stream_isMMapUsed = nullptr;
signature_I_PSII stream_setOffloadDelayPadding = nullptr;
signature_I_PS stream_getOffloadDelay = nullptr;
signature_I_PS stream_getOffloadPadding = nullptr;
signature_I_PS stream_setOffloadEndOfStream = 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_O_PS load_O_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);
signature_V_PBU load_V_PBU(const char *name);
signature_U_PS load_U_PS(const char *name);
signature_V_PBO load_V_PBO(const char *name);
signature_I_II load_I_II(const char *name);
signature_I_I load_I_I(const char *name);
signature_I load_I(const char *name);
signature_V_PBPRPV load_V_PBPRPV(const char *name);
signature_I_PSII load_I_PSII(const char *name);
signature_I_PSPIPI load_I_PSPIPI(const char *name);
void *mLibHandle = nullptr;
};
} // namespace oboe
#endif //OBOE_AAUDIO_LOADER_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,171 @@
/*
* 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 <common/AdpfWrapper.h>
#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 release() 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();
void closePerformanceHint() override {
mAdpfWrapper.close();
mAdpfOpenAttempted = false;
}
oboe::Result reportWorkload(int32_t appWorkload) override {
if (!isPerformanceHintEnabled()) {
return oboe::Result::ErrorInvalidState;
}
mAdpfWrapper.reportWorkload(appWorkload);
return oboe::Result::OK;
}
Result setOffloadDelayPadding(int32_t delayInFrames, int32_t paddingInFrames) override;
ResultWithValue<int32_t> getOffloadDelay() override;
ResultWithValue<int32_t> getOffloadPadding() override;
Result setOffloadEndOfStream() override;
protected:
static void internalErrorCallback(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
static void internalPresentationEndCallback(
AAudioStream *stream,
void *userData);
void *getUnderlyingStream() const override {
return mAAudioStream.load();
}
void updateFramesRead() override;
void updateFramesWritten() override;
void logUnsupportedAttributes();
void beginPerformanceHintInCallback() override;
void endPerformanceHintInCallback(int32_t numFrames) override;
// set by callback (or app when idle)
std::atomic<bool> mAdpfOpenAttempted{false};
AdpfWrapper mAdpfWrapper;
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();
void updateDeviceIds();
private:
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,150 @@
/*
* Copyright 2021 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 <stdint.h>
#include <sys/types.h>
#include "oboe/AudioClock.h"
#include "AdpfWrapper.h"
#include "OboeDebug.h"
#include "Trace.h"
using namespace oboe;
typedef APerformanceHintManager* (*APH_getManager)();
typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
size_t, int64_t);
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_closeSession)(APerformanceHintSession* session);
static bool gAPerformanceHintBindingInitialized = false;
static APH_getManager gAPH_getManagerFn = nullptr;
static APH_createSession gAPH_createSessionFn = nullptr;
static APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
static APH_closeSession gAPH_closeSessionFn = nullptr;
static int loadAphFunctions() {
if (gAPerformanceHintBindingInitialized) return true;
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
if (handle_ == nullptr) {
return -1000;
}
gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
if (gAPH_getManagerFn == nullptr) {
return -1001;
}
gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
if (gAPH_getManagerFn == nullptr) {
return -1002;
}
gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
handle_, "APerformanceHint_reportActualWorkDuration");
if (gAPH_getManagerFn == nullptr) {
return -1003;
}
gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
if (gAPH_getManagerFn == nullptr) {
return -1004;
}
gAPerformanceHintBindingInitialized = true;
Trace::initialize();
return 0;
}
bool AdpfWrapper::sUseAlternativeHack = false; // TODO remove hack
int AdpfWrapper::open(pid_t threadId,
int64_t targetDurationNanos) {
std::lock_guard<std::mutex> lock(mLock);
int result = loadAphFunctions();
if (result < 0) return result;
// This is a singleton.
APerformanceHintManager* manager = gAPH_getManagerFn();
int32_t thread32 = threadId;
if (sUseAlternativeHack) {
// TODO Remove this hack when we finish experimenting with alternative algorithms.
// The A5 is an arbitrary signal to a hacked version of ADPF to try an alternative
// algorithm that is not based on PID.
targetDurationNanos = (targetDurationNanos & ~0xFF) | 0xA5;
}
mHintSession = gAPH_createSessionFn(manager, &thread32, 1 /* size */, targetDurationNanos);
if (mHintSession == nullptr) {
return -1;
}
return 0;
}
void AdpfWrapper::reportActualDuration(int64_t actualDurationNanos) {
//LOGD("ADPF Oboe %s(dur=%lld)", __func__, (long long)actualDurationNanos);
std::lock_guard<std::mutex> lock(mLock);
Trace::beginSection("reportActualDuration");
Trace::setCounter("actualDurationNanos", actualDurationNanos);
if (mHintSession != nullptr) {
gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
}
Trace::endSection();
}
void AdpfWrapper::close() {
std::lock_guard<std::mutex> lock(mLock);
if (mHintSession != nullptr) {
gAPH_closeSessionFn(mHintSession);
mHintSession = nullptr;
}
}
void AdpfWrapper::onBeginCallback() {
if (isOpen()) {
mBeginCallbackNanos = oboe::AudioClock::getNanoseconds();
}
}
void AdpfWrapper::onEndCallback(double durationScaler) {
if (isOpen()) {
int64_t endCallbackNanos = oboe::AudioClock::getNanoseconds();
int64_t actualDurationNanos = endCallbackNanos - mBeginCallbackNanos;
int64_t scaledDurationNanos = static_cast<int64_t>(actualDurationNanos * durationScaler);
reportActualDuration(scaledDurationNanos);
// When the workload is non-zero, update the conversion factor from workload
// units to nanoseconds duration.
if (mPreviousWorkload > 0) {
mNanosPerWorkloadUnit = ((double) scaledDurationNanos) / mPreviousWorkload;
}
}
}
void AdpfWrapper::reportWorkload(int32_t appWorkload) {
if (isOpen()) {
// Compare with previous workload. If we think we will need more
// time to render the callback then warn ADPF as soon as possible.
if (appWorkload > mPreviousWorkload && mNanosPerWorkloadUnit > 0.0) {
int64_t predictedDuration = (int64_t) (appWorkload * mNanosPerWorkloadUnit);
reportActualDuration(predictedDuration);
}
mPreviousWorkload = appWorkload;
}
}

92
externals/oboe/src/common/AdpfWrapper.h vendored Normal file
View file

@ -0,0 +1,92 @@
/*
* Copyright 2021 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 SYNTHMARK_ADPF_WRAPPER_H
#define SYNTHMARK_ADPF_WRAPPER_H
#include <algorithm>
#include <functional>
#include <stdint.h>
#include <sys/types.h>
#include <unistd.h>
#include <mutex>
namespace oboe {
struct APerformanceHintManager;
struct APerformanceHintSession;
typedef struct APerformanceHintManager APerformanceHintManager;
typedef struct APerformanceHintSession APerformanceHintSession;
class AdpfWrapper {
public:
/**
* Create an ADPF session that can be used to boost performance.
* @param threadId
* @param targetDurationNanos - nominal period of isochronous task
* @return zero or negative error
*/
int open(pid_t threadId,
int64_t targetDurationNanos);
bool isOpen() const {
return (mHintSession != nullptr);
}
void close();
/**
* Call this at the beginning of the callback that you are measuring.
*/
void onBeginCallback();
/**
* Call this at the end of the callback that you are measuring.
* It is OK to skip this if you have a short callback.
*/
void onEndCallback(double durationScaler);
/**
* For internal use only!
* This is a hack for communicating with experimental versions of ADPF.
* @param enabled
*/
static void setUseAlternative(bool enabled) {
sUseAlternativeHack = enabled;
}
/**
* Report the measured duration of a callback.
* This is normally called by onEndCallback().
* You may want to call this directly in order to give an advance hint of a jump in workload.
* @param actualDurationNanos
*/
void reportActualDuration(int64_t actualDurationNanos);
void reportWorkload(int32_t appWorkload);
private:
std::mutex mLock;
APerformanceHintSession *mHintSession = nullptr;
int64_t mBeginCallbackNanos = 0;
static bool sUseAlternativeHack;
int32_t mPreviousWorkload = 0;
double mNanosPerWorkloadUnit = 0.0;
};
}
#endif //SYNTHMARK_ADPF_WRAPPER_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,232 @@
/*
* 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/AudioClock.h"
#include "oboe/AudioStream.h"
#include "oboe/Utilities.h"
#include "OboeDebug.h"
namespace oboe {
/*
* AudioStream
*/
AudioStream::AudioStream(const AudioStreamBuilder &builder)
: AudioStreamBase(builder) {
LOGD("Constructor for AudioStream at %p", this);
}
AudioStream::~AudioStream() {
// This is to help debug use after free bugs.
LOGD("Destructor for AudioStream at %p", this);
}
Result AudioStream::close() {
closePerformanceHint();
// 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
}
beginPerformanceHintInCallback();
// Call the app to do the work.
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);
endPerformanceHintInCallback(numFrames);
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;
result = waitForStateTransition(StreamState::Starting,
StreamState::Started, timeoutNanoseconds);
if (result != Result::OK) {
LOGE("AudioStream::%s() timed out before moving from STARTING to STARTED", __func__);
}
return result;
}
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;
// Make sure we don't try to wait for more frames than the buffer can hold.
// Subtract framesPerBurst because this is often called from a callback
// and we don't want to be sleeping if the buffer is close to overflowing.
const int32_t maxAvailableFrames = getBufferCapacityInFrames() - getFramesPerBurst();
numFrames = std::min(numFrames, maxAvailableFrames);
// The capacity should never be less than one burst. But clip to zero just in case.
numFrames = std::max(0, numFrames);
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));
}
}
void AudioStream::calculateDefaultDelayBeforeCloseMillis() {
// Calculate delay time before close based on burst duration.
// Start with a burst duration then add 1 msec as a safety margin.
mDelayBeforeCloseMillis = std::max(kMinDelayBeforeCloseMillis,
1 + ((mFramesPerBurst * 1000) / getSampleRate()));
LOGD("calculateDefaultDelayBeforeCloseMillis() default = %d",
static_cast<int>(mDelayBeforeCloseMillis));
}
} // namespace oboe

View file

@ -0,0 +1,236 @@
/*
* 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) {
LOGW("Passing AudioStream pointer deprecated, Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.");
return openStreamInternal(streamPP);
}
Result AudioStreamBuilder::openStreamInternal(AudioStream **streamPP) {
auto result = isValidConfig();
if (result != Result::OK) {
LOGW("%s() invalid config. Error %s", __func__, oboe::convertToText(result));
return result;
}
#ifndef OBOE_SUPPRESS_LOG_SPAM
LOGI("%s() %s -------- %s --------",
__func__, getDirection() == Direction::Input ? "INPUT" : "OUTPUT", getVersionText());
#endif
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.openStreamInternal(&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__);
std::shared_ptr<AudioStream> childStream(tempStream);
FilterAudioStream *filterStream = new FilterAudioStream(parentBuilder, childStream);
childStream->setWeakThis(childStream);
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) {
// AAudio supports setBufferSizeInFrames() so use it.
if (streamP->getAudioApi() == AudioApi::AAudio) {
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) {
LOGW("`openManagedStream` is deprecated. Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.");
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 = openStreamInternal(&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,266 @@
/*
* 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/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: %s to %s"
", rate: %d to %d, cbsize: %d to %d, qual = %s",
__func__,
sourceChannelCount, sinkChannelCount,
oboe::convertToText(sourceFormat), oboe::convertToText(sinkFormat),
sourceSampleRate, sinkSampleRate,
sourceFramesPerCallback, sinkFramesPerCallback,
oboe::convertToText(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__, static_cast<int>(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__, static_cast<int>(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__, static_cast<int>(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, std::shared_ptr<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();
mSharingMode = mChildStream->getSharingMode();
mInputPreset = mChildStream->getInputPreset();
mFramesPerBurst = mChildStream->getFramesPerBurst();
mDeviceIds = mChildStream->getDeviceIds();
mHardwareSampleRate = mChildStream->getHardwareSampleRate();
mHardwareChannelCount = mChildStream->getHardwareChannelCount();
mHardwareFormat = mChildStream->getHardwareFormat();
}
virtual ~FilterAudioStream() = default;
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::shared_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

41
externals/oboe/src/common/OboeDebug.h vendored Normal file
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,36 @@
/*
* Copyright (C) 2022 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/OboeExtensions.h"
#include "aaudio/AAudioExtensions.h"
using namespace oboe;
bool OboeExtensions::isMMapSupported(){
return AAudioExtensions::getInstance().isMMapSupported();
}
bool OboeExtensions::isMMapEnabled(){
return AAudioExtensions::getInstance().isMMapEnabled();
}
int32_t OboeExtensions::setMMapEnabled(bool enabled){
return AAudioExtensions::getInstance().setMMapEnabled(enabled);
}
bool OboeExtensions::isMMapUsed(oboe::AudioStream *oboeStream){
return AAudioExtensions::getInstance().isMMapUsed(oboeStream);
}

View file

@ -0,0 +1,319 @@
/*
* 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 <oboe/Utilities.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;
}
bool QuirksManager::DeviceQuirks::shouldConvertFloatToI16ForOutputStreams() {
std::string productManufacturer = getPropertyString("ro.product.manufacturer");
if (getSdkVersion() < __ANDROID_API_L__) {
return true;
} else if ((productManufacturer == "vivo") && (getSdkVersion() < __ANDROID_API_M__)) {
return true;
}
return false;
}
/**
* This is for Samsung Exynos quirks. Samsung Mobile uses Qualcomm chips so
* the QualcommDeviceQuirks would apply.
*/
class SamsungExynosDeviceQuirks : public QuirksManager::DeviceQuirks {
public:
SamsungExynosDeviceQuirks() {
std::string chipname = getPropertyString("ro.hardware.chipname");
isExynos9810 = (chipname == "exynos9810");
isExynos990 = (chipname == "exynos990");
isExynos850 = (chipname == "exynos850");
mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
}
virtual ~SamsungExynosDeviceQuirks() = default;
int32_t getExclusiveBottomMarginInBursts() const override {
return kBottomMargin;
}
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 kBottomMargin = 2;
static constexpr int32_t kTopMargin = 1;
bool isExynos9810 = false;
bool isExynos990 = false;
bool isExynos850 = false;
int mBuildChangelist = 0;
};
class QualcommDeviceQuirks : public QuirksManager::DeviceQuirks {
public:
QualcommDeviceQuirks() {
std::string modelName = getPropertyString("ro.soc.model");
isSM8150 = (modelName == "SDM8150");
}
virtual ~QualcommDeviceQuirks() = default;
int32_t getExclusiveBottomMarginInBursts() const override {
return kBottomMargin;
}
bool isMMapSafe(const AudioStreamBuilder &builder) override {
// See https://github.com/google/oboe/issues/1121#issuecomment-897957749
bool isMMapBroken = false;
if (isSM8150 && (getSdkVersion() <= __ANDROID_API_P__)) {
LOGI("QuirksManager::%s() MMAP not actually supported on this chip."
" Switching off MMAP.", __func__);
isMMapBroken = true;
}
return !isMMapBroken;
}
private:
bool isSM8150 = false;
static constexpr int32_t kBottomMargin = 1;
};
QuirksManager::QuirksManager() {
std::string productManufacturer = getPropertyString("ro.product.manufacturer");
if (productManufacturer == "samsung") {
std::string arch = getPropertyString("ro.arch");
bool isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
if (isExynos) {
mDeviceQuirks = std::make_unique<SamsungExynosDeviceQuirks>();
}
}
if (!mDeviceQuirks) {
std::string socManufacturer = getPropertyString("ro.soc.manufacturer");
if (socManufacturer == "Qualcomm") {
// This may include Samsung Mobile devices.
mDeviceQuirks = std::make_unique<QualcommDeviceQuirks>();
} 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;
const bool isIEC61937 = builder.getFormat() == AudioFormat::IEC61937;
const bool isCompressed = isCompressedFormat(builder.getFormat());
// There should be no conversion for IEC61937. Sample rates and channel counts must be set explicitly.
if (isIEC61937) {
LOGI("QuirksManager::%s() conversion not needed for IEC61937", __func__);
return false;
}
if (isCompressed) {
LOGI("QuirksManager::%s() conversion not needed for compressed format %d",
__func__, builder.getFormat());
return false;
}
// 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, let the native code choose an optimal rate.
// This isn't really a workaround. It is an Oboe feature that is convenient to place here.
// 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 (OboeGlobals::areWorkaroundsEnabled()
&& 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 when needed.
if (OboeGlobals::areWorkaroundsEnabled()
&& isFloat
&& !isInput
&& builder.isFormatConversionAllowed()
&& mDeviceQuirks->shouldConvertFloatToI16ForOutputStreams()
) {
childBuilder.setFormat(AudioFormat::I16);
conversionNeeded = true;
LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices "
"and some devices like Vivo devices may have issues on 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,134 @@
/*
* 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;
}
// On some devices, Float does not work so it should be converted to I16.
static bool shouldConvertFloatToI16ForOutputStreams();
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

33
externals/oboe/src/common/README.md vendored Normal file
View file

@ -0,0 +1,33 @@
# Notes on Implementation
## Latency from Resampling
There are two components of the latency. The resampler itself, and a buffer that
is used to adapt the block sizes.
1) The resampler is an FIR running at the target sample rate. So its latency is the number of taps.
From MultiChannelResampler.cpp, numTaps is
Fastest: 2
Low: 4
Medium: 8
High: 16
Best: 32
For output, the device sampling rate is used, which is typically 48000.For input, the app sampling rate is used.
2) There is a block size adapter that collects odd sized blocks into larger blocks of the correct size.
The adapter contains one burst of frames, from getFramesPerBurst(). But if the app specifies a
particular size using setFramesPerCallback() then that size will be used.
Here is some pseudo-code to calculate the latency.
latencyMillis = 0
targetRate = isOutput ? deviceRate : applicationRate
// Add latency from FIR
latencyMillis += numTaps * 1000.0 / targetRate
// Add latency from block size adaptation
adapterSize = (callbackSize > 0) ? callbackSize : burstSize
if (isOutput && isCallbackUsed) latencyMillis += adapterSize * 1000.0 / deviceRate
else if (isInput && isCallbackUsed) latencyMillis += adapterSize * 1000.0 / applicationRate
else if (isInput && !isCallbackUsed) latencyMillis += adapterSize * 1000.0 / deviceRate

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,49 @@
/*
* 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[]>(static_cast<size_t>(channelCount)
* static_cast<size_t>(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,53 @@
/*
* 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[]>(static_cast<size_t>(kBytesPerI24Packed)
* static_cast<size_t>(channelCount)
* static_cast<size_t>(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,53 @@
/*
* 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[]>(static_cast<size_t>(channelCount)
* static_cast<size_t>(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 "common/Trace.h"
#include "oboe/AudioClock.h"
#include "oboe/StabilizedCallback.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);
}
}

104
externals/oboe/src/common/Trace.cpp vendored Normal file
View file

@ -0,0 +1,104 @@
/*
* 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"
using namespace oboe;
static char buffer[256];
// Tracing functions
static void *(*ATrace_beginSection)(const char *sectionName);
static void *(*ATrace_endSection)();
static void *(*ATrace_setCounter)(const char *counterName, int64_t counterValue);
static bool *(*ATrace_isEnabled)(void);
typedef void *(*fp_ATrace_beginSection)(const char *sectionName);
typedef void *(*fp_ATrace_endSection)();
typedef void *(*fp_ATrace_setCounter)(const char *counterName, int64_t counterValue);
typedef bool *(*fp_ATrace_isEnabled)(void);
bool Trace::mIsTracingEnabled = false;
bool Trace::mIsSetCounterSupported = false;
bool Trace::mHasErrorBeenShown = false;
void Trace::beginSection(const char *format, ...){
if (mIsTracingEnabled) {
va_list va;
va_start(va, format);
vsprintf(buffer, format, va);
ATrace_beginSection(buffer);
va_end(va);
} else if (!mHasErrorBeenShown) {
LOGE("Tracing is either not initialized (call Trace::initialize()) "
"or not supported on this device");
mHasErrorBeenShown = true;
}
}
void Trace::endSection() {
if (mIsTracingEnabled) {
ATrace_endSection();
}
}
void Trace::setCounter(const char *counterName, int64_t counterValue) {
if (mIsSetCounterSupported) {
ATrace_setCounter(counterName, counterValue);
}
}
void Trace::initialize() {
//LOGE("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"));
ATrace_setCounter =
reinterpret_cast<fp_ATrace_setCounter >(
dlsym(lib, "ATrace_setCounter"));
ATrace_isEnabled =
reinterpret_cast<fp_ATrace_isEnabled >(
dlsym(lib, "ATrace_isEnabled"));
if (ATrace_beginSection != nullptr && ATrace_endSection != nullptr
&& ATrace_isEnabled != nullptr && ATrace_isEnabled()) {
mIsTracingEnabled = true;
if (ATrace_setCounter != nullptr) {
mIsSetCounterSupported = true;
} else {
LOGE("setCounter not supported");
}
}
}
}

45
externals/oboe/src/common/Trace.h vendored Normal file
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 OBOE_TRACE_H
#define OBOE_TRACE_H
#include <cstdint>
namespace oboe {
/**
* Wrapper for tracing use with Perfetto
*/
class Trace {
public:
static void beginSection(const char *format, ...);
static void endSection();
static void setCounter(const char *counterName, int64_t counterValue);
static void initialize();
private:
static bool mIsTracingEnabled;
static bool mIsSetCounterSupported;
static bool mHasErrorBeenShown;
};
}
#endif //OBOE_TRACE_H

375
externals/oboe/src/common/Utilities.cpp vendored Normal file
View file

@ -0,0 +1,375 @@
/*
* 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 <set>
#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;
case AudioFormat::IEC61937:
size = sizeof(int16_t);
break;
case AudioFormat::MP3:
case AudioFormat::AAC_LC:
case AudioFormat::AAC_HE_V1:
case AudioFormat::AAC_HE_V2:
case AudioFormat::AAC_ELD:
case AudioFormat::AAC_XHE:
case AudioFormat::OPUS:
// For compressed formats, set the size per sample as 0 as they may not have
// fix size per sample.
size = 0;
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";
case AudioFormat::IEC61937: return "IEC61937";
case AudioFormat::MP3: return "MP3";
case AudioFormat::AAC_LC: return "AAC_LC";
case AudioFormat::AAC_HE_V1: return "AAC_HE_V1";
case AudioFormat::AAC_HE_V2: return "AAC_HE_V2";
case AudioFormat::AAC_ELD: return "AAC_ELD";
case AudioFormat::AAC_XHE: return "AAC_XHE";
case AudioFormat::OPUS: return "OPUS";
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";
}
}
template<>
const char *convertToText<SampleRateConversionQuality>(SampleRateConversionQuality sampleRateConversionQuality) {
switch (sampleRateConversionQuality) {
case SampleRateConversionQuality::None: return "None";
case SampleRateConversionQuality::Fastest: return "Fastest";
case SampleRateConversionQuality::Low: return "Low";
case SampleRateConversionQuality::Medium: return "Medium";
case SampleRateConversionQuality::High: return "High";
case SampleRateConversionQuality::Best: return "Best";
default: return "Unrecognized sample rate conversion quality";
}
}
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;
}
bool isAtLeastPreReleaseCodename(const std::string& codename) {
std::string buildCodename = getPropertyString("ro.build.version.codename");
// Special case "REL", which means the build is not a pre-release build.
if ("REL" == buildCodename) {
return false;
}
// Otherwise lexically compare them. Return true if the build codename is equal to or
// greater than the requested codename.
return buildCodename.compare(codename) >= 0;
}
int getChannelCountFromChannelMask(ChannelMask channelMask) {
return __builtin_popcount(static_cast<uint32_t>(channelMask));
}
std::set<AudioFormat> COMPRESSED_FORMATS = {
AudioFormat::MP3, AudioFormat::AAC_LC, AudioFormat::AAC_HE_V1, AudioFormat::AAC_HE_V2,
AudioFormat::AAC_ELD, AudioFormat::AAC_XHE, AudioFormat::OPUS
};
bool isCompressedFormat(AudioFormat format) {
return COMPRESSED_FORMATS.count(format) != 0;
}
}// namespace oboe

28
externals/oboe/src/common/Version.cpp vendored Normal file
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

178
externals/oboe/src/fifo/FifoBuffer.cpp vendored Normal file
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 "oboe/FifoControllerBase.h"
#include "fifo/FifoController.h"
#include "fifo/FifoControllerIndirect.h"
#include "oboe/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,30 @@
/*
* 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 "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 "oboe/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 "oboe/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,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 "oboe/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() = default;
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,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.
*/
#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::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_OUTER_NAMESPACE::flowgraph */
#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,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.
*/
#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::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_OUTER_NAMESPACE::flowgraph */
#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,450 @@
/*
* 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 flags FLOWGRAPH_ANDROID_INTERNAL and FLOWGRAPH_OUTER_NAMESPACE based on whether compiler
// flag __ANDROID_NDK__ is defined. __ANDROID_NDK__ should be defined in oboe and not aaudio.
#ifndef FLOWGRAPH_ANDROID_INTERNAL
#ifdef __ANDROID_NDK__
#define FLOWGRAPH_ANDROID_INTERNAL 0
#else
#define FLOWGRAPH_ANDROID_INTERNAL 1
#endif // __ANDROID_NDK__
#endif // FLOWGRAPH_ANDROID_INTERNAL
#ifndef FLOWGRAPH_OUTER_NAMESPACE
#ifdef __ANDROID_NDK__
#define FLOWGRAPH_OUTER_NAMESPACE oboe
#else
#define FLOWGRAPH_OUTER_NAMESPACE aaudio
#endif // __ANDROID_NDK__
#endif // FLOWGRAPH_OUTER_NAMESPACE
namespace FLOWGRAPH_OUTER_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() = default;
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.emplace_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;
/**
* Do nothing. 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_OUTER_NAMESPACE::flowgraph */
#endif /* FLOWGRAPH_FLOW_GRAPH_NODE_H */

View file

@ -0,0 +1,70 @@
/*
* 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 <math.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 INT32_MIN;
} else if (f >= limpos) {
return INT32_MAX;
}
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;
}
/**
* Convert a single-precision floating point value to a Q0.23 integer value, stored in a
* 32 bit signed integer (technically stored as Q8.23, but clamped to Q0.23).
*
* Values outside the range [-1.0, 1.0) are properly clamped to -8388608 and 8388607,
* 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 clamp24FromFloat(float f)
{
static const float scale = 1 << 23;
return (int32_t) lroundf(fmaxf(fminf(f * scale, scale - 1.f), -scale));
}
};
#endif // FLOWGRAPH_UTILITIES_H

View file

@ -0,0 +1,67 @@
/*
* Copyright 2022 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 <math.h>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "Limiter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
Limiter::Limiter(int32_t channelCount)
: FlowGraphFilter(channelCount) {
}
int32_t Limiter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t numSamples = numFrames * output.getSamplesPerFrame();
// Cache the last valid output to reduce memory read/write
float lastValidOutput = mLastValidOutput;
for (int32_t i = 0; i < numSamples; i++) {
// Use the previous output if the input is NaN
if (!isnan(*inputBuffer)) {
lastValidOutput = processFloat(*inputBuffer);
}
inputBuffer++;
*outputBuffer++ = lastValidOutput;
}
mLastValidOutput = lastValidOutput;
return numFrames;
}
float Limiter::processFloat(float in)
{
float in_abs = fabsf(in);
if (in_abs <= 1) {
return in;
}
float out;
if (in_abs < kXWhenYis3Decibels) {
out = (kPolynomialSplineA * in_abs + kPolynomialSplineB) * in_abs + kPolynomialSplineC;
} else {
out = M_SQRT2;
}
if (in < 0) {
out = -out;
}
return out;
}

64
externals/oboe/src/flowgraph/Limiter.h vendored Normal file
View file

@ -0,0 +1,64 @@
/*
* Copyright 2022 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_LIMITER_H
#define FLOWGRAPH_LIMITER_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class Limiter : public FlowGraphFilter {
public:
explicit Limiter(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "Limiter";
}
private:
// These numbers are based on a polynomial spline for a quadratic solution Ax^2 + Bx + C
// The range is up to 3 dB, (10^(3/20)), to match AudioTrack for float data.
static constexpr float kPolynomialSplineA = -0.6035533905; // -(1+sqrt(2))/4
static constexpr float kPolynomialSplineB = 2.2071067811; // (3+sqrt(2))/2
static constexpr float kPolynomialSplineC = -0.6035533905; // -(1+sqrt(2))/4
static constexpr float kXWhenYis3Decibels = 1.8284271247; // -1+2sqrt(2)
/**
* Process an input based on the following:
* If between -1 and 1, return the input value.
* If above kXWhenYis3Decibels, return sqrt(2).
* If below -kXWhenYis3Decibels, return -sqrt(2).
* If between 1 and kXWhenYis3Decibels, use a quadratic spline (Ax^2 + Bx + C).
* If between -kXWhenYis3Decibels and -1, use the absolute value for the spline and flip it.
* The derivative of the spline is 1 at 1 and 0 at kXWhenYis3Decibels.
* This way, the graph is both continuous and differentiable.
*/
float processFloat(float in);
// Use the previous valid output for NaN inputs
float mLastValidOutput = 0.0f;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_LIMITER_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,53 @@
/*
* 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::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_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H

View file

@ -0,0 +1,46 @@
/*
* Copyright 2021 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 "MonoBlend.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MonoBlend::MonoBlend(int32_t channelCount)
: FlowGraphFilter(channelCount)
, mInvChannelCount(1. / channelCount)
{
}
int32_t MonoBlend::onProcess(int32_t numFrames) {
int32_t channelCount = output.getSamplesPerFrame();
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
for (size_t i = 0; i < numFrames; ++i) {
float accum = 0;
for (size_t j = 0; j < channelCount; ++j) {
accum += *inputBuffer++;
}
accum *= mInvChannelCount;
for (size_t j = 0; j < channelCount; ++j) {
*outputBuffer++ = accum;
}
}
return numFrames;
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2021 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_BLEND_H
#define FLOWGRAPH_MONO_BLEND_H
#include <sys/types.h>
#include <unistd.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Combine data between multiple channels so each channel is an average
* of all channels.
*/
class MonoBlend : public FlowGraphFilter {
public:
explicit MonoBlend(int32_t channelCount);
virtual ~MonoBlend() = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MonoBlend";
}
private:
const float mInvChannelCount;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MONO_BLEND

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 "MonoToMultiConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MonoToMultiConverter::MonoToMultiConverter(int32_t outputChannelCount)
: input(*this, 1)
, output(*this, outputChannelCount) {
}
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,49 @@
/*
* 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::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() = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MonoToMultiConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H

View file

@ -0,0 +1,47 @@
/*
* Copyright 2021 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 "MultiToManyConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MultiToManyConverter::MultiToManyConverter(int32_t channelCount)
: outputs(channelCount)
, input(*this, channelCount) {
for (int i = 0; i < channelCount; i++) {
outputs[i] = std::make_unique<FlowGraphPortFloatOutput>(*this, 1);
}
}
MultiToManyConverter::~MultiToManyConverter() = default;
int32_t MultiToManyConverter::onProcess(int32_t numFrames) {
int32_t channelCount = input.getSamplesPerFrame();
for (int ch = 0; ch < channelCount; ch++) {
const float *inputBuffer = input.getBuffer() + ch;
float *outputBuffer = outputs[ch]->getBuffer();
for (int i = 0; i < numFrames; i++) {
*outputBuffer++ = *inputBuffer;
inputBuffer += channelCount;
}
}
return numFrames;
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2021 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_MANY_CONVERTER_H
#define FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
/**
* Convert a multi-channel interleaved stream to multiple mono-channel
* outputs
*/
class MultiToManyConverter : public FlowGraphNode {
public:
explicit MultiToManyConverter(int32_t channelCount);
virtual ~MultiToManyConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MultiToManyConverter";
}
std::vector<std::unique_ptr<flowgraph::FlowGraphPortFloatOutput>> outputs;
flowgraph::FlowGraphPortFloatInput input;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_MULTI_TO_MANY_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() = default;
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,49 @@
/*
* 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::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_OUTER_NAMESPACE::flowgraph */
#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,96 @@
/*
* 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::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_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_RAMP_LINEAR_H

View file

@ -0,0 +1,72 @@
/*
* 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_OUTER_NAMESPACE::resampler;
SampleRateConverter::SampleRateConverter(int32_t channelCount,
MultiChannelResampler &resampler)
: FlowGraphFilter(channelCount)
, mResampler(resampler) {
setDataPulledAutomatically(false);
}
void SampleRateConverter::reset() {
FlowGraphNode::reset();
mInputCallCount = kInitialCallCount;
mInputCursor = 0;
}
// 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,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.
*/
#ifndef FLOWGRAPH_SAMPLE_RATE_CONVERTER_H
#define FLOWGRAPH_SAMPLE_RATE_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
#include "resampler/MultiChannelResampler.h"
namespace FLOWGRAPH_OUTER_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 = kInitialCallCount;
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SAMPLE_RATE_CONVERTER_H

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.
*/
#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) {
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);
if (framesPulled <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesPulled * channelCount;
memcpy(floatData, signal, numSamples * sizeof(float));
floatData += numSamples;
framesLeft -= framesPulled;
}
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_FLOAT_H
#define FLOWGRAPH_SINK_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
#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;
}

43
externals/oboe/src/flowgraph/SinkI16.h vendored Normal file
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.
*/
#ifndef FLOWGRAPH_SINK_I16_H
#define FLOWGRAPH_SINK_I16_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
#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;
}

44
externals/oboe/src/flowgraph/SinkI24.h vendored Normal file
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_SINK_I24_H
#define FLOWGRAPH_SINK_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
#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.
*/
#include "FlowGraphNode.h"
#include "FlowgraphUtilities.h"
#include "SinkI32.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
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;
}

40
externals/oboe/src/flowgraph/SinkI32.h vendored Normal file
View file

@ -0,0 +1,40 @@
/*
* 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::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_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SINK_I32_H

View file

@ -0,0 +1,55 @@
/*
* Copyright 2023 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 "FlowGraphNode.h"
#include "FlowgraphUtilities.h"
#include "SinkI8_24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI8_24::SinkI8_24(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI8_24::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_q8_23_from_float_with_clamp(intData, signal, numSamples);
intData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
*intData++ = FlowgraphUtilities::clamp24FromFloat(*signal++);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2023 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_I8_24_H
#define FLOWGRAPH_SINK_I8_24_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class SinkI8_24 : public FlowGraphSink {
public:
explicit SinkI8_24(int32_t channelCount);
~SinkI8_24() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI8_24";
}
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SINK_I8_24_H

View file

@ -0,0 +1,42 @@
/*
* 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 "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,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_FLOAT_H
#define FLOWGRAPH_SOURCE_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
#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,42 @@
/*
* 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::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_OUTER_NAMESPACE::flowgraph */
#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>
#include "FlowGraphNode.h"
#include "SourceI24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
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,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.
*/
#ifndef FLOWGRAPH_SOURCE_I24_H
#define FLOWGRAPH_SOURCE_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
#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>
#include "FlowGraphNode.h"
#include "SourceI32.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
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,42 @@
/*
* 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::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_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SOURCE_I32_H

View file

@ -0,0 +1,54 @@
/*
* Copyright 2023 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 "SourceI8_24.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI8_24::SourceI8_24(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI8_24::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_q8_23(floatData, intData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2023 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_I8_24_H
#define FLOWGRAPH_SOURCE_I8_24_H
#include <stdint.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
class SourceI8_24 : public FlowGraphSourceBuffered {
public:
explicit SourceI8_24(int32_t channelCount);
~SourceI8_24() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI8_24";
}
private:
static constexpr float kScale = 1.0 / (1UL << 23);
};
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
#endif //FLOWGRAPH_SOURCE_I8_24_H

View file

@ -0,0 +1,71 @@
/*
* 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>
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_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_OUTER_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_OUTER_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,54 @@
/*
* 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_INTEGER_RATIO_H
#define RESAMPLER_INTEGER_RATIO_H
#include <sys/types.h>
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_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;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_INTEGER_RATIO_H

View file

@ -0,0 +1,90 @@
/*
* 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>
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_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_OUTER_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_OUTER_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,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 RESAMPLER_LINEAR_RESAMPLER_H
#define RESAMPLER_LINEAR_RESAMPLER_H
#include <memory>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_NAMESPACE::resampler {
/**
* Simple resampler that uses bi-linear interpolation.
*/
class LinearResampler : public MultiChannelResampler {
public:
explicit 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_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_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_OUTER_NAMESPACE::resampler;
MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
: mNumTaps(builder.getNumTaps())
, mX(static_cast<size_t>(builder.getChannelCount())
* static_cast<size_t>(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; // so we start with a write needed
}
// 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[static_cast<size_t>(mCursor) * static_cast<size_t>(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 (fabsf(radians) < 1.0e-9f) 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(static_cast<size_t>(getNumTaps()) * static_cast<size_t>(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 = (outputRate < inputRate)
? (normalizedCutoff * (float)outputRate / inputRate)
: 1.0f; // Do not filter when upsampling.
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(static_cast<double>(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,281 @@
/*
* 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_MULTICHANNEL_RESAMPLER_H
#define RESAMPLER_MULTICHANNEL_RESAMPLER_H
#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#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
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_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.
*
* Note that this value is ignored when upsampling, which is when
* the outputRate is higher than the inputRate.
*
* @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.
*
* Note that normalizedCutoff is ignored when upsampling, which is when
* the outputRate is higher than the inputRate.
*
* @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;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_MULTICHANNEL_RESAMPLER_H

View file

@ -0,0 +1,61 @@
/*
* 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> // Do NOT delete. Needed for LLVM. See #1746
#include <cassert>
#include <math.h>
#include "IntegerRatio.h"
#include "PolyphaseResampler.h"
using namespace RESAMPLER_OUTER_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);
// Multiply input times windowed sinc function.
float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[static_cast<size_t>(mCursor) * static_cast<size_t>(getChannelCount())];
for (int i = 0; i < mNumTaps; i++) {
float coefficient = *coefficients++;
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,53 @@
/*
* 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_POLYPHASE_RESAMPLER_H
#define RESAMPLER_POLYPHASE_RESAMPLER_H
#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include "MultiChannelResampler.h"
#include "ResamplerDefinitions.h"
namespace RESAMPLER_OUTER_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;
};
} /* namespace RESAMPLER_OUTER_NAMESPACE::resampler */
#endif //RESAMPLER_POLYPHASE_RESAMPLER_H

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