AudioTrack: Improve pause handling.
Add pauseAndWait() method to ensure that the pause is completed
(ramped to silence) before any other operation, especially flush().
Incorporate into MediaPlayer pause().
Test: Clarity video with scrubbing bar.
Test: Ringtone and Alarm playback.
Bug: 196194083
Change-Id: Id8842c6c8abfd76d11b8316a95052c2e0da7fb7c
diff --git a/include/private/media/AudioTrackShared.h b/include/private/media/AudioTrackShared.h
index 200e92d..bd6db55 100644
--- a/include/private/media/AudioTrackShared.h
+++ b/include/private/media/AudioTrackShared.h
@@ -53,6 +53,83 @@
//EL_FIXME 20 seconds may not be enough and must be reconciled with new obtainBuffer implementation
#define MAX_RUN_OFFLOADED_TIMEOUT_MS 20000 // assuming up to a maximum of 20 seconds of offloaded
+// for audio_track_cblk_t::mState, to match TrackBase.h
+static inline constexpr int CBLK_STATE_IDLE = 0;
+static inline constexpr int CBLK_STATE_PAUSING = 7;
+
+/**
+ * MirroredVariable is a local variable which simultaneously updates
+ * a mirrored storage location. This is useful for server side variables
+ * where a local copy is kept, but a client visible copy is offered through shared memory.
+ *
+ * We use std::atomic as the default container class to access this memory.
+ */
+template <typename T, template <typename> class Container = std::atomic>
+class MirroredVariable {
+ template <typename C>
+ struct Constraints {
+ // If setMirror is used with a different type U != T passed in,
+ // as a general rule, the Container must issue a memcpy to read or write
+ // (or its equivalent) to avoid possible strict aliasing issues.
+ // The memcpy also avoids gaps in structs and alignment issues with different types.
+ static constexpr bool ok_ = false; // Containers must specify constraints.
+ };
+ template <typename X>
+ struct Constraints<std::atomic<X>> {
+ // Atomics force read and write to memory.
+ static constexpr bool ok = std::is_same_v<X, T> ||
+ (std::atomic<X>::is_always_lock_free // no additional locking
+ && sizeof(std::atomic<X>) == sizeof(X) // layout identical to X.
+ && (std::is_arithmetic_v<X> || std::is_enum_v<X>)); // No gaps in the layout.
+ };
+
+static_assert(Constraints<Container<T>>::ok);
+public:
+ explicit MirroredVariable(const T& t) : t_{t} {}
+
+ // implicit conversion operator
+ operator T() const {
+ return t_;
+ }
+
+ MirroredVariable& operator=(const T& t) {
+ t_ = t;
+ if (mirror_ != nullptr) {
+ *mirror_ = t;
+ }
+ return *this;
+ }
+
+ template <typename U>
+ void setMirror(Container<U> *other_mirror) {
+ // Much of the concern is with T != U, however there are additional concerns
+ // when storage uses shared memory between processes. For atomics, it must be
+ // lock free.
+ static_assert(sizeof(U) == sizeof(T));
+ static_assert(alignof(U) == alignof(T));
+ static_assert(Constraints<Container<U>>::ok);
+ static_assert(sizeof(Container<U>) == sizeof(Container<T>));
+ static_assert(alignof(Container<U>) == alignof(Container<T>));
+ auto mirror = reinterpret_cast<Container<T>*>(other_mirror);
+ if (mirror_ != mirror) {
+ mirror_ = mirror;
+ if (mirror != nullptr) {
+ *mirror = t_;
+ }
+ }
+ }
+
+ void clear() {
+ mirror_ = nullptr;
+ }
+
+ MirroredVariable& operator&() const = delete;
+
+protected:
+ T t_{};
+ Container<T>* mirror_ = nullptr;
+};
+
struct AudioTrackSharedStreaming {
// similar to NBAIO MonoPipe
// in continuously incrementing frame units, take modulo buffer size, which must be a power of 2
@@ -188,6 +265,8 @@
volatile int32_t mFlags; // combinations of CBLK_*
+ std::atomic<int32_t> mState; // current TrackBase state.
+
public:
union {
AudioTrackSharedStreaming mStreaming;
@@ -198,6 +277,9 @@
// Cache line boundary (32 bytes)
};
+// TODO: ensure standard layout.
+// static_assert(std::is_standard_layout_v<audio_track_cblk_t>);
+
// ----------------------------------------------------------------------------
// Proxy for shared memory control block, to isolate callers from needing to know the details.
@@ -323,6 +405,7 @@
return mEpoch;
}
+ int32_t getState() const { return mCblk->mState; }
uint32_t getBufferSizeInFrames() const { return mBufferSizeInFrames; }
// See documentation for AudioTrack::setBufferSizeInFrames()
uint32_t setBufferSizeInFrames(uint32_t requestedSize);
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 5f802de..3bc666b 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -21,6 +21,7 @@
#include <inttypes.h>
#include <math.h>
#include <sys/resource.h>
+#include <thread>
#include <android/media/IAudioPolicyService.h>
#include <android-base/macros.h>
@@ -947,6 +948,44 @@
mAudioTrack->flush();
}
+bool AudioTrack::pauseAndWait(const std::chrono::milliseconds& timeout)
+{
+ using namespace std::chrono_literals;
+
+ pause();
+
+ AutoMutex lock(mLock);
+ // offload and direct tracks do not wait because pause volume ramp is handled by hardware.
+ if (isOffloadedOrDirect_l()) return true;
+
+ // Wait for the track state to be anything besides pausing.
+ // This ensures that the volume has ramped down.
+ constexpr auto SLEEP_INTERVAL_MS = 10ms;
+ auto begin = std::chrono::steady_clock::now();
+ while (true) {
+ // wait for state to change
+ const int state = mProxy->getState();
+
+ mLock.unlock(); // only local variables accessed until lock.
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - begin);
+ if (state != CBLK_STATE_PAUSING) {
+ ALOGV("%s: success state:%d after %lld ms", __func__, state, elapsed.count());
+ return true;
+ }
+ std::chrono::milliseconds remaining = timeout - elapsed;
+ if (remaining.count() <= 0) {
+ ALOGW("%s: timeout expired state:%d still pausing:%d after %lld ms",
+ __func__, state, CBLK_STATE_PAUSING, elapsed.count());
+ return false;
+ }
+ // It is conceivable that the track is restored while sleeping;
+ // as this logic is advisory, we allow that.
+ std::this_thread::sleep_for(std::min(remaining, SLEEP_INTERVAL_MS));
+ mLock.lock();
+ }
+}
+
void AudioTrack::pause()
{
const int64_t beginNs = systemTime();
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index cb00990..6b592cb 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -28,6 +28,7 @@
#include <utils/threads.h>
#include <android/content/AttributionSourceState.h>
+#include <chrono>
#include <string>
#include "android/media/BnAudioTrackCallback.h"
@@ -510,6 +511,14 @@
*/
void pause();
+ /* Pause and wait (with timeout) for the audio track to ramp to silence.
+ *
+ * \param timeout is the time limit to wait before returning.
+ * A negative number is treated as 0.
+ * \return true if the track is ramped to silence, false if the timeout occurred.
+ */
+ bool pauseAndWait(const std::chrono::milliseconds& timeout);
+
/* Set volume for this track, mostly used for games' sound effects
* left and right volumes. Levels must be >= 0.0 and <= 1.0.
* This is the older API. New applications should use setVolume(float) when possible.
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index d278a01..f85887e 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -21,6 +21,7 @@
#define LOG_TAG "MediaPlayerService"
#include <utils/Log.h>
+#include <chrono>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
@@ -2467,8 +2468,13 @@
void MediaPlayerService::AudioOutput::pause()
{
ALOGV("pause");
+ // We use pauseAndWait() instead of pause() to ensure tracks ramp to silence before
+ // any flush. We choose 40 ms timeout to allow 1 deep buffer mixer period
+ // to occur. Often waiting is 0 - 20 ms.
+ using namespace std::chrono_literals;
+ constexpr auto TIMEOUT_MS = 40ms;
Mutex::Autolock lock(mLock);
- if (mTrack != 0) mTrack->pause();
+ if (mTrack != 0) mTrack->pauseAndWait(TIMEOUT_MS);
}
void MediaPlayerService::AudioOutput::close()
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index b9cdab8..746d875 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -5089,7 +5089,7 @@
break;
case TrackBase::IDLE:
default:
- LOG_ALWAYS_FATAL("unexpected track state %d", track->mState);
+ LOG_ALWAYS_FATAL("unexpected track state %d", (int)track->mState);
}
if (isActive) {
@@ -5148,7 +5148,7 @@
// TODO Remove the ALOGW when this theory is confirmed.
ALOGW("fast track %d should have been active; "
"mState=%d, mTrackMask=%#x, recentUnderruns=%u, isShared=%d",
- j, track->mState, state->mTrackMask, recentUnderruns,
+ j, (int)track->mState, state->mTrackMask, recentUnderruns,
track->sharedBuffer() != 0);
// Since the FastMixer state already has the track inactive, do nothing here.
}
@@ -8041,7 +8041,7 @@
ALOGV("active record track PAUSING -> ACTIVE");
recordTrack->mState = TrackBase::ACTIVE;
} else {
- ALOGV("active record track state %d", recordTrack->mState);
+ ALOGV("active record track state %d", (int)recordTrack->mState);
}
return status;
}
@@ -8067,7 +8067,7 @@
}
if (recordTrack->mState != TrackBase::STARTING_1) {
ALOGW("%s(%d): unsynchronized mState:%d change",
- __func__, recordTrack->id(), recordTrack->mState);
+ __func__, recordTrack->id(), (int)recordTrack->mState);
// Someone else has changed state, let them take over,
// leave mState in the new state.
recordTrack->clearSyncStartEvent();
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 92f129c..5311fe2 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -23,7 +23,7 @@
class TrackBase : public ExtendedAudioBufferProvider, public RefBase {
public:
- enum track_state {
+ enum track_state : int32_t {
IDLE,
FLUSHED, // for PlaybackTracks only
STOPPED,
@@ -271,6 +271,7 @@
void releaseCblk() {
if (mCblk != nullptr) {
+ mState.clear();
mCblk->~audio_track_cblk_t(); // destroy our shared-structure.
if (mClient == 0) {
free(mCblk);
@@ -355,7 +356,7 @@
// except for OutputTrack when it is in local memory
size_t mBufferSize; // size of mBuffer in bytes
// we don't really need a lock for these
- track_state mState;
+ MirroredVariable<track_state> mState;
const audio_attributes_t mAttr;
const uint32_t mSampleRate; // initial sample rate only; for tracks which
// support dynamic rates, the current value is in control block
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index d2a30b1..e0c5fa5 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -234,7 +234,11 @@
#ifdef TEE_SINK
mTee.set(sampleRate, mChannelCount, format, NBAIO_Tee::TEE_FLAG_TRACK);
#endif
-
+ // mState is mirrored for the client to read.
+ mState.setMirror(&mCblk->mState);
+ // ensure our state matches up until we consolidate the enumeration.
+ static_assert(CBLK_STATE_IDLE == IDLE);
+ static_assert(CBLK_STATE_PAUSING == PAUSING);
}
}
@@ -933,7 +937,7 @@
buffer->raw = buf.mRaw;
if (buf.mFrameCount == 0 && !isStopping() && !isStopped() && !isPaused() && !isOffloaded()) {
ALOGV("%s(%d): underrun, framesReady(%zu) < framesDesired(%zd), state: %d",
- __func__, mId, buf.mFrameCount, desiredFrames, mState);
+ __func__, mId, buf.mFrameCount, desiredFrames, (int)mState);
mAudioTrackServerProxy->tallyUnderrunFrames(desiredFrames);
} else {
mAudioTrackServerProxy->tallyUnderrunFrames(0);
@@ -1590,7 +1594,7 @@
(mState == STOPPED)))) {
ALOGW("%s(%d): in invalid state %d on session %d %s mode, framesReady %zu",
__func__, mId,
- mState, mSessionId, (mSharedBuffer != 0) ? "static" : "stream", framesReady());
+ (int)mState, mSessionId, (mSharedBuffer != 0) ? "static" : "stream", framesReady());
event->cancel();
return INVALID_OPERATION;
}