Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4799fa44 authored by Glenn Kasten's avatar Glenn Kasten Committed by Android (Google) Code Review
Browse files

Merge "Start pulling bits of FastMixer up to FastThread"

parents c12ea1ce 2234002b
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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"'

+1 −0
Original line number Diff line number Diff line
@@ -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
+327 −561
Original line number Diff line number Diff line
@@ -40,184 +40,96 @@
#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();
}

FastMixer::~FastMixer()
{
}
        // 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;
FastMixerStateQueue* FastMixer::sq()
{
    return &mSQ;
}

        FastMixerState::Command command = next->mCommand;
        if (next != current) {
const FastThreadState *FastMixer::poll()
{
    return mSQ.poll();
}

            // 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;
void FastMixer::setLog(NBLog::Writer *logWriter)
{
    if (mixer != NULL) {
        mixer->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 & FastMixerState::IDLE)) {
                if (command & FastMixerState::IDLE) {
                    preIdle = *current;
void FastMixer::onIdle()
{
    preIdle = *(const FastMixerState *)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

        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:
void FastMixer::onExit()
{
    delete mixer;
    delete[] mixBuffer;
            return false;
}

bool FastMixer::isSubClassCommand(FastThreadState::Command command)
{
    switch ((FastMixerState::Command) command) {
    case FastMixerState::MIX:
    case FastMixerState::WRITE:
    case FastMixerState::MIX_WRITE:
            break;
        return true;
    default:
            LOG_ALWAYS_FATAL("bad command %d", command);
        return false;
    }
}

        // there is a non-idle state available to us; did the state change?
        size_t frameCount = current->mFrameCount;
        if (current != previous) {
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
@@ -265,7 +177,7 @@ bool FastMixer::threadLoop()
        }
        mixBufferState = UNDEFINED;
#if !LOG_NDEBUG
                for (i = 0; i < FastMixerState::kMaxFastTracks; ++i) {
        for (unsigned i = 0; i < FastMixerState::kMaxFastTracks; ++i) {
            fastTrackNames[i] = -1;
        }
#endif
@@ -278,7 +190,7 @@ bool FastMixer::threadLoop()
    }

    // check for change in active track set
            unsigned currentTrackMask = current->mTrackMask;
    const unsigned currentTrackMask = current->mTrackMask;
    dumpState->mTrackMask = currentTrackMask;
    if (current->mFastTracksGen != fastTracksGen) {
        ALOG_ASSERT(mixBuffer != NULL);
@@ -287,7 +199,7 @@ bool FastMixer::threadLoop()
        // process removed tracks first to avoid running out of track names
        unsigned removedTracks = previousTrackMask & ~currentTrackMask;
        while (removedTracks != 0) {
                    i = __builtin_ctz(removedTracks);
            int i = __builtin_ctz(removedTracks);
            removedTracks &= ~(1 << i);
            const FastTrack* fastTrack = &current->mFastTracks[i];
            ALOG_ASSERT(fastTrack->mBufferProvider == NULL);
@@ -306,7 +218,7 @@ bool FastMixer::threadLoop()
        // now process added tracks
        unsigned addedTracks = currentTrackMask & ~previousTrackMask;
        while (addedTracks != 0) {
                    i = __builtin_ctz(addedTracks);
            int i = __builtin_ctz(addedTracks);
            addedTracks &= ~(1 << i);
            const FastTrack* fastTrack = &current->mFastTracks[i];
            AudioBufferProvider *bufferProvider = fastTrack->mBufferProvider;
@@ -332,7 +244,7 @@ bool FastMixer::threadLoop()
        // but may have a different buffer provider or volume provider
        unsigned modifiedTracks = currentTrackMask & previousTrackMask;
        while (modifiedTracks != 0) {
                    i = __builtin_ctz(modifiedTracks);
            int i = __builtin_ctz(modifiedTracks);
            modifiedTracks &= ~(1 << i);
            const FastTrack* fastTrack = &current->mFastTracks[i];
            if (fastTrack->mGeneration != generations[i]) {
@@ -363,20 +275,21 @@ bool FastMixer::threadLoop()

        dumpState->mNumTracks = popcount(currentTrackMask);
    }

#if 1   // FIXME shouldn't need this
            // only process state change once
            previous = current;
#endif
}

        // do work using current state here
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) {
                i = __builtin_ctz(currentTrackMask);
            int i = __builtin_ctz(currentTrackMask);
            currentTrackMask &= ~(1 << i);
            const FastTrack* fastTrack = &current->mFastTracks[i];

@@ -451,14 +364,15 @@ bool FastMixer::threadLoop()
    } 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) {
        // 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,
@@ -493,166 +407,18 @@ bool FastMixer::threadLoop()
            }
        }
    }

        // 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
}

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
Loading