Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,713 @@
/*
* 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_H_
#define OBOE_STREAM_H_
#include <atomic>
#include <cstdint>
#include <ctime>
#include <mutex>
#include "oboe/Definitions.h"
#include "oboe/ResultWithValue.h"
#include "oboe/AudioStreamBuilder.h"
#include "oboe/AudioStreamBase.h"
/** WARNING - UNDER CONSTRUCTION - THIS API WILL CHANGE. */
namespace oboe {
/**
* The default number of nanoseconds to wait for when performing state change operations on the
* stream, such as `start` and `stop`.
*
* @see oboe::AudioStream::start
*/
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() {}
/**
* Construct an `AudioStream` using the given `AudioStreamBuilder`
*
* @param builder containing all the stream's attributes
*/
explicit AudioStream(const AudioStreamBuilder &builder);
virtual ~AudioStream() = default;
/**
* Open a stream based on the current settings.
*
* Note that we do not recommend re-opening a stream that has been closed.
* TODO Should we prevent re-opening?
*
* @return
*/
virtual Result open() {
return Result::OK; // Called by subclasses. Might do more in the future.
}
/**
* Free the audio resources associated with a stream created by AAudioStreamBuilder_openStream().
*
* AAudioStream_close() should be called at some point after calling this function.
*
* After this call, the stream will be in AAUDIO_STREAM_STATE_CLOSING
*
* This function is useful if you want to release the audio resources immediately, but still allow
* queries to the stream to occur from other threads. This often happens if you are monitoring
* stream progress from a UI thread.
*
* NOTE: This function is only fully implemented for MMAP streams, which are low latency streams
* supported by some devices. On other "Legacy" streams some audio resources will still be in use
* and some callbacks may still be in process after this call.
*
* Available in AAudio since API level 30. Returns Result::ErrorUnimplemented otherwise.
*
* * @return either Result::OK or an error.
*/
virtual Result release() {
return Result::ErrorUnimplemented;
}
/**
* Close the stream and deallocate any resources from the open() call.
*/
virtual Result close();
/**
* Start the stream. This will block until the stream has been started, an error occurs
* or `timeoutNanoseconds` has been reached.
*/
virtual Result start(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
/**
* Pause the stream. This will block until the stream has been paused, an error occurs
* or `timeoutNanoseconds` has been reached.
*/
virtual Result pause(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
/**
* Flush the stream. This will block until the stream has been flushed, an error occurs
* or `timeoutNanoseconds` has been reached.
*/
virtual Result flush(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
/**
* Stop the stream. This will block until the stream has been stopped, an error occurs
* or `timeoutNanoseconds` has been reached.
*/
virtual Result stop(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
/* Asynchronous requests.
* Use waitForStateChange() if you need to wait for completion.
*/
/**
* Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `start(0)`.
*/
virtual Result requestStart() = 0;
/**
* Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `pause(0)`.
*/
virtual Result requestPause() = 0;
/**
* Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `flush(0)`.
*/
virtual Result requestFlush() = 0;
/**
* Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `stop(0)`.
*/
virtual Result requestStop() = 0;
/**
* Query the current state, eg. StreamState::Pausing
*
* @return state or a negative error.
*/
virtual StreamState getState() = 0;
/**
* Wait until the stream's current state no longer matches the input state.
* The input state is passed to avoid race conditions caused by the state
* changing between calls.
*
* Note that generally applications do not need to call this. It is considered
* an advanced technique and is mostly used for testing.
*
* <pre><code>
* int64_t timeoutNanos = 500 * kNanosPerMillisecond; // arbitrary 1/2 second
* StreamState currentState = stream->getState();
* StreamState nextState = StreamState::Unknown;
* while (result == Result::OK && currentState != StreamState::Paused) {
* result = stream->waitForStateChange(
* currentState, &nextState, timeoutNanos);
* currentState = nextState;
* }
* </code></pre>
*
* If the state does not change within the timeout period then it will
* return ErrorTimeout. This is true even if timeoutNanoseconds is zero.
*
* @param inputState The state we want to change away from.
* @param nextState Pointer to a variable that will be set to the new state.
* @param timeoutNanoseconds The maximum time to wait in nanoseconds.
* @return Result::OK or a Result::Error.
*/
virtual Result waitForStateChange(StreamState inputState,
StreamState *nextState,
int64_t timeoutNanoseconds) = 0;
/**
* This can be used to adjust the latency of the buffer by changing
* the threshold where blocking will occur.
* By combining this with getXRunCount(), the latency can be tuned
* at run-time for each device.
*
* This cannot be set higher than getBufferCapacity().
*
* This should only be used with Output streams. It will
* be ignored for Input streams because they are generally kept as empty as possible.
*
* For OpenSL ES, this method only has an effect on output stream that do NOT
* use a callback. The blocking writes goes into a buffer in Oboe and the size of that
* buffer is controlled by this method.
*
* @param requestedFrames requested number of frames that can be filled without blocking
* @return the resulting buffer size in frames (obtained using value()) or an error (obtained
* using error())
*/
virtual ResultWithValue<int32_t> setBufferSizeInFrames(int32_t /* requestedFrames */) {
return Result::ErrorUnimplemented;
}
/**
* An XRun is an Underrun or an Overrun.
* During playing, an underrun will occur if the stream is not written in time
* and the system runs out of valid data.
* During recording, an overrun will occur if the stream is not read in time
* and there is no place to put the incoming data so it is discarded.
*
* An underrun or overrun can cause an audible "pop" or "glitch".
*
* @return a result which is either Result::OK with the xRun count as the value, or a
* Result::Error* code
*/
virtual ResultWithValue<int32_t> getXRunCount() {
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
}
/**
* @return true if XRun counts are supported on the stream
*/
virtual bool isXRunCountSupported() const = 0;
/**
* Query the number of frames that are read or written by the endpoint at one time.
*
* @return burst size
*/
int32_t getFramesPerBurst() const {
return mFramesPerBurst;
}
/**
* Get the number of bytes in each audio frame. This is calculated using the channel count
* and the sample format. For example, a 2 channel floating point stream will have
* 2 * 4 = 8 bytes per frame.
*
* @return number of bytes in each audio frame.
*/
int32_t getBytesPerFrame() const { return mChannelCount * getBytesPerSample(); }
/**
* Get the number of bytes per sample. This is calculated using the sample format. For example,
* a stream using 16-bit integer samples will have 2 bytes per sample.
*
* @return the number of bytes per sample.
*/
int32_t getBytesPerSample() const;
/**
* The number of audio frames written into the stream.
* This monotonic counter will never get reset.
*
* @return the number of frames written so far
*/
virtual int64_t getFramesWritten();
/**
* The number of audio frames read from the stream.
* This monotonic counter will never get reset.
*
* @return the number of frames read so far
*/
virtual int64_t getFramesRead();
/**
* Calculate the latency of a stream based on getTimestamp().
*
* Output latency is the time it takes for a given frame to travel from the
* app to some type of digital-to-analog converter. If the DAC is external, for example
* in a USB interface or a TV connected by HDMI, then there may be additional latency
* that the Android device is unaware of.
*
* Input latency is the time it takes to a given frame to travel from an analog-to-digital
* converter (ADC) to the app.
*
* Note that the latency of an OUTPUT stream will increase abruptly when you write data to it
* and then decrease slowly over time as the data is consumed.
*
* The latency of an INPUT stream will decrease abruptly when you read data from it
* and then increase slowly over time as more data arrives.
*
* The latency of an OUTPUT stream is generally higher than the INPUT latency
* because an app generally tries to keep the OUTPUT buffer full and the INPUT buffer empty.
*
* Note that due to issues in Android before R, we recommend NOT calling
* this method from a data callback. See this tech note for more details.
* https://github.com/google/oboe/wiki/TechNote_ReleaseBuffer
*
* @return a ResultWithValue which has a result of Result::OK and a value containing the latency
* in milliseconds, or a result of Result::Error*.
*/
virtual ResultWithValue<double> calculateLatencyMillis() {
return ResultWithValue<double>(Result::ErrorUnimplemented);
}
/**
* Get the estimated time that the frame at `framePosition` entered or left the audio processing
* pipeline.
*
* This can be used to coordinate events and interactions with the external environment, and to
* estimate the latency of an audio stream. An example of usage can be found in the hello-oboe
* sample (search for "calculateCurrentOutputLatencyMillis").
*
* The time is based on the implementation's best effort, using whatever knowledge is available
* to the system, but cannot account for any delay unknown to the implementation.
*
* Note that due to issues in Android before R, we recommend NOT calling
* this method from a data callback. See this tech note for more details.
* https://github.com/google/oboe/wiki/TechNote_ReleaseBuffer
*
* @deprecated since 1.0, use AudioStream::getTimestamp(clockid_t clockId) instead, which
* returns ResultWithValue
* @param clockId the type of clock to use e.g. CLOCK_MONOTONIC
* @param framePosition the frame number to query
* @param timeNanoseconds an output parameter which will contain the presentation timestamp
*/
virtual Result getTimestamp(clockid_t /* clockId */,
int64_t* /* framePosition */,
int64_t* /* timeNanoseconds */) {
return Result::ErrorUnimplemented;
}
/**
* Get the estimated time that the frame at `framePosition` entered or left the audio processing
* pipeline.
*
* This can be used to coordinate events and interactions with the external environment, and to
* estimate the latency of an audio stream. An example of usage can be found in the hello-oboe
* sample (search for "calculateCurrentOutputLatencyMillis").
*
* The time is based on the implementation's best effort, using whatever knowledge is available
* to the system, but cannot account for any delay unknown to the implementation.
*
* Note that due to issues in Android before R, we recommend NOT calling
* this method from a data callback. See this tech note for more details.
* https://github.com/google/oboe/wiki/TechNote_ReleaseBuffer
*
* See
* @param clockId the type of clock to use e.g. CLOCK_MONOTONIC
* @return a FrameTimestamp containing the position and time at which a particular audio frame
* entered or left the audio processing pipeline, or an error if the operation failed.
*/
virtual ResultWithValue<FrameTimestamp> getTimestamp(clockid_t /* clockId */);
// ============== I/O ===========================
/**
* Write data from the supplied buffer into the stream. This method will block until the write
* is complete or it runs out of time.
*
* If `timeoutNanoseconds` is zero then this call will not wait.
*
* @param buffer The address of the first sample.
* @param numFrames Number of frames to write. Only complete frames will be written.
* @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion.
* @return a ResultWithValue which has a result of Result::OK and a value containing the number
* of frames actually written, or result of Result::Error*.
*/
virtual ResultWithValue<int32_t> write(const void* /* buffer */,
int32_t /* numFrames */,
int64_t /* timeoutNanoseconds */ ) {
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
}
/**
* Read data into the supplied buffer from the stream. This method will block until the read
* is complete or it runs out of time.
*
* If `timeoutNanoseconds` is zero then this call will not wait.
*
* @param buffer The address of the first sample.
* @param numFrames Number of frames to read. Only complete frames will be read.
* @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion.
* @return a ResultWithValue which has a result of Result::OK and a value containing the number
* of frames actually read, or result of Result::Error*.
*/
virtual ResultWithValue<int32_t> read(void* /* buffer */,
int32_t /* numFrames */,
int64_t /* timeoutNanoseconds */) {
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
}
/**
* Get the underlying audio API which the stream uses.
*
* @return the API that this stream uses.
*/
virtual AudioApi getAudioApi() const = 0;
/**
* Returns true if the underlying audio API is AAudio.
*
* @return true if this stream is implemented using the AAudio API.
*/
bool usesAAudio() const {
return getAudioApi() == AudioApi::AAudio;
}
/**
* Only for debugging. Do not use in production.
* If you need to call this method something is wrong.
* If you think you need it for production then please let us know
* so we can modify Oboe so that you don't need this.
*
* @return nullptr or a pointer to a stream from the system API
*/
virtual void *getUnderlyingStream() const {
return nullptr;
}
/**
* Update mFramesWritten.
* For internal use only.
*/
virtual void updateFramesWritten() = 0;
/**
* Update mFramesRead.
* For internal use only.
*/
virtual void updateFramesRead() = 0;
/*
* Swap old callback for new callback.
* This not atomic.
* This should only be used internally.
* @param dataCallback
* @return previous dataCallback
*/
AudioStreamDataCallback *swapDataCallback(AudioStreamDataCallback *dataCallback) {
AudioStreamDataCallback *previousCallback = mDataCallback;
mDataCallback = dataCallback;
return previousCallback;
}
/*
* Swap old callback for new callback.
* This not atomic.
* This should only be used internally.
* @param errorCallback
* @return previous errorCallback
*/
AudioStreamErrorCallback *swapErrorCallback(AudioStreamErrorCallback *errorCallback) {
AudioStreamErrorCallback *previousCallback = mErrorCallback;
mErrorCallback = errorCallback;
return previousCallback;
}
/**
* @return number of frames of data currently in the buffer
*/
ResultWithValue<int32_t> getAvailableFrames();
/**
* Wait until the stream has a minimum amount of data available in its buffer.
* This can be used with an EXCLUSIVE MMAP input stream to avoid reading data too close to
* the DSP write position, which may cause glitches.
*
* Starting with Oboe 1.7.1, the numFrames will be clipped internally against the
* BufferCapacity minus BurstSize. This is to prevent trying to wait for more frames
* than could possibly be available. In this case, the return value may be less than numFrames.
* Note that there may still be glitching if numFrames is too high.
*
* @param numFrames requested minimum frames available
* @param timeoutNanoseconds
* @return number of frames available, ErrorTimeout
*/
ResultWithValue<int32_t> waitForAvailableFrames(int32_t numFrames,
int64_t timeoutNanoseconds);
/**
* @return last result passed from an error callback
*/
virtual oboe::Result getLastErrorCallbackResult() const {
return mErrorCallbackResult;
}
int32_t getDelayBeforeCloseMillis() const {
return mDelayBeforeCloseMillis;
}
/**
* Set the time to sleep before closing the internal stream.
*
* Sometimes a callback can occur shortly after a stream has been stopped and
* even after a close! If the stream has been closed then the callback
* might access memory that has been freed, which could cause a crash.
* This seems to be more likely in Android P or earlier.
* But it can also occur in later versions. By sleeping, we give time for
* the callback threads to finish.
*
* Note that this only has an effect when OboeGlobals::areWorkaroundsEnabled() is true.
*
* @param delayBeforeCloseMillis time to sleep before close.
*/
void setDelayBeforeCloseMillis(int32_t delayBeforeCloseMillis) {
mDelayBeforeCloseMillis = delayBeforeCloseMillis;
}
/**
* Enable or disable a device specific CPU performance hint.
* Runtime benchmarks such as the callback duration may be used to
* speed up the CPU and improve real-time performance.
*
* Note that this feature is device specific and may not be implemented.
* Also the benefits may vary by device.
*
* The flag will be checked in the Oboe data callback. If it transitions from false to true
* then the PerformanceHint feature will be started.
* This only needs to be called once.
*
* You may want to enable this if you have a dynamically changing workload
* and you notice that you are getting underruns and glitches when your workload increases.
* This might happen, for example, if you suddenly go from playing one note to
* ten notes on a synthesizer.
*
* Try the CPU Load test in OboeTester if you would like to experiment with this interactively.
*
* On some devices, this may be implemented using the "ADPF" library.
*
* @param enabled true if you would like a performance boost
*/
void setPerformanceHintEnabled(bool enabled) {
mPerformanceHintEnabled = enabled;
}
/**
* This only tells you if the feature has been requested.
* It does not tell you if the PerformanceHint feature is implemented or active on the device.
*
* @return true if set using setPerformanceHintEnabled().
*/
bool isPerformanceHintEnabled() {
return mPerformanceHintEnabled;
}
protected:
/**
* This is used to detect more than one error callback from a stream.
* These were bugs in some versions of Android that caused multiple error callbacks.
* Internal bug b/63087953
*
* Calling this sets an atomic<bool> true and returns the previous value.
*
* @return false on first call, true on subsequent calls
*/
bool wasErrorCallbackCalled() {
return mErrorCallbackCalled.exchange(true);
}
/**
* Wait for a transition from one state to another.
* @return OK if the endingState was observed, or ErrorUnexpectedState
* if any state that was not the startingState or endingState was observed
* or ErrorTimeout.
*/
virtual Result waitForStateTransition(StreamState startingState,
StreamState endingState,
int64_t timeoutNanoseconds);
/**
* Override this to provide a default for when the application did not specify a callback.
*
* @param audioData
* @param numFrames
* @return result
*/
virtual DataCallbackResult onDefaultCallback(void* /* audioData */, int /* numFrames */) {
return DataCallbackResult::Stop;
}
/**
* Override this to provide your own behaviour for the audio callback
*
* @param audioData container array which audio frames will be written into or read from
* @param numFrames number of frames which were read/written
* @return the result of the callback: stop or continue
*
*/
DataCallbackResult fireDataCallback(void *audioData, int numFrames);
/**
* @return true if callbacks may be called
*/
bool isDataCallbackEnabled() {
return mDataCallbackEnabled;
}
/**
* This can be set false internally to prevent callbacks
* after DataCallbackResult::Stop has been returned.
*/
void setDataCallbackEnabled(bool enabled) {
mDataCallbackEnabled = enabled;
}
/**
* This should only be called as a stream is being opened.
* Otherwise we might override setDelayBeforeCloseMillis().
*/
void calculateDefaultDelayBeforeCloseMillis();
/**
* Try to avoid a race condition when closing.
*/
void sleepBeforeClose() {
if (mDelayBeforeCloseMillis > 0) {
usleep(mDelayBeforeCloseMillis * 1000);
}
}
/**
* This may be called internally at the beginning of a callback.
*/
virtual void beginPerformanceHintInCallback() {}
/**
* This may be called internally at the end of a callback.
* @param numFrames passed to the callback
*/
virtual void endPerformanceHintInCallback(int32_t numFrames) {}
/**
* This will be called when the stream is closed just in case performance hints were enabled.
*/
virtual void closePerformanceHint() {}
/*
* 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
*
* This is signed integer to match the counters in AAudio.
* At audio rates, the counter will overflow in about six million years.
*/
std::atomic<int64_t> mFramesWritten{};
/**
* Number of frames which have been read from the stream.
*
* This is signed integer to match the counters in AAudio.
* At audio rates, the counter will overflow in about six million years.
*/
std::atomic<int64_t> mFramesRead{};
std::mutex mLock; // for synchronizing start/stop/close
oboe::Result mErrorCallbackResult = oboe::Result::OK;
/**
* Number of frames which will be copied to/from the audio device in a single read/write
* operation
*/
int32_t mFramesPerBurst = kUnspecified;
// Time to sleep in order to prevent a race condition with a callback after a close().
// Two milliseconds may be enough but 10 msec is even safer.
static constexpr int kMinDelayBeforeCloseMillis = 10;
int32_t mDelayBeforeCloseMillis = kMinDelayBeforeCloseMillis;
private:
// Log the scheduler if it changes.
void checkScheduler();
int mPreviousScheduler = -1;
std::atomic<bool> mDataCallbackEnabled{false};
std::atomic<bool> mErrorCallbackCalled{false};
std::atomic<bool> mPerformanceHintEnabled{false}; // set only by app
};
/**
* 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 {
void operator()(AudioStream *audioStream) {
if (audioStream) {
audioStream->close();
}
delete audioStream;
}
};
} // namespace oboe
#endif /* OBOE_STREAM_H_ */

View File

@@ -0,0 +1,343 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_STREAM_BASE_H_
#define OBOE_STREAM_BASE_H_
#include <memory>
#include <string>
#include "oboe/AudioStreamCallback.h"
#include "oboe/Definitions.h"
namespace oboe {
/**
* Base class containing parameters for audio streams and builders.
**/
class AudioStreamBase {
public:
AudioStreamBase() {}
virtual ~AudioStreamBase() = default;
// This class only contains primitives so we can use default constructor and copy methods.
/**
* Default copy constructor
*/
AudioStreamBase(const AudioStreamBase&) = default;
/**
* Default assignment operator
*/
AudioStreamBase& operator=(const AudioStreamBase&) = default;
/**
* @return number of channels, for example 2 for stereo, or kUnspecified
*/
int32_t getChannelCount() const { return mChannelCount; }
/**
* @return Direction::Input or Direction::Output
*/
Direction getDirection() const { return mDirection; }
/**
* @return sample rate for the stream or kUnspecified
*/
int32_t getSampleRate() const { return mSampleRate; }
/**
* @deprecated use `getFramesPerDataCallback` instead.
*/
int32_t getFramesPerCallback() const { return getFramesPerDataCallback(); }
/**
* @return the number of frames in each data callback or kUnspecified.
*/
int32_t getFramesPerDataCallback() const { return mFramesPerCallback; }
/**
* @return the audio sample format (e.g. Float or I16)
*/
AudioFormat getFormat() const { return mFormat; }
/**
* Query the maximum number of frames that can be filled without blocking.
* If the stream has been closed the last known value will be returned.
*
* @return buffer size
*/
virtual int32_t getBufferSizeInFrames() { return mBufferSizeInFrames; }
/**
* @return capacityInFrames or kUnspecified
*/
virtual int32_t getBufferCapacityInFrames() const { return mBufferCapacityInFrames; }
/**
* @return the sharing mode of the stream.
*/
SharingMode getSharingMode() const { return mSharingMode; }
/**
* @return the performance mode of the stream.
*/
PerformanceMode getPerformanceMode() const { return mPerformanceMode; }
/**
* @return the device ID of the stream.
*/
int32_t getDeviceId() const { return mDeviceId; }
/**
* For internal use only.
* @return the data callback object for this stream, if set.
*/
AudioStreamDataCallback *getDataCallback() const {
return mDataCallback;
}
/**
* For internal use only.
* @return the error callback object for this stream, if set.
*/
AudioStreamErrorCallback *getErrorCallback() const {
return mErrorCallback;
}
/**
* @return true if a data callback was set for this stream
*/
bool isDataCallbackSpecified() const {
return mDataCallback != nullptr;
}
/**
* Note that if the app does not set an error callback then a
* default one may be provided.
* @return true if an error callback was set for this stream
*/
bool isErrorCallbackSpecified() const {
return mErrorCallback != nullptr;
}
/**
* @return the usage for this stream.
*/
Usage getUsage() const { return mUsage; }
/**
* @return the stream's content type.
*/
ContentType getContentType() const { return mContentType; }
/**
* @return the stream's input preset.
*/
InputPreset getInputPreset() const { return mInputPreset; }
/**
* @return the stream's session ID allocation strategy (None or Allocate).
*/
SessionId getSessionId() const { return mSessionId; }
/**
* @return whether the content of the stream is spatialized.
*/
bool isContentSpatialized() const { return mIsContentSpatialized; }
/**
* @return the spatialization behavior for the stream.
*/
SpatializationBehavior getSpatializationBehavior() const { return mSpatializationBehavior; }
/**
* Return the policy that determines whether the audio may or may not be captured
* by other apps or the system.
*
* See AudioStreamBuilder_setAllowedCapturePolicy().
*
* Added in API level 29 to AAudio.
*
* @return the allowed capture policy, for example AllowedCapturePolicy::All
*/
AllowedCapturePolicy getAllowedCapturePolicy() const { return mAllowedCapturePolicy; }
/**
* Return whether this input stream is marked as privacy sensitive.
*
* See AudioStreamBuilder_setPrivacySensitiveMode().
*
* Added in API level 30 to AAudio.
*
* @return PrivacySensitiveMode::Enabled if privacy sensitive,
* PrivacySensitiveMode::Disabled if not privacy sensitive, and
* PrivacySensitiveMode::Unspecified if API is not supported.
*/
PrivacySensitiveMode getPrivacySensitiveMode() const { return mPrivacySensitiveMode; }
/**
* @return true if Oboe can convert channel counts to achieve optimal results.
*/
bool isChannelConversionAllowed() const {
return mChannelConversionAllowed;
}
/**
* @return true if Oboe can convert data formats to achieve optimal results.
*/
bool isFormatConversionAllowed() const {
return mFormatConversionAllowed;
}
/**
* @return whether and how Oboe can convert sample rates to achieve optimal results.
*/
SampleRateConversionQuality getSampleRateConversionQuality() const {
return mSampleRateConversionQuality;
}
/**
* @return the stream's channel mask.
*/
ChannelMask getChannelMask() const {
return mChannelMask;
}
/**
* @return number of channels for the hardware, for example 2 for stereo, or kUnspecified.
*/
int32_t getHardwareChannelCount() const { return mHardwareChannelCount; }
/**
* @return hardware sample rate for the stream or kUnspecified
*/
int32_t getHardwareSampleRate() const { return mHardwareSampleRate; }
/**
* @return the audio sample format of the hardware (e.g. Float or I16)
*/
AudioFormat getHardwareFormat() const { return mHardwareFormat; }
protected:
/** The callback which will be fired when new data is ready to be read/written. **/
AudioStreamDataCallback *mDataCallback = nullptr;
std::shared_ptr<AudioStreamDataCallback> mSharedDataCallback;
/** The callback which will be fired when an error or a disconnect occurs. **/
AudioStreamErrorCallback *mErrorCallback = nullptr;
std::shared_ptr<AudioStreamErrorCallback> mSharedErrorCallback;
/** Number of audio frames which will be requested in each callback */
int32_t mFramesPerCallback = kUnspecified;
/** Stream channel count */
int32_t mChannelCount = kUnspecified;
/** Stream sample rate */
int32_t mSampleRate = kUnspecified;
/** Stream audio device ID */
int32_t mDeviceId = kUnspecified;
/** Stream buffer capacity specified as a number of audio frames */
int32_t mBufferCapacityInFrames = kUnspecified;
/** Stream buffer size specified as a number of audio frames */
int32_t mBufferSizeInFrames = kUnspecified;
/** Stream channel mask. Only active on Android 32+ */
ChannelMask mChannelMask = ChannelMask::Unspecified;
/** Stream sharing mode */
SharingMode mSharingMode = SharingMode::Shared;
/** Format of audio frames */
AudioFormat mFormat = AudioFormat::Unspecified;
/** Stream direction */
Direction mDirection = Direction::Output;
/** Stream performance mode */
PerformanceMode mPerformanceMode = PerformanceMode::None;
/** Stream usage. Only active on Android 28+ */
Usage mUsage = Usage::Media;
/** Stream content type. Only active on Android 28+ */
ContentType mContentType = ContentType::Music;
/** Stream input preset. Only active on Android 28+
* TODO InputPreset::Unspecified should be considered as a possible default alternative.
*/
InputPreset mInputPreset = InputPreset::VoiceRecognition;
/** Stream session ID allocation strategy. Only active on Android 28+ */
SessionId mSessionId = SessionId::None;
/** Allowed Capture Policy. Only active on Android 29+ */
AllowedCapturePolicy mAllowedCapturePolicy = AllowedCapturePolicy::Unspecified;
/** Privacy Sensitive Mode. Only active on Android 30+ */
PrivacySensitiveMode mPrivacySensitiveMode = PrivacySensitiveMode::Unspecified;
/** Control the name of the package creating the stream. Only active on Android 31+ */
std::string mPackageName;
/** Control the attribution tag of the context creating the stream. Only active on Android 31+ */
std::string mAttributionTag;
/** Whether the content is already spatialized. Only used on Android 32+ */
bool mIsContentSpatialized = false;
/** Spatialization Behavior. Only active on Android 32+ */
SpatializationBehavior mSpatializationBehavior = SpatializationBehavior::Unspecified;
/** Hardware channel count. Only specified on Android 34+ AAudio streams */
int32_t mHardwareChannelCount = kUnspecified;
/** Hardware sample rate. Only specified on Android 34+ AAudio streams */
int32_t mHardwareSampleRate = kUnspecified;
/** Hardware format. Only specified on Android 34+ AAudio streams */
AudioFormat mHardwareFormat = AudioFormat::Unspecified;
// Control whether Oboe can convert channel counts to achieve optimal results.
bool mChannelConversionAllowed = false;
// Control whether Oboe can convert data formats to achieve optimal results.
bool mFormatConversionAllowed = false;
// Control whether and how Oboe can convert sample rates to achieve optimal results.
SampleRateConversionQuality mSampleRateConversionQuality = SampleRateConversionQuality::None;
/** Validate stream parameters that might not be checked in lower layers */
virtual Result isValidConfig() {
switch (mFormat) {
case AudioFormat::Unspecified:
case AudioFormat::I16:
case AudioFormat::Float:
case AudioFormat::I24:
case AudioFormat::I32:
case AudioFormat::IEC61937:
break;
default:
return Result::ErrorInvalidFormat;
}
switch (mSampleRateConversionQuality) {
case SampleRateConversionQuality::None:
case SampleRateConversionQuality::Fastest:
case SampleRateConversionQuality::Low:
case SampleRateConversionQuality::Medium:
case SampleRateConversionQuality::High:
case SampleRateConversionQuality::Best:
return Result::OK;
default:
return Result::ErrorIllegalArgument;
}
}
};
} // namespace oboe
#endif /* OBOE_STREAM_BASE_H_ */

View File

@@ -0,0 +1,678 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_STREAM_BUILDER_H_
#define OBOE_STREAM_BUILDER_H_
#include "oboe/Definitions.h"
#include "oboe/AudioStreamBase.h"
#include "oboe/Utilities.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.
*/
class AudioStreamBuilder : public AudioStreamBase {
public:
AudioStreamBuilder() : AudioStreamBase() {}
AudioStreamBuilder(const AudioStreamBase &audioStreamBase): AudioStreamBase(audioStreamBase) {}
/**
* Request a specific number of channels.
*
* Default is kUnspecified. If the value is unspecified then
* the application should query for the actual value after the stream is opened.
*
* As the channel count here may be different from the corresponding channel count of
* provided channel mask used in setChannelMask(). The last called will be respected
* if this function and setChannelMask() are called.
*/
AudioStreamBuilder *setChannelCount(int channelCount) {
mChannelCount = channelCount;
mChannelMask = ChannelMask::Unspecified;
return this;
}
/**
* Request a specific channel mask.
*
* Default is kUnspecified. If the value is unspecified then the application
* should query for the actual value after the stream is opened.
*
* As the corresponding channel count of provided channel mask here may be different
* from the channel count used in setChannelCount(). The last called will be respected
* if this function and setChannelCount() are called.
*
* As the setChannelMask API is available on Android 32+, this call will only take effects
* on Android 32+.
*/
AudioStreamBuilder *setChannelMask(ChannelMask channelMask) {
mChannelMask = channelMask;
mChannelCount = getChannelCountFromChannelMask(channelMask);
return this;
}
/**
* Request the direction for a stream. The default is Direction::Output.
*
* @param direction Direction::Output or Direction::Input
*/
AudioStreamBuilder *setDirection(Direction direction) {
mDirection = direction;
return this;
}
/**
* Request a specific sample rate in Hz.
*
* Default is kUnspecified. If the value is unspecified then
* the application should query for the actual value after the stream is opened.
*
* Technically, this should be called the "frame rate" or "frames per second",
* because it refers to the number of complete frames transferred per second.
* But it is traditionally called "sample rate". Se we use that term.
*
*/
AudioStreamBuilder *setSampleRate(int32_t sampleRate) {
mSampleRate = sampleRate;
return this;
}
/**
* @deprecated use `setFramesPerDataCallback` instead.
*/
AudioStreamBuilder *setFramesPerCallback(int framesPerCallback) {
return setFramesPerDataCallback(framesPerCallback);
}
/**
* Request a specific number of frames for the data callback.
*
* Default is kUnspecified. If the value is unspecified then
* the actual number may vary from callback to callback.
*
* If an application can handle a varying number of frames then we recommend
* leaving this unspecified. This allow the underlying API to optimize
* the callbacks. But if your application is, for example, doing FFTs or other block
* oriented operations, then call this function to get the sizes you need.
*
* Calling setFramesPerDataCallback() does not guarantee anything about timing.
* This just collects the data into a the number of frames that your app requires.
* We encourage leaving this unspecified in most cases.
*
* If this number is larger than the burst size, some bursts will not receive a callback.
* If this number is smaller than the burst size, there may be multiple callbacks in a single
* burst.
*
* @param framesPerCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setFramesPerDataCallback(int framesPerCallback) {
mFramesPerCallback = framesPerCallback;
return this;
}
/**
* Request a sample data format, for example Format::Float.
*
* Default is Format::Unspecified. If the value is unspecified then
* the application should query for the actual value after the stream is opened.
*/
AudioStreamBuilder *setFormat(AudioFormat format) {
mFormat = format;
return this;
}
/**
* Set the requested buffer capacity in frames.
* BufferCapacityInFrames is the maximum possible BufferSizeInFrames.
*
* The final stream capacity may differ. For AAudio it should be at least this big.
* For OpenSL ES, it could be smaller.
*
* Default is kUnspecified.
*
* @param bufferCapacityInFrames the desired buffer capacity in frames or kUnspecified
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setBufferCapacityInFrames(int32_t bufferCapacityInFrames) {
mBufferCapacityInFrames = bufferCapacityInFrames;
return this;
}
/**
* Get the audio API which will be requested when opening the stream. No guarantees that this is
* the API which will actually be used. Query the stream itself to find out the API which is
* being used.
*
* If you do not specify the API, then AAudio will be used if isAAudioRecommended()
* returns true. Otherwise OpenSL ES will be used.
*
* @return the requested audio API
*/
AudioApi getAudioApi() const { return mAudioApi; }
/**
* If you leave this unspecified then Oboe will choose the best API
* for the device and SDK version at runtime.
*
* This should almost always be left unspecified, except for debugging purposes.
* Specifying AAudio will force Oboe to use AAudio on 8.0, which is extremely risky.
* Specifying OpenSLES should mainly be used to test legacy performance/functionality.
*
* If the caller requests AAudio and it is supported then AAudio will be used.
*
* @param audioApi Must be AudioApi::Unspecified, AudioApi::OpenSLES or AudioApi::AAudio.
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setAudioApi(AudioApi audioApi) {
mAudioApi = audioApi;
return this;
}
/**
* Is the AAudio API supported on this device?
*
* AAudio was introduced in the Oreo 8.0 release.
*
* @return true if supported
*/
static bool isAAudioSupported();
/**
* Is the AAudio API recommended this device?
*
* AAudio may be supported but not recommended because of version specific issues.
* AAudio is not recommended for Android 8.0 or earlier versions.
*
* @return true if recommended
*/
static bool isAAudioRecommended();
/**
* Request a mode for sharing the device.
* The requested sharing mode may not be available.
* So the application should query for the actual mode after the stream is opened.
*
* @param sharingMode SharingMode::Shared or SharingMode::Exclusive
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setSharingMode(SharingMode sharingMode) {
mSharingMode = sharingMode;
return this;
}
/**
* Request a performance level for the stream.
* This will determine the latency, the power consumption, and the level of
* protection from glitches.
*
* @param performanceMode for example, PerformanceMode::LowLatency
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setPerformanceMode(PerformanceMode performanceMode) {
mPerformanceMode = performanceMode;
return this;
}
/**
* Set the intended use case for an output stream.
*
* The system will use this information to optimize the behavior of the stream.
* This could, for example, affect how volume and focus is handled for the stream.
* The usage is ignored for input streams.
*
* The default, if you do not call this function, is Usage::Media.
*
* Added in API level 28.
*
* @param usage the desired usage, eg. Usage::Game
*/
AudioStreamBuilder *setUsage(Usage usage) {
mUsage = usage;
return this;
}
/**
* Set the type of audio data that an output stream will carry.
*
* The system will use this information to optimize the behavior of the stream.
* This could, for example, affect whether a stream is paused when a notification occurs.
* The contentType is ignored for input streams.
*
* The default, if you do not call this function, is ContentType::Music.
*
* Added in API level 28.
*
* @param contentType the type of audio data, eg. ContentType::Speech
*/
AudioStreamBuilder *setContentType(ContentType contentType) {
mContentType = contentType;
return this;
}
/**
* Set the input (capture) preset for the stream.
*
* The system will use this information to optimize the behavior of the stream.
* This could, for example, affect which microphones are used and how the
* recorded data is processed.
*
* The default, if you do not call this function, is InputPreset::VoiceRecognition.
* That is because VoiceRecognition is the preset with the lowest latency
* on many platforms.
*
* Added in API level 28.
*
* @param inputPreset the desired configuration for recording
*/
AudioStreamBuilder *setInputPreset(InputPreset inputPreset) {
mInputPreset = inputPreset;
return this;
}
/** Set the requested session ID.
*
* The session ID can be used to associate a stream with effects processors.
* The effects are controlled using the Android AudioEffect Java API.
*
* The default, if you do not call this function, is SessionId::None.
*
* If set to SessionId::Allocate then a session ID will be allocated
* when the stream is opened.
*
* The allocated session ID can be obtained by calling AudioStream::getSessionId()
* and then used with this function when opening another stream.
* This allows effects to be shared between streams.
*
* Session IDs from Oboe can be used the Android Java APIs and vice versa.
* So a session ID from an Oboe stream can be passed to Java
* and effects applied using the Java AudioEffect API.
*
* Allocated session IDs will always be positive and nonzero.
*
* Added in API level 28.
*
* @param sessionId an allocated sessionID or SessionId::Allocate
*/
AudioStreamBuilder *setSessionId(SessionId sessionId) {
mSessionId = sessionId;
return this;
}
/**
* Request a stream to a specific audio input/output device given an audio device ID.
*
* In most cases, the primary device will be the appropriate device to use, and the
* deviceId can be left kUnspecified.
*
* The ID could be obtained from the Java AudioManager.
* AudioManager.getDevices() returns an array of AudioDeviceInfo,
* which contains a getId() method. That ID can be passed to this function.
*
* It is possible that you may not get the device that you requested.
* So if it is important to you, you should call
* stream->getDeviceId() after the stream is opened to
* verify the actual ID.
*
* Note that when using OpenSL ES, this will be ignored and the created
* stream will have deviceId kUnspecified.
*
* @param deviceId device identifier or kUnspecified
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setDeviceId(int32_t deviceId) {
mDeviceId = deviceId;
return this;
}
/**
* Specify whether this stream audio may or may not be captured by other apps or the system.
*
* The default is AllowedCapturePolicy::Unspecified which maps to AAUDIO_ALLOW_CAPTURE_BY_ALL.
*
* Note that an application can also set its global policy, in which case the most restrictive
* policy is always applied. See android.media.AudioAttributes.setAllowedCapturePolicy.
*
* Added in API level 29 to AAudio.
*
* @param inputPreset the desired level of opt-out from being captured.
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setAllowedCapturePolicy(AllowedCapturePolicy allowedCapturePolicy) {
mAllowedCapturePolicy = allowedCapturePolicy;
return this;
}
/** Indicates whether this input stream must be marked as privacy sensitive or not.
*
* When PrivacySensitiveMode::Enabled, this input stream is privacy sensitive and any
* concurrent capture is not permitted.
*
* This is off (PrivacySensitiveMode::Disabled) by default except when the input preset is
* InputPreset::VoiceRecognition or InputPreset::Camcorder
*
* Always takes precedence over default from input preset when set explicitly.
*
* Only relevant if the stream direction is Direction::Input and AAudio is used.
*
* Added in API level 30 to AAudio.
*
* @param privacySensitive PrivacySensitiveMode::Enabled if capture from this stream must be
* marked as privacy sensitive, PrivacySensitiveMode::Disabled if stream should be marked as
* not sensitive.
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setPrivacySensitiveMode(PrivacySensitiveMode privacySensitiveMode) {
mPrivacySensitiveMode = privacySensitiveMode;
return this;
}
/**
* Specifies whether the audio data of this output stream has already been processed for spatialization.
*
* If the stream has been processed for spatialization, setting this to true will prevent issues such as
* double-processing on platforms that will spatialize audio data.
*
* This is false by default.
*
* Available since API level 32.
*
* @param isContentSpatialized whether the content is already spatialized
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setIsContentSpatialized(bool isContentSpatialized) {
mIsContentSpatialized = isContentSpatialized;
return this;
}
/**
* Sets the behavior affecting whether spatialization will be used.
*
* The AAudio system will use this information to select whether the stream will go through a
* spatializer effect or not when the effect is supported and enabled.
*
* This is SpatializationBehavior::Never by default.
*
* Available since API level 32.
*
* @param spatializationBehavior the desired spatialization behavior
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setSpatializationBehavior(SpatializationBehavior spatializationBehavior) {
mSpatializationBehavior = spatializationBehavior;
return this;
}
/**
* Specifies an object to handle data related callbacks from the underlying API.
*
* <strong>Important: See AudioStreamCallback for restrictions on what may be called
* from the callback methods.</strong>
*
* We pass a shared_ptr so that the sharedDataCallback object cannot be deleted
* before the stream is deleted.
*
* @param sharedDataCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setDataCallback(std::shared_ptr<AudioStreamDataCallback> sharedDataCallback) {
// Use this raw pointer in the rest of the code to retain backwards compatibility.
mDataCallback = sharedDataCallback.get();
// Hold a shared_ptr to protect the raw pointer for the lifetime of the stream.
mSharedDataCallback = sharedDataCallback;
return this;
}
/**
* Pass a raw pointer to a data callback. This is not recommended because the dataCallback
* object might get deleted by the app while it is being used.
*
* @deprecated Call setDataCallback(std::shared_ptr<AudioStreamDataCallback>) instead.
* @param dataCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setDataCallback(AudioStreamDataCallback *dataCallback) {
mDataCallback = dataCallback;
mSharedDataCallback = nullptr;
return this;
}
/**
* Specifies an object to handle error related callbacks from the underlying API.
* This can occur when a stream is disconnected because a headset is plugged in or unplugged.
* It can also occur if the audio service fails or if an exclusive stream is stolen by
* another stream.
*
* <strong>Important: See AudioStreamCallback for restrictions on what may be called
* from the callback methods.</strong>
*
* <strong>When an error callback occurs, the associated stream must be stopped and closed
* in a separate thread.</strong>
*
* We pass a shared_ptr so that the errorCallback object cannot be deleted before the stream is deleted.
* If the stream was created using a shared_ptr then the stream cannot be deleted before the
* error callback has finished running.
*
* @param sharedErrorCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setErrorCallback(std::shared_ptr<AudioStreamErrorCallback> sharedErrorCallback) {
// Use this raw pointer in the rest of the code to retain backwards compatibility.
mErrorCallback = sharedErrorCallback.get();
// Hold a shared_ptr to protect the raw pointer for the lifetime of the stream.
mSharedErrorCallback = sharedErrorCallback;
return this;
}
/**
* Pass a raw pointer to an error callback. This is not recommended because the errorCallback
* object might get deleted by the app while it is being used.
*
* @deprecated Call setErrorCallback(std::shared_ptr<AudioStreamErrorCallback>) instead.
* @param errorCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setErrorCallback(AudioStreamErrorCallback *errorCallback) {
mErrorCallback = errorCallback;
mSharedErrorCallback = nullptr;
return this;
}
/**
* Specifies an object to handle data or error related callbacks from the underlying API.
*
* This is the equivalent of calling both setDataCallback() and setErrorCallback().
*
* <strong>Important: See AudioStreamCallback for restrictions on what may be called
* from the callback methods.</strong>
*
* @deprecated Call setDataCallback(std::shared_ptr<AudioStreamDataCallback>) and
* setErrorCallback(std::shared_ptr<AudioStreamErrorCallback>) instead.
* @param streamCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setCallback(AudioStreamCallback *streamCallback) {
// Use the same callback object for both, dual inheritance.
mDataCallback = streamCallback;
mErrorCallback = streamCallback;
return this;
}
/**
* If true then Oboe might convert channel counts to achieve optimal results.
* On some versions of Android for example, stereo streams could not use a FAST track.
* So a mono stream might be used instead and duplicated to two channels.
* On some devices, mono streams might be broken, so a stereo stream might be opened
* and converted to mono.
*
* Default is false.
*/
AudioStreamBuilder *setChannelConversionAllowed(bool allowed) {
mChannelConversionAllowed = allowed;
return this;
}
/**
* If true then Oboe might convert data formats to achieve optimal results.
* On some versions of Android, for example, a float stream could not get a
* low latency data path. So an I16 stream might be opened and converted to float.
*
* Default is false.
*/
AudioStreamBuilder *setFormatConversionAllowed(bool allowed) {
mFormatConversionAllowed = allowed;
return this;
}
/**
* Specify the quality of the sample rate converter in Oboe.
*
* If set to None then Oboe will not do sample rate conversion. But the underlying APIs might
* still do sample rate conversion if you specify a sample rate.
* That can prevent you from getting a low latency stream.
*
* If you do the conversion in Oboe then you might still get a low latency stream.
*
* Default is SampleRateConversionQuality::None
*/
AudioStreamBuilder *setSampleRateConversionQuality(SampleRateConversionQuality quality) {
mSampleRateConversionQuality = quality;
return this;
}
/**
* Declare the name of the package creating the stream.
*
* This is usually {@code Context#getPackageName()}.
*
* The default, if you do not call this function, is a random package in the calling uid.
* The vast majority of apps have only one package per calling UID.
* If an invalid package name is set, input streams may not be given permission to
* record when started.
*
* The package name is usually the applicationId in your app's build.gradle file.
*
* Available since API level 31.
*
* @param packageName packageName of the calling app.
*/
AudioStreamBuilder *setPackageName(std::string packageName) {
mPackageName = packageName;
return this;
}
/**
* Declare the attribution tag of the context creating the stream.
*
* This is usually {@code Context#getAttributionTag()}.
*
* The default, if you do not call this function, is null.
*
* Available since API level 31.
*
* @param attributionTag attributionTag of the calling context.
*/
AudioStreamBuilder *setAttributionTag(std::string attributionTag) {
mAttributionTag = attributionTag;
return this;
}
/**
* @return true if AAudio will be used based on the current settings.
*/
bool willUseAAudio() const {
return (mAudioApi == AudioApi::AAudio && isAAudioSupported())
|| (mAudioApi == AudioApi::Unspecified && isAAudioRecommended());
}
/**
* Create and open a stream object based on the current settings.
*
* The caller owns the pointer to the AudioStream object
* and must delete it when finished.
*
* @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.
*
* The caller must create a unique ptr, and pass by reference so it can be
* modified to point to an opened stream. The caller owns the unique ptr,
* and it will be automatically closed and deleted when going out of scope.
*
* @deprecated Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.
* @param stream Reference to the ManagedStream (uniqueptr) used to keep track of stream
* @return OBOE_OK if successful or a negative error code.
*/
Result openManagedStream(ManagedStream &stream);
private:
/**
* Use this internally to implement opening with a shared_ptr.
*
* @param stream pointer to a variable to receive the stream address
* @return OBOE_OK if successful or a negative error code.
*/
Result openStreamInternal(AudioStream **streamPP);
/**
* @param other
* @return true if channels, format and sample rate match
*/
bool isCompatible(AudioStreamBase &other);
/**
* Create an AudioStream object. The AudioStream must be opened before use.
*
* The caller owns the pointer.
*
* @return pointer to an AudioStream object or nullptr.
*/
oboe::AudioStream *build();
AudioApi mAudioApi = AudioApi::Unspecified;
};
} // namespace oboe
#endif /* OBOE_STREAM_BUILDER_H_ */

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_STREAM_CALLBACK_H
#define OBOE_STREAM_CALLBACK_H
#include "oboe/Definitions.h"
namespace oboe {
class AudioStream;
/**
* AudioStreamDataCallback defines a callback interface for
* moving data to/from an audio stream using `onAudioReady`
* 2) being alerted when a stream has an error using `onError*` methods
*
* It is used with AudioStreamBuilder::setDataCallback().
*/
class AudioStreamDataCallback {
public:
virtual ~AudioStreamDataCallback() = default;
/**
* A buffer is ready for processing.
*
* For an output stream, this function should render and write numFrames of data
* in the stream's current data format to the audioData buffer.
*
* For an input stream, this function should read and process numFrames of data
* from the audioData buffer.
*
* The audio data is passed through the buffer. So do NOT call read() or
* write() on the stream that is making the callback.
*
* Note that numFrames can vary unless AudioStreamBuilder::setFramesPerCallback()
* is called. If AudioStreamBuilder::setFramesPerCallback() is NOT called then
* numFrames should always be <= AudioStream::getFramesPerBurst().
*
* Also note that this callback function should be considered a "real-time" function.
* It must not do anything that could cause an unbounded delay because that can cause the
* audio to glitch or pop.
*
* These are things the function should NOT do:
* <ul>
* <li>allocate memory using, for example, malloc() or new</li>
* <li>any file operations such as opening, closing, reading or writing</li>
* <li>any network operations such as streaming</li>
* <li>use any mutexes or other synchronization primitives</li>
* <li>sleep</li>
* <li>oboeStream->stop(), pause(), flush() or close()</li>
* <li>oboeStream->read()</li>
* <li>oboeStream->write()</li>
* </ul>
*
* The following are OK to call from the data callback:
* <ul>
* <li>oboeStream->get*()</li>
* <li>oboe::convertToText()</li>
* <li>oboeStream->setBufferSizeInFrames()</li>
* </ul>
*
* If you need to move data, eg. MIDI commands, in or out of the callback function then
* we recommend the use of non-blocking techniques such as an atomic FIFO.
*
* @param audioStream pointer to the associated stream
* @param audioData buffer containing input data or a place to put output data
* @param numFrames number of frames to be processed
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
*/
virtual DataCallbackResult onAudioReady(
AudioStream *audioStream,
void *audioData,
int32_t numFrames) = 0;
};
/**
* AudioStreamErrorCallback defines a callback interface for
* being alerted when a stream has an error or is disconnected
* using `onError*` methods.
*
* Note: This callback is only fired when an AudioStreamCallback is set.
* If you use AudioStream::write() you have to evaluate the return codes of
* AudioStream::write() to notice errors in the stream.
*
* It is used with AudioStreamBuilder::setErrorCallback().
*/
class AudioStreamErrorCallback {
public:
virtual ~AudioStreamErrorCallback() = default;
/**
* This will be called before other `onError` methods when an error occurs on a stream,
* such as when the stream is disconnected.
*
* It can be used to override and customize the normal error processing.
* Use of this method is considered an advanced technique.
* It might, for example, be used if an app want to use a high level lock when
* closing and reopening a stream.
* Or it might be used when an app want to signal a management thread that handles
* all of the stream state.
*
* If this method returns false it indicates that the stream has *not been stopped and closed
* by the application. In this case it will be stopped by Oboe in the following way:
* onErrorBeforeClose() will be called, then the stream will be closed and onErrorAfterClose()
* will be closed.
*
* If this method returns true it indicates that the stream *has* been stopped and closed
* by the application and Oboe will not do this.
* In that case, the app MUST stop() and close() the stream.
*
* This method will be called on a thread created by Oboe.
*
* @param audioStream pointer to the associated stream
* @param error
* @return true if the stream has been stopped and closed, false if not
*/
virtual bool onError(AudioStream* /* audioStream */, Result /* error */) {
return false;
}
/**
* This will be called when an error occurs on a stream,
* such as when the stream is disconnected,
* and if onError() returns false (indicating that the error has not already been handled).
*
* Note that this will be called on a thread created by Oboe.
*
* The underlying stream will already be stopped by Oboe but not yet closed.
* So the stream can be queried.
*
* Do not close or delete the stream in this method because it will be
* closed after this method returns.
*
* @param audioStream pointer to the associated stream
* @param error
*/
virtual void onErrorBeforeClose(AudioStream* /* audioStream */, Result /* error */) {}
/**
* This will be called when an error occurs on a stream,
* such as when the stream is disconnected,
* and if onError() returns false (indicating that the error has not already been handled).
*
* The underlying AAudio or OpenSL ES stream will already be stopped AND closed by Oboe.
* So the underlying stream cannot be referenced.
* But you can still query most parameters.
*
* This callback could be used to reopen a new stream on another device.
*
* @param audioStream pointer to the associated stream
* @param error
*/
virtual void onErrorAfterClose(AudioStream* /* audioStream */, Result /* error */) {}
};
/**
* AudioStreamCallback defines a callback interface for:
*
* 1) moving data to/from an audio stream using `onAudioReady`
* 2) being alerted when a stream has an error using `onError*` methods
*
* It is used with AudioStreamBuilder::setCallback().
*
* It combines the interfaces defined by AudioStreamDataCallback and AudioStreamErrorCallback.
* This was the original callback object. We now recommend using the individual interfaces
* and using setDataCallback() and setErrorCallback().
*
* @deprecated Use `AudioStreamDataCallback` and `AudioStreamErrorCallback` instead
*/
class AudioStreamCallback : public AudioStreamDataCallback,
public AudioStreamErrorCallback {
public:
virtual ~AudioStreamCallback() = default;
};
} // namespace oboe
#endif //OBOE_STREAM_CALLBACK_H

View File

@@ -0,0 +1,897 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_DEFINITIONS_H
#define OBOE_DEFINITIONS_H
#include <cstdint>
#include <type_traits>
// Oboe needs to be able to build on old NDKs so we use hard coded constants.
// The correctness of these constants is verified in "aaudio/AAudioLoader.cpp".
namespace oboe {
/**
* Represents any attribute, property or value which hasn't been specified.
*/
constexpr int32_t kUnspecified = 0;
// TODO: Investigate using std::chrono
/**
* The number of nanoseconds in a microsecond. 1,000.
*/
constexpr int64_t kNanosPerMicrosecond = 1000;
/**
* The number of nanoseconds in a millisecond. 1,000,000.
*/
constexpr int64_t kNanosPerMillisecond = kNanosPerMicrosecond * 1000;
/**
* The number of milliseconds in a second. 1,000.
*/
constexpr int64_t kMillisPerSecond = 1000;
/**
* The number of nanoseconds in a second. 1,000,000,000.
*/
constexpr int64_t kNanosPerSecond = kNanosPerMillisecond * kMillisPerSecond;
/**
* The state of the audio stream.
*/
enum class StreamState : int32_t { // aaudio_stream_state_t
Uninitialized = 0, // AAUDIO_STREAM_STATE_UNINITIALIZED,
Unknown = 1, // AAUDIO_STREAM_STATE_UNKNOWN,
Open = 2, // AAUDIO_STREAM_STATE_OPEN,
Starting = 3, // AAUDIO_STREAM_STATE_STARTING,
Started = 4, // AAUDIO_STREAM_STATE_STARTED,
Pausing = 5, // AAUDIO_STREAM_STATE_PAUSING,
Paused = 6, // AAUDIO_STREAM_STATE_PAUSED,
Flushing = 7, // AAUDIO_STREAM_STATE_FLUSHING,
Flushed = 8, // AAUDIO_STREAM_STATE_FLUSHED,
Stopping = 9, // AAUDIO_STREAM_STATE_STOPPING,
Stopped = 10, // AAUDIO_STREAM_STATE_STOPPED,
Closing = 11, // AAUDIO_STREAM_STATE_CLOSING,
Closed = 12, // AAUDIO_STREAM_STATE_CLOSED,
Disconnected = 13, // AAUDIO_STREAM_STATE_DISCONNECTED,
};
/**
* The direction of the stream.
*/
enum class Direction : int32_t { // aaudio_direction_t
/**
* Used for playback.
*/
Output = 0, // AAUDIO_DIRECTION_OUTPUT,
/**
* Used for recording.
*/
Input = 1, // AAUDIO_DIRECTION_INPUT,
};
/**
* The format of audio samples.
*/
enum class AudioFormat : int32_t { // aaudio_format_t
/**
* Invalid format.
*/
Invalid = -1, // AAUDIO_FORMAT_INVALID,
/**
* Unspecified format. Format will be decided by Oboe.
* When calling getHardwareFormat(), this will be returned if
* the API is not supported.
*/
Unspecified = 0, // AAUDIO_FORMAT_UNSPECIFIED,
/**
* Signed 16-bit integers.
*/
I16 = 1, // AAUDIO_FORMAT_PCM_I16,
/**
* Single precision floating point.
*
* This is the recommended format for most applications.
* But note that the use of Float may prevent the opening of
* a low-latency input path on OpenSL ES or Legacy AAudio streams.
*/
Float = 2, // AAUDIO_FORMAT_PCM_FLOAT,
/**
* Signed 24-bit integers, packed into 3 bytes.
*
* Note that the use of this format does not guarantee that
* the full precision will be provided. The underlying device may
* be using I16 format.
*
* Added in API 31 (S).
*/
I24 = 3, // AAUDIO_FORMAT_PCM_I24_PACKED
/**
* Signed 32-bit integers.
*
* Note that the use of this format does not guarantee that
* the full precision will be provided. The underlying device may
* be using I16 format.
*
* Added in API 31 (S).
*/
I32 = 4, // AAUDIO_FORMAT_PCM_I32
/**
* This format is used for compressed audio wrapped in IEC61937 for HDMI
* or S/PDIF passthrough.
*
* Unlike PCM playback, the Android framework is not able to do format
* conversion for IEC61937. In that case, when IEC61937 is requested, sampling
* rate and channel count or channel mask must be specified. Otherwise, it may
* fail when opening the stream. Apps are able to get the correct configuration
* for the playback by calling AudioManager#getDevices(int).
*
* Available since API 34 (U).
*/
IEC61937 = 5, // AAUDIO_FORMAT_IEC61937
};
/**
* The result of an audio callback.
*/
enum class DataCallbackResult : int32_t { // aaudio_data_callback_result_t
// Indicates to the caller that the callbacks should continue.
Continue = 0, // AAUDIO_CALLBACK_RESULT_CONTINUE,
// Indicates to the caller that the callbacks should stop immediately.
Stop = 1, // AAUDIO_CALLBACK_RESULT_STOP,
};
/**
* The result of an operation. All except the `OK` result indicates that an error occurred.
* The `Result` can be converted into a human readable string using `convertToText`.
*/
enum class Result : int32_t { // aaudio_result_t
OK = 0, // AAUDIO_OK
ErrorBase = -900, // AAUDIO_ERROR_BASE,
ErrorDisconnected = -899, // AAUDIO_ERROR_DISCONNECTED,
ErrorIllegalArgument = -898, // AAUDIO_ERROR_ILLEGAL_ARGUMENT,
ErrorInternal = -896, // AAUDIO_ERROR_INTERNAL,
ErrorInvalidState = -895, // AAUDIO_ERROR_INVALID_STATE,
ErrorInvalidHandle = -892, // AAUDIO_ERROR_INVALID_HANDLE,
ErrorUnimplemented = -890, // AAUDIO_ERROR_UNIMPLEMENTED,
ErrorUnavailable = -889, // AAUDIO_ERROR_UNAVAILABLE,
ErrorNoFreeHandles = -888, // AAUDIO_ERROR_NO_FREE_HANDLES,
ErrorNoMemory = -887, // AAUDIO_ERROR_NO_MEMORY,
ErrorNull = -886, // AAUDIO_ERROR_NULL,
ErrorTimeout = -885, // AAUDIO_ERROR_TIMEOUT,
ErrorWouldBlock = -884, // AAUDIO_ERROR_WOULD_BLOCK,
ErrorInvalidFormat = -883, // AAUDIO_ERROR_INVALID_FORMAT,
ErrorOutOfRange = -882, // AAUDIO_ERROR_OUT_OF_RANGE,
ErrorNoService = -881, // AAUDIO_ERROR_NO_SERVICE,
ErrorInvalidRate = -880, // AAUDIO_ERROR_INVALID_RATE,
// Reserved for future AAudio result types
Reserved1,
Reserved2,
Reserved3,
Reserved4,
Reserved5,
Reserved6,
Reserved7,
Reserved8,
Reserved9,
Reserved10,
ErrorClosed = -869,
};
/**
* The sharing mode of the audio stream.
*/
enum class SharingMode : int32_t { // aaudio_sharing_mode_t
/**
* This will be the only stream using a particular source or sink.
* This mode will provide the lowest possible latency.
* You should close EXCLUSIVE streams immediately when you are not using them.
*
* If you do not need the lowest possible latency then we recommend using Shared,
* which is the default.
*/
Exclusive = 0, // AAUDIO_SHARING_MODE_EXCLUSIVE,
/**
* Multiple applications can share the same device.
* The data from output streams will be mixed by the audio service.
* The data for input streams will be distributed by the audio service.
*
* This will have higher latency than the EXCLUSIVE mode.
*/
Shared = 1, // AAUDIO_SHARING_MODE_SHARED,
};
/**
* The performance mode of the audio stream.
*/
enum class PerformanceMode : int32_t { // aaudio_performance_mode_t
/**
* No particular performance needs. Default.
*/
None = 10, // AAUDIO_PERFORMANCE_MODE_NONE,
/**
* Extending battery life is most important.
*/
PowerSaving = 11, // AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
/**
* Reducing latency is most important.
*/
LowLatency = 12, // AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
};
/**
* The underlying audio API used by the audio stream.
*/
enum class AudioApi : int32_t {
/**
* Try to use AAudio. If not available then use OpenSL ES.
*/
Unspecified = kUnspecified,
/**
* Use OpenSL ES.
* Note that OpenSL ES is deprecated in Android 13, API 30 and above.
*/
OpenSLES,
/**
* Try to use AAudio. Fail if unavailable.
* AAudio was first supported in Android 8, API 26 and above.
* It is only recommended for API 27 and above.
*/
AAudio
};
/**
* Specifies the quality of the sample rate conversion performed by Oboe.
* Higher quality will require more CPU load.
* Higher quality conversion will probably be implemented using a sinc based resampler.
*/
enum class SampleRateConversionQuality : int32_t {
/**
* No conversion by Oboe. Underlying APIs may still do conversion.
*/
None,
/**
* Fastest conversion but may not sound great.
* This may be implemented using bilinear interpolation.
*/
Fastest,
/**
* Low quality conversion with 8 taps.
*/
Low,
/**
* Medium quality conversion with 16 taps.
*/
Medium,
/**
* High quality conversion with 32 taps.
*/
High,
/**
* Highest quality conversion, which may be expensive in terms of CPU.
*/
Best,
};
/**
* The Usage attribute expresses *why* you are playing a sound, what is this sound used for.
* This information is used by certain platforms or routing policies
* to make more refined volume or routing decisions.
*
* Note that these match the equivalent values in AudioAttributes in the Android Java API.
*
* This attribute only has an effect on Android API 28+.
*/
enum class Usage : int32_t { // aaudio_usage_t
/**
* Use this for streaming media, music performance, video, podcasts, etcetera.
*/
Media = 1, // AAUDIO_USAGE_MEDIA
/**
* Use this for voice over IP, telephony, etcetera.
*/
VoiceCommunication = 2, // AAUDIO_USAGE_VOICE_COMMUNICATION
/**
* Use this for sounds associated with telephony such as busy tones, DTMF, etcetera.
*/
VoiceCommunicationSignalling = 3, // AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING
/**
* Use this to demand the users attention.
*/
Alarm = 4, // AAUDIO_USAGE_ALARM
/**
* Use this for notifying the user when a message has arrived or some
* other background event has occured.
*/
Notification = 5, // AAUDIO_USAGE_NOTIFICATION
/**
* Use this when the phone rings.
*/
NotificationRingtone = 6, // AAUDIO_USAGE_NOTIFICATION_RINGTONE
/**
* Use this to attract the users attention when, for example, the battery is low.
*/
NotificationEvent = 10, // AAUDIO_USAGE_NOTIFICATION_EVENT
/**
* Use this for screen readers, etcetera.
*/
AssistanceAccessibility = 11, // AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY
/**
* Use this for driving or navigation directions.
*/
AssistanceNavigationGuidance = 12, // AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
/**
* Use this for user interface sounds, beeps, etcetera.
*/
AssistanceSonification = 13, // AAUDIO_USAGE_ASSISTANCE_SONIFICATION
/**
* Use this for game audio and sound effects.
*/
Game = 14, // AAUDIO_USAGE_GAME
/**
* Use this for audio responses to user queries, audio instructions or help utterances.
*/
Assistant = 16, // AAUDIO_USAGE_ASSISTANT
};
/**
* The ContentType attribute describes *what* you are playing.
* It expresses the general category of the content. This information is optional.
* But in case it is known (for instance {@link Movie} for a
* movie streaming service or {@link Speech} for
* an audio book application) this information might be used by the audio framework to
* enforce audio focus.
*
* Note that these match the equivalent values in AudioAttributes in the Android Java API.
*
* This attribute only has an effect on Android API 28+.
*/
enum ContentType : int32_t { // aaudio_content_type_t
/**
* Use this for spoken voice, audio books, etcetera.
*/
Speech = 1, // AAUDIO_CONTENT_TYPE_SPEECH
/**
* Use this for pre-recorded or live music.
*/
Music = 2, // AAUDIO_CONTENT_TYPE_MUSIC
/**
* Use this for a movie or video soundtrack.
*/
Movie = 3, // AAUDIO_CONTENT_TYPE_MOVIE
/**
* Use this for sound is designed to accompany a user action,
* such as a click or beep sound made when the user presses a button.
*/
Sonification = 4, // AAUDIO_CONTENT_TYPE_SONIFICATION
};
/**
* Defines the audio source.
* An audio source defines both a default physical source of audio signal, and a recording
* configuration.
*
* Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API.
*
* This attribute only has an effect on Android API 28+.
*/
enum InputPreset : int32_t { // aaudio_input_preset_t
/**
* Use this preset when other presets do not apply.
*/
Generic = 1, // AAUDIO_INPUT_PRESET_GENERIC
/**
* Use this preset when recording video.
*/
Camcorder = 5, // AAUDIO_INPUT_PRESET_CAMCORDER
/**
* Use this preset when doing speech recognition.
*/
VoiceRecognition = 6, // AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
/**
* Use this preset when doing telephony or voice messaging.
*/
VoiceCommunication = 7, // AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION
/**
* Use this preset to obtain an input with no effects.
* Note that this input will not have automatic gain control
* so the recorded volume may be very low.
*/
Unprocessed = 9, // AAUDIO_INPUT_PRESET_UNPROCESSED
/**
* Use this preset for capturing audio meant to be processed in real time
* and played back for live performance (e.g karaoke).
* The capture path will minimize latency and coupling with playback path.
*/
VoicePerformance = 10, // AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE
};
/**
* This attribute can be used to allocate a session ID to the audio stream.
*
* This attribute only has an effect on Android API 28+.
*/
enum SessionId {
/**
* Do not allocate a session ID.
* Effects cannot be used with this stream.
* Default.
*/
None = -1, // AAUDIO_SESSION_ID_NONE
/**
* Allocate a session ID that can be used to attach and control
* effects using the Java AudioEffects API.
* Note that the use of this flag may result in higher latency.
*
* Note that this matches the value of AudioManager.AUDIO_SESSION_ID_GENERATE.
*/
Allocate = 0, // AAUDIO_SESSION_ID_ALLOCATE
};
/**
* The channel count of the audio stream. The underlying type is `int32_t`.
* Use of this enum is convenient to avoid "magic"
* numbers when specifying the channel count.
*
* For example, you can write
* `builder.setChannelCount(ChannelCount::Stereo)`
* rather than `builder.setChannelCount(2)`
*
*/
enum ChannelCount : int32_t {
/**
* Audio channel count definition, use Mono or Stereo
*/
Unspecified = kUnspecified,
/**
* Use this for mono audio
*/
Mono = 1,
/**
* Use this for stereo audio.
*/
Stereo = 2,
};
/**
* The channel mask of the audio stream. The underlying type is `uint32_t`.
* Use of this enum is convenient.
*
* ChannelMask::Unspecified means this is not specified.
* The rest of the enums are channel position masks.
* Use the combinations of the channel position masks defined below instead of
* using those values directly.
*
* Channel masks are for input only, output only, or both input and output.
* These channel masks are different than those defined in AudioFormat.java.
* If an app gets a channel mask from Java API and wants to use it in Oboe,
* conversion should be done by the app.
*/
enum class ChannelMask : uint32_t { // aaudio_channel_mask_t
Unspecified = kUnspecified,
FrontLeft = 1 << 0,
FrontRight = 1 << 1,
FrontCenter = 1 << 2,
LowFrequency = 1 << 3,
BackLeft = 1 << 4,
BackRight = 1 << 5,
FrontLeftOfCenter = 1 << 6,
FrontRightOfCenter = 1 << 7,
BackCenter = 1 << 8,
SideLeft = 1 << 9,
SideRight = 1 << 10,
TopCenter = 1 << 11,
TopFrontLeft = 1 << 12,
TopFrontCenter = 1 << 13,
TopFrontRight = 1 << 14,
TopBackLeft = 1 << 15,
TopBackCenter = 1 << 16,
TopBackRight = 1 << 17,
TopSideLeft = 1 << 18,
TopSideRight = 1 << 19,
BottomFrontLeft = 1 << 20,
BottomFrontCenter = 1 << 21,
BottomFrontRight = 1 << 22,
LowFrequency2 = 1 << 23,
FrontWideLeft = 1 << 24,
FrontWideRight = 1 << 25,
/**
* Supported for Input and Output
*/
Mono = FrontLeft,
/**
* Supported for Input and Output
*/
Stereo = FrontLeft |
FrontRight,
/**
* Supported for only Output
*/
CM2Point1 = FrontLeft |
FrontRight |
LowFrequency,
/**
* Supported for only Output
*/
Tri = FrontLeft |
FrontRight |
FrontCenter,
/**
* Supported for only Output
*/
TriBack = FrontLeft |
FrontRight |
BackCenter,
/**
* Supported for only Output
*/
CM3Point1 = FrontLeft |
FrontRight |
FrontCenter |
LowFrequency,
/**
* Supported for Input and Output
*/
CM2Point0Point2 = FrontLeft |
FrontRight |
TopSideLeft |
TopSideRight,
/**
* Supported for Input and Output
*/
CM2Point1Point2 = CM2Point0Point2 |
LowFrequency,
/**
* Supported for Input and Output
*/
CM3Point0Point2 = FrontLeft |
FrontRight |
FrontCenter |
TopSideLeft |
TopSideRight,
/**
* Supported for Input and Output
*/
CM3Point1Point2 = CM3Point0Point2 |
LowFrequency,
/**
* Supported for only Output
*/
Quad = FrontLeft |
FrontRight |
BackLeft |
BackRight,
/**
* Supported for only Output
*/
QuadSide = FrontLeft |
FrontRight |
SideLeft |
SideRight,
/**
* Supported for only Output
*/
Surround = FrontLeft |
FrontRight |
FrontCenter |
BackCenter,
/**
* Supported for only Output
*/
Penta = Quad |
FrontCenter,
/**
* Supported for Input and Output. aka 5Point1Back
*/
CM5Point1 = FrontLeft |
FrontRight |
FrontCenter |
LowFrequency |
BackLeft |
BackRight,
/**
* Supported for only Output
*/
CM5Point1Side = FrontLeft |
FrontRight |
FrontCenter |
LowFrequency |
SideLeft |
SideRight,
/**
* Supported for only Output
*/
CM6Point1 = FrontLeft |
FrontRight |
FrontCenter |
LowFrequency |
BackLeft |
BackRight |
BackCenter,
/**
* Supported for only Output
*/
CM7Point1 = CM5Point1 |
SideLeft |
SideRight,
/**
* Supported for only Output
*/
CM5Point1Point2 = CM5Point1 |
TopSideLeft |
TopSideRight,
/**
* Supported for only Output
*/
CM5Point1Point4 = CM5Point1 |
TopFrontLeft |
TopFrontRight |
TopBackLeft |
TopBackRight,
/**
* Supported for only Output
*/
CM7Point1Point2 = CM7Point1 |
TopSideLeft |
TopSideRight,
/**
* Supported for only Output
*/
CM7Point1Point4 = CM7Point1 |
TopFrontLeft |
TopFrontRight |
TopBackLeft |
TopBackRight,
/**
* Supported for only Output
*/
CM9Point1Point4 = CM7Point1Point4 |
FrontWideLeft |
FrontWideRight,
/**
* Supported for only Output
*/
CM9Point1Point6 = CM9Point1Point4 |
TopSideLeft |
TopSideRight,
/**
* Supported for only Input
*/
FrontBack = FrontCenter |
BackCenter,
};
/**
* The spatialization behavior of the audio stream.
*/
enum class SpatializationBehavior : int32_t {
/**
* Constant indicating that the spatialization behavior is not specified.
*/
Unspecified = kUnspecified,
/**
* Constant indicating the audio content associated with these attributes will follow the
* default platform behavior with regards to which content will be spatialized or not.
*/
Auto = 1,
/**
* Constant indicating the audio content associated with these attributes should never
* be spatialized.
*/
Never = 2,
};
/**
* The PrivacySensitiveMode attribute determines whether an input stream can be shared
* with another privileged app, for example the Assistant.
*
* This allows to override the default behavior tied to the audio source (e.g
* InputPreset::VoiceCommunication is private by default but InputPreset::Unprocessed is not).
*/
enum class PrivacySensitiveMode : int32_t {
/**
* When not explicitly requested, set privacy sensitive mode according to input preset:
* communication and camcorder captures are considered privacy sensitive by default.
*/
Unspecified = kUnspecified,
/**
* Privacy sensitive mode disabled.
*/
Disabled = 1,
/**
* Privacy sensitive mode enabled.
*/
Enabled = 2,
};
/**
* Specifies whether audio may or may not be captured by other apps or the system for an
* output stream.
*
* Note that these match the equivalent values in AudioAttributes in the Android Java API.
*
* Added in API level 29 for AAudio.
*/
enum class AllowedCapturePolicy : int32_t {
/**
* When not explicitly requested, set privacy sensitive mode according to the Usage.
* This should behave similarly to setting AllowedCapturePolicy::All.
*/
Unspecified = kUnspecified,
/**
* Indicates that the audio may be captured by any app.
*
* For privacy, the following Usages can not be recorded: VoiceCommunication*,
* Notification*, Assistance* and Assistant.
*
* On Android Q, only Usage::Game and Usage::Media may be captured.
*
* See ALLOW_CAPTURE_BY_ALL in the AudioAttributes Java API.
*/
All = 1,
/**
* Indicates that the audio may only be captured by system apps.
*
* System apps can capture for many purposes like accessibility, user guidance...
* but have strong restriction. See ALLOW_CAPTURE_BY_SYSTEM in the AudioAttributes Java API
* for what the system apps can do with the capture audio.
*/
System = 2,
/**
* Indicates that the audio may not be recorded by any app, even if it is a system app.
*
* It is encouraged to use AllowedCapturePolicy::System instead of this value as system apps
* provide significant and useful features for the user (eg. accessibility).
* See ALLOW_CAPTURE_BY_NONE in the AudioAttributes Java API
*/
None = 3,
};
/**
* On API 16 to 26 OpenSL ES will be used. When using OpenSL ES the optimal values for sampleRate and
* framesPerBurst are not known by the native code.
* On API 17+ these values should be obtained from the AudioManager using this code:
*
* <pre><code>
* // Note that this technique only works for built-in speakers and headphones.
* AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
* String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
* int defaultSampleRate = Integer.parseInt(sampleRateStr);
* String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
* int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr);
* </code></pre>
*
* It can then be passed down to Oboe through JNI.
*
* AAudio will get the optimal framesPerBurst from the HAL and will ignore this value.
*/
class DefaultStreamValues {
public:
/** The default sample rate to use when opening new audio streams */
static int32_t SampleRate;
/** The default frames per burst to use when opening new audio streams */
static int32_t FramesPerBurst;
/** The default channel count to use when opening new audio streams */
static int32_t ChannelCount;
};
/**
* The time at which the frame at `position` was presented
*/
struct FrameTimestamp {
int64_t position; // in frames
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

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

View File

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

View File

@@ -0,0 +1,324 @@
/*
* 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 OBOE_FULL_DUPLEX_STREAM_
#define OBOE_FULL_DUPLEX_STREAM_
#include <cstdint>
#include "oboe/Definitions.h"
#include "oboe/AudioStream.h"
#include "oboe/AudioStreamCallback.h"
namespace oboe {
/**
* FullDuplexStream can be used to synchronize an input and output stream.
*
* For the builder of the output stream, call setDataCallback() with this object.
*
* When both streams are ready, onAudioReady() of the output stream will call onBothStreamsReady().
* Callers must override onBothStreamsReady().
*
* To ensure best results, open an output stream before the input stream.
* Call inputBuilder.setBufferCapacityInFrames(mOutputStream->getBufferCapacityInFrames() * 2).
* Also, call inputBuilder.setSampleRate(mOutputStream->getSampleRate()).
*
* Callers must call setInputStream() and setOutputStream().
* Call start() to start both streams and stop() to stop both streams.
* Caller is responsible for closing both streams.
*
* Callers should handle error callbacks with setErrorCallback() for the output stream.
* When an error callback occurs for the output stream, Oboe will stop and close the output stream.
* The caller is responsible for stopping and closing the input stream.
* The caller should also reopen and restart both streams when the error callback is ErrorDisconnected.
* See the LiveEffect sample as an example of this.
*
*/
class FullDuplexStream : public AudioStreamDataCallback {
public:
FullDuplexStream() {}
virtual ~FullDuplexStream() = default;
/**
* Sets the input stream. Calling this is mandatory.
*
* @param stream the output stream
*/
void setInputStream(AudioStream *stream) {
mInputStream = stream;
}
/**
* Gets the input stream
*
* @return the input stream
*/
AudioStream *getInputStream() {
return mInputStream;
}
/**
* Sets the output stream. Calling this is mandatory.
*
* @param stream the output stream
*/
void setOutputStream(AudioStream *stream) {
mOutputStream = stream;
}
/**
* Gets the output stream
*
* @return the output stream
*/
AudioStream *getOutputStream() {
return mOutputStream;
}
/**
* Attempts to start both streams. Please call setInputStream() and setOutputStream() before
* calling this function.
*
* @return result of the operation
*/
virtual Result start() {
mCountCallbacksToDrain = kNumCallbacksToDrain;
mCountInputBurstsCushion = mNumInputBurstsCushion;
mCountCallbacksToDiscard = kNumCallbacksToDiscard;
// Determine maximum size that could possibly be called.
int32_t bufferSize = getOutputStream()->getBufferCapacityInFrames()
* getOutputStream()->getChannelCount();
if (bufferSize > mBufferSize) {
mInputBuffer = std::make_unique<float[]>(bufferSize);
mBufferSize = bufferSize;
}
oboe::Result result = getInputStream()->requestStart();
if (result != oboe::Result::OK) {
return result;
}
return getOutputStream()->requestStart();
}
/**
* Stops both streams. Returns Result::OK if neither stream had an error during close.
*
* @return result of the operation
*/
virtual Result stop() {
Result outputResult = Result::OK;
Result inputResult = Result::OK;
if (getOutputStream()) {
outputResult = mOutputStream->requestStop();
}
if (getInputStream()) {
inputResult = mInputStream->requestStop();
}
if (outputResult != Result::OK) {
return outputResult;
} else {
return inputResult;
}
}
/**
* Reads input from the input stream. Callers should not call this directly as this is called
* in onAudioReady().
*
* @param numFrames
* @return result of the operation
*/
virtual ResultWithValue<int32_t> readInput(int32_t numFrames) {
return getInputStream()->read(mInputBuffer.get(), numFrames, 0 /* timeout */);
}
/**
* Called when data is available on both streams.
* Caller should override this method.
* numInputFrames and numOutputFrames may be zero.
*
* @param inputData buffer containing input data
* @param numInputFrames number of input frames
* @param outputData a place to put output data
* @param numOutputFrames number of output frames
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
*/
virtual DataCallbackResult onBothStreamsReady(
const void *inputData,
int numInputFrames,
void *outputData,
int numOutputFrames
) = 0;
/**
* Called when the output stream is ready to process audio.
* This in return calls onBothStreamsReady() when data is available on both streams.
* Callers should call this function when the output stream is ready.
* Callers must override onBothStreamsReady().
*
* @param audioStream pointer to the associated stream
* @param audioData a place to put output data
* @param numFrames number of frames to be processed
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
*
*/
DataCallbackResult onAudioReady(
AudioStream *audioStream,
void *audioData,
int numFrames) {
DataCallbackResult callbackResult = DataCallbackResult::Continue;
int32_t actualFramesRead = 0;
// Silence the output.
int32_t numBytes = numFrames * getOutputStream()->getBytesPerFrame();
memset(audioData, 0 /* value */, numBytes);
if (mCountCallbacksToDrain > 0) {
// Drain the input.
int32_t totalFramesRead = 0;
do {
ResultWithValue<int32_t> result = readInput(numFrames);
if (!result) {
// Ignore errors because input stream may not be started yet.
break;
}
actualFramesRead = result.value();
totalFramesRead += actualFramesRead;
} while (actualFramesRead > 0);
// Only counts if we actually got some data.
if (totalFramesRead > 0) {
mCountCallbacksToDrain--;
}
} else if (mCountInputBurstsCushion > 0) {
// Let the input fill up a bit so we are not so close to the write pointer.
mCountInputBurstsCushion--;
} else if (mCountCallbacksToDiscard > 0) {
mCountCallbacksToDiscard--;
// Ignore. Allow the input to reach to equilibrium with the output.
ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
if (!resultAvailable) {
callbackResult = DataCallbackResult::Stop;
} else {
int32_t framesAvailable = resultAvailable.value();
if (framesAvailable >= mMinimumFramesBeforeRead) {
ResultWithValue<int32_t> resultRead = readInput(numFrames);
if (!resultRead) {
callbackResult = DataCallbackResult::Stop;
}
}
}
} else {
int32_t framesRead = 0;
ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
if (!resultAvailable) {
callbackResult = DataCallbackResult::Stop;
} else {
int32_t framesAvailable = resultAvailable.value();
if (framesAvailable >= mMinimumFramesBeforeRead) {
// Read data into input buffer.
ResultWithValue<int32_t> resultRead = readInput(numFrames);
if (!resultRead) {
callbackResult = DataCallbackResult::Stop;
} else {
framesRead = resultRead.value();
}
}
}
if (callbackResult == DataCallbackResult::Continue) {
callbackResult = onBothStreamsReady(mInputBuffer.get(), framesRead,
audioData, numFrames);
}
}
if (callbackResult == DataCallbackResult::Stop) {
getInputStream()->requestStop();
}
return callbackResult;
}
/**
*
* This is a cushion between the DSP and the application processor cursors to prevent collisions.
* Typically 0 for latency measurements or 1 for glitch tests.
*
* @param numBursts number of bursts to leave in the input buffer as a cushion
*/
void setNumInputBurstsCushion(int32_t numBursts) {
mNumInputBurstsCushion = numBursts;
}
/**
* Get the number of bursts left in the input buffer as a cushion.
*
* @return number of bursts in the input buffer as a cushion
*/
int32_t getNumInputBurstsCushion() const {
return mNumInputBurstsCushion;
}
/**
* Minimum number of frames in the input stream buffer before calling readInput().
*
* @param numFrames number of bursts in the input buffer as a cushion
*/
void setMinimumFramesBeforeRead(int32_t numFrames) {
mMinimumFramesBeforeRead = numFrames;
}
/**
* Gets the minimum number of frames in the input stream buffer before calling readInput().
*
* @return minimum number of frames before reading
*/
int32_t getMinimumFramesBeforeRead() const {
return mMinimumFramesBeforeRead;
}
private:
// TODO add getters and setters
static constexpr int32_t kNumCallbacksToDrain = 20;
static constexpr int32_t kNumCallbacksToDiscard = 30;
// let input fill back up, usually 0 or 1
int32_t mNumInputBurstsCushion = 0;
int32_t mMinimumFramesBeforeRead = 0;
// We want to reach a state where the input buffer is empty and
// the output buffer is full.
// These are used in order.
// Drain several callback so that input is empty.
int32_t mCountCallbacksToDrain = kNumCallbacksToDrain;
// Let the input fill back up slightly so we don't run dry.
int32_t mCountInputBurstsCushion = mNumInputBurstsCushion;
// Discard some callbacks so the input and output reach equilibrium.
int32_t mCountCallbacksToDiscard = kNumCallbacksToDiscard;
AudioStream *mInputStream = nullptr;
AudioStream *mOutputStream = nullptr;
int32_t mBufferSize = 0;
std::unique_ptr<float[]> mInputBuffer;
};
} // namespace oboe
#endif //OBOE_FULL_DUPLEX_STREAM_

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_LATENCY_TUNER_
#define OBOE_LATENCY_TUNER_
#include <atomic>
#include <cstdint>
#include "oboe/Definitions.h"
#include "oboe/AudioStream.h"
namespace oboe {
/**
* LatencyTuner can be used to dynamically tune the latency of an output stream.
* It adjusts the stream's bufferSize by monitoring the number of underruns.
*
* This only affects the latency associated with the first level of buffering that is closest
* to the application. It does not affect low latency in the HAL, or touch latency in the UI.
*
* Call tune() right before returning from your data callback function if using callbacks.
* Call tune() right before calling write() if using blocking writes.
*
* If you want to see the ongoing results of this tuning process then call
* stream->getBufferSize() periodically.
*
*/
class LatencyTuner {
public:
/**
* Construct a new LatencyTuner object which will act on the given audio stream
*
* @param stream the stream who's latency will be tuned
*/
explicit LatencyTuner(AudioStream &stream);
/**
* Construct a new LatencyTuner object which will act on the given audio stream.
*
* @param stream the stream who's latency will be tuned
* @param the maximum buffer size which the tune() operation will set the buffer size to
*/
explicit LatencyTuner(AudioStream &stream, int32_t maximumBufferSize);
/**
* Adjust the bufferSizeInFrames to optimize latency.
* It will start with a low latency and then raise it if an underrun occurs.
*
* Latency tuning is only supported for AAudio.
*
* @return OK or negative error, ErrorUnimplemented for OpenSL ES
*/
Result tune();
/**
* This may be called from another thread. Then tune() will call reset(),
* which will lower the latency to the minimum and then allow it to rise back up
* if there are glitches.
*
* This is typically called in response to a user decision to minimize latency. In other words,
* call this from a button handler.
*/
void requestReset();
/**
* @return true if the audio stream's buffer size is at the maximum value. If no maximum value
* was specified when constructing the LatencyTuner then the value of
* stream->getBufferCapacityInFrames is used
*/
bool isAtMaximumBufferSize();
/**
* Set the minimum bufferSize in frames that is used when the tuner is reset.
* You may wish to call requestReset() after calling this.
* @param bufferSize
*/
void setMinimumBufferSize(int32_t bufferSize) {
mMinimumBufferSize = bufferSize;
}
int32_t getMinimumBufferSize() const {
return mMinimumBufferSize;
}
/**
* Set the amount the bufferSize will be incremented while tuning.
* By default, this will be one burst.
*
* Note that AAudio will quantize the buffer size to a multiple of the burstSize.
* So the final buffer sizes may not be a multiple of this increment.
*
* @param sizeIncrement
*/
void setBufferSizeIncrement(int32_t sizeIncrement) {
mBufferSizeIncrement = sizeIncrement;
}
int32_t getBufferSizeIncrement() const {
return mBufferSizeIncrement;
}
private:
/**
* Drop the latency down to the minimum and then let it rise back up.
* This is useful if a glitch caused the latency to increase and it hasn't gone back down.
*
* This should only be called in the same thread as tune().
*/
void reset();
enum class State {
Idle,
Active,
AtMax,
Unsupported
} ;
// arbitrary number of calls to wait before bumping up the latency
static constexpr int32_t kIdleCount = 8;
static constexpr int32_t kDefaultNumBursts = 2;
AudioStream &mStream;
State mState = State::Idle;
int32_t mMaxBufferSize = 0;
int32_t mPreviousXRuns = 0;
int32_t mIdleCountDown = 0;
int32_t mMinimumBufferSize;
int32_t mBufferSizeIncrement;
std::atomic<int32_t> mLatencyTriggerRequests{0}; // TODO user atomic requester from AAudio
std::atomic<int32_t> mLatencyTriggerResponses{0};
};
} // namespace oboe
#endif // OBOE_LATENCY_TUNER_

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_OBOE_H
#define OBOE_OBOE_H
/**
* \mainpage API reference
*
* All documentation is found in the <a href="namespaceoboe.html">oboe namespace section</a>
*
*/
#include "oboe/Definitions.h"
#include "oboe/ResultWithValue.h"
#include "oboe/LatencyTuner.h"
#include "oboe/AudioStream.h"
#include "oboe/AudioStreamBase.h"
#include "oboe/AudioStreamBuilder.h"
#include "oboe/Utilities.h"
#include "oboe/Version.h"
#include "oboe/StabilizedCallback.h"
#include "oboe/FifoBuffer.h"
#include "oboe/OboeExtensions.h"
#include "oboe/FullDuplexStream.h"
#endif //OBOE_OBOE_H

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 OBOE_EXTENSIONS_
#define OBOE_EXTENSIONS_
#include <stdint.h>
#include "oboe/Definitions.h"
#include "oboe/AudioStream.h"
namespace oboe {
/**
* The definitions below are only for testing.
* They are not recommended for use in an application.
* They may change or be removed at any time.
*/
class OboeExtensions {
public:
/**
* @returns true if the device supports AAudio MMAP
*/
static bool isMMapSupported();
/**
* @returns true if the AAudio MMAP data path can be selected
*/
static bool isMMapEnabled();
/**
* Controls whether the AAudio 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
*/
static int32_t setMMapEnabled(bool enabled);
/**
* @param oboeStream
* @return true if the AAudio MMAP data path is used on the stream
*/
static bool isMMapUsed(oboe::AudioStream *oboeStream);
};
} // namespace oboe
#endif // OBOE_LATENCY_TUNER_

View File

@@ -0,0 +1,155 @@
/*
* 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.
*/
#ifndef OBOE_RESULT_WITH_VALUE_H
#define OBOE_RESULT_WITH_VALUE_H
#include "oboe/Definitions.h"
#include <iostream>
#include <sstream>
namespace oboe {
/**
* A ResultWithValue can store both the result of an operation (either OK or an error) and a value.
*
* It has been designed for cases where the caller needs to know whether an operation succeeded and,
* if it did, a value which was obtained during the operation.
*
* For example, when reading from a stream the caller needs to know the result of the read operation
* and, if it was successful, how many frames were read. Note that ResultWithValue can be evaluated
* as a boolean so it's simple to check whether the result is OK.
*
* <code>
* ResultWithValue<int32_t> resultOfRead = myStream.read(&buffer, numFrames, timeoutNanoseconds);
*
* if (resultOfRead) {
* LOGD("Frames read: %d", resultOfRead.value());
* } else {
* LOGD("Error reading from stream: %s", resultOfRead.error());
* }
* </code>
*/
template <typename T>
class ResultWithValue {
public:
/**
* Construct a ResultWithValue containing an error result.
*
* @param error The error
*/
ResultWithValue(oboe::Result error)
: mValue{}
, mError(error) {}
/**
* Construct a ResultWithValue containing an OK result and a value.
*
* @param value the value to store
*/
explicit ResultWithValue(T value)
: mValue(value)
, mError(oboe::Result::OK) {}
/**
* Get the result.
*
* @return the result
*/
oboe::Result error() const {
return mError;
}
/**
* Get the value
* @return
*/
T value() const {
return mValue;
}
/**
* @return true if OK
*/
explicit operator bool() const { return mError == oboe::Result::OK; }
/**
* Quick way to check for an error.
*
* The caller could write something like this:
* <code>
* if (!result) { printf("Got error %s\n", convertToText(result.error())); }
* </code>
*
* @return true if an error occurred
*/
bool operator !() const { return mError != oboe::Result::OK; }
/**
* Implicitly convert to a Result. This enables easy comparison with Result values. Example:
*
* <code>
* ResultWithValue result = openStream();
* if (result == Result::ErrorNoMemory){ // tell user they're out of memory }
* </code>
*/
operator Result() const {
return mError;
}
/**
* Create a ResultWithValue from a number. If the number is positive the ResultWithValue will
* have a result of Result::OK and the value will contain the number. If the number is negative
* the result will be obtained from the negative number (numeric error codes can be found in
* AAudio.h) and the value will be null.
*
*/
static ResultWithValue<T> createBasedOnSign(T numericResult){
// Ensure that the type is either an integer or float
static_assert(std::is_arithmetic<T>::value,
"createBasedOnSign can only be called for numeric types (int or float)");
if (numericResult >= 0){
return ResultWithValue<T>(numericResult);
} else {
return ResultWithValue<T>(static_cast<Result>(numericResult));
}
}
private:
const T mValue;
const oboe::Result mError;
};
/**
* If the result is `OK` then return the value, otherwise return a human-readable error message.
*/
template <typename T>
std::ostream& operator<<(std::ostream &strm, const ResultWithValue<T> &result) {
if (!result) {
strm << convertToText(result.error());
} else {
strm << result.value();
}
return strm;
}
} // namespace oboe
#endif //OBOE_RESULT_WITH_VALUE_H

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_STABILIZEDCALLBACK_H
#define OBOE_STABILIZEDCALLBACK_H
#include <cstdint>
#include "oboe/AudioStream.h"
namespace oboe {
class StabilizedCallback : public AudioStreamCallback {
public:
explicit StabilizedCallback(AudioStreamCallback *callback);
DataCallbackResult
onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override;
void onErrorBeforeClose(AudioStream *oboeStream, Result error) override {
return mCallback->onErrorBeforeClose(oboeStream, error);
}
void onErrorAfterClose(AudioStream *oboeStream, Result error) override {
// Reset all fields now that the stream has been closed
mFrameCount = 0;
mEpochTimeNanos = 0;
mOpsPerNano = 1;
return mCallback->onErrorAfterClose(oboeStream, error);
}
private:
AudioStreamCallback *mCallback = nullptr;
int64_t mFrameCount = 0;
int64_t mEpochTimeNanos = 0;
double mOpsPerNano = 1;
void generateLoad(int64_t durationNanos);
};
/**
* cpu_relax is an architecture specific method of telling the CPU that you don't want it to
* do much work. asm volatile keeps the compiler from optimising these instructions out.
*/
#if defined(__i386__) || defined(__x86_64__)
#define cpu_relax() asm volatile("rep; nop" ::: "memory");
#elif defined(__arm__) || defined(__mips__) || defined(__riscv)
#define cpu_relax() asm volatile("":::"memory")
#elif defined(__aarch64__)
#define cpu_relax() asm volatile("yield" ::: "memory")
#else
#error "cpu_relax is not defined for this architecture"
#endif
}
#endif //OBOE_STABILIZEDCALLBACK_H

View File

@@ -0,0 +1,99 @@
/*
* 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_UTILITIES_H
#define OBOE_UTILITIES_H
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include "oboe/Definitions.h"
namespace oboe {
/**
* Convert an array of floats to an array of 16-bit integers.
*
* @param source the input array.
* @param destination the output array.
* @param numSamples the number of values to convert.
*/
void convertFloatToPcm16(const float *source, int16_t *destination, int32_t numSamples);
/**
* Convert an array of 16-bit integers to an array of floats.
*
* @param source the input array.
* @param destination the output array.
* @param numSamples the number of values to convert.
*/
void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples);
/**
* @return the size of a sample of the given format in bytes or 0 if format is invalid
*/
int32_t convertFormatToSizeInBytes(AudioFormat format);
/**
* The text is the ASCII symbol corresponding to the supplied Oboe enum value,
* or an English message saying the value is unrecognized.
* This is intended for developers to use when debugging.
* It is not for displaying to users.
*
* @param input object to convert from. @see common/Utilities.cpp for concrete implementations
* @return text representation of an Oboe enum value. There is no need to call free on this.
*/
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.
*
* For example, on Android, this would return 27 for Oreo 8.1.
* If the version number cannot be determined then this will return -1.
*
* @return version number or -1
*/
int getSdkVersion();
/**
* Returns whether a device is on a pre-release SDK that is at least the specified codename
* version.
*
* @param codename the code name to verify.
* @return boolean of whether the device is on a pre-release SDK and is at least the specified
* codename
*/
bool isAtLeastPreReleaseCodename(const std::string& codename);
int getChannelCountFromChannelMask(ChannelMask channelMask);
} // namespace oboe
#endif //OBOE_UTILITIES_H

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_VERSIONINFO_H
#define OBOE_VERSIONINFO_H
#include <cstdint>
/**
* A note on use of preprocessor defines:
*
* This is one of the few times when it's suitable to use preprocessor defines rather than constexpr
* Why? Because C++11 requires a lot of boilerplate code to convert integers into compile-time
* string literals. The preprocessor, despite it's lack of type checking, is more suited to the task
*
* See: https://stackoverflow.com/questions/6713420/c-convert-integer-to-string-at-compile-time/26824971#26824971
*
*/
// Type: 8-bit unsigned int. Min value: 0 Max value: 255. See below for description.
#define OBOE_VERSION_MAJOR 1
// Type: 8-bit unsigned int. Min value: 0 Max value: 255. See below for description.
#define OBOE_VERSION_MINOR 8
// Type: 16-bit unsigned int. Min value: 0 Max value: 65535. See below for description.
#define OBOE_VERSION_PATCH 1
#define OBOE_STRINGIFY(x) #x
#define OBOE_TOSTRING(x) OBOE_STRINGIFY(x)
// Type: String literal. See below for description.
#define OBOE_VERSION_TEXT \
OBOE_TOSTRING(OBOE_VERSION_MAJOR) "." \
OBOE_TOSTRING(OBOE_VERSION_MINOR) "." \
OBOE_TOSTRING(OBOE_VERSION_PATCH)
// Type: 32-bit unsigned int. See below for description.
#define OBOE_VERSION_NUMBER ((OBOE_VERSION_MAJOR << 24) | (OBOE_VERSION_MINOR << 16) | OBOE_VERSION_PATCH)
namespace oboe {
const char * getVersionText();
/**
* Oboe versioning object
*/
struct Version {
/**
* This is incremented when we make breaking API changes. Based loosely on https://semver.org/.
*/
static constexpr uint8_t Major = OBOE_VERSION_MAJOR;
/**
* This is incremented when we add backwards compatible functionality. Or set to zero when MAJOR is
* incremented.
*/
static constexpr uint8_t Minor = OBOE_VERSION_MINOR;
/**
* This is incremented when we make backwards compatible bug fixes. Or set to zero when MINOR is
* incremented.
*/
static constexpr uint16_t Patch = OBOE_VERSION_PATCH;
/**
* Version string in the form MAJOR.MINOR.PATCH.
*/
static constexpr const char * Text = OBOE_VERSION_TEXT;
/**
* Integer representation of the current Oboe library version. This will always increase when the
* version number changes so can be compared using integer comparison.
*/
static constexpr uint32_t Number = OBOE_VERSION_NUMBER;
};
} // namespace oboe
#endif //OBOE_VERSIONINFO_H

View File

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

View File

@@ -0,0 +1,506 @@
/*
* 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");
}
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");
}
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);
}
// 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
#endif // AAUDIO_AAUDIO_H
} // namespace oboe

View File

@@ -0,0 +1,299 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AAUDIO_LOADER_H_
#define OBOE_AAUDIO_LOADER_H_
#include <unistd.h>
#include "oboe/Definitions.h"
// If the NDK is before O then define this in your build
// so that AAudio.h will not be included.
#ifdef OBOE_NO_INCLUDE_AAUDIO
// Define missing types from AAudio.h
typedef int32_t aaudio_stream_state_t;
typedef int32_t aaudio_direction_t;
typedef int32_t aaudio_format_t;
typedef int32_t aaudio_data_callback_result_t;
typedef int32_t aaudio_result_t;
typedef int32_t aaudio_sharing_mode_t;
typedef int32_t aaudio_performance_mode_t;
typedef struct AAudioStreamStruct AAudioStream;
typedef struct AAudioStreamBuilderStruct AAudioStreamBuilder;
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames);
typedef void (*AAudioStream_errorCallback)(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
// These were defined in P
typedef int32_t aaudio_usage_t;
typedef int32_t aaudio_content_type_t;
typedef int32_t aaudio_input_preset_t;
typedef int32_t aaudio_session_id_t;
// There are a few definitions used by Oboe.
#define AAUDIO_OK static_cast<aaudio_result_t>(Result::OK)
#define AAUDIO_ERROR_TIMEOUT static_cast<aaudio_result_t>(Result::ErrorTimeout)
#define AAUDIO_STREAM_STATE_STARTING static_cast<aaudio_stream_state_t>(StreamState::Starting)
#define AAUDIO_STREAM_STATE_STARTED static_cast<aaudio_stream_state_t>(StreamState::Started)
#else
#include <aaudio/AAudio.h>
#endif
#ifndef __NDK_MAJOR__
#define __NDK_MAJOR__ 0
#endif
#if __NDK_MAJOR__ < 24
// Defined in SC_V2
typedef uint32_t aaudio_channel_mask_t;
typedef int32_t aaudio_spatialization_behavior_t;
#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
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
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 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 *);
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_I_PB builder_delete = nullptr;
signature_F_PS stream_getFormat = nullptr;
signature_I_PSPVIL stream_read = nullptr;
signature_I_PSCPVIL stream_write = nullptr;
signature_I_PSTPTL stream_waitForStateChange = nullptr;
signature_I_PSKPLPL stream_getTimestamp = nullptr;
signature_I_PS stream_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;
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);
void *mLibHandle = nullptr;
};
} // namespace oboe
#endif //OBOE_AAUDIO_LOADER_H_

View File

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

View File

@@ -0,0 +1,152 @@
/*
* 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;
}
protected:
static void internalErrorCallback(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
void *getUnderlyingStream() const override {
return mAAudioStream.load();
}
void updateFramesRead() override;
void updateFramesWritten() override;
void logUnsupportedAttributes();
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();
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,124 @@
/*
* 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 "AdpfWrapper.h"
#include "AudioClock.h"
#include "OboeDebug.h"
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;
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);
if (mHintSession != nullptr) {
gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
}
}
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(CLOCK_REALTIME);
}
}
void AdpfWrapper::onEndCallback(double durationScaler) {
if (isOpen()) {
int64_t endCallbackNanos = oboe::AudioClock::getNanoseconds(CLOCK_REALTIME);
int64_t actualDurationNanos = endCallbackNanos - mBeginCallbackNanos;
int64_t scaledDurationNanos = static_cast<int64_t>(actualDurationNanos * durationScaler);
reportActualDuration(scaledDurationNanos);
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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>
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);
private:
std::mutex mLock;
APerformanceHintSession* mHintSession = nullptr;
int64_t mBeginCallbackNanos = 0;
static bool sUseAlternativeHack;
};
#endif //SYNTHMARK_ADPF_WRAPPER_H

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/types.h>
#include <pthread.h>
#include <thread>
#include <oboe/AudioStream.h>
#include "OboeDebug.h"
#include "AudioClock.h"
#include <oboe/Utilities.h>
namespace oboe {
/*
* AudioStream
*/
AudioStream::AudioStream(const AudioStreamBuilder &builder)
: AudioStreamBase(builder) {
}
Result AudioStream::close() {
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;
return waitForStateTransition(StreamState::Starting,
StreamState::Started, timeoutNanoseconds);
}
Result AudioStream::pause(int64_t timeoutNanoseconds)
{
Result result = requestPause();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Pausing,
StreamState::Paused, timeoutNanoseconds);
}
Result AudioStream::flush(int64_t timeoutNanoseconds)
{
Result result = requestFlush();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Flushing,
StreamState::Flushed, timeoutNanoseconds);
}
Result AudioStream::stop(int64_t timeoutNanoseconds)
{
Result result = requestStop();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Stopping,
StreamState::Stopped, timeoutNanoseconds);
}
int32_t AudioStream::getBytesPerSample() const {
return convertFormatToSizeInBytes(mFormat);
}
int64_t AudioStream::getFramesRead() {
updateFramesRead();
return mFramesRead;
}
int64_t AudioStream::getFramesWritten() {
updateFramesWritten();
return mFramesWritten;
}
ResultWithValue<int32_t> AudioStream::getAvailableFrames() {
int64_t readCounter = getFramesRead();
if (readCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(readCounter);
int64_t writeCounter = getFramesWritten();
if (writeCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(writeCounter);
int32_t framesAvailable = writeCounter - readCounter;
return ResultWithValue<int32_t>(framesAvailable);
}
ResultWithValue<int32_t> AudioStream::waitForAvailableFrames(int32_t numFrames,
int64_t timeoutNanoseconds) {
if (numFrames == 0) return Result::OK;
if (numFrames < 0) return Result::ErrorOutOfRange;
// 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,230 @@
/*
* 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 %d", __func__, result);
return result;
}
LOGI("%s() %s -------- %s --------",
__func__, getDirection() == Direction::Input ? "INPUT" : "OUTPUT", getVersionText());
if (streamPP == nullptr) {
return Result::ErrorNull;
}
*streamPP = nullptr;
AudioStream *streamP = nullptr;
// Maybe make a FilterInputStream.
AudioStreamBuilder childBuilder(*this);
// Check need for conversion and modify childBuilder for optimal stream.
bool conversionNeeded = QuirksManager::getInstance().isConversionNeeded(*this, childBuilder);
// Do we need to make a child stream and convert.
if (conversionNeeded) {
AudioStream *tempStream;
result = childBuilder.openStream(&tempStream);
if (result != Result::OK) {
return result;
}
if (isCompatible(*tempStream)) {
// The child stream would work as the requested stream so we can just use it directly.
*streamPP = tempStream;
return result;
} else {
AudioStreamBuilder parentBuilder = *this;
// Build a stream that is as close as possible to the childStream.
if (getFormat() == oboe::AudioFormat::Unspecified) {
parentBuilder.setFormat(tempStream->getFormat());
}
if (getChannelCount() == oboe::Unspecified) {
parentBuilder.setChannelCount(tempStream->getChannelCount());
}
if (getSampleRate() == oboe::Unspecified) {
parentBuilder.setSampleRate(tempStream->getSampleRate());
}
if (getFramesPerDataCallback() == oboe::Unspecified) {
parentBuilder.setFramesPerCallback(tempStream->getFramesPerDataCallback());
}
// Use childStream in a FilterAudioStream.
LOGI("%s() create a FilterAudioStream for data conversion.", __func__);
FilterAudioStream *filterStream = new FilterAudioStream(parentBuilder, tempStream);
result = filterStream->configureFlowGraph();
if (result != Result::OK) {
filterStream->close();
delete filterStream;
// Just open streamP the old way.
} else {
streamP = static_cast<AudioStream *>(filterStream);
}
}
}
if (streamP == nullptr) {
streamP = build();
if (streamP == nullptr) {
return Result::ErrorNull;
}
}
// If MMAP has a problem in this case then disable it temporarily.
bool wasMMapOriginallyEnabled = AAudioExtensions::getInstance().isMMapEnabled();
bool wasMMapTemporarilyDisabled = false;
if (wasMMapOriginallyEnabled) {
bool isMMapSafe = QuirksManager::getInstance().isMMapSafe(childBuilder);
if (!isMMapSafe) {
AAudioExtensions::getInstance().setMMapEnabled(false);
wasMMapTemporarilyDisabled = true;
}
}
result = streamP->open();
if (wasMMapTemporarilyDisabled) {
AAudioExtensions::getInstance().setMMapEnabled(wasMMapOriginallyEnabled); // restore original
}
if (result == Result::OK) {
int32_t optimalBufferSize = -1;
// Use a reasonable default buffer size.
if (streamP->getDirection() == Direction::Input) {
// For input, small size does not improve latency because the stream is usually
// run close to empty. And a low size can result in XRuns so always use the maximum.
optimalBufferSize = streamP->getBufferCapacityInFrames();
} else if (streamP->getPerformanceMode() == PerformanceMode::LowLatency
&& streamP->getDirection() == Direction::Output) { // Output check is redundant.
optimalBufferSize = streamP->getFramesPerBurst() *
kBufferSizeInBurstsForLowLatencyStreams;
}
if (optimalBufferSize >= 0) {
auto setBufferResult = streamP->setBufferSizeInFrames(optimalBufferSize);
if (!setBufferResult) {
LOGW("Failed to setBufferSizeInFrames(%d). Error was %s",
optimalBufferSize,
convertToText(setBufferResult.error()));
}
}
*streamPP = streamP;
} else {
delete streamP;
}
return result;
}
Result AudioStreamBuilder::openManagedStream(oboe::ManagedStream &stream) {
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: %d to %d"
", rate: %d to %d, cbsize: %d to %d, qual = %d",
__func__,
sourceChannelCount, sinkChannelCount,
sourceFormat, sinkFormat,
sourceSampleRate, sinkSampleRate,
sourceFramesPerCallback, sinkFramesPerCallback,
sourceStream->getSampleRateConversionQuality());
// Source
// IF OUTPUT and using a callback then call back to the app using a SourceCaller.
// OR IF INPUT and NOT using a callback then read from the child stream using a SourceCaller.
bool isDataCallbackSpecified = sourceStream->isDataCallbackSpecified();
if ((isDataCallbackSpecified && isOutput)
|| (!isDataCallbackSpecified && isInput)) {
int32_t actualSourceFramesPerCallback = (sourceFramesPerCallback == kUnspecified)
? sourceStream->getFramesPerBurst()
: sourceFramesPerCallback;
switch (sourceFormat) {
case AudioFormat::Float:
mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I16:
mSourceCaller = std::make_unique<SourceI16Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I24:
mSourceCaller = std::make_unique<SourceI24Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I32:
mSourceCaller = std::make_unique<SourceI32Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
default:
LOGE("%s() Unsupported source caller format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
mSourceCaller->setStream(sourceStream);
lastOutput = &mSourceCaller->output;
} else {
// IF OUTPUT and NOT using a callback then write to the child stream using a BlockWriter.
// OR IF INPUT and using a callback then write to the app using a BlockWriter.
switch (sourceFormat) {
case AudioFormat::Float:
mSource = std::make_unique<SourceFloat>(sourceChannelCount);
break;
case AudioFormat::I16:
mSource = std::make_unique<SourceI16>(sourceChannelCount);
break;
case AudioFormat::I24:
mSource = std::make_unique<SourceI24>(sourceChannelCount);
break;
case AudioFormat::I32:
mSource = std::make_unique<SourceI32>(sourceChannelCount);
break;
default:
LOGE("%s() Unsupported source format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
if (isInput) {
int32_t actualSinkFramesPerCallback = (sinkFramesPerCallback == kUnspecified)
? sinkStream->getFramesPerBurst()
: sinkFramesPerCallback;
// The BlockWriter is after the Sink so use the SinkStream size.
mBlockWriter.open(actualSinkFramesPerCallback * sinkStream->getBytesPerFrame());
mAppBuffer = std::make_unique<uint8_t[]>(
kDefaultBufferSize * sinkStream->getBytesPerFrame());
}
lastOutput = &mSource->output;
}
// If we are going to reduce the number of channels then do it before the
// sample rate converter.
if (sourceChannelCount > sinkChannelCount) {
if (sinkChannelCount == 1) {
mMultiToMonoConverter = std::make_unique<MultiToMonoConverter>(sourceChannelCount);
lastOutput->connect(&mMultiToMonoConverter->input);
lastOutput = &mMultiToMonoConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sample Rate conversion
if (sourceSampleRate != sinkSampleRate) {
// Create a resampler to do the math.
mResampler.reset(MultiChannelResampler::make(lastOutput->getSamplesPerFrame(),
sourceSampleRate,
sinkSampleRate,
convertOboeSRQualityToMCR(
sourceStream->getSampleRateConversionQuality())));
// Make a flowgraph node that uses the resampler.
mRateConverter = std::make_unique<SampleRateConverter>(lastOutput->getSamplesPerFrame(),
*mResampler.get());
lastOutput->connect(&mRateConverter->input);
lastOutput = &mRateConverter->output;
}
// Expand the number of channels if required.
if (sourceChannelCount < sinkChannelCount) {
if (sourceChannelCount == 1) {
mMonoToMultiConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
lastOutput->connect(&mMonoToMultiConverter->input);
lastOutput = &mMonoToMultiConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sink
switch (sinkFormat) {
case AudioFormat::Float:
mSink = std::make_unique<SinkFloat>(sinkChannelCount);
break;
case AudioFormat::I16:
mSink = std::make_unique<SinkI16>(sinkChannelCount);
break;
case AudioFormat::I24:
mSink = std::make_unique<SinkI24>(sinkChannelCount);
break;
case AudioFormat::I32:
mSink = std::make_unique<SinkI32>(sinkChannelCount);
break;
default:
LOGE("%s() Unsupported sink format = %d", __func__, sinkFormat);
return Result::ErrorIllegalArgument;;
}
lastOutput->connect(&mSink->input);
return Result::OK;
}
int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames, int64_t timeoutNanos) {
if (mSourceCaller) {
mSourceCaller->setTimeoutNanos(timeoutNanos);
}
int32_t numRead = mSink->read(buffer, numFrames);
return numRead;
}
// This is similar to pushing data through the flowgraph.
int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) {
// Put the data from the input at the head of the flowgraph.
mSource->setData(inputBuffer, numFrames);
while (true) {
// Pull and read some data in app format into a small buffer.
int32_t framesRead = mSink->read(mAppBuffer.get(), flowgraph::kDefaultBufferSize);
if (framesRead <= 0) break;
// Write to a block adapter, which will call the destination whenever it has enough data.
int32_t bytesRead = mBlockWriter.write(mAppBuffer.get(),
framesRead * mFilterStream->getBytesPerFrame());
if (bytesRead < 0) return bytesRead; // TODO review
}
return numFrames;
}
int32_t DataConversionFlowGraph::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
int32_t numFrames = numBytes / mFilterStream->getBytesPerFrame();
mCallbackResult = mFilterStream->getDataCallback()->onAudioReady(mFilterStream, buffer, numFrames);
// TODO handle STOP from callback, process data remaining in the block adapter
return numBytes;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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,311 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <oboe/AudioStreamBuilder.h>
#include <oboe/Oboe.h>
#include "OboeDebug.h"
#include "QuirksManager.h"
using namespace oboe;
int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream,
int32_t requestedSize) {
if (!OboeGlobals::areWorkaroundsEnabled()) {
return requestedSize;
}
int bottomMargin = kDefaultBottomMarginInBursts;
int topMargin = kDefaultTopMarginInBursts;
if (isMMapUsed(stream)) {
if (stream.getSharingMode() == SharingMode::Exclusive) {
bottomMargin = getExclusiveBottomMarginInBursts();
topMargin = getExclusiveTopMarginInBursts();
}
} else {
bottomMargin = kLegacyBottomMarginInBursts;
}
int32_t burst = stream.getFramesPerBurst();
int32_t minSize = bottomMargin * burst;
int32_t adjustedSize = requestedSize;
if (adjustedSize < minSize ) {
adjustedSize = minSize;
} else {
int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst);
if (adjustedSize > maxSize ) {
adjustedSize = maxSize;
}
}
return adjustedSize;
}
bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
bool isSampleRateCompatible =
builder.getSampleRate() == oboe::Unspecified
|| builder.getSampleRate() == kCommonNativeRate
|| builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
return builder.getPerformanceMode() == PerformanceMode::LowLatency
&& isSampleRateCompatible
&& builder.getChannelCount() <= kChannelCountStereo;
}
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;
// 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;
}
// 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

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 "oboe/StabilizedCallback.h"
#include "common/AudioClock.h"
#include "common/Trace.h"
constexpr int32_t kLoadGenerationStepSizeNanos = 20000;
constexpr float kPercentageOfCallbackToUse = 0.8;
using namespace oboe;
StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback){
Trace::initialize();
}
/**
* An audio callback which attempts to do work for a fixed amount of time.
*
* @param oboeStream
* @param audioData
* @param numFrames
* @return
*/
DataCallbackResult
StabilizedCallback::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
int64_t startTimeNanos = AudioClock::getNanoseconds();
if (mFrameCount == 0){
mEpochTimeNanos = startTimeNanos;
}
int64_t durationSinceEpochNanos = startTimeNanos - mEpochTimeNanos;
// In an ideal world the callback start time will be exactly the same as the duration of the
// frames already read/written into the stream. In reality the callback can start early
// or late. By finding the delta we can calculate the target duration for our stabilized
// callback.
int64_t idealStartTimeNanos = (mFrameCount * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t lateStartNanos = durationSinceEpochNanos - idealStartTimeNanos;
if (lateStartNanos < 0){
// This was an early start which indicates that our previous epoch was a late callback.
// Update our epoch to this more accurate time.
mEpochTimeNanos = startTimeNanos;
mFrameCount = 0;
}
int64_t numFramesAsNanos = (numFrames * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t targetDurationNanos = static_cast<int64_t>(
(numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos);
Trace::beginSection("Actual load");
DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames);
Trace::endSection();
int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos;
int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos;
Trace::beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos);
generateLoad(stabilizingLoadDurationNanos);
Trace::endSection();
// Wraparound: At 48000 frames per second mFrameCount wraparound will occur after 6m years,
// significantly longer than the average lifetime of an Android phone.
mFrameCount += numFrames;
return result;
}
void StabilizedCallback::generateLoad(int64_t durationNanos) {
int64_t currentTimeNanos = AudioClock::getNanoseconds();
int64_t deadlineTimeNanos = currentTimeNanos + durationNanos;
// opsPerStep gives us an estimated number of operations which need to be run to fully utilize
// the CPU for a fixed amount of time (specified by kLoadGenerationStepSizeNanos).
// After each step the opsPerStep value is re-calculated based on the actual time taken to
// execute those operations.
auto opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos);
int64_t stepDurationNanos = 0;
int64_t previousTimeNanos = 0;
while (currentTimeNanos <= deadlineTimeNanos){
for (int i = 0; i < opsPerStep; i++) cpu_relax();
previousTimeNanos = currentTimeNanos;
currentTimeNanos = AudioClock::getNanoseconds();
stepDurationNanos = currentTimeNanos - previousTimeNanos;
// Calculate exponential moving average to smooth out values, this acts as a low pass filter.
// @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
static const float kFilterCoefficient = 0.1;
auto measuredOpsPerNano = (double) opsPerStep / stepDurationNanos;
mOpsPerNano = kFilterCoefficient * measuredOpsPerNano + (1.0 - kFilterCoefficient) * mOpsPerNano;
opsPerStep = (int) (mOpsPerNano * kLoadGenerationStepSizeNanos);
}
}

View File

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

31
vendor/oboe-sys/oboe/src/common/Trace.h vendored Normal file
View File

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

View File

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

View File

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

View File

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

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,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.
*/
#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();
mInputCursor = kInitialCallCount;
}
// Return true if there is a sample available.
bool SampleRateConverter::isInputAvailable() {
// If we have consumed all of the input data then go out and get some more.
if (mInputCursor >= mNumValidInputFrames) {
mInputCallCount++;
mNumValidInputFrames = input.pullData(mInputCallCount, input.getFramesPerBuffer());
mInputCursor = 0;
}
return (mInputCursor < mNumValidInputFrames);
}
const float *SampleRateConverter::getNextInputFrame() {
const float *inputBuffer = input.getBuffer();
return &inputBuffer[mInputCursor++ * input.getSamplesPerFrame()];
}
int32_t SampleRateConverter::onProcess(int32_t numFrames) {
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int framesLeft = numFrames;
while (framesLeft > 0) {
// Gather input samples as needed.
if(mResampler.isWriteNeeded()) {
if (isInputAvailable()) {
const float *frame = getNextInputFrame();
mResampler.writeNextFrame(frame);
} else {
break;
}
} else {
// Output frame is interpolated from input samples.
mResampler.readNextFrame(outputBuffer);
outputBuffer += channelCount;
framesLeft--;
}
}
return numFrames - framesLeft;
}

View File

@@ -0,0 +1,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 = 0;
};
} /* 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;
}

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;
}

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;
}

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

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