From f2a7053f99b84aee9095f5da358ed56f6bf272ad Mon Sep 17 00:00:00 2001 From: Phil Burk Date: Wed, 9 May 2018 18:36:21 -0700 Subject: [PATCH 1/2] Implement getFramesRead for OpenSL ES OUTPUT and getFramesWritten for OpenSL ES INPUT. Query position in milliseconds from OpenSL ES. Use MonotonicCounter to build 64-bit frame counter from 32-bit internal counter. Fixes Issue #81 --- src/common/MonotonicCounter.h | 110 +++++++++++++++++++++ src/opensles/AudioInputStreamOpenSLES.cpp | 25 +++++ src/opensles/AudioInputStreamOpenSLES.h | 6 ++ src/opensles/AudioOutputStreamOpenSLES.cpp | 38 +++++++ src/opensles/AudioOutputStreamOpenSLES.h | 8 ++ src/opensles/AudioStreamBuffered.cpp | 5 +- src/opensles/AudioStreamBuffered.h | 2 + src/opensles/AudioStreamOpenSLES.cpp | 1 + src/opensles/AudioStreamOpenSLES.h | 7 +- 9 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 src/common/MonotonicCounter.h diff --git a/src/common/MonotonicCounter.h b/src/common/MonotonicCounter.h new file mode 100644 index 000000000..a0693d5a3 --- /dev/null +++ b/src/common/MonotonicCounter.h @@ -0,0 +1,110 @@ +/* + * 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 + +/** + * 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. + * + * @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 diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp index f80d2de40..6051e8611 100644 --- a/src/opensles/AudioInputStreamOpenSLES.cpp +++ b/src/opensles/AudioInputStreamOpenSLES.cpp @@ -190,6 +190,7 @@ Result AudioInputStreamOpenSLES::requestPause() { result = Result::ErrorInvalidState; // TODO review } else { setState(StreamState::Pausing); + mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when paused. } return result; } @@ -205,10 +206,17 @@ Result AudioInputStreamOpenSLES::requestStop() { result = Result::ErrorInvalidState; // TODO review } else { setState(StreamState::Stopping); + mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped. } return result; } +int64_t AudioInputStreamOpenSLES::getFramesWritten() const { + int64_t millis64 = mPositionMillis.get(); + int64_t framesRead = millis64 * getSampleRate() / kMillisPerSecond; + return framesRead; +} + Result AudioInputStreamOpenSLES::waitForStateChange(StreamState currentState, StreamState *nextState, int64_t timeoutNanoseconds) { @@ -218,3 +226,20 @@ Result AudioInputStreamOpenSLES::waitForStateChange(StreamState currentState, } return Result::ErrorUnimplemented; // TODO } + +Result AudioInputStreamOpenSLES::updateServiceFrameCounter() { + if (mRecordInterface == NULL) { + return Result::ErrorNull; + } + SLmillisecond msec = 0; + SLresult slResult = (*mRecordInterface)->GetPosition(mRecordInterface, &msec); + Result result = Result::OK; + if(SL_RESULT_SUCCESS != slResult) { + LOGD("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult)); + // set result based on SLresult + result = Result::ErrorInternal; + } else { + mPositionMillis.update32(msec); + } + return result; +} diff --git a/src/opensles/AudioInputStreamOpenSLES.h b/src/opensles/AudioInputStreamOpenSLES.h index 8fb2b8bd7..f27b8c4c6 100644 --- a/src/opensles/AudioInputStreamOpenSLES.h +++ b/src/opensles/AudioInputStreamOpenSLES.h @@ -51,6 +51,12 @@ class AudioInputStreamOpenSLES : public AudioStreamOpenSLES { int chanCountToChanMask(int chanCount); + int64_t getFramesWritten() const override; + +protected: + + Result updateServiceFrameCounter() override; + private: Result setRecordState(SLuint32 newState); diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp index 406632ba7..16fc61fd7 100644 --- a/src/opensles/AudioOutputStreamOpenSLES.cpp +++ b/src/opensles/AudioOutputStreamOpenSLES.cpp @@ -191,6 +191,11 @@ Result AudioOutputStreamOpenSLES::requestPause() { result = Result::ErrorInvalidState; // TODO review } else { setState(StreamState::Pausing); + // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused. + int64_t framesWritten = getFramesWritten(); + if (framesWritten >= 0) { + setFramesRead(framesWritten); + } } return result; } @@ -210,10 +215,26 @@ Result AudioOutputStreamOpenSLES::requestStop() { result = Result::ErrorInvalidState; // TODO review } else { setState(StreamState::Stopping); + mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped. + int64_t framesWritten = getFramesWritten(); + if (framesWritten >= 0) { + setFramesRead(framesWritten); + } } return result; } +void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) { + int64_t millisWritten = framesRead * kMillisPerSecond / getSampleRate(); + mPositionMillis.set(millisWritten); +} + +int64_t AudioOutputStreamOpenSLES::getFramesRead() const { + int64_t millis64 = mPositionMillis.get(); + int64_t framesRead = millis64 * getSampleRate() / kMillisPerSecond; + return framesRead; +} + Result AudioOutputStreamOpenSLES::waitForStateChange(StreamState currentState, StreamState *nextState, int64_t timeoutNanoseconds) { @@ -223,3 +244,20 @@ Result AudioOutputStreamOpenSLES::waitForStateChange(StreamState currentState, } return Result::ErrorUnimplemented; // TODO } + +Result AudioOutputStreamOpenSLES::updateServiceFrameCounter() { + if (mPlayInterface == NULL) { + return Result::ErrorNull; + } + SLmillisecond msec = 0; + SLresult slResult = (*mPlayInterface)->GetPosition(mPlayInterface, &msec); + Result result = Result::OK; + if(SL_RESULT_SUCCESS != slResult) { + LOGD("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult)); + // set result based on SLresult + result = Result::ErrorInternal; + } else { + mPositionMillis.update32(msec); + } + return result; +} diff --git a/src/opensles/AudioOutputStreamOpenSLES.h b/src/opensles/AudioOutputStreamOpenSLES.h index f8b83de9f..67cba5fa3 100644 --- a/src/opensles/AudioOutputStreamOpenSLES.h +++ b/src/opensles/AudioOutputStreamOpenSLES.h @@ -50,6 +50,14 @@ class AudioOutputStreamOpenSLES : public AudioStreamOpenSLES { int chanCountToChanMask(int chanCount); + int64_t getFramesRead() const override; + +protected: + + void setFramesRead(int64_t framesRead); + + Result updateServiceFrameCounter() override; + private: /** diff --git a/src/opensles/AudioStreamBuffered.cpp b/src/opensles/AudioStreamBuffered.cpp index 1cb721fa8..4ef2b6dc3 100644 --- a/src/opensles/AudioStreamBuffered.cpp +++ b/src/opensles/AudioStreamBuffered.cpp @@ -60,8 +60,7 @@ int64_t AudioStreamBuffered::getFramesRead() const { } } - - // This is called by the OpenSL ES callback to read or write the back end of the FIFO. +// 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; @@ -189,6 +188,7 @@ ErrorOrValue AudioStreamBuffered::write(const void *buffer, if (getDirection() == Direction::Input) { return ErrorOrValue(Result::ErrorUnavailable); // TODO review, better error code? } + updateServiceFrameCounter(); return transfer((void *) buffer, numFrames, timeoutNanoseconds); } @@ -199,6 +199,7 @@ ErrorOrValue AudioStreamBuffered::read(void *buffer, if (getDirection() == Direction::Output) { return ErrorOrValue(Result::ErrorUnavailable); // TODO review, better error code? } + updateServiceFrameCounter(); return transfer(buffer, numFrames, timeoutNanoseconds); } diff --git a/src/opensles/AudioStreamBuffered.h b/src/opensles/AudioStreamBuffered.h index f8ea2d743..00e3cf38c 100644 --- a/src/opensles/AudioStreamBuffered.h +++ b/src/opensles/AudioStreamBuffered.h @@ -66,6 +66,8 @@ class AudioStreamBuffered : public AudioStream { // If there is no callback then we need a FIFO between the App and OpenSL ES. bool usingFIFO() const { return getCallback() == nullptr; } + virtual Result updateServiceFrameCounter() { return Result::OK; }; + private: diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp index 0ab0265fd..f93ca1730 100644 --- a/src/opensles/AudioStreamOpenSLES.cpp +++ b/src/opensles/AudioStreamOpenSLES.cpp @@ -137,6 +137,7 @@ SLresult AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueIt LOGE("Oboe callback returned %d", result); return SL_RESULT_INTERNAL_ERROR; // TODO How should we stop OpenSL ES. } else { + updateServiceFrameCounter(); // Pass the data to OpenSLES. return enqueueCallbackBuffer(bq); } diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h index 22fd22349..4500f1f0a 100644 --- a/src/opensles/AudioStreamOpenSLES.h +++ b/src/opensles/AudioStreamOpenSLES.h @@ -21,8 +21,9 @@ #include #include "oboe/Oboe.h" -#include "AudioStreamBuffered.h" -#include "EngineOpenSLES.h" +#include "common/MonotonicCounter.h" +#include "opensles/AudioStreamBuffered.h" +#include "opensles/EngineOpenSLES.h" namespace oboe { @@ -96,6 +97,8 @@ class AudioStreamOpenSLES : public AudioStreamBuffered { int32_t mFramesPerBurst = 0; int32_t mBurstsPerBuffer = 2; // Double buffered StreamState mState = StreamState::Uninitialized; + + MonotonicCounter mPositionMillis; // for tracking OpenSL ES service position }; } // namespace oboe From b09eb33ce088755c564d403f94e13baec1981e54 Mon Sep 17 00:00:00 2001 From: Phil Burk Date: Mon, 21 May 2018 17:40:52 -0700 Subject: [PATCH 2/2] Add getFramesProcessedByServer. Minor cleanup. --- src/common/MonotonicCounter.h | 2 ++ src/opensles/AudioInputStreamOpenSLES.cpp | 4 +--- src/opensles/AudioOutputStreamOpenSLES.cpp | 4 +--- src/opensles/AudioStreamOpenSLES.cpp | 6 ++++++ src/opensles/AudioStreamOpenSLES.h | 2 ++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/common/MonotonicCounter.h b/src/common/MonotonicCounter.h index a0693d5a3..7d62c29e9 100644 --- a/src/common/MonotonicCounter.h +++ b/src/common/MonotonicCounter.h @@ -92,6 +92,8 @@ class MonotonicCounter { /** * 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) { diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp index 6051e8611..accc86ce6 100644 --- a/src/opensles/AudioInputStreamOpenSLES.cpp +++ b/src/opensles/AudioInputStreamOpenSLES.cpp @@ -212,9 +212,7 @@ Result AudioInputStreamOpenSLES::requestStop() { } int64_t AudioInputStreamOpenSLES::getFramesWritten() const { - int64_t millis64 = mPositionMillis.get(); - int64_t framesRead = millis64 * getSampleRate() / kMillisPerSecond; - return framesRead; + return getFramesProcessedByServer(); } Result AudioInputStreamOpenSLES::waitForStateChange(StreamState currentState, diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp index 16fc61fd7..48f5358b8 100644 --- a/src/opensles/AudioOutputStreamOpenSLES.cpp +++ b/src/opensles/AudioOutputStreamOpenSLES.cpp @@ -230,9 +230,7 @@ void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) { } int64_t AudioOutputStreamOpenSLES::getFramesRead() const { - int64_t millis64 = mPositionMillis.get(); - int64_t framesRead = millis64 * getSampleRate() / kMillisPerSecond; - return framesRead; + return getFramesProcessedByServer(); } Result AudioOutputStreamOpenSLES::waitForStateChange(StreamState currentState, diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp index f93ca1730..ba33a7b96 100644 --- a/src/opensles/AudioStreamOpenSLES.cpp +++ b/src/opensles/AudioStreamOpenSLES.cpp @@ -170,3 +170,9 @@ SLresult AudioStreamOpenSLES::registerBufferQueueCallback() { int32_t AudioStreamOpenSLES::getFramesPerBurst() { return mFramesPerBurst; } + +int64_t AudioStreamOpenSLES::getFramesProcessedByServer() const { + int64_t millis64 = mPositionMillis.get(); + int64_t framesRead = millis64 * getSampleRate() / kMillisPerSecond; + return framesRead; +} diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h index 4500f1f0a..f70ab9af7 100644 --- a/src/opensles/AudioStreamOpenSLES.h +++ b/src/opensles/AudioStreamOpenSLES.h @@ -88,6 +88,8 @@ class AudioStreamOpenSLES : public AudioStreamBuffered { mState = state; } + int64_t getFramesProcessedByServer() const; + // OpenSLES stuff SLObjectItf mObjectInterface = nullptr; SLAndroidSimpleBufferQueueItf mSimpleBufferQueueInterface = nullptr;