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

Commit c15d6657 authored by Glenn Kasten's avatar Glenn Kasten
Browse files

Add audio watchdog thread

Change-Id: I4ed62087bd6554179abb8258d2da606050e762c0
parent b7acdfb8
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -95,4 +95,8 @@ LOCAL_CFLAGS += -DHAVE_REQUEST_PRIORITY -UFAST_TRACKS_AT_NON_NATIVE_SAMPLE_RATE
# 47.5 seconds at 44.1 kHz, 8 megabytes
# LOCAL_CFLAGS += -DTEE_SINK_FRAMES=0x200000

# uncomment to enable the audio watchdog
LOCAL_SRC_FILES += AudioWatchdog.cpp
LOCAL_CFLAGS += -DAUDIO_WATCHDOG

include $(BUILD_SHARED_LIBRARY)
+35 −0
Original line number Diff line number Diff line
@@ -2300,6 +2300,19 @@ AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, Aud
        }
#endif

#ifdef AUDIO_WATCHDOG
        // create and start the watchdog
        mAudioWatchdog = new AudioWatchdog();
        mAudioWatchdog->setDump(&mAudioWatchdogDump);
        mAudioWatchdog->run("AudioWatchdog", PRIORITY_URGENT_AUDIO);
        tid = mAudioWatchdog->getTid();
        err = requestPriority(getpid_cached, tid, 1);
        if (err != 0) {
            ALOGW("Policy SCHED_FIFO priority %d is unavailable for pid %d tid %d; error %d",
                    1, getpid_cached, tid, err);
        }
#endif

    } else {
        mFastMixer = NULL;
    }
@@ -2349,6 +2362,11 @@ AudioFlinger::MixerThread::~MixerThread()
        }
        delete mSoaker;
#endif
        if (mAudioWatchdog != 0) {
            mAudioWatchdog->requestExit();
            mAudioWatchdog->requestExitAndWait();
            mAudioWatchdog.clear();
        }
    }
    delete mAudioMixer;
}
@@ -2670,6 +2688,9 @@ void AudioFlinger::MixerThread::threadLoop_write()
                if (old == -1) {
                    __futex_syscall3(&mFastMixerFutex, FUTEX_WAKE_PRIVATE, 1);
                }
                if (mAudioWatchdog != 0) {
                    mAudioWatchdog->resume();
                }
            }
            state->mCommand = FastMixerState::MIX_WRITE;
            sq->end();
@@ -2746,6 +2767,9 @@ void AudioFlinger::MixerThread::threadLoop_standby()
            if (kUseFastMixer == FastMixer_Dynamic) {
                mNormalSink = mOutputSink;
            }
            if (mAudioWatchdog != 0) {
                mAudioWatchdog->pause();
            }
        } else {
            sq->end(false /*didModify*/);
        }
@@ -3234,6 +3258,7 @@ track_is_ready: ;
    }

    // Push the new FastMixer state if necessary
    bool pauseAudioWatchdog = false;
    if (didModify) {
        state->mFastTracksGen++;
        // if the fast mixer was active, but now there are no fast tracks, then put it in cold idle
@@ -3249,6 +3274,7 @@ track_is_ready: ;
            // If we go into cold idle, need to wait for acknowledgement
            // so that fast mixer stops doing I/O.
            block = FastMixerStateQueue::BLOCK_UNTIL_ACKED;
            pauseAudioWatchdog = true;
        }
        sq->end();
    }
@@ -3256,6 +3282,9 @@ track_is_ready: ;
        sq->end(didModify);
        sq->push(block);
    }
    if (pauseAudioWatchdog && mAudioWatchdog != 0) {
        mAudioWatchdog->pause();
    }

    // Now perform the deferred reset on fast tracks that have stopped
    while (resetMask != 0) {
@@ -3576,6 +3605,12 @@ status_t AudioFlinger::MixerThread::dumpInternals(int fd, const Vector<String16>
        }
    }

    if (mAudioWatchdog != 0) {
        // Make a non-atomic copy of audio watchdog dump so it won't change underneath us
        AudioWatchdogDump wdCopy = mAudioWatchdogDump;
        wdCopy.dump(fd);
    }

    return NO_ERROR;
}

+3 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@
#include "ExtendedAudioBufferProvider.h"
#include "FastMixer.h"
#include "NBAIO.h"
#include "AudioWatchdog.h"

#include <powermanager/IPowerManager.h>

@@ -1169,6 +1170,7 @@ public:
#endif
                    // one-time initialization, no locks required
                    FastMixer*  mFastMixer;         // non-NULL if there is also a fast mixer
                    sp<AudioWatchdog> mAudioWatchdog; // non-0 if there is an audio watchdog thread

                    // contents are not guaranteed to be consistent, no locks required
                    FastMixerDumpState mFastMixerDumpState;
@@ -1176,6 +1178,7 @@ public:
                    StateQueueObserverDump mStateQueueObserverDump;
                    StateQueueMutatorDump  mStateQueueMutatorDump;
#endif
                    AudioWatchdogDump mAudioWatchdogDump;

                    // accessible only within the threadLoop(), no locks required
                    //          mFastMixer->sq()    // for mutating and pushing state
+134 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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 "AudioWatchdog"
//#define LOG_NDEBUG 0

#include <utils/Log.h>
#include "AudioWatchdog.h"

namespace android {

void AudioWatchdogDump::dump(int fd)
{
    char buf[32];
    if (mMostRecent != 0) {
        // includes NUL terminator
        ctime_r(&mMostRecent, buf);
    } else {
        strcpy(buf, "N/A\n");
    }
    fdprintf(fd, "Watchdog: underruns=%u, logs=%u, most recent underrun log at %s",
            mUnderruns, mLogs, buf);
}

bool AudioWatchdog::threadLoop()
{
    {
        AutoMutex _l(mMyLock);
        if (mPaused) {
            mMyCond.wait(mMyLock);
            // ignore previous timestamp after resume()
            mOldTsValid = false;
            // force an immediate log on first underrun after resume()
            mLogTs.tv_sec = MIN_TIME_BETWEEN_LOGS_SEC;
            mLogTs.tv_nsec = 0;
            // caller will check for exitPending()
            return true;
        }
    }
    struct timespec newTs;
    int rc = clock_gettime(CLOCK_MONOTONIC, &newTs);
    if (rc != 0) {
        pause();
        return false;
    }
    if (!mOldTsValid) {
        mOldTs = newTs;
        mOldTsValid = true;
        return true;
    }
    time_t sec = newTs.tv_sec - mOldTs.tv_sec;
    long nsec = newTs.tv_nsec - mOldTs.tv_nsec;
    if (nsec < 0) {
        --sec;
        nsec += 1000000000;
    }
    mOldTs = newTs;
    // cycleNs is same as sec*1e9 + nsec, but limited to about 4 seconds
    uint32_t cycleNs = nsec;
    if (sec > 0) {
        if (sec < 4) {
            cycleNs += sec * 1000000000;
        } else {
            cycleNs = 4000000000u;
        }
    }
    mLogTs.tv_sec += sec;
    if ((mLogTs.tv_nsec += nsec) >= 1000000000) {
        mLogTs.tv_sec++;
        mLogTs.tv_nsec -= 1000000000;
    }
    if (cycleNs > mMaxCycleNs) {
        mDump->mUnderruns = ++mUnderruns;
        if (mLogTs.tv_sec >= MIN_TIME_BETWEEN_LOGS_SEC) {
            mDump->mLogs = ++mLogs;
            mDump->mMostRecent = time(NULL);
            ALOGW("Insufficient CPU for load: expected=%.1f actual=%.1f ms; underruns=%u logs=%u",
                mPeriodNs * 1e-6, cycleNs * 1e-6, mUnderruns, mLogs);
            mLogTs.tv_sec = 0;
            mLogTs.tv_nsec = 0;
        }
    }
    struct timespec req;
    req.tv_sec = 0;
    req.tv_nsec = mPeriodNs;
    rc = nanosleep(&req, NULL);
    if (!((rc == 0) || (rc == -1 && errno == EINTR))) {
        pause();
        return false;
    }
    return true;
}

void AudioWatchdog::requestExit()
{
    // must be in this order to avoid a race condition
    Thread::requestExit();
    resume();
}

void AudioWatchdog::pause()
{
    AutoMutex _l(mMyLock);
    mPaused = true;
}

void AudioWatchdog::resume()
{
    AutoMutex _l(mMyLock);
    if (mPaused) {
        mPaused = false;
        mMyCond.signal();
    }
}

void AudioWatchdog::setDump(AudioWatchdogDump *dump)
{
    mDump = dump != NULL ? dump : &mDummyDump;
}

}   // namespace android
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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.
 */

// The watchdog thread runs periodically.  It has two functions:
//   (a) verify that adequate CPU time is available, and log
//       as soon as possible when there appears to be a CPU shortage
//   (b) monitor the other threads [not yet implemented]

#ifndef AUDIO_WATCHDOG_H
#define AUDIO_WATCHDOG_H

#include <time.h>
#include <utils/Thread.h>

namespace android {

// Keeps a cache of AudioWatchdog statistics that can be logged by dumpsys.
// The usual caveats about atomicity of information apply.
struct AudioWatchdogDump {
    AudioWatchdogDump() : mUnderruns(0), mLogs(0), mMostRecent(0) { }
    /*virtual*/ ~AudioWatchdogDump() { }
    uint32_t mUnderruns;    // total number of underruns
    uint32_t mLogs;         // total number of log messages
    time_t   mMostRecent;   // time of most recent log
    void     dump(int fd);  // should only be called on a stable copy, not the original
};

class AudioWatchdog : public Thread {

public:
    AudioWatchdog(unsigned periodMs = 50) : Thread(false /*canCallJava*/), mPaused(false),
            mPeriodNs(periodMs * 1000000), mMaxCycleNs(mPeriodNs * 2),
            // mOldTs
            // mLogTs initialized below
            mOldTsValid(false), mUnderruns(0), mLogs(0), mDump(&mDummyDump)
        {
#define MIN_TIME_BETWEEN_LOGS_SEC 60
            // force an immediate log on first underrun
            mLogTs.tv_sec = MIN_TIME_BETWEEN_LOGS_SEC;
            mLogTs.tv_nsec = 0;
        }
    virtual         ~AudioWatchdog() { }

     // Do not call Thread::requestExitAndWait() without first calling requestExit().
    // Thread::requestExitAndWait() is not virtual, and the implementation doesn't do enough.
    virtual void        requestExit();

    // FIXME merge API and implementation with AudioTrackThread
    void            pause();        // suspend thread from execution at next loop boundary
    void            resume();       // allow thread to execute, if not requested to exit

    // Where to store the dump, or NULL to not update
    void            setDump(AudioWatchdogDump* dump);

private:
    virtual bool    threadLoop();

    Mutex           mMyLock;        // Thread::mLock is private
    Condition       mMyCond;        // Thread::mThreadExitedCondition is private
    bool            mPaused;        // whether thread is currently paused

    uint32_t        mPeriodNs;      // nominal period
    uint32_t        mMaxCycleNs;    // maximum allowed time of one cycle before declaring underrun
    struct timespec mOldTs;         // monotonic time when threadLoop last ran
    struct timespec mLogTs;         // time since last log
    bool            mOldTsValid;    // whether mOldTs is valid
    uint32_t        mUnderruns;     // total number of underruns
    uint32_t        mLogs;          // total number of logs
    AudioWatchdogDump*  mDump;      // where to store the dump, always non-NULL
    AudioWatchdogDump   mDummyDump; // default area for dump in case setDump() is not called
};

}   // namespace android

#endif  // AUDIO_WATCHDOG_H