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

Commit d3bd8f9a authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "audio: Add initial support for clip transition reporting" into main

parents c45da27a 1b8f65d4
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -188,6 +188,14 @@ parcelable StreamDescriptor {
         * In the 'DRAINING' state the producer is inactive, the consumer is
         * finishing up on the buffer contents, emptying it up. As soon as it
         * gets empty, the stream transfers itself into the next state.
         *
         * Note that "early notify" draining is a more complex procedure
         * intended for transitioning between two clips. Both 'DRAINING' and
         * 'DRAIN_PAUSED' states have "sub-states" not visible via the API. See
         * the details in the 'stream-out-async-sm.gv' state machine
         * description. In the HAL API V3 this behavior is enabled when the
         * HAL exposes "aosp.clipTransitionSupport" property, and in the HAL
         * API V4 it is the default behavior.
         */
        DRAINING = 5,
        /**
@@ -234,9 +242,15 @@ parcelable StreamDescriptor {
        /**
         * Used with output streams only, the HAL module indicates drain
         * completion shortly before all audio data has been consumed in order
         * to give the client an opportunity to provide data for the next track
         * to give the client an opportunity to provide data for the next clip
         * for gapless playback. The exact amount of provided time is specific
         * to the HAL implementation.
         *
         * In the HAL API V3, the HAL sends two 'onDrainReady' notifications:
         * one to indicate readiness to receive next clip data, and another when
         * the previous clip has finished playing. This behavior is enabled when
         * the HAL exposes "aosp.clipTransitionSupport" property, and in the HAL
         * API V4 it is the default behavior.
         */
        DRAIN_EARLY_NOTIFY = 2,
    }
+56 −5
Original line number Diff line number Diff line
@@ -32,27 +32,78 @@ digraph stream_out_async_state_machine {
    IDLE -> TRANSFERRING [label="burst"];             // producer -> active
    IDLE -> ACTIVE [label="burst"];                   // full write
    ACTIVE -> PAUSED [label="pause"];                 // consumer -> passive (not consuming)
    ACTIVE -> DRAINING [label="drain"];               // producer -> passive
    ACTIVE -> DRAINING [label="drain(ALL)"];          // producer -> passive
    ACTIVE -> DRAINING_en [label="drain(EARLY_NOTIFY)"];  // prepare for clip transition
    ACTIVE -> TRANSFERRING [label="burst"];           // early unblocking
    ACTIVE -> ACTIVE [label="burst"];                 // full write
    TRANSFERRING -> ACTIVE [label="←IStreamCallback.onTransferReady"];
    TRANSFERRING -> TRANSFER_PAUSED [label="pause"];  // consumer -> passive (not consuming)
    TRANSFERRING -> DRAINING [label="drain"];         // producer -> passive
    TRANSFERRING -> DRAINING [label="drain(ALL)"];    // producer -> passive
    TRANSFERRING -> DRAINING_en [label="drain(EARLY_NOTIFY)"]; // prepare for clip transition
    TRANSFER_PAUSED -> TRANSFERRING [label="start"];  // consumer -> active
    TRANSFER_PAUSED -> DRAIN_PAUSED [label="drain"];  // producer -> passive
    TRANSFER_PAUSED -> DRAIN_PAUSED [label="drain(ALL)"];  // producer -> passive
    TRANSFER_PAUSED -> IDLE [label="flush"];          // buffer is cleared
    PAUSED -> PAUSED [label="burst"];
    PAUSED -> ACTIVE [label="start"];                 // consumer -> active
    PAUSED -> IDLE [label="flush"];                   // producer -> passive, buffer is cleared
    DRAINING -> IDLE [label="←IStreamCallback.onDrainReady"];
    DRAINING -> DRAINING [label="←IStreamCallback.onDrainReady"];  // allowed for `DRAIN_EARLY_NOTIFY`
    DRAINING -> IDLE [label="<empty buffer>"];        // allowed for `DRAIN_EARLY_NOTIFY`
    DRAINING -> TRANSFERRING [label="burst"];         // producer -> active
    DRAINING -> ACTIVE [label="burst"];               // full write
    DRAINING -> DRAIN_PAUSED [label="pause"];         // consumer -> passive (not consuming)
    DRAIN_PAUSED -> DRAINING [label="start"];         // consumer -> active
    DRAIN_PAUSED -> TRANSFER_PAUSED [label="burst"];  // producer -> active
    DRAIN_PAUSED -> IDLE [label="flush"];             // buffer is cleared
    // Note that the states in both clusters are combined with 'DRAINING' and 'DRAIN_PAUSED'
    // state at the API level. The 'en' and 'en_sent' attributes only belong to the internal
    // state of the stream and are not observable outside.
    subgraph cluster_early_notify_entering {
        // The stream is preparing for a transition between two clips. After
        // receiving 'drain(EARLY_NOTIFY)' command, the stream continues playing
        // the current clip, and at some point notifies the client that it is
        // ready for the next clip data by issuing the first 'onDrainReady'
        // callback.
        label="EARLY_NOTIFY (entering)";
        color=gray;
        // Getting 'burst' or 'flush' command in these states resets the "clip
        // transition" mode.
        DRAINING_en;
        DRAIN_PAUSED_en;
    }
    subgraph cluster_early_notify_notification_sent {
        // After the stream has sent "onDrainReady", the client can now send
        // 'burst' commands with the data of the next clip. These 'bursts' are
        // always "early unblocking" because the previous clip is still playing
        // thus the stream is unable to play any of the received data
        // synchronously (in other words, it can not do a "full write"). To
        // indicate readiness to accept the next burst the stream uses the usual
        // 'onTransferReady' callback.
        label="EARLY_NOTIFY (notification sent)";
        color=gray;
        // The state machine remains in these states until the current clip ends
        // playing. When it ends, the stream sends 'onDrainReady' (note that
        // it's the second 'onDrainReady' for the same 'drain(EARLY_NOTIFY)'),
        // and transitions either to 'IDLE' if there is no data for the next
        // clip, or to 'TRANSFERRING' otherwise. Note that it can not transition
        // to 'ACTIVE' because that transition is associated with
        // 'onTransferReady' callback.
        DRAINING_en_sent;
        DRAIN_PAUSED_en_sent;
    }
    DRAINING_en -> TRANSFERRING [label="burst"];                  // producer -> active
    DRAINING_en -> ACTIVE [label="burst"];                        // full write
    DRAINING_en -> DRAIN_PAUSED_en [label="pause"];               // consumer -> passive (not consuming)
    DRAINING_en -> DRAINING_en_sent [label="←IStreamCallback.onDrainReady"];
    DRAIN_PAUSED_en -> DRAINING_en [label="start"];               // consumer -> active
    DRAIN_PAUSED_en -> TRANSFER_PAUSED [label="burst"];           // producer -> active
    DRAIN_PAUSED_en -> IDLE [label="flush"];                      // buffer is cleared
    DRAINING_en_sent -> DRAINING_en_sent [label="burst"];
    DRAINING_en_sent -> DRAINING_en_sent [label="←IStreamCallback.onTransferReady"];
    DRAINING_en_sent -> DRAIN_PAUSED_en_sent [label="pause"];     // consumer -> passive (not consuming)
    DRAINING_en_sent -> TRANSFERRING [label="←IStreamCallback.onDrainReady"];
    DRAINING_en_sent -> IDLE [label="←IStreamCallback.onDrainReady"];
    DRAIN_PAUSED_en_sent -> DRAINING_en_sent [label="start"];     // consumer -> active
    DRAIN_PAUSED_en_sent -> DRAIN_PAUSED_en_sent [label="burst"]; // producer -> active
    DRAIN_PAUSED_en_sent -> IDLE [label="flush"];                 // buffer is cleared
    ANY_STATE -> ERROR [label="←IStreamCallback.onError"];
    ANY_STATE -> CLOSED [label="→IStream*.close"];
    CLOSED -> F;
+5 −0
Original line number Diff line number Diff line
@@ -1555,6 +1555,7 @@ ndk::ScopedAStatus Module::generateHwAvSyncId(int32_t* _aidl_return) {

const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
const std::string Module::kClipTransitionSupportName = "aosp.clipTransitionSupport";

ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
                                               std::vector<VendorParameter>* _aidl_return) {
@@ -1569,6 +1570,10 @@ ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& i
            VendorParameter forceSynchronousDrain{.id = id};
            forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
            _aidl_return->push_back(std::move(forceSynchronousDrain));
        } else if (id == kClipTransitionSupportName) {
            VendorParameter clipTransitionSupport{.id = id};
            clipTransitionSupport.ext.setParcelable(Boolean{true});
            _aidl_return->push_back(std::move(clipTransitionSupport));
        } else {
            allParametersKnown = false;
            LOG(VERBOSE) << __func__ << ": " << mType << ": unrecognized parameter \"" << id << "\"";
+16 −6
Original line number Diff line number Diff line
@@ -406,12 +406,16 @@ const std::string StreamOutWorkerLogic::kThreadName = "writer";

void StreamOutWorkerLogic::onBufferStateChange(size_t bufferFramesLeft) {
    const StreamDescriptor::State state = mState;
    LOG(DEBUG) << __func__ << ": state: " << toString(state)
    const DrainState drainState = mDrainState;
    LOG(DEBUG) << __func__ << ": state: " << toString(state) << ", drainState: " << drainState
               << ", bufferFramesLeft: " << bufferFramesLeft;
    if (state == StreamDescriptor::State::TRANSFERRING || drainState == DrainState::EN_SENT) {
        if (state == StreamDescriptor::State::TRANSFERRING) {
            mState = StreamDescriptor::State::ACTIVE;
        }
        std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
        if (asyncCallback != nullptr) {
            LOG(VERBOSE) << __func__ << ": sending onTransferReady";
            ndk::ScopedAStatus status = asyncCallback->onTransferReady();
            if (!status.isOk()) {
                LOG(ERROR) << __func__ << ": error from onTransferReady: " << status;
@@ -430,8 +434,10 @@ void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNext
        mState =
                hasNextClip ? StreamDescriptor::State::TRANSFERRING : StreamDescriptor::State::IDLE;
        mDrainState = DrainState::NONE;
        if (drainState == DrainState::ALL && asyncCallback != nullptr) {
        if ((drainState == DrainState::ALL || drainState == DrainState::EN_SENT) &&
            asyncCallback != nullptr) {
            LOG(DEBUG) << __func__ << ": sending onDrainReady";
            // For EN_SENT, this is the second onDrainReady which notifies about clip transition.
            ndk::ScopedAStatus status = asyncCallback->onDrainReady();
            if (!status.isOk()) {
                LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
@@ -559,13 +565,17 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
                            mState = StreamDescriptor::State::TRANSFER_PAUSED;
                        }
                    } else if (mState == StreamDescriptor::State::IDLE ||
                               mState == StreamDescriptor::State::DRAINING ||
                               mState == StreamDescriptor::State::ACTIVE) {
                               mState == StreamDescriptor::State::ACTIVE ||
                               (mState == StreamDescriptor::State::DRAINING &&
                                mDrainState != DrainState::EN_SENT)) {
                        if (asyncCallback == nullptr || reply.fmqByteCount == fmqByteCount) {
                            mState = StreamDescriptor::State::ACTIVE;
                        } else {
                            switchToTransientState(StreamDescriptor::State::TRANSFERRING);
                        }
                    } else if (mState == StreamDescriptor::State::DRAINING &&
                               mDrainState == DrainState::EN_SENT) {
                        // keep mState
                    }
                } else {
                    populateReplyWrongState(&reply, command);
+1 −0
Original line number Diff line number Diff line
@@ -161,6 +161,7 @@ class Module : public BnModule {
    // Multimap because both ports and configs can be used by multiple patches.
    using Patches = std::multimap<int32_t, int32_t>;

    static const std::string kClipTransitionSupportName;
    const Type mType;
    std::unique_ptr<Configuration> mConfig;
    ModuleDebug mDebug;
Loading