| /* | |
| * Copyright (C) 2014 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. | |
| */ | |
| #define LOG_TAG "FastCapture" | |
| //#define LOG_NDEBUG 0 | |
| #define ATRACE_TAG ATRACE_TAG_AUDIO | |
| #include "Configuration.h" | |
| #include <audio_utils/format.h> | |
| #include <linux/futex.h> | |
| #include <sys/syscall.h> | |
| #include <media/AudioBufferProvider.h> | |
| #include <utils/Log.h> | |
| #include <utils/Trace.h> | |
| #include "FastCapture.h" | |
| namespace android { | |
| /*static*/ const FastCaptureState FastCapture::sInitial{}; | |
| FastCapture::FastCapture() : FastThread("cycleC_ms", "loadC_us") | |
| { | |
| // base class initialization | |
| mPrevious = &sInitial; | |
| mCurrent = &sInitial; | |
| mDummyDumpState = &mDummyFastCaptureDumpState; | |
| } | |
| FastCaptureStateQueue* FastCapture::sq() | |
| { | |
| return &mSQ; | |
| } | |
| const FastThreadState *FastCapture::poll() | |
| { | |
| return mSQ.poll(); | |
| } | |
| void FastCapture::setNBLogWriter(NBLog::Writer *logWriter __unused) | |
| { | |
| } | |
| void FastCapture::onIdle() | |
| { | |
| mPreIdle = *(const FastCaptureState *)mCurrent; | |
| mCurrent = &mPreIdle; | |
| } | |
| void FastCapture::onExit() | |
| { | |
| free(mReadBuffer); | |
| } | |
| bool FastCapture::isSubClassCommand(FastThreadState::Command command) | |
| { | |
| switch ((FastCaptureState::Command) command) { | |
| case FastCaptureState::READ: | |
| case FastCaptureState::WRITE: | |
| case FastCaptureState::READ_WRITE: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| void FastCapture::onStateChange() | |
| { | |
| const FastCaptureState * const current = (const FastCaptureState *) mCurrent; | |
| const FastCaptureState * const previous = (const FastCaptureState *) mPrevious; | |
| FastCaptureDumpState * const dumpState = (FastCaptureDumpState *) mDumpState; | |
| const size_t frameCount = current->mFrameCount; | |
| bool eitherChanged = false; | |
| // check for change in input HAL configuration | |
| const NBAIO_Format previousFormat = mFormat; | |
| if (current->mInputSourceGen != mInputSourceGen) { | |
| mInputSource = current->mInputSource; | |
| mInputSourceGen = current->mInputSourceGen; | |
| if (mInputSource == nullptr) { | |
| mFormat = Format_Invalid; | |
| mSampleRate = 0; | |
| } else { | |
| mFormat = mInputSource->format(); | |
| mSampleRate = Format_sampleRate(mFormat); | |
| #if !LOG_NDEBUG | |
| unsigned channelCount = Format_channelCount(mFormat); | |
| ALOG_ASSERT(channelCount >= 1 && channelCount <= FCC_LIMIT); | |
| #endif | |
| } | |
| dumpState->mSampleRate = mSampleRate; | |
| eitherChanged = true; | |
| } | |
| // check for change in pipe | |
| if (current->mPipeSinkGen != mPipeSinkGen) { | |
| mPipeSink = current->mPipeSink; | |
| mPipeSinkGen = current->mPipeSinkGen; | |
| eitherChanged = true; | |
| } | |
| // input source and pipe sink must be compatible | |
| if (eitherChanged && mInputSource != nullptr && mPipeSink != nullptr) { | |
| ALOG_ASSERT(Format_isEqual(mFormat, mPipeSink->format())); | |
| } | |
| if ((!Format_isEqual(mFormat, previousFormat)) || (frameCount != previous->mFrameCount)) { | |
| // FIXME to avoid priority inversion, don't free here | |
| free(mReadBuffer); | |
| mReadBuffer = nullptr; | |
| if (frameCount > 0 && mSampleRate > 0) { | |
| // FIXME new may block for unbounded time at internal mutex of the heap | |
| // implementation; it would be better to have normal capture thread allocate for | |
| // us to avoid blocking here and to prevent possible priority inversion | |
| const size_t bufferSize = frameCount * Format_frameSize(mFormat); | |
| (void)posix_memalign(&mReadBuffer, 32, bufferSize); | |
| memset(mReadBuffer, 0, bufferSize); // if posix_memalign fails, will segv here. | |
| mPeriodNs = (frameCount * 1000000000LL) / mSampleRate; // 1.00 | |
| mUnderrunNs = (frameCount * 1750000000LL) / mSampleRate; // 1.75 | |
| mOverrunNs = (frameCount * 500000000LL) / mSampleRate; // 0.50 | |
| mForceNs = (frameCount * 950000000LL) / mSampleRate; // 0.95 | |
| mWarmupNsMin = (frameCount * 750000000LL) / mSampleRate; // 0.75 | |
| mWarmupNsMax = (frameCount * 1250000000LL) / mSampleRate; // 1.25 | |
| } else { | |
| mPeriodNs = 0; | |
| mUnderrunNs = 0; | |
| mOverrunNs = 0; | |
| mForceNs = 0; | |
| mWarmupNsMin = 0; | |
| mWarmupNsMax = LONG_MAX; | |
| } | |
| mReadBufferState = -1; | |
| dumpState->mFrameCount = frameCount; | |
| } | |
| dumpState->mSilenced = current->mSilenceCapture; | |
| } | |
| void FastCapture::onWork() | |
| { | |
| const FastCaptureState * const current = (const FastCaptureState *) mCurrent; | |
| FastCaptureDumpState * const dumpState = (FastCaptureDumpState *) mDumpState; | |
| const FastCaptureState::Command command = mCommand; | |
| size_t frameCount = current->mFrameCount; | |
| AudioBufferProvider* fastPatchRecordBufferProvider = current->mFastPatchRecordBufferProvider; | |
| AudioBufferProvider::Buffer patchBuffer; | |
| if (fastPatchRecordBufferProvider != nullptr) { | |
| patchBuffer.frameCount = ~0; | |
| const status_t status = fastPatchRecordBufferProvider->getNextBuffer(&patchBuffer); | |
| if (status != NO_ERROR) { | |
| frameCount = 0; | |
| } else if (patchBuffer.frameCount < frameCount) { | |
| // TODO: Make sure that it doesn't cause any issues if we just get a small available | |
| // buffer from the buffer provider. | |
| frameCount = patchBuffer.frameCount; | |
| } | |
| } | |
| if ((command & FastCaptureState::READ) /*&& isWarm*/) { | |
| ALOG_ASSERT(mInputSource != nullptr); | |
| ALOG_ASSERT(mReadBuffer != nullptr); | |
| dumpState->mReadSequence++; | |
| ATRACE_BEGIN("read"); | |
| const ssize_t framesRead = mInputSource->read(mReadBuffer, frameCount); | |
| ATRACE_END(); | |
| dumpState->mReadSequence++; | |
| if (framesRead >= 0) { | |
| LOG_ALWAYS_FATAL_IF((size_t) framesRead > frameCount); | |
| mTotalNativeFramesRead += framesRead; | |
| dumpState->mFramesRead = mTotalNativeFramesRead; | |
| mReadBufferState = framesRead; | |
| patchBuffer.frameCount = framesRead; | |
| } else { | |
| dumpState->mReadErrors++; | |
| mReadBufferState = 0; | |
| } | |
| // FIXME rename to attemptedIO | |
| mAttemptedWrite = true; | |
| } | |
| if (command & FastCaptureState::WRITE) { | |
| ALOG_ASSERT(mPipeSink != nullptr); | |
| ALOG_ASSERT(mReadBuffer != nullptr); | |
| if (mReadBufferState < 0) { | |
| memset(mReadBuffer, 0, frameCount * Format_frameSize(mFormat)); | |
| mReadBufferState = frameCount; | |
| } | |
| if (mReadBufferState > 0) { | |
| if (current->mSilenceCapture) { | |
| memset(mReadBuffer, 0, mReadBufferState * Format_frameSize(mFormat)); | |
| } | |
| const ssize_t framesWritten = mPipeSink->write(mReadBuffer, mReadBufferState); | |
| audio_track_cblk_t* cblk = current->mCblk; | |
| if (fastPatchRecordBufferProvider != nullptr) { | |
| // This indicates the fast track is a patch record, update the cblk by | |
| // calling releaseBuffer(). | |
| memcpy_by_audio_format(patchBuffer.raw, current->mFastPatchRecordFormat, | |
| mReadBuffer, mFormat.mFormat, framesWritten * mFormat.mChannelCount); | |
| patchBuffer.frameCount = framesWritten; | |
| fastPatchRecordBufferProvider->releaseBuffer(&patchBuffer); | |
| } else if (cblk != nullptr && framesWritten > 0) { | |
| // FIXME This supports at most one fast capture client. | |
| // To handle multiple clients this could be converted to an array, | |
| // or with a lot more work the control block could be shared by all clients. | |
| const int32_t rear = cblk->u.mStreaming.mRear; | |
| android_atomic_release_store(framesWritten + rear, &cblk->u.mStreaming.mRear); | |
| cblk->mServer += framesWritten; | |
| const int32_t old = android_atomic_or(CBLK_FUTEX_WAKE, &cblk->mFutex); | |
| if (!(old & CBLK_FUTEX_WAKE)) { | |
| // client is never in server process, so don't use FUTEX_WAKE_PRIVATE | |
| (void) syscall(__NR_futex, &cblk->mFutex, FUTEX_WAKE, 1); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } // namespace android |