Add PassthruPatchRecord for low latency software patches
Implement a subclass of PatchRecord that uses PatchTrack's
thread for reading from HAL. This eliminates the need for
buffering that adds latency.
The only modification needed for PatchTrack is to indicate
unlimited amount of available frames to the playback thread.
This is to prevent PatchTrack from being deactivated by
DirectOutputThread due to lack of frames available.
RecordThread believes it reads audio data on its thread,
and manages timestamps as usual. The data that it "reads"
and passes to PassthruPatchRecord is discarded by the latter.
Bug: 117564323
Test: with MSD module
Merged-In: I376656e3c791e91e2196331ecdf2b425697c4e18
Change-Id: I376656e3c791e91e2196331ecdf2b425697c4e18
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index d9a7cd2..d0f8b17 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -385,6 +385,8 @@
const Timeout& timeout = {});
virtual ~PatchTrack();
+ size_t framesReady() const override;
+
virtual status_t start(AudioSystem::sync_event_t event =
AudioSystem::SYNC_EVENT_NONE,
audio_session_t triggerSession = AUDIO_SESSION_NONE);
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index e083b38..da05dac 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -143,9 +143,66 @@
return writeFrames(this, src, frameCount, frameSize);
}
-private:
+protected:
/** Write the source data into the buffer provider. @return written frame count. */
static size_t writeFrames(AudioBufferProvider* dest, const void* src,
size_t frameCount, size_t frameSize);
}; // end of PatchRecord
+
+class PassthruPatchRecord : public PatchRecord, public Source {
+public:
+ PassthruPatchRecord(RecordThread *recordThread,
+ uint32_t sampleRate,
+ audio_channel_mask_t channelMask,
+ audio_format_t format,
+ size_t frameCount,
+ audio_input_flags_t flags);
+
+ Source* getSource() override { return static_cast<Source*>(this); }
+
+ // Source interface
+ status_t read(void *buffer, size_t bytes, size_t *read) override;
+ status_t getCapturePosition(int64_t *frames, int64_t *time) override;
+ status_t standby() override;
+
+ // AudioBufferProvider interface
+ // This interface is used by RecordThread to pass the data obtained
+ // from HAL or other source to the client. PassthruPatchRecord receives
+ // the data in 'obtainBuffer' so these calls are stubbed out.
+ status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) override;
+ void releaseBuffer(AudioBufferProvider::Buffer* buffer) override;
+
+ // PatchProxyBufferProvider interface
+ // This interface is used from DirectOutputThread to acquire data from HAL.
+ bool producesBufferOnDemand() const override { return true; }
+ status_t obtainBuffer(Proxy::Buffer *buffer, const struct timespec *timeOut = nullptr) override;
+ void releaseBuffer(Proxy::Buffer *buffer) override;
+
+private:
+ // This is to use with PatchRecord::writeFrames
+ struct PatchRecordAudioBufferProvider : public AudioBufferProvider {
+ explicit PatchRecordAudioBufferProvider(PassthruPatchRecord& passthru) :
+ mPassthru(passthru) {}
+ status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) override {
+ return mPassthru.PatchRecord::getNextBuffer(buffer);
+ }
+ void releaseBuffer(AudioBufferProvider::Buffer* buffer) override {
+ return mPassthru.PatchRecord::releaseBuffer(buffer);
+ }
+ private:
+ PassthruPatchRecord& mPassthru;
+ };
+
+ sp<StreamInHalInterface> obtainStream(sp<ThreadBase>* thread);
+
+ PatchRecordAudioBufferProvider mPatchRecordAudioBufferProvider;
+ std::unique_ptr<void, decltype(free)*> mSinkBuffer; // frame size aligned continuous buffer
+ std::unique_ptr<void, decltype(free)*> mStubBuffer; // buffer used for AudioBufferProvider
+ size_t mUnconsumedFrames = 0;
+ std::mutex mReadLock;
+ std::condition_variable mReadCV;
+ size_t mReadBytes = 0; // GUARDED_BY(mReadLock)
+ status_t mReadError = NO_ERROR; // GUARDED_BY(mReadLock)
+ int64_t mLastReadFrames = 0; // accessed on RecordThread only
+};
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 8f720b5..7a3bb0d 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -325,6 +325,7 @@
virtual ~PatchProxyBufferProvider() {}
+ virtual bool producesBufferOnDemand() const = 0;
virtual status_t obtainBuffer(Proxy::Buffer* buffer,
const struct timespec *requested = NULL) = 0;
virtual void releaseBuffer(Proxy::Buffer* buffer) = 0;
@@ -347,6 +348,8 @@
mPeerProxy = nullptr;
}
+ bool producesBufferOnDemand() const override { return false; }
+
protected:
const sp<ClientProxy> mProxy;
sp<RefBase> mPeerReferenceHold; // keeps mPeerProxy alive during access.
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index cbb216e..932c32b 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -1785,6 +1785,15 @@
ALOGV("%s(%d)", __func__, mId);
}
+size_t AudioFlinger::PlaybackThread::PatchTrack::framesReady() const
+{
+ if (mPeerProxy && mPeerProxy->producesBufferOnDemand()) {
+ return std::numeric_limits<size_t>::max();
+ } else {
+ return Track::framesReady();
+ }
+}
+
status_t AudioFlinger::PlaybackThread::PatchTrack::start(AudioSystem::sync_event_t event,
audio_session_t triggerSession)
{
@@ -2345,6 +2354,180 @@
mProxy->releaseBuffer(buffer);
}
+#undef LOG_TAG
+#define LOG_TAG "AF::PthrPatchRecord"
+
+static std::unique_ptr<void, decltype(free)*> allocAligned(size_t alignment, size_t size)
+{
+ void *ptr = nullptr;
+ (void)posix_memalign(&ptr, alignment, size);
+ return std::unique_ptr<void, decltype(free)*>(ptr, free);
+}
+
+AudioFlinger::RecordThread::PassthruPatchRecord::PassthruPatchRecord(
+ RecordThread *recordThread,
+ uint32_t sampleRate,
+ audio_channel_mask_t channelMask,
+ audio_format_t format,
+ size_t frameCount,
+ audio_input_flags_t flags)
+ : PatchRecord(recordThread, sampleRate, channelMask, format, frameCount,
+ nullptr /*buffer*/, 0 /*bufferSize*/, flags),
+ mPatchRecordAudioBufferProvider(*this),
+ mSinkBuffer(allocAligned(32, mFrameCount * mFrameSize)),
+ mStubBuffer(allocAligned(32, mFrameCount * mFrameSize))
+{
+ memset(mStubBuffer.get(), 0, mFrameCount * mFrameSize);
+}
+
+sp<StreamInHalInterface> AudioFlinger::RecordThread::PassthruPatchRecord::obtainStream(
+ sp<ThreadBase>* thread)
+{
+ *thread = mThread.promote();
+ if (!*thread) return nullptr;
+ RecordThread *recordThread = static_cast<RecordThread*>((*thread).get());
+ Mutex::Autolock _l(recordThread->mLock);
+ return recordThread->mInput ? recordThread->mInput->stream : nullptr;
+}
+
+// PatchProxyBufferProvider methods are called on DirectOutputThread
+status_t AudioFlinger::RecordThread::PassthruPatchRecord::obtainBuffer(
+ Proxy::Buffer* buffer, const struct timespec* timeOut)
+{
+ if (mUnconsumedFrames) {
+ buffer->mFrameCount = std::min(buffer->mFrameCount, mUnconsumedFrames);
+ // mUnconsumedFrames is decreased in releaseBuffer to use actual frame consumption figure.
+ return PatchRecord::obtainBuffer(buffer, timeOut);
+ }
+
+ // Otherwise, execute a read from HAL and write into the buffer.
+ nsecs_t startTimeNs = 0;
+ if (timeOut && (timeOut->tv_sec != 0 || timeOut->tv_nsec != 0) && timeOut->tv_sec != INT_MAX) {
+ // Will need to correct timeOut by elapsed time.
+ startTimeNs = systemTime();
+ }
+ const size_t framesToRead = std::min(buffer->mFrameCount, mFrameCount);
+ buffer->mFrameCount = 0;
+ buffer->mRaw = nullptr;
+ sp<ThreadBase> thread;
+ sp<StreamInHalInterface> stream = obtainStream(&thread);
+ if (!stream) return NO_INIT; // If there is no stream, RecordThread is not reading.
+
+ status_t result = NO_ERROR;
+ struct timespec newTimeOut = *timeOut;
+ size_t bytesRead = 0;
+ {
+ ATRACE_NAME("read");
+ result = stream->read(mSinkBuffer.get(), framesToRead * mFrameSize, &bytesRead);
+ if (result != NO_ERROR) goto stream_error;
+ if (bytesRead == 0) return NO_ERROR;
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(mReadLock);
+ mReadBytes += bytesRead;
+ mReadError = NO_ERROR;
+ }
+ mReadCV.notify_one();
+ // writeFrames handles wraparound and should write all the provided frames.
+ // If it couldn't, there is something wrong with the client/server buffer of the software patch.
+ buffer->mFrameCount = writeFrames(
+ &mPatchRecordAudioBufferProvider,
+ mSinkBuffer.get(), bytesRead / mFrameSize, mFrameSize);
+ ALOGW_IF(buffer->mFrameCount < bytesRead / mFrameSize,
+ "Lost %zu frames obtained from HAL", bytesRead / mFrameSize - buffer->mFrameCount);
+ mUnconsumedFrames = buffer->mFrameCount;
+ // Correct newTimeOut by elapsed time.
+ if (startTimeNs) {
+ nsecs_t newTimeOutNs =
+ audio_utils_ns_from_timespec(&newTimeOut) - (systemTime() - startTimeNs);
+ if (newTimeOutNs < 0) newTimeOutNs = 0;
+ newTimeOut.tv_sec = newTimeOutNs / NANOS_PER_SECOND;
+ newTimeOut.tv_nsec = newTimeOutNs - newTimeOut.tv_sec * NANOS_PER_SECOND;
+ }
+ return PatchRecord::obtainBuffer(buffer, &newTimeOut);
+
+stream_error:
+ stream->standby();
+ {
+ std::lock_guard<std::mutex> lock(mReadLock);
+ mReadError = result;
+ }
+ mReadCV.notify_one();
+ return result;
+}
+
+void AudioFlinger::RecordThread::PassthruPatchRecord::releaseBuffer(Proxy::Buffer* buffer)
+{
+ if (buffer->mFrameCount <= mUnconsumedFrames) {
+ mUnconsumedFrames -= buffer->mFrameCount;
+ } else {
+ ALOGW("Write side has consumed more frames than we had: %zu > %zu",
+ buffer->mFrameCount, mUnconsumedFrames);
+ mUnconsumedFrames = 0;
+ }
+ PatchRecord::releaseBuffer(buffer);
+}
+
+// AudioBufferProvider and Source methods are called on RecordThread
+// 'read' emulates actual audio data with 0's. This is OK as 'getNextBuffer'
+// and 'releaseBuffer' are stubbed out and ignore their input.
+// It's not possible to retrieve actual data here w/o blocking 'obtainBuffer'
+// until we copy it.
+status_t AudioFlinger::RecordThread::PassthruPatchRecord::read(
+ void* buffer, size_t bytes, size_t* read)
+{
+ bytes = std::min(bytes, mFrameCount * mFrameSize);
+ {
+ std::unique_lock<std::mutex> lock(mReadLock);
+ mReadCV.wait(lock, [&]{ return mReadError != NO_ERROR || mReadBytes != 0; });
+ if (mReadError != NO_ERROR) {
+ mLastReadFrames = 0;
+ return mReadError;
+ }
+ *read = std::min(bytes, mReadBytes);
+ mReadBytes -= *read;
+ }
+ mLastReadFrames = *read / mFrameSize;
+ memset(buffer, 0, *read);
+ return 0;
+}
+
+status_t AudioFlinger::RecordThread::PassthruPatchRecord::getCapturePosition(
+ int64_t* frames, int64_t* time)
+{
+ sp<ThreadBase> thread;
+ sp<StreamInHalInterface> stream = obtainStream(&thread);
+ return stream ? stream->getCapturePosition(frames, time) : NO_INIT;
+}
+
+status_t AudioFlinger::RecordThread::PassthruPatchRecord::standby()
+{
+ // RecordThread issues 'standby' command in two major cases:
+ // 1. Error on read--this case is handled in 'obtainBuffer'.
+ // 2. Track is stopping--as PassthruPatchRecord assumes continuous
+ // output, this can only happen when the software patch
+ // is being torn down. In this case, the RecordThread
+ // will terminate and close the HAL stream.
+ return 0;
+}
+
+// As the buffer gets filled in obtainBuffer, here we only simulate data consumption.
+status_t AudioFlinger::RecordThread::PassthruPatchRecord::getNextBuffer(
+ AudioBufferProvider::Buffer* buffer)
+{
+ buffer->frameCount = mLastReadFrames;
+ buffer->raw = buffer->frameCount != 0 ? mStubBuffer.get() : nullptr;
+ return NO_ERROR;
+}
+
+void AudioFlinger::RecordThread::PassthruPatchRecord::releaseBuffer(
+ AudioBufferProvider::Buffer* buffer)
+{
+ buffer->frameCount = 0;
+ buffer->raw = nullptr;
+}
+
// ----------------------------------------------------------------------------
#undef LOG_TAG
#define LOG_TAG "AF::MmapTrack"