Loading audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl +15 −1 Original line number Diff line number Diff line Loading @@ -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, /** Loading Loading @@ -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, } Loading audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv +56 −5 Original line number Diff line number Diff line Loading @@ -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; Loading audio/aidl/default/Module.cpp +5 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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 << "\""; Loading audio/aidl/default/Stream.cpp +16 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading audio/aidl/default/include/core-impl/Module.h +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl +15 −1 Original line number Diff line number Diff line Loading @@ -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, /** Loading Loading @@ -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, } Loading
audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv +56 −5 Original line number Diff line number Diff line Loading @@ -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; Loading
audio/aidl/default/Module.cpp +5 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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 << "\""; Loading
audio/aidl/default/Stream.cpp +16 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading
audio/aidl/default/include/core-impl/Module.h +1 −0 Original line number Diff line number Diff line Loading @@ -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