Skip to content

Commit 4bbb563

Browse files
authored
Support routing changed callback. (#2348)
* Support routing changed callback. Fixes #2347.
1 parent 7d4dc30 commit 4bbb563

13 files changed

Lines changed: 264 additions & 7 deletions

File tree

apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,12 +929,22 @@ void ActivityDataPath::finishOpen(bool isInput, std::shared_ptr<oboe::AudioStrea
929929
}
930930

931931
// =================================================================== ActivityTestDisconnect
932+
ActivityTestDisconnect::ActivityTestDisconnect() {
933+
mRoutingCallback = std::make_shared<TestRoutingCallback>(this);
934+
}
935+
932936
void ActivityTestDisconnect::close(int32_t streamIndex) {
933937
ActivityContext::close(streamIndex);
934938
mSinkFloat.reset();
935939
}
936940

941+
void ActivityTestDisconnect::configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) {
942+
ActivityContext::configureBuilder(isInput, builder);
943+
builder.setRoutingCallback(mRoutingCallback);
944+
}
945+
937946
void ActivityTestDisconnect::configureAfterOpen() {
947+
mRoutingChangedCount = 0;
938948
std::shared_ptr<oboe::AudioStream> outputStream = getOutputStream();
939949
std::shared_ptr<oboe::AudioStream> inputStream = getInputStream();
940950
if (outputStream) {

apps/OboeTester/app/src/main/cpp/NativeAudioContext.h

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ class ActivityDataPath : public ActivityFullDuplex {
813813
*/
814814
class ActivityTestDisconnect : public ActivityContext {
815815
public:
816-
ActivityTestDisconnect() {}
816+
ActivityTestDisconnect();
817817

818818
virtual ~ActivityTestDisconnect() = default;
819819

@@ -832,9 +832,35 @@ class ActivityTestDisconnect : public ActivityContext {
832832
return oboe::Result::ErrorNull;
833833
}
834834

835+
void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
836+
835837
void configureAfterOpen() override;
836838

839+
int32_t getRoutingChangedCount() {
840+
return mRoutingChangedCount;
841+
}
842+
843+
void onRoutingChanged() {
844+
mRoutingChangedCount++;
845+
}
846+
837847
private:
848+
class TestRoutingCallback : public oboe::AudioStreamRoutingCallback {
849+
public:
850+
TestRoutingCallback(ActivityTestDisconnect *activity)
851+
: mActivity(activity) {}
852+
virtual ~TestRoutingCallback() = default;
853+
void onRoutingChanged(oboe::AudioStream* /* audioStream */,
854+
const int32_t* /* deviceIds */,
855+
int32_t /* numDevices */) override {
856+
mActivity->onRoutingChanged();
857+
}
858+
private:
859+
ActivityTestDisconnect *mActivity;
860+
};
861+
862+
std::shared_ptr<TestRoutingCallback> mRoutingCallback;
863+
std::atomic<int32_t> mRoutingChangedCount{0};
838864
std::unique_ptr<SineOscillator> sineOscillator;
839865
std::unique_ptr<MonoToMultiConverter> monoToMulti;
840866
std::shared_ptr<oboe::flowgraph::SinkFloat> mSinkFloat;

apps/OboeTester/app/src/main/cpp/jni-bridge.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,4 +1643,9 @@ Java_com_mobileer_oboetester_ReverseJniEngine_setAudioBuffer(JNIEnv *env, jobjec
16431643
}
16441644
}
16451645

1646+
JNIEXPORT jint JNICALL
1647+
Java_com_mobileer_oboetester_TestDisconnectActivity_getRoutingChangedCount(JNIEnv *env, jobject) {
1648+
return engine.mActivityTestDisconnect.getRoutingChangedCount();
1649+
}
1650+
16461651
} // extern "C"

apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDisconnectActivity.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public class TestDisconnectActivity extends TestAudioActivity {
6464

6565
protected AutomatedTestRunner mAutomatedTestRunner;
6666

67+
private native int getRoutingChangedCount();
68+
6769
// Receive a broadcast Intent when a headset is plugged in or unplugged.
6870
// Display a count on screen.
6971
public class PluginBroadcastReceiver extends BroadcastReceiver {
@@ -396,6 +398,7 @@ private void testConfiguration(boolean isInput,
396398
}
397399

398400
int oldPlugCount = mPlugCount;
401+
int oldRoutingChangedCount = getRoutingChangedCount();
399402
if (!openFailed && valid) {
400403
mTestFailed = false;
401404
// poll until stream started
@@ -418,12 +421,15 @@ private void testConfiguration(boolean isInput,
418421
}
419422
}
420423

424+
int currentRoutingCount = getRoutingChangedCount();
421425
// Wait for timeout or stream to disconnect.
422426
while (!mTestFailed && mAutomatedTestRunner.isThreadEnabled() && !mSkipTest && (timeoutCount > 0) &&
423-
stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) {
427+
stream.getState() == StreamConfiguration.STREAM_STATE_STARTED &&
428+
oldRoutingChangedCount == currentRoutingCount) {
424429
flushLog();
425430
Thread.sleep(POLL_DURATION_MILLIS);
426431
timeoutCount--;
432+
currentRoutingCount = getRoutingChangedCount();
427433
if (timeoutCount == 0) {
428434
mTestFailed = true;
429435
} else {
@@ -442,9 +448,12 @@ private void testConfiguration(boolean isInput,
442448
}
443449
} else {
444450
int error = stream.getLastErrorCallbackResult();
445-
if (error != StreamConfiguration.ERROR_DISCONNECTED) {
451+
if (error != StreamConfiguration.ERROR_DISCONNECTED &&
452+
oldRoutingChangedCount == currentRoutingCount) {
446453
log("onErrorCallback error = " + error
447-
+ ", expected " + StreamConfiguration.ERROR_DISCONNECTED);
454+
+ ", expected " + StreamConfiguration.ERROR_DISCONNECTED +
455+
", old routing count = " + oldRoutingChangedCount +
456+
", current routing count = " + currentRoutingCount);
448457
mTestFailed = true;
449458
}
450459
}

docs/FullGuide.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ builder setting:
202202
| `setChannelConversionAllowed()` | `isChannelConversionAllowed()` |
203203
| `setFormatConversionAllowed()` | `setFormatConversionAllowed()` |
204204
| `setSampleRateConversionQuality` | `getSampleRateConversionQuality()` |
205+
| `setRoutingCallback()` | `getRoutingCallback()` |
205206

206207
### AAudio specific AudioStreamBuilder fields
207208

@@ -364,6 +365,20 @@ You can prime the stream's buffer before starting the stream by writing data or
364365

365366
The data in the buffer must match the data format returned by `mStream->getDataFormat()`.
366367

368+
### Handle routing changed event
369+
370+
A routing changed can happen when there is any peripheral connected/disconnected or any system-wide event, such as system Settings enable/disable a route, happens.
371+
372+
Before Android API level 37, when the routed device(s) is changed, Android AAudio framework will always disconnect the stream. See **Disconnected audio stream** section for more information about stream disconnection.
373+
In Android API level 37, routing changed callback is introduced to notify apps when the routed devices are changed.
374+
375+
Instead of disconnecting stream at all cases, Android AAudio will now evaluate if the audio configuration stays similar when the routed device(s) is changed.
376+
If the configuration stays the same but just the routed device(s) is changed, AAudio framework will fire a routing changed callback if it is register when opening.
377+
This usually happens when the stream is offloaded or it is not on mmap path. If the audio configuration may be affected by routing changed, AAudio framework will
378+
disconnect the stream. See **Disconnected audio stream** section for how to handle stream disconnection.
379+
380+
If your apps need to be notified when the routed device is changed, write a class which extends `AudioStreamRoutingCallback` and then register your class using `builder.setRoutingCallback(yourCallbackClass)`.
381+
367382
### Closing an audio stream
368383

369384
When you are finished using a stream, close it:

include/oboe/AudioStreamBase.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ class AudioStreamBase {
104104
/**
105105
* @return the device ID of the stream.
106106
*/
107-
int32_t getDeviceId() const {
107+
virtual int32_t getDeviceId() const {
108108
return mDeviceIds.empty() ? kUnspecified : mDeviceIds[0];
109109
}
110110

111-
std::vector<int32_t> getDeviceIds() const {
111+
virtual std::vector<int32_t> getDeviceIds() const {
112112
return mDeviceIds;
113113
}
114114

@@ -144,6 +144,14 @@ class AudioStreamBase {
144144
return mSharedPresentationCallback;
145145
}
146146

147+
/**
148+
* For internal use only.
149+
* @return the routing callback object for this stream, if set.
150+
*/
151+
std::shared_ptr<AudioStreamRoutingCallback> getRoutingCallback() const {
152+
return mSharedRoutingCallback;
153+
}
154+
147155
/**
148156
* @return true if a data callback was set for this stream
149157
*/
@@ -316,6 +324,8 @@ class AudioStreamBase {
316324

317325
std::shared_ptr<AudioStreamPresentationCallback> mSharedPresentationCallback;
318326

327+
std::shared_ptr<AudioStreamRoutingCallback> mSharedRoutingCallback;
328+
319329
/** Number of audio frames which will be requested in each callback */
320330
int32_t mFramesPerCallback = kUnspecified;
321331
/** Stream channel count */

include/oboe/AudioStreamBuilder.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,26 @@ class AudioStreamBuilder : public AudioStreamBase {
599599
return this;
600600
}
601601

602+
/**
603+
* Specifies an object to handle routing related callbacks from the underlying API.
604+
* This will be called when the routed devices of the stream are changed.
605+
*
606+
* <strong>Important: See AudioStreamCallback for restrictions on what may be called
607+
* from the callback methods.</strong>
608+
*
609+
* We pass a shared_ptr so that the routingCallback object cannot be deleted before the
610+
* stream is deleted. If the stream was created using a shared_ptr then the stream cannot be
611+
* deleted before the routing callback has finished running.
612+
*
613+
* @param sharedRoutingCallback
614+
* @return pointer to the builder so calls can be chained
615+
*/
616+
AudioStreamBuilder *setRoutingCallback(
617+
std::shared_ptr<AudioStreamRoutingCallback> sharedRoutingCallback) {
618+
mSharedRoutingCallback = sharedRoutingCallback;
619+
return this;
620+
}
621+
602622
/**
603623
* Specifies an object to handle data or error related callbacks from the underlying API.
604624
*

include/oboe/AudioStreamCallback.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,26 @@ class AudioStreamPresentationCallback {
190190
virtual void onPresentationEnded(AudioStream* /* audioStream */) {}
191191
};
192192

193+
/**
194+
* AudioStreamRoutingCallback defines a callback interface for
195+
* being notified when the routed devices of a stream are changed.
196+
*
197+
* It is used with AudioStreamBuilder::setRoutingCallback().
198+
*/
199+
class AudioStreamRoutingCallback {
200+
public:
201+
/**
202+
* This will be called when the routed devices of the stream are changed.
203+
*
204+
* @param audioStream pointer to the associated stream
205+
* @param deviceIds a pointer to a list of current routed device ids.
206+
* @param numDevices the number of current routed devices.
207+
*/
208+
virtual void onRoutingChanged(AudioStream* /* audioStream */,
209+
const int32_t* /* deviceIds */,
210+
int32_t /* numDevices */) {}
211+
};
212+
193213
/**
194214
* AudioStreamPartialDataCallback defines a callback interface for
195215
* moving data to/from an audio stream using `onAudioReady`

src/aaudio/AAudioLoader.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ int AAudioLoader::open() {
106106
builder_setPresentationEndCallback = load_V_PBPRPV("AAudioStreamBuilder_setPresentationEndCallback");
107107
}
108108

109+
if (getSdkVersion() >= __ANDROID_API_C__) {
110+
builder_setRoutingChangedCallback = load_V_PBRCCPV("AAudioStreamBuilder_setRoutingChangedCallback");
111+
}
112+
109113
builder_delete = load_I_PB("AAudioStreamBuilder_delete");
110114

111115

@@ -360,6 +364,12 @@ AAudioLoader::signature_V_PBPRPV AAudioLoader::load_V_PBPRPV(const char *functio
360364
return reinterpret_cast<signature_V_PBPRPV>(proc);
361365
}
362366

367+
AAudioLoader::signature_V_PBRCCPV AAudioLoader::load_V_PBRCCPV(const char *functionName) {
368+
void *proc = dlsym(mLibHandle, functionName);
369+
AAudioLoader_check(proc, functionName);
370+
return reinterpret_cast<signature_V_PBRCCPV>(proc);
371+
}
372+
363373
AAudioLoader::signature_I_PSII AAudioLoader::load_I_PSII(const char *functionName) {
364374
void *proc = dlsym(mLibHandle, functionName);
365375
AAudioLoader_check(proc, functionName);

src/aaudio/AAudioLoader.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ typedef void (*AAudioStream_presentationEndCallback)(
8282
void* userData);
8383
#endif
8484

85+
#if OBOE_USING_NDK && __NDK_MAJOR__ < 30
86+
// Defined in Android C
87+
typedef void (*AAudioStream_routingChangedCallback)(
88+
AAudioStream* stream,
89+
void* userData,
90+
const int32_t* deviceIds,
91+
int32_t numDevices);
92+
#endif
93+
8594
#ifndef __ANDROID_API_Q__
8695
#define __ANDROID_API_Q__ 29
8796
#endif
@@ -106,6 +115,10 @@ typedef void (*AAudioStream_presentationEndCallback)(
106115
#define __ANDROID_API_B__ 36
107116
#endif
108117

118+
#ifndef __ANDROID_API_C__
119+
#define __ANDROID_API_C__ 37
120+
#endif
121+
109122
#if OBOE_USING_NDK && __NDK_MAJOR__ < 30
110123
// These were defined in Android B
111124
typedef int32_t AAudio_DeviceType;
@@ -201,6 +214,10 @@ class AAudioLoader {
201214
AAudioStream_presentationEndCallback,
202215
void *);
203216

217+
typedef void (*signature_V_PBRCCPV)(AAudioStreamBuilder *,
218+
AAudioStream_routingChangedCallback,
219+
void *);
220+
204221
typedef aaudio_format_t (*signature_F_PS)(AAudioStream *stream);
205222

206223
typedef int32_t (*signature_I_PSPVIL)(AAudioStream *, void *, int32_t, int64_t);
@@ -278,6 +295,7 @@ class AAudioLoader {
278295
signature_V_PBPDPV builder_setDataCallback = nullptr;
279296
signature_V_PBPEPV builder_setErrorCallback = nullptr;
280297
signature_V_PBPRPV builder_setPresentationEndCallback = nullptr;
298+
signature_V_PBRCCPV builder_setRoutingChangedCallback = nullptr;
281299
signature_V_PBPDPV builder_setPartialDataCallback = nullptr;
282300

283301
signature_I_PB builder_delete = nullptr;
@@ -382,6 +400,7 @@ class AAudioLoader {
382400
signature_I_I load_I_I(const char *name);
383401
signature_I load_I(const char *name);
384402
signature_V_PBPRPV load_V_PBPRPV(const char *name);
403+
signature_V_PBRCCPV load_V_PBRCCPV(const char *name);
385404
signature_I_PSII load_I_PSII(const char *name);
386405
signature_I_PSPIPI load_I_PSPIPI(const char *name);
387406
signature_I_PSIPL load_I_PSIPL(const char *name);

0 commit comments

Comments
 (0)