Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement getFramesRead for OpenSL ES OUTPUT #89

Merged
merged 2 commits into from
May 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions src/common/MonotonicCounter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2016 The Android Open Source Project
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2018

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code was written in 2016. I copied it from inside AAudio.

*
* 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;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe should be atomic.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Atomicity isn't for free--there are costs on synchronizing CPU caches. Also, in update32 we seem to update both 32-bit and 64-bit counters in one transaction. This will require making the whole pair to be atomic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an std::atomic way to do that or do you just mean to use a mutex?

Copy link
Collaborator

@mnaganov mnaganov May 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make a struct and put it into std::atomic. Depending on different conditions it may use a mutex internally, you can check that with is_lock_free function: http://en.cppreference.com/w/cpp/atomic/atomic/is_lock_free

For two counters, it should usually be lock-free, unless it's a 32-bit ARM CPU and the fields alignment is wrong.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mikhail and I talked. The caller is only updating the internal data from one thread at a time. So I will document that this is not thread-safe and require caller to guarantee synchronization.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

int32_t mCounter32 = 0;
};


#endif //COMMON_MONOTONIC_COUNTER_H
23 changes: 23 additions & 0 deletions src/opensles/AudioInputStreamOpenSLES.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -205,10 +206,15 @@ 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 {
return getFramesProcessedByServer();
}

Result AudioInputStreamOpenSLES::waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) {
Expand All @@ -218,3 +224,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;
}
6 changes: 6 additions & 0 deletions src/opensles/AudioInputStreamOpenSLES.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
36 changes: 36 additions & 0 deletions src/opensles/AudioOutputStreamOpenSLES.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -210,10 +215,24 @@ 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);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is supposed to prevent drift but does not seem to be working.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is no longer drifting.

}
}
return result;
}

void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) {
int64_t millisWritten = framesRead * kMillisPerSecond / getSampleRate();
mPositionMillis.set(millisWritten);
}

int64_t AudioOutputStreamOpenSLES::getFramesRead() const {
return getFramesProcessedByServer();
}

Result AudioOutputStreamOpenSLES::waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) {
Expand All @@ -223,3 +242,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;
}
8 changes: 8 additions & 0 deletions src/opensles/AudioOutputStreamOpenSLES.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:

/**
Expand Down
5 changes: 3 additions & 2 deletions src/opensles/AudioStreamBuffered.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -189,6 +188,7 @@ ErrorOrValue<int32_t> AudioStreamBuffered::write(const void *buffer,
if (getDirection() == Direction::Input) {
return ErrorOrValue<int32_t>(Result::ErrorUnavailable); // TODO review, better error code?
}
updateServiceFrameCounter();
return transfer((void *) buffer, numFrames, timeoutNanoseconds);
}

Expand All @@ -199,6 +199,7 @@ ErrorOrValue<int32_t> AudioStreamBuffered::read(void *buffer,
if (getDirection() == Direction::Output) {
return ErrorOrValue<int32_t>(Result::ErrorUnavailable); // TODO review, better error code?
}
updateServiceFrameCounter();
return transfer(buffer, numFrames, timeoutNanoseconds);
}

Expand Down
2 changes: 2 additions & 0 deletions src/opensles/AudioStreamBuffered.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:


Expand Down
7 changes: 7 additions & 0 deletions src/opensles/AudioStreamOpenSLES.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -169,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;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to framesProcessed

return framesRead;
}
9 changes: 7 additions & 2 deletions src/opensles/AudioStreamOpenSLES.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
#include <SLES/OpenSLES_Android.h>

#include "oboe/Oboe.h"
#include "AudioStreamBuffered.h"
#include "EngineOpenSLES.h"
#include "common/MonotonicCounter.h"
#include "opensles/AudioStreamBuffered.h"
#include "opensles/EngineOpenSLES.h"

namespace oboe {

Expand Down Expand Up @@ -87,6 +88,8 @@ class AudioStreamOpenSLES : public AudioStreamBuffered {
mState = state;
}

int64_t getFramesProcessedByServer() const;

// OpenSLES stuff
SLObjectItf mObjectInterface = nullptr;
SLAndroidSimpleBufferQueueItf mSimpleBufferQueueInterface = nullptr;
Expand All @@ -96,6 +99,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
Expand Down