Add AudioRecord timestamps
Bug: 13569372
Bug: 22886739
Change-Id: Ibc81afefb733d23676a632a0f2da31163fdbe05f
diff --git a/include/media/AudioRecord.h b/include/media/AudioRecord.h
index c47a4e7..521557d 100644
--- a/include/media/AudioRecord.h
+++ b/include/media/AudioRecord.h
@@ -19,6 +19,7 @@
#include <cutils/sched_policy.h>
#include <media/AudioSystem.h>
+#include <media/AudioTimestamp.h>
#include <media/IAudioRecord.h>
#include <media/Modulo.h>
#include <utils/threads.h>
@@ -314,6 +315,17 @@
*/
status_t getPosition(uint32_t *position) const;
+ /* Return the record timestamp.
+ *
+ * Parameters:
+ * timestamp: A pointer to the timestamp to be filled.
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - BAD_VALUE: timestamp is NULL
+ */
+ status_t getTimestamp(ExtendedTimestamp *timestamp);
+
/* Returns a handle on the audio input used by this AudioRecord.
*
* Parameters:
@@ -571,6 +583,11 @@
size_t mReqFrameCount; // frame count to request the first or next time
// a new IAudioRecord is needed, non-decreasing
+ int64_t mFramesRead; // total frames read. reset to zero after
+ // the start() following stop(). It is not
+ // changed after restoring the track.
+ int64_t mFramesReadServerOffset; // An offset to server frames read due to
+ // restoring AudioRecord, or stop/start.
// constant after constructor or set()
uint32_t mSampleRate;
audio_format_t mFormat;
diff --git a/include/media/AudioTimestamp.h b/include/media/AudioTimestamp.h
index 99e9c3e..e6ca225 100644
--- a/include/media/AudioTimestamp.h
+++ b/include/media/AudioTimestamp.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_AUDIO_TIMESTAMP_H
#define ANDROID_AUDIO_TIMESTAMP_H
+#include <string>
+#include <sstream>
#include <time.h>
namespace android {
@@ -32,6 +34,85 @@
struct timespec mTime; // corresponding CLOCK_MONOTONIC when frame is expected to present
};
+struct ExtendedTimestamp {
+ enum Location {
+ LOCATION_CLIENT, // timestamp of last read frame from client-server track buffer
+ LOCATION_SERVER, // timestamp of newest frame from client-server track buffer
+ LOCATION_KERNEL, // timestamp of newest frame in the kernel (alsa) buffer.
+ LOCATION_MAX // for sizing arrays only
+ };
+
+ // This needs to be kept in sync with android.media.AudioTimestamp
+ enum Timebase {
+ TIMEBASE_MONOTONIC, // Clock monotonic offset (generally 0)
+ TIMEBASE_BOOTTIME,
+ TIMEBASE_MAX,
+ };
+
+ ExtendedTimestamp() {
+ clear();
+ }
+
+ // mPosition is expressed in frame units.
+ // It is generally nonnegative, though we keep this signed for
+ // to potentially express algorithmic latency at the start of the stream
+ // and to prevent unintentional unsigned integer underflow.
+ int64_t mPosition[LOCATION_MAX];
+
+ // mTimeNs is in nanoseconds for the default timebase, monotonic.
+ // If this value is -1, then both time and position are invalid.
+ // If this value is 0, then the time is not valid but the position is valid.
+ int64_t mTimeNs[LOCATION_MAX];
+
+ // mTimebaseOffset is the offset in ns from monotonic when the
+ // timestamp was taken. This may vary due to suspend time
+ // or NTP adjustment.
+ int64_t mTimebaseOffset[TIMEBASE_MAX];
+
+ void clear() {
+ memset(mPosition, 0, sizeof(mPosition)); // actually not necessary if time is -1
+ for (int i = 0; i < LOCATION_MAX; ++i) {
+ mTimeNs[i] = -1;
+ }
+ memset(mTimebaseOffset, 0, sizeof(mTimebaseOffset));
+ }
+
+ // Returns the best timestamp as judged from the closest-to-hw stage in the
+ // pipeline with a valid timestamp.
+ int getBestTimestamp(int64_t *position, int64_t *time, int timebase) {
+ if (position == nullptr || time == nullptr
+ || timebase < 0 || timebase >= TIMEBASE_MAX) {
+ return BAD_VALUE;
+ }
+ // look for the closest-to-hw stage in the pipeline with a valid timestamp.
+ // We omit LOCATION_CLIENT as we prefer at least LOCATION_SERVER based accuracy
+ // when getting the best timestamp.
+ for (int i = LOCATION_MAX - 1; i >= LOCATION_SERVER; --i) {
+ if (mTimeNs[i] > 0) {
+ *position = mPosition[i];
+ *time = mTimeNs[i] + mTimebaseOffset[timebase];
+ return OK;
+ }
+ }
+ return INVALID_OPERATION;
+ }
+
+ // convert fields to a printable string
+ std::string toString() {
+ std::stringstream ss;
+
+ ss << "BOOTTIME offset " << mTimebaseOffset[TIMEBASE_BOOTTIME] << "\n";
+ for (int i = 0; i < LOCATION_MAX; ++i) {
+ ss << "ExtendedTimestamp[" << i << "] position: "
+ << mPosition[i] << " time: " << mTimeNs[i] << "\n";
+ }
+ return ss.str();
+ }
+ // TODO:
+ // Consider adding buffer status:
+ // size, available, algorithmic latency
+};
+
} // namespace
#endif // ANDROID_AUDIO_TIMESTAMP_H
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 770f007..2270c85 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -26,6 +26,7 @@
#include <utils/RefBase.h>
#include <audio_utils/roundup.h>
#include <media/AudioResamplerPublic.h>
+#include <media/AudioTimestamp.h>
#include <media/Modulo.h>
#include <media/SingleStateQueue.h>
@@ -118,6 +119,8 @@
typedef SingleStateQueue<AudioPlaybackRate> PlaybackRateQueue;
+typedef SingleStateQueue<ExtendedTimestamp> ExtendedTimestampQueue;
+
// ----------------------------------------------------------------------------
// Important: do not add any virtual methods, including ~
@@ -171,6 +174,8 @@
uint16_t mPad2; // unused
+ // server write-only, client read
+ ExtendedTimestampQueue::Shared mExtendedTimestampQueue;
public:
volatile int32_t mFlags; // combinations of CBLK_*
@@ -426,8 +431,39 @@
AudioRecordClientProxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount,
size_t frameSize)
: ClientProxy(cblk, buffers, frameCount, frameSize,
- false /*isOut*/, false /*clientInServer*/) { }
+ false /*isOut*/, false /*clientInServer*/)
+ , mTimestampObserver(&cblk->mExtendedTimestampQueue) { }
~AudioRecordClientProxy() { }
+
+ status_t getTimestamp(ExtendedTimestamp *timestamp) {
+ if (timestamp == nullptr) {
+ return BAD_VALUE;
+ }
+ (void) mTimestampObserver.poll(mTimestamp);
+ *timestamp = mTimestamp;
+ return OK;
+ }
+
+ void clearTimestamp() {
+ mTimestamp.clear();
+ }
+
+ // Advances the client read pointer to the server write head pointer
+ // effectively flushing the client read buffer. The effect is
+ // instantaneous. Returns the number of frames flushed.
+ uint32_t flush() {
+ int32_t rear = android_atomic_acquire_load(&mCblk->u.mStreaming.mRear);
+ int32_t front = mCblk->u.mStreaming.mFront;
+ android_atomic_release_store(rear, &mCblk->u.mStreaming.mFront);
+ return (Modulo<int32_t>(rear) - front).unsignedValue();
+ }
+
+private:
+ // The shared buffer contents referred to by the timestamp observer
+ // is initialized when the server proxy created. A local zero timestamp
+ // is initialized by the client constructor.
+ ExtendedTimestampQueue::Observer mTimestampObserver;
+ ExtendedTimestamp mTimestamp; // initialized by constructor
};
// ----------------------------------------------------------------------------
@@ -476,6 +512,7 @@
protected:
size_t mAvailToClient; // estimated frames available to client prior to releaseBuffer()
int32_t mFlush; // our copy of cblk->u.mStreaming.mFlush, for streaming output only
+ int64_t mReleased; // our copy of cblk->mServer, at 64 bit resolution
};
// Proxy used by AudioFlinger for servicing AudioTrack
@@ -520,7 +557,7 @@
virtual uint32_t getUnderrunFrames() const { return mCblk->u.mStreaming.mUnderrunFrames; }
// Return the total number of frames that AudioFlinger has obtained and released
- virtual size_t framesReleased() const { return mCblk->mServer; }
+ virtual size_t framesReleased() const { return mReleased; }
// Return the playback speed and pitch read atomically. Not multi-thread safe on server side.
AudioPlaybackRate getPlaybackRate();
@@ -574,9 +611,20 @@
public:
AudioRecordServerProxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount,
size_t frameSize, bool clientInServer)
- : ServerProxy(cblk, buffers, frameCount, frameSize, false /*isOut*/, clientInServer) { }
+ : ServerProxy(cblk, buffers, frameCount, frameSize, false /*isOut*/, clientInServer)
+ , mTimestampMutator(&cblk->mExtendedTimestampQueue) { }
+
+ // Return the total number of frames that AudioFlinger has obtained and released
+ virtual int64_t framesReleased() const { return mReleased; }
+
+ // Expose timestamp to client proxy. Should only be called by a single thread.
+ virtual void setExtendedTimestamp(const ExtendedTimestamp ×tamp) {
+ mTimestampMutator.push(timestamp);
+ }
protected:
virtual ~AudioRecordServerProxy() { }
+
+ ExtendedTimestampQueue::Mutator mTimestampMutator;
};
// ----------------------------------------------------------------------------
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 1c0d904..ec57d96 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -284,6 +284,8 @@
mSequence = 1;
mObservedSequence = mSequence;
mInOverrun = false;
+ mFramesRead = 0;
+ mFramesReadServerOffset = 0;
return NO_ERROR;
}
@@ -299,6 +301,12 @@
return NO_ERROR;
}
+ // discard data in buffer
+ const uint32_t framesFlushed = mProxy->flush();
+ mFramesReadServerOffset -= mFramesRead + framesFlushed;
+ mFramesRead = 0;
+ mProxy->clearTimestamp(); // timestamp is invalid until next server push
+
// reset current position as seen by client to 0
mProxy->setEpoch(mProxy->getEpoch() - mProxy->getPosition());
// force refresh of remaining frames by processAudioBuffer() as last
@@ -449,6 +457,27 @@
return AudioSystem::getInputFramesLost(getInputPrivate());
}
+status_t AudioRecord::getTimestamp(ExtendedTimestamp *timestamp)
+{
+ if (timestamp == nullptr) {
+ return BAD_VALUE;
+ }
+ AutoMutex lock(mLock);
+ status_t status = mProxy->getTimestamp(timestamp);
+ if (status == OK) {
+ timestamp->mPosition[ExtendedTimestamp::LOCATION_CLIENT] = mFramesRead;
+ timestamp->mTimeNs[ExtendedTimestamp::LOCATION_CLIENT] = 0;
+ // server side frame offset in case AudioRecord has been restored.
+ for (int i = ExtendedTimestamp::LOCATION_SERVER;
+ i < ExtendedTimestamp::LOCATION_MAX; ++i) {
+ if (timestamp->mTimeNs[i] >= 0) {
+ timestamp->mPosition[i] += mFramesReadServerOffset;
+ }
+ }
+ }
+ return status;
+}
+
// ---- Explicit Routing ---------------------------------------------------
status_t AudioRecord::setInputDevice(audio_port_handle_t deviceId) {
AutoMutex lock(mLock);
@@ -837,7 +866,10 @@
releaseBuffer(&audioBuffer);
}
-
+ if (read > 0) {
+ mFramesRead += read / mFrameSize;
+ // mFramesReadTime = systemTime(SYSTEM_TIME_MONOTONIC); // not provided at this time.
+ }
return read;
}
@@ -988,6 +1020,7 @@
requested = &timeout;
}
+ size_t readFrames = 0;
while (mRemainingFrames > 0) {
Buffer audioBuffer;
@@ -1049,6 +1082,7 @@
}
releaseBuffer(&audioBuffer);
+ readFrames += releasedFrames;
// FIXME here is where we would repeat EVENT_MORE_DATA again on same advanced buffer
// if callback doesn't like to accept the full chunk
@@ -1072,6 +1106,11 @@
#endif
}
+ if (readFrames > 0) {
+ AutoMutex lock(mLock);
+ mFramesRead += readFrames;
+ // mFramesReadTime = systemTime(SYSTEM_TIME_MONOTONIC); // not provided at this time.
+ }
mRemainingFrames = notificationFrames;
mRetryOnPartialBuffer = true;
@@ -1096,6 +1135,7 @@
// FIXME this fails if we have a new AudioFlinger instance
result = mAudioRecord->start(AudioSystem::SYNC_EVENT_SAME, 0);
}
+ mFramesReadServerOffset = mFramesRead; // server resets to zero so we need an offset.
}
if (result != NO_ERROR) {
ALOGW("restoreRecord_l() failed status %d", result);
diff --git a/media/libmedia/AudioTrackShared.cpp b/media/libmedia/AudioTrackShared.cpp
index d75ad87..988386e 100644
--- a/media/libmedia/AudioTrackShared.cpp
+++ b/media/libmedia/AudioTrackShared.cpp
@@ -597,7 +597,7 @@
ServerProxy::ServerProxy(audio_track_cblk_t* cblk, void *buffers, size_t frameCount,
size_t frameSize, bool isOut, bool clientInServer)
: Proxy(cblk, buffers, frameCount, frameSize, isOut, clientInServer),
- mAvailToClient(0), mFlush(0)
+ mAvailToClient(0), mFlush(0), mReleased(0)
{
}
@@ -733,6 +733,7 @@
}
cblk->mServer += stepCount;
+ mReleased += stepCount;
size_t half = mFrameCount / 2;
if (half == 0) {
@@ -1033,6 +1034,8 @@
mFramesReadySafe = clampToSize(mFramesReady);
cblk->mServer += stepCount;
+ mReleased += stepCount;
+
// This may overflow, but client is not supposed to rely on it
StaticAudioTrackPosLoop posLoop;
posLoop.mBufferPosition = mState.mPosition;
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index e2014b7..5f70479 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -54,6 +54,10 @@
void handleSyncStartEvent(const sp<SyncEvent>& event);
void clearSyncStartEvent();
+ void updateTrackFrameInfo(int64_t trackFramesReleased,
+ int64_t sourceFramesRead,
+ uint32_t halSampleRate,
+ const ExtendedTimestamp ×tamp);
private:
friend class AudioFlinger; // for mState
@@ -72,6 +76,8 @@
// be dropped and therefore not read by the application.
sp<SyncEvent> mSyncStartEvent;
+ AudioRecordServerProxy *mAudioRecordServerProxy;
+
// number of captured frames to drop after the start sync event has been received.
// when < 0, maximum frames to drop before starting capture even if sync event is
// not received
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 7a29cce..2fd5758 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -220,6 +220,94 @@
}
#endif
+// Track the CLOCK_BOOTTIME versus CLOCK_MONOTONIC timebase offset
+struct {
+ // call when you acquire a partial wakelock
+ void acquire(const sp<IBinder> &wakeLockToken) {
+ pthread_mutex_lock(&mLock);
+ if (wakeLockToken.get() == nullptr) {
+ adjustTimebaseOffset(&mBoottimeOffset, ExtendedTimestamp::TIMEBASE_BOOTTIME);
+ } else {
+ if (mCount == 0) {
+ adjustTimebaseOffset(&mBoottimeOffset, ExtendedTimestamp::TIMEBASE_BOOTTIME);
+ }
+ ++mCount;
+ }
+ pthread_mutex_unlock(&mLock);
+ }
+
+ // call when you release a partial wakelock.
+ void release(const sp<IBinder> &wakeLockToken) {
+ if (wakeLockToken.get() == nullptr) {
+ return;
+ }
+ pthread_mutex_lock(&mLock);
+ if (--mCount < 0) {
+ ALOGE("negative wakelock count");
+ mCount = 0;
+ }
+ pthread_mutex_unlock(&mLock);
+ }
+
+ // retrieves the boottime timebase offset from monotonic.
+ int64_t getBoottimeOffset() {
+ pthread_mutex_lock(&mLock);
+ int64_t boottimeOffset = mBoottimeOffset;
+ pthread_mutex_unlock(&mLock);
+ return boottimeOffset;
+ }
+
+ // Adjusts the timebase offset between TIMEBASE_MONOTONIC
+ // and the selected timebase.
+ // Currently only TIMEBASE_BOOTTIME is allowed.
+ //
+ // This only needs to be called upon acquiring the first partial wakelock
+ // after all other partial wakelocks are released.
+ //
+ // We do an empirical measurement of the offset rather than parsing
+ // /proc/timer_list since the latter is not a formal kernel ABI.
+ static void adjustTimebaseOffset(int64_t *offset, ExtendedTimestamp::Timebase timebase) {
+ int clockbase;
+ switch (timebase) {
+ case ExtendedTimestamp::TIMEBASE_BOOTTIME:
+ clockbase = SYSTEM_TIME_BOOTTIME;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("invalid timebase %d", timebase);
+ break;
+ }
+ // try three times to get the clock offset, choose the one
+ // with the minimum gap in measurements.
+ const int tries = 3;
+ nsecs_t bestGap, measured;
+ for (int i = 0; i < tries; ++i) {
+ const nsecs_t tmono = systemTime(SYSTEM_TIME_MONOTONIC);
+ const nsecs_t tbase = systemTime(clockbase);
+ const nsecs_t tmono2 = systemTime(SYSTEM_TIME_MONOTONIC);
+ const nsecs_t gap = tmono2 - tmono;
+ if (i == 0 || gap < bestGap) {
+ bestGap = gap;
+ measured = tbase - ((tmono + tmono2) >> 1);
+ }
+ }
+
+ // to avoid micro-adjusting, we don't change the timebase
+ // unless it is significantly different.
+ //
+ // Assumption: It probably takes more than toleranceNs to
+ // suspend and resume the device.
+ static int64_t toleranceNs = 10000; // 10 us
+ if (llabs(*offset - measured) > toleranceNs) {
+ ALOGV("Adjusting timebase offset old: %lld new: %lld",
+ (long long)*offset, (long long)measured);
+ *offset = measured;
+ }
+ }
+
+ pthread_mutex_t mLock;
+ int32_t mCount;
+ int64_t mBoottimeOffset;
+} gBoottime = { PTHREAD_MUTEX_INITIALIZER, 0, 0 }; // static, so use POD initialization
// ----------------------------------------------------------------------------
// CPU Stats
@@ -945,6 +1033,7 @@
BatteryNotifier::getInstance().noteStartAudio();
mNotifiedBatteryStart = true;
}
+ gBoottime.acquire(mWakeLockToken);
}
void AudioFlinger::ThreadBase::releaseWakeLock()
@@ -955,6 +1044,7 @@
void AudioFlinger::ThreadBase::releaseWakeLock_l()
{
+ gBoottime.release(mWakeLockToken);
if (mWakeLockToken != 0) {
ALOGV("releaseWakeLock_l() %s", mThreadName);
if (mPowerManager != 0) {
@@ -5685,6 +5775,9 @@
}
}
+ mTimestamp.mTimebaseOffset[ExtendedTimestamp::TIMEBASE_BOOTTIME] =
+ gBoottime.getBoottimeOffset();
+
// used to request a deferred sleep, to be executed later while mutex is unlocked
uint32_t sleepUs = 0;
@@ -5898,6 +5991,28 @@
}
}
+ // Update server timestamp with server stats
+ // systemTime() is optional if the hardware supports timestamps.
+ mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER] += framesRead;
+ mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] = systemTime();
+
+ // Update server timestamp with kernel stats
+ if (mInput->stream->get_capture_position != nullptr) {
+ int64_t position, time;
+ int ret = mInput->stream->get_capture_position(mInput->stream, &position, &time);
+ if (ret == NO_ERROR) {
+ mTimestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL] = position;
+ mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] = time;
+ // Note: In general record buffers should tend to be empty in
+ // a properly running pipeline.
+ //
+ // Also, it is not advantageous to call get_presentation_position during the read
+ // as the read obtains a lock, preventing the timestamp call from executing.
+ }
+ }
+ // Use this to track timestamp information
+ // ALOGD("%s", mTimestamp.toString().c_str());
+
if (framesRead < 0 || (framesRead == 0 && mPipeSource == 0)) {
ALOGE("read failed: framesRead=%d", framesRead);
// Force input into standby so that it tries to recover at next read attempt
@@ -6026,6 +6141,11 @@
break;
}
+ // update frame information and push timestamp out
+ activeTrack->updateTrackFrameInfo(
+ activeTrack->mAudioRecordServerProxy->framesReleased(),
+ mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER],
+ mSampleRate, mTimestamp);
}
unlock:
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index ae8bbb9..ad47277 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1324,6 +1324,8 @@
// rolling index that is never cleared
int32_t mRsmpInRear; // last filled frame + 1
+ ExtendedTimestamp mTimestamp;
+
// For dumpsys
const sp<NBAIO_Sink> mTeeSink;
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 5830f75..b4c1fdd 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -1418,8 +1418,10 @@
return;
}
- mServerProxy = new AudioRecordServerProxy(mCblk, mBuffer, frameCount,
- mFrameSize, !isExternalTrack());
+ mAudioRecordServerProxy = new AudioRecordServerProxy(mCblk, mBuffer, frameCount,
+ mFrameSize, !isExternalTrack());
+ mServerProxy = mAudioRecordServerProxy;
+
mResamplerBufferProvider = new ResamplerBufferProvider(this);
if (flags & IAudioFlinger::TRACK_FAST) {
@@ -1556,6 +1558,24 @@
mFramesToDrop = 0;
}
+void AudioFlinger::RecordThread::RecordTrack::updateTrackFrameInfo(
+ int64_t trackFramesReleased, int64_t sourceFramesRead,
+ uint32_t halSampleRate, const ExtendedTimestamp ×tamp)
+{
+ ExtendedTimestamp local = timestamp;
+
+ // Convert HAL frames to server-side track frames at track sample rate.
+ // We use trackFramesReleased and sourceFramesRead as an anchor point.
+ for (int i = ExtendedTimestamp::LOCATION_SERVER; i < ExtendedTimestamp::LOCATION_MAX; ++i) {
+ if (local.mTimeNs[i] != 0) {
+ const int64_t relativeServerFrames = local.mPosition[i] - sourceFramesRead;
+ const int64_t relativeTrackFrames = relativeServerFrames
+ * mSampleRate / halSampleRate; // TODO: potential computation overflow
+ local.mPosition[i] = relativeTrackFrames + trackFramesReleased;
+ }
+ }
+ mAudioRecordServerProxy->setExtendedTimestamp(local);
+}
AudioFlinger::RecordThread::PatchRecord::PatchRecord(RecordThread *recordThread,
uint32_t sampleRate,