/* * 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 #include "oboe/Oboe.h" #include "opensles/AudioStreamBuffered.h" #include "common/AudioClock.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(); } } // TODO consider using std::make_unique if we require c++14 mFifoBuffer.reset(new FifoBuffer(getBytesPerFrame(), capacityFrames)); mBufferCapacityInFrames = capacityFrames; } } void AudioStreamBuffered::updateFramesWritten() { if (mFifoBuffer) { mFramesWritten = static_cast(mFifoBuffer->getWriteCounter()); } // or else it will get updated by processBufferCallback() } void AudioStreamBuffered::updateFramesRead() { if (mFifoBuffer) { mFramesRead = static_cast(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(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 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(Result::ErrorInternal); } if (getDirection() == Direction::Input && readBuffer == nullptr) { LOGE("AudioStreamBuffered::%s(): readBuffer is NULL", __func__); return ResultWithValue(Result::ErrorNull); } if (getDirection() == Direction::Output && writeBuffer == nullptr) { LOGE("AudioStreamBuffered::%s(): writeBuffer is NULL", __func__); return ResultWithValue(Result::ErrorNull); } if (numFrames < 0) { LOGE("AudioStreamBuffered::%s(): numFrames is negative", __func__); return ResultWithValue(Result::ErrorOutOfRange); } else if (numFrames == 0) { return ResultWithValue(numFrames); } if (timeoutNanoseconds < 0) { LOGE("AudioStreamBuffered::%s(): timeoutNanoseconds is negative", __func__); return ResultWithValue(Result::ErrorOutOfRange); } int32_t result = 0; uint8_t *readData = reinterpret_cast(readBuffer); const uint8_t *writeData = reinterpret_cast(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(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(static_cast(result)); } else { int32_t framesWritten = numFrames - framesLeft; return ResultWithValue(framesWritten); } } // Write to the FIFO so the callback can read from it. ResultWithValue AudioStreamBuffered::write(const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) { if (getState() == StreamState::Closed){ return ResultWithValue(Result::ErrorClosed); } if (getDirection() == Direction::Input) { return ResultWithValue(Result::ErrorUnavailable); // TODO review, better error code? } Result result = updateServiceFrameCounter(); if (result != Result::OK) return ResultWithValue(static_cast(result)); return transfer(nullptr, buffer, numFrames, timeoutNanoseconds); } // Read data from the FIFO that was written by the callback. ResultWithValue AudioStreamBuffered::read(void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) { if (getState() == StreamState::Closed){ return ResultWithValue(Result::ErrorClosed); } if (getDirection() == Direction::Output) { return ResultWithValue(Result::ErrorUnavailable); // TODO review, better error code? } Result result = updateServiceFrameCounter(); if (result != Result::OK) return ResultWithValue(static_cast(result)); return transfer(buffer, nullptr, numFrames, timeoutNanoseconds); } // Only supported when we are not using a callback. ResultWithValue AudioStreamBuffered::setBufferSizeInFrames(int32_t requestedFrames) { if (getState() == StreamState::Closed){ return ResultWithValue(Result::ErrorClosed); } if (!mFifoBuffer) { return ResultWithValue(Result::ErrorUnimplemented); } if (requestedFrames > mFifoBuffer->getBufferCapacityInFrames()) { requestedFrames = mFifoBuffer->getBufferCapacityInFrames(); } else if (requestedFrames < getFramesPerBurst()) { requestedFrames = getFramesPerBurst(); } mBufferSizeInFrames = requestedFrames; return ResultWithValue(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