audio: Allow going to 'IDLE' for synchronous drain
For small buffers, the driver can perform draining
synhronously, returning control to the HAL only after
the buffer is empty. This makes going through
the 'DRAINING' state artificial. Thus, we allow going
to the 'IDLE' state directly.
In order to make sure that VTS handles both transitions:
to 'DRAINING' and to 'IDLE', correctly, add an "AOSP as
vendor" parameter "aosp.forceSynchronousDrain" to induce
this behavior in the default implementation.
Bug: 262402957
Test: atest VtsHalAudioCoreTargetTest
Change-Id: Ic8eaee53cb4596afb5317b4b905e004af3f112aa
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
index 6aa5c61..47e7fda 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
@@ -31,6 +31,7 @@
ACTIVE -> ACTIVE [label="burst"];
ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming)
ACTIVE -> DRAINING [label="drain"]; // producer -> passive
+ ACTIVE -> IDLE [label="drain"]; // synchronous drain
PAUSED -> PAUSED [label="burst"];
PAUSED -> ACTIVE [label="start"]; // consumer -> active
PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 780d6a9..13b04cd 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -140,7 +140,8 @@
!isBitPositionFlagSet(flags.get<AudioIoFlags::Tag::output>(),
AudioOutputFlags::MMAP_NOIRQ))) {
StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
- mVendorDebug.forceTransientBurst};
+ mVendorDebug.forceTransientBurst,
+ mVendorDebug.forceSynchronousDrain};
StreamContext temp(
std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
@@ -980,6 +981,7 @@
}
const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
+const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
std::vector<VendorParameter>* _aidl_return) {
@@ -990,6 +992,10 @@
VendorParameter forceTransientBurst{.id = id};
forceTransientBurst.ext.setParcelable(Boolean{mVendorDebug.forceTransientBurst});
_aidl_return->push_back(std::move(forceTransientBurst));
+ } else if (id == VendorDebug::kForceSynchronousDrainName) {
+ VendorParameter forceSynchronousDrain{.id = id};
+ forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
+ _aidl_return->push_back(std::move(forceSynchronousDrain));
} else {
allParametersKnown = false;
LOG(ERROR) << __func__ << ": unrecognized parameter \"" << id << "\"";
@@ -999,6 +1005,23 @@
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
+namespace {
+
+template <typename W>
+bool extractParameter(const VendorParameter& p, decltype(W::value)* v) {
+ std::optional<W> value;
+ binder_status_t result = p.ext.getParcelable(&value);
+ if (result == STATUS_OK && value.has_value()) {
+ *v = value.value().value;
+ return true;
+ }
+ LOG(ERROR) << __func__ << ": failed to read the value of the parameter \"" << p.id
+ << "\": " << result;
+ return false;
+}
+
+} // namespace
+
ndk::ScopedAStatus Module::setVendorParameters(const std::vector<VendorParameter>& in_parameters,
bool in_async) {
LOG(DEBUG) << __func__ << ": parameter count " << in_parameters.size()
@@ -1006,13 +1029,11 @@
bool allParametersKnown = true;
for (const auto& p : in_parameters) {
if (p.id == VendorDebug::kForceTransientBurstName) {
- std::optional<Boolean> value;
- binder_status_t result = p.ext.getParcelable(&value);
- if (result == STATUS_OK) {
- mVendorDebug.forceTransientBurst = value.value().value;
- } else {
- LOG(ERROR) << __func__ << ": failed to read the value of the parameter \"" << p.id
- << "\"";
+ if (!extractParameter<Boolean>(p, &mVendorDebug.forceTransientBurst)) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ } else if (p.id == VendorDebug::kForceSynchronousDrainName) {
+ if (!extractParameter<Boolean>(p, &mVendorDebug.forceSynchronousDrain)) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
} else {
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 9be8896..0520cba 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -402,7 +402,11 @@
usleep(1000); // Simulate a blocking call into the driver.
populateReply(&reply, mIsConnected);
// Can switch the state to ERROR if a driver error occurs.
- switchToTransientState(StreamDescriptor::State::DRAINING);
+ if (mState == StreamDescriptor::State::ACTIVE && mForceSynchronousDrain) {
+ mState = StreamDescriptor::State::IDLE;
+ } else {
+ switchToTransientState(StreamDescriptor::State::DRAINING);
+ }
} else if (mState == StreamDescriptor::State::TRANSFER_PAUSED) {
mState = StreamDescriptor::State::DRAIN_PAUSED;
populateReply(&reply, mIsConnected);
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 9c95ea8..000a704 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -38,7 +38,9 @@
private:
struct VendorDebug {
static const std::string kForceTransientBurstName;
+ static const std::string kForceSynchronousDrainName;
bool forceTransientBurst = false;
+ bool forceSynchronousDrain = false;
};
ndk::ScopedAStatus setModuleDebug(
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 29c1e2e..2cf5951 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -67,6 +67,8 @@
int transientStateDelayMs = 0;
// Force the "burst" command to move the SM to the TRANSFERRING state.
bool forceTransientBurst = false;
+ // Force the "drain" command to be synchronous, going directly to the IDLE state.
+ bool forceSynchronousDrain = false;
};
StreamContext() = default;
@@ -115,6 +117,7 @@
return mFormat;
}
bool getForceTransientBurst() const { return mDebugParameters.forceTransientBurst; }
+ bool getForceSynchronousDrain() const { return mDebugParameters.forceSynchronousDrain; }
size_t getFrameSize() const;
int getInternalCommandCookie() const { return mInternalCommandCookie; }
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
@@ -150,7 +153,8 @@
mDataMQ(context.getDataMQ()),
mAsyncCallback(context.getAsyncCallback()),
mTransientStateDelayMs(context.getTransientStateDelayMs()),
- mForceTransientBurst(context.getForceTransientBurst()) {}
+ mForceTransientBurst(context.getForceTransientBurst()),
+ mForceSynchronousDrain(context.getForceSynchronousDrain()) {}
std::string init() override;
void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
void populateReplyWrongState(StreamDescriptor::Reply* reply,
@@ -174,6 +178,7 @@
const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
const bool mForceTransientBurst;
+ const bool mForceSynchronousDrain;
// We use an array and the "size" field instead of a vector to be able to detect
// memory allocation issues.
std::unique_ptr<int8_t[]> mDataBuffer;
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 58e5cde..d4f2811 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -2965,6 +2965,23 @@
RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
}
}
+ } else if (!IOTraits<Stream>::is_input) {
+ // Also try running the same sequence with "aosp.forceSynchronousDrain" set.
+ // This will only work with the default implementation. When it works, the stream
+ // tries always to move to the 'IDLE' state after a drain.
+ // This helps to check more paths for our test scenarios.
+ WithModuleParameter forceSynchronousDrain("aosp.forceSynchronousDrain",
+ Boolean{true});
+ if (forceSynchronousDrain.SetUpNoChecks(module.get(), true /*failureExpected*/)
+ .isOk()) {
+ if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(
+ RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
+ }
+ }
}
}
}
@@ -3402,14 +3419,17 @@
std::shared_ptr<StateSequence> makeDrainOutCommands(bool isSync) {
using State = StreamDescriptor::State;
auto d = std::make_unique<StateDag>();
+ StateDag::Node last = d->makeFinalNode(State::IDLE);
StateDag::Node active = d->makeNodes(
{std::make_pair(State::ACTIVE, kDrainOutAllCommand),
std::make_pair(State::DRAINING, isSync ? TransitionTrigger(kGetStatusCommand)
: TransitionTrigger(kDrainReadyEvent))},
- State::IDLE);
+ last);
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
+ } else {
+ active.children().push_back(last);
}
d->makeNode(State::STANDBY, kStartCommand, idle);
return std::make_shared<StateSequenceFollower>(std::move(d));
@@ -3431,6 +3451,9 @@
StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
+ } else {
+ // If we get straight into IDLE on drain, no further testing is possible.
+ active.children().push_back(d->makeFinalNode(State::IDLE));
}
d->makeNode(State::STANDBY, kStartCommand, idle);
return std::make_shared<StateSequenceFollower>(std::move(d));
@@ -3569,11 +3592,13 @@
StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
std::make_pair(State::DRAIN_PAUSED, kFlushCommand)},
State::IDLE);
- StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
- std::make_pair(State::ACTIVE, kDrainOutAllCommand)},
- draining);
+ StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining);
+ StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
if (!isSync) {
idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
+ } else {
+ // If we get straight into IDLE on drain, no further testing is possible.
+ active.children().push_back(d->makeFinalNode(State::IDLE));
}
d->makeNode(State::STANDBY, kStartCommand, idle);
return std::make_shared<StateSequenceFollower>(std::move(d));