| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "NativeAudioAnalyzer.h" |
| |
| static void convertPcm16ToFloat(const int16_t *source, |
| float *destination, |
| int32_t numSamples) { |
| constexpr float scaler = 1.0f / 32768.0f; |
| for (int i = 0; i < numSamples; i++) { |
| destination[i] = source[i] * scaler; |
| } |
| } |
| |
| // Fill the audio output buffer. |
| int32_t NativeAudioAnalyzer::readFormattedData(int32_t numFrames) { |
| int32_t framesRead = AAUDIO_ERROR_INVALID_FORMAT; |
| if (mActualInputFormat == AAUDIO_FORMAT_PCM_I16) { |
| framesRead = AAudioStream_read(mInputStream, mInputShortData, |
| numFrames, |
| 0 /* timeoutNanoseconds */); |
| } else if (mActualInputFormat == AAUDIO_FORMAT_PCM_FLOAT) { |
| framesRead = AAudioStream_read(mInputStream, mInputFloatData, |
| numFrames, |
| 0 /* timeoutNanoseconds */); |
| } else { |
| ALOGE("ERROR actualInputFormat = %d\n", mActualInputFormat); |
| assert(false); |
| } |
| if (framesRead < 0) { |
| // Expect INVALID_STATE if STATE_STARTING |
| if (mFramesReadTotal > 0) { |
| mInputError = framesRead; |
| ALOGE("ERROR in read = %d = %s\n", framesRead, |
| AAudio_convertResultToText(framesRead)); |
| } else { |
| framesRead = 0; |
| } |
| } else { |
| mFramesReadTotal += framesRead; |
| } |
| return framesRead; |
| } |
| |
| aaudio_data_callback_result_t NativeAudioAnalyzer::dataCallbackProc( |
| void *audioData, |
| int32_t numFrames |
| ) { |
| aaudio_data_callback_result_t callbackResult = AAUDIO_CALLBACK_RESULT_CONTINUE; |
| float *outputData = (float *) audioData; |
| |
| // Read audio data from the input stream. |
| int32_t actualFramesRead; |
| |
| if (numFrames > mInputFramesMaximum) { |
| ALOGE("%s() numFrames:%d > mInputFramesMaximum:%d", __func__, numFrames, mInputFramesMaximum); |
| mInputError = AAUDIO_ERROR_OUT_OF_RANGE; |
| return AAUDIO_CALLBACK_RESULT_STOP; |
| } |
| |
| if (numFrames > mMaxNumFrames) { |
| mMaxNumFrames = numFrames; |
| } |
| if (numFrames < mMinNumFrames) { |
| mMinNumFrames = numFrames; |
| } |
| |
| // Silence the output. |
| int32_t numBytes = numFrames * mActualOutputChannelCount * sizeof(float); |
| memset(audioData, 0 /* value */, numBytes); |
| |
| if (mNumCallbacksToDrain > 0) { |
| // Drain the input FIFOs. |
| int32_t totalFramesRead = 0; |
| do { |
| actualFramesRead = readFormattedData(numFrames); |
| if (actualFramesRead > 0) { |
| totalFramesRead += actualFramesRead; |
| } else if (actualFramesRead < 0) { |
| callbackResult = AAUDIO_CALLBACK_RESULT_STOP; |
| } |
| // Ignore errors because input stream may not be started yet. |
| } while (actualFramesRead > 0); |
| // Only counts if we actually got some data. |
| if (totalFramesRead > 0) { |
| mNumCallbacksToDrain--; |
| } |
| |
| } else if (mNumCallbacksToNotRead > 0) { |
| // Let the input fill up a bit so we are not so close to the write pointer. |
| mNumCallbacksToNotRead--; |
| } else if (mNumCallbacksToDiscard > 0) { |
| // Ignore. Allow the input to fill back up to equilibrium with the output. |
| actualFramesRead = readFormattedData(numFrames); |
| if (actualFramesRead < 0) { |
| callbackResult = AAUDIO_CALLBACK_RESULT_STOP; |
| } |
| mNumCallbacksToDiscard--; |
| |
| } else { |
| // The full duplex stream is now stable so process the audio. |
| int32_t numInputBytes = numFrames * mActualInputChannelCount * sizeof(float); |
| memset(mInputFloatData, 0 /* value */, numInputBytes); |
| |
| int64_t inputFramesWritten = AAudioStream_getFramesWritten(mInputStream); |
| int64_t inputFramesRead = AAudioStream_getFramesRead(mInputStream); |
| int64_t framesAvailable = inputFramesWritten - inputFramesRead; |
| |
| // Read the INPUT data. |
| actualFramesRead = readFormattedData(numFrames); // READ |
| if (actualFramesRead < 0) { |
| callbackResult = AAUDIO_CALLBACK_RESULT_STOP; |
| } else { |
| if (actualFramesRead < numFrames) { |
| if(actualFramesRead < (int32_t) framesAvailable) { |
| ALOGE("insufficient for no reason, numFrames = %d" |
| ", actualFramesRead = %d" |
| ", inputFramesWritten = %d" |
| ", inputFramesRead = %d" |
| ", available = %d\n", |
| numFrames, |
| actualFramesRead, |
| (int) inputFramesWritten, |
| (int) inputFramesRead, |
| (int) framesAvailable); |
| } |
| mInsufficientReadCount++; |
| mInsufficientReadFrames += numFrames - actualFramesRead; // deficit |
| // ALOGE("Error insufficientReadCount = %d\n",(int)mInsufficientReadCount); |
| } |
| |
| int32_t numSamples = actualFramesRead * mActualInputChannelCount; |
| |
| if (mActualInputFormat == AAUDIO_FORMAT_PCM_I16) { |
| convertPcm16ToFloat(mInputShortData, mInputFloatData, numSamples); |
| } |
| |
| // Process the INPUT and generate the OUTPUT. |
| mLoopbackProcessor->process(mInputFloatData, |
| mActualInputChannelCount, |
| numFrames, |
| outputData, |
| mActualOutputChannelCount, |
| numFrames); |
| |
| mIsDone = mLoopbackProcessor->isDone(); |
| if (mIsDone) { |
| callbackResult = AAUDIO_CALLBACK_RESULT_STOP; |
| } |
| } |
| } |
| mFramesWrittenTotal += numFrames; |
| |
| return callbackResult; |
| } |
| |
| static aaudio_data_callback_result_t s_MyDataCallbackProc( |
| AAudioStream * /* outputStream */, |
| void *userData, |
| void *audioData, |
| int32_t numFrames) { |
| NativeAudioAnalyzer *myData = (NativeAudioAnalyzer *) userData; |
| return myData->dataCallbackProc(audioData, numFrames); |
| } |
| |
| static void s_MyErrorCallbackProc( |
| AAudioStream * /* stream */, |
| void * userData, |
| aaudio_result_t error) { |
| ALOGE("Error Callback, error: %d\n",(int)error); |
| NativeAudioAnalyzer *myData = (NativeAudioAnalyzer *) userData; |
| myData->mOutputError = error; |
| } |
| |
| bool NativeAudioAnalyzer::isRecordingComplete() { |
| return mPulseLatencyAnalyzer.isRecordingComplete(); |
| } |
| |
| int NativeAudioAnalyzer::analyze() { |
| mPulseLatencyAnalyzer.analyze(); |
| return getError(); // TODO review |
| } |
| |
| double NativeAudioAnalyzer::getLatencyMillis() { |
| return mPulseLatencyAnalyzer.getMeasuredLatency() * 1000.0 / 48000; |
| } |
| |
| double NativeAudioAnalyzer::getConfidence() { |
| return mPulseLatencyAnalyzer.getMeasuredConfidence(); |
| } |
| |
| bool NativeAudioAnalyzer::isLowLatencyStream() { |
| return mIsLowLatencyStream; |
| } |
| |
| int NativeAudioAnalyzer::getSampleRate() { |
| return mOutputSampleRate; |
| } |
| |
| aaudio_result_t NativeAudioAnalyzer::openAudio() { |
| AAudioStreamBuilder *builder = nullptr; |
| |
| mLoopbackProcessor = &mPulseLatencyAnalyzer; // for latency test |
| |
| // Use an AAudioStreamBuilder to contain requested parameters. |
| aaudio_result_t result = AAudio_createStreamBuilder(&builder); |
| if (result != AAUDIO_OK) { |
| ALOGE("AAudio_createStreamBuilder() returned %s", |
| AAudio_convertResultToText(result)); |
| return result; |
| } |
| |
| // Create the OUTPUT stream ----------------------- |
| AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT); |
| AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); |
| AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE); |
| AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT); |
| AAudioStreamBuilder_setChannelCount(builder, 2); // stereo |
| AAudioStreamBuilder_setDataCallback(builder, s_MyDataCallbackProc, this); |
| AAudioStreamBuilder_setErrorCallback(builder, s_MyErrorCallbackProc, this); |
| |
| result = AAudioStreamBuilder_openStream(builder, &mOutputStream); |
| if (result != AAUDIO_OK) { |
| ALOGE("NativeAudioAnalyzer::openAudio() OUTPUT error %s", |
| AAudio_convertResultToText(result)); |
| return result; |
| } |
| |
| // Did we get a low-latency stream? |
| mIsLowLatencyStream = |
| AAudioStream_getPerformanceMode(mOutputStream) == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY; |
| |
| int32_t outputFramesPerBurst = AAudioStream_getFramesPerBurst(mOutputStream); |
| (void) AAudioStream_setBufferSizeInFrames(mOutputStream, outputFramesPerBurst * kDefaultOutputSizeBursts); |
| |
| mOutputSampleRate = AAudioStream_getSampleRate(mOutputStream); |
| mActualOutputChannelCount = AAudioStream_getChannelCount(mOutputStream); |
| |
| // Create the INPUT stream ----------------------- |
| AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT); |
| AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_UNSPECIFIED); |
| AAudioStreamBuilder_setSampleRate(builder, mOutputSampleRate); // must match |
| AAudioStreamBuilder_setChannelCount(builder, 1); // mono |
| AAudioStreamBuilder_setDataCallback(builder, nullptr, nullptr); |
| AAudioStreamBuilder_setErrorCallback(builder, nullptr, nullptr); |
| result = AAudioStreamBuilder_openStream(builder, &mInputStream); |
| if (result != AAUDIO_OK) { |
| ALOGE("NativeAudioAnalyzer::openAudio() INPUT error %s", |
| AAudio_convertResultToText(result)); |
| return result; |
| } |
| |
| int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(mInputStream); |
| (void) AAudioStream_setBufferSizeInFrames(mInputStream, actualCapacity); |
| |
| // ------- Setup loopbackData ----------------------------- |
| mActualInputFormat = AAudioStream_getFormat(mInputStream); |
| mActualInputChannelCount = AAudioStream_getChannelCount(mInputStream); |
| |
| // Allocate a buffer for the audio data. |
| mInputFramesMaximum = 32 * AAudioStream_getFramesPerBurst(mInputStream); |
| |
| if (mActualInputFormat == AAUDIO_FORMAT_PCM_I16) { |
| mInputShortData = new int16_t[mInputFramesMaximum * mActualInputChannelCount]{}; |
| } |
| mInputFloatData = new float[mInputFramesMaximum * mActualInputChannelCount]{}; |
| |
| return result; |
| } |
| |
| aaudio_result_t NativeAudioAnalyzer::startAudio() { |
| mLoopbackProcessor->prepareToTest(); |
| |
| // Start OUTPUT first so INPUT does not overflow. |
| aaudio_result_t result = AAudioStream_requestStart(mOutputStream); |
| if (result != AAUDIO_OK) { |
| stopAudio(); |
| return result; |
| } |
| |
| result = AAudioStream_requestStart(mInputStream); |
| if (result != AAUDIO_OK) { |
| stopAudio(); |
| return result; |
| } |
| |
| return result; |
| } |
| |
| aaudio_result_t NativeAudioAnalyzer::stopAudio() { |
| aaudio_result_t result1 = AAUDIO_OK; |
| aaudio_result_t result2 = AAUDIO_OK; |
| ALOGD("stopAudio() , minNumFrames = %d, maxNumFrames = %d\n", mMinNumFrames, mMaxNumFrames); |
| // Stop OUTPUT first because it uses INPUT. |
| if (mOutputStream != nullptr) { |
| result1 = AAudioStream_requestStop(mOutputStream); |
| } |
| |
| // Stop INPUT. |
| if (mInputStream != nullptr) { |
| result2 = AAudioStream_requestStop(mInputStream); |
| } |
| return result1 != AAUDIO_OK ? result1 : result2; |
| } |
| |
| aaudio_result_t NativeAudioAnalyzer::closeAudio() { |
| aaudio_result_t result1 = AAUDIO_OK; |
| aaudio_result_t result2 = AAUDIO_OK; |
| // Stop and close OUTPUT first because it uses INPUT. |
| if (mOutputStream != nullptr) { |
| result1 = AAudioStream_close(mOutputStream); |
| mOutputStream = nullptr; |
| } |
| |
| // Stop and close INPUT. |
| if (mInputStream != nullptr) { |
| result2 = AAudioStream_close(mInputStream); |
| mInputStream = nullptr; |
| } |
| return result1 != AAUDIO_OK ? result1 : result2; |
| } |