aaudio: fix device switch detection in legacy path
Implement device switch detection on legacy path (AudioTrack and
AudioRecord) based on audio routing callbacks forcing the stream state
to disconnected.
Bug: 33355262
Bug: 62090113
Test: tested with write_sine and input_monitor command line tools.
Change-Id: I9e0421fee233964b1bf318acb640569196a00f13
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
index f89234a..dfac4fb 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -30,7 +30,7 @@
using namespace aaudio;
AudioStreamLegacy::AudioStreamLegacy()
- : AudioStream() {
+ : AudioStream(), mDeviceCallback(new StreamDeviceCallback(this)) {
}
AudioStreamLegacy::~AudioStreamLegacy() {
@@ -60,44 +60,54 @@
void AudioStreamLegacy::processCallbackCommon(aaudio_callback_operation_t opcode, void *info) {
aaudio_data_callback_result_t callbackResult;
+
+ if (!mCallbackEnabled.load()) {
+ return;
+ }
+
switch (opcode) {
case AAUDIO_CALLBACK_OPERATION_PROCESS_DATA: {
- // Note that this code assumes an AudioTrack::Buffer is the same as AudioRecord::Buffer
- // TODO define our own AudioBuffer and pass it from the subclasses.
- AudioTrack::Buffer *audioBuffer = static_cast<AudioTrack::Buffer *>(info);
- if (audioBuffer->frameCount == 0) return;
+ if (getState() != AAUDIO_STREAM_STATE_DISCONNECTED) {
+ // Note that this code assumes an AudioTrack::Buffer is the same as
+ // AudioRecord::Buffer
+ // TODO define our own AudioBuffer and pass it from the subclasses.
+ AudioTrack::Buffer *audioBuffer = static_cast<AudioTrack::Buffer *>(info);
+ if (audioBuffer->frameCount == 0) return;
- // If the caller specified an exact size then use a block size adapter.
- if (mBlockAdapter != nullptr) {
- int32_t byteCount = audioBuffer->frameCount * getBytesPerFrame();
- callbackResult = mBlockAdapter->processVariableBlock((uint8_t *) audioBuffer->raw,
- byteCount);
- } else {
- // Call using the AAudio callback interface.
- callbackResult = (*getDataCallbackProc())(
- (AAudioStream *) this,
- getDataCallbackUserData(),
- audioBuffer->raw,
- audioBuffer->frameCount
- );
- }
- if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
- audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame();
- incrementClientFrameCounter(audioBuffer->frameCount);
- } else {
- audioBuffer->size = 0;
+ // If the caller specified an exact size then use a block size adapter.
+ if (mBlockAdapter != nullptr) {
+ int32_t byteCount = audioBuffer->frameCount * getBytesPerFrame();
+ callbackResult = mBlockAdapter->processVariableBlock(
+ (uint8_t *) audioBuffer->raw, byteCount);
+ } else {
+ // Call using the AAudio callback interface.
+ callbackResult = (*getDataCallbackProc())(
+ (AAudioStream *) this,
+ getDataCallbackUserData(),
+ audioBuffer->raw,
+ audioBuffer->frameCount
+ );
+ }
+ if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
+ audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame();
+ incrementClientFrameCounter(audioBuffer->frameCount);
+ } else {
+ audioBuffer->size = 0;
+ }
+ break;
}
}
- break;
+ /// FALL THROUGH
// Stream got rerouted so we disconnect.
case AAUDIO_CALLBACK_OPERATION_DISCONNECTED: {
- ALOGD("AudioStreamAAudio(): callbackLoop() stream disconnected");
+ setState(AAUDIO_STREAM_STATE_DISCONNECTED);
+ ALOGD("processCallbackCommon() stream disconnected");
if (getErrorCallbackProc() != nullptr) {
(*getErrorCallbackProc())(
(AAudioStream *) this,
getErrorCallbackUserData(),
- AAUDIO_OK
+ AAUDIO_ERROR_DISCONNECTED
);
}
mCallbackEnabled.store(false);
@@ -129,3 +139,22 @@
status_t status = extendedTimestamp->getBestTimestamp(framePosition, timeNanoseconds, timebase);
return AAudioConvert_androidToAAudioResult(status);
}
+
+void AudioStreamLegacy::onAudioDeviceUpdate(audio_port_handle_t deviceId)
+{
+ ALOGD("onAudioDeviceUpdate() deviceId %d", (int)deviceId);
+ if (getDeviceId() != AAUDIO_UNSPECIFIED && getDeviceId() != deviceId &&
+ getState() != AAUDIO_STREAM_STATE_DISCONNECTED) {
+ setState(AAUDIO_STREAM_STATE_DISCONNECTED);
+ // if we have a data callback and the stream is active, send the error callback from
+ // data callback thread when it sees the DISCONNECTED state
+ if (!isDataCallbackActive() && getErrorCallbackProc() != nullptr) {
+ (*getErrorCallbackProc())(
+ (AAudioStream *) this,
+ getErrorCallbackUserData(),
+ AAUDIO_ERROR_DISCONNECTED
+ );
+ }
+ }
+ setDeviceId(deviceId);
+}
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.h b/media/libaaudio/src/legacy/AudioStreamLegacy.h
index 38f1a56..0ded8e1 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.h
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.h
@@ -18,6 +18,7 @@
#define LEGACY_AUDIO_STREAM_LEGACY_H
#include <media/AudioTimestamp.h>
+#include <media/AudioSystem.h>
#include <aaudio/AAudio.h>
@@ -75,14 +76,37 @@
protected:
+ class StreamDeviceCallback : public android::AudioSystem::AudioDeviceCallback
+ {
+ public:
+
+ StreamDeviceCallback(AudioStreamLegacy *parent) : mParent(parent) {}
+ virtual ~StreamDeviceCallback() {}
+
+ virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo __unused,
+ audio_port_handle_t deviceId) {
+ if (mParent != nullptr) {
+ mParent->onAudioDeviceUpdate(deviceId);
+ }
+ }
+
+ AudioStreamLegacy *mParent;
+ };
+
aaudio_result_t getBestTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds,
android::ExtendedTimestamp *extendedTimestamp);
+ void onAudioDeviceUpdate(audio_port_handle_t deviceId);
+
+ void onStart() { mCallbackEnabled.store(true); }
+ void onStop() { mCallbackEnabled.store(false); }
+
FixedBlockAdapter *mBlockAdapter = nullptr;
aaudio_wrapping_frames_t mPositionWhenStarting = 0;
int32_t mCallbackBufferSize = 0;
+ const android::sp<StreamDeviceCallback> mDeviceCallback;
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index 69dfb71..0918c2b 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -99,15 +99,21 @@
ALOGD("AudioStreamRecord::open(), request notificationFrames = %u, frameCount = %u",
notificationFrames, (uint)frameCount);
mAudioRecord = new AudioRecord(
+ mOpPackageName // const String16& opPackageName TODO does not compile
+ );
+ if (getDeviceId() != AAUDIO_UNSPECIFIED) {
+ mAudioRecord->setInputDevice(getDeviceId());
+ }
+ mAudioRecord->set(
AUDIO_SOURCE_VOICE_RECOGNITION,
getSampleRate(),
format,
channelMask,
- mOpPackageName, // const String16& opPackageName TODO does not compile
frameCount,
callback,
callbackData,
notificationFrames,
+ false /*threadCanCallJava*/,
AUDIO_SESSION_ALLOCATE,
streamTransferType,
flags
@@ -162,6 +168,8 @@
perfMode, actualPerformanceMode);
setState(AAUDIO_STREAM_STATE_OPEN);
+ setDeviceId(mAudioRecord->getRoutedDeviceId());
+ mAudioRecord->addAudioDeviceCallback(mDeviceCallback);
return AAUDIO_OK;
}
@@ -209,6 +217,7 @@
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
} else {
+ onStart();
setState(AAUDIO_STREAM_STATE_STARTING);
}
return AAUDIO_OK;
@@ -230,6 +239,7 @@
if (mAudioRecord.get() == nullptr) {
return AAUDIO_ERROR_INVALID_STATE;
}
+ onStop();
setState(AAUDIO_STREAM_STATE_STOPPING);
incrementFramesWritten(getFramesRead() - getFramesWritten()); // TODO review
mAudioRecord->stop();
@@ -274,12 +284,22 @@
return result;
}
+ if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ return AAUDIO_ERROR_DISCONNECTED;
+ }
+
// TODO add timeout to AudioRecord
bool blocking = (timeoutNanoseconds > 0);
ssize_t bytesRead = mAudioRecord->read(buffer, numBytes, blocking);
if (bytesRead == WOULD_BLOCK) {
return 0;
} else if (bytesRead < 0) {
+ // in this context, a DEAD_OBJECT is more likely to be a disconnect notification due to
+ // AudioRecord invalidation
+ if (bytesRead == DEAD_OBJECT) {
+ setState(AAUDIO_STREAM_STATE_DISCONNECTED);
+ return AAUDIO_ERROR_DISCONNECTED;
+ }
return AAudioConvert_androidToAAudioResult(bytesRead);
}
int32_t framesRead = (int32_t)(bytesRead / bytesPerFrame);
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index e566332..1e3119c 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -115,7 +115,11 @@
ALOGD("AudioStreamTrack::open(), request notificationFrames = %d, frameCount = %u",
notificationFrames, (uint)frameCount);
- mAudioTrack = new AudioTrack(
+ mAudioTrack = new AudioTrack();
+ if (getDeviceId() != AAUDIO_UNSPECIFIED) {
+ mAudioTrack->setOutputDevice(getDeviceId());
+ }
+ mAudioTrack->set(
(audio_stream_type_t) AUDIO_STREAM_MUSIC,
getSampleRate(),
format,
@@ -125,6 +129,8 @@
callback,
callbackData,
notificationFrames,
+ 0 /*sharedBuffer*/,
+ false /*threadCanCallJava*/,
AUDIO_SESSION_ALLOCATE,
streamTransferType
);
@@ -160,6 +166,7 @@
setState(AAUDIO_STREAM_STATE_OPEN);
setDeviceId(mAudioTrack->getRoutedDeviceId());
+ mAudioTrack->addAudioDeviceCallback(mDeviceCallback);
// Update performance mode based on the actual stream.
// For example, if the sample rate is not allowed then you won't get a FAST track.
@@ -229,6 +236,7 @@
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
} else {
+ onStart();
setState(AAUDIO_STREAM_STATE_STARTING);
}
return AAUDIO_OK;
@@ -246,6 +254,7 @@
AAudio_convertStreamStateToText(getState()));
return AAUDIO_ERROR_INVALID_STATE;
}
+ onStop();
setState(AAUDIO_STREAM_STATE_PAUSING);
mAudioTrack->pause();
status_t err = mAudioTrack->getPosition(&mPositionWhenPausing);
@@ -276,6 +285,7 @@
if (mAudioTrack.get() == nullptr) {
return AAUDIO_ERROR_INVALID_STATE;
}
+ onStop();
setState(AAUDIO_STREAM_STATE_STOPPING);
incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review
mAudioTrack->stop();
@@ -339,6 +349,10 @@
return result;
}
+ if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED) {
+ return AAUDIO_ERROR_DISCONNECTED;
+ }
+
// TODO add timeout to AudioTrack
bool blocking = timeoutNanoseconds > 0;
ssize_t bytesWritten = mAudioTrack->write(buffer, numBytes, blocking);
@@ -346,6 +360,12 @@
return 0;
} else if (bytesWritten < 0) {
ALOGE("invalid write, returned %d", (int)bytesWritten);
+ // in this context, a DEAD_OBJECT is more likely to be a disconnect notification due to
+ // AudioTrack invalidation
+ if (bytesWritten == DEAD_OBJECT) {
+ setState(AAUDIO_STREAM_STATE_DISCONNECTED);
+ return AAUDIO_ERROR_DISCONNECTED;
+ }
return AAudioConvert_androidToAAudioResult(bytesWritten);
}
int32_t framesWritten = (int32_t)(bytesWritten / bytesPerFrame);
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index 750e8ad..e749ac4 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -481,12 +481,14 @@
AutoMutex lock(mLock);
if (mSelectedDeviceId != deviceId) {
mSelectedDeviceId = deviceId;
- // stop capture so that audio policy manager does not reject the new instance start request
- // as only one capture can be active at a time.
- if (mAudioRecord != 0 && mActive) {
- mAudioRecord->stop();
+ if (mStatus == NO_ERROR) {
+ // stop capture so that audio policy manager does not reject the new instance start request
+ // as only one capture can be active at a time.
+ if (mAudioRecord != 0 && mActive) {
+ mAudioRecord->stop();
+ }
+ android_atomic_or(CBLK_INVALID, &mCblk->mFlags);
}
- android_atomic_or(CBLK_INVALID, &mCblk->mFlags);
}
return NO_ERROR;
}
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index 5cd2789..9ef1db7 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -19,6 +19,7 @@
#include <utils/Log.h>
#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
#include <media/AudioSystem.h>
#include <media/IAudioFlinger.h>
#include <media/IAudioPolicyService.h>
@@ -68,6 +69,8 @@
gAudioFlinger = interface_cast<IAudioFlinger>(binder);
LOG_ALWAYS_FATAL_IF(gAudioFlinger == 0);
afc = gAudioFlingerClient;
+ // Make sure callbacks can be received by gAudioFlingerClient
+ ProcessState::self()->startThreadPool();
}
af = gAudioFlinger;
}
@@ -711,6 +714,8 @@
gAudioPolicyService = interface_cast<IAudioPolicyService>(binder);
LOG_ALWAYS_FATAL_IF(gAudioPolicyService == 0);
apc = gAudioPolicyServiceClient;
+ // Make sure callbacks can be received by gAudioPolicyServiceClient
+ ProcessState::self()->startThreadPool();
}
ap = gAudioPolicyService;
}
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 38d90bc..ffb7703 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -1209,7 +1209,9 @@
AutoMutex lock(mLock);
if (mSelectedDeviceId != deviceId) {
mSelectedDeviceId = deviceId;
- android_atomic_or(CBLK_INVALID, &mCblk->mFlags);
+ if (mStatus == NO_ERROR) {
+ android_atomic_or(CBLK_INVALID, &mCblk->mFlags);
+ }
}
return NO_ERROR;
}