Allow frame count regress in AudioStreamOut
AudioStreamOut attempts to detect wraparound by assuming that the
HAL frame count is monotonic, which is not true wrt gapless offload
tracks. When a non-monotonic update is detected, we freeze the fw
side framecount, rather than updating. The exception is when we
are notified that framecount will reset (e.g. flush()).
Since the notification that we are transitioning can occur arbitrarily
in advance from the HAL, upon presentationComplete for a offload stream,
we notify AudioStreamOut that we expect an upcoming framecount reset
from the HAL.
Then, we do not suppress the next detected non-monotonic sample, and
appropriately decrease the uint64 mRenderPosition.
Test: offloadAudioTrackPlayer manual test app (exoplayer test in
google3)
Bug: 191950723
Change-Id: I6060d0a9750783c2e2a7b46ce592c042d1405aa0
diff --git a/services/audioflinger/AudioStreamOut.cpp b/services/audioflinger/AudioStreamOut.cpp
index d8565bd..65ec0e8 100644
--- a/services/audioflinger/AudioStreamOut.cpp
+++ b/services/audioflinger/AudioStreamOut.cpp
@@ -39,6 +39,7 @@
, mRateMultiplier(1)
, mHalFormatHasProportionalFrames(false)
, mHalFrameSize(0)
+ , mExpectRetrograde(false)
{
}
@@ -69,8 +70,12 @@
const uint32_t truncatedPosition = (uint32_t)mRenderPosition;
int32_t deltaHalPosition; // initialization not needed, overwitten by __builtin_sub_overflow()
(void) __builtin_sub_overflow(halPosition, truncatedPosition, &deltaHalPosition);
+
if (deltaHalPosition > 0) {
mRenderPosition += deltaHalPosition;
+ } else if (mExpectRetrograde) {
+ mExpectRetrograde = false;
+ mRenderPosition -= static_cast<uint64_t>(-deltaHalPosition);
}
// Scale from HAL sample rate to application rate.
*frames = mRenderPosition / mRateMultiplier;
@@ -187,6 +192,7 @@
int AudioStreamOut::flush()
{
mRenderPosition = 0;
+ mExpectRetrograde = false;
mFramesWritten = 0;
mFramesWrittenAtStandby = 0;
status_t result = stream->flush();
@@ -196,6 +202,7 @@
int AudioStreamOut::standby()
{
mRenderPosition = 0;
+ mExpectRetrograde = false;
mFramesWrittenAtStandby = mFramesWritten;
return stream->standby();
}
diff --git a/services/audioflinger/AudioStreamOut.h b/services/audioflinger/AudioStreamOut.h
index 565f43a..82fe238 100644
--- a/services/audioflinger/AudioStreamOut.h
+++ b/services/audioflinger/AudioStreamOut.h
@@ -93,13 +93,21 @@
virtual status_t flush();
virtual status_t standby();
+ // Avoid suppressing retrograde motion in mRenderPosition for gapless offload/direct when
+ // transitioning between tracks.
+ // The HAL resets the frame position without flush/stop being called, but calls back prior to
+ // this event. So, on the next occurrence of retrograde motion, we permit backwards movement of
+ // mRenderPosition.
+ virtual void presentationComplete() { mExpectRetrograde = true; }
+
protected:
uint64_t mFramesWritten; // reset by flush
uint64_t mFramesWrittenAtStandby;
- uint64_t mRenderPosition; // reset by flush or standby
+ uint64_t mRenderPosition; // reset by flush, standby, or presentation complete
int mRateMultiplier;
bool mHalFormatHasProportionalFrames;
size_t mHalFrameSize;
+ bool mExpectRetrograde; // see presentationComplete
};
} // namespace android
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 9344e20..596ff36 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -6320,9 +6320,13 @@
track->isStopping_2() || track->isPaused()) {
// We have consumed all the buffers of this track.
// Remove it from the list of active tracks.
+ bool presComplete = false;
if (mStandby || !last ||
- track->presentationComplete(latency_l()) ||
+ (presComplete = track->presentationComplete(latency_l())) ||
track->isPaused() || mHwPaused) {
+ if (presComplete) {
+ mOutput->presentationComplete();
+ }
if (track->isStopping_2()) {
track->mState = TrackBase::STOPPED;
}
@@ -6901,7 +6905,8 @@
// Drain has completed or we are in standby, signal presentation complete
if (!(mDrainSequence & 1) || !last || mStandby) {
track->mState = TrackBase::STOPPED;
- track->presentationComplete(latency_l());
+ mOutput->presentationComplete();
+ track->presentationComplete(latency_l()); // always returns true
track->reset();
tracksToRemove->add(track);
// OFFLOADED stop resets frame counts.