From c15d6657a17d7cef91f800f40d11760e2e7340af Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Wed, 30 May 2012 14:52:57 -0700 Subject: Add audio watchdog thread Change-Id: I4ed62087bd6554179abb8258d2da606050e762c0 --- services/audioflinger/Android.mk | 4 + services/audioflinger/AudioFlinger.cpp | 35 +++++++++ services/audioflinger/AudioFlinger.h | 3 + services/audioflinger/AudioWatchdog.cpp | 134 ++++++++++++++++++++++++++++++++ services/audioflinger/AudioWatchdog.h | 88 +++++++++++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 services/audioflinger/AudioWatchdog.cpp create mode 100644 services/audioflinger/AudioWatchdog.h (limited to 'services/audioflinger') diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk index ee843aa..f32d90f 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -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) diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index be59ca0..586a916 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -2300,6 +2300,19 @@ AudioFlinger::MixerThread::MixerThread(const sp& 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 } } + 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; } diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index 677d466..384306c 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -49,6 +49,7 @@ #include "ExtendedAudioBufferProvider.h" #include "FastMixer.h" #include "NBAIO.h" +#include "AudioWatchdog.h" #include @@ -1169,6 +1170,7 @@ public: #endif // one-time initialization, no locks required FastMixer* mFastMixer; // non-NULL if there is also a fast mixer + sp 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 diff --git a/services/audioflinger/AudioWatchdog.cpp b/services/audioflinger/AudioWatchdog.cpp new file mode 100644 index 0000000..8f328ee --- /dev/null +++ b/services/audioflinger/AudioWatchdog.cpp @@ -0,0 +1,134 @@ +/* + * 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 +#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 diff --git a/services/audioflinger/AudioWatchdog.h b/services/audioflinger/AudioWatchdog.h new file mode 100644 index 0000000..4657530 --- /dev/null +++ b/services/audioflinger/AudioWatchdog.h @@ -0,0 +1,88 @@ +/* + * 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 +#include + +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 -- cgit v1.1