1# Adding Oboe to your project 2There are two ways use Oboe in your Android Studio project: 3 41) **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 6or 7 82) **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 10## Option 1) Using pre-built binaries and headers 11 12Oboe 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 14Add 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 16 dependencies { 17 implementation 'com.google.oboe:oboe:X.X.X' 18 } 19 20Also enable prefab by adding: 21 22 android { 23 buildFeatures { 24 prefab true 25 } 26 } 27 28Include and link to oboe by updating your `CMakeLists.txt`: 29 30 find_package (oboe REQUIRED CONFIG) 31 target_link_libraries(native-lib oboe::oboe) # You may have other libraries here such as `log`. 32 33Here's a complete example `CMakeLists.txt` file: 34 35 cmake_minimum_required(VERSION 3.4.1) 36 37 # Build our own native library 38 add_library (native-lib SHARED native-lib.cpp ) 39 40 # Find the Oboe package 41 find_package (oboe REQUIRED CONFIG) 42 43 # Specify the libraries which our native library is dependent on, including Oboe 44 target_link_libraries(native-lib log oboe::oboe) 45 46Configure your app to use the shared STL by updating your `app/build.gradle`: 47 48 android { 49 defaultConfig { 50 externalNativeBuild { 51 cmake { 52 arguments "-DANDROID_STL=c++_shared" 53 } 54 } 55 } 56 } 57 58## Option 2) Building from source 59 60### 1. Clone the github repository 61Start by cloning the [latest stable release](https://github.com/google/oboe/releases/) of the Oboe repository, for example: 62 63 git clone -b 1.6-stable https://github.com/google/oboe 64 65**Make a note of the path which you cloned oboe into - you will need it shortly** 66 67If you use git as your version control system, consider adding Oboe as a [submodule](https://gist.github.com/gitaarik/8735255) (underneath your 68cpp directory) 69 70``` 71git submodule add https://github.com/google/oboe 72``` 73 74This makes it easier to integrate updates to Oboe into your app, as well as contribute to the Oboe project. 75 76### 2. Update CMakeLists.txt 77Open 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 79 80 81Now 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 83 # Set the path to the Oboe directory. 84 set (OBOE_DIR ***PATH TO OBOE***) 85 86 # Add the Oboe library as a subdirectory in your project. 87 # add_subdirectory tells CMake to look in this directory to 88 # compile oboe source files using oboe's CMake file. 89 # ./oboe specifies where the compiled binaries will be stored 90 add_subdirectory (${OBOE_DIR} ./oboe) 91 92 # Specify the path to the Oboe header files. 93 # This allows targets compiled with this CMake (application code) 94 # to see public Oboe headers, in order to access its API. 95 include_directories (${OBOE_DIR}/include) 96 97 98In the same file find the [`target_link_libraries`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) command. 99Add `oboe` to the list of libraries which your app's library depends on. For example: 100 101 target_link_libraries(native-lib oboe) 102 103Here's a complete example `CMakeLists.txt` file: 104 105 cmake_minimum_required(VERSION 3.4.1) 106 107 # Build our own native library 108 add_library (native-lib SHARED native-lib.cpp ) 109 110 # Build the Oboe library 111 set (OBOE_DIR ./oboe) 112 add_subdirectory (${OBOE_DIR} ./oboe) 113 114 # Make the Oboe public headers available to our app 115 include_directories (${OBOE_DIR}/include) 116 117 # Specify the libraries which our native library is dependent on, including Oboe 118 target_link_libraries (native-lib log oboe) 119 120 121Now go to `Build->Refresh Linked C++ Projects` to have Android Studio index the Oboe library. 122 123Verify that your project builds correctly. If you have any issues building please [report them here](issues/new). 124 125# Using Oboe 126Once 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 128## Creating an audio stream 129Include the Oboe header: 130 131 #include <oboe/Oboe.h> 132 133Streams are built using an `AudioStreamBuilder`. Create one like this: 134 135 oboe::AudioStreamBuilder builder; 136 137Use 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 139 builder.setDirection(oboe::Direction::Output); 140 builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); 141 builder.setSharingMode(oboe::SharingMode::Exclusive); 142 builder.setFormat(oboe::AudioFormat::Float); 143 builder.setChannelCount(oboe::ChannelCount::Mono); 144 145The builder's set methods return a pointer to the builder. So they can be easily chained: 146 147``` 148oboe::AudioStreamBuilder builder; 149builder.setPerformanceMode(oboe::PerformanceMode::LowLatency) 150 ->setSharingMode(oboe::SharingMode::Exclusive) 151 ->setDataCallback(myCallback) 152 ->setFormat(oboe::AudioFormat::Float); 153``` 154 155Define an `AudioStreamDataCallback` class to receive callbacks whenever the stream requires new data. 156 157 class MyCallback : public oboe::AudioStreamDataCallback { 158 public: 159 oboe::DataCallbackResult 160 onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) { 161 162 // We requested AudioFormat::Float. So if the stream opens 163 // we know we got the Float format. 164 // If you do not specify a format then you should check what format 165 // the stream has and cast to the appropriate type. 166 auto *outputData = static_cast<float *>(audioData); 167 168 // Generate random numbers (white noise) centered around zero. 169 const float amplitude = 0.2f; 170 for (int i = 0; i < numFrames; ++i){ 171 outputData[i] = ((float)drand48() - 0.5f) * 2 * amplitude; 172 } 173 174 return oboe::DataCallbackResult::Continue; 175 } 176 }; 177 178You can find examples of how to play sound using digital synthesis and pre-recorded audio in the [code samples](../samples). 179 180Declare your callback somewhere that it won't get deleted while you are using it. 181 182 MyCallback myCallback; 183 184Supply this callback class to the builder: 185 186 builder.setDataCallback(&myCallback); 187 188Declare 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 190 std::shared_ptr<oboe::AudioStream> mStream; 191 192Open the stream: 193 194 oboe::Result result = builder.openStream(mStream); 195 196Check 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 198 if (result != oboe::Result::OK) { 199 LOGE("Failed to create stream. Error: %s", oboe::convertToText(result)); 200 } 201 202Note 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 204## Playing audio 205Check the properties of the created stream. If you did not specify a channelCount, sampleRate, or format then you need to 206query 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 208 oboe::AudioFormat format = mStream->getFormat(); 209 LOGI("AudioStream format is %s", oboe::convertToText(format)); 210 211Now start the stream. 212 213 mStream->requestStart(); 214 215At this point you should start receiving callbacks. 216 217To stop receiving callbacks call 218 219 mStream->requestStop(); 220 221## Closing the stream 222It 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 224Streams should be explicitly closed when the app is no longer playing audio. 225 226 mStream->close(); 227 228`close()` is a blocking call which also stops the stream. 229 230For 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 232## Reconfiguring streams 233After closing, in order to change the configuration of the stream, simply call `openStream` 234again. The existing stream is deleted and a new stream is built and 235populates the `mStream` variable. 236``` 237// Modify the builder with some additional properties at runtime. 238builder.setDeviceId(MY_DEVICE_ID); 239// Re-open the stream with some additional config 240// The old AudioStream is automatically deleted 241builder.openStream(mStream); 242``` 243 244## Example 245 246The following class is a complete implementation of an audio player that 247renders a sine wave. 248``` 249#include <oboe/Oboe.h> 250#include <math.h> 251using namespace oboe; 252 253class OboeSinePlayer: public oboe::AudioStreamDataCallback { 254public: 255 256 virtual ~OboeSinePlayer() = default; 257 258 // Call this from Activity onResume() 259 int32_t startAudio() { 260 std::lock_guard<std::mutex> lock(mLock); 261 oboe::AudioStreamBuilder builder; 262 // The builder set methods can be chained for convenience. 263 Result result = builder.setSharingMode(oboe::SharingMode::Exclusive) 264 ->setPerformanceMode(oboe::PerformanceMode::LowLatency) 265 ->setChannelCount(kChannelCount) 266 ->setSampleRate(kSampleRate) 267 ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium) 268 ->setFormat(oboe::AudioFormat::Float) 269 ->setDataCallback(this) 270 ->openStream(mStream); 271 if (result != Result::OK) return (int32_t) result; 272 273 // Typically, start the stream after querying some stream information, as well as some input from the user 274 result = outStream->requestStart(); 275 return (int32_t) result; 276 } 277 278 // Call this from Activity onPause() 279 void stopAudio() { 280 // Stop, close and delete in case not already closed. 281 std::lock_guard<std::mutex> lock(mLock); 282 if (mStream) { 283 mStream->stop(); 284 mStream->close(); 285 mStream.reset(); 286 } 287 } 288 289 oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override { 290 float *floatData = (float *) audioData; 291 for (int i = 0; i < numFrames; ++i) { 292 float sampleValue = kAmplitude * sinf(mPhase); 293 for (int j = 0; j < kChannelCount; j++) { 294 floatData[i * kChannelCount + j] = sampleValue; 295 } 296 mPhase += mPhaseIncrement; 297 if (mPhase >= kTwoPi) mPhase -= kTwoPi; 298 } 299 return oboe::DataCallbackResult::Continue; 300 } 301 302private: 303 std::mutex mLock; 304 std::shared_ptr<oboe::AudioStream> mStream; 305 306 // Stream params 307 static int constexpr kChannelCount = 2; 308 static int constexpr kSampleRate = 48000; 309 // Wave params, these could be instance variables in order to modify at runtime 310 static float constexpr kAmplitude = 0.5f; 311 static float constexpr kFrequency = 440; 312 static float constexpr kPI = M_PI; 313 static float constexpr kTwoPi = kPI * 2; 314 static double constexpr mPhaseIncrement = kFrequency * kTwoPi / (double) kSampleRate; 315 // Keeps track of where the wave is 316 float mPhase = 0.0; 317}; 318``` 319Note that this implementation computes sine values at run-time for simplicity, 320rather than pre-computing them. 321Additionally, best practice is to implement a separate data callback class, rather 322than managing the stream and defining its data callback in the same class. 323 324For more examples on how to use Oboe look in the [samples](https://github.com/google/oboe/tree/main/samples) folder. 325 326## Obtaining optimal latency 327One of the goals of the Oboe library is to provide low latency audio streams on the widest range of hardware configurations. 328When 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 330But 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 332Here's a code sample showing how to set these default values. 333 334*MainActivity.java* 335 336 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){ 337 AudioManager myAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 338 String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 339 int defaultSampleRate = Integer.parseInt(sampleRateStr); 340 String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 341 int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr); 342 343 native_setDefaultStreamValues(defaultSampleRate, defaultFramesPerBurst); 344 } 345 346*jni-bridge.cpp* 347 348 JNIEXPORT void JNICALL 349 Java_com_google_sample_oboe_hellooboe_MainActivity_native_1setDefaultStreamValues(JNIEnv *env, 350 jclass type, 351 jint sampleRate, 352 jint framesPerBurst) { 353 oboe::DefaultStreamValues::SampleRate = (int32_t) sampleRate; 354 oboe::DefaultStreamValues::FramesPerBurst = (int32_t) framesPerBurst; 355 } 356 357Note that the values from Java are for built-in audio devices. Peripheral devices, such as Bluetooth may need larger framesPerBurst. 358 359# Further information 360- [Code samples](https://github.com/google/oboe/tree/main/samples) 361- [Full guide to Oboe](FullGuide.md) 362