aaudio: convert I16 input to float
This allows an app that requests a LOW_LATENCY FLOAT INPUT stream
to get a FAST track when using Legacy mode.
This is needed because Legacy AudioRecord does not allow FAST for FLOAT
streams.
Bug: 38268031
Test: adb shell input_monitor -m1 -pl -f2
Test: adb shell input_monitor_callback -m1 -pl -f2
Test: On Pixel phones you should get actual performanceMode=12
Change-Id: Ia5879ff4904f50bbb5009a3fc058800e53770710
diff --git a/media/libaaudio/src/Android.bp b/media/libaaudio/src/Android.bp
index 788833b..b9e28a0 100644
--- a/media/libaaudio/src/Android.bp
+++ b/media/libaaudio/src/Android.bp
@@ -57,6 +57,7 @@
shared_libs: [
"libaudioclient",
+ "libaudioutils",
"liblog",
"libcutils",
"libutils",
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 2a3e668..ff138aa 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -156,7 +156,7 @@
setInputPreset(configurationOutput.getInputPreset());
// Save device format so we can do format conversion and volume scaling together.
- mDeviceFormat = configurationOutput.getFormat();
+ setDeviceFormat(configurationOutput.getFormat());
result = mServiceInterface.getStreamDescription(mServiceStreamHandle, mEndPointParcelable);
if (result != AAUDIO_OK) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 0e0724b..0425cd5 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -138,8 +138,6 @@
// Calculate timeout for an operation involving framesPerOperation.
int64_t calculateReasonableTimeout(int32_t framesPerOperation);
- aaudio_format_t getDeviceFormat() const { return mDeviceFormat; }
-
int32_t getDeviceChannelCount() const { return mDeviceChannelCount; }
/**
@@ -195,9 +193,6 @@
int64_t mServiceLatencyNanos = 0;
- // Sometimes the hardware is operating with a different format or channel count from the app.
- // Then we require conversion in AAudio.
- aaudio_format_t mDeviceFormat = AAUDIO_FORMAT_UNSPECIFIED;
int32_t mDeviceChannelCount = 0;
};
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 61e03db..acb82c8 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -367,7 +367,6 @@
return err ? AAudioConvert_androidToAAudioResult(-errno) : mThreadRegistrationResult;
}
-
aaudio_data_callback_result_t AudioStream::maybeCallDataCallback(void *audioData,
int32_t numFrames) {
aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_STOP;
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 5273e36..c46bdc5 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -252,6 +252,20 @@
return AAudioConvert_formatToSizeInBytes(mFormat);
}
+ /**
+ * This is only valid after setSamplesPerFrame() and setDeviceFormat() have been called.
+ */
+ int32_t getBytesPerDeviceFrame() const {
+ return mSamplesPerFrame * getBytesPerDeviceSample();
+ }
+
+ /**
+ * This is only valid after setDeviceFormat() has been called.
+ */
+ int32_t getBytesPerDeviceSample() const {
+ return AAudioConvert_formatToSizeInBytes(getDeviceFormat());
+ }
+
virtual int64_t getFramesWritten() = 0;
virtual int64_t getFramesRead() = 0;
@@ -471,6 +485,17 @@
mFormat = format;
}
+ /**
+ * This should not be called after the open() call.
+ */
+ void setDeviceFormat(aaudio_format_t format) {
+ mDeviceFormat = format;
+ }
+
+ aaudio_format_t getDeviceFormat() const {
+ return mDeviceFormat;
+ }
+
void setState(aaudio_stream_state_t state);
void setDeviceId(int32_t deviceId) {
@@ -485,9 +510,23 @@
float mDuckAndMuteVolume = 1.0f;
-
protected:
+ /**
+ * Either convert the data from device format to app format and return a pointer
+ * to the conversion buffer,
+ * OR just pass back the original pointer.
+ *
+ * Note that this is only used for the INPUT path.
+ *
+ * @param audioData
+ * @param numFrames
+ * @return original pointer or the conversion buffer
+ */
+ virtual const void * maybeConvertDeviceData(const void *audioData, int32_t numFrames) {
+ return audioData;
+ }
+
void setPeriodNanoseconds(int64_t periodNanoseconds) {
mPeriodNanoseconds.store(periodNanoseconds, std::memory_order_release);
}
@@ -539,6 +578,10 @@
int32_t mSessionId = AAUDIO_UNSPECIFIED;
+ // Sometimes the hardware is operating with a different format from the app.
+ // Then we require conversion in AAudio.
+ aaudio_format_t mDeviceFormat = AAUDIO_FORMAT_UNSPECIFIED;
+
// callback ----------------------------------
AAudioStream_dataCallback mDataCallbackProc = nullptr; // external callback functions
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
index 8bbb9d9..a6b9f5d 100644
--- a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -19,10 +19,12 @@
#include <utils/Log.h>
#include <stdint.h>
-#include <utils/String16.h>
+
+#include <aaudio/AAudio.h>
+#include <audio_utils/primitives.h>
#include <media/AudioTrack.h>
#include <media/AudioTimestamp.h>
-#include <aaudio/AAudio.h>
+#include <utils/String16.h>
#include "core/AudioStream.h"
#include "legacy/AudioStreamLegacy.h"
@@ -48,14 +50,17 @@
return AudioStreamLegacy_callback;
}
-aaudio_data_callback_result_t AudioStreamLegacy::callDataCallbackFrames(uint8_t *buffer, int32_t numFrames) {
+aaudio_data_callback_result_t AudioStreamLegacy::callDataCallbackFrames(uint8_t *buffer,
+ int32_t numFrames) {
+ void *finalAudioData = buffer;
if (getDirection() == AAUDIO_DIRECTION_INPUT) {
// Increment before because we already got the data from the device.
incrementFramesRead(numFrames);
+ finalAudioData = (void *) maybeConvertDeviceData(buffer, numFrames);
}
// Call using the AAudio callback interface.
- aaudio_data_callback_result_t callbackResult = maybeCallDataCallback(buffer, numFrames);
+ aaudio_data_callback_result_t callbackResult = maybeCallDataCallback(finalAudioData, numFrames);
if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE
&& getDirection() == AAUDIO_DIRECTION_OUTPUT) {
@@ -67,15 +72,15 @@
// Implement FixedBlockProcessor
int32_t AudioStreamLegacy::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
- int32_t numFrames = numBytes / getBytesPerFrame();
+ int32_t numFrames = numBytes / getBytesPerDeviceFrame();
return (int32_t) callDataCallbackFrames(buffer, numFrames);
}
void AudioStreamLegacy::processCallbackCommon(aaudio_callback_operation_t opcode, void *info) {
aaudio_data_callback_result_t callbackResult;
- // This illegal size can be used to AudioFlinger to stop calling us.
+ // This illegal size can be used to tell AudioFlinger to stop calling us.
// This takes advantage of AudioFlinger killing the stream.
- // TODO need API change in AudioRecord and AudioTrack
+ // TODO add to API in AudioRecord and AudioTrack
const size_t SIZE_STOP_CALLBACKS = SIZE_MAX;
switch (opcode) {
@@ -100,7 +105,7 @@
// If the caller specified an exact size then use a block size adapter.
if (mBlockAdapter != nullptr) {
- int32_t byteCount = audioBuffer->frameCount * getBytesPerFrame();
+ int32_t byteCount = audioBuffer->frameCount * getBytesPerDeviceFrame();
callbackResult = mBlockAdapter->processVariableBlock(
(uint8_t *) audioBuffer->raw, byteCount);
} else {
@@ -109,7 +114,7 @@
audioBuffer->frameCount);
}
if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
- audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame();
+ audioBuffer->size = audioBuffer->frameCount * getBytesPerDeviceFrame();
} else { // STOP or invalid result
ALOGW("%s() callback requested stop, fake an error", __func__);
audioBuffer->size = SIZE_STOP_CALLBACKS;
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index 28158e2..1981ba3 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -19,13 +19,15 @@
#include <utils/Log.h>
#include <stdint.h>
-#include <utils/String16.h>
-#include <media/AudioRecord.h>
-#include <aaudio/AAudio.h>
-#include "AudioClock.h"
+#include <aaudio/AAudio.h>
+#include <audio_utils/primitives.h>
+#include <media/AudioRecord.h>
+#include <utils/String16.h>
+
#include "legacy/AudioStreamLegacy.h"
#include "legacy/AudioStreamRecord.h"
+#include "utility/AudioClock.h"
#include "utility/FixedBlockWriter.h"
using namespace android;
@@ -63,10 +65,6 @@
size_t frameCount = (builder.getBufferCapacity() == AAUDIO_UNSPECIFIED) ? 0
: builder.getBufferCapacity();
- // TODO implement an unspecified Android format then use that.
- audio_format_t format = (getFormat() == AAUDIO_FORMAT_UNSPECIFIED)
- ? AUDIO_FORMAT_PCM_FLOAT
- : AAudioConvert_aaudioToAndroidDataFormat(getFormat());
audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE;
aaudio_performance_mode_t perfMode = getPerformanceMode();
@@ -82,6 +80,35 @@
break;
}
+ // Preserve behavior of API 26
+ if (getFormat() == AAUDIO_FORMAT_UNSPECIFIED) {
+ setFormat(AAUDIO_FORMAT_PCM_FLOAT);
+ }
+
+ // Maybe change device format to get a FAST path.
+ // AudioRecord does not support FAST mode for FLOAT data.
+ // TODO AudioRecord should allow FLOAT data paths for FAST tracks.
+ // So IF the user asks for low latency FLOAT
+ // AND the sampleRate is likely to be compatible with FAST
+ // THEN request I16 and convert to FLOAT when passing to user.
+ // Note that hard coding 48000 Hz is not ideal because the sampleRate
+ // for a FAST path might not be 48000 Hz.
+ // It normally is but there is a chance that it is not.
+ // And there is no reliable way to know that in advance.
+ // Luckily the consequences of a wrong guess are minor.
+ // We just may not get a FAST track.
+ // But we wouldn't have anyway without this hack.
+ constexpr int32_t kMostLikelySampleRateForFast = 48000;
+ if (getFormat() == AAUDIO_FORMAT_PCM_FLOAT
+ && perfMode == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
+ && (samplesPerFrame <= 2) // FAST only for mono and stereo
+ && (getSampleRate() == kMostLikelySampleRateForFast
+ || getSampleRate() == AAUDIO_UNSPECIFIED)) {
+ setDeviceFormat(AAUDIO_FORMAT_PCM_I16);
+ } else {
+ setDeviceFormat(getFormat());
+ }
+
uint32_t notificationFrames = 0;
// Setup the callback if there is one.
@@ -96,9 +123,6 @@
}
mCallbackBufferSize = builder.getFramesPerDataCallback();
- ALOGD("open(), request notificationFrames = %u, frameCount = %u",
- notificationFrames, (uint)frameCount);
-
// Don't call mAudioRecord->setInputDevice() because it will be overwritten by set()!
audio_port_handle_t selectedDeviceId = (getDeviceId() == AAUDIO_UNSPECIFIED)
? AUDIO_PORT_HANDLE_NONE
@@ -120,39 +144,59 @@
aaudio_session_id_t requestedSessionId = builder.getSessionId();
audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
- mAudioRecord = new AudioRecord(
- mOpPackageName // const String16& opPackageName TODO does not compile
- );
- mAudioRecord->set(
- AUDIO_SOURCE_DEFAULT, // ignored because we pass attributes below
- getSampleRate(),
- format,
- channelMask,
- frameCount,
- callback,
- callbackData,
- notificationFrames,
- false /*threadCanCallJava*/,
- sessionId,
- streamTransferType,
- flags,
- AUDIO_UID_INVALID, // DEFAULT uid
- -1, // DEFAULT pid
- &attributes,
- selectedDeviceId
- );
+ // ----------- open the AudioRecord ---------------------
+ // Might retry, but never more than once.
+ for (int i = 0; i < 2; i ++) {
+ audio_format_t requestedInternalFormat =
+ AAudioConvert_aaudioToAndroidDataFormat(getDeviceFormat());
- // Did we get a valid track?
- status_t status = mAudioRecord->initCheck();
- if (status != OK) {
- close();
- ALOGE("open(), initCheck() returned %d", status);
- return AAudioConvert_androidToAAudioResult(status);
+ mAudioRecord = new AudioRecord(
+ mOpPackageName // const String16& opPackageName TODO does not compile
+ );
+ mAudioRecord->set(
+ AUDIO_SOURCE_DEFAULT, // ignored because we pass attributes below
+ getSampleRate(),
+ requestedInternalFormat,
+ channelMask,
+ frameCount,
+ callback,
+ callbackData,
+ notificationFrames,
+ false /*threadCanCallJava*/,
+ sessionId,
+ streamTransferType,
+ flags,
+ AUDIO_UID_INVALID, // DEFAULT uid
+ -1, // DEFAULT pid
+ &attributes,
+ selectedDeviceId
+ );
+
+ // Did we get a valid track?
+ status_t status = mAudioRecord->initCheck();
+ if (status != OK) {
+ close();
+ ALOGE("open(), initCheck() returned %d", status);
+ return AAudioConvert_androidToAAudioResult(status);
+ }
+
+ // Check to see if it was worth hacking the deviceFormat.
+ bool gotFastPath = (mAudioRecord->getFlags() & AUDIO_INPUT_FLAG_FAST)
+ == AUDIO_INPUT_FLAG_FAST;
+ if (getFormat() != getDeviceFormat() && !gotFastPath) {
+ // We tried to get a FAST path by switching the device format.
+ // But it didn't work. So we might as well reopen using the same
+ // format for device and for app.
+ ALOGD("%s() used a different device format but no FAST path, reopen", __func__);
+ mAudioRecord.clear();
+ setDeviceFormat(getFormat());
+ } else {
+ break; // Keep the one we just opened.
+ }
}
// Get the actual values from the AudioRecord.
setSamplesPerFrame(mAudioRecord->channelCount());
- setFormat(AAudioConvert_androidToAAudioDataFormat(mAudioRecord->format()));
int32_t actualSampleRate = mAudioRecord->getSampleRate();
ALOGW_IF(actualSampleRate != getSampleRate(),
@@ -169,6 +213,29 @@
mBlockAdapter = nullptr;
}
+ // Allocate format conversion buffer if needed.
+ if (getDeviceFormat() == AAUDIO_FORMAT_PCM_I16
+ && getFormat() == AAUDIO_FORMAT_PCM_FLOAT) {
+
+ if (builder.getDataCallbackProc() != nullptr) {
+ // If we have a callback then we need to convert the data into an internal float
+ // array and then pass that entire array to the app.
+ mFormatConversionBufferSizeInFrames =
+ (mCallbackBufferSize != AAUDIO_UNSPECIFIED)
+ ? mCallbackBufferSize : getFramesPerBurst();
+ int32_t numSamples = mFormatConversionBufferSizeInFrames * getSamplesPerFrame();
+ mFormatConversionBufferFloat = std::make_unique<float[]>(numSamples);
+ } else {
+ // If we don't have a callback then we will read into an internal short array
+ // and then convert into the app float array in read().
+ mFormatConversionBufferSizeInFrames = getFramesPerBurst();
+ int32_t numSamples = mFormatConversionBufferSizeInFrames * getSamplesPerFrame();
+ mFormatConversionBufferI16 = std::make_unique<int16_t[]>(numSamples);
+ }
+ ALOGD("%s() setup I16>FLOAT conversion buffer with %d frames",
+ __func__, mFormatConversionBufferSizeInFrames);
+ }
+
// Update performance mode based on the actual stream.
// For example, if the sample rate does not match native then you won't get a FAST track.
audio_input_flags_t actualFlags = mAudioRecord->getFlags();
@@ -216,6 +283,24 @@
return AudioStream::close();
}
+const void * AudioStreamRecord::maybeConvertDeviceData(const void *audioData, int32_t numFrames) {
+ if (mFormatConversionBufferFloat.get() != nullptr) {
+ LOG_ALWAYS_FATAL_IF(numFrames > mFormatConversionBufferSizeInFrames,
+ "%s() conversion size %d too large for buffer %d",
+ __func__, numFrames, mFormatConversionBufferSizeInFrames);
+
+ int32_t numSamples = numFrames * getSamplesPerFrame();
+ // Only conversion supported is I16 to FLOAT
+ memcpy_to_float_from_i16(
+ mFormatConversionBufferFloat.get(),
+ (const int16_t *) audioData,
+ numSamples);
+ return mFormatConversionBufferFloat.get();
+ } else {
+ return audioData;
+ }
+}
+
void AudioStreamRecord::processCallback(int event, void *info) {
switch (event) {
case AudioRecord::EVENT_MORE_DATA:
@@ -302,9 +387,10 @@
int32_t numFrames,
int64_t timeoutNanoseconds)
{
- int32_t bytesPerFrame = getBytesPerFrame();
+ int32_t bytesPerDeviceFrame = getBytesPerDeviceFrame();
int32_t numBytes;
- aaudio_result_t result = AAudioConvert_framesToBytes(numFrames, bytesPerFrame, &numBytes);
+ // This will detect out of range values for numFrames.
+ aaudio_result_t result = AAudioConvert_framesToBytes(numFrames, bytesPerDeviceFrame, &numBytes);
if (result != AAUDIO_OK) {
return result;
}
@@ -315,19 +401,49 @@
// TODO add timeout to AudioRecord
bool blocking = (timeoutNanoseconds > 0);
- ssize_t bytesRead = mAudioRecord->read(buffer, numBytes, blocking);
- if (bytesRead == WOULD_BLOCK) {
+
+ ssize_t bytesActuallyRead = 0;
+ ssize_t totalBytesRead = 0;
+ if (mFormatConversionBufferI16.get() != nullptr) {
+ // Convert I16 data to float using an intermediate buffer.
+ float *floatBuffer = (float *) buffer;
+ int32_t framesLeft = numFrames;
+ // Perform conversion using multiple read()s if necessary.
+ while (framesLeft > 0) {
+ // Read into short internal buffer.
+ int32_t framesToRead = std::min(framesLeft, mFormatConversionBufferSizeInFrames);
+ size_t bytesToRead = framesToRead * bytesPerDeviceFrame;
+ bytesActuallyRead = mAudioRecord->read(mFormatConversionBufferI16.get(), bytesToRead, blocking);
+ if (bytesActuallyRead <= 0) {
+ break;
+ }
+ totalBytesRead += bytesActuallyRead;
+ int32_t framesToConvert = bytesActuallyRead / bytesPerDeviceFrame;
+ // Convert into app float buffer.
+ size_t numSamples = framesToConvert * getSamplesPerFrame();
+ memcpy_to_float_from_i16(
+ floatBuffer,
+ mFormatConversionBufferI16.get(),
+ numSamples);
+ floatBuffer += numSamples;
+ framesLeft -= framesToConvert;
+ }
+ } else {
+ bytesActuallyRead = mAudioRecord->read(buffer, numBytes, blocking);
+ totalBytesRead = bytesActuallyRead;
+ }
+ if (bytesActuallyRead == 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) {
+ } else if (bytesActuallyRead < 0) {
+ // In this context, a DEAD_OBJECT is more likely to be a disconnect notification due to
+ // AudioRecord invalidation.
+ if (bytesActuallyRead == DEAD_OBJECT) {
setState(AAUDIO_STREAM_STATE_DISCONNECTED);
return AAUDIO_ERROR_DISCONNECTED;
}
- return AAudioConvert_androidToAAudioResult(bytesRead);
+ return AAudioConvert_androidToAAudioResult(bytesActuallyRead);
}
- int32_t framesRead = (int32_t)(bytesRead / bytesPerFrame);
+ int32_t framesRead = (int32_t)(totalBytesRead / bytesPerDeviceFrame);
incrementFramesRead(framesRead);
result = updateStateMachine();
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.h b/media/libaaudio/src/legacy/AudioStreamRecord.h
index c1723ba..2f41d34 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.h
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.h
@@ -76,6 +76,8 @@
return incrementFramesRead(frames);
}
+ const void * maybeConvertDeviceData(const void *audioData, int32_t numFrames) override;
+
private:
android::sp<android::AudioRecord> mAudioRecord;
// adapts between variable sized blocks and fixed size blocks
@@ -83,6 +85,11 @@
// TODO add 64-bit position reporting to AudioRecord and use it.
android::String16 mOpPackageName;
+
+ // Only one type of conversion buffer is used.
+ std::unique_ptr<float[]> mFormatConversionBufferFloat;
+ std::unique_ptr<int16_t[]> mFormatConversionBufferI16;
+ int32_t mFormatConversionBufferSizeInFrames = 0;
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 023e8af..9653601 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -181,6 +181,7 @@
aaudio_format_t aaudioFormat =
AAudioConvert_androidToAAudioDataFormat(mAudioTrack->format());
setFormat(aaudioFormat);
+ setDeviceFormat(aaudioFormat);
int32_t actualSampleRate = mAudioTrack->getSampleRate();
ALOGW_IF(actualSampleRate != getSampleRate(),