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

oboe: add blocking read/write #55

Merged
merged 9 commits into from
Feb 14, 2018
Merged

oboe: add blocking read/write #55

merged 9 commits into from
Feb 14, 2018

Conversation

philburk
Copy link
Collaborator

@philburk philburk commented Feb 9, 2018

Use FIFO for OpenSL ES.
This will allow blocking read/write calls on a stream without using a callback.

Use FIFO for OpenSL ES.
@@ -25,23 +25,18 @@ namespace oboe {
* AudioStream with a FifoBuffer
*/
AudioStreamBuffered::AudioStreamBuffered(const AudioStreamBuilder &builder)
: AudioStream(builder)
, mFifoBuffer(nullptr)
Copy link
Collaborator

@mnaganov mnaganov Feb 9, 2018

Choose a reason for hiding this comment

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

Why not to change the field type to a smart pointer and forget about manual initialization / deletion:

std::unique_ptr<FifoBuffer> mFifoBuffer;

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

LOGD("AudioStreamBuffered(): new FifoBuffer");
// TODO: Fix memory leak here
LOGD("AudioStreamBuffered(): new FifoBuffer(bytesPerFrame=%d", getBytesPerFrame());
// FIFO is configured with the same format and channels as the stream.
mFifoBuffer = new FifoBuffer(getBytesPerFrame(), 1024); // TODO size?
// Create a callback that reads from the FIFO
mInternalCallback = std::unique_ptr<AudioStreamBufferedCallback>(new AudioStreamBufferedCallback(this));
Copy link
Collaborator

@mnaganov mnaganov Feb 9, 2018

Choose a reason for hiding this comment

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

You can use std::unique_ptr::reset method http://en.cppreference.com/w/cpp/memory/unique_ptr/reset:

mInternalCallback.reset(new AudioStreamBufferedCallback(this))

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. I cleaned up this section quite a bit.

Implment timestamps.
Improve buffer capacity calculation.
Refactored and simplified buffer callback.
@philburk philburk requested a review from dturner February 9, 2018 22:41
Also move some curly brackets.
int32_t AudioStreamAAudio::read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds)
{
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

move to "{" previous line.

Phil Burk and others added 5 commits February 9, 2018 14:55
Use FIFO for OpenSL ES.
Implment timestamps.
Improve buffer capacity calculation.
Refactored and simplified buffer callback.
Also move some curly brackets.
int32_t numFrames,
int64_t timeoutNanoseconds) {
if (getDirection() == Direction::Output) {
return (int32_t) Result::ErrorUnavailable; // TODO review, better error code?
Copy link
Collaborator

Choose a reason for hiding this comment

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

ErrorInvalidState?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

or maybe ErrorUnsupported

Copy link
Collaborator

@dturner dturner Feb 12, 2018

Choose a reason for hiding this comment

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

I would categorize writing to an input stream or reading from an output stream as an invalid operation, so ErrorInvalidOperation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I like InvalidOperation. Currently all oboe errors are defined as AAUDIO_ERROR_*. We could add INVALID_OPERATION to aaudio and try keep the numbers in sync.

, mFramesReadCount(0)
, mFramesUnderrunCount(0)
, mUnderrunCount(0)
{
assert(bytesPerFrame > 0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a more elegant way we could handle these two assertions?

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. I added them because this is a constructor and the FIFO cannot work if the values are incorrect. If they are zero then the FIFO seems to work but doesn't. This took a while to debug. I generally don't like asserts but they seem justified here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes agreed, just checking they weren't left in for debugging purposes.

// FIFO is configured with the same format and channels as the stream.
int32_t capacity = getBufferCapacityInFrames();
if (capacity == oboe::kUnspecified) {
capacity = getFramesPerBurst() * 16; // arbitrary
Copy link
Collaborator

Choose a reason for hiding this comment

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

Change 16 to konstant

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

if (getDirection() == oboe::Direction::Output) {
// Read from the FIFO and write to audioData
framesTransferred = mFifoBuffer->readNow(audioData, numFrames);
LOGV("%s() read %d / %d frames from FIFO", __func__, framesTransferred, numFrames);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Logging in callback, remove

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

} else {
// Read from audioData and write to the FIFO
framesTransferred = mFifoBuffer->write(audioData, numFrames); // FIXME writeNow????
LOGV("%s() wrote %d / %d frames to FIFO", __func__, framesTransferred, numFrames);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Logging in callback, remove

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

return mBackgroundRanAtNanoseconds + nanosPerBuffer + margin;
}

// TODO: This method should return a tuple of Result,int32_t where the
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's a few instances where returning a tuple would certainly be easier to understand, rather than having:

positive int32_t = some interesting value for the caller
negative int32_t = error which must be cast back to a Result type

Any thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Right, the current way is more appropriate for C. E.g. for sizes, there is a standard type is GNU C library called 'ssize_t' which either contains a positive size or negative error code.

In C++, I would prefer a dedicated type that can be checked as a boolean in a logical expression, and then either the value or an error code can be extracted from it. Here is a good example: https://www.kdab.com/tuple-pair-cpp-apis/ Look for "class value_or_error" in the article. Note that they use std::variant in the implementation, which is from c++17, but we don't actually need it, can just have two fields instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmmm. Maybe. The way that value_or_error template override * seems odd. You use *result to get the value and result.error() to get the error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I implemented this. But I also notice that many oboe functions return a positive value or a negative error. Changing all of them would be disruptive.

LOGE("Oboe callback returned %d", result);
return SL_RESULT_INTERNAL_ERROR;
return SL_RESULT_INTERNAL_ERROR; // TODO How should we stop OpenSL ES.
Copy link
Collaborator

Choose a reason for hiding this comment

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

We could use the SL player interface to set the play state to stopped. E.g.

https://github.com/googlesamples/android-ndk/blob/master/audio-echo/app/src/main/cpp/audio_player.cpp#L235

Additionally, we could set the stream state to stopped.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Can that be called safely from inside an OpenSL callback?

Copy link
Collaborator

Choose a reason for hiding this comment

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

90% sure they can. We should probably add a test for this.

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 a separate issue from the read/write patch.
I filed: #61

Let's finish up this patch.

Copy link
Collaborator

@dturner dturner left a comment

Choose a reason for hiding this comment

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

Few comments/questions:

  • Is it worth adding any unit tests for this?
  • Should we move sample code inside the oboe repo so we can launch simultaneously with sample code? It'd make testing new features (like this) much easier too.
  • Main thing to fix is the logging in the callback
  • I ran a few tests using a port of the aaudio-echo sample, results:

Pixel API 25 (OpenSL ES) performance mode NONE: worked great
Pixel 2 API 27 (AAudio), performance mode NONE: quite glitchy
Pixel 2 API 27 (AAudio), performance mode LOW_LATENCY: worked great

Check for negative setBufferSize()
Add more asserts.
@philburk philburk merged commit 44c6b6e into master Feb 14, 2018
@philburk philburk deleted the readwrite branch February 14, 2018 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants