AudioFlinger: Associate audio time with client uid
Group active track operations for add and remove together.
PlaybackThread now uses strong pointer for active tracks.
Test: Play Music, Youtube, Batterystats, CTS AudioTrack
Bug: 32361950
Change-Id: I101df081dd8d090560a83c44c2fa9ffcbf39c84d
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 71d6b92..aa8824e 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -20,6 +20,7 @@
#include "Configuration.h"
#include <deque>
+#include <map>
#include <stdint.h>
#include <sys/types.h>
#include <limits.h>
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 9966eeb..16f86c2 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -508,8 +508,7 @@
mAudioSource(AUDIO_SOURCE_DEFAULT), mId(id),
// mName will be set by concrete (non-virtual) subclass
mDeathRecipient(new PMDeathRecipient(this)),
- mSystemReady(systemReady),
- mNotifiedBatteryStart(false)
+ mSystemReady(systemReady)
{
memset(&mPatch, 0, sizeof(struct audio_patch));
}
@@ -850,10 +849,10 @@
}
}
-void AudioFlinger::ThreadBase::acquireWakeLock(int uid)
+void AudioFlinger::ThreadBase::acquireWakeLock()
{
Mutex::Autolock _l(mLock);
- acquireWakeLock_l(uid);
+ acquireWakeLock_l();
}
String16 AudioFlinger::ThreadBase::getWakeLockTag()
@@ -875,37 +874,23 @@
}
}
-void AudioFlinger::ThreadBase::acquireWakeLock_l(int uid)
+void AudioFlinger::ThreadBase::acquireWakeLock_l()
{
getPowerManager_l();
if (mPowerManager != 0) {
sp<IBinder> binder = new BBinder();
- status_t status;
- if (uid >= 0) {
- status = mPowerManager->acquireWakeLockWithUid(POWERMANAGER_PARTIAL_WAKE_LOCK,
- binder,
- getWakeLockTag(),
- String16("audioserver"),
- uid,
- true /* FIXME force oneway contrary to .aidl */);
- } else {
- status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK,
+ // Uses AID_AUDIOSERVER for wakelock. updateWakeLockUids_l() updates with client uids.
+ status_t status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK,
binder,
getWakeLockTag(),
String16("audioserver"),
true /* FIXME force oneway contrary to .aidl */);
- }
if (status == NO_ERROR) {
mWakeLockToken = binder;
}
ALOGV("acquireWakeLock_l() %s status %d", mThreadName, status);
}
- if (!mNotifiedBatteryStart) {
- // TODO: call this function for each track when it becomes active.
- BatteryNotifier::getInstance().noteStartAudio(AID_AUDIOSERVER);
- mNotifiedBatteryStart = true;
- }
gBoottime.acquire(mWakeLockToken);
mTimestamp.mTimebaseOffset[ExtendedTimestamp::TIMEBASE_BOOTTIME] =
gBoottime.getBoottimeOffset();
@@ -928,12 +913,6 @@
}
mWakeLockToken.clear();
}
-
- if (mNotifiedBatteryStart) {
- // TODO: call this function for each track when it becomes inactive.
- BatteryNotifier::getInstance().noteStopAudio(AID_AUDIOSERVER);
- mNotifiedBatteryStart = false;
- }
}
void AudioFlinger::ThreadBase::getPowerManager_l() {
@@ -952,6 +931,15 @@
void AudioFlinger::ThreadBase::updateWakeLockUids_l(const SortedVector<int> &uids) {
getPowerManager_l();
+
+#if !LOG_NDEBUG
+ std::stringstream s;
+ for (int uid : uids) {
+ s << uid << " ";
+ }
+ ALOGD("updateWakeLockUids_l %s uids:%s", mThreadName, s.str().c_str());
+#endif
+
if (mWakeLockToken == NULL) { // token may be NULL if AudioFlinger::systemReady() not called.
if (mSystemReady) {
ALOGE("no wake lock to update, but system ready!");
@@ -1534,6 +1522,73 @@
mPendingConfigEvents.clear();
}
+template <typename T>
+ssize_t AudioFlinger::ThreadBase::ActiveTracks<T>::add(const sp<T> &track) {
+ ssize_t index = mActiveTracks.indexOf(track);
+ if (index >= 0) {
+ ALOGW("ActiveTracks<T>::add track %p already there", track.get());
+ return index;
+ }
+ mActiveTracksGeneration++;
+ mLatestActiveTrack = track;
+ ++mBatteryCounter[track->uid()].second;
+ return mActiveTracks.add(track);
+}
+
+template <typename T>
+ssize_t AudioFlinger::ThreadBase::ActiveTracks<T>::remove(const sp<T> &track) {
+ ssize_t index = mActiveTracks.remove(track);
+ if (index < 0) {
+ ALOGW("ActiveTracks<T>::remove nonexistent track %p", track.get());
+ return index;
+ }
+ mActiveTracksGeneration++;
+ --mBatteryCounter[track->uid()].second;
+ // mLatestActiveTrack is not cleared even if is the same as track.
+ return index;
+}
+
+template <typename T>
+void AudioFlinger::ThreadBase::ActiveTracks<T>::clear() {
+ for (const sp<T> &track : mActiveTracks) {
+ BatteryNotifier::getInstance().noteStopAudio(track->uid());
+ }
+ mLastActiveTracksGeneration = mActiveTracksGeneration;
+ mActiveTracks.clear();
+ mLatestActiveTrack.clear();
+ mBatteryCounter.clear();
+}
+
+template <typename T>
+void AudioFlinger::ThreadBase::ActiveTracks<T>::updatePowerState(
+ sp<ThreadBase> thread, bool force) {
+ // Updates ActiveTracks client uids to the thread wakelock.
+ if (mActiveTracksGeneration != mLastActiveTracksGeneration || force) {
+ thread->updateWakeLockUids_l(getWakeLockUids());
+ mLastActiveTracksGeneration = mActiveTracksGeneration;
+ }
+
+ // Updates BatteryNotifier uids
+ for (auto it = mBatteryCounter.begin(); it != mBatteryCounter.end();) {
+ const uid_t uid = it->first;
+ ssize_t &previous = it->second.first;
+ ssize_t ¤t = it->second.second;
+ if (current > 0) {
+ if (previous == 0) {
+ BatteryNotifier::getInstance().noteStartAudio(uid);
+ }
+ previous = current;
+ ++it;
+ } else if (current == 0) {
+ if (previous > 0) {
+ BatteryNotifier::getInstance().noteStopAudio(uid);
+ }
+ it = mBatteryCounter.erase(it); // std::map<> is stable on iterator erase.
+ } else /* (current < 0) */ {
+ LOG_ALWAYS_FATAL("negative battery count %zd", current);
+ }
+ }
+}
// ----------------------------------------------------------------------------
// Playback
@@ -1560,7 +1615,6 @@
mSuspended(0), mBytesWritten(0),
mFramesWritten(0),
mSuspendedFrames(0),
- mActiveTracksGeneration(0),
// mStreamTypes[] initialized in constructor body
mOutput(output),
mLastWriteTime(-1), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false),
@@ -1680,8 +1734,8 @@
result.append(buffer);
Track::appendDumpHeader(result);
for (size_t i = 0; i < numactive; ++i) {
- sp<Track> track = mActiveTracks[i].promote();
- if (track != 0 && mTracks.indexOf(track) < 0) {
+ sp<Track> track = mActiveTracks[i];
+ if (mTracks.indexOf(track) < 0) {
track->dump(buffer, SIZE, true);
result.append(buffer);
}
@@ -2084,9 +2138,6 @@
track->mResetDone = false;
track->mPresentationCompleteFrames = 0;
mActiveTracks.add(track);
- mWakeLockUids.add(track->uid());
- mActiveTracksGeneration++;
- mLatestActiveTrack = track;
sp<EffectChain> chain = getEffectChain_l(track->sessionId());
if (chain != 0) {
ALOGV("addTrack_l() starting track on chain %p for session %d", chain.get(),
@@ -2638,6 +2689,11 @@
sp<Track> track = mTracks[i];
track->invalidate();
}
+ // Clear ActiveTracks to update BatteryNotifier in case active tracks remain.
+ // After we exit there are no more track changes sent to BatteryNotifier
+ // because that requires an active threadLoop.
+ // TODO: should we decActiveTrackCnt() of the cleared track effect chain?
+ mActiveTracks.clear();
}
}
@@ -2730,11 +2786,7 @@
}
// indicate all active tracks in the chain
- for (size_t i = 0 ; i < mActiveTracks.size() ; ++i) {
- sp<Track> track = mActiveTracks[i].promote();
- if (track == 0) {
- continue;
- }
+ for (const sp<Track> &track : mActiveTracks) {
if (session == track->sessionId()) {
ALOGV("addEffectChain_l() activating track %p on session %d", track.get(), session);
chain->incActiveTrackCnt();
@@ -2781,11 +2833,7 @@
if (chain == mEffectChains[i]) {
mEffectChains.removeAt(i);
// detach all active tracks from the chain
- for (size_t i = 0 ; i < mActiveTracks.size() ; ++i) {
- sp<Track> track = mActiveTracks[i].promote();
- if (track == 0) {
- continue;
- }
+ for (const sp<Track> &track : mActiveTracks) {
if (session == track->sessionId()) {
ALOGV("removeEffectChain_l(): stopping track on chain %p for session Id: %d",
chain.get(), session);
@@ -2862,8 +2910,6 @@
// FIXME could this be made local to while loop?
writeFrames = 0;
- int lastGeneration = 0;
-
cacheParameters_l();
mSleepTimeUs = mIdleSleepTimeUs;
@@ -2961,10 +3007,9 @@
mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] = mLastWriteTime == -1
? systemTime() : mLastWriteTime;
}
- const size_t size = mActiveTracks.size();
- for (size_t i = 0; i < size; ++i) {
- sp<Track> t = mActiveTracks[i].promote();
- if (t != 0 && !t->isFastTrack()) {
+
+ for (const sp<Track> &t : mActiveTracks) {
+ if (!t->isFastTrack()) {
t->updateTrackFrameInfo(
t->mAudioTrackServerProxy->framesReleased(),
mFramesWritten,
@@ -2985,8 +3030,6 @@
if (!keepWakeLock()) {
releaseWakeLock_l();
released = true;
- mWakeLockUids.clear();
- mActiveTracksGeneration++;
}
ALOGV("wait async completion");
mWaitWorkCV.wait(mLock);
@@ -3020,8 +3063,6 @@
}
releaseWakeLock_l();
- mWakeLockUids.clear();
- mActiveTracksGeneration++;
// wait until we have something to do...
ALOGV("%s going to sleep", myName.string());
mWaitWorkCV.wait(mLock);
@@ -3046,12 +3087,7 @@
// mMixerStatusIgnoringFastTracks is also updated internally
mMixerStatus = prepareTracks_l(&tracksToRemove);
- // compare with previously applied list
- if (lastGeneration != mActiveTracksGeneration) {
- // update wakelock
- updateWakeLockUids_l(mWakeLockUids);
- lastGeneration = mActiveTracksGeneration;
- }
+ mActiveTracks.updatePowerState(this);
// prevent any changes in effect chain list and in each effect chain
// during mixing and effect process as the audio buffers could be deleted
@@ -3269,8 +3305,6 @@
}
releaseWakeLock();
- mWakeLockUids.clear();
- mActiveTracksGeneration++;
ALOGV("Thread %p type %d exiting", this, mType);
return false;
@@ -3284,8 +3318,6 @@
for (size_t i=0 ; i<count ; i++) {
const sp<Track>& track = tracksToRemove.itemAt(i);
mActiveTracks.remove(track);
- mWakeLockUids.remove(track->uid());
- mActiveTracksGeneration++;
ALOGV("removeTracks_l removing track on session %d", track->sessionId());
sp<EffectChain> chain = getEffectChain_l(track->sessionId());
if (chain != 0) {
@@ -3917,10 +3949,7 @@
mEffectBufferValid = false; // mEffectBuffer has no valid data until tracks found.
for (size_t i=0 ; i<count ; i++) {
- const sp<Track> t = mActiveTracks[i].promote();
- if (t == 0) {
- continue;
- }
+ const sp<Track> t = mActiveTracks[i];
// this const just means the local variable doesn't change
Track* const track = t.get();
@@ -4416,11 +4445,7 @@
size_t i = __builtin_ctz(resetMask);
ALOG_ASSERT(i < count);
resetMask &= ~(1 << i);
- sp<Track> t = mActiveTracks[i].promote();
- if (t == 0) {
- continue;
- }
- Track* track = t.get();
+ sp<Track> track = mActiveTracks[i];
ALOG_ASSERT(track->isFastTrack() && track->isStopped());
track->reset();
}
@@ -4730,7 +4755,7 @@
void AudioFlinger::DirectOutputThread::onAddNewTrack_l()
{
sp<Track> previousTrack = mPreviousTrack.promote();
- sp<Track> latestTrack = mLatestActiveTrack.promote();
+ sp<Track> latestTrack = mActiveTracks.getLatest();
if (previousTrack != 0 && latestTrack != 0) {
if (mType == DIRECT) {
@@ -4756,13 +4781,7 @@
bool doHwResume = false;
// find out which tracks need to be processed
- for (size_t i = 0; i < count; i++) {
- sp<Track> t = mActiveTracks[i].promote();
- // The track died recently
- if (t == 0) {
- continue;
- }
-
+ for (const sp<Track> &t : mActiveTracks) {
if (t->isInvalid()) {
ALOGW("An invalidated track shouldn't be in active list");
tracksToRemove->add(t);
@@ -4777,7 +4796,7 @@
// In theory an older track could underrun and restart after the new one starts
// but as we only care about the transition phase between two tracks on a
// direct output, it is not a problem to ignore the underrun case.
- sp<Track> l = mLatestActiveTrack.promote();
+ sp<Track> l = mActiveTracks.getLatest();
bool last = l.get() == track;
if (track->isPausing()) {
@@ -5311,12 +5330,7 @@
ALOGV("OffloadThread::prepareTracks_l active tracks %zu", count);
// find out which tracks need to be processed
- for (size_t i = 0; i < count; i++) {
- sp<Track> t = mActiveTracks[i].promote();
- // The track died recently
- if (t == 0) {
- continue;
- }
+ for (const sp<Track> &t : mActiveTracks) {
Track* const track = t.get();
#ifdef VERY_VERY_VERBOSE_LOGGING
audio_track_cblk_t* cblk = track->cblk();
@@ -5325,7 +5339,7 @@
// In theory an older track could underrun and restart after the new one starts
// but as we only care about the transition phase between two tracks on a
// direct output, it is not a problem to ignore the underrun case.
- sp<Track> l = mLatestActiveTrack.promote();
+ sp<Track> l = mActiveTracks.getLatest();
bool last = l.get() == track;
if (track->isInvalid()) {
@@ -5784,7 +5798,7 @@
#endif
) :
ThreadBase(audioFlinger, id, outDevice, inDevice, RECORD, systemReady),
- mInput(input), mActiveTracksGen(0), mRsmpInBuffer(NULL),
+ mInput(input), mRsmpInBuffer(NULL),
// mRsmpInFrames, mRsmpInFramesP2, and mRsmpInFramesOA are set by readInputParameters_l()
mRsmpInRear(0)
#ifdef TEE_SINK
@@ -5942,25 +5956,9 @@
reacquire_wakelock:
sp<RecordTrack> activeTrack;
- int activeTracksGen;
{
Mutex::Autolock _l(mLock);
- size_t size = mActiveTracks.size();
- activeTracksGen = mActiveTracksGen;
- if (size > 0) {
- // FIXME an arbitrary choice
- activeTrack = mActiveTracks[0];
- acquireWakeLock_l(activeTrack->uid());
- if (size > 1) {
- SortedVector<int> tmp;
- for (size_t i = 0; i < size; i++) {
- tmp.add(mActiveTracks[i]->uid());
- }
- updateWakeLockUids_l(tmp);
- }
- } else {
- acquireWakeLock_l(-1);
- }
+ acquireWakeLock_l();
}
// used to request a deferred sleep, to be executed later while mutex is unlocked
@@ -6012,15 +6010,6 @@
goto reacquire_wakelock;
}
- if (mActiveTracksGen != activeTracksGen) {
- activeTracksGen = mActiveTracksGen;
- SortedVector<int> tmp;
- for (size_t i = 0; i < size; i++) {
- tmp.add(mActiveTracks[i]->uid());
- }
- updateWakeLockUids_l(tmp);
- }
-
bool doBroadcast = false;
bool allStopped = true;
for (size_t i = 0; i < size; ) {
@@ -6033,7 +6022,6 @@
}
removeTrack_l(activeTrack);
mActiveTracks.remove(activeTrack);
- mActiveTracksGen++;
size--;
continue;
}
@@ -6043,7 +6031,6 @@
case TrackBase::PAUSING:
mActiveTracks.remove(activeTrack);
- mActiveTracksGen++;
doBroadcast = true;
size--;
continue;
@@ -6083,6 +6070,8 @@
}
}
+ mActiveTracks.updatePowerState(this);
+
if (allStopped) {
standbyIfNotAlreadyInStandby();
}
@@ -6381,7 +6370,6 @@
track->invalidate();
}
mActiveTracks.clear();
- mActiveTracksGen++;
mStartStopCond.broadcast();
}
@@ -6635,7 +6623,6 @@
// or using a separate command thread
recordTrack->mState = TrackBase::STARTING_1;
mActiveTracks.add(recordTrack);
- mActiveTracksGen++;
status_t status = NO_ERROR;
if (recordTrack->isExternalTrack()) {
mLock.unlock();
@@ -6644,7 +6631,6 @@
// FIXME should verify that recordTrack is still in mActiveTracks
if (status != NO_ERROR) {
mActiveTracks.remove(recordTrack);
- mActiveTracksGen++;
recordTrack->clearSyncStartEvent();
ALOGV("RecordThread::start error %d", status);
return status;
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index d261ea5..8607815 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -403,8 +403,8 @@
effect_uuid_t mType; // effect type UUID
};
- void acquireWakeLock(int uid = -1);
- virtual void acquireWakeLock_l(int uid = -1);
+ void acquireWakeLock();
+ virtual void acquireWakeLock_l();
void releaseWakeLock();
void releaseWakeLock_l();
void updateWakeLockUids_l(const SortedVector<int> &uids);
@@ -481,8 +481,92 @@
static const size_t kLogSize = 4 * 1024;
sp<NBLog::Writer> mNBLogWriter;
bool mSystemReady;
- bool mNotifiedBatteryStart;
ExtendedTimestamp mTimestamp;
+
+ // ActiveTracks is a sorted vector of track type T representing the
+ // active tracks of threadLoop() to be considered by the locked prepare portion.
+ // ActiveTracks should be accessed with the ThreadBase lock held.
+ //
+ // During processing and I/O, the threadLoop does not hold the lock;
+ // hence it does not directly use ActiveTracks. Care should be taken
+ // to hold local strong references or defer removal of tracks
+ // if the threadLoop may still be accessing those tracks due to mix, etc.
+ //
+ // This class updates power information appropriately.
+ //
+
+ template <typename T>
+ class ActiveTracks {
+ public:
+ ActiveTracks()
+ : mActiveTracksGeneration(0)
+ , mLastActiveTracksGeneration(0)
+ { }
+
+ ~ActiveTracks() {
+ ALOGW_IF(!mActiveTracks.isEmpty(),
+ "ActiveTracks should be empty in destructor");
+ }
+ // returns the last track added (even though it may have been
+ // subsequently removed from ActiveTracks).
+ //
+ // Used for DirectOutputThread to ensure a flush is called when transitioning
+ // to a new track (even though it may be on the same session).
+ // Used for OffloadThread to ensure that volume and mixer state is
+ // taken from the latest track added.
+ //
+ // The latest track is saved with a weak pointer to prevent keeping an
+ // otherwise useless track alive. Thus the function will return nullptr
+ // if the latest track has subsequently been removed and destroyed.
+ sp<T> getLatest() {
+ return mLatestActiveTrack.promote();
+ }
+
+ // SortedVector methods
+ ssize_t add(const sp<T> &track);
+ ssize_t remove(const sp<T> &track);
+ size_t size() const {
+ return mActiveTracks.size();
+ }
+ ssize_t indexOf(const sp<T>& item) {
+ return mActiveTracks.indexOf(item);
+ }
+ sp<T> operator[](size_t index) const {
+ return mActiveTracks[index];
+ }
+ typename SortedVector<sp<T>>::iterator begin() {
+ return mActiveTracks.begin();
+ }
+ typename SortedVector<sp<T>>::iterator end() {
+ return mActiveTracks.end();
+ }
+
+ // Due to Binder recursion optimization, clear() and updatePowerState()
+ // cannot be called from a Binder thread because they may call back into
+ // the original calling process (system server) for BatteryNotifier
+ // (which requires a Java environment that may not be present).
+ // Hence, call clear() and updatePowerState() only from the
+ // ThreadBase thread.
+ void clear();
+ // periodically called in the threadLoop() to update power state uids.
+ void updatePowerState(sp<ThreadBase> thread, bool force = false);
+
+ private:
+ SortedVector<int> getWakeLockUids() {
+ SortedVector<int> wakeLockUids;
+ for (const sp<T> &track : mActiveTracks) {
+ wakeLockUids.add(track->uid());
+ }
+ return wakeLockUids; // moved by underlying SharedBuffer
+ }
+
+ std::map<uid_t, std::pair<ssize_t /* previous */, ssize_t /* current */>>
+ mBatteryCounter;
+ SortedVector<sp<T>> mActiveTracks;
+ int mActiveTracksGeneration;
+ int mLastActiveTracksGeneration;
+ wp<T> mLatestActiveTrack; // latest track added to ActiveTracks
+ };
};
// --- PlaybackThread ---
@@ -561,6 +645,10 @@
virtual void preExit();
virtual bool keepWakeLock() const { return true; }
+ virtual void acquireWakeLock_l() {
+ ThreadBase::acquireWakeLock_l();
+ mActiveTracks.updatePowerState(this, true /* force */);
+ }
public:
@@ -733,10 +821,7 @@
bool mMasterMute;
void setMasterMute_l(bool muted) { mMasterMute = muted; }
protected:
- SortedVector< wp<Track> > mActiveTracks; // FIXME check if this could be sp<>
- SortedVector<int> mWakeLockUids;
- int mActiveTracksGeneration;
- wp<Track> mLatestActiveTrack; // latest track added to mActiveTracks
+ ActiveTracks<Track> mActiveTracks;
// Allocate a track name for a given channel mask.
// Returns name >= 0 if successful, -1 on failure.
@@ -974,8 +1059,8 @@
virtual uint32_t suspendSleepTimeUs() const;
virtual void cacheParameters_l();
- virtual void acquireWakeLock_l(int uid = -1) {
- PlaybackThread::acquireWakeLock_l(uid);
+ virtual void acquireWakeLock_l() {
+ PlaybackThread::acquireWakeLock_l();
if (hasFastMixer()) {
mFastMixer->setBoottimeOffset(
mTimestamp.mTimebaseOffset[ExtendedTimestamp::TIMEBASE_BOOTTIME]);
@@ -1414,6 +1499,11 @@
virtual status_t checkEffectCompatibility_l(const effect_descriptor_t *desc,
audio_session_t sessionId);
+ virtual void acquireWakeLock_l() {
+ ThreadBase::acquireWakeLock_l();
+ mActiveTracks.updatePowerState(this, true /* force */);
+ }
+
private:
// Enter standby if not already in standby, and set mStandby flag
void standbyIfNotAlreadyInStandby();
@@ -1425,9 +1515,8 @@
SortedVector < sp<RecordTrack> > mTracks;
// mActiveTracks has dual roles: it indicates the current active track(s), and
// is used together with mStartStopCond to indicate start()/stop() progress
- SortedVector< sp<RecordTrack> > mActiveTracks;
- // generation counter for mActiveTracks
- int mActiveTracksGen;
+ ActiveTracks<RecordTrack> mActiveTracks;
+
Condition mStartStopCond;
// resampler converts input at HAL Hz to output at AudioRecord client Hz