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:
parent
d1bfb83fa4
commit
5fe53862ae
26 changed files with 397 additions and 116 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace oboe {
|
|||
* Base class containing parameters for audio streams and builders.
|
||||
**/
|
||||
class AudioStreamBase {
|
||||
|
||||
public:
|
||||
|
||||
AudioStreamBase() {}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/**
|
||||
* Interface for a class that needs fixed-size blocks.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue