aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/src/opensles/AudioStreamBuffered.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'externals/oboe/src/opensles/AudioStreamBuffered.cpp')
-rw-r--r--externals/oboe/src/opensles/AudioStreamBuffered.cpp285
1 files changed, 285 insertions, 0 deletions
diff --git a/externals/oboe/src/opensles/AudioStreamBuffered.cpp b/externals/oboe/src/opensles/AudioStreamBuffered.cpp
new file mode 100644
index 0000000000..d608f20681
--- /dev/null
+++ b/externals/oboe/src/opensles/AudioStreamBuffered.cpp
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+
+#include "oboe/Oboe.h"
+
+#include "common/OboeDebug.h"
+#include "oboe/AudioClock.h"
+#include "opensles/AudioStreamBuffered.h"
+
+namespace oboe {
+
+constexpr int kDefaultBurstsPerBuffer = 16; // arbitrary, allows dynamic latency tuning
+constexpr int kMinBurstsPerBuffer = 4; // arbitrary, allows dynamic latency tuning
+constexpr int kMinFramesPerBuffer = 48 * 32; // arbitrary
+
+/*
+ * AudioStream with a FifoBuffer
+ */
+AudioStreamBuffered::AudioStreamBuffered(const AudioStreamBuilder &builder)
+ : AudioStream(builder) {
+}
+
+void AudioStreamBuffered::allocateFifo() {
+ // If the caller does not provide a callback use our own internal
+ // callback that reads data from the FIFO.
+ if (usingFIFO()) {
+ // FIFO is configured with the same format and channels as the stream.
+ int32_t capacityFrames = getBufferCapacityInFrames();
+ if (capacityFrames == oboe::kUnspecified) {
+ capacityFrames = getFramesPerBurst() * kDefaultBurstsPerBuffer;
+ } else {
+ int32_t minFramesPerBufferByBursts = getFramesPerBurst() * kMinBurstsPerBuffer;
+ if (capacityFrames <= minFramesPerBufferByBursts) {
+ capacityFrames = minFramesPerBufferByBursts;
+ } else {
+ capacityFrames = std::max(kMinFramesPerBuffer, capacityFrames);
+ // round up to nearest burst
+ int32_t numBursts = (capacityFrames + getFramesPerBurst() - 1)
+ / getFramesPerBurst();
+ capacityFrames = numBursts * getFramesPerBurst();
+ }
+ }
+
+ mFifoBuffer = std::make_unique<FifoBuffer>(getBytesPerFrame(), capacityFrames);
+ mBufferCapacityInFrames = capacityFrames;
+ mBufferSizeInFrames = mBufferCapacityInFrames;
+ }
+}
+
+void AudioStreamBuffered::updateFramesWritten() {
+ if (mFifoBuffer) {
+ mFramesWritten = static_cast<int64_t>(mFifoBuffer->getWriteCounter());
+ } // or else it will get updated by processBufferCallback()
+}
+
+void AudioStreamBuffered::updateFramesRead() {
+ if (mFifoBuffer) {
+ mFramesRead = static_cast<int64_t>(mFifoBuffer->getReadCounter());
+ } // or else it will get updated by processBufferCallback()
+}
+
+// This is called by the OpenSL ES callback to read or write the back end of the FIFO.
+DataCallbackResult AudioStreamBuffered::onDefaultCallback(void *audioData, int numFrames) {
+ int32_t framesTransferred = 0;
+
+ if (getDirection() == oboe::Direction::Output) {
+ // Read from the FIFO and write to audioData, clear part of buffer if not enough data.
+ framesTransferred = mFifoBuffer->readNow(audioData, numFrames);
+ } else {
+ // Read from audioData and write to the FIFO
+ framesTransferred = mFifoBuffer->write(audioData, numFrames); // There is no writeNow()
+ }
+
+ if (framesTransferred < numFrames) {
+ LOGD("AudioStreamBuffered::%s(): xrun! framesTransferred = %d, numFrames = %d",
+ __func__, framesTransferred, numFrames);
+ // TODO If we do not allow FIFO to wrap then our timestamps will drift when there is an XRun!
+ incrementXRunCount();
+ }
+ markCallbackTime(static_cast<int32_t>(numFrames)); // so foreground knows how long to wait.
+ return DataCallbackResult::Continue;
+}
+
+void AudioStreamBuffered::markCallbackTime(int32_t numFrames) {
+ mLastBackgroundSize = numFrames;
+ mBackgroundRanAtNanoseconds = AudioClock::getNanoseconds();
+}
+
+int64_t AudioStreamBuffered::predictNextCallbackTime() {
+ if (mBackgroundRanAtNanoseconds == 0) {
+ return 0;
+ }
+ int64_t nanosPerBuffer = (kNanosPerSecond * mLastBackgroundSize) / getSampleRate();
+ const int64_t margin = 200 * kNanosPerMicrosecond; // arbitrary delay so we wake up just after
+ return mBackgroundRanAtNanoseconds + nanosPerBuffer + margin;
+}
+
+// Common code for read/write.
+// @return Result::OK with frames read/written, or Result::Error*
+ResultWithValue<int32_t> AudioStreamBuffered::transfer(
+ void *readBuffer,
+ const void *writeBuffer,
+ int32_t numFrames,
+ int64_t timeoutNanoseconds) {
+ // Validate arguments.
+ if (readBuffer != nullptr && writeBuffer != nullptr) {
+ LOGE("AudioStreamBuffered::%s(): both buffers are not NULL", __func__);
+ return ResultWithValue<int32_t>(Result::ErrorInternal);
+ }
+ if (getDirection() == Direction::Input && readBuffer == nullptr) {
+ LOGE("AudioStreamBuffered::%s(): readBuffer is NULL", __func__);
+ return ResultWithValue<int32_t>(Result::ErrorNull);
+ }
+ if (getDirection() == Direction::Output && writeBuffer == nullptr) {
+ LOGE("AudioStreamBuffered::%s(): writeBuffer is NULL", __func__);
+ return ResultWithValue<int32_t>(Result::ErrorNull);
+ }
+ if (numFrames < 0) {
+ LOGE("AudioStreamBuffered::%s(): numFrames is negative", __func__);
+ return ResultWithValue<int32_t>(Result::ErrorOutOfRange);
+ } else if (numFrames == 0) {
+ return ResultWithValue<int32_t>(numFrames);
+ }
+ if (timeoutNanoseconds < 0) {
+ LOGE("AudioStreamBuffered::%s(): timeoutNanoseconds is negative", __func__);
+ return ResultWithValue<int32_t>(Result::ErrorOutOfRange);
+ }
+
+ int32_t result = 0;
+ uint8_t *readData = reinterpret_cast<uint8_t *>(readBuffer);
+ const uint8_t *writeData = reinterpret_cast<const uint8_t *>(writeBuffer);
+ int32_t framesLeft = numFrames;
+ int64_t timeToQuit = 0;
+ bool repeat = true;
+
+ // Calculate when to timeout.
+ if (timeoutNanoseconds > 0) {
+ timeToQuit = AudioClock::getNanoseconds() + timeoutNanoseconds;
+ }
+
+ // Loop until we get the data, or we have an error, or we timeout.
+ do {
+ // read or write
+ if (getDirection() == Direction::Input) {
+ result = mFifoBuffer->read(readData, framesLeft);
+ if (result > 0) {
+ readData += mFifoBuffer->convertFramesToBytes(result);
+ framesLeft -= result;
+ }
+ } else {
+ // between zero and capacity
+ uint32_t fullFrames = mFifoBuffer->getFullFramesAvailable();
+ // Do not write above threshold size.
+ int32_t emptyFrames = getBufferSizeInFrames() - static_cast<int32_t>(fullFrames);
+ int32_t framesToWrite = std::max(0, std::min(framesLeft, emptyFrames));
+ result = mFifoBuffer->write(writeData, framesToWrite);
+ if (result > 0) {
+ writeData += mFifoBuffer->convertFramesToBytes(result);
+ framesLeft -= result;
+ }
+ }
+
+ // If we need more data then sleep and try again.
+ if (framesLeft > 0 && result >= 0 && timeoutNanoseconds > 0) {
+ int64_t timeNow = AudioClock::getNanoseconds();
+ if (timeNow >= timeToQuit) {
+ LOGE("AudioStreamBuffered::%s(): TIMEOUT", __func__);
+ repeat = false; // TIMEOUT
+ } else {
+ // Figure out how long to sleep.
+ int64_t sleepForNanos;
+ int64_t wakeTimeNanos = predictNextCallbackTime();
+ if (wakeTimeNanos <= 0) {
+ // No estimate available. Sleep for one burst.
+ sleepForNanos = (getFramesPerBurst() * kNanosPerSecond) / getSampleRate();
+ } else {
+ // Don't sleep past timeout.
+ if (wakeTimeNanos > timeToQuit) {
+ wakeTimeNanos = timeToQuit;
+ }
+ sleepForNanos = wakeTimeNanos - timeNow;
+ // Avoid rapid loop with no sleep.
+ const int64_t minSleepTime = kNanosPerMillisecond; // arbitrary
+ if (sleepForNanos < minSleepTime) {
+ sleepForNanos = minSleepTime;
+ }
+ }
+
+ AudioClock::sleepForNanos(sleepForNanos);
+ }
+
+ } else {
+ repeat = false;
+ }
+ } while(repeat);
+
+ if (result < 0) {
+ return ResultWithValue<int32_t>(static_cast<Result>(result));
+ } else {
+ int32_t framesWritten = numFrames - framesLeft;
+ return ResultWithValue<int32_t>(framesWritten);
+ }
+}
+
+// Write to the FIFO so the callback can read from it.
+ResultWithValue<int32_t> AudioStreamBuffered::write(const void *buffer,
+ int32_t numFrames,
+ int64_t timeoutNanoseconds) {
+ if (getState() == StreamState::Closed){
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+
+ if (getDirection() == Direction::Input) {
+ return ResultWithValue<int32_t>(Result::ErrorUnavailable); // TODO review, better error code?
+ }
+ Result result = updateServiceFrameCounter();
+ if (result != Result::OK) return ResultWithValue<int32_t>(static_cast<Result>(result));
+ return transfer(nullptr, buffer, numFrames, timeoutNanoseconds);
+}
+
+// Read data from the FIFO that was written by the callback.
+ResultWithValue<int32_t> AudioStreamBuffered::read(void *buffer,
+ int32_t numFrames,
+ int64_t timeoutNanoseconds) {
+ if (getState() == StreamState::Closed){
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+
+ if (getDirection() == Direction::Output) {
+ return ResultWithValue<int32_t>(Result::ErrorUnavailable); // TODO review, better error code?
+ }
+ Result result = updateServiceFrameCounter();
+ if (result != Result::OK) return ResultWithValue<int32_t>(static_cast<Result>(result));
+ return transfer(buffer, nullptr, numFrames, timeoutNanoseconds);
+}
+
+// Only supported when we are not using a callback.
+ResultWithValue<int32_t> AudioStreamBuffered::setBufferSizeInFrames(int32_t requestedFrames)
+{
+ if (getState() == StreamState::Closed){
+ return ResultWithValue<int32_t>(Result::ErrorClosed);
+ }
+
+ if (!mFifoBuffer) {
+ return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
+ }
+
+ if (requestedFrames > mFifoBuffer->getBufferCapacityInFrames()) {
+ requestedFrames = mFifoBuffer->getBufferCapacityInFrames();
+ } else if (requestedFrames < getFramesPerBurst()) {
+ requestedFrames = getFramesPerBurst();
+ }
+ mBufferSizeInFrames = requestedFrames;
+ return ResultWithValue<int32_t>(requestedFrames);
+}
+
+int32_t AudioStreamBuffered::getBufferCapacityInFrames() const {
+ if (mFifoBuffer) {
+ return mFifoBuffer->getBufferCapacityInFrames();
+ } else {
+ return AudioStream::getBufferCapacityInFrames();
+ }
+}
+
+bool AudioStreamBuffered::isXRunCountSupported() const {
+ // XRun count is only supported if we're using blocking I/O (not callbacks)
+ return (!isDataCallbackSpecified());
+}
+
+} // namespace oboe