diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl index 80ee185bc8..467d37b6f7 100644 --- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl @@ -35,4 +35,5 @@ package android.hardware.audio.core; @JavaDerive(equals=true, toString=true) @VintfStability parcelable ModuleDebug { boolean simulateDeviceConnections; + int streamTransientStateDelayMs; } diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl index 3a77ad1765..84a1fe7bad 100644 --- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl @@ -57,7 +57,8 @@ parcelable StreamDescriptor { } @FixedSize @VintfStability union Command { - int hal_reserved_exit; + int halReservedExit; + android.media.audio.common.Void getStatus; android.media.audio.common.Void start; int burst; android.media.audio.common.Void drain; diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl index be400512cd..1bc2ccc54b 100644 --- a/audio/aidl/android/hardware/audio/core/IModule.aidl +++ b/audio/aidl/android/hardware/audio/core/IModule.aidl @@ -53,9 +53,13 @@ interface IModule { * the HAL module behavior that would otherwise require human intervention. * * The HAL module must throw an error if there is an attempt to change - * the debug behavior for the aspect which is currently in use. + * the debug behavior for the aspect which is currently in use, or when + * the value of any of the debug flags is invalid. See 'ModuleDebug' for + * the full list of constraints. * * @param debug The debug options. + * @throws EX_ILLEGAL_ARGUMENT If some of the configuration parameters are + * invalid. * @throws EX_ILLEGAL_STATE If the flag(s) being changed affect functionality * which is currently in use. */ diff --git a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl index 858a9bd841..86a9b220ce 100644 --- a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl +++ b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl @@ -35,4 +35,13 @@ parcelable ModuleDebug { * profiles. */ boolean simulateDeviceConnections; + /** + * Must be non-negative. When set to non-zero, HAL module must delay + * transition from "transient" stream states (see StreamDescriptor.aidl) + * by the specified amount of milliseconds. The purpose of this delay + * is to allow VTS to test sending of stream commands while the stream is + * in a transient state. The delay must apply to newly created streams, + * it is not required to apply the delay to already opened streams. + */ + int streamTransientStateDelayMs; } diff --git a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl index 2b1fc993e7..92d131a8ad 100644 --- a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl +++ b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl @@ -84,13 +84,13 @@ import android.media.audio.common.Void; * are different. * * State machines of both input and output streams start from the 'STANDBY' - * state. Transitions between states happen naturally with changes in the + * state. Transitions between states happen naturally with changes in the * states of the model elements. For simplicity, we restrict the change to one * element only, for example, in the 'STANDBY' state, either the producer or the * consumer can become active, but not both at the same time. States 'STANDBY', * 'IDLE', 'READY', and '*PAUSED' are "stable"—they require an external event, * whereas a change from the 'DRAINING' state can happen with time as the buffer - * gets empty. + * gets empty, thus it's a "transient" state. * * The state machine for input streams is defined in the `stream-in-sm.gv` file, * for output streams—in the `stream-out-sm.gv` file. State machines define how @@ -198,7 +198,14 @@ parcelable StreamDescriptor { * implementation must pass a random cookie as the command argument, * which is only known to the implementation. */ - int hal_reserved_exit; + int halReservedExit; + /** + * Retrieve the current state of the stream. This command must be + * processed by the stream in any state. The stream must provide current + * positions, counters, and its state in the reply. This command must be + * handled by the HAL module without any observable side effects. + */ + Void getStatus; /** * See the state machines on the applicability of this command to * different states. @@ -215,15 +222,14 @@ parcelable StreamDescriptor { * read from the hardware into the 'audio.fmq' queue. * * In both cases it is allowed for this field to contain any - * non-negative number. The value 0 can be used if the client only needs - * to retrieve current positions and latency. Any sufficiently big value - * which exceeds the size of the queue's area which is currently - * available for reading or writing by the HAL module must be trimmed by - * the HAL module to the available size. Note that the HAL module is - * allowed to consume or provide less data than requested, and it must - * return the amount of actually read or written data via the - * 'Reply.fmqByteCount' field. Thus, only attempts to pass a negative - * number must be constituted as a client's error. + * non-negative number. Any sufficiently big value which exceeds the + * size of the queue's area which is currently available for reading or + * writing by the HAL module must be trimmed by the HAL module to the + * available size. Note that the HAL module is allowed to consume or + * provide less data than requested, and it must return the amount of + * actually read or written data via the 'Reply.fmqByteCount' + * field. Thus, only attempts to pass a negative number must be + * constituted as a client's error. * * Differences for the MMap No IRQ mode: * @@ -233,6 +239,9 @@ parcelable StreamDescriptor { * with sending of this command. * * - the value must always be set to 0. + * + * See the state machines on the applicability of this command to + * different states. */ int burst; /** diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp index 6863fe34cc..a8f3b9b376 100644 --- a/audio/aidl/default/Module.cpp +++ b/audio/aidl/default/Module.cpp @@ -135,8 +135,8 @@ ndk::ScopedAStatus Module::createStreamContext(int32_t in_portConfigId, int64_t StreamContext temp( std::make_unique(1, true /*configureEventFlagWord*/), std::make_unique(1, true /*configureEventFlagWord*/), - frameSize, - std::make_unique(frameSize * in_bufferSizeFrames)); + frameSize, std::make_unique(frameSize * in_bufferSizeFrames), + mDebug.streamTransientStateDelayMs); if (temp.isValid()) { *out_context = std::move(temp); } else { @@ -242,6 +242,11 @@ ndk::ScopedAStatus Module::setModuleDebug( << "while having external devices connected"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } + if (in_debug.streamTransientStateDelayMs < 0) { + LOG(ERROR) << __func__ << ": streamTransientStateDelayMs is negative: " + << in_debug.streamTransientStateDelayMs; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } mDebug = in_debug; return ndk::ScopedAStatus::ok(); } diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp index 21dc4b6c08..c5d00a2517 100644 --- a/audio/aidl/default/Stream.cpp +++ b/audio/aidl/default/Stream.cpp @@ -96,23 +96,36 @@ void StreamWorkerCommonLogic::populateReply(StreamDescriptor::Reply* reply, } } +void StreamWorkerCommonLogic::populateReplyWrongState( + StreamDescriptor::Reply* reply, const StreamDescriptor::Command& command) const { + LOG(WARNING) << "command '" << toString(command.getTag()) + << "' can not be handled in the state " << toString(mState); + reply->status = STATUS_INVALID_OPERATION; +} + const std::string StreamInWorkerLogic::kThreadName = "reader"; StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() { + // Note: for input streams, draining is driven by the client, thus + // "empty buffer" condition can only happen while handling the 'burst' + // command. Thus, unlike for output streams, it does not make sense to + // delay the 'DRAINING' state here by 'mTransientStateDelayMs'. + // TODO: Add a delay for transitions of async operations when/if they added. + StreamDescriptor::Command command{}; if (!mCommandMQ->readBlocking(&command, 1)) { LOG(ERROR) << __func__ << ": reading of command from MQ failed"; mState = StreamDescriptor::State::ERROR; return Status::ABORT; } + LOG(DEBUG) << __func__ << ": received command " << command.toString() << " in " << kThreadName; StreamDescriptor::Reply reply{}; reply.status = STATUS_BAD_VALUE; using Tag = StreamDescriptor::Command::Tag; switch (command.getTag()) { - case Tag::hal_reserved_exit: - if (const int32_t cookie = command.get(); + case Tag::halReservedExit: + if (const int32_t cookie = command.get(); cookie == mInternalCommandCookie) { - LOG(DEBUG) << __func__ << ": received EXIT command"; setClosed(); // This is an internal command, no need to reply. return Status::EXIT; @@ -120,8 +133,10 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() { LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie; } break; + case Tag::getStatus: + populateReply(&reply, mIsConnected); + break; case Tag::start: - LOG(DEBUG) << __func__ << ": received START read command"; if (mState == StreamDescriptor::State::STANDBY || mState == StreamDescriptor::State::DRAINING) { populateReply(&reply, mIsConnected); @@ -129,15 +144,13 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() { ? StreamDescriptor::State::IDLE : StreamDescriptor::State::ACTIVE; } else { - LOG(WARNING) << __func__ << ": START command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; case Tag::burst: if (const int32_t fmqByteCount = command.get(); fmqByteCount >= 0) { - LOG(DEBUG) << __func__ << ": received BURST read command for " << fmqByteCount - << " bytes"; + LOG(DEBUG) << __func__ << ": '" << toString(command.getTag()) << "' command for " + << fmqByteCount << " bytes"; if (mState == StreamDescriptor::State::IDLE || mState == StreamDescriptor::State::ACTIVE || mState == StreamDescriptor::State::PAUSED || @@ -151,69 +164,56 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() { } else if (mState == StreamDescriptor::State::DRAINING) { // To simplify the reference code, we assume that the read operation // has consumed all the data remaining in the hardware buffer. - // TODO: Provide parametrization on the duration of draining to test - // handling of commands during the 'DRAINING' state. + // In a real implementation, here we would either remain in + // the 'DRAINING' state, or transfer to 'STANDBY' depending on the + // buffer state. mState = StreamDescriptor::State::STANDBY; } } else { - LOG(WARNING) << __func__ << ": BURST command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } } else { LOG(WARNING) << __func__ << ": invalid burst byte count: " << fmqByteCount; } break; case Tag::drain: - LOG(DEBUG) << __func__ << ": received DRAIN read command"; if (mState == StreamDescriptor::State::ACTIVE) { usleep(1000); // Simulate a blocking call into the driver. populateReply(&reply, mIsConnected); // Can switch the state to ERROR if a driver error occurs. mState = StreamDescriptor::State::DRAINING; } else { - LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; case Tag::standby: - LOG(DEBUG) << __func__ << ": received STANDBY read command"; if (mState == StreamDescriptor::State::IDLE) { usleep(1000); // Simulate a blocking call into the driver. populateReply(&reply, mIsConnected); // Can switch the state to ERROR if a driver error occurs. mState = StreamDescriptor::State::STANDBY; } else { - LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; case Tag::pause: - LOG(DEBUG) << __func__ << ": received PAUSE read command"; if (mState == StreamDescriptor::State::ACTIVE) { usleep(1000); // Simulate a blocking call into the driver. populateReply(&reply, mIsConnected); // Can switch the state to ERROR if a driver error occurs. mState = StreamDescriptor::State::PAUSED; } else { - LOG(WARNING) << __func__ << ": PAUSE command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; case Tag::flush: - LOG(DEBUG) << __func__ << ": received FLUSH read command"; if (mState == StreamDescriptor::State::PAUSED) { usleep(1000); // Simulate a blocking call into the driver. populateReply(&reply, mIsConnected); // Can switch the state to ERROR if a driver error occurs. mState = StreamDescriptor::State::STANDBY; } else { - LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; } @@ -261,20 +261,32 @@ bool StreamInWorkerLogic::read(size_t clientSize, StreamDescriptor::Reply* reply const std::string StreamOutWorkerLogic::kThreadName = "writer"; StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { + if (mState == StreamDescriptor::State::DRAINING) { + if (auto stateDurationMs = std::chrono::duration_cast( + std::chrono::steady_clock::now() - mTransientStateStart); + stateDurationMs >= mTransientStateDelayMs) { + mState = StreamDescriptor::State::IDLE; + if (mTransientStateDelayMs.count() != 0) { + LOG(DEBUG) << __func__ << ": switched to state " << toString(mState) + << " after a timeout"; + } + } + } + StreamDescriptor::Command command{}; if (!mCommandMQ->readBlocking(&command, 1)) { LOG(ERROR) << __func__ << ": reading of command from MQ failed"; mState = StreamDescriptor::State::ERROR; return Status::ABORT; } + LOG(DEBUG) << __func__ << ": received command " << command.toString() << " in " << kThreadName; StreamDescriptor::Reply reply{}; reply.status = STATUS_BAD_VALUE; using Tag = StreamDescriptor::Command::Tag; switch (command.getTag()) { - case Tag::hal_reserved_exit: - if (const int32_t cookie = command.get(); + case Tag::halReservedExit: + if (const int32_t cookie = command.get(); cookie == mInternalCommandCookie) { - LOG(DEBUG) << __func__ << ": received EXIT command"; setClosed(); // This is an internal command, no need to reply. return Status::EXIT; @@ -282,31 +294,31 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie; } break; + case Tag::getStatus: + populateReply(&reply, mIsConnected); + break; case Tag::start: - LOG(DEBUG) << __func__ << ": received START write command"; switch (mState) { case StreamDescriptor::State::STANDBY: mState = StreamDescriptor::State::IDLE; + populateReply(&reply, mIsConnected); break; case StreamDescriptor::State::PAUSED: mState = StreamDescriptor::State::ACTIVE; + populateReply(&reply, mIsConnected); break; case StreamDescriptor::State::DRAIN_PAUSED: - mState = StreamDescriptor::State::PAUSED; + switchToTransientState(StreamDescriptor::State::DRAINING); + populateReply(&reply, mIsConnected); break; default: - LOG(WARNING) << __func__ << ": START command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; - } - if (reply.status != STATUS_INVALID_OPERATION) { - populateReply(&reply, mIsConnected); + populateReplyWrongState(&reply, command); } break; case Tag::burst: if (const int32_t fmqByteCount = command.get(); fmqByteCount >= 0) { - LOG(DEBUG) << __func__ << ": received BURST write command for " << fmqByteCount - << " bytes"; + LOG(DEBUG) << __func__ << ": '" << toString(command.getTag()) << "' command for " + << fmqByteCount << " bytes"; if (mState != StreamDescriptor::State::ERROR) { // BURST can be handled in all valid states if (!write(fmqByteCount, &reply)) { @@ -320,47 +332,33 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { mState = StreamDescriptor::State::ACTIVE; } // When in 'ACTIVE' and 'PAUSED' do not need to change the state. } else { - LOG(WARNING) << __func__ << ": BURST command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } } else { LOG(WARNING) << __func__ << ": invalid burst byte count: " << fmqByteCount; } break; case Tag::drain: - LOG(DEBUG) << __func__ << ": received DRAIN write command"; if (mState == StreamDescriptor::State::ACTIVE) { usleep(1000); // Simulate a blocking call into the driver. populateReply(&reply, mIsConnected); // Can switch the state to ERROR if a driver error occurs. - mState = StreamDescriptor::State::IDLE; - // Since there is no actual hardware that would be draining the buffer, - // in order to simplify the reference code, we assume that draining - // happens instantly, thus skipping the 'DRAINING' state. - // TODO: Provide parametrization on the duration of draining to test - // handling of commands during the 'DRAINING' state. + switchToTransientState(StreamDescriptor::State::DRAINING); } else { - LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; case Tag::standby: - LOG(DEBUG) << __func__ << ": received STANDBY write command"; if (mState == StreamDescriptor::State::IDLE) { usleep(1000); // Simulate a blocking call into the driver. populateReply(&reply, mIsConnected); // Can switch the state to ERROR if a driver error occurs. mState = StreamDescriptor::State::STANDBY; } else { - LOG(WARNING) << __func__ << ": STANDBY command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; case Tag::pause: - LOG(DEBUG) << __func__ << ": received PAUSE write command"; if (mState == StreamDescriptor::State::ACTIVE || mState == StreamDescriptor::State::DRAINING) { populateReply(&reply, mIsConnected); @@ -368,21 +366,16 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { ? StreamDescriptor::State::PAUSED : StreamDescriptor::State::DRAIN_PAUSED; } else { - LOG(WARNING) << __func__ << ": PAUSE command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; case Tag::flush: - LOG(DEBUG) << __func__ << ": received FLUSH write command"; if (mState == StreamDescriptor::State::PAUSED || mState == StreamDescriptor::State::DRAIN_PAUSED) { populateReply(&reply, mIsConnected); mState = StreamDescriptor::State::IDLE; } else { - LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state " - << toString(mState); - reply.status = STATUS_INVALID_OPERATION; + populateReplyWrongState(&reply, command); } break; } @@ -450,9 +443,8 @@ template void StreamCommon::stopWorker() { if (auto commandMQ = mContext.getCommandMQ(); commandMQ != nullptr) { LOG(DEBUG) << __func__ << ": asking the worker to exit..."; - auto cmd = - StreamDescriptor::Command::make( - mContext.getInternalCommandCookie()); + auto cmd = StreamDescriptor::Command::make( + mContext.getInternalCommandCookie()); // Note: never call 'pause' and 'resume' methods of StreamWorker // in the HAL implementation. These methods are to be used by // the client side only. Preventing the worker loop from running diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h index 5ee0f82946..bcbabad7d0 100644 --- a/audio/aidl/default/include/core-impl/Stream.h +++ b/audio/aidl/default/include/core-impl/Stream.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include #include @@ -59,24 +60,27 @@ class StreamContext { StreamContext() = default; StreamContext(std::unique_ptr commandMQ, std::unique_ptr replyMQ, - size_t frameSize, std::unique_ptr dataMQ) + size_t frameSize, std::unique_ptr dataMQ, int transientStateDelayMs) : mCommandMQ(std::move(commandMQ)), mInternalCommandCookie(std::rand()), mReplyMQ(std::move(replyMQ)), mFrameSize(frameSize), - mDataMQ(std::move(dataMQ)) {} + mDataMQ(std::move(dataMQ)), + mTransientStateDelayMs(transientStateDelayMs) {} StreamContext(StreamContext&& other) : mCommandMQ(std::move(other.mCommandMQ)), mInternalCommandCookie(other.mInternalCommandCookie), mReplyMQ(std::move(other.mReplyMQ)), mFrameSize(other.mFrameSize), - mDataMQ(std::move(other.mDataMQ)) {} + mDataMQ(std::move(other.mDataMQ)), + mTransientStateDelayMs(other.mTransientStateDelayMs) {} StreamContext& operator=(StreamContext&& other) { mCommandMQ = std::move(other.mCommandMQ); mInternalCommandCookie = other.mInternalCommandCookie; mReplyMQ = std::move(other.mReplyMQ); mFrameSize = other.mFrameSize; mDataMQ = std::move(other.mDataMQ); + mTransientStateDelayMs = other.mTransientStateDelayMs; return *this; } @@ -86,6 +90,7 @@ class StreamContext { size_t getFrameSize() const { return mFrameSize; } int getInternalCommandCookie() const { return mInternalCommandCookie; } ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); } + int getTransientStateDelayMs() const { return mTransientStateDelayMs; } bool isValid() const; void reset(); @@ -95,6 +100,7 @@ class StreamContext { std::unique_ptr mReplyMQ; size_t mFrameSize; std::unique_ptr mDataMQ; + int mTransientStateDelayMs; }; class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic { @@ -111,9 +117,16 @@ class StreamWorkerCommonLogic : public ::android::hardware::audio::common::Strea mFrameSize(context.getFrameSize()), mCommandMQ(context.getCommandMQ()), mReplyMQ(context.getReplyMQ()), - mDataMQ(context.getDataMQ()) {} + mDataMQ(context.getDataMQ()), + mTransientStateDelayMs(context.getTransientStateDelayMs()) {} std::string init() override; void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const; + void populateReplyWrongState(StreamDescriptor::Reply* reply, + const StreamDescriptor::Command& command) const; + void switchToTransientState(StreamDescriptor::State state) { + mState = state; + mTransientStateStart = std::chrono::steady_clock::now(); + } // Atomic fields are used both by the main and worker threads. std::atomic mIsConnected = false; @@ -125,6 +138,8 @@ class StreamWorkerCommonLogic : public ::android::hardware::audio::common::Strea StreamContext::CommandMQ* mCommandMQ; StreamContext::ReplyMQ* mReplyMQ; StreamContext::DataMQ* mDataMQ; + const std::chrono::duration mTransientStateDelayMs; + std::chrono::time_point mTransientStateStart; // We use an array and the "size" field instead of a vector to be able to detect // memory allocation issues. std::unique_ptr mDataBuffer; diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp index 2f72bb0d2e..a0580697ab 100644 --- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp @@ -1557,8 +1557,7 @@ class AudioStream : public AudioCoreModule { void SendInvalidCommandImpl(const AudioPortConfig& portConfig) { std::vector commands = { - StreamDescriptor::Command::make( - 0), + StreamDescriptor::Command::make(0), // TODO: For proper testing of input streams, need to put the stream into // a state which accepts BURST commands. StreamDescriptor::Command::make(-1), @@ -1695,7 +1694,8 @@ class StreamLogicDefaultDriver : public StreamLogicDriver { std::string mUnexpectedTransition; }; -using NamedCommandSequence = std::pair>; +enum { NAMED_CMD_NAME, NAMED_CMD_DELAY_MS, NAMED_CMD_CMDS }; +using NamedCommandSequence = std::tuple>; enum { PARAM_MODULE_NAME, PARAM_CMD_SEQ, PARAM_SETUP_SEQ }; using StreamIoTestParameters = std::tuple; @@ -1716,7 +1716,12 @@ class AudioStreamIo : public AudioCoreModuleBase, } for (const auto& portConfig : allPortConfigs) { SCOPED_TRACE(portConfig.toString()); - const auto& commandsAndStates = std::get(GetParam()).second; + WithDebugFlags delayTransientStates = WithDebugFlags::createNested(debug); + delayTransientStates.flags().streamTransientStateDelayMs = + std::get(std::get(GetParam())); + ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get())); + const auto& commandsAndStates = + std::get(std::get(GetParam())); if (!std::get(GetParam())) { ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates)); } else { @@ -1974,6 +1979,11 @@ INSTANTIATE_TEST_SUITE_P(AudioStreamOutTest, AudioStreamOut, android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut); +// This is the value used in test sequences for which the test needs to ensure +// that the HAL stays in a transient state long enough to receive the next command. +static const int kStreamTransientStateTransitionDelayMs = 3000; +static const StreamDescriptor::Command kGetStatusCommand = + StreamDescriptor::Command::make(Void{}); static const StreamDescriptor::Command kStartCommand = StreamDescriptor::Command::make(Void{}); static const StreamDescriptor::Command kBurstCommand = @@ -1987,83 +1997,100 @@ static const StreamDescriptor::Command kPauseCommand = static const StreamDescriptor::Command kFlushCommand = StreamDescriptor::Command::make(Void{}); static const NamedCommandSequence kReadOrWriteSeq = - std::make_pair(std::string("ReadOrWrite"), - std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)}); + std::make_tuple(std::string("ReadOrWrite"), 0, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)}); static const NamedCommandSequence kDrainInSeq = - std::make_pair(std::string("Drain"), - std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING), - std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING), - // TODO: This will need to be changed once DRAIN starts taking time. - std::make_pair(kBurstCommand, StreamDescriptor::State::STANDBY)}); + std::make_tuple(std::string("Drain"), 0, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING), + std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING) + // TODO: Test that from DRAINING the stream goes either to DRAINING + // or to STANDBY. Supporting this in a generic would complicate the + // test code because after state bifurcation we probably need to use + // different commands for each state, this way a sequence of + // commands and states becomes a tree. + }); static const NamedCommandSequence kDrainOutSeq = - std::make_pair(std::string("Drain"), - std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - // TODO: This will need to be changed once DRAIN starts taking time. - std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)}); -// TODO: This will need to be changed once DRAIN starts taking time so we can pause it. -static const NamedCommandSequence kDrainPauseOutSeq = std::make_pair( - std::string("DrainPause"), - std::vector{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)}); + std::make_tuple(std::string("Drain"), 0, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING), + // Draining is synchronous, the stream switches to IDLE afterwards. + std::make_pair(kGetStatusCommand, StreamDescriptor::State::IDLE)}); +static const NamedCommandSequence kDrainPauseOutSeq = std::make_tuple( + std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING), + std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), + std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING), + std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), + std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED)}); static const NamedCommandSequence kStandbySeq = - std::make_pair(std::string("Standby"), - std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY), - // Perform a read or write in order to advance observable position - // (this is verified by tests). - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)}); + std::make_tuple(std::string("Standby"), 0, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY), + // Perform a read or write in order to advance observable position + // (this is verified by tests). + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)}); static const NamedCommandSequence kPauseInSeq = - std::make_pair(std::string("Pause"), - std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}); + std::make_tuple(std::string("Pause"), 0, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), + std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}); static const NamedCommandSequence kPauseOutSeq = - std::make_pair(std::string("Pause"), - std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)}); + std::make_tuple(std::string("Pause"), 0, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), + std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), + std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED), + std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)}); static const NamedCommandSequence kFlushInSeq = - std::make_pair(std::string("Flush"), - std::vector{ - std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), - std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), - std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), - std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}); -static const NamedCommandSequence kFlushOutSeq = std::make_pair( - std::string("Flush"), + std::make_tuple(std::string("Flush"), 0, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), + std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}); +static const NamedCommandSequence kFlushOutSeq = std::make_tuple( + std::string("Flush"), 0, std::vector{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED), std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)}); +static const NamedCommandSequence kDrainPauseFlushOutSeq = std::make_tuple( + std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs, + std::vector{ + std::make_pair(kStartCommand, StreamDescriptor::State::IDLE), + std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE), + std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING), + std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED), + std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)}); std::string GetStreamIoTestName(const testing::TestParamInfo& info) { return android::PrintInstanceNameToString( testing::TestParamInfo{std::get(info.param), info.index}) .append("_") - .append(std::get(info.param).first) + .append(std::get(std::get(info.param))) .append("_SetupSeq") .append(std::get(info.param) ? "2" : "1"); } @@ -2079,7 +2106,8 @@ INSTANTIATE_TEST_SUITE_P( AudioStreamIoOutTest, AudioStreamIoOut, testing::Combine(testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), testing::Values(kReadOrWriteSeq, kDrainOutSeq, kDrainPauseOutSeq, - kStandbySeq, kPauseOutSeq, kFlushOutSeq), + kStandbySeq, kPauseOutSeq, kFlushOutSeq, + kDrainPauseFlushOutSeq), testing::Values(false, true)), GetStreamIoTestName); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIoOut);