Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ca39706b authored by Andy Hung's avatar Andy Hung Committed by Android (Google) Code Review
Browse files

Merge "AudioTrack: Improve pause handling." into sc-qpr1-dev

parents d18a2c7d 959b5b8a
Loading
Loading
Loading
Loading
+83 −0
Original line number Diff line number Diff line
@@ -53,6 +53,83 @@ namespace android {
//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 @@ public:

    volatile    int32_t     mFlags;         // combinations of CBLK_*

                std::atomic<int32_t>  mState; // current TrackBase state.

public:
                union {
                    AudioTrackSharedStreaming   mStreaming;
@@ -198,6 +277,9 @@ public:
                // 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 @@ public:
        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);
+39 −0
Original line number Diff line number Diff line
@@ -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 @@ void AudioTrack::flush_l()
    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();
+9 −0
Original line number Diff line number Diff line
@@ -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 @@ public:
     */
            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.
+7 −1
Original line number Diff line number Diff line
@@ -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::flush()
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()
+4 −4
Original line number Diff line number Diff line
@@ -5089,7 +5089,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
                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 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTrac
                    // 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 @@ status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrac
                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 @@ status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrac
            }
            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();
Loading