From 9f80dd223d83d9bb9077fb6baee056cee4eaf7e5 Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Tue, 18 Dec 2012 15:57:32 -0800 Subject: New control block for AudioTrack and AudioRecord Main differences between old and new control block: - removes the mutex, which was a potential source of priority inversion - circular indices into shared buffer, which is now always a power-of-2 size Change-Id: I4e9b7fa99858b488ac98a441fa70e31dbba1b865 --- media/libmedia/AudioRecord.cpp | 832 +++++++++++++++++++++++------------------ 1 file changed, 474 insertions(+), 358 deletions(-) (limited to 'media/libmedia/AudioRecord.cpp') diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index a2b8ae2..9faa497 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -19,18 +19,13 @@ #define LOG_TAG "AudioRecord" #include -#include - #include -#include -#include #include -#include -#include #include - #include +#define WAIT_PERIOD_MS 10 + namespace android { // --------------------------------------------------------------------------- @@ -41,7 +36,9 @@ status_t AudioRecord::getMinFrameCount( audio_format_t format, audio_channel_mask_t channelMask) { - if (frameCount == NULL) return BAD_VALUE; + if (frameCount == NULL) { + return BAD_VALUE; + } // default to 0 in case of error *frameCount = 0; @@ -75,8 +72,7 @@ status_t AudioRecord::getMinFrameCount( AudioRecord::AudioRecord() : mStatus(NO_INIT), mSessionId(0), - mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT), - mProxy(NULL) + mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT) { } @@ -89,14 +85,15 @@ AudioRecord::AudioRecord( callback_t cbf, void* user, int notificationFrames, - int sessionId) + int sessionId, + transfer_type transferType) : mStatus(NO_INIT), mSessionId(0), mPreviousPriority(ANDROID_PRIORITY_NORMAL), mPreviousSchedulingGroup(SP_DEFAULT), mProxy(NULL) { - mStatus = set(inputSource, sampleRate, format, channelMask, - frameCount, cbf, user, notificationFrames, false /*threadCanCallJava*/, sessionId); + mStatus = set(inputSource, sampleRate, format, channelMask, frameCount, cbf, user, + notificationFrames, false /*threadCanCallJava*/, sessionId, transferType); } AudioRecord::~AudioRecord() @@ -111,11 +108,13 @@ AudioRecord::~AudioRecord() mAudioRecordThread->requestExitAndWait(); mAudioRecordThread.clear(); } - mAudioRecord.clear(); + if (mAudioRecord != 0) { + mAudioRecord->asBinder()->unlinkToDeath(mDeathNotifier, this); + mAudioRecord.clear(); + } IPCThreadState::self()->flushCommands(); AudioSystem::releaseAudioSessionId(mSessionId); } - delete mProxy; } status_t AudioRecord::set( @@ -128,8 +127,32 @@ status_t AudioRecord::set( void* user, int notificationFrames, bool threadCanCallJava, - int sessionId) + int sessionId, + transfer_type transferType) { + switch (transferType) { + case TRANSFER_DEFAULT: + if (cbf == NULL || threadCanCallJava) { + transferType = TRANSFER_SYNC; + } else { + transferType = TRANSFER_CALLBACK; + } + break; + case TRANSFER_CALLBACK: + if (cbf == NULL) { + ALOGE("Transfer type TRANSFER_CALLBACK but cbf == NULL"); + return BAD_VALUE; + } + break; + case TRANSFER_OBTAIN: + case TRANSFER_SYNC: + break; + default: + ALOGE("Invalid transfer type %d", transferType); + return BAD_VALUE; + } + mTransfer = transferType; + // FIXME "int" here is legacy and will be replaced by size_t later if (frameCountInt < 0) { ALOGE("Invalid frame count %d", frameCountInt); @@ -143,6 +166,7 @@ status_t AudioRecord::set( AutoMutex lock(mLock); if (mAudioRecord != 0) { + ALOGE("Track already in use"); return INVALID_OPERATION; } @@ -159,14 +183,16 @@ status_t AudioRecord::set( if (format == AUDIO_FORMAT_DEFAULT) { format = AUDIO_FORMAT_PCM_16_BIT; } + // validate parameters if (!audio_is_valid_format(format)) { - ALOGE("Invalid format"); + ALOGE("Invalid format %d", format); return BAD_VALUE; } mFormat = format; if (!audio_is_input_channel(channelMask)) { + ALOGE("Invalid channel mask %#x", channelMask); return BAD_VALUE; } mChannelMask = channelMask; @@ -200,6 +226,7 @@ status_t AudioRecord::set( size_t minFrameCount = 0; status_t status = getMinFrameCount(&minFrameCount, sampleRate, format, channelMask); if (status != NO_ERROR) { + ALOGE("getMinFrameCount() failed; status %d", status); return status; } ALOGV("AudioRecord::set() minFrameCount = %d", minFrameCount); @@ -207,6 +234,7 @@ status_t AudioRecord::set( if (frameCount == 0) { frameCount = minFrameCount; } else if (frameCount < minFrameCount) { + ALOGE("frameCount %u < minFrameCount %u", frameCount, minFrameCount); return BAD_VALUE; } @@ -215,7 +243,7 @@ status_t AudioRecord::set( } // create the IAudioRecord - status = openRecord_l(sampleRate, format, frameCount, input); + status = openRecord_l(sampleRate, format, frameCount, input, 0 /*epoch*/); if (status != NO_ERROR) { return status; } @@ -233,7 +261,7 @@ status_t AudioRecord::set( mActive = false; mCbf = cbf; mNotificationFrames = notificationFrames; - mRemainingFrames = notificationFrames; + mRefreshRemaining = true; mUserData = user; // TODO: add audio hardware input latency here mLatency = (1000*mFrameCount) / sampleRate; @@ -244,117 +272,78 @@ status_t AudioRecord::set( mInputSource = inputSource; mInput = input; AudioSystem::acquireAudioSessionId(mSessionId); + mSequence = 1; + mObservedSequence = mSequence; + mInOverrun = false; return NO_ERROR; } -status_t AudioRecord::initCheck() const -{ - return mStatus; -} - -// ------------------------------------------------------------------------- - -uint32_t AudioRecord::latency() const -{ - return mLatency; -} - -audio_format_t AudioRecord::format() const -{ - return mFormat; -} - -uint32_t AudioRecord::channelCount() const -{ - return mChannelCount; -} - -size_t AudioRecord::frameCount() const -{ - return mFrameCount; -} - -audio_source_t AudioRecord::inputSource() const -{ - return mInputSource; -} - // ------------------------------------------------------------------------- status_t AudioRecord::start(AudioSystem::sync_event_t event, int triggerSession) { - status_t ret = NO_ERROR; - sp t = mAudioRecordThread; - ALOGV("start, sync event %d trigger session %d", event, triggerSession); AutoMutex lock(mLock); - // acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed - // while we are accessing the cblk - sp audioRecord = mAudioRecord; - sp iMem = mCblkMemory; - audio_track_cblk_t* cblk = mCblk; + if (mActive) { + return NO_ERROR; + } - if (!mActive) { - mActive = true; + // reset current position as seen by client to 0 + mProxy->setEpoch(mProxy->getEpoch() - mProxy->getPosition()); - cblk->lock.lock(); - if (!(cblk->flags & CBLK_INVALID)) { - cblk->lock.unlock(); - ALOGV("mAudioRecord->start()"); - ret = mAudioRecord->start(event, triggerSession); - cblk->lock.lock(); - if (ret == DEAD_OBJECT) { - android_atomic_or(CBLK_INVALID, &cblk->flags); - } - } - if (cblk->flags & CBLK_INVALID) { - audio_track_cblk_t* temp = cblk; - ret = restoreRecord_l(temp); - cblk = temp; + mNewPosition = mProxy->getPosition() + mUpdatePeriod; + int32_t flags = android_atomic_acquire_load(&mCblk->flags); + + status_t status = NO_ERROR; + if (!(flags & CBLK_INVALID)) { + ALOGV("mAudioRecord->start()"); + status = mAudioRecord->start(event, triggerSession); + if (status == DEAD_OBJECT) { + flags |= CBLK_INVALID; } - cblk->lock.unlock(); - if (ret == NO_ERROR) { - mNewPosition = cblk->user + mUpdatePeriod; - cblk->bufferTimeoutMs = (event == AudioSystem::SYNC_EVENT_NONE) ? MAX_RUN_TIMEOUT_MS : - AudioSystem::kSyncRecordStartTimeOutMs; - cblk->waitTimeMs = 0; - if (t != 0) { - t->resume(); - } else { - mPreviousPriority = getpriority(PRIO_PROCESS, 0); - get_sched_policy(0, &mPreviousSchedulingGroup); - androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO); - } + } + if (flags & CBLK_INVALID) { + status = restoreRecord_l("start"); + } + + if (status != NO_ERROR) { + ALOGE("start() status %d", status); + } else { + mActive = true; + sp t = mAudioRecordThread; + if (t != 0) { + t->resume(); } else { - mActive = false; + mPreviousPriority = getpriority(PRIO_PROCESS, 0); + get_sched_policy(0, &mPreviousSchedulingGroup); + androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO); } } - return ret; + return status; } void AudioRecord::stop() { - sp t = mAudioRecordThread; - - ALOGV("stop"); - AutoMutex lock(mLock); - if (mActive) { - mActive = false; - mCblk->cv.signal(); - mAudioRecord->stop(); - // the record head position will reset to 0, so if a marker is set, we need - // to activate it again - mMarkerReached = false; - if (t != 0) { - t->pause(); - } else { - setpriority(PRIO_PROCESS, 0, mPreviousPriority); - set_sched_policy(0, mPreviousSchedulingGroup); - } + if (!mActive) { + return; + } + + mActive = false; + mProxy->interrupt(); + mAudioRecord->stop(); + // the record head position will reset to 0, so if a marker is set, we need + // to activate it again + mMarkerReached = false; + sp t = mAudioRecordThread; + if (t != 0) { + t->pause(); + } else { + setpriority(PRIO_PROCESS, 0, mPreviousPriority); + set_sched_policy(0, mPreviousSchedulingGroup); } } @@ -364,14 +353,11 @@ bool AudioRecord::stopped() const return !mActive; } -uint32_t AudioRecord::getSampleRate() const -{ - return mSampleRate; -} - status_t AudioRecord::setMarkerPosition(uint32_t marker) { - if (mCbf == NULL) return INVALID_OPERATION; + if (mCbf == NULL) { + return INVALID_OPERATION; + } AutoMutex lock(mLock); mMarkerPosition = marker; @@ -382,7 +368,9 @@ status_t AudioRecord::setMarkerPosition(uint32_t marker) status_t AudioRecord::getMarkerPosition(uint32_t *marker) const { - if (marker == NULL) return BAD_VALUE; + if (marker == NULL) { + return BAD_VALUE; + } AutoMutex lock(mLock); *marker = mMarkerPosition; @@ -392,13 +380,12 @@ status_t AudioRecord::getMarkerPosition(uint32_t *marker) const status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod) { - if (mCbf == NULL) return INVALID_OPERATION; - - uint32_t curPosition; - getPosition(&curPosition); + if (mCbf == NULL) { + return INVALID_OPERATION; + } AutoMutex lock(mLock); - mNewPosition = curPosition + updatePeriod; + mNewPosition = mProxy->getPosition() + updatePeriod; mUpdatePeriod = updatePeriod; return NO_ERROR; @@ -406,7 +393,9 @@ status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod) status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) const { - if (updatePeriod == NULL) return BAD_VALUE; + if (updatePeriod == NULL) { + return BAD_VALUE; + } AutoMutex lock(mLock); *updatePeriod = mUpdatePeriod; @@ -416,10 +405,12 @@ status_t AudioRecord::getPositionUpdatePeriod(uint32_t *updatePeriod) const status_t AudioRecord::getPosition(uint32_t *position) const { - if (position == NULL) return BAD_VALUE; + if (position == NULL) { + return BAD_VALUE; + } AutoMutex lock(mLock); - *position = mCblk->user; + *position = mProxy->getPosition(); return NO_ERROR; } @@ -427,7 +418,7 @@ status_t AudioRecord::getPosition(uint32_t *position) const unsigned int AudioRecord::getInputFramesLost() const { // no need to check mActive, because if inactive this will return 0, which is what we want - return AudioSystem::getInputFramesLost(mInput); + return AudioSystem::getInputFramesLost(getInput()); } // ------------------------------------------------------------------------- @@ -437,7 +428,8 @@ status_t AudioRecord::openRecord_l( uint32_t sampleRate, audio_format_t format, size_t frameCount, - audio_io_handle_t input) + audio_io_handle_t input, + size_t epoch) { status_t status; const sp& audioFlinger = AudioSystem::get_audio_flinger(); @@ -447,7 +439,7 @@ status_t AudioRecord::openRecord_l( } pid_t tid = -1; - // FIXME see similar logic at AudioTrack + // FIXME see similar logic at AudioTrack for tid int originalSessionId = mSessionId; sp record = audioFlinger->openRecord(input, @@ -470,133 +462,138 @@ status_t AudioRecord::openRecord_l( ALOGE("Could not get control block"); return NO_INIT; } - mAudioRecord.clear(); + if (mAudioRecord != 0) { + mAudioRecord->asBinder()->unlinkToDeath(mDeathNotifier, this); + mDeathNotifier.clear(); + } mAudioRecord = record; - mCblkMemory.clear(); mCblkMemory = iMem; audio_track_cblk_t* cblk = static_cast(iMem->pointer()); mCblk = cblk; - mBuffers = (char*)cblk + sizeof(audio_track_cblk_t); - cblk->bufferTimeoutMs = MAX_RUN_TIMEOUT_MS; - cblk->waitTimeMs = 0; + + // starting address of buffers in shared memory + void *buffers = (char*)cblk + sizeof(audio_track_cblk_t); // update proxy - delete mProxy; - mProxy = new AudioRecordClientProxy(cblk, mBuffers, frameCount, mFrameSize); + mProxy = new AudioRecordClientProxy(cblk, buffers, frameCount, mFrameSize); + mProxy->setEpoch(epoch); + mProxy->setMinimum(mNotificationFrames); + + mDeathNotifier = new DeathNotifier(this); + mAudioRecord->asBinder()->linkToDeath(mDeathNotifier, this); return NO_ERROR; } status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, int32_t waitCount) { - ALOG_ASSERT(mStatus == NO_ERROR && mProxy != NULL); + if (audioBuffer == NULL) { + return BAD_VALUE; + } + if (mTransfer != TRANSFER_OBTAIN) { + audioBuffer->frameCount = 0; + audioBuffer->size = 0; + audioBuffer->raw = NULL; + return INVALID_OPERATION; + } - AutoMutex lock(mLock); - bool active; - status_t result = NO_ERROR; - audio_track_cblk_t* cblk = mCblk; - uint32_t framesReq = audioBuffer->frameCount; - uint32_t waitTimeMs = (waitCount < 0) ? cblk->bufferTimeoutMs : WAIT_PERIOD_MS; - - audioBuffer->frameCount = 0; - audioBuffer->size = 0; - - size_t framesReady = mProxy->framesReady(); - - if (framesReady == 0) { - cblk->lock.lock(); - goto start_loop_here; - while (framesReady == 0) { - active = mActive; - if (CC_UNLIKELY(!active)) { - cblk->lock.unlock(); - return NO_MORE_BUFFERS; - } - if (CC_UNLIKELY(!waitCount)) { - cblk->lock.unlock(); - return WOULD_BLOCK; - } - if (!(cblk->flags & CBLK_INVALID)) { - mLock.unlock(); - // this condition is in shared memory, so if IAudioRecord and control block - // are replaced due to mediaserver death or IAudioRecord invalidation then - // cv won't be signalled, but fortunately the timeout will limit the wait - result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs)); - cblk->lock.unlock(); - mLock.lock(); - if (!mActive) { - return status_t(STOPPED); - } - // IAudioRecord may have been re-created while mLock was unlocked - cblk = mCblk; - cblk->lock.lock(); - } - if (cblk->flags & CBLK_INVALID) { - goto create_new_record; - } - if (CC_UNLIKELY(result != NO_ERROR)) { - cblk->waitTimeMs += waitTimeMs; - if (cblk->waitTimeMs >= cblk->bufferTimeoutMs) { - ALOGW( "obtainBuffer timed out (is the CPU pegged?) " - "user=%08x, server=%08x", cblk->user, cblk->server); - cblk->lock.unlock(); - // callback thread or sync event hasn't changed - result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, 0); - cblk->lock.lock(); - if (result == DEAD_OBJECT) { - android_atomic_or(CBLK_INVALID, &cblk->flags); -create_new_record: - audio_track_cblk_t* temp = cblk; - result = AudioRecord::restoreRecord_l(temp); - cblk = temp; - } - if (result != NO_ERROR) { - ALOGW("obtainBuffer create Track error %d", result); - cblk->lock.unlock(); - return result; + const struct timespec *requested; + if (waitCount == -1) { + requested = &ClientProxy::kForever; + } else if (waitCount == 0) { + requested = &ClientProxy::kNonBlocking; + } else if (waitCount > 0) { + long long ms = WAIT_PERIOD_MS * (long long) waitCount; + struct timespec timeout; + timeout.tv_sec = ms / 1000; + timeout.tv_nsec = (int) (ms % 1000) * 1000000; + requested = &timeout; + } else { + ALOGE("%s invalid waitCount %d", __func__, waitCount); + requested = NULL; + } + return obtainBuffer(audioBuffer, requested); +} + +status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested, + struct timespec *elapsed, size_t *nonContig) +{ + // previous and new IAudioRecord sequence numbers are used to detect track re-creation + uint32_t oldSequence = 0; + uint32_t newSequence; + + Proxy::Buffer buffer; + status_t status = NO_ERROR; + + static const int32_t kMaxTries = 5; + int32_t tryCounter = kMaxTries; + + do { + // obtainBuffer() is called with mutex unlocked, so keep extra references to these fields to + // keep them from going away if another thread re-creates the track during obtainBuffer() + sp proxy; + sp iMem; + { + // start of lock scope + AutoMutex lock(mLock); + + newSequence = mSequence; + // did previous obtainBuffer() fail due to media server death or voluntary invalidation? + if (status == DEAD_OBJECT) { + // re-create track, unless someone else has already done so + if (newSequence == oldSequence) { + status = restoreRecord_l("obtainBuffer"); + if (status != NO_ERROR) { + break; } - cblk->waitTimeMs = 0; - } - if (--waitCount == 0) { - cblk->lock.unlock(); - return TIMED_OUT; } } - // read the server count again -start_loop_here: - framesReady = mProxy->framesReady(); - } - cblk->lock.unlock(); - } + oldSequence = newSequence; - cblk->waitTimeMs = 0; - // reset time out to running value after obtaining a buffer - cblk->bufferTimeoutMs = MAX_RUN_TIMEOUT_MS; + // Keep the extra references + proxy = mProxy; + iMem = mCblkMemory; - if (framesReq > framesReady) { - framesReq = framesReady; - } + // Non-blocking if track is stopped + if (!mActive) { + requested = &ClientProxy::kNonBlocking; + } - uint32_t u = cblk->user; - uint32_t bufferEnd = cblk->userBase + mFrameCount; + } // end of lock scope - if (framesReq > bufferEnd - u) { - framesReq = bufferEnd - u; - } + buffer.mFrameCount = audioBuffer->frameCount; + // FIXME starts the requested timeout and elapsed over from scratch + status = proxy->obtainBuffer(&buffer, requested, elapsed); + + } while ((status == DEAD_OBJECT) && (tryCounter-- > 0)); - audioBuffer->frameCount = framesReq; - audioBuffer->size = framesReq * mFrameSize; - audioBuffer->raw = mProxy->buffer(u); - active = mActive; - return active ? status_t(NO_ERROR) : status_t(STOPPED); + audioBuffer->frameCount = buffer.mFrameCount; + audioBuffer->size = buffer.mFrameCount * mFrameSize; + audioBuffer->raw = buffer.mRaw; + if (nonContig != NULL) { + *nonContig = buffer.mNonContig; + } + return status; } void AudioRecord::releaseBuffer(Buffer* audioBuffer) { - ALOG_ASSERT(mStatus == NO_ERROR && mProxy != NULL); + // all TRANSFER_* are valid + + size_t stepCount = audioBuffer->size / mFrameSize; + if (stepCount == 0) { + return; + } + + Proxy::Buffer buffer; + buffer.mFrameCount = stepCount; + buffer.mRaw = audioBuffer->raw; AutoMutex lock(mLock); - (void) mProxy->stepUser(audioBuffer->frameCount); + mInOverrun = false; + mProxy->releaseBuffer(&buffer); + + // the server does not automatically disable recorder on overrun, so no need to restart } audio_io_handle_t AudioRecord::getInput() const @@ -616,215 +613,304 @@ audio_io_handle_t AudioRecord::getInput_l() return mInput; } -int AudioRecord::getSessionId() const -{ - // no lock needed because session ID doesn't change after first set() - return mSessionId; -} - // ------------------------------------------------------------------------- ssize_t AudioRecord::read(void* buffer, size_t userSize) { - ssize_t read = 0; - Buffer audioBuffer; - int8_t *dst = static_cast(buffer); + if (mTransfer != TRANSFER_SYNC) { + return INVALID_OPERATION; + } - if (ssize_t(userSize) < 0) { - // sanity-check. user is most-likely passing an error code. - ALOGE("AudioRecord::read(buffer=%p, size=%u (%d)", - buffer, userSize, userSize); + if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) { + // sanity-check. user is most-likely passing an error code, and it would + // make the return value ambiguous (actualSize vs error). + ALOGE("AudioRecord::read(buffer=%p, size=%u (%d)", buffer, userSize, userSize); return BAD_VALUE; } - mLock.lock(); - // acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed - // while we are accessing the cblk - sp audioRecord = mAudioRecord; - sp iMem = mCblkMemory; - mLock.unlock(); - - do { + ssize_t read = 0; + Buffer audioBuffer; - audioBuffer.frameCount = userSize/frameSize(); + while (userSize >= mFrameSize) { + audioBuffer.frameCount = userSize / mFrameSize; - // By using a wait count corresponding to twice the timeout period in - // obtainBuffer() we give a chance to recover once for a read timeout - // (if media_server crashed for instance) before returning a length of - // 0 bytes read to the client - status_t err = obtainBuffer(&audioBuffer, ((2 * MAX_RUN_TIMEOUT_MS) / WAIT_PERIOD_MS)); + status_t err = obtainBuffer(&audioBuffer, &ClientProxy::kForever); if (err < 0) { - // out of buffers, return #bytes written - if (err == status_t(NO_MORE_BUFFERS)) { + if (read > 0) { break; } - if (err == status_t(TIMED_OUT)) { - // return partial transfer count - return read; - } return ssize_t(err); } size_t bytesRead = audioBuffer.size; - memcpy(dst, audioBuffer.i8, bytesRead); - - dst += bytesRead; + memcpy(buffer, audioBuffer.i8, bytesRead); + buffer = ((char *) buffer) + bytesRead; userSize -= bytesRead; read += bytesRead; releaseBuffer(&audioBuffer); - } while (userSize); + } return read; } // ------------------------------------------------------------------------- -bool AudioRecord::processAudioBuffer(const sp& thread) +nsecs_t AudioRecord::processAudioBuffer(const sp& thread) { - Buffer audioBuffer; - uint32_t frames = mRemainingFrames; - size_t readSize; - mLock.lock(); - // acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed - // while we are accessing the cblk - sp audioRecord = mAudioRecord; - sp iMem = mCblkMemory; - audio_track_cblk_t* cblk = mCblk; + + // Can only reference mCblk while locked + int32_t flags = android_atomic_and(~CBLK_OVERRUN, &mCblk->flags); + + // Check for track invalidation + if (flags & CBLK_INVALID) { + (void) restoreRecord_l("processAudioBuffer"); + mLock.unlock(); + // Run again immediately, but with a new IAudioRecord + return 0; + } + bool active = mActive; - uint32_t markerPosition = mMarkerPosition; - uint32_t newPosition = mNewPosition; - uint32_t user = cblk->user; - // determine whether a marker callback will be needed, while locked - bool needMarker = !mMarkerReached && (mMarkerPosition > 0) && (user >= mMarkerPosition); - if (needMarker) { - mMarkerReached = true; - } - // determine the number of new position callback(s) that will be needed, while locked + + // Manage overrun callback, must be done under lock to avoid race with releaseBuffer() + bool newOverrun = false; + if (flags & CBLK_OVERRUN) { + if (!mInOverrun) { + mInOverrun = true; + newOverrun = true; + } + } + + // Get current position of server + size_t position = mProxy->getPosition(); + + // Manage marker callback + bool markerReached = false; + size_t markerPosition = mMarkerPosition; + // FIXME fails for wraparound, need 64 bits + if (!mMarkerReached && (markerPosition > 0) && (position >= markerPosition)) { + mMarkerReached = markerReached = true; + } + + // Determine the number of new position callback(s) that will be needed, while locked + size_t newPosCount = 0; + size_t newPosition = mNewPosition; uint32_t updatePeriod = mUpdatePeriod; - uint32_t needNewPos = updatePeriod > 0 && user >= newPosition ? - ((user - newPosition) / updatePeriod) + 1 : 0; - mNewPosition = newPosition + updatePeriod * needNewPos; + // FIXME fails for wraparound, need 64 bits + if (updatePeriod > 0 && position >= newPosition) { + newPosCount = ((position - newPosition) / updatePeriod) + 1; + mNewPosition += updatePeriod * newPosCount; + } + + // Cache other fields that will be needed soon + size_t notificationFrames = mNotificationFrames; + if (mRefreshRemaining) { + mRefreshRemaining = false; + mRemainingFrames = notificationFrames; + mRetryOnPartialBuffer = false; + } + size_t misalignment = mProxy->getMisalignment(); + int32_t sequence = mSequence; + + // These fields don't need to be cached, because they are assigned only by set(): + // mTransfer, mCbf, mUserData, mSampleRate + mLock.unlock(); - // perform marker callback, while unlocked - if (needMarker) { + // perform callbacks while unlocked + if (newOverrun) { + mCbf(EVENT_OVERRUN, mUserData, NULL); + } + if (markerReached) { mCbf(EVENT_MARKER, mUserData, &markerPosition); } - - // perform new position callback(s), while unlocked - for (; needNewPos > 0; --needNewPos) { - uint32_t temp = newPosition; + while (newPosCount > 0) { + size_t temp = newPosition; mCbf(EVENT_NEW_POS, mUserData, &temp); newPosition += updatePeriod; + newPosCount--; + } + if (mObservedSequence != sequence) { + mObservedSequence = sequence; + mCbf(EVENT_NEW_IAUDIORECORD, mUserData, NULL); } - do { - audioBuffer.frameCount = frames; - // Calling obtainBuffer() with a wait count of 1 - // limits wait time to WAIT_PERIOD_MS. This prevents from being - // stuck here not being able to handle timed events (position, markers). - status_t err = obtainBuffer(&audioBuffer, 1); - if (err < NO_ERROR) { - if (err != TIMED_OUT) { - ALOGE_IF(err != status_t(NO_MORE_BUFFERS), - "Error obtaining an audio buffer, giving up."); - return false; + // if inactive, then don't run me again until re-started + if (!active) { + return NS_INACTIVE; + } + + // Compute the estimated time until the next timed event (position, markers) + uint32_t minFrames = ~0; + if (!markerReached && position < markerPosition) { + minFrames = markerPosition - position; + } + if (updatePeriod > 0 && updatePeriod < minFrames) { + minFrames = updatePeriod; + } + + // If > 0, poll periodically to recover from a stuck server. A good value is 2. + static const uint32_t kPoll = 0; + if (kPoll > 0 && mTransfer == TRANSFER_CALLBACK && kPoll * notificationFrames < minFrames) { + minFrames = kPoll * notificationFrames; + } + + // Convert frame units to time units + nsecs_t ns = NS_WHENEVER; + if (minFrames != (uint32_t) ~0) { + // This "fudge factor" avoids soaking CPU, and compensates for late progress by server + static const nsecs_t kFudgeNs = 10000000LL; // 10 ms + ns = ((minFrames * 1000000000LL) / mSampleRate) + kFudgeNs; + } + + // If not supplying data by EVENT_MORE_DATA, then we're done + if (mTransfer != TRANSFER_CALLBACK) { + return ns; + } + + struct timespec timeout; + const struct timespec *requested = &ClientProxy::kForever; + if (ns != NS_WHENEVER) { + timeout.tv_sec = ns / 1000000000LL; + timeout.tv_nsec = ns % 1000000000LL; + ALOGV("timeout %ld.%03d", timeout.tv_sec, (int) timeout.tv_nsec / 1000000); + requested = &timeout; + } + + while (mRemainingFrames > 0) { + + Buffer audioBuffer; + audioBuffer.frameCount = mRemainingFrames; + size_t nonContig; + status_t err = obtainBuffer(&audioBuffer, requested, NULL, &nonContig); + LOG_ALWAYS_FATAL_IF((err != NO_ERROR) != (audioBuffer.frameCount == 0), + "obtainBuffer() err=%d frameCount=%u", err, audioBuffer.frameCount); + requested = &ClientProxy::kNonBlocking; + size_t avail = audioBuffer.frameCount + nonContig; + ALOGV("obtainBuffer(%u) returned %u = %u + %u", + mRemainingFrames, avail, audioBuffer.frameCount, nonContig); + if (err != NO_ERROR) { + if (err == TIMED_OUT || err == WOULD_BLOCK || err == -EINTR) { + break; + } + ALOGE("Error %d obtaining an audio buffer, giving up.", err); + return NS_NEVER; + } + + if (mRetryOnPartialBuffer) { + mRetryOnPartialBuffer = false; + if (avail < mRemainingFrames) { + int64_t myns = ((mRemainingFrames - avail) * + 1100000000LL) / mSampleRate; + if (ns < 0 || myns < ns) { + ns = myns; + } + return ns; } - break; } - if (err == status_t(STOPPED)) return false; size_t reqSize = audioBuffer.size; mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer); - readSize = audioBuffer.size; + size_t readSize = audioBuffer.size; // Sanity check on returned size - if (ssize_t(readSize) <= 0) { - // The callback is done filling buffers + if (ssize_t(readSize) < 0 || readSize > reqSize) { + ALOGE("EVENT_MORE_DATA requested %u bytes but callback returned %d bytes", + reqSize, (int) readSize); + return NS_NEVER; + } + + if (readSize == 0) { + // The callback is done consuming buffers // Keep this thread going to handle timed events and - // still try to get more data in intervals of WAIT_PERIOD_MS + // still try to provide more data in intervals of WAIT_PERIOD_MS // but don't just loop and block the CPU, so wait - usleep(WAIT_PERIOD_MS*1000); - break; + return WAIT_PERIOD_MS * 1000000LL; } - if (readSize > reqSize) readSize = reqSize; - audioBuffer.size = readSize; - audioBuffer.frameCount = readSize/frameSize(); - frames -= audioBuffer.frameCount; + size_t releasedFrames = readSize / mFrameSize; + audioBuffer.frameCount = releasedFrames; + mRemainingFrames -= releasedFrames; + if (misalignment >= releasedFrames) { + misalignment -= releasedFrames; + } else { + misalignment = 0; + } releaseBuffer(&audioBuffer); - } while (frames); + // FIXME here is where we would repeat EVENT_MORE_DATA again on same advanced buffer + // if callback doesn't like to accept the full chunk + if (readSize < reqSize) { + continue; + } + // There could be enough non-contiguous frames available to satisfy the remaining request + if (mRemainingFrames <= nonContig) { + continue; + } - // Manage overrun callback - if (active && (mProxy->framesAvailable() == 0)) { - // The value of active is stale, but we are almost sure to be active here because - // otherwise we would have exited when obtainBuffer returned STOPPED earlier. - ALOGV("Overrun user: %x, server: %x, flags %04x", cblk->user, cblk->server, cblk->flags); - if (!(android_atomic_or(CBLK_UNDERRUN, &cblk->flags) & CBLK_UNDERRUN)) { - mCbf(EVENT_OVERRUN, mUserData, NULL); +#if 0 + // This heuristic tries to collapse a series of EVENT_MORE_DATA that would total to a + // sum <= notificationFrames. It replaces that series by at most two EVENT_MORE_DATA + // that total to a sum == notificationFrames. + if (0 < misalignment && misalignment <= mRemainingFrames) { + mRemainingFrames = misalignment; + return (mRemainingFrames * 1100000000LL) / mSampleRate; } - } +#endif - if (frames == 0) { - mRemainingFrames = mNotificationFrames; - } else { - mRemainingFrames = frames; } - return true; + mRemainingFrames = notificationFrames; + mRetryOnPartialBuffer = true; + + // A lot has transpired since ns was calculated, so run again immediately and re-calculate + return 0; } -// must be called with mLock and cblk.lock held. Callers must also hold strong references on -// the IAudioRecord and IMemory in case they are recreated here. -// If the IAudioRecord is successfully restored, the cblk pointer is updated -status_t AudioRecord::restoreRecord_l(audio_track_cblk_t*& refCblk) +status_t AudioRecord::restoreRecord_l(const char *from) { + ALOGW("dead IAudioRecord, creating a new one from %s()", from); + ++mSequence; status_t result; - audio_track_cblk_t* cblk = refCblk; - audio_track_cblk_t* newCblk = cblk; - ALOGW("dead IAudioRecord, creating a new one"); - - // signal old cblk condition so that other threads waiting for available buffers stop - // waiting now - cblk->cv.broadcast(); - cblk->lock.unlock(); - // if the new IAudioRecord is created, openRecord_l() will modify the // following member variables: mAudioRecord, mCblkMemory and mCblk. // It will also delete the strong references on previous IAudioRecord and IMemory - result = openRecord_l(mSampleRate, mFormat, mFrameCount, getInput_l()); + size_t position = mProxy->getPosition(); + mNewPosition = position + mUpdatePeriod; + result = openRecord_l(mSampleRate, mFormat, mFrameCount, getInput_l(), position); if (result == NO_ERROR) { - newCblk = mCblk; - // callback thread or sync event hasn't changed - result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, 0); + if (mActive) { + // callback thread or sync event hasn't changed + // FIXME this fails if we have a new AudioFlinger instance + result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, 0); + } } if (result != NO_ERROR) { + ALOGW("restoreRecord_l() failed status %d", result); mActive = false; } - ALOGV("restoreRecord_l() status %d mActive %d cblk %p, old cblk %p flags %08x old flags %08x", - result, mActive, newCblk, cblk, newCblk->flags, cblk->flags); - - if (result == NO_ERROR) { - // from now on we switch to the newly created cblk - refCblk = newCblk; - } - newCblk->lock.lock(); + return result; +} - ALOGW_IF(result != NO_ERROR, "restoreRecord_l() error %d", result); +// ========================================================================= - return result; +void AudioRecord::DeathNotifier::binderDied(const wp& who) +{ + sp audioRecord = mAudioRecord.promote(); + if (audioRecord != 0) { + AutoMutex lock(audioRecord->mLock); + audioRecord->mProxy->binderDied(); + } } // ========================================================================= AudioRecord::AudioRecordThread::AudioRecordThread(AudioRecord& receiver, bool bCanCallJava) - : Thread(bCanCallJava), mReceiver(receiver), mPaused(true) + : Thread(bCanCallJava), mReceiver(receiver), mPaused(true), mResumeLatch(false) { } @@ -842,10 +928,26 @@ bool AudioRecord::AudioRecordThread::threadLoop() return true; } } - if (!mReceiver.processAudioBuffer(this)) { - pause(); + nsecs_t ns = mReceiver.processAudioBuffer(this); + switch (ns) { + case 0: + return true; + case NS_WHENEVER: + sleep(1); + return true; + case NS_INACTIVE: + pauseConditional(); + return true; + case NS_NEVER: + return false; + default: + LOG_ALWAYS_FATAL_IF(ns < 0, "processAudioBuffer() returned %lld", ns); + struct timespec req; + req.tv_sec = ns / 1000000000LL; + req.tv_nsec = ns % 1000000000LL; + nanosleep(&req, NULL /*rem*/); + return true; } - return true; } void AudioRecord::AudioRecordThread::requestExit() @@ -859,6 +961,17 @@ void AudioRecord::AudioRecordThread::pause() { AutoMutex _l(mMyLock); mPaused = true; + mResumeLatch = false; +} + +void AudioRecord::AudioRecordThread::pauseConditional() +{ + AutoMutex _l(mMyLock); + if (mResumeLatch) { + mResumeLatch = false; + } else { + mPaused = true; + } } void AudioRecord::AudioRecordThread::resume() @@ -866,7 +979,10 @@ void AudioRecord::AudioRecordThread::resume() AutoMutex _l(mMyLock); if (mPaused) { mPaused = false; + mResumeLatch = false; mMyCond.signal(); + } else { + mResumeLatch = true; } } -- cgit v1.1