Merge "audio: Add initial support for clip transition reporting" into main am: d3bd8f9a8a
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/3474372
Change-Id: Ia62f1ff49aef995b8dcb6d0142f48466cf801835
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
index cfe001e..9b7fea2 100644
--- a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
+++ b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
@@ -188,6 +188,14 @@
* 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 @@
/**
* 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,
}
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
index e2da90d..bf75594 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
@@ -32,27 +32,78 @@
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;
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 6a92481..123a5ec 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -1555,6 +1555,7 @@
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 @@
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 << "\"";
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 762c75e..2800bed 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -406,12 +406,16 @@
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) {
- mState = StreamDescriptor::State::ACTIVE;
+ 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 @@
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 @@
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);
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 6d59028..0661015 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -161,6 +161,7 @@
// 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;
diff --git a/audio/aidl/default/include/core-impl/StreamOffloadStub.h b/audio/aidl/default/include/core-impl/StreamOffloadStub.h
index 67abe95..24e98c2 100644
--- a/audio/aidl/default/include/core-impl/StreamOffloadStub.h
+++ b/audio/aidl/default/include/core-impl/StreamOffloadStub.h
@@ -26,14 +26,16 @@
namespace aidl::android::hardware::audio::core {
struct DspSimulatorState {
+ static constexpr int64_t kSkipBufferNotifyFrames = -1;
+
const std::string formatEncoding;
const int sampleRate;
const int64_t earlyNotifyFrames;
- const int64_t bufferNotifyFrames;
DriverCallbackInterface* callback = nullptr; // set before starting DSP worker
std::mutex lock;
std::vector<int64_t> clipFramesLeft GUARDED_BY(lock);
- int64_t bufferFramesLeft GUARDED_BY(lock);
+ int64_t bufferFramesLeft GUARDED_BY(lock) = 0;
+ int64_t bufferNotifyFrames GUARDED_BY(lock) = kSkipBufferNotifyFrames;
};
class DspSimulatorLogic : public ::android::hardware::audio::common::StreamLogic {
@@ -68,6 +70,7 @@
private:
::android::status_t startWorkerIfNeeded();
+ const int64_t mBufferNotifyFrames;
DspSimulatorState mState;
DspSimulatorWorker mDspWorker;
bool mDspWorkerStarted = false;
diff --git a/audio/aidl/default/stub/StreamOffloadStub.cpp b/audio/aidl/default/stub/StreamOffloadStub.cpp
index 95cef35..155f76d 100644
--- a/audio/aidl/default/stub/StreamOffloadStub.cpp
+++ b/audio/aidl/default/stub/StreamOffloadStub.cpp
@@ -42,14 +42,13 @@
const int64_t clipFramesPlayed =
(::android::uptimeNanos() - timeBeginNs) * mSharedState.sampleRate / NANOS_PER_SECOND;
const int64_t bufferFramesConsumed = clipFramesPlayed / 2; // assume 1:2 compression ratio
- int64_t bufferFramesLeft = 0;
+ int64_t bufferFramesLeft = 0, bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
{
std::lock_guard l(mSharedState.lock);
mSharedState.bufferFramesLeft =
mSharedState.bufferFramesLeft > bufferFramesConsumed
? mSharedState.bufferFramesLeft - bufferFramesConsumed
: 0;
- bufferFramesLeft = mSharedState.bufferFramesLeft;
int64_t framesPlayed = clipFramesPlayed;
while (framesPlayed > 0 && !mSharedState.clipFramesLeft.empty()) {
LOG(VERBOSE) << __func__ << ": clips: "
@@ -65,10 +64,21 @@
clipNotifies.emplace_back(0 /*clipFramesLeft*/, hasNextClip);
framesPlayed -= mSharedState.clipFramesLeft[0];
mSharedState.clipFramesLeft.erase(mSharedState.clipFramesLeft.begin());
+ if (!hasNextClip) {
+ // Since it's a simulation, the buffer consumption rate it not real,
+ // thus 'bufferFramesLeft' might still have something, need to erase it.
+ mSharedState.bufferFramesLeft = 0;
+ }
}
}
+ bufferFramesLeft = mSharedState.bufferFramesLeft;
+ bufferNotifyFrames = mSharedState.bufferNotifyFrames;
+ if (bufferFramesLeft <= bufferNotifyFrames) {
+ // Suppress further notifications.
+ mSharedState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
+ }
}
- if (bufferFramesLeft <= mSharedState.bufferNotifyFrames) {
+ if (bufferFramesLeft <= bufferNotifyFrames) {
LOG(DEBUG) << __func__ << ": sending onBufferStateChange: " << bufferFramesLeft;
mSharedState.callback->onBufferStateChange(bufferFramesLeft);
}
@@ -82,9 +92,9 @@
DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
: DriverStubImpl(context, 0 /*asyncSleepTimeUs*/),
+ mBufferNotifyFrames(static_cast<int64_t>(context.getBufferSizeInFrames()) / 2),
mState{context.getFormat().encoding, context.getSampleRate(),
- 250 /*earlyNotifyMs*/ * context.getSampleRate() / MILLIS_PER_SECOND,
- static_cast<int64_t>(context.getBufferSizeInFrames()) / 2},
+ 250 /*earlyNotifyMs*/ * context.getSampleRate() / MILLIS_PER_SECOND},
mDspWorker(mState) {
LOG_IF(FATAL, !mIsAsynchronous) << "The steam must be used in asynchronous mode";
}
@@ -111,6 +121,7 @@
mState.clipFramesLeft.resize(1);
}
}
+ mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
return ::android::OK;
}
@@ -121,6 +132,7 @@
std::lock_guard l(mState.lock);
mState.clipFramesLeft.clear();
mState.bufferFramesLeft = 0;
+ mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
}
return ::android::OK;
}
@@ -128,6 +140,10 @@
::android::status_t DriverOffloadStubImpl::pause() {
RETURN_STATUS_IF_ERROR(DriverStubImpl::pause());
mDspWorker.pause();
+ {
+ std::lock_guard l(mState.lock);
+ mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
+ }
return ::android::OK;
}
@@ -140,6 +156,7 @@
hasClips = !mState.clipFramesLeft.empty();
LOG(DEBUG) << __func__
<< ": clipFramesLeft: " << ::android::internal::ToString(mState.clipFramesLeft);
+ mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
}
if (hasClips) {
mDspWorker.resume();
@@ -184,6 +201,7 @@
{
std::lock_guard l(mState.lock);
mState.bufferFramesLeft = *actualFrameCount;
+ mState.bufferNotifyFrames = mBufferNotifyFrames;
}
mDspWorker.resume();
return ::android::OK;
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 21b7aff..806c93f 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -1025,6 +1025,7 @@
auto [eventSeq, event] = mEventReceiver->waitForEvent(mLastEventSeq);
mLastEventSeq = eventSeq;
if (event != *expEvent) {
+ // TODO: Make available as an error so it can be displayed by GTest
LOG(ERROR) << __func__ << ": expected event " << toString(*expEvent) << ", got "
<< toString(event);
return {};
@@ -1327,7 +1328,8 @@
public:
// To avoid timing out the whole test suite in case no event is received
// from the HAL, use a local timeout for event waiting.
- static constexpr auto kEventTimeoutMs = std::chrono::milliseconds(1000);
+ // TODO: The timeout for 'onTransferReady' should depend on the buffer size.
+ static constexpr auto kEventTimeoutMs = std::chrono::milliseconds(3000);
StreamEventReceiver* getEventReceiver() { return this; }
std::tuple<int, Event> getLastEvent() const override {
@@ -4236,15 +4238,17 @@
enum {
NAMED_CMD_NAME,
NAMED_CMD_MIN_INTERFACE_VERSION,
+ NAMED_CMD_FEATURE_PROPERTY,
NAMED_CMD_DELAY_MS,
NAMED_CMD_STREAM_TYPE,
NAMED_CMD_CMDS,
NAMED_CMD_VALIDATE_POS_INCREASE
};
-enum class StreamTypeFilter { ANY, SYNC, ASYNC };
+enum class StreamTypeFilter { ANY, SYNC, ASYNC, OFFLOAD };
using NamedCommandSequence =
- std::tuple<std::string, int /*minInterfaceVersion*/, int /*cmdDelayMs*/, StreamTypeFilter,
- std::shared_ptr<StateSequence>, bool /*validatePositionIncrease*/>;
+ std::tuple<std::string, int /*minInterfaceVersion*/, std::string /*featureProperty*/,
+ int /*cmdDelayMs*/, StreamTypeFilter, std::shared_ptr<StateSequence>,
+ bool /*validatePositionIncrease*/>;
enum { PARAM_MODULE_NAME, PARAM_CMD_SEQ, PARAM_SETUP_SEQ };
using StreamIoTestParameters =
std::tuple<std::string /*moduleName*/, NamedCommandSequence, bool /*useSetupSequence2*/>;
@@ -4255,11 +4259,24 @@
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(SetUpImpl(std::get<PARAM_MODULE_NAME>(GetParam())));
ASSERT_GE(aidlVersion, kAidlVersion1);
- if (const int minVersion =
- std::get<NAMED_CMD_MIN_INTERFACE_VERSION>(std::get<PARAM_CMD_SEQ>(GetParam()));
- aidlVersion < minVersion) {
+ const int minVersion =
+ std::get<NAMED_CMD_MIN_INTERFACE_VERSION>(std::get<PARAM_CMD_SEQ>(GetParam()));
+ if (aidlVersion < minVersion) {
GTEST_SKIP() << "Skip for audio HAL version lower than " << minVersion;
}
+ // When an associated feature property is defined, need to check that either that the HAL
+ // exposes this property, or it's of the version 'NAMED_CMD_MIN_INTERFACE_VERSION' + 1
+ // which must have this functionality implemented by default.
+ if (const std::string featureProperty =
+ std::get<NAMED_CMD_FEATURE_PROPERTY>(std::get<PARAM_CMD_SEQ>(GetParam()));
+ !featureProperty.empty() && aidlVersion < (minVersion + 1)) {
+ std::vector<VendorParameter> parameters;
+ ScopedAStatus result = module->getVendorParameters({featureProperty}, ¶meters);
+ if (!result.isOk() || parameters.size() != 1) {
+ GTEST_SKIP() << "Skip as audio HAL does not support feature \"" << featureProperty
+ << "\"";
+ }
+ }
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
}
@@ -4300,10 +4317,18 @@
isBitPositionFlagSet(portConfig.flags.value()
.template get<AudioIoFlags::Tag::output>(),
AudioOutputFlags::NON_BLOCKING);
+ const bool isOffload =
+ IOTraits<Stream>::is_input
+ ? false
+ : isBitPositionFlagSet(
+ portConfig.flags.value()
+ .template get<AudioIoFlags::Tag::output>(),
+ AudioOutputFlags::COMPRESS_OFFLOAD);
if (auto streamType =
std::get<NAMED_CMD_STREAM_TYPE>(std::get<PARAM_CMD_SEQ>(GetParam()));
(isNonBlocking && streamType == StreamTypeFilter::SYNC) ||
- (!isNonBlocking && streamType == StreamTypeFilter::ASYNC)) {
+ (!isNonBlocking && streamType == StreamTypeFilter::ASYNC) ||
+ (!isOffload && streamType == StreamTypeFilter::OFFLOAD)) {
continue;
}
WithDebugFlags delayTransientStates = WithDebugFlags::createNested(*debug);
@@ -4760,6 +4785,18 @@
// TODO: Add async test cases for input once it is implemented.
+// Allow optional routing via the TRANSFERRING state on bursts.
+StateDag::Node makeAsyncBurstCommands(StateDag* d, size_t burstCount, StateDag::Node last) {
+ using State = StreamDescriptor::State;
+ std::reference_wrapper<std::remove_reference_t<StateDag::Node>> prev = last;
+ for (size_t i = 0; i < burstCount; ++i) {
+ StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, prev);
+ active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, prev));
+ prev = active;
+ }
+ return prev;
+}
+
std::shared_ptr<StateSequence> makeBurstCommands(bool isSync, size_t burstCount,
bool standbyInputWhenDone) {
using State = StreamDescriptor::State;
@@ -4776,25 +4813,21 @@
d->makeNodes(State::ACTIVE, kBurstCommand, burstCount, last));
d->makeNode(State::STANDBY, kStartCommand, idle);
} else {
- StateDag::Node active2 = d->makeNode(State::ACTIVE, kBurstCommand, last);
- StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, active2);
+ StateDag::Node active = makeAsyncBurstCommands(d.get(), burstCount, last);
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
- // Allow optional routing via the TRANSFERRING state on bursts.
- active2.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, last));
- active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active2));
idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
d->makeNode(State::STANDBY, kStartCommand, idle);
}
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kReadSeq =
- std::make_tuple(std::string("Read"), kAidlVersion1, 0, StreamTypeFilter::ANY,
+ std::make_tuple(std::string("Read"), kAidlVersion1, "", 0, StreamTypeFilter::ANY,
makeBurstCommands(true), true /*validatePositionIncrease*/);
static const NamedCommandSequence kWriteSyncSeq =
- std::make_tuple(std::string("Write"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
+ std::make_tuple(std::string("Write"), kAidlVersion1, "", 0, StreamTypeFilter::SYNC,
makeBurstCommands(true), true /*validatePositionIncrease*/);
static const NamedCommandSequence kWriteAsyncSeq =
- std::make_tuple(std::string("Write"), kAidlVersion1, 0, StreamTypeFilter::ASYNC,
+ std::make_tuple(std::string("Write"), kAidlVersion1, "", 0, StreamTypeFilter::ASYNC,
makeBurstCommands(false), true /*validatePositionIncrease*/);
std::shared_ptr<StateSequence> makeAsyncDrainCommands(bool isInput) {
@@ -4824,10 +4857,10 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kWriteDrainAsyncSeq = std::make_tuple(
- std::string("WriteDrain"), kAidlVersion1, kStreamTransientStateTransitionDelayMs,
+ std::string("WriteDrain"), kAidlVersion1, "", kStreamTransientStateTransitionDelayMs,
StreamTypeFilter::ASYNC, makeAsyncDrainCommands(false), false /*validatePositionIncrease*/);
static const NamedCommandSequence kDrainInSeq =
- std::make_tuple(std::string("Drain"), kAidlVersion1, 0, StreamTypeFilter::ANY,
+ std::make_tuple(std::string("Drain"), kAidlVersion1, "", 0, StreamTypeFilter::ANY,
makeAsyncDrainCommands(true), false /*validatePositionIncrease*/);
std::shared_ptr<StateSequence> makeDrainOutCommands(bool isSync) {
@@ -4849,10 +4882,10 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainOutSyncSeq =
- std::make_tuple(std::string("Drain"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
+ std::make_tuple(std::string("Drain"), kAidlVersion1, "", 0, StreamTypeFilter::SYNC,
makeDrainOutCommands(true), false /*validatePositionIncrease*/);
static const NamedCommandSequence kDrainOutAsyncSeq =
- std::make_tuple(std::string("Drain"), kAidlVersion3, 0, StreamTypeFilter::ASYNC,
+ std::make_tuple(std::string("Drain"), kAidlVersion3, "", 0, StreamTypeFilter::ASYNC,
makeDrainOutCommands(false), false /*validatePositionIncrease*/);
std::shared_ptr<StateSequence> makeDrainEarlyOutCommands() {
@@ -4873,9 +4906,32 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainEarlyOutAsyncSeq =
- std::make_tuple(std::string("DrainEarly"), kAidlVersion3, 0, StreamTypeFilter::ASYNC,
+ std::make_tuple(std::string("DrainEarly"), kAidlVersion3, "", 0, StreamTypeFilter::ASYNC,
makeDrainEarlyOutCommands(), false /*validatePositionIncrease*/);
+// DRAINING_en ->(onDrainReady) DRAINING_en_sent ->(onDrainReady) IDLE | TRANSFERRING
+std::shared_ptr<StateSequence> makeDrainEarlyOffloadCommands() {
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ StateDag::Node lastIdle = d->makeFinalNode(State::IDLE);
+ StateDag::Node lastTransferring = d->makeFinalNode(State::TRANSFERRING);
+ // The second onDrainReady event.
+ StateDag::Node continueDraining =
+ d->makeNode(State::DRAINING, kDrainReadyEvent, lastIdle, lastTransferring);
+ // The first onDrainReady event.
+ StateDag::Node draining = d->makeNode(State::DRAINING, kDrainReadyEvent, continueDraining);
+ StateDag::Node drain = d->makeNode(State::ACTIVE, kDrainOutEarlyCommand, draining);
+ StateDag::Node active = makeAsyncBurstCommands(d.get(), 10, drain);
+ StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+ idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ return std::make_shared<StateSequenceFollower>(std::move(d));
+}
+static const NamedCommandSequence kDrainEarlyOffloadSeq =
+ std::make_tuple(std::string("DrainEarly"), kAidlVersion3, "aosp.clipTransitionSupport", 0,
+ StreamTypeFilter::OFFLOAD, makeDrainEarlyOffloadCommands(),
+ true /*validatePositionIncrease*/);
+
std::shared_ptr<StateSequence> makeDrainPauseOutCommands(bool isSync) {
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
@@ -4896,11 +4952,11 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainPauseOutSyncSeq =
- std::make_tuple(std::string("DrainPause"), kAidlVersion1,
+ std::make_tuple(std::string("DrainPause"), kAidlVersion1, "",
kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC,
makeDrainPauseOutCommands(true), false /*validatePositionIncrease*/);
static const NamedCommandSequence kDrainPauseOutAsyncSeq =
- std::make_tuple(std::string("DrainPause"), kAidlVersion1,
+ std::make_tuple(std::string("DrainPause"), kAidlVersion1, "",
kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
makeDrainPauseOutCommands(false), false /*validatePositionIncrease*/);
@@ -4919,7 +4975,7 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainEarlyPauseOutAsyncSeq =
- std::make_tuple(std::string("DrainEarlyPause"), kAidlVersion3,
+ std::make_tuple(std::string("DrainEarlyPause"), kAidlVersion3, "",
kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
makeDrainEarlyPauseOutCommands(), false /*validatePositionIncrease*/);
@@ -4963,13 +5019,13 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kStandbyInSeq =
- std::make_tuple(std::string("Standby"), kAidlVersion1, 0, StreamTypeFilter::ANY,
+ std::make_tuple(std::string("Standby"), kAidlVersion1, "", 0, StreamTypeFilter::ANY,
makeStandbyCommands(true, false), false /*validatePositionIncrease*/);
static const NamedCommandSequence kStandbyOutSyncSeq =
- std::make_tuple(std::string("Standby"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
+ std::make_tuple(std::string("Standby"), kAidlVersion1, "", 0, StreamTypeFilter::SYNC,
makeStandbyCommands(false, true), false /*validatePositionIncrease*/);
static const NamedCommandSequence kStandbyOutAsyncSeq =
- std::make_tuple(std::string("Standby"), kAidlVersion1,
+ std::make_tuple(std::string("Standby"), kAidlVersion1, "",
kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
makeStandbyCommands(false, false), false /*validatePositionIncrease*/);
@@ -5006,15 +5062,15 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kPauseInSeq =
- std::make_tuple(std::string("Pause"), kAidlVersion1, 0, StreamTypeFilter::ANY,
+ std::make_tuple(std::string("Pause"), kAidlVersion1, "", 0, StreamTypeFilter::ANY,
makePauseCommands(true, false), false /*validatePositionIncrease*/);
static const NamedCommandSequence kPauseOutSyncSeq =
- std::make_tuple(std::string("Pause"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
+ std::make_tuple(std::string("Pause"), kAidlVersion1, "", 0, StreamTypeFilter::SYNC,
makePauseCommands(false, true), false /*validatePositionIncrease*/);
static const NamedCommandSequence kPauseOutAsyncSeq =
- std::make_tuple(std::string("Pause"), kAidlVersion3, kStreamTransientStateTransitionDelayMs,
- StreamTypeFilter::ASYNC, makePauseCommands(false, false),
- false /*validatePositionIncrease*/);
+ std::make_tuple(std::string("Pause"), kAidlVersion3, "",
+ kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
+ makePauseCommands(false, false), false /*validatePositionIncrease*/);
std::shared_ptr<StateSequence> makeFlushCommands(bool isInput, bool isSync) {
using State = StreamDescriptor::State;
@@ -5042,15 +5098,15 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kFlushInSeq =
- std::make_tuple(std::string("Flush"), kAidlVersion1, 0, StreamTypeFilter::ANY,
+ std::make_tuple(std::string("Flush"), kAidlVersion1, "", 0, StreamTypeFilter::ANY,
makeFlushCommands(true, false), false /*validatePositionIncrease*/);
static const NamedCommandSequence kFlushOutSyncSeq =
- std::make_tuple(std::string("Flush"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
+ std::make_tuple(std::string("Flush"), kAidlVersion1, "", 0, StreamTypeFilter::SYNC,
makeFlushCommands(false, true), false /*validatePositionIncrease*/);
static const NamedCommandSequence kFlushOutAsyncSeq =
- std::make_tuple(std::string("Flush"), kAidlVersion1, kStreamTransientStateTransitionDelayMs,
- StreamTypeFilter::ASYNC, makeFlushCommands(false, false),
- false /*validatePositionIncrease*/);
+ std::make_tuple(std::string("Flush"), kAidlVersion1, "",
+ kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
+ makeFlushCommands(false, false), false /*validatePositionIncrease*/);
std::shared_ptr<StateSequence> makeDrainPauseFlushOutCommands(bool isSync) {
using State = StreamDescriptor::State;
@@ -5070,11 +5126,11 @@
return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainPauseFlushOutSyncSeq =
- std::make_tuple(std::string("DrainPauseFlush"), kAidlVersion1,
+ std::make_tuple(std::string("DrainPauseFlush"), kAidlVersion1, "",
kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC,
makeDrainPauseFlushOutCommands(true), false /*validatePositionIncrease*/);
static const NamedCommandSequence kDrainPauseFlushOutAsyncSeq =
- std::make_tuple(std::string("DrainPauseFlush"), kAidlVersion1,
+ std::make_tuple(std::string("DrainPauseFlush"), kAidlVersion1, "",
kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
makeDrainPauseFlushOutCommands(false), false /*validatePositionIncrease*/);
@@ -5087,6 +5143,8 @@
return "Sync";
case StreamTypeFilter::ASYNC:
return "Async";
+ case StreamTypeFilter::OFFLOAD:
+ return "Offload";
}
return std::string("Unknown").append(std::to_string(static_cast<int32_t>(filter)));
}
@@ -5119,7 +5177,8 @@
kDrainPauseOutAsyncSeq, kDrainEarlyPauseOutAsyncSeq,
kStandbyOutSyncSeq, kStandbyOutAsyncSeq, kPauseOutSyncSeq,
kPauseOutAsyncSeq, kFlushOutSyncSeq, kFlushOutAsyncSeq,
- kDrainPauseFlushOutSyncSeq, kDrainPauseFlushOutAsyncSeq),
+ kDrainPauseFlushOutSyncSeq, kDrainPauseFlushOutAsyncSeq,
+ kDrainEarlyOffloadSeq),
testing::Values(false, true)),
GetStreamIoTestName);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIoOut);