-
Notifications
You must be signed in to change notification settings - Fork 565
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
Conversation
Use FIFO for OpenSL ES.
@@ -25,23 +25,18 @@ namespace oboe { | |||
* AudioStream with a FifoBuffer | |||
*/ | |||
AudioStreamBuffered::AudioStreamBuffered(const AudioStreamBuilder &builder) | |||
: AudioStream(builder) | |||
, mFifoBuffer(nullptr) |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/opensles/AudioStreamBuffered.cpp
Outdated
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)); |
There was a problem hiding this comment.
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))
There was a problem hiding this comment.
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.
Also move some curly brackets.
src/aaudio/AudioStreamAAudio.cpp
Outdated
int32_t AudioStreamAAudio::read(void *buffer, | ||
int32_t numFrames, | ||
int64_t timeoutNanoseconds) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to "{" previous line.
Use FIFO for OpenSL ES.
Implment timestamps. Improve buffer capacity calculation. Refactored and simplified buffer callback.
Also move some curly brackets.
…rite Had to rebase on github.
int32_t numFrames, | ||
int64_t timeoutNanoseconds) { | ||
if (getDirection() == Direction::Output) { | ||
return (int32_t) Result::ErrorUnavailable; // TODO review, better error code? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ErrorInvalidState?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or maybe ErrorUnsupported
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/opensles/AudioStreamBuffered.cpp
Outdated
// FIFO is configured with the same format and channels as the stream. | ||
int32_t capacity = getBufferCapacityInFrames(); | ||
if (capacity == oboe::kUnspecified) { | ||
capacity = getFramesPerBurst() * 16; // arbitrary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change 16 to konstant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/opensles/AudioStreamBuffered.cpp
Outdated
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logging in callback, remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/opensles/AudioStreamBuffered.cpp
Outdated
} 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logging in callback, remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/opensles/AudioStreamBuffered.cpp
Outdated
return mBackgroundRanAtNanoseconds + nanosPerBuffer + margin; | ||
} | ||
|
||
// TODO: This method should return a tuple of Result,int32_t where the |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
Additionally, we could set the stream state to stopped.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
Use FIFO for OpenSL ES.
This will allow blocking read/write calls on a stream without using a callback.