1*05767d91SRobert Wu# Adding Oboe to your project 2*05767d91SRobert WuThere are two ways use Oboe in your Android Studio project: 3*05767d91SRobert Wu 4*05767d91SRobert Wu1) **Use the Oboe pre-built library binaries and headers**. Use this approach if you just want to use a stable version of the Oboe library in your project. 5*05767d91SRobert Wu 6*05767d91SRobert Wuor 7*05767d91SRobert Wu 8*05767d91SRobert Wu2) **Build Oboe from source.** Use this approach if you would like to debug or make changes to the Oboe source code and contribute back to the project. 9*05767d91SRobert Wu 10*05767d91SRobert Wu## Option 1) Using pre-built binaries and headers 11*05767d91SRobert Wu 12*05767d91SRobert WuOboe is distributed as a [prefab](https://github.com/google/prefab) package via [Google Maven](https://maven.google.com/web/index.html) (search for "oboe"). [Prefab support was added](https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html) to [Android Studio 4.0](https://developer.android.com/studio) so you'll need to be using this version of Android Studio or above. 13*05767d91SRobert Wu 14*05767d91SRobert WuAdd the oboe dependency to your app's `build.gradle` file. Replace "X.X.X" with the [latest stable version](https://github.com/google/oboe/releases/) of Oboe: 15*05767d91SRobert Wu 16*05767d91SRobert Wu dependencies { 17*05767d91SRobert Wu implementation 'com.google.oboe:oboe:X.X.X' 18*05767d91SRobert Wu } 19*05767d91SRobert Wu 20*05767d91SRobert WuAlso enable prefab by adding: 21*05767d91SRobert Wu 22*05767d91SRobert Wu android { 23*05767d91SRobert Wu buildFeatures { 24*05767d91SRobert Wu prefab true 25*05767d91SRobert Wu } 26*05767d91SRobert Wu } 27*05767d91SRobert Wu 28*05767d91SRobert WuInclude and link to oboe by updating your `CMakeLists.txt`: 29*05767d91SRobert Wu 30*05767d91SRobert Wu find_package (oboe REQUIRED CONFIG) 31*05767d91SRobert Wu target_link_libraries(native-lib oboe::oboe) # You may have other libraries here such as `log`. 32*05767d91SRobert Wu 33*05767d91SRobert WuHere's a complete example `CMakeLists.txt` file: 34*05767d91SRobert Wu 35*05767d91SRobert Wu cmake_minimum_required(VERSION 3.4.1) 36*05767d91SRobert Wu 37*05767d91SRobert Wu # Build our own native library 38*05767d91SRobert Wu add_library (native-lib SHARED native-lib.cpp ) 39*05767d91SRobert Wu 40*05767d91SRobert Wu # Find the Oboe package 41*05767d91SRobert Wu find_package (oboe REQUIRED CONFIG) 42*05767d91SRobert Wu 43*05767d91SRobert Wu # Specify the libraries which our native library is dependent on, including Oboe 44*05767d91SRobert Wu target_link_libraries(native-lib log oboe::oboe) 45*05767d91SRobert Wu 46*05767d91SRobert WuConfigure your app to use the shared STL by updating your `app/build.gradle`: 47*05767d91SRobert Wu 48*05767d91SRobert Wu android { 49*05767d91SRobert Wu defaultConfig { 50*05767d91SRobert Wu externalNativeBuild { 51*05767d91SRobert Wu cmake { 52*05767d91SRobert Wu arguments "-DANDROID_STL=c++_shared" 53*05767d91SRobert Wu } 54*05767d91SRobert Wu } 55*05767d91SRobert Wu } 56*05767d91SRobert Wu } 57*05767d91SRobert Wu 58*05767d91SRobert Wu## Option 2) Building from source 59*05767d91SRobert Wu 60*05767d91SRobert Wu### 1. Clone the github repository 61*05767d91SRobert WuStart by cloning the [latest stable release](https://github.com/google/oboe/releases/) of the Oboe repository, for example: 62*05767d91SRobert Wu 63*05767d91SRobert Wu git clone -b 1.6-stable https://github.com/google/oboe 64*05767d91SRobert Wu 65*05767d91SRobert Wu**Make a note of the path which you cloned oboe into - you will need it shortly** 66*05767d91SRobert Wu 67*05767d91SRobert WuIf you use git as your version control system, consider adding Oboe as a [submodule](https://gist.github.com/gitaarik/8735255) (underneath your 68*05767d91SRobert Wucpp directory) 69*05767d91SRobert Wu 70*05767d91SRobert Wu``` 71*05767d91SRobert Wugit submodule add https://github.com/google/oboe 72*05767d91SRobert Wu``` 73*05767d91SRobert Wu 74*05767d91SRobert WuThis makes it easier to integrate updates to Oboe into your app, as well as contribute to the Oboe project. 75*05767d91SRobert Wu 76*05767d91SRobert Wu### 2. Update CMakeLists.txt 77*05767d91SRobert WuOpen your app's `CMakeLists.txt`. This can be found under `External Build Files` in the Android project view. If you don't have a `CMakeLists.txt` you will need to [add C++ support to your project](https://developer.android.com/studio/projects/add-native-code). 78*05767d91SRobert Wu 79*05767d91SRobert Wu 80*05767d91SRobert Wu 81*05767d91SRobert WuNow add the following commands to the end of `CMakeLists.txt`. **Remember to update `**PATH TO OBOE**` with your local Oboe path from the previous step**: 82*05767d91SRobert Wu 83*05767d91SRobert Wu # Set the path to the Oboe directory. 84*05767d91SRobert Wu set (OBOE_DIR ***PATH TO OBOE***) 85*05767d91SRobert Wu 86*05767d91SRobert Wu # Add the Oboe library as a subdirectory in your project. 87*05767d91SRobert Wu # add_subdirectory tells CMake to look in this directory to 88*05767d91SRobert Wu # compile oboe source files using oboe's CMake file. 89*05767d91SRobert Wu # ./oboe specifies where the compiled binaries will be stored 90*05767d91SRobert Wu add_subdirectory (${OBOE_DIR} ./oboe) 91*05767d91SRobert Wu 92*05767d91SRobert Wu # Specify the path to the Oboe header files. 93*05767d91SRobert Wu # This allows targets compiled with this CMake (application code) 94*05767d91SRobert Wu # to see public Oboe headers, in order to access its API. 95*05767d91SRobert Wu include_directories (${OBOE_DIR}/include) 96*05767d91SRobert Wu 97*05767d91SRobert Wu 98*05767d91SRobert WuIn the same file find the [`target_link_libraries`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) command. 99*05767d91SRobert WuAdd `oboe` to the list of libraries which your app's library depends on. For example: 100*05767d91SRobert Wu 101*05767d91SRobert Wu target_link_libraries(native-lib oboe) 102*05767d91SRobert Wu 103*05767d91SRobert WuHere's a complete example `CMakeLists.txt` file: 104*05767d91SRobert Wu 105*05767d91SRobert Wu cmake_minimum_required(VERSION 3.4.1) 106*05767d91SRobert Wu 107*05767d91SRobert Wu # Build our own native library 108*05767d91SRobert Wu add_library (native-lib SHARED native-lib.cpp ) 109*05767d91SRobert Wu 110*05767d91SRobert Wu # Build the Oboe library 111*05767d91SRobert Wu set (OBOE_DIR ./oboe) 112*05767d91SRobert Wu add_subdirectory (${OBOE_DIR} ./oboe) 113*05767d91SRobert Wu 114*05767d91SRobert Wu # Make the Oboe public headers available to our app 115*05767d91SRobert Wu include_directories (${OBOE_DIR}/include) 116*05767d91SRobert Wu 117*05767d91SRobert Wu # Specify the libraries which our native library is dependent on, including Oboe 118*05767d91SRobert Wu target_link_libraries (native-lib log oboe) 119*05767d91SRobert Wu 120*05767d91SRobert Wu 121*05767d91SRobert WuNow go to `Build->Refresh Linked C++ Projects` to have Android Studio index the Oboe library. 122*05767d91SRobert Wu 123*05767d91SRobert WuVerify that your project builds correctly. If you have any issues building please [report them here](issues/new). 124*05767d91SRobert Wu 125*05767d91SRobert Wu# Using Oboe 126*05767d91SRobert WuOnce you've added Oboe to your project you can start using Oboe's features. The simplest, and probably most common thing you'll do in Oboe is to create an audio stream. 127*05767d91SRobert Wu 128*05767d91SRobert Wu## Creating an audio stream 129*05767d91SRobert WuInclude the Oboe header: 130*05767d91SRobert Wu 131*05767d91SRobert Wu #include <oboe/Oboe.h> 132*05767d91SRobert Wu 133*05767d91SRobert WuStreams are built using an `AudioStreamBuilder`. Create one like this: 134*05767d91SRobert Wu 135*05767d91SRobert Wu oboe::AudioStreamBuilder builder; 136*05767d91SRobert Wu 137*05767d91SRobert WuUse the builder's set methods to set properties on the stream (you can read more about these properties in the [full guide](FullGuide.md)): 138*05767d91SRobert Wu 139*05767d91SRobert Wu builder.setDirection(oboe::Direction::Output); 140*05767d91SRobert Wu builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); 141*05767d91SRobert Wu builder.setSharingMode(oboe::SharingMode::Exclusive); 142*05767d91SRobert Wu builder.setFormat(oboe::AudioFormat::Float); 143*05767d91SRobert Wu builder.setChannelCount(oboe::ChannelCount::Mono); 144*05767d91SRobert Wu 145*05767d91SRobert WuThe builder's set methods return a pointer to the builder. So they can be easily chained: 146*05767d91SRobert Wu 147*05767d91SRobert Wu``` 148*05767d91SRobert Wuoboe::AudioStreamBuilder builder; 149*05767d91SRobert Wubuilder.setPerformanceMode(oboe::PerformanceMode::LowLatency) 150*05767d91SRobert Wu ->setSharingMode(oboe::SharingMode::Exclusive) 151*05767d91SRobert Wu ->setDataCallback(myCallback) 152*05767d91SRobert Wu ->setFormat(oboe::AudioFormat::Float); 153*05767d91SRobert Wu``` 154*05767d91SRobert Wu 155*05767d91SRobert WuDefine an `AudioStreamDataCallback` class to receive callbacks whenever the stream requires new data. 156*05767d91SRobert Wu 157*05767d91SRobert Wu class MyCallback : public oboe::AudioStreamDataCallback { 158*05767d91SRobert Wu public: 159*05767d91SRobert Wu oboe::DataCallbackResult 160*05767d91SRobert Wu onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) { 161*05767d91SRobert Wu 162*05767d91SRobert Wu // We requested AudioFormat::Float. So if the stream opens 163*05767d91SRobert Wu // we know we got the Float format. 164*05767d91SRobert Wu // If you do not specify a format then you should check what format 165*05767d91SRobert Wu // the stream has and cast to the appropriate type. 166*05767d91SRobert Wu auto *outputData = static_cast<float *>(audioData); 167*05767d91SRobert Wu 168*05767d91SRobert Wu // Generate random numbers (white noise) centered around zero. 169*05767d91SRobert Wu const float amplitude = 0.2f; 170*05767d91SRobert Wu for (int i = 0; i < numFrames; ++i){ 171*05767d91SRobert Wu outputData[i] = ((float)drand48() - 0.5f) * 2 * amplitude; 172*05767d91SRobert Wu } 173*05767d91SRobert Wu 174*05767d91SRobert Wu return oboe::DataCallbackResult::Continue; 175*05767d91SRobert Wu } 176*05767d91SRobert Wu }; 177*05767d91SRobert Wu 178*05767d91SRobert WuYou can find examples of how to play sound using digital synthesis and pre-recorded audio in the [code samples](../samples). 179*05767d91SRobert Wu 180*05767d91SRobert WuDeclare your callback somewhere that it won't get deleted while you are using it. 181*05767d91SRobert Wu 182*05767d91SRobert Wu MyCallback myCallback; 183*05767d91SRobert Wu 184*05767d91SRobert WuSupply this callback class to the builder: 185*05767d91SRobert Wu 186*05767d91SRobert Wu builder.setDataCallback(&myCallback); 187*05767d91SRobert Wu 188*05767d91SRobert WuDeclare a shared pointer for the stream. Make sure it is declared with the appropriate scope. The best place is as a member variable in a managing class or as a global. Avoid declaring it as a local variable because the stream may get deleted when the function returns. 189*05767d91SRobert Wu 190*05767d91SRobert Wu std::shared_ptr<oboe::AudioStream> mStream; 191*05767d91SRobert Wu 192*05767d91SRobert WuOpen the stream: 193*05767d91SRobert Wu 194*05767d91SRobert Wu oboe::Result result = builder.openStream(mStream); 195*05767d91SRobert Wu 196*05767d91SRobert WuCheck the result to make sure the stream was opened successfully. Oboe has a convenience method for converting its types into human-readable strings called `oboe::convertToText`: 197*05767d91SRobert Wu 198*05767d91SRobert Wu if (result != oboe::Result::OK) { 199*05767d91SRobert Wu LOGE("Failed to create stream. Error: %s", oboe::convertToText(result)); 200*05767d91SRobert Wu } 201*05767d91SRobert Wu 202*05767d91SRobert WuNote that this sample code uses the [logging macros from here](https://github.com/googlesamples/android-audio-high-performance/blob/master/debug-utils/logging_macros.h). 203*05767d91SRobert Wu 204*05767d91SRobert Wu## Playing audio 205*05767d91SRobert WuCheck the properties of the created stream. If you did not specify a channelCount, sampleRate, or format then you need to 206*05767d91SRobert Wuquery the stream to see what you got. The **format** property will dictate the `audioData` type in the `AudioStreamDataCallback::onAudioReady` callback. If you did specify any of those three properties then you will get what you requested. 207*05767d91SRobert Wu 208*05767d91SRobert Wu oboe::AudioFormat format = mStream->getFormat(); 209*05767d91SRobert Wu LOGI("AudioStream format is %s", oboe::convertToText(format)); 210*05767d91SRobert Wu 211*05767d91SRobert WuNow start the stream. 212*05767d91SRobert Wu 213*05767d91SRobert Wu mStream->requestStart(); 214*05767d91SRobert Wu 215*05767d91SRobert WuAt this point you should start receiving callbacks. 216*05767d91SRobert Wu 217*05767d91SRobert WuTo stop receiving callbacks call 218*05767d91SRobert Wu 219*05767d91SRobert Wu mStream->requestStop(); 220*05767d91SRobert Wu 221*05767d91SRobert Wu## Closing the stream 222*05767d91SRobert WuIt is important to close your stream when you're not using it to avoid hogging audio resources which other apps could use. This is particularly true when using `SharingMode::Exclusive` because you might prevent other apps from obtaining a low latency audio stream. 223*05767d91SRobert Wu 224*05767d91SRobert WuStreams should be explicitly closed when the app is no longer playing audio. 225*05767d91SRobert Wu 226*05767d91SRobert Wu mStream->close(); 227*05767d91SRobert Wu 228*05767d91SRobert Wu`close()` is a blocking call which also stops the stream. 229*05767d91SRobert Wu 230*05767d91SRobert WuFor apps which only play or record audio when they are in the foreground this is usually done when [`Activity.onPause()`](https://developer.android.com/guide/components/activities/activity-lifecycle#onpause) is called. 231*05767d91SRobert Wu 232*05767d91SRobert Wu## Reconfiguring streams 233*05767d91SRobert WuAfter closing, in order to change the configuration of the stream, simply call `openStream` 234*05767d91SRobert Wuagain. The existing stream is deleted and a new stream is built and 235*05767d91SRobert Wupopulates the `mStream` variable. 236*05767d91SRobert Wu``` 237*05767d91SRobert Wu// Modify the builder with some additional properties at runtime. 238*05767d91SRobert Wubuilder.setDeviceId(MY_DEVICE_ID); 239*05767d91SRobert Wu// Re-open the stream with some additional config 240*05767d91SRobert Wu// The old AudioStream is automatically deleted 241*05767d91SRobert Wubuilder.openStream(mStream); 242*05767d91SRobert Wu``` 243*05767d91SRobert Wu 244*05767d91SRobert Wu## Example 245*05767d91SRobert Wu 246*05767d91SRobert WuThe following class is a complete implementation of an audio player that 247*05767d91SRobert Wurenders a sine wave. 248*05767d91SRobert Wu``` 249*05767d91SRobert Wu#include <oboe/Oboe.h> 250*05767d91SRobert Wu#include <math.h> 251*05767d91SRobert Wuusing namespace oboe; 252*05767d91SRobert Wu 253*05767d91SRobert Wuclass OboeSinePlayer: public oboe::AudioStreamDataCallback { 254*05767d91SRobert Wupublic: 255*05767d91SRobert Wu 256*05767d91SRobert Wu virtual ~OboeSinePlayer() = default; 257*05767d91SRobert Wu 258*05767d91SRobert Wu // Call this from Activity onResume() 259*05767d91SRobert Wu int32_t startAudio() { 260*05767d91SRobert Wu std::lock_guard<std::mutex> lock(mLock); 261*05767d91SRobert Wu oboe::AudioStreamBuilder builder; 262*05767d91SRobert Wu // The builder set methods can be chained for convenience. 263*05767d91SRobert Wu Result result = builder.setSharingMode(oboe::SharingMode::Exclusive) 264*05767d91SRobert Wu ->setPerformanceMode(oboe::PerformanceMode::LowLatency) 265*05767d91SRobert Wu ->setChannelCount(kChannelCount) 266*05767d91SRobert Wu ->setSampleRate(kSampleRate) 267*05767d91SRobert Wu ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium) 268*05767d91SRobert Wu ->setFormat(oboe::AudioFormat::Float) 269*05767d91SRobert Wu ->setDataCallback(this) 270*05767d91SRobert Wu ->openStream(mStream); 271*05767d91SRobert Wu if (result != Result::OK) return (int32_t) result; 272*05767d91SRobert Wu 273*05767d91SRobert Wu // Typically, start the stream after querying some stream information, as well as some input from the user 274*05767d91SRobert Wu result = outStream->requestStart(); 275*05767d91SRobert Wu return (int32_t) result; 276*05767d91SRobert Wu } 277*05767d91SRobert Wu 278*05767d91SRobert Wu // Call this from Activity onPause() 279*05767d91SRobert Wu void stopAudio() { 280*05767d91SRobert Wu // Stop, close and delete in case not already closed. 281*05767d91SRobert Wu std::lock_guard<std::mutex> lock(mLock); 282*05767d91SRobert Wu if (mStream) { 283*05767d91SRobert Wu mStream->stop(); 284*05767d91SRobert Wu mStream->close(); 285*05767d91SRobert Wu mStream.reset(); 286*05767d91SRobert Wu } 287*05767d91SRobert Wu } 288*05767d91SRobert Wu 289*05767d91SRobert Wu oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override { 290*05767d91SRobert Wu float *floatData = (float *) audioData; 291*05767d91SRobert Wu for (int i = 0; i < numFrames; ++i) { 292*05767d91SRobert Wu float sampleValue = kAmplitude * sinf(mPhase); 293*05767d91SRobert Wu for (int j = 0; j < kChannelCount; j++) { 294*05767d91SRobert Wu floatData[i * kChannelCount + j] = sampleValue; 295*05767d91SRobert Wu } 296*05767d91SRobert Wu mPhase += mPhaseIncrement; 297*05767d91SRobert Wu if (mPhase >= kTwoPi) mPhase -= kTwoPi; 298*05767d91SRobert Wu } 299*05767d91SRobert Wu return oboe::DataCallbackResult::Continue; 300*05767d91SRobert Wu } 301*05767d91SRobert Wu 302*05767d91SRobert Wuprivate: 303*05767d91SRobert Wu std::mutex mLock; 304*05767d91SRobert Wu std::shared_ptr<oboe::AudioStream> mStream; 305*05767d91SRobert Wu 306*05767d91SRobert Wu // Stream params 307*05767d91SRobert Wu static int constexpr kChannelCount = 2; 308*05767d91SRobert Wu static int constexpr kSampleRate = 48000; 309*05767d91SRobert Wu // Wave params, these could be instance variables in order to modify at runtime 310*05767d91SRobert Wu static float constexpr kAmplitude = 0.5f; 311*05767d91SRobert Wu static float constexpr kFrequency = 440; 312*05767d91SRobert Wu static float constexpr kPI = M_PI; 313*05767d91SRobert Wu static float constexpr kTwoPi = kPI * 2; 314*05767d91SRobert Wu static double constexpr mPhaseIncrement = kFrequency * kTwoPi / (double) kSampleRate; 315*05767d91SRobert Wu // Keeps track of where the wave is 316*05767d91SRobert Wu float mPhase = 0.0; 317*05767d91SRobert Wu}; 318*05767d91SRobert Wu``` 319*05767d91SRobert WuNote that this implementation computes sine values at run-time for simplicity, 320*05767d91SRobert Wurather than pre-computing them. 321*05767d91SRobert WuAdditionally, best practice is to implement a separate data callback class, rather 322*05767d91SRobert Wuthan managing the stream and defining its data callback in the same class. 323*05767d91SRobert Wu 324*05767d91SRobert WuFor more examples on how to use Oboe look in the [samples](https://github.com/google/oboe/tree/main/samples) folder. 325*05767d91SRobert Wu 326*05767d91SRobert Wu## Obtaining optimal latency 327*05767d91SRobert WuOne of the goals of the Oboe library is to provide low latency audio streams on the widest range of hardware configurations. 328*05767d91SRobert WuWhen a stream is opened using AAudio, the optimal rate will be chosen unless the app requests a specific rate. The framesPerBurst is also provided by AAudio. 329*05767d91SRobert Wu 330*05767d91SRobert WuBut OpenSL ES cannot determine those values. So applications should query them using Java and then pass them to Oboe. They will be used for OpenSL ES streams on older devices. 331*05767d91SRobert Wu 332*05767d91SRobert WuHere's a code sample showing how to set these default values. 333*05767d91SRobert Wu 334*05767d91SRobert Wu*MainActivity.java* 335*05767d91SRobert Wu 336*05767d91SRobert Wu if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){ 337*05767d91SRobert Wu AudioManager myAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 338*05767d91SRobert Wu String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 339*05767d91SRobert Wu int defaultSampleRate = Integer.parseInt(sampleRateStr); 340*05767d91SRobert Wu String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 341*05767d91SRobert Wu int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr); 342*05767d91SRobert Wu 343*05767d91SRobert Wu native_setDefaultStreamValues(defaultSampleRate, defaultFramesPerBurst); 344*05767d91SRobert Wu } 345*05767d91SRobert Wu 346*05767d91SRobert Wu*jni-bridge.cpp* 347*05767d91SRobert Wu 348*05767d91SRobert Wu JNIEXPORT void JNICALL 349*05767d91SRobert Wu Java_com_google_sample_oboe_hellooboe_MainActivity_native_1setDefaultStreamValues(JNIEnv *env, 350*05767d91SRobert Wu jclass type, 351*05767d91SRobert Wu jint sampleRate, 352*05767d91SRobert Wu jint framesPerBurst) { 353*05767d91SRobert Wu oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate; 354*05767d91SRobert Wu oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst; 355*05767d91SRobert Wu } 356*05767d91SRobert Wu 357*05767d91SRobert WuNote that the values from Java are for built-in audio devices. Peripheral devices, such as Bluetooth may need larger framesPerBurst. 358*05767d91SRobert Wu 359*05767d91SRobert Wu# Further information 360*05767d91SRobert Wu- [Code samples](https://github.com/google/oboe/tree/main/samples) 361*05767d91SRobert Wu- [Full guide to Oboe](FullGuide.md) 362