summaryrefslogtreecommitdiffstats
path: root/services/audioflinger/FastMixer.cpp
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/FastMixer.cpp
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/FastMixer.cpp')
-rw-r--r--services/audioflinger/FastMixer.cpp888
1 files changed, 327 insertions, 561 deletions
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