summaryrefslogtreecommitdiffstats
path: root/services/audioflinger
diff options
context:
space:
mode:
authorGlenn Kasten <gkasten@google.com>2014-04-07 12:04:41 -0700
committerGlenn Kasten <gkasten@google.com>2014-04-28 12:26:11 -0700
commit2234002b0710c8db73f82d397cb945cd541c6bbb (patch)
treebe662712131225845bb0afd7d32e7c37a7f295ad /services/audioflinger
parent67ee990d324229ab0565ce632bd5a04297e16a01 (diff)
downloadframeworks_av-2234002b0710c8db73f82d397cb945cd541c6bbb.zip
frameworks_av-2234002b0710c8db73f82d397cb945cd541c6bbb.tar.gz
frameworks_av-2234002b0710c8db73f82d397cb945cd541c6bbb.tar.bz2
Start pulling bits of FastMixer up to FastThread
Change-Id: I4c6f7b8f88fcf107bb29ee6432feecd4ab6554d2
Diffstat (limited to 'services/audioflinger')
-rw-r--r--services/audioflinger/Android.mk3
-rw-r--r--services/audioflinger/Configuration.h1
-rw-r--r--services/audioflinger/FastMixer.cpp888
-rw-r--r--services/audioflinger/FastMixer.h134
-rw-r--r--services/audioflinger/FastMixerDumpState.h95
-rw-r--r--services/audioflinger/FastMixerState.cpp5
-rw-r--r--services/audioflinger/FastMixerState.h1
-rw-r--r--services/audioflinger/FastThread.cpp348
-rw-r--r--services/audioflinger/FastThread.h60
-rw-r--r--services/audioflinger/FastThreadState.cpp21
-rw-r--r--services/audioflinger/FastThreadState.h44
11 files changed, 932 insertions, 668 deletions
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 = &current->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 = &current->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 = &current->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 = &current->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 = &current->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 = &current->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 = &current->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 = &current->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 <utils/Debug.h>
+#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<FastMixerState> 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 <utils/Log.h>
+extern "C" {
+#include "../private/bionic_futex.h"
+}
+#include <utils/Trace.h>
+#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 <cpustats/ThreadCpuUsage.h>
+#endif
#include <utils/Thread.h>
+#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 <stdint.h>
#include <media/nbaio/NBLog.h>
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