1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-29 02:40:05 +00:00

Android: Updated to Oboe 1.4.2

This commit is contained in:
ed 2020-07-01 11:45:13 +01:00
parent d1bfb83fa4
commit 5fe53862ae
26 changed files with 397 additions and 116 deletions

View file

@ -42,6 +42,7 @@ constexpr int64_t kDefaultTimeoutNanos = (2000 * kNanosPerMillisecond);
* Base class for Oboe C++ audio stream.
*/
class AudioStream : public AudioStreamBase {
friend class AudioStreamBuilder; // allow access to setWeakThis() and lockWeakThis()
public:
AudioStream() {}
@ -479,6 +480,23 @@ protected:
mDataCallbackEnabled = enabled;
}
/*
* Set a weak_ptr to this stream from the shared_ptr so that we can
* later use a shared_ptr in the error callback.
*/
void setWeakThis(std::shared_ptr<oboe::AudioStream> &sharedStream) {
mWeakThis = sharedStream;
}
/*
* Make a shared_ptr that will prevent this stream from being deleted.
*/
std::shared_ptr<oboe::AudioStream> lockWeakThis() {
return mWeakThis.lock();
}
std::weak_ptr<AudioStream> mWeakThis; // weak pointer to this object
/**
* Number of frames which have been written into the stream
*
@ -497,17 +515,19 @@ protected:
std::mutex mLock; // for synchronizing start/stop/close
private:
// Log the scheduler if it changes.
void checkScheduler();
int mPreviousScheduler = -1;
std::atomic<bool> mDataCallbackEnabled{false};
std::atomic<bool> mErrorCallbackCalled{false};
};
/**
* This struct is a stateless functor which closes a audiostream prior to its deletion.
* This struct is a stateless functor which closes an AudioStream prior to its deletion.
* This means it can be used to safely delete a smart pointer referring to an open stream.
*/
struct StreamDeleterFunctor {

View file

@ -27,6 +27,7 @@ namespace oboe {
* Base class containing parameters for audio streams and builders.
**/
class AudioStreamBase {
public:
AudioStreamBase() {}

View file

@ -19,12 +19,14 @@
#include "oboe/Definitions.h"
#include "oboe/AudioStreamBase.h"
#include "ResultWithValue.h"
namespace oboe {
// This depends on AudioStream, so we use forward declaration, it will close and delete the stream
struct StreamDeleterFunctor;
using ManagedStream = std::unique_ptr<AudioStream, StreamDeleterFunctor>;
/**
* Factory class for an audio Stream.
*/
@ -382,11 +384,23 @@ public:
*
* The caller owns the pointer to the AudioStream object.
*
* @deprecated Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.
* @param stream pointer to a variable to receive the stream address
* @return OBOE_OK if successful or a negative error code
*/
Result openStream(AudioStream **stream);
/**
* Create and open a stream object based on the current settings.
*
* The caller shares the pointer to the AudioStream object.
* The shared_ptr is used internally by Oboe to prevent the stream from being
* deleted while it is being used by callbacks.
*
* @param stream reference to a shared_ptr to receive the stream address
* @return OBOE_OK if successful or a negative error code
*/
Result openStream(std::shared_ptr<oboe::AudioStream> &stream);
/**
* Create and open a ManagedStream object based on the current builder state.

View file

@ -495,6 +495,25 @@ namespace oboe {
int64_t timestamp; // in nanoseconds
};
class OboeGlobals {
public:
static bool areWorkaroundsEnabled() {
return mWorkaroundsEnabled;
}
/**
* Disable this when writing tests to reproduce bugs in AAudio or OpenSL ES
* that have workarounds in Oboe.
* @param enabled
*/
static void setWorkaroundsEnabled(bool enabled) {
mWorkaroundsEnabled = enabled;
}
private:
static bool mWorkaroundsEnabled;
};
} // namespace oboe
#endif // OBOE_DEFINITIONS_H

View file

@ -19,6 +19,7 @@
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include "oboe/Definitions.h"
namespace oboe {
@ -58,6 +59,19 @@ int32_t convertFormatToSizeInBytes(AudioFormat format);
template <typename FromType>
const char * convertToText(FromType input);
/**
* @param name
* @return the value of a named system property in a string or empty string
*/
std::string getPropertyString(const char * name);
/**
* @param name
* @param defaultValue
* @return integer value associated with a property or the default value
*/
int getPropertyInteger(const char * name, int defaultValue);
/**
* Return the version of the SDK that is currently running.
*

View file

@ -34,10 +34,10 @@
#define OBOE_VERSION_MAJOR 1
// Type: 8-bit unsigned int. Min value: 0 Max value: 255. See below for description.
#define OBOE_VERSION_MINOR 2
#define OBOE_VERSION_MINOR 4
// Type: 16-bit unsigned int. Min value: 0 Max value: 65535. See below for description.
#define OBOE_VERSION_PATCH 4
#define OBOE_VERSION_PATCH 2
#define OBOE_STRINGIFY(x) #x
#define OBOE_TOSTRING(x) OBOE_STRINGIFY(x)

View file

@ -1,7 +1,7 @@
The files in this directory are reproduced from the official Oboe repository, which can be found at
github.com/google/oboe.
These files are from tag 1.3 (5c42747).
These files are from tag 1.4.2 (55304d7).
We've included only those parts of the original repository which are required to build the Oboe
library. Documentation, samples, tests, and other non-library items have been omitted.

View file

@ -26,6 +26,8 @@
#ifdef __ANDROID__
#include <sys/system_properties.h>
#include <common/QuirksManager.h>
#endif
#ifndef OBOE_FIX_FORCE_STARTING_TO_STARTED
@ -72,6 +74,15 @@ static void oboe_aaudio_error_thread_proc(AudioStreamAAudio *oboeStream,
LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__);
}
// This runs in its own thread.
// Only one of these threads will be launched from internalErrorCallback().
// Prevents deletion of the stream if the app is using AudioStreamBuilder::openSharedStream()
static void oboe_aaudio_error_thread_proc_shared(std::shared_ptr<AudioStream> sharedStream,
Result error) {
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(sharedStream.get());
oboe_aaudio_error_thread_proc(oboeStream, error);
}
namespace oboe {
/*
@ -81,7 +92,6 @@ AudioStreamAAudio::AudioStreamAAudio(const AudioStreamBuilder &builder)
: AudioStream(builder)
, mAAudioStream(nullptr) {
mCallbackThreadEnabled.store(false);
LOGD("AudioStreamAAudio() call isSupported()");
isSupported();
}
@ -99,12 +109,21 @@ void AudioStreamAAudio::internalErrorCallback(
void *userData,
aaudio_result_t error) {
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
// Prevents deletion of the stream if the app is using AudioStreamBuilder::openSharedStream()
std::shared_ptr<AudioStream> sharedStream = oboeStream->lockWeakThis();
// These checks should be enough because we assume that the stream close()
// will join() any active callback threads and will not allow new callbacks.
if (oboeStream->wasErrorCallbackCalled()) { // block extra error callbacks
LOGE("%s() multiple error callbacks called!", __func__);
} else if (stream != oboeStream->getUnderlyingStream()) {
LOGD("%s() stream already closed", __func__); // can happen if there are bugs
LOGW("%s() stream already closed", __func__); // can happen if there are bugs
} else if (sharedStream) {
// Handle error on a separate thread using shared pointer.
std::thread t(oboe_aaudio_error_thread_proc_shared, sharedStream,
static_cast<Result>(error));
t.detach();
} else {
// Handle error on a separate thread.
std::thread t(oboe_aaudio_error_thread_proc, oboeStream,
@ -161,7 +180,8 @@ Result AudioStreamAAudio::open() {
// does not increase latency.
int32_t capacity = mBufferCapacityInFrames;
constexpr int kCapacityRequiredForFastLegacyTrack = 4096; // matches value in AudioFinger
if (mDirection == oboe::Direction::Input
if (OboeGlobals::areWorkaroundsEnabled()
&& mDirection == oboe::Direction::Input
&& capacity != oboe::Unspecified
&& capacity < kCapacityRequiredForFastLegacyTrack
&& mPerformanceMode == oboe::PerformanceMode::LowLatency) {
@ -250,15 +270,14 @@ Result AudioStreamAAudio::open() {
mSessionId = SessionId::None;
}
LOGD("AudioStreamAAudio.open() app format = %d", static_cast<int>(mFormat));
LOGD("AudioStreamAAudio.open() sample rate = %d", static_cast<int>(mSampleRate));
LOGD("AudioStreamAAudio.open() capacity = %d", static_cast<int>(mBufferCapacityInFrames));
LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d",
static_cast<int>(mFormat), static_cast<int>(mSampleRate),
static_cast<int>(mBufferCapacityInFrames));
error2:
mLibLoader->builder_delete(aaudioBuilder);
LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s, mAAudioStream = %p",
mLibLoader->convertResultToText(static_cast<aaudio_result_t>(result)),
mAAudioStream.load());
LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s",
mLibLoader->convertResultToText(static_cast<aaudio_result_t>(result)));
return result;
}
@ -288,7 +307,7 @@ DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream *stream,
return result;
} else {
if (result == DataCallbackResult::Stop) {
LOGE("Oboe callback returned DataCallbackResult::Stop");
LOGD("Oboe callback returned DataCallbackResult::Stop");
} else {
LOGE("Oboe callback returned unexpected value = %d", result);
}
@ -445,7 +464,8 @@ Result AudioStreamAAudio::waitForStateChange(StreamState currentState,
break;
}
#if OBOE_FIX_FORCE_STARTING_TO_STARTED
if (aaudioNextState == static_cast<aaudio_stream_state_t >(StreamState::Starting)) {
if (OboeGlobals::areWorkaroundsEnabled()
&& aaudioNextState == static_cast<aaudio_stream_state_t >(StreamState::Starting)) {
aaudioNextState = static_cast<aaudio_stream_state_t >(StreamState::Started);
}
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
@ -481,11 +501,13 @@ ResultWithValue<int32_t> AudioStreamAAudio::setBufferSizeInFrames(int32_t reques
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
if (requestedFrames > mBufferCapacityInFrames) {
requestedFrames = mBufferCapacityInFrames;
int32_t adjustedFrames = requestedFrames;
if (adjustedFrames > mBufferCapacityInFrames) {
adjustedFrames = mBufferCapacityInFrames;
}
int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, requestedFrames);
adjustedFrames = QuirksManager::getInstance().clipBufferSize(*this, adjustedFrames);
int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, adjustedFrames);
// Cache the result if it's valid
if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize;
@ -502,7 +524,8 @@ StreamState AudioStreamAAudio::getState() const {
if (stream != nullptr) {
aaudio_stream_state_t aaudioState = mLibLoader->stream_getState(stream);
#if OBOE_FIX_FORCE_STARTING_TO_STARTED
if (aaudioState == AAUDIO_STREAM_STATE_STARTING) {
if (OboeGlobals::areWorkaroundsEnabled()
&& aaudioState == AAUDIO_STREAM_STATE_STARTING) {
aaudioState = AAUDIO_STREAM_STATE_STARTED;
}
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED

View file

@ -91,6 +91,8 @@ public:
void *audioData,
int32_t numFrames);
bool isMMapUsed();
protected:
static void internalErrorCallback(
AAudioStream *stream,
@ -108,8 +110,6 @@ protected:
private:
bool isMMapUsed();
std::atomic<bool> mCallbackThreadEnabled;
// pointer to the underlying AAudio stream, valid if open, null if closed

View file

@ -39,23 +39,25 @@ Result AudioStream::close() {
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; // We should not be getting called any more.
}
// TODO remove
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 result;
if (mStreamCallback == nullptr) {
result = onDefaultCallback(audioData, numFrames);
@ -195,11 +197,9 @@ ResultWithValue<FrameTimestamp> AudioStream::getTimestamp(clockid_t clockId) {
}
static void oboe_stop_thread_proc(AudioStream *oboeStream) {
LOGD("%s() called ----)))))", __func__);
if (oboeStream != nullptr) {
oboeStream->requestStop();
}
LOGD("%s() returning (((((----", __func__);
}
void AudioStream::launchStopThread() {

View file

@ -26,6 +26,8 @@
#include "opensles/AudioStreamOpenSLES.h"
#include "QuirksManager.h"
bool oboe::OboeGlobals::mWorkaroundsEnabled = true;
namespace oboe {
/**
@ -85,7 +87,7 @@ bool AudioStreamBuilder::isCompatible(AudioStreamBase &other) {
Result AudioStreamBuilder::openStream(AudioStream **streamPP) {
Result result = Result::OK;
LOGD("%s() %s -------- %s --------",
LOGI("%s() %s -------- %s --------",
__func__, getDirection() == Direction::Input ? "INPUT" : "OUTPUT", getVersionText());
if (streamPP == nullptr) {
@ -126,7 +128,7 @@ Result AudioStreamBuilder::openStream(AudioStream **streamPP) {
}
// Use childStream in a FilterAudioStream.
LOGD("%s() create a FilterAudioStream for data conversion.", __func__);
LOGI("%s() create a FilterAudioStream for data conversion.", __func__);
FilterAudioStream *filterStream = new FilterAudioStream(parentBuilder, tempStream);
result = filterStream->configureFlowGraph();
if (result != Result::OK) {
@ -184,4 +186,16 @@ Result AudioStreamBuilder::openManagedStream(oboe::ManagedStream &stream) {
return result;
}
} // namespace oboe
Result AudioStreamBuilder::openStream(std::shared_ptr<AudioStream> &sharedStream) {
sharedStream.reset();
AudioStream *streamptr;
auto result = openStream(&streamptr);
if (result == Result::OK) {
sharedStream.reset(streamptr);
// Save a weak_ptr in the stream for use with callbacks.
streamptr->setWeakThis(sharedStream);
}
return result;
}
} // namespace oboe

View file

@ -75,6 +75,7 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream
FlowGraphPortFloatOutput *lastOutput = nullptr;
bool isOutput = sourceStream->getDirection() == Direction::Output;
bool isInput = !isOutput;
mFilterStream = isOutput ? sourceStream : sinkStream;
AudioFormat sourceFormat = sourceStream->getFormat();
@ -85,7 +86,7 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream
int32_t sinkChannelCount = sinkStream->getChannelCount();
int32_t sinkSampleRate = sinkStream->getSampleRate();
LOGD("%s() flowgraph converts channels: %d to %d, format: %d to %d, rate: %d to %d, qual = %d",
LOGI("%s() flowgraph converts channels: %d to %d, format: %d to %d, rate: %d to %d, qual = %d",
__func__,
sourceChannelCount, sinkChannelCount,
sourceFormat, sinkFormat,
@ -96,8 +97,10 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream
? sourceStream->getFramesPerBurst()
: sourceStream->getFramesPerCallback();
// Source
// If OUTPUT and using a callback then call back to the app using a SourceCaller.
// If INPUT and NOT using a callback then read from the child stream using a SourceCaller.
if ((sourceStream->getCallback() != nullptr && isOutput)
|| (sourceStream->getCallback() == nullptr && !isOutput)) {
|| (sourceStream->getCallback() == nullptr && isInput)) {
switch (sourceFormat) {
case AudioFormat::Float:
mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount,
@ -114,6 +117,8 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream
mSourceCaller->setStream(sourceStream);
lastOutput = &mSourceCaller->output;
} else {
// If OUTPUT and NOT using a callback then write to the child stream using a BlockWriter.
// 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);
@ -125,7 +130,7 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream
LOGE("%s() Unsupported source format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
if (!isOutput) {
if (isInput) {
// The BlockWriter is after the Sink so use the SinkStream size.
mBlockWriter.open(framesPerCallback * sinkStream->getBytesPerFrame());
mAppBuffer = std::make_unique<uint8_t[]>(
@ -187,14 +192,14 @@ int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames, int64_t t
// This is similar to pushing data through the flowgraph.
int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) {
// Put the data from the Source at the head of the flowgraph.
// 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(mFramePosition, mAppBuffer.get(), flowgraph::kDefaultBufferSize);
mFramePosition += framesRead;
if (framesRead <= 0) break;
// Write to a block adapter, which will call the app whenever it has enough data.
// 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

View file

@ -21,6 +21,25 @@
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;
@ -70,3 +89,4 @@ ResultWithValue<int32_t> FilterAudioStream::read(void *buffer,
int32_t framesRead = mFlowGraph->read(buffer, numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(framesRead);
}

View file

@ -187,9 +187,19 @@ public:
: mFlowGraph->getDataCallbackResult();
}
void onErrorBeforeClose(AudioStream *oboeStream, Result error) override {}
void onErrorBeforeClose(AudioStream *oboeStream, Result error) override {
if (mStreamCallback != nullptr) {
mStreamCallback->onErrorBeforeClose(this, error);
}
}
void onErrorAfterClose(AudioStream *oboeStream, Result error) override {}
void onErrorAfterClose(AudioStream *oboeStream, Result error) override {
// Close this parent stream because the callback will only close the child.
AudioStream::close();
if (mStreamCallback != nullptr) {
mStreamCallback->onErrorAfterClose(this, error);
}
}
private:

View file

@ -19,6 +19,7 @@
#include <memory>
#include <stdint.h>
#include <sys/types.h>
/**
* Interface for a class that needs fixed-size blocks.

View file

@ -18,29 +18,24 @@
#ifndef OBOE_DEBUG_H
#define OBOE_DEBUG_H
#if OBOE_ENABLE_LOGGING
#include <android/log.h>
#ifndef MODULE_NAME
#define MODULE_NAME "OboeAudio"
#endif
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
// 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(...)
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#define LOGF(...)
#endif
#endif //OBOE_DEBUG_H

View file

@ -16,10 +16,76 @@
#include <oboe/AudioStreamBuilder.h>
#include <oboe/Oboe.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;
}
class SamsungDeviceQuirks : public QuirksManager::DeviceQuirks {
public:
SamsungDeviceQuirks() {
std::string arch = getPropertyString("ro.arch");
isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
}
virtual ~SamsungDeviceQuirks() = default;
int32_t getExclusiveBottomMarginInBursts() const override {
// TODO Make this conditional on build version when MMAP timing improves.
return isExynos ? kBottomMarginExynos : kBottomMarginOther;
}
int32_t getExclusiveTopMarginInBursts() const override {
return kTopMargin;
}
private:
// Stay farther away from DSP position on Exynos devices.
static constexpr int32_t kBottomMarginExynos = 2;
static constexpr int32_t kBottomMarginOther = 1;
static constexpr int32_t kTopMargin = 1;
bool isExynos = false;
};
QuirksManager::QuirksManager() {
std::string manufacturer = getPropertyString("ro.product.manufacturer");
if (manufacturer == "samsung") {
mDeviceQuirks = std::make_unique<SamsungDeviceQuirks>();
} else {
mDeviceQuirks = std::make_unique<DeviceQuirks>();
}
}
bool QuirksManager::isConversionNeeded(
const AudioStreamBuilder &builder,
AudioStreamBuilder &childBuilder) {
@ -54,11 +120,13 @@ bool QuirksManager::isConversionNeeded(
// Channel Count
if (builder.getChannelCount() != oboe::Unspecified
&& builder.isChannelConversionAllowed()) {
if (builder.getChannelCount() == 2 // stereo?
if (OboeGlobals::areWorkaroundsEnabled()
&& builder.getChannelCount() == 2 // stereo?
&& isInput
&& isLowLatency
&& (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))) {
// workaround for temporary heap size regression, b/66967812
// Workaround for heap size regression in O.
// b/66967812 AudioRecord does not allow FAST track for stereo capture in O
childBuilder.setChannelCount(1);
conversionNeeded = true;
}

View file

@ -19,10 +19,13 @@
#include <memory>
#include <oboe/AudioStreamBuilder.h>
#include <aaudio/AudioStreamAAudio.h>
namespace oboe {
/**
* INTERNAL USE ONLY.
*
* Based on manufacturer, model and Android version number
* decide whether data conversion needs to occur.
*
@ -33,11 +36,17 @@ class QuirksManager {
public:
static QuirksManager &getInstance() {
static QuirksManager instance;
static QuirksManager instance; // singleton
return instance;
}
QuirksManager();
virtual ~QuirksManager() = default;
/**
* Do we need to do channel, format or rate conversion to provide a low latency
* stream for this builder? If so then provide a builder for the native child stream
* that will be used to get low latency.
*
* @param builder builder provided by application
* @param childBuilder modified builder appropriate for the underlying device
@ -45,7 +54,56 @@ public:
*/
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;
}
static constexpr int32_t kDefaultBottomMarginInBursts = 0;
static constexpr int32_t kDefaultTopMarginInBursts = 0;
// For Legacy streams, do not let the buffer go below one burst.
// b/129545119 | AAudio Legacy allows setBufferSizeInFrames too low
// Fixed in Q
static constexpr int32_t kLegacyBottomMarginInBursts = 1;
};
private:
std::unique_ptr<DeviceQuirks> mDeviceQuirks{};
};
}

View file

@ -266,18 +266,40 @@ const char *convertToText<ChannelCount>(ChannelCount channelCount) {
}
}
int getSdkVersion() {
std::string getPropertyString(const char * name) {
std::string result;
#ifdef __ANDROID__
static int sCachedSdkVersion = -1;
if (sCachedSdkVersion == -1) {
char sdk[PROP_VALUE_MAX] = {0};
if (__system_property_get("ro.build.version.sdk", sdk) != 0) {
sCachedSdkVersion = atoi(sdk);
}
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = valueText;
}
return sCachedSdkVersion;
#else
(void) name;
#endif
return -1;
return result;
}
int getPropertyInteger(const char * name, int defaultValue) {
int result = defaultValue;
#ifdef __ANDROID__
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = atoi(valueText);
}
#else
(void) name;
#endif
return result;
}
int getSdkVersion() {
static int sCachedSdkVersion = -1;
#ifdef __ANDROID__
if (sCachedSdkVersion == -1) {
sCachedSdkVersion = getPropertyInteger("ro.build.version.sdk", -1);
}
#endif
return sCachedSdkVersion;
}
}// namespace oboe

View file

@ -40,8 +40,6 @@ FifoBuffer::FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames)
int32_t bytesPerBuffer = bytesPerFrame * capacityInFrames;
mStorage = new uint8_t[bytesPerBuffer];
mStorageOwned = true;
LOGD("%s() capacityInFrames = %d, bytesPerFrame = %d",
__func__, capacityInFrames, bytesPerFrame);
}
FifoBuffer::FifoBuffer( uint32_t bytesPerFrame,
@ -60,8 +58,6 @@ FifoBuffer::FifoBuffer( uint32_t bytesPerFrame,
writeCounterAddress);
mStorage = dataStorageAddress;
mStorageOwned = false;
LOGD("%s(*) capacityInFrames = %d, bytesPerFrame = %d",
__func__, capacityInFrames, bytesPerFrame);
}
FifoBuffer::~FifoBuffer() {

View file

@ -14,14 +14,12 @@
* limitations under the License.
*/
#include <vector>
#include "IntegerRatio.h"
using namespace resampler;
// Enough primes to cover the common sample rates.
const std::vector<int> IntegerRatio::kPrimes{
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,

View file

@ -18,7 +18,6 @@
#define OBOE_INTEGER_RATIO_H
#include <sys/types.h>
#include <vector>
namespace resampler {
@ -46,7 +45,6 @@ public:
private:
int32_t mNumerator;
int32_t mDenominator;
static const std::vector<int> kPrimes;
};
}

View file

@ -198,7 +198,7 @@ Result AudioInputStreamOpenSLES::open() {
goto error;
}
oboeResult = configureBufferSizes();
oboeResult = configureBufferSizes(mSampleRate);
if (Result::OK != oboeResult) {
goto error;
}
@ -340,7 +340,7 @@ Result AudioInputStreamOpenSLES::updateServiceFrameCounter() {
SLmillisecond msec = 0;
SLresult slResult = (*mRecordInterface)->GetPosition(mRecordInterface, &msec);
if (SL_RESULT_SUCCESS != slResult) {
LOGD("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult));
LOGW("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult));
// set result based on SLresult
result = Result::ErrorInternal;
} else {

View file

@ -223,7 +223,7 @@ Result AudioOutputStreamOpenSLES::open() {
goto error;
}
oboeResult = configureBufferSizes();
oboeResult = configureBufferSizes(mSampleRate);
if (Result::OK != oboeResult) {
goto error;
}
@ -271,7 +271,7 @@ Result AudioOutputStreamOpenSLES::setPlayState_l(SLuint32 newState) {
SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
if (SL_RESULT_SUCCESS != slResult) {
LOGD("AudioOutputStreamOpenSLES(): %s() returned %s", __func__, getSLErrStr(slResult));
LOGW("AudioOutputStreamOpenSLES(): %s() returned %s", __func__, getSLErrStr(slResult));
result = Result::ErrorInternal; // TODO convert slResult to Result::Error
}
return result;
@ -312,7 +312,6 @@ Result AudioOutputStreamOpenSLES::requestStart() {
setState(initialState);
mLock.unlock();
}
LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
return result;
}
@ -343,7 +342,6 @@ Result AudioOutputStreamOpenSLES::requestPause() {
} else {
setState(initialState);
}
LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
return result;
}
@ -371,7 +369,6 @@ Result AudioOutputStreamOpenSLES::requestFlush_l() {
result = Result::ErrorInternal;
}
}
LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
return result;
}
@ -410,7 +407,6 @@ Result AudioOutputStreamOpenSLES::requestStop() {
} else {
setState(initialState);
}
LOGD("AudioOutputStreamOpenSLES(): %s() returning %d", __func__, result);
return result;
}
@ -440,7 +436,7 @@ Result AudioOutputStreamOpenSLES::updateServiceFrameCounter() {
SLmillisecond msec = 0;
SLresult slResult = (*mPlayInterface)->GetPosition(mPlayInterface, &msec);
if (SL_RESULT_SUCCESS != slResult) {
LOGD("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult));
LOGW("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult));
// set result based on SLresult
result = Result::ErrorInternal;
} else {

View file

@ -37,7 +37,7 @@ AudioStreamOpenSLES::AudioStreamOpenSLES(const AudioStreamBuilder &builder)
mSessionId = SessionId::None;
}
static constexpr int32_t kFramesPerHighLatencyBurst = 960; // typical, 20 msec at 48000
static constexpr int32_t kHighLatencyBufferSizeMillis = 20; // typical Android period
static constexpr SLuint32 kAudioChannelCountMax = 30; // TODO Why 30?
static constexpr SLuint32 SL_ANDROID_UNKNOWN_CHANNELMASK = 0; // Matches name used internally.
@ -69,8 +69,7 @@ SLuint32 AudioStreamOpenSLES::getDefaultByteOrder() {
Result AudioStreamOpenSLES::open() {
LOGI("AudioStreamOpenSLES::open(chans:%d, rate:%d)",
mChannelCount, mSampleRate);
LOGI("AudioStreamOpenSLES::open() chans=%d, rate=%d", mChannelCount, mSampleRate);
SLresult result = EngineOpenSLES::getInstance().open();
if (SL_RESULT_SUCCESS != result) {
@ -94,24 +93,35 @@ Result AudioStreamOpenSLES::open() {
return Result::OK;
}
Result AudioStreamOpenSLES::configureBufferSizes() {
Result AudioStreamOpenSLES::configureBufferSizes(int32_t sampleRate) {
LOGD("AudioStreamOpenSLES:%s(%d) initial mFramesPerBurst = %d, mFramesPerCallback = %d",
__func__, sampleRate, mFramesPerBurst, mFramesPerCallback);
// Decide frames per burst based on hints from caller.
mFramesPerBurst = mFramesPerCallback;
if (mFramesPerBurst == kUnspecified) {
if (mFramesPerCallback != kUnspecified) {
// Requested framesPerCallback must be honored.
mFramesPerBurst = mFramesPerCallback;
} else {
mFramesPerBurst = DefaultStreamValues::FramesPerBurst;
// Calculate the size of a fixed duration high latency buffer based on sample rate.
int32_t framesPerHighLatencyBuffer =
(kHighLatencyBufferSizeMillis * sampleRate) / kMillisPerSecond;
// For high latency streams, use a larger buffer size.
// Performance Mode support was added in N_MR1 (7.1)
if (getSdkVersion() >= __ANDROID_API_N_MR1__
&& mPerformanceMode != PerformanceMode::LowLatency
&& mFramesPerBurst < framesPerHighLatencyBuffer) {
// Find a multiple of framesPerBurst >= framesPerHighLatencyBuffer.
int32_t numBursts = (framesPerHighLatencyBuffer + mFramesPerBurst - 1) / mFramesPerBurst;
mFramesPerBurst *= numBursts;
LOGD("AudioStreamOpenSLES:%s() NOT low latency, set mFramesPerBurst = %d",
__func__, mFramesPerBurst);
}
mFramesPerCallback = mFramesPerBurst;
}
// For high latency streams, use a larger buffer size.
// Performance Mode support was added in N_MR1 (7.1)
if (getSdkVersion() >= __ANDROID_API_N_MR1__
&& mPerformanceMode != PerformanceMode::LowLatency
&& mFramesPerBurst < kFramesPerHighLatencyBurst) {
// Find a multiple of framesPerBurst >= kFramesPerHighLatencyBurst.
int32_t numBursts = (kFramesPerHighLatencyBurst + mFramesPerBurst - 1) / mFramesPerBurst;
mFramesPerBurst *= numBursts;
LOGD("AudioStreamOpenSLES:%s() NOT low latency, set mFramesPerBurst = %d",
__func__, mFramesPerBurst);
}
mFramesPerCallback = mFramesPerBurst;
LOGD("AudioStreamOpenSLES:%s(%d) final mFramesPerBurst = %d, mFramesPerCallback = %d",
__func__, sampleRate, mFramesPerBurst, mFramesPerCallback);
mBytesPerCallback = mFramesPerCallback * getBytesPerFrame();
if (mBytesPerCallback <= 0) {
@ -291,23 +301,21 @@ int32_t AudioStreamOpenSLES::getBufferDepth(SLAndroidSimpleBufferQueueItf bq) {
void AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) {
bool stopStream = false;
// Ask the callback to fill the output buffer with data.
// Ask the app callback to process the buffer.
DataCallbackResult result = fireDataCallback(mCallbackBuffer.get(), mFramesPerCallback);
if (result == DataCallbackResult::Continue) {
// Update Oboe service position based on OpenSL ES position.
updateServiceFrameCounter();
// Pass the buffer to OpenSLES.
SLresult enqueueResult = enqueueCallbackBuffer(bq);
if (enqueueResult != SL_RESULT_SUCCESS) {
LOGE("%s() returned %d", __func__, enqueueResult);
stopStream = true;
}
// Update Oboe client position with frames handled by the callback.
if (getDirection() == Direction::Input) {
mFramesRead += mFramesPerCallback;
} else {
mFramesWritten += mFramesPerCallback;
}
// Pass the data to OpenSLES.
SLresult enqueueResult = enqueueCallbackBuffer(bq);
if (enqueueResult != SL_RESULT_SUCCESS) {
LOGE("%s() returned %d", __func__, enqueueResult);
stopStream = true;
}
} else if (result == DataCallbackResult::Stop) {
LOGD("Oboe callback returned Stop");
stopStream = true;
@ -320,7 +328,7 @@ void AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq
}
}
// this callback handler is called every time a buffer needs processing
// This callback handler is called every time a buffer has been processed by OpenSL ES.
static void bqCallbackGlue(SLAndroidSimpleBufferQueueItf bq, void *context) {
(reinterpret_cast<AudioStreamOpenSLES *>(context))->processBufferCallback(bq);
}
@ -348,7 +356,8 @@ int32_t AudioStreamOpenSLES::getFramesPerBurst() {
return mFramesPerBurst;
}
int64_t AudioStreamOpenSLES::getFramesProcessedByServer() const {
int64_t AudioStreamOpenSLES::getFramesProcessedByServer() {
updateServiceFrameCounter();
int64_t millis64 = mPositionMillis.get();
int64_t framesProcessed = millis64 * getSampleRate() / kMillisPerSecond;
return framesProcessed;

View file

@ -100,7 +100,7 @@ protected:
PerformanceMode convertPerformanceMode(SLuint32 openslMode) const;
SLuint32 convertPerformanceMode(PerformanceMode oboeMode) const;
Result configureBufferSizes();
Result configureBufferSizes(int32_t sampleRate);
void logUnsupportedAttributes();
@ -112,7 +112,7 @@ protected:
mState.store(state);
}
int64_t getFramesProcessedByServer() const;
int64_t getFramesProcessedByServer();
// OpenSLES stuff
SLObjectItf mObjectInterface = nullptr;