From 2234002b0710c8db73f82d397cb945cd541c6bbb Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Mon, 7 Apr 2014 12:04:41 -0700 Subject: Start pulling bits of FastMixer up to FastThread Change-Id: I4c6f7b8f88fcf107bb29ee6432feecd4ab6554d2 --- services/audioflinger/Android.mk | 3 +- services/audioflinger/Configuration.h | 1 + services/audioflinger/FastMixer.cpp | 888 +++++++++++------------------ services/audioflinger/FastMixer.h | 134 ++--- services/audioflinger/FastMixerDumpState.h | 95 +++ services/audioflinger/FastMixerState.cpp | 5 +- services/audioflinger/FastMixerState.h | 1 - services/audioflinger/FastThread.cpp | 348 +++++++++++ services/audioflinger/FastThread.h | 60 +- services/audioflinger/FastThreadState.cpp | 21 +- services/audioflinger/FastThreadState.h | 44 +- 11 files changed, 932 insertions(+), 668 deletions(-) create mode 100644 services/audioflinger/FastMixerDumpState.h create mode 100644 services/audioflinger/FastThread.cpp (limited to 'services/audioflinger') diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk index 27e38a3..8d0a705 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -61,7 +61,8 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_MODULE:= libaudioflinger LOCAL_32_BIT_ONLY := true -LOCAL_SRC_FILES += FastMixer.cpp FastMixerState.cpp AudioWatchdog.cpp FastThreadState.cpp +LOCAL_SRC_FILES += FastMixer.cpp FastMixerState.cpp AudioWatchdog.cpp +LOCAL_SRC_FILES += FastThread.cpp FastThreadState.cpp LOCAL_CFLAGS += -DSTATE_QUEUE_INSTANTIATIONS='"StateQueueInstantiations.cpp"' diff --git a/services/audioflinger/Configuration.h b/services/audioflinger/Configuration.h index 0754d9d..6a8aeb1 100644 --- a/services/audioflinger/Configuration.h +++ b/services/audioflinger/Configuration.h @@ -31,6 +31,7 @@ // uncomment to enable fast mixer to take performance samples for later statistical analysis #define FAST_MIXER_STATISTICS +// FIXME rename to FAST_THREAD_STATISTICS // uncomment for debugging timing problems related to StateQueue::push() //#define STATE_QUEUE_DUMP diff --git a/services/audioflinger/FastMixer.cpp b/services/audioflinger/FastMixer.cpp index ca0d65e..5cb42cc 100644 --- a/services/audioflinger/FastMixer.cpp +++ b/services/audioflinger/FastMixer.cpp @@ -40,619 +40,385 @@ #include "AudioMixer.h" #include "FastMixer.h" -#define FAST_HOT_IDLE_NS 1000000L // 1 ms: time to sleep while hot idling -#define FAST_DEFAULT_NS 999999999L // ~1 sec: default time to sleep -#define MIN_WARMUP_CYCLES 2 // minimum number of loop cycles to wait for warmup -#define MAX_WARMUP_CYCLES 10 // maximum number of loop cycles to wait for warmup - #define FCC_2 2 // fixed channel count assumption namespace android { -// Fast mixer thread -bool FastMixer::threadLoop() +/*static*/ const FastMixerState FastMixer::initial; + +FastMixer::FastMixer() : FastThread(), + slopNs(0), + // fastTrackNames + // generations + outputSink(NULL), + outputSinkGen(0), + mixer(NULL), + mixBuffer(NULL), + mixBufferState(UNDEFINED), + format(Format_Invalid), + sampleRate(0), + fastTracksGen(0), + totalNativeFramesWritten(0), + // timestamp + nativeFramesWrittenButNotPresented(0) // the = 0 is to silence the compiler { - static const FastMixerState initial; - const FastMixerState *previous = &initial, *current = &initial; - FastMixerState preIdle; // copy of state before we went into idle - struct timespec oldTs = {0, 0}; - bool oldTsValid = false; - long slopNs = 0; // accumulated time we've woken up too early (> 0) or too late (< 0) - long sleepNs = -1; // -1: busy wait, 0: sched_yield, > 0: nanosleep - int fastTrackNames[FastMixerState::kMaxFastTracks]; // handles used by mixer to identify tracks - int generations[FastMixerState::kMaxFastTracks]; // last observed mFastTracks[i].mGeneration + // FIXME pass initial as parameter to base class constructor, and make it static local + previous = &initial; + current = &initial; + + mDummyDumpState = &dummyDumpState; + unsigned i; for (i = 0; i < FastMixerState::kMaxFastTracks; ++i) { fastTrackNames[i] = -1; generations[i] = 0; } - NBAIO_Sink *outputSink = NULL; - int outputSinkGen = 0; - AudioMixer* mixer = NULL; - short *mixBuffer = NULL; - enum {UNDEFINED, MIXED, ZEROED} mixBufferState = UNDEFINED; - NBAIO_Format format = Format_Invalid; - unsigned sampleRate = 0; - int fastTracksGen = 0; - long periodNs = 0; // expected period; the time required to render one mix buffer - long underrunNs = 0; // underrun likely when write cycle is greater than this value - long overrunNs = 0; // overrun likely when write cycle is less than this value - long forceNs = 0; // if overrun detected, force the write cycle to take this much time - long warmupNs = 0; // warmup complete when write cycle is greater than to this value - FastMixerDumpState dummyDumpState, *dumpState = &dummyDumpState; - bool ignoreNextOverrun = true; // used to ignore initial overrun and first after an underrun #ifdef FAST_MIXER_STATISTICS - struct timespec oldLoad = {0, 0}; // previous value of clock_gettime(CLOCK_THREAD_CPUTIME_ID) - bool oldLoadValid = false; // whether oldLoad is valid - uint32_t bounds = 0; - bool full = false; // whether we have collected at least mSamplingN samples -#ifdef CPU_FREQUENCY_STATISTICS - ThreadCpuUsage tcu; // for reading the current CPU clock frequency in kHz -#endif + oldLoad.tv_sec = 0; + oldLoad.tv_nsec = 0; #endif - unsigned coldGen = 0; // last observed mColdGen - bool isWarm = false; // true means ready to mix, false means wait for warmup before mixing - struct timespec measuredWarmupTs = {0, 0}; // how long did it take for warmup to complete - uint32_t warmupCycles = 0; // counter of number of loop cycles required to warmup - NBAIO_Sink* teeSink = NULL; // if non-NULL, then duplicate write() to this non-blocking sink - NBLog::Writer dummyLogWriter, *logWriter = &dummyLogWriter; - uint32_t totalNativeFramesWritten = 0; // copied to dumpState->mFramesWritten - - // next 2 fields are valid only when timestampStatus == NO_ERROR - AudioTimestamp timestamp; - uint32_t nativeFramesWrittenButNotPresented = 0; // the = 0 is to silence the compiler - status_t timestampStatus = INVALID_OPERATION; - - for (;;) { - - // either nanosleep, sched_yield, or busy wait - if (sleepNs >= 0) { - if (sleepNs > 0) { - ALOG_ASSERT(sleepNs < 1000000000); - const struct timespec req = {0, sleepNs}; - nanosleep(&req, NULL); - } else { - sched_yield(); - } - } - // default to long sleep for next cycle - sleepNs = FAST_DEFAULT_NS; - - // poll for state change - const FastMixerState *next = mSQ.poll(); - if (next == NULL) { - // continue to use the default initial state until a real state is available - ALOG_ASSERT(current == &initial && previous == &initial); - next = current; - } +} - FastMixerState::Command command = next->mCommand; - if (next != current) { +FastMixer::~FastMixer() +{ +} - // As soon as possible of learning of a new dump area, start using it - dumpState = next->mDumpState != NULL ? next->mDumpState : &dummyDumpState; - teeSink = next->mTeeSink; - logWriter = next->mNBLogWriter != NULL ? next->mNBLogWriter : &dummyLogWriter; - if (mixer != NULL) { - mixer->setLog(logWriter); - } +FastMixerStateQueue* FastMixer::sq() +{ + return &mSQ; +} - // We want to always have a valid reference to the previous (non-idle) state. - // However, the state queue only guarantees access to current and previous states. - // So when there is a transition from a non-idle state into an idle state, we make a - // copy of the last known non-idle state so it is still available on return from idle. - // The possible transitions are: - // non-idle -> non-idle update previous from current in-place - // non-idle -> idle update previous from copy of current - // idle -> idle don't update previous - // idle -> non-idle don't update previous - if (!(current->mCommand & FastMixerState::IDLE)) { - if (command & FastMixerState::IDLE) { - preIdle = *current; - current = &preIdle; - oldTsValid = false; -#ifdef FAST_MIXER_STATISTICS - oldLoadValid = false; -#endif - ignoreNextOverrun = true; - } - previous = current; - } - current = next; - } -#if !LOG_NDEBUG - next = NULL; // not referenced again -#endif +const FastThreadState *FastMixer::poll() +{ + return mSQ.poll(); +} - dumpState->mCommand = command; - - switch (command) { - case FastMixerState::INITIAL: - case FastMixerState::HOT_IDLE: - sleepNs = FAST_HOT_IDLE_NS; - continue; - case FastMixerState::COLD_IDLE: - // only perform a cold idle command once - // FIXME consider checking previous state and only perform if previous != COLD_IDLE - if (current->mColdGen != coldGen) { - int32_t *coldFutexAddr = current->mColdFutexAddr; - ALOG_ASSERT(coldFutexAddr != NULL); - int32_t old = android_atomic_dec(coldFutexAddr); - if (old <= 0) { - __futex_syscall4(coldFutexAddr, FUTEX_WAIT_PRIVATE, old - 1, NULL); - } - int policy = sched_getscheduler(0); - if (!(policy == SCHED_FIFO || policy == SCHED_RR)) { - ALOGE("did not receive expected priority boost"); - } - // This may be overly conservative; there could be times that the normal mixer - // requests such a brief cold idle that it doesn't require resetting this flag. - isWarm = false; - measuredWarmupTs.tv_sec = 0; - measuredWarmupTs.tv_nsec = 0; - warmupCycles = 0; - sleepNs = -1; - coldGen = current->mColdGen; -#ifdef FAST_MIXER_STATISTICS - bounds = 0; - full = false; -#endif - oldTsValid = !clock_gettime(CLOCK_MONOTONIC, &oldTs); - timestampStatus = INVALID_OPERATION; - } else { - sleepNs = FAST_HOT_IDLE_NS; - } - continue; - case FastMixerState::EXIT: - delete mixer; - delete[] mixBuffer; - return false; - case FastMixerState::MIX: - case FastMixerState::WRITE: - case FastMixerState::MIX_WRITE: - break; - default: - LOG_ALWAYS_FATAL("bad command %d", command); - } +void FastMixer::setLog(NBLog::Writer *logWriter) +{ + if (mixer != NULL) { + mixer->setLog(logWriter); + } +} - // there is a non-idle state available to us; did the state change? - size_t frameCount = current->mFrameCount; - if (current != previous) { - - // handle state change here, but since we want to diff the state, - // we're prepared for previous == &initial the first time through - unsigned previousTrackMask; - - // check for change in output HAL configuration - NBAIO_Format previousFormat = format; - if (current->mOutputSinkGen != outputSinkGen) { - outputSink = current->mOutputSink; - outputSinkGen = current->mOutputSinkGen; - if (outputSink == NULL) { - format = Format_Invalid; - sampleRate = 0; - } else { - format = outputSink->format(); - sampleRate = Format_sampleRate(format); - ALOG_ASSERT(Format_channelCount(format) == FCC_2); - } - dumpState->mSampleRate = sampleRate; - } +void FastMixer::onIdle() +{ + preIdle = *(const FastMixerState *)current; + current = &preIdle; +} - if ((!Format_isEqual(format, previousFormat)) || (frameCount != previous->mFrameCount)) { - // FIXME to avoid priority inversion, don't delete here - delete mixer; - mixer = NULL; - delete[] mixBuffer; - mixBuffer = NULL; - if (frameCount > 0 && sampleRate > 0) { - // FIXME new may block for unbounded time at internal mutex of the heap - // implementation; it would be better to have normal mixer allocate for us - // to avoid blocking here and to prevent possible priority inversion - mixer = new AudioMixer(frameCount, sampleRate, FastMixerState::kMaxFastTracks); - mixBuffer = new short[frameCount * FCC_2]; - periodNs = (frameCount * 1000000000LL) / sampleRate; // 1.00 - underrunNs = (frameCount * 1750000000LL) / sampleRate; // 1.75 - overrunNs = (frameCount * 500000000LL) / sampleRate; // 0.50 - forceNs = (frameCount * 950000000LL) / sampleRate; // 0.95 - warmupNs = (frameCount * 500000000LL) / sampleRate; // 0.50 - } else { - periodNs = 0; - underrunNs = 0; - overrunNs = 0; - forceNs = 0; - warmupNs = 0; - } - mixBufferState = UNDEFINED; +void FastMixer::onExit() +{ + delete mixer; + delete[] mixBuffer; +} + +bool FastMixer::isSubClassCommand(FastThreadState::Command command) +{ + switch ((FastMixerState::Command) command) { + case FastMixerState::MIX: + case FastMixerState::WRITE: + case FastMixerState::MIX_WRITE: + return true; + default: + return false; + } +} + +void FastMixer::onStateChange() +{ + const FastMixerState * const current = (const FastMixerState *) this->current; + const FastMixerState * const previous = (const FastMixerState *) this->previous; + FastMixerDumpState * const dumpState = (FastMixerDumpState *) this->dumpState; + const size_t frameCount = current->mFrameCount; + + // handle state change here, but since we want to diff the state, + // we're prepared for previous == &initial the first time through + unsigned previousTrackMask; + + // check for change in output HAL configuration + NBAIO_Format previousFormat = format; + if (current->mOutputSinkGen != outputSinkGen) { + outputSink = current->mOutputSink; + outputSinkGen = current->mOutputSinkGen; + if (outputSink == NULL) { + format = Format_Invalid; + sampleRate = 0; + } else { + format = outputSink->format(); + sampleRate = Format_sampleRate(format); + ALOG_ASSERT(Format_channelCount(format) == FCC_2); + } + dumpState->mSampleRate = sampleRate; + } + + if ((!Format_isEqual(format, previousFormat)) || (frameCount != previous->mFrameCount)) { + // FIXME to avoid priority inversion, don't delete here + delete mixer; + mixer = NULL; + delete[] mixBuffer; + mixBuffer = NULL; + if (frameCount > 0 && sampleRate > 0) { + // FIXME new may block for unbounded time at internal mutex of the heap + // implementation; it would be better to have normal mixer allocate for us + // to avoid blocking here and to prevent possible priority inversion + mixer = new AudioMixer(frameCount, sampleRate, FastMixerState::kMaxFastTracks); + mixBuffer = new short[frameCount * FCC_2]; + periodNs = (frameCount * 1000000000LL) / sampleRate; // 1.00 + underrunNs = (frameCount * 1750000000LL) / sampleRate; // 1.75 + overrunNs = (frameCount * 500000000LL) / sampleRate; // 0.50 + forceNs = (frameCount * 950000000LL) / sampleRate; // 0.95 + warmupNs = (frameCount * 500000000LL) / sampleRate; // 0.50 + } else { + periodNs = 0; + underrunNs = 0; + overrunNs = 0; + forceNs = 0; + warmupNs = 0; + } + mixBufferState = UNDEFINED; #if !LOG_NDEBUG - for (i = 0; i < FastMixerState::kMaxFastTracks; ++i) { - fastTrackNames[i] = -1; - } + for (unsigned i = 0; i < FastMixerState::kMaxFastTracks; ++i) { + fastTrackNames[i] = -1; + } #endif - // we need to reconfigure all active tracks - previousTrackMask = 0; - fastTracksGen = current->mFastTracksGen - 1; - dumpState->mFrameCount = frameCount; - } else { - previousTrackMask = previous->mTrackMask; - } + // we need to reconfigure all active tracks + previousTrackMask = 0; + fastTracksGen = current->mFastTracksGen - 1; + dumpState->mFrameCount = frameCount; + } else { + previousTrackMask = previous->mTrackMask; + } - // check for change in active track set - unsigned currentTrackMask = current->mTrackMask; - dumpState->mTrackMask = currentTrackMask; - if (current->mFastTracksGen != fastTracksGen) { - ALOG_ASSERT(mixBuffer != NULL); - int name; - - // process removed tracks first to avoid running out of track names - unsigned removedTracks = previousTrackMask & ~currentTrackMask; - while (removedTracks != 0) { - i = __builtin_ctz(removedTracks); - removedTracks &= ~(1 << i); - const FastTrack* fastTrack = ¤t->mFastTracks[i]; - ALOG_ASSERT(fastTrack->mBufferProvider == NULL); - if (mixer != NULL) { - name = fastTrackNames[i]; - ALOG_ASSERT(name >= 0); - mixer->deleteTrackName(name); - } + // check for change in active track set + const unsigned currentTrackMask = current->mTrackMask; + dumpState->mTrackMask = currentTrackMask; + if (current->mFastTracksGen != fastTracksGen) { + ALOG_ASSERT(mixBuffer != NULL); + int name; + + // process removed tracks first to avoid running out of track names + unsigned removedTracks = previousTrackMask & ~currentTrackMask; + while (removedTracks != 0) { + int i = __builtin_ctz(removedTracks); + removedTracks &= ~(1 << i); + const FastTrack* fastTrack = ¤t->mFastTracks[i]; + ALOG_ASSERT(fastTrack->mBufferProvider == NULL); + if (mixer != NULL) { + name = fastTrackNames[i]; + ALOG_ASSERT(name >= 0); + mixer->deleteTrackName(name); + } #if !LOG_NDEBUG - fastTrackNames[i] = -1; + fastTrackNames[i] = -1; #endif - // don't reset track dump state, since other side is ignoring it - generations[i] = fastTrack->mGeneration; - } + // don't reset track dump state, since other side is ignoring it + generations[i] = fastTrack->mGeneration; + } - // now process added tracks - unsigned addedTracks = currentTrackMask & ~previousTrackMask; - while (addedTracks != 0) { - i = __builtin_ctz(addedTracks); - addedTracks &= ~(1 << i); - const FastTrack* fastTrack = ¤t->mFastTracks[i]; - AudioBufferProvider *bufferProvider = fastTrack->mBufferProvider; - ALOG_ASSERT(bufferProvider != NULL && fastTrackNames[i] == -1); - if (mixer != NULL) { - // calling getTrackName with default channel mask and a random invalid - // sessionId (no effects here) - name = mixer->getTrackName(AUDIO_CHANNEL_OUT_STEREO, -555); - ALOG_ASSERT(name >= 0); - fastTrackNames[i] = name; - mixer->setBufferProvider(name, bufferProvider); - mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER, - (void *) mixBuffer); - // newly allocated track names default to full scale volume - mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::CHANNEL_MASK, - (void *)(uintptr_t)fastTrack->mChannelMask); - mixer->enable(name); - } - generations[i] = fastTrack->mGeneration; - } + // now process added tracks + unsigned addedTracks = currentTrackMask & ~previousTrackMask; + while (addedTracks != 0) { + int i = __builtin_ctz(addedTracks); + addedTracks &= ~(1 << i); + const FastTrack* fastTrack = ¤t->mFastTracks[i]; + AudioBufferProvider *bufferProvider = fastTrack->mBufferProvider; + ALOG_ASSERT(bufferProvider != NULL && fastTrackNames[i] == -1); + if (mixer != NULL) { + // calling getTrackName with default channel mask and a random invalid + // sessionId (no effects here) + name = mixer->getTrackName(AUDIO_CHANNEL_OUT_STEREO, -555); + ALOG_ASSERT(name >= 0); + fastTrackNames[i] = name; + mixer->setBufferProvider(name, bufferProvider); + mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER, + (void *) mixBuffer); + // newly allocated track names default to full scale volume + mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::CHANNEL_MASK, + (void *)(uintptr_t)fastTrack->mChannelMask); + mixer->enable(name); + } + generations[i] = fastTrack->mGeneration; + } - // finally process (potentially) modified tracks; these use the same slot - // but may have a different buffer provider or volume provider - unsigned modifiedTracks = currentTrackMask & previousTrackMask; - while (modifiedTracks != 0) { - i = __builtin_ctz(modifiedTracks); - modifiedTracks &= ~(1 << i); - const FastTrack* fastTrack = ¤t->mFastTracks[i]; - if (fastTrack->mGeneration != generations[i]) { - // this track was actually modified - AudioBufferProvider *bufferProvider = fastTrack->mBufferProvider; - ALOG_ASSERT(bufferProvider != NULL); - if (mixer != NULL) { - name = fastTrackNames[i]; - ALOG_ASSERT(name >= 0); - mixer->setBufferProvider(name, bufferProvider); - if (fastTrack->mVolumeProvider == NULL) { - mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, - (void *)0x1000); - mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, - (void *)0x1000); - } - mixer->setParameter(name, AudioMixer::RESAMPLE, - AudioMixer::REMOVE, NULL); - mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::CHANNEL_MASK, - (void *)(uintptr_t) fastTrack->mChannelMask); - // already enabled - } - generations[i] = fastTrack->mGeneration; + // finally process (potentially) modified tracks; these use the same slot + // but may have a different buffer provider or volume provider + unsigned modifiedTracks = currentTrackMask & previousTrackMask; + while (modifiedTracks != 0) { + int i = __builtin_ctz(modifiedTracks); + modifiedTracks &= ~(1 << i); + const FastTrack* fastTrack = ¤t->mFastTracks[i]; + if (fastTrack->mGeneration != generations[i]) { + // this track was actually modified + AudioBufferProvider *bufferProvider = fastTrack->mBufferProvider; + ALOG_ASSERT(bufferProvider != NULL); + if (mixer != NULL) { + name = fastTrackNames[i]; + ALOG_ASSERT(name >= 0); + mixer->setBufferProvider(name, bufferProvider); + if (fastTrack->mVolumeProvider == NULL) { + mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, + (void *)0x1000); + mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, + (void *)0x1000); } + mixer->setParameter(name, AudioMixer::RESAMPLE, + AudioMixer::REMOVE, NULL); + mixer->setParameter(name, AudioMixer::TRACK, AudioMixer::CHANNEL_MASK, + (void *)(uintptr_t) fastTrack->mChannelMask); + // already enabled } - - fastTracksGen = current->mFastTracksGen; - - dumpState->mNumTracks = popcount(currentTrackMask); + generations[i] = fastTrack->mGeneration; } - -#if 1 // FIXME shouldn't need this - // only process state change once - previous = current; -#endif } - // do work using current state here - if ((command & FastMixerState::MIX) && (mixer != NULL) && isWarm) { - ALOG_ASSERT(mixBuffer != NULL); - // for each track, update volume and check for underrun - unsigned currentTrackMask = current->mTrackMask; - while (currentTrackMask != 0) { - i = __builtin_ctz(currentTrackMask); - currentTrackMask &= ~(1 << i); - const FastTrack* fastTrack = ¤t->mFastTracks[i]; - - // Refresh the per-track timestamp - if (timestampStatus == NO_ERROR) { - uint32_t trackFramesWrittenButNotPresented = - nativeFramesWrittenButNotPresented; - uint32_t trackFramesWritten = fastTrack->mBufferProvider->framesReleased(); - // Can't provide an AudioTimestamp before first frame presented, - // or during the brief 32-bit wraparound window - if (trackFramesWritten >= trackFramesWrittenButNotPresented) { - AudioTimestamp perTrackTimestamp; - perTrackTimestamp.mPosition = - trackFramesWritten - trackFramesWrittenButNotPresented; - perTrackTimestamp.mTime = timestamp.mTime; - fastTrack->mBufferProvider->onTimestamp(perTrackTimestamp); - } - } + fastTracksGen = current->mFastTracksGen; - int name = fastTrackNames[i]; - ALOG_ASSERT(name >= 0); - if (fastTrack->mVolumeProvider != NULL) { - uint32_t vlr = fastTrack->mVolumeProvider->getVolumeLR(); - mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, - (void *)(uintptr_t)(vlr & 0xFFFF)); - mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, - (void *)(uintptr_t)(vlr >> 16)); - } - // FIXME The current implementation of framesReady() for fast tracks - // takes a tryLock, which can block - // up to 1 ms. If enough active tracks all blocked in sequence, this would result - // in the overall fast mix cycle being delayed. Should use a non-blocking FIFO. - size_t framesReady = fastTrack->mBufferProvider->framesReady(); - if (ATRACE_ENABLED()) { - // I wish we had formatted trace names - char traceName[16]; - strcpy(traceName, "fRdy"); - traceName[4] = i + (i < 10 ? '0' : 'A' - 10); - traceName[5] = '\0'; - ATRACE_INT(traceName, framesReady); - } - FastTrackDump *ftDump = &dumpState->mTracks[i]; - FastTrackUnderruns underruns = ftDump->mUnderruns; - if (framesReady < frameCount) { - if (framesReady == 0) { - underruns.mBitFields.mEmpty++; - underruns.mBitFields.mMostRecent = UNDERRUN_EMPTY; - mixer->disable(name); - } else { - // allow mixing partial buffer - underruns.mBitFields.mPartial++; - underruns.mBitFields.mMostRecent = UNDERRUN_PARTIAL; - mixer->enable(name); - } - } else { - underruns.mBitFields.mFull++; - underruns.mBitFields.mMostRecent = UNDERRUN_FULL; - mixer->enable(name); - } - ftDump->mUnderruns = underruns; - ftDump->mFramesReady = framesReady; - } + dumpState->mNumTracks = popcount(currentTrackMask); + } +} - int64_t pts; - if (outputSink == NULL || (OK != outputSink->getNextWriteTimestamp(&pts))) { - pts = AudioBufferProvider::kInvalidPTS; +void FastMixer::onWork() +{ + const FastMixerState * const current = (const FastMixerState *) this->current; + FastMixerDumpState * const dumpState = (FastMixerDumpState *) this->dumpState; + const FastMixerState::Command command = this->command; + const size_t frameCount = current->mFrameCount; + + if ((command & FastMixerState::MIX) && (mixer != NULL) && isWarm) { + ALOG_ASSERT(mixBuffer != NULL); + // for each track, update volume and check for underrun + unsigned currentTrackMask = current->mTrackMask; + while (currentTrackMask != 0) { + int i = __builtin_ctz(currentTrackMask); + currentTrackMask &= ~(1 << i); + const FastTrack* fastTrack = ¤t->mFastTracks[i]; + + // Refresh the per-track timestamp + if (timestampStatus == NO_ERROR) { + uint32_t trackFramesWrittenButNotPresented = + nativeFramesWrittenButNotPresented; + uint32_t trackFramesWritten = fastTrack->mBufferProvider->framesReleased(); + // Can't provide an AudioTimestamp before first frame presented, + // or during the brief 32-bit wraparound window + if (trackFramesWritten >= trackFramesWrittenButNotPresented) { + AudioTimestamp perTrackTimestamp; + perTrackTimestamp.mPosition = + trackFramesWritten - trackFramesWrittenButNotPresented; + perTrackTimestamp.mTime = timestamp.mTime; + fastTrack->mBufferProvider->onTimestamp(perTrackTimestamp); + } } - // process() is CPU-bound - mixer->process(pts); - mixBufferState = MIXED; - } else if (mixBufferState == MIXED) { - mixBufferState = UNDEFINED; - } - bool attemptedWrite = false; - //bool didFullWrite = false; // dumpsys could display a count of partial writes - if ((command & FastMixerState::WRITE) && (outputSink != NULL) && (mixBuffer != NULL)) { - if (mixBufferState == UNDEFINED) { - memset(mixBuffer, 0, frameCount * FCC_2 * sizeof(short)); - mixBufferState = ZEROED; - } - if (teeSink != NULL) { - (void) teeSink->write(mixBuffer, frameCount); + int name = fastTrackNames[i]; + ALOG_ASSERT(name >= 0); + if (fastTrack->mVolumeProvider != NULL) { + uint32_t vlr = fastTrack->mVolumeProvider->getVolumeLR(); + mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME0, + (void *)(uintptr_t)(vlr & 0xFFFF)); + mixer->setParameter(name, AudioMixer::VOLUME, AudioMixer::VOLUME1, + (void *)(uintptr_t)(vlr >> 16)); } - // FIXME write() is non-blocking and lock-free for a properly implemented NBAIO sink, - // but this code should be modified to handle both non-blocking and blocking sinks - dumpState->mWriteSequence++; - ATRACE_BEGIN("write"); - ssize_t framesWritten = outputSink->write(mixBuffer, frameCount); - ATRACE_END(); - dumpState->mWriteSequence++; - if (framesWritten >= 0) { - ALOG_ASSERT((size_t) framesWritten <= frameCount); - totalNativeFramesWritten += framesWritten; - dumpState->mFramesWritten = totalNativeFramesWritten; - //if ((size_t) framesWritten == frameCount) { - // didFullWrite = true; - //} - } else { - dumpState->mWriteErrors++; + // FIXME The current implementation of framesReady() for fast tracks + // takes a tryLock, which can block + // up to 1 ms. If enough active tracks all blocked in sequence, this would result + // in the overall fast mix cycle being delayed. Should use a non-blocking FIFO. + size_t framesReady = fastTrack->mBufferProvider->framesReady(); + if (ATRACE_ENABLED()) { + // I wish we had formatted trace names + char traceName[16]; + strcpy(traceName, "fRdy"); + traceName[4] = i + (i < 10 ? '0' : 'A' - 10); + traceName[5] = '\0'; + ATRACE_INT(traceName, framesReady); } - attemptedWrite = true; - // FIXME count # of writes blocked excessively, CPU usage, etc. for dump - - timestampStatus = outputSink->getTimestamp(timestamp); - if (timestampStatus == NO_ERROR) { - uint32_t totalNativeFramesPresented = timestamp.mPosition; - if (totalNativeFramesPresented <= totalNativeFramesWritten) { - nativeFramesWrittenButNotPresented = - totalNativeFramesWritten - totalNativeFramesPresented; + FastTrackDump *ftDump = &dumpState->mTracks[i]; + FastTrackUnderruns underruns = ftDump->mUnderruns; + if (framesReady < frameCount) { + if (framesReady == 0) { + underruns.mBitFields.mEmpty++; + underruns.mBitFields.mMostRecent = UNDERRUN_EMPTY; + mixer->disable(name); } else { - // HAL reported that more frames were presented than were written - timestampStatus = INVALID_OPERATION; + // allow mixing partial buffer + underruns.mBitFields.mPartial++; + underruns.mBitFields.mMostRecent = UNDERRUN_PARTIAL; + mixer->enable(name); } + } else { + underruns.mBitFields.mFull++; + underruns.mBitFields.mMostRecent = UNDERRUN_FULL; + mixer->enable(name); } + ftDump->mUnderruns = underruns; + ftDump->mFramesReady = framesReady; } - // To be exactly periodic, compute the next sleep time based on current time. - // This code doesn't have long-term stability when the sink is non-blocking. - // FIXME To avoid drift, use the local audio clock or watch the sink's fill status. - struct timespec newTs; - int rc = clock_gettime(CLOCK_MONOTONIC, &newTs); - if (rc == 0) { - //logWriter->logTimestamp(newTs); - if (oldTsValid) { - time_t sec = newTs.tv_sec - oldTs.tv_sec; - long nsec = newTs.tv_nsec - oldTs.tv_nsec; - ALOGE_IF(sec < 0 || (sec == 0 && nsec < 0), - "clock_gettime(CLOCK_MONOTONIC) failed: was %ld.%09ld but now %ld.%09ld", - oldTs.tv_sec, oldTs.tv_nsec, newTs.tv_sec, newTs.tv_nsec); - if (nsec < 0) { - --sec; - nsec += 1000000000; - } - // To avoid an initial underrun on fast tracks after exiting standby, - // do not start pulling data from tracks and mixing until warmup is complete. - // Warmup is considered complete after the earlier of: - // MIN_WARMUP_CYCLES write() attempts and last one blocks for at least warmupNs - // MAX_WARMUP_CYCLES write() attempts. - // This is overly conservative, but to get better accuracy requires a new HAL API. - if (!isWarm && attemptedWrite) { - measuredWarmupTs.tv_sec += sec; - measuredWarmupTs.tv_nsec += nsec; - if (measuredWarmupTs.tv_nsec >= 1000000000) { - measuredWarmupTs.tv_sec++; - measuredWarmupTs.tv_nsec -= 1000000000; - } - ++warmupCycles; - if ((nsec > warmupNs && warmupCycles >= MIN_WARMUP_CYCLES) || - (warmupCycles >= MAX_WARMUP_CYCLES)) { - isWarm = true; - dumpState->mMeasuredWarmupTs = measuredWarmupTs; - dumpState->mWarmupCycles = warmupCycles; - } - } - sleepNs = -1; - if (isWarm) { - if (sec > 0 || nsec > underrunNs) { - ATRACE_NAME("underrun"); - // FIXME only log occasionally - ALOGV("underrun: time since last cycle %d.%03ld sec", - (int) sec, nsec / 1000000L); - dumpState->mUnderruns++; - ignoreNextOverrun = true; - } else if (nsec < overrunNs) { - if (ignoreNextOverrun) { - ignoreNextOverrun = false; - } else { - // FIXME only log occasionally - ALOGV("overrun: time since last cycle %d.%03ld sec", - (int) sec, nsec / 1000000L); - dumpState->mOverruns++; - } - // This forces a minimum cycle time. It: - // - compensates for an audio HAL with jitter due to sample rate conversion - // - works with a variable buffer depth audio HAL that never pulls at a - // rate < than overrunNs per buffer. - // - recovers from overrun immediately after underrun - // It doesn't work with a non-blocking audio HAL. - sleepNs = forceNs - nsec; - } else { - ignoreNextOverrun = false; - } - } -#ifdef FAST_MIXER_STATISTICS - if (isWarm) { - // advance the FIFO queue bounds - size_t i = bounds & (dumpState->mSamplingN - 1); - bounds = (bounds & 0xFFFF0000) | ((bounds + 1) & 0xFFFF); - if (full) { - bounds += 0x10000; - } else if (!(bounds & (dumpState->mSamplingN - 1))) { - full = true; - } - // compute the delta value of clock_gettime(CLOCK_MONOTONIC) - uint32_t monotonicNs = nsec; - if (sec > 0 && sec < 4) { - monotonicNs += sec * 1000000000; - } - // compute raw CPU load = delta value of clock_gettime(CLOCK_THREAD_CPUTIME_ID) - uint32_t loadNs = 0; - struct timespec newLoad; - rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &newLoad); - if (rc == 0) { - if (oldLoadValid) { - sec = newLoad.tv_sec - oldLoad.tv_sec; - nsec = newLoad.tv_nsec - oldLoad.tv_nsec; - if (nsec < 0) { - --sec; - nsec += 1000000000; - } - loadNs = nsec; - if (sec > 0 && sec < 4) { - loadNs += sec * 1000000000; - } - } else { - // first time through the loop - oldLoadValid = true; - } - oldLoad = newLoad; - } -#ifdef CPU_FREQUENCY_STATISTICS - // get the absolute value of CPU clock frequency in kHz - int cpuNum = sched_getcpu(); - uint32_t kHz = tcu.getCpukHz(cpuNum); - kHz = (kHz << 4) | (cpuNum & 0xF); -#endif - // save values in FIFO queues for dumpsys - // these stores #1, #2, #3 are not atomic with respect to each other, - // or with respect to store #4 below - dumpState->mMonotonicNs[i] = monotonicNs; - dumpState->mLoadNs[i] = loadNs; -#ifdef CPU_FREQUENCY_STATISTICS - dumpState->mCpukHz[i] = kHz; -#endif - // this store #4 is not atomic with respect to stores #1, #2, #3 above, but - // the newest open & oldest closed halves are atomic with respect to each other - dumpState->mBounds = bounds; - ATRACE_INT("cycle_ms", monotonicNs / 1000000); - ATRACE_INT("load_us", loadNs / 1000); - } -#endif + int64_t pts; + if (outputSink == NULL || (OK != outputSink->getNextWriteTimestamp(&pts))) { + pts = AudioBufferProvider::kInvalidPTS; + } + + // process() is CPU-bound + mixer->process(pts); + mixBufferState = MIXED; + } else if (mixBufferState == MIXED) { + mixBufferState = UNDEFINED; + } + //bool didFullWrite = false; // dumpsys could display a count of partial writes + if ((command & FastMixerState::WRITE) && (outputSink != NULL) && (mixBuffer != NULL)) { + if (mixBufferState == UNDEFINED) { + memset(mixBuffer, 0, frameCount * FCC_2 * sizeof(short)); + mixBufferState = ZEROED; + } + // if non-NULL, then duplicate write() to this non-blocking sink + NBAIO_Sink* teeSink; + if ((teeSink = current->mTeeSink) != NULL) { + (void) teeSink->write(mixBuffer, frameCount); + } + // FIXME write() is non-blocking and lock-free for a properly implemented NBAIO sink, + // but this code should be modified to handle both non-blocking and blocking sinks + dumpState->mWriteSequence++; + ATRACE_BEGIN("write"); + ssize_t framesWritten = outputSink->write(mixBuffer, frameCount); + ATRACE_END(); + dumpState->mWriteSequence++; + if (framesWritten >= 0) { + ALOG_ASSERT((size_t) framesWritten <= frameCount); + totalNativeFramesWritten += framesWritten; + dumpState->mFramesWritten = totalNativeFramesWritten; + //if ((size_t) framesWritten == frameCount) { + // didFullWrite = true; + //} + } else { + dumpState->mWriteErrors++; + } + attemptedWrite = true; + // FIXME count # of writes blocked excessively, CPU usage, etc. for dump + + timestampStatus = outputSink->getTimestamp(timestamp); + if (timestampStatus == NO_ERROR) { + uint32_t totalNativeFramesPresented = timestamp.mPosition; + if (totalNativeFramesPresented <= totalNativeFramesWritten) { + nativeFramesWrittenButNotPresented = + totalNativeFramesWritten - totalNativeFramesPresented; } else { - // first time through the loop - oldTsValid = true; - sleepNs = periodNs; - ignoreNextOverrun = true; + // HAL reported that more frames were presented than were written + timestampStatus = INVALID_OPERATION; } - oldTs = newTs; - } else { - // monotonic clock is broken - oldTsValid = false; - sleepNs = periodNs; } - - - } // for (;;) - - // never return 'true'; Thread::_threadLoop() locks mutex which can result in priority inversion + } } FastMixerDumpState::FastMixerDumpState( #ifdef FAST_MIXER_STATISTICS uint32_t samplingN #endif - ) : - mCommand(FastMixerState::INITIAL), mWriteSequence(0), mFramesWritten(0), - mNumTracks(0), mWriteErrors(0), mUnderruns(0), mOverruns(0), - mSampleRate(0), mFrameCount(0), /* mMeasuredWarmupTs({0, 0}), */ mWarmupCycles(0), + ) : FastThreadDumpState(), + mWriteSequence(0), mFramesWritten(0), + mNumTracks(0), mWriteErrors(0), + mSampleRate(0), mFrameCount(0), mTrackMask(0) -#ifdef FAST_MIXER_STATISTICS - , mSamplingN(0), mBounds(0) -#endif { - mMeasuredWarmupTs.tv_sec = 0; - mMeasuredWarmupTs.tv_nsec = 0; #ifdef FAST_MIXER_STATISTICS increaseSamplingN(samplingN); #endif diff --git a/services/audioflinger/FastMixer.h b/services/audioflinger/FastMixer.h index 7aeddef..981c1a7 100644 --- a/services/audioflinger/FastMixer.h +++ b/services/audioflinger/FastMixer.h @@ -18,122 +18,64 @@ #define ANDROID_AUDIO_FAST_MIXER_H #include +#if 1 // FIXME move to where used extern "C" { #include "../private/bionic_futex.h" } +#endif #include "FastThread.h" #include "StateQueue.h" #include "FastMixerState.h" +#include "FastMixerDumpState.h" namespace android { +class AudioMixer; + typedef StateQueue FastMixerStateQueue; class FastMixer : public FastThread { public: - FastMixer() : FastThread() { } - virtual ~FastMixer() { } + FastMixer(); + virtual ~FastMixer(); - FastMixerStateQueue* sq() { return &mSQ; } + FastMixerStateQueue* sq(); private: - virtual bool threadLoop(); FastMixerStateQueue mSQ; -}; // class FastMixer - -// Describes the underrun status for a single "pull" attempt -enum FastTrackUnderrunStatus { - UNDERRUN_FULL, // framesReady() is full frame count, no underrun - UNDERRUN_PARTIAL, // framesReady() is non-zero but < full frame count, partial underrun - UNDERRUN_EMPTY, // framesReady() is zero, total underrun -}; - -// Underrun counters are not reset to zero for new tracks or if track generation changes. -// This packed representation is used to keep the information atomic. -union FastTrackUnderruns { - FastTrackUnderruns() { mAtomic = 0; - COMPILE_TIME_ASSERT_FUNCTION_SCOPE(sizeof(FastTrackUnderruns) == sizeof(uint32_t)); } - FastTrackUnderruns(const FastTrackUnderruns& copyFrom) : mAtomic(copyFrom.mAtomic) { } - FastTrackUnderruns& operator=(const FastTrackUnderruns& rhs) - { if (this != &rhs) mAtomic = rhs.mAtomic; return *this; } - struct { -#define UNDERRUN_BITS 10 -#define UNDERRUN_MASK ((1 << UNDERRUN_BITS) - 1) - uint32_t mFull : UNDERRUN_BITS; // framesReady() is full frame count - uint32_t mPartial : UNDERRUN_BITS; // framesReady() is non-zero but < full frame count - uint32_t mEmpty : UNDERRUN_BITS; // framesReady() is zero - FastTrackUnderrunStatus mMostRecent : 2; // status of most recent framesReady() - } mBitFields; -private: - uint32_t mAtomic; -}; - -// Represents the dump state of a fast track -struct FastTrackDump { - FastTrackDump() : mFramesReady(0) { } - /*virtual*/ ~FastTrackDump() { } - FastTrackUnderruns mUnderruns; - size_t mFramesReady; // most recent value only; no long-term statistics kept -}; + // callouts + virtual const FastThreadState *poll(); + virtual void setLog(NBLog::Writer *logWriter); + virtual void onIdle(); + virtual void onExit(); + virtual bool isSubClassCommand(FastThreadState::Command command); + virtual void onStateChange(); + virtual void onWork(); + + // FIXME these former local variables need comments and to be renamed to have "m" prefix + static const FastMixerState initial; + FastMixerState preIdle; // copy of state before we went into idle + long slopNs; // accumulated time we've woken up too early (> 0) or too late (< 0) + int fastTrackNames[FastMixerState::kMaxFastTracks]; // handles used by mixer to identify tracks + int generations[FastMixerState::kMaxFastTracks]; // last observed mFastTracks[i].mGeneration + NBAIO_Sink *outputSink; + int outputSinkGen; + AudioMixer* mixer; + short *mixBuffer; + enum {UNDEFINED, MIXED, ZEROED} mixBufferState; + NBAIO_Format format; + unsigned sampleRate; + int fastTracksGen; + FastMixerDumpState dummyDumpState; + uint32_t totalNativeFramesWritten; // copied to dumpState->mFramesWritten + + // next 2 fields are valid only when timestampStatus == NO_ERROR + AudioTimestamp timestamp; + uint32_t nativeFramesWrittenButNotPresented; -// The FastMixerDumpState keeps a cache of FastMixer statistics that can be logged by dumpsys. -// Each individual native word-sized field is accessed atomically. But the -// overall structure is non-atomic, that is there may be an inconsistency between fields. -// No barriers or locks are used for either writing or reading. -// Only POD types are permitted, and the contents shouldn't be trusted (i.e. do range checks). -// It has a different lifetime than the FastMixer, and so it can't be a member of FastMixer. -struct FastMixerDumpState { - FastMixerDumpState( -#ifdef FAST_MIXER_STATISTICS - uint32_t samplingN = kSamplingNforLowRamDevice -#endif - ); - /*virtual*/ ~FastMixerDumpState(); - - void dump(int fd) const; // should only be called on a stable copy, not the original - - FastMixerState::Command mCommand; // current command - uint32_t mWriteSequence; // incremented before and after each write() - uint32_t mFramesWritten; // total number of frames written successfully - uint32_t mNumTracks; // total number of active fast tracks - uint32_t mWriteErrors; // total number of write() errors - uint32_t mUnderruns; // total number of underruns - uint32_t mOverruns; // total number of overruns - uint32_t mSampleRate; - size_t mFrameCount; - struct timespec mMeasuredWarmupTs; // measured warmup time - uint32_t mWarmupCycles; // number of loop cycles required to warmup - uint32_t mTrackMask; // mask of active tracks - FastTrackDump mTracks[FastMixerState::kMaxFastTracks]; - -#ifdef FAST_MIXER_STATISTICS - // Recently collected samples of per-cycle monotonic time, thread CPU time, and CPU frequency. - // kSamplingN is max size of sampling frame (statistics), and must be a power of 2 <= 0x8000. - // The sample arrays are virtually allocated based on this compile-time constant, - // but are only initialized and used based on the runtime parameter mSamplingN. - static const uint32_t kSamplingN = 0x8000; - // Compile-time constant for a "low RAM device", must be a power of 2 <= kSamplingN. - // This value was chosen such that each array uses 1 small page (4 Kbytes). - static const uint32_t kSamplingNforLowRamDevice = 0x400; - // Corresponding runtime maximum size of sample arrays, must be a power of 2 <= kSamplingN. - uint32_t mSamplingN; - // The bounds define the interval of valid samples, and are represented as follows: - // newest open (excluded) endpoint = lower 16 bits of bounds, modulo N - // oldest closed (included) endpoint = upper 16 bits of bounds, modulo N - // Number of valid samples is newest - oldest. - uint32_t mBounds; // bounds for mMonotonicNs, mThreadCpuNs, and mCpukHz - // The elements in the *Ns arrays are in units of nanoseconds <= 3999999999. - uint32_t mMonotonicNs[kSamplingN]; // delta monotonic (wall clock) time - uint32_t mLoadNs[kSamplingN]; // delta CPU load in time -#ifdef CPU_FREQUENCY_STATISTICS - uint32_t mCpukHz[kSamplingN]; // absolute CPU clock frequency in kHz, bits 0-3 are CPU# -#endif - // Increase sampling window after construction, must be a power of 2 <= kSamplingN - void increaseSamplingN(uint32_t samplingN); -#endif -}; +}; // class FastMixer } // namespace android diff --git a/services/audioflinger/FastMixerDumpState.h b/services/audioflinger/FastMixerDumpState.h new file mode 100644 index 0000000..6a1e464 --- /dev/null +++ b/services/audioflinger/FastMixerDumpState.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#ifndef ANDROID_AUDIO_FAST_MIXER_DUMP_STATE_H +#define ANDROID_AUDIO_FAST_MIXER_DUMP_STATE_H + +#include "Configuration.h" + +namespace android { + +// Describes the underrun status for a single "pull" attempt +enum FastTrackUnderrunStatus { + UNDERRUN_FULL, // framesReady() is full frame count, no underrun + UNDERRUN_PARTIAL, // framesReady() is non-zero but < full frame count, partial underrun + UNDERRUN_EMPTY, // framesReady() is zero, total underrun +}; + +// Underrun counters are not reset to zero for new tracks or if track generation changes. +// This packed representation is used to keep the information atomic. +union FastTrackUnderruns { + FastTrackUnderruns() { mAtomic = 0; + COMPILE_TIME_ASSERT_FUNCTION_SCOPE(sizeof(FastTrackUnderruns) == sizeof(uint32_t)); } + FastTrackUnderruns(const FastTrackUnderruns& copyFrom) : mAtomic(copyFrom.mAtomic) { } + FastTrackUnderruns& operator=(const FastTrackUnderruns& rhs) + { if (this != &rhs) mAtomic = rhs.mAtomic; return *this; } + struct { +#define UNDERRUN_BITS 10 +#define UNDERRUN_MASK ((1 << UNDERRUN_BITS) - 1) + uint32_t mFull : UNDERRUN_BITS; // framesReady() is full frame count + uint32_t mPartial : UNDERRUN_BITS; // framesReady() is non-zero but < full frame count + uint32_t mEmpty : UNDERRUN_BITS; // framesReady() is zero + FastTrackUnderrunStatus mMostRecent : 2; // status of most recent framesReady() + } mBitFields; +private: + uint32_t mAtomic; +}; + +// Represents the dump state of a fast track +struct FastTrackDump { + FastTrackDump() : mFramesReady(0) { } + /*virtual*/ ~FastTrackDump() { } + FastTrackUnderruns mUnderruns; + size_t mFramesReady; // most recent value only; no long-term statistics kept +}; + +// The FastMixerDumpState keeps a cache of FastMixer statistics that can be logged by dumpsys. +// Each individual native word-sized field is accessed atomically. But the +// overall structure is non-atomic, that is there may be an inconsistency between fields. +// No barriers or locks are used for either writing or reading. +// Only POD types are permitted, and the contents shouldn't be trusted (i.e. do range checks). +// It has a different lifetime than the FastMixer, and so it can't be a member of FastMixer. +struct FastMixerDumpState : FastThreadDumpState { + FastMixerDumpState( +#ifdef FAST_MIXER_STATISTICS + uint32_t samplingN = kSamplingNforLowRamDevice +#endif + ); + /*virtual*/ ~FastMixerDumpState(); + + void dump(int fd) const; // should only be called on a stable copy, not the original + + uint32_t mWriteSequence; // incremented before and after each write() + uint32_t mFramesWritten; // total number of frames written successfully + uint32_t mNumTracks; // total number of active fast tracks + uint32_t mWriteErrors; // total number of write() errors + uint32_t mSampleRate; + size_t mFrameCount; + uint32_t mTrackMask; // mask of active tracks + FastTrackDump mTracks[FastMixerState::kMaxFastTracks]; + +#ifdef FAST_MIXER_STATISTICS + // Compile-time constant for a "low RAM device", must be a power of 2 <= kSamplingN. + // This value was chosen such that each array uses 1 small page (4 Kbytes). + static const uint32_t kSamplingNforLowRamDevice = 0x400; + // Increase sampling window after construction, must be a power of 2 <= kSamplingN + void increaseSamplingN(uint32_t samplingN); +#endif +}; + +} // android + +#endif // ANDROID_AUDIO_FAST_MIXER_DUMP_STATE_H diff --git a/services/audioflinger/FastMixerState.cpp b/services/audioflinger/FastMixerState.cpp index 4631274..8e6d0d4 100644 --- a/services/audioflinger/FastMixerState.cpp +++ b/services/audioflinger/FastMixerState.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include "Configuration.h" #include "FastMixerState.h" namespace android { @@ -30,9 +29,9 @@ FastTrack::~FastTrack() } FastMixerState::FastMixerState() : FastThreadState(), + // mFastTracks mFastTracksGen(0), mTrackMask(0), mOutputSink(NULL), mOutputSinkGen(0), - mFrameCount(0), - mDumpState(NULL), mTeeSink(NULL) + mFrameCount(0), mTeeSink(NULL) { } diff --git a/services/audioflinger/FastMixerState.h b/services/audioflinger/FastMixerState.h index 10696e8..be1a376 100644 --- a/services/audioflinger/FastMixerState.h +++ b/services/audioflinger/FastMixerState.h @@ -71,7 +71,6 @@ struct FastMixerState : FastThreadState { MIX_WRITE = 0x18; // mix tracks and write to output sink // This might be a one-time configuration rather than per-state - FastMixerDumpState* mDumpState; // if non-NULL, then update dump state periodically NBAIO_Sink* mTeeSink; // if non-NULL, then duplicate write()s to this non-blocking sink }; // struct FastMixerState diff --git a/services/audioflinger/FastThread.cpp b/services/audioflinger/FastThread.cpp new file mode 100644 index 0000000..8a216b3 --- /dev/null +++ b/services/audioflinger/FastThread.cpp @@ -0,0 +1,348 @@ +/* + * 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 "FastThread" +//#define LOG_NDEBUG 0 + +#define ATRACE_TAG ATRACE_TAG_AUDIO + +#include "Configuration.h" +#include +extern "C" { +#include "../private/bionic_futex.h" +} +#include +#include "FastThread.h" + +#define FAST_DEFAULT_NS 999999999L // ~1 sec: default time to sleep +#define FAST_HOT_IDLE_NS 1000000L // 1 ms: time to sleep while hot idling +#define MIN_WARMUP_CYCLES 2 // minimum number of loop cycles to wait for warmup +#define MAX_WARMUP_CYCLES 10 // maximum number of loop cycles to wait for warmup + +namespace android { + +FastThread::FastThread() : Thread(false /*canCallJava*/), + // re-initialized to &initial by subclass constructor + previous(NULL), current(NULL), + /* oldTs({0, 0}), */ + oldTsValid(false), + sleepNs(-1), + periodNs(0), + underrunNs(0), + overrunNs(0), + forceNs(0), + warmupNs(0), + // re-initialized to &dummyDumpState by subclass constructor + mDummyDumpState(NULL), + dumpState(NULL), + ignoreNextOverrun(true), +#ifdef FAST_MIXER_STATISTICS + // oldLoad + oldLoadValid(false), + bounds(0), + full(false), + // tcu +#endif + coldGen(0), + isWarm(false), + /* measuredWarmupTs({0, 0}), */ + warmupCycles(0), + // dummyLogWriter + logWriter(&dummyLogWriter), + timestampStatus(INVALID_OPERATION), + + command(FastThreadState::INITIAL), +#if 0 + frameCount(0), +#endif + attemptedWrite(false) +{ + oldTs.tv_sec = 0; + oldTs.tv_nsec = 0; + measuredWarmupTs.tv_sec = 0; + measuredWarmupTs.tv_nsec = 0; +} + +FastThread::~FastThread() +{ +} + +bool FastThread::threadLoop() +{ + for (;;) { + + // either nanosleep, sched_yield, or busy wait + if (sleepNs >= 0) { + if (sleepNs > 0) { + ALOG_ASSERT(sleepNs < 1000000000); + const struct timespec req = {0, sleepNs}; + nanosleep(&req, NULL); + } else { + sched_yield(); + } + } + // default to long sleep for next cycle + sleepNs = FAST_DEFAULT_NS; + + // poll for state change + const FastThreadState *next = poll(); + if (next == NULL) { + // continue to use the default initial state until a real state is available + // FIXME &initial not available, should save address earlier + //ALOG_ASSERT(current == &initial && previous == &initial); + next = current; + } + + command = next->mCommand; + if (next != current) { + + // As soon as possible of learning of a new dump area, start using it + dumpState = next->mDumpState != NULL ? next->mDumpState : mDummyDumpState; + logWriter = next->mNBLogWriter != NULL ? next->mNBLogWriter : &dummyLogWriter; + setLog(logWriter); + + // We want to always have a valid reference to the previous (non-idle) state. + // However, the state queue only guarantees access to current and previous states. + // So when there is a transition from a non-idle state into an idle state, we make a + // copy of the last known non-idle state so it is still available on return from idle. + // The possible transitions are: + // non-idle -> non-idle update previous from current in-place + // non-idle -> idle update previous from copy of current + // idle -> idle don't update previous + // idle -> non-idle don't update previous + if (!(current->mCommand & FastThreadState::IDLE)) { + if (command & FastThreadState::IDLE) { + onIdle(); + oldTsValid = false; +#ifdef FAST_MIXER_STATISTICS + oldLoadValid = false; +#endif + ignoreNextOverrun = true; + } + previous = current; + } + current = next; + } +#if !LOG_NDEBUG + next = NULL; // not referenced again +#endif + + dumpState->mCommand = command; + + // << current, previous, command, dumpState >> + + switch (command) { + case FastThreadState::INITIAL: + case FastThreadState::HOT_IDLE: + sleepNs = FAST_HOT_IDLE_NS; + continue; + case FastThreadState::COLD_IDLE: + // only perform a cold idle command once + // FIXME consider checking previous state and only perform if previous != COLD_IDLE + if (current->mColdGen != coldGen) { + int32_t *coldFutexAddr = current->mColdFutexAddr; + ALOG_ASSERT(coldFutexAddr != NULL); + int32_t old = android_atomic_dec(coldFutexAddr); + if (old <= 0) { + __futex_syscall4(coldFutexAddr, FUTEX_WAIT_PRIVATE, old - 1, NULL); + } + int policy = sched_getscheduler(0); + if (!(policy == SCHED_FIFO || policy == SCHED_RR)) { + ALOGE("did not receive expected priority boost"); + } + // This may be overly conservative; there could be times that the normal mixer + // requests such a brief cold idle that it doesn't require resetting this flag. + isWarm = false; + measuredWarmupTs.tv_sec = 0; + measuredWarmupTs.tv_nsec = 0; + warmupCycles = 0; + sleepNs = -1; + coldGen = current->mColdGen; +#ifdef FAST_MIXER_STATISTICS + bounds = 0; + full = false; +#endif + oldTsValid = !clock_gettime(CLOCK_MONOTONIC, &oldTs); + timestampStatus = INVALID_OPERATION; + } else { + sleepNs = FAST_HOT_IDLE_NS; + } + continue; + case FastThreadState::EXIT: + onExit(); + return false; + default: + LOG_ALWAYS_FATAL_IF(!isSubClassCommand(command)); + break; + } + + // there is a non-idle state available to us; did the state change? + if (current != previous) { + onStateChange(); +#if 1 // FIXME shouldn't need this + // only process state change once + previous = current; +#endif + } + + // do work using current state here + attemptedWrite = false; + onWork(); + + // To be exactly periodic, compute the next sleep time based on current time. + // This code doesn't have long-term stability when the sink is non-blocking. + // FIXME To avoid drift, use the local audio clock or watch the sink's fill status. + struct timespec newTs; + int rc = clock_gettime(CLOCK_MONOTONIC, &newTs); + if (rc == 0) { + //logWriter->logTimestamp(newTs); + if (oldTsValid) { + time_t sec = newTs.tv_sec - oldTs.tv_sec; + long nsec = newTs.tv_nsec - oldTs.tv_nsec; + ALOGE_IF(sec < 0 || (sec == 0 && nsec < 0), + "clock_gettime(CLOCK_MONOTONIC) failed: was %ld.%09ld but now %ld.%09ld", + oldTs.tv_sec, oldTs.tv_nsec, newTs.tv_sec, newTs.tv_nsec); + if (nsec < 0) { + --sec; + nsec += 1000000000; + } + // To avoid an initial underrun on fast tracks after exiting standby, + // do not start pulling data from tracks and mixing until warmup is complete. + // Warmup is considered complete after the earlier of: + // MIN_WARMUP_CYCLES write() attempts and last one blocks for at least warmupNs + // MAX_WARMUP_CYCLES write() attempts. + // This is overly conservative, but to get better accuracy requires a new HAL API. + if (!isWarm && attemptedWrite) { + measuredWarmupTs.tv_sec += sec; + measuredWarmupTs.tv_nsec += nsec; + if (measuredWarmupTs.tv_nsec >= 1000000000) { + measuredWarmupTs.tv_sec++; + measuredWarmupTs.tv_nsec -= 1000000000; + } + ++warmupCycles; + if ((nsec > warmupNs && warmupCycles >= MIN_WARMUP_CYCLES) || + (warmupCycles >= MAX_WARMUP_CYCLES)) { + isWarm = true; + dumpState->mMeasuredWarmupTs = measuredWarmupTs; + dumpState->mWarmupCycles = warmupCycles; + } + } + sleepNs = -1; + if (isWarm) { + if (sec > 0 || nsec > underrunNs) { + ATRACE_NAME("underrun"); + // FIXME only log occasionally + ALOGV("underrun: time since last cycle %d.%03ld sec", + (int) sec, nsec / 1000000L); + dumpState->mUnderruns++; + ignoreNextOverrun = true; + } else if (nsec < overrunNs) { + if (ignoreNextOverrun) { + ignoreNextOverrun = false; + } else { + // FIXME only log occasionally + ALOGV("overrun: time since last cycle %d.%03ld sec", + (int) sec, nsec / 1000000L); + dumpState->mOverruns++; + } + // This forces a minimum cycle time. It: + // - compensates for an audio HAL with jitter due to sample rate conversion + // - works with a variable buffer depth audio HAL that never pulls at a + // rate < than overrunNs per buffer. + // - recovers from overrun immediately after underrun + // It doesn't work with a non-blocking audio HAL. + sleepNs = forceNs - nsec; + } else { + ignoreNextOverrun = false; + } + } +#ifdef FAST_MIXER_STATISTICS + if (isWarm) { + // advance the FIFO queue bounds + size_t i = bounds & (dumpState->mSamplingN - 1); + bounds = (bounds & 0xFFFF0000) | ((bounds + 1) & 0xFFFF); + if (full) { + bounds += 0x10000; + } else if (!(bounds & (dumpState->mSamplingN - 1))) { + full = true; + } + // compute the delta value of clock_gettime(CLOCK_MONOTONIC) + uint32_t monotonicNs = nsec; + if (sec > 0 && sec < 4) { + monotonicNs += sec * 1000000000; + } + // compute raw CPU load = delta value of clock_gettime(CLOCK_THREAD_CPUTIME_ID) + uint32_t loadNs = 0; + struct timespec newLoad; + rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &newLoad); + if (rc == 0) { + if (oldLoadValid) { + sec = newLoad.tv_sec - oldLoad.tv_sec; + nsec = newLoad.tv_nsec - oldLoad.tv_nsec; + if (nsec < 0) { + --sec; + nsec += 1000000000; + } + loadNs = nsec; + if (sec > 0 && sec < 4) { + loadNs += sec * 1000000000; + } + } else { + // first time through the loop + oldLoadValid = true; + } + oldLoad = newLoad; + } +#ifdef CPU_FREQUENCY_STATISTICS + // get the absolute value of CPU clock frequency in kHz + int cpuNum = sched_getcpu(); + uint32_t kHz = tcu.getCpukHz(cpuNum); + kHz = (kHz << 4) | (cpuNum & 0xF); +#endif + // save values in FIFO queues for dumpsys + // these stores #1, #2, #3 are not atomic with respect to each other, + // or with respect to store #4 below + dumpState->mMonotonicNs[i] = monotonicNs; + dumpState->mLoadNs[i] = loadNs; +#ifdef CPU_FREQUENCY_STATISTICS + dumpState->mCpukHz[i] = kHz; +#endif + // this store #4 is not atomic with respect to stores #1, #2, #3 above, but + // the newest open & oldest closed halves are atomic with respect to each other + dumpState->mBounds = bounds; + ATRACE_INT("cycle_ms", monotonicNs / 1000000); + ATRACE_INT("load_us", loadNs / 1000); + } +#endif + } else { + // first time through the loop + oldTsValid = true; + sleepNs = periodNs; + ignoreNextOverrun = true; + } + oldTs = newTs; + } else { + // monotonic clock is broken + oldTsValid = false; + sleepNs = periodNs; + } + + } // for (;;) + + // never return 'true'; Thread::_threadLoop() locks mutex which can result in priority inversion +} + +} // namespace android diff --git a/services/audioflinger/FastThread.h b/services/audioflinger/FastThread.h index 6caf7bd..1330334 100644 --- a/services/audioflinger/FastThread.h +++ b/services/audioflinger/FastThread.h @@ -17,7 +17,12 @@ #ifndef ANDROID_AUDIO_FAST_THREAD_H #define ANDROID_AUDIO_FAST_THREAD_H +#include "Configuration.h" +#ifdef CPU_FREQUENCY_STATISTICS +#include +#endif #include +#include "FastThreadState.h" namespace android { @@ -25,11 +30,60 @@ namespace android { class FastThread : public Thread { public: - FastThread() : Thread(false /*canCallJava*/) { } - virtual ~FastThread() { } + FastThread(); + virtual ~FastThread(); + +private: + // implement Thread::threadLoop() + virtual bool threadLoop(); protected: - virtual bool threadLoop() = 0; + // callouts to subclass in same lexical order as they were in original FastMixer.cpp + // FIXME need comments + virtual const FastThreadState *poll() = 0; + virtual void setLog(NBLog::Writer *logWriter __unused) { } + virtual void onIdle() = 0; + virtual void onExit() = 0; + virtual bool isSubClassCommand(FastThreadState::Command command) = 0; + virtual void onStateChange() = 0; + virtual void onWork() = 0; + + // FIXME these former local variables need comments and to be renamed to have an "m" prefix + const FastThreadState *previous; + const FastThreadState *current; + struct timespec oldTs; + bool oldTsValid; + long sleepNs; // -1: busy wait, 0: sched_yield, > 0: nanosleep + long periodNs; // expected period; the time required to render one mix buffer + long underrunNs; // underrun likely when write cycle is greater than this value + long overrunNs; // overrun likely when write cycle is less than this value + long forceNs; // if overrun detected, force the write cycle to take this much time + long warmupNs; // warmup complete when write cycle is greater than to this value + FastThreadDumpState *mDummyDumpState; + FastThreadDumpState *dumpState; + bool ignoreNextOverrun; // used to ignore initial overrun and first after an underrun +#ifdef FAST_MIXER_STATISTICS + struct timespec oldLoad; // previous value of clock_gettime(CLOCK_THREAD_CPUTIME_ID) + bool oldLoadValid; // whether oldLoad is valid + uint32_t bounds; + bool full; // whether we have collected at least mSamplingN samples +#ifdef CPU_FREQUENCY_STATISTICS + ThreadCpuUsage tcu; // for reading the current CPU clock frequency in kHz +#endif +#endif + unsigned coldGen; // last observed mColdGen + bool isWarm; // true means ready to mix, false means wait for warmup before mixing + struct timespec measuredWarmupTs; // how long did it take for warmup to complete + uint32_t warmupCycles; // counter of number of loop cycles required to warmup + NBLog::Writer dummyLogWriter; + NBLog::Writer *logWriter; + status_t timestampStatus; + + FastThreadState::Command command; +#if 0 + size_t frameCount; +#endif + bool attemptedWrite; }; // class FastThread diff --git a/services/audioflinger/FastThreadState.cpp b/services/audioflinger/FastThreadState.cpp index 427ada5..d4d6255 100644 --- a/services/audioflinger/FastThreadState.cpp +++ b/services/audioflinger/FastThreadState.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ +#include "Configuration.h" #include "FastThreadState.h" namespace android { FastThreadState::FastThreadState() : - mCommand(INITIAL), mColdFutexAddr(NULL), mColdGen(0), mNBLogWriter(NULL) + mCommand(INITIAL), mColdFutexAddr(NULL), mColdGen(0), mDumpState(NULL), mNBLogWriter(NULL) + { } @@ -27,4 +29,21 @@ FastThreadState::~FastThreadState() { } + +FastThreadDumpState::FastThreadDumpState() : + mCommand(FastThreadState::INITIAL), mUnderruns(0), mOverruns(0), + /* mMeasuredWarmupTs({0, 0}), */ + mWarmupCycles(0) +#ifdef FAST_MIXER_STATISTICS + , mSamplingN(0), mBounds(0) +#endif +{ + mMeasuredWarmupTs.tv_sec = 0; + mMeasuredWarmupTs.tv_nsec = 0; +} + +FastThreadDumpState::~FastThreadDumpState() +{ +} + } // namespace android diff --git a/services/audioflinger/FastThreadState.h b/services/audioflinger/FastThreadState.h index 148fb7b..1ab8a0a 100644 --- a/services/audioflinger/FastThreadState.h +++ b/services/audioflinger/FastThreadState.h @@ -17,11 +17,14 @@ #ifndef ANDROID_AUDIO_FAST_THREAD_STATE_H #define ANDROID_AUDIO_FAST_THREAD_STATE_H +#include "Configuration.h" #include #include namespace android { +struct FastThreadDumpState; + // Represents a single state of a FastThread struct FastThreadState { FastThreadState(); @@ -35,14 +38,51 @@ struct FastThreadState { IDLE = 3, // either HOT_IDLE or COLD_IDLE EXIT = 4; // exit from thread // additional values defined per subclass - Command mCommand; - + Command mCommand; // current command int32_t* mColdFutexAddr; // for COLD_IDLE only, pointer to the associated futex unsigned mColdGen; // increment when COLD_IDLE is requested so it's only performed once + // This might be a one-time configuration rather than per-state + FastThreadDumpState* mDumpState; // if non-NULL, then update dump state periodically NBLog::Writer* mNBLogWriter; // non-blocking logger + }; // struct FastThreadState + +// FIXME extract common part of comment at FastMixerDumpState +struct FastThreadDumpState { + FastThreadDumpState(); + /*virtual*/ ~FastThreadDumpState(); + + FastThreadState::Command mCommand; // current command + uint32_t mUnderruns; // total number of underruns + uint32_t mOverruns; // total number of overruns + struct timespec mMeasuredWarmupTs; // measured warmup time + uint32_t mWarmupCycles; // number of loop cycles required to warmup + +#ifdef FAST_MIXER_STATISTICS + // Recently collected samples of per-cycle monotonic time, thread CPU time, and CPU frequency. + // kSamplingN is max size of sampling frame (statistics), and must be a power of 2 <= 0x8000. + // The sample arrays are virtually allocated based on this compile-time constant, + // but are only initialized and used based on the runtime parameter mSamplingN. + static const uint32_t kSamplingN = 0x8000; + // Corresponding runtime maximum size of sample arrays, must be a power of 2 <= kSamplingN. + uint32_t mSamplingN; + // The bounds define the interval of valid samples, and are represented as follows: + // newest open (excluded) endpoint = lower 16 bits of bounds, modulo N + // oldest closed (included) endpoint = upper 16 bits of bounds, modulo N + // Number of valid samples is newest - oldest. + uint32_t mBounds; // bounds for mMonotonicNs, mThreadCpuNs, and mCpukHz + // The elements in the *Ns arrays are in units of nanoseconds <= 3999999999. + uint32_t mMonotonicNs[kSamplingN]; // delta monotonic (wall clock) time + uint32_t mLoadNs[kSamplingN]; // delta CPU load in time +#ifdef CPU_FREQUENCY_STATISTICS + uint32_t mCpukHz[kSamplingN]; // absolute CPU clock frequency in kHz, bits 0-3 are CPU# +#endif +#endif + +}; // struct FastThreadDumpState + } // android #endif // ANDROID_AUDIO_FAST_THREAD_STATE_H -- cgit v1.1