-
Notifications
You must be signed in to change notification settings - Fork 580
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
New namespace, constexprs and scoped enums #18
Conversation
GettingStarted.md
Outdated
@@ -48,24 +48,24 @@ Include the Oboe header: | |||
|
|||
#include <oboe/Oboe.h> | |||
|
|||
Streams are built using an `OboeStreamBuilder`. Create one like this: | |||
Streams are built using an `StreamBuilder`. Create one like 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.
an -> a
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.
Deferring doing the an/a changes until we have made a decision on Stream
vs AudioStream
naming
GettingStarted.md
Outdated
|
||
Define an `OboeStreamCallback` class to receive callbacks whenever the stream requires new data. | ||
Define an `StreamCallback` class to receive callbacks whenever the stream requires new data. |
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.
an -> a
GettingStarted.md
Outdated
OboeStream *stream; | ||
oboe_result_t result = builder.openStream(&stream); | ||
oboe::Stream *stream; | ||
oboe::Result result = builder.openStream(&stream); | ||
|
||
Check the result to make sure the stream was opened successfully. Oboe has many convenience methods for converting its types into human-readable strings, they all start with `Oboe_convert`: |
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.
all start with oboe::convert
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
include/oboe/Stream.h
Outdated
@@ -177,16 +175,16 @@ class OboeStream : public OboeStreamBase { | |||
* @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion. | |||
* @return The number of frames actually written or a negative error. | |||
*/ | |||
virtual oboe_result_t write(const void *buffer, | |||
virtual int32_t write(const void *buffer, |
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.
IMO this method would be better off returning a std::tuple
of Result
and int32_t
. This avoids the return type representing both the frames written (if positive) or an error (if negative).
include/oboe/Stream.h
Outdated
|
||
/** | ||
* Base class for Oboe C++ audio stream. | ||
*/ | ||
class OboeStream : public OboeStreamBase { | ||
class Stream : public StreamBase { |
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 might want to consider calling this AudioStream
since Stream
is quite a generic term. At some point in the future we might also have a MidiStream
if (--mIdleCountDown <= 0) { | ||
mState = STATE_ACTIVE; | ||
mState = State::Active; | ||
} | ||
mPreviousXRuns = mStream.getXRunCount(); |
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.
getXRunCount
might also be better returning a std::tuple
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.
Nice!
GettingStarted.md
Outdated
@@ -48,24 +48,24 @@ Include the Oboe header: | |||
|
|||
#include <oboe/Oboe.h> | |||
|
|||
Streams are built using an `OboeStreamBuilder`. Create one like this: | |||
Streams are built using an `StreamBuilder`. Create one like 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.
c/an/a/
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.
Are the class names Stream and StreamBuilder too generic? Is there a danger of colliding if developers are using multiple namespaces?
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 would only be a danger if they're using non-qualified namespaces e.g.
using namespace oboe; Stream stream;
But yes, I do think Stream
is too generic. AudioStream
, AudioStreamBuilder
etc would be more readable. Also if we ever support MIDI we could have a MidiStream
. If you agree I'll rename.
GettingStarted.md
Outdated
OboeStream *stream; | ||
oboe_result_t result = builder.openStream(&stream); | ||
oboe::Stream *stream; | ||
oboe::Result result = builder.openStream(&stream); | ||
|
||
Check the result to make sure the stream was opened successfully. Oboe has many convenience methods for converting its types into human-readable strings, they all start with `Oboe_convert`: |
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.
they now start with "oboe::convert"
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.
Fixed
include/oboe/StreamBuilder.h
Outdated
@@ -171,7 +174,7 @@ class OboeStreamBuilder : public OboeStreamBase { | |||
* @param performanceMode for example, OBOE_PERFORMANCE_MODE_LOW_LATENCY |
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.
old
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.
Fixed
include/oboe/StreamBuilder.h
Outdated
@@ -185,7 +188,7 @@ class OboeStreamBuilder : public OboeStreamBase { | |||
* @param deviceId device identifier or OBOE_DEVICE_UNSPECIFIED |
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.
old
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.
Fixed
src/common/Utilities.cpp
Outdated
return size; | ||
} | ||
|
||
const char *convertResultToText(Result returnCode) { |
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.
If running with a future version AAudio, there may be new results or new formats. So we should add a default "Unrecognized" return value.
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 for all convert
methods
include/oboe/Definitions.h
Outdated
|
||
constexpr int32_t kUnspecified = 0; | ||
|
||
constexpr int64_t kNanosPerMicrosecond = 1000; |
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.
Please add a TODO to consider using std::chrono, as it provides a lot of time unit conversion utilities.
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
Stop = AAUDIO_CALLBACK_RESULT_STOP, | ||
}; | ||
|
||
enum class Result : aaudio_result_t { |
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.
Consider renaming this class to "Error", which would allow dropping the "Error" prefix from the enum items.
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 it normal to have an Error::OK
value? We're going to see a lot of:
if (error != Error::OK){
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, that's perhaps not the best expression. But seeing a lot of "Error" prefixes doesn't make me happy either. Although, I've just checked Vulkan's 'Result' type, and they also have "error" prefixes everywhere. OK, let's not consider changing anything here for now.
|
||
enum class Result : aaudio_result_t { | ||
OK, | ||
ErrorBase = AAUDIO_ERROR_BASE, |
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.
ErrorBase shouldn't be used as a source of an assignment anywhere. To prevent this, I would suggest introducing a helper function 'isError' which compares the value against AAUDIO_ERROR_BASE.
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 don't I just remove ErrorBase
from the list of possible values in the Result
enum?
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.
Sure. What I've meant is that if anyone will ever need to check against AAUDIO_ERROR_BASE, you can provide a convenience method.
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.
ErrorBase
removed
include/oboe/StreamBase.h
Outdated
oboe_audio_format_t mFormat = OBOE_AUDIO_FORMAT_UNSPECIFIED; | ||
oboe_direction_t mDirection = OBOE_DIRECTION_OUTPUT; | ||
oboe_performance_mode_t mPerformanceMode = OBOE_PERFORMANCE_MODE_NONE; | ||
StreamCallback *mStreamCallback = NULL; |
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.
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.
Fixed
src/common/Stream.cpp
Outdated
); | ||
mPreviousScheduler = scheduler; | ||
} | ||
if (mStreamCallback == NULL) { |
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.
nullptr
include/oboe/StreamBuilder.h
Outdated
@@ -199,7 +202,7 @@ class OboeStreamBuilder : public OboeStreamBase { | |||
* @param streamCallback | |||
* @return | |||
*/ | |||
OboeStreamBuilder *setCallback(OboeStreamCallback *streamCallback) { | |||
StreamBuilder *setCallback(StreamCallback *streamCallback) { |
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.
Please consider specifying StreamCallback ownership via use of std::unique_ptr or std::shared_ptr. With a raw pointer, it's easy to run into a trouble of the client code deletes the callback instance.
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.
Good spot. I think it makes sense for the client to retain ownership of the callback so that means using a std::shared_ptr<StreamCallback>
. I've updated the code to do this. Would you mind taking a look.
|
||
void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples) { | ||
for (int i = 0; i < numSamples; i++) { | ||
destination[i] = source[i] * (1.0f / 32768.0f); |
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.
Curious: why not just "source[i] / 32768.0f"?
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.
@philburk one for you
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.
To avoid rounding in opposite directions because of the cast. But Andy has a better technique now. Will change.
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.
Makes sense to me. Thanks!
include/oboe/LatencyTuner.h
Outdated
@@ -83,7 +83,7 @@ class LatencyTuner { | |||
// arbitrary number of calls to wait before bumping up the latency | |||
static constexpr int32_t kIdleCount = 8; | |||
|
|||
Stream &mStream; | |||
AudioStream &mStream; |
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.
realign
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/StreamBuffered.cpp
Outdated
mInternalCallback = new AudioStreamBufferedCallback(this); | ||
mStreamCallback = mInternalCallback; | ||
LOGD("StreamBuffered(): mInternalCallback = %p", mInternalCallback); | ||
mStreamCallback = std::make_shared<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.
This makes me nervous. Can this object be deleted properly if it holds a reference to itself?
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 "this" actually an AudioStreamBufferedCallback?
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 the argument for AudioStreamBufferedCallback's constructor. Thus, the constructed callback will hold a raw pointer to the stream, while the stream will be holding a refcounted pointer to the 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.
this
is an AudioStreamBuffered
. The call is equivalent to doing new AudioStreamBufferedCallback(this)
except that instead of a raw pointer the result is a shared_ptr
which will ensure deletion when the refcount drops to zero.
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.
Cool. Thanks for the explanation.
@@ -28,11 +28,11 @@ The audio device attached to a stream determines whether the stream is for input | |||
|
|||
A stream has a sharing mode: | |||
|
|||
* `OBOE_SHARING_MODE_EXCLUSIVE` (available on API 26+) means the stream has exclusive access to its audio device; the device cannot be used by any other audio stream. If the audio device is already in use, it might not be possible for the stream to have exclusive access. Exclusive streams provide the lowest possible latency, but they are also more likely to get disconnected. You should close exclusive streams as soon as you no longer need them, so that other apps can access the device. | |||
* `OBOE_SHARING_MODE_SHARED` allows Oboe to mix audio. Oboe mixes all the shared streams assigned to the same device. | |||
* `SharingMode::Exclusive` (available on API 26+) means the stream has exclusive access to its audio device; the device cannot be used by any other audio stream. If the audio device is already in use, it might not be possible for the stream to have exclusive access. Exclusive streams provide the lowest possible latency, but they are also more likely to get disconnected. You should close exclusive streams as soon as you no longer need them, so that other apps can access the device. |
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.
audio device
Are we defining the oboe meaning of audio device somewhere ? Because audio device as define by the policy and audio flinger means the actual hardware (speaker, mic...). But exclusive mode allows exclusive access to a front end of this hardware. So
the device cannot be used by any other audio stream
might be confusing for the user as it does not mean the hardware can not be concurrently played to. For example on pixel, the dsp will mix the aaudio exclusive port with the audioflinger low latency port resulting in concurrent playback on the same hardware.
Ignore this comment if this is subtility of the "exclusive" mode is explain in some other part of the doc.
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 am very open to a better term than "audio device". Calling it a "front end of this hardware" is still not very clear. Maybe "audio resource"? A phone can have two "front ends" for a device. One is used by AudioTrack and one is available for MMAP.
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.
Let's fix this doc issue in a later CL. We need to get this big refactoring done.
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 used the concept of an audio device "endpoint" to explain this more clearly in #23.
Stop = AAUDIO_CALLBACK_RESULT_STOP, | ||
}; | ||
|
||
enum class Result : aaudio_result_t { |
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 are relying on aaudio_result_t beeing int32_t in multiple places. Maybe we should add in some .cpp static_assert(is_same<int32_t, aaudio_result_t>)
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. Added checks for all AAudio primitive data types.
#include "oboe/AudioStreamCallback.h" | ||
#include "oboe/Definitions.h" | ||
|
||
namespace oboe { |
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 may or may not want to add an inline version namespace.
http://en.cppreference.com/w/cpp/language/namespace#Inline_namespaces
For non template library like Oboe, the advantage of inline version namespace is that symbols of different version will have a different name. Thus preventing a user from linking to the wrong version (aka less user error).
It also allows to have 2 oboe version in the same process.
Though I am not sure we want to support 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.
The intention was to have an App statically link to Oboe.
I suppose there could be a problem if an app tried to use with a library that had it's own calls to an old version of Oboe. But that seems unlikely. Most audio libraries just pass PCM in and out and leave the audio I/O to the main app..
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.
If Oboe is static linked, inline version namespace have a lot less attractivity. Do not bother with it.
OBOE_CASE_ENUM(Result::ErrorNoService); | ||
OBOE_CASE_ENUM(Result::ErrorInvalidRate); | ||
default: | ||
return "Unrecognized result"; |
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.
It would be nice to return the unrecognized value. Eg: "unrecognized result (-50)" to help the developer determined the problem (wrong casting, value is positive (not an error)...). Though, it would mean changing the api...
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've added a TODO for this as it's a non-breaking change and I couldn't figure out exactly how this could be achieved easily without a lot of messing about with stringstream->string->c_str
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 breaking change. You would have to return a string buffer that the client owned (eg: std::string). instead of returning a static char *
src/common/Utilities.cpp
Outdated
#include "oboe/Definitions.h" | ||
#include "oboe/Utilities.h" | ||
|
||
#define OBOE_CASE_ENUM(name) case name: return #name |
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 usually like having both the human readable value and the number. How about
#define STR(x) #x
#define OBOE_CASE_ENUM(name) case name: return #name " (" STR(name) ")"
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 tried this and it just outputs the type name e.g. "Result::OK" twice in a row. I think I need to static_cast name
to an int32_t
then concat with the return string - could be tough to implement in a macro.
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, for Kevin's approach to work, "name" must be a preprocessor define, so it can be expanded into a value during macro expansion phase, like described here: https://gcc.gnu.org/onlinedocs/gcc-4.1.2/cpp/Stringification.html. Preprocessor knows nothing about the values of enums.
But actually, the tricky part that involves the use of preprocessor is getting the program symbol name, that is, the name of the enum constant. The value of a enum constant can be obtained at compile- or run-time.
So, for example, in your enum to string function you could use a stringstream, where you first put the value of the enum (unconditionally), and then use the stringification trick in order to add the symbol name of the enum.
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, I though the name was an AAudio constant. There are solutions to convert the enum value to a compile time string, but it would add too much complexity. Hopefully in C++20, reflection will make all that much easier.
The alternative would be to construct the string at build time which would make it much easier:
template <class Enum, Enum enum>
const char* enumToStr(const char* name) {
const static str = name
}
#define OBOE_CASE_ENUM(name) case name: return std::string(#name) + " (" + std::to_string(name) + ')';
But that changes the API, as a std::string has to be returned.
src/common/Utilities.cpp
Outdated
} | ||
} | ||
|
||
const char *convertSharingModeToText(SharingMode mode) { |
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.
Because the input type is already in the signature, you may or may not want to remove it from the function name:
const char *convertToText(AudioFormat format);
const char *convertToText(PerformanceMode mode);
const char *convertToText(SharingMode mode);
but for one it is a style preference secondly some people prefer to be explicit.
Of course there is also the template route that allows explicit and implicit input type specification:
template <class From>
const char * convertToText(From);
template <>
const char* convertToText<AudioFormat>(AudioFormat format);
// ...
// Which can be used explicitly:
auto format = AudioFormat::I16;
cout << convertToText<AudioFormat>(format);
// or implicitly
cout << convertToText(format);
But this is not the current style of the API, plus it is a backward incompatible change that might not be worth it.
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 CL breaks the API anyway. We warned people it would break. And we are modernizing. So now is the time if we want to do this kind of stuff. Up to you Don.
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.
The implicit use of convertToText is really nice. Have implemented.
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.
Looks great! Thanks Don.
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.
OK to finish adding the int value in a later CL.
!!!BREAKING CHANGE!!!
Summary
oboe::
namespaceconstexpr
and scoped enumsOboe_convert*Text
methods replaced with singleconvertToText
methodDetailed changelog
1)
oboe
namespace used for all classes and functionsWhy?
Having a namespace provides better encapsulation, avoids polluting the global namespace and also avoids having to prefix all Oboe classes and methods with the word "Oboe".
What code changes do I need to make?
If you're using any classes which start with "Oboe" you'll need to change them to use
oboe::
. For example:becomes
or you can use the oboe namespace without qualification:
2) Constants defined by macros are replaced by
constexpr
and scoped enumsWhy?
C++ Core Guidelines ES.31: Don’t use macros for constants or “functions” http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-macros2.
Effective Modern C++ (Scott Meyer) Item 15 - Use constexpr wherever possible.
Scoped enums allows better type checking at compile time and eliminate the need for long #defines in the global namespace.
What code changes do I need to make?
Anywhere you were using a value starting with OBOE_ you'll need to change it either to the corresponding value starting with oboe::k. For example
OBOE_NANOS_PER_MICROSECOND
becomesoboe::kNanosPerMicrosecond
. Or you'll need to change it to the corresponding scoped enum:OBOE_AUDIO_FORMAT_PCM_FLOAT
becomesoboe::AudioFormat::Float
3)
Oboe_convert*Text
methods replaced with singleconvertToText
method. **Why?
A single method is easier to remember and to use.
What code changes do I need to make?
If you are using a
Oboe_convert*Text
method then you'll need to change it to useconvertToText
. For example:becomes