xref: /aosp_15_r20/external/oboe/docs/GettingStarted.md (revision 05767d913155b055644481607e6fa1e35e2fe72c)
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![CMakeLists.txt location in Android Studio](images/cmakelists-location-in-as.png "CMakeLists.txt location in Android Studio")
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