Merge "audio: Make StreamDescriptor::Command a union" am: 2b68543625 am: 224a3b1755

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2296167

Change-Id: I29a46c3c67df1311d9e1d9ed88e2b85b2de16f45
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mikhail Naganov
2022-11-11 05:36:22 +00:00
committed by Automerger Merge Worker
7 changed files with 428 additions and 421 deletions

View File

@@ -55,19 +55,15 @@ parcelable StreamDescriptor {
DRAIN_PAUSED = 6,
ERROR = 100,
}
@Backing(type="int") @VintfStability
enum CommandCode {
START = 1,
BURST = 2,
DRAIN = 3,
STANDBY = 4,
PAUSE = 5,
FLUSH = 6,
}
@FixedSize @VintfStability
parcelable Command {
android.hardware.audio.core.StreamDescriptor.CommandCode code = android.hardware.audio.core.StreamDescriptor.CommandCode.START;
int fmqByteCount;
union Command {
int hal_reserved_exit;
android.media.audio.common.Void start;
int burst;
android.media.audio.common.Void drain;
android.media.audio.common.Void standby;
android.media.audio.common.Void pause;
android.media.audio.common.Void flush;
}
@FixedSize @VintfStability
parcelable Reply {

View File

@@ -19,6 +19,7 @@ package android.hardware.audio.core;
import android.hardware.audio.core.MmapBufferDescriptor;
import android.hardware.common.fmq.MQDescriptor;
import android.hardware.common.fmq.SynchronizedReadWrite;
import android.media.audio.common.Void;
/**
* Stream descriptor contains fast message queues and buffers used for sending
@@ -177,76 +178,41 @@ parcelable StreamDescriptor {
ERROR = 100,
}
@VintfStability
@Backing(type="int")
enum CommandCode {
/**
* See the state machines on the applicability of this command to
* different states. The 'fmqByteCount' field must always be set to 0.
*/
START = 1,
/**
* The BURST command used for audio I/O, see 'AudioBuffer'. Differences
* for the MMap No IRQ mode:
*
* - this command only provides updated positions and latency because
* actual audio I/O is done via the 'AudioBuffer.mmap' shared buffer.
* The client does not synchronize reads and writes into the buffer
* with sending of this command.
*
* - the 'fmqByteCount' must always be set to 0.
*/
BURST = 2,
/**
* See the state machines on the applicability of this command to
* different states. The 'fmqByteCount' field must always be set to 0.
*/
DRAIN = 3,
/**
* See the state machines on the applicability of this command to
* different states. The 'fmqByteCount' field must always be set to 0.
*
* Note that it's left on the discretion of the HAL implementation to
* assess all the necessary conditions that could prevent hardware from
* being suspended. Even if it can not be suspended, the state machine
* must still enter the 'STANDBY' state for consistency. Since the
* buffer must remain empty in this state, even if capturing hardware is
* still active, captured data must be discarded.
*/
STANDBY = 4,
/**
* See the state machines on the applicability of this command to
* different states. The 'fmqByteCount' field must always be set to 0.
*/
PAUSE = 5,
/**
* See the state machines on the applicability of this command to
* different states. The 'fmqByteCount' field must always be set to 0.
*/
FLUSH = 6,
}
/**
* Used for sending commands to the HAL module. The client writes into
* the queue, the HAL module reads. The queue can only contain a single
* command.
*
* Variants of type 'Void' correspond to commands without
* arguments. Variants of other types correspond to commands with an
* argument. Would in future a need for a command with multiple argument
* arise, a Parcelable type should be used for the corresponding variant.
*/
@VintfStability
@FixedSize
parcelable Command {
union Command {
/**
* The code of the command.
* Reserved for the HAL implementation to allow unblocking the wait on a
* command and exiting the I/O thread. A command of this variant must
* never be sent from the client side. To prevent that, the
* implementation must pass a random cookie as the command argument,
* which is only known to the implementation.
*/
CommandCode code = CommandCode.START;
int hal_reserved_exit;
/**
* This field is only used for the BURST command. For all other commands
* it must be set to 0. The following description applies to the use
* of this field for the BURST command.
* See the state machines on the applicability of this command to
* different states.
*/
Void start;
/**
* The 'burst' command used for audio I/O, see 'AudioBuffer'. The value
* specifies:
*
* For output streams: the amount of bytes that the client requests the
* HAL module to use out of the data contained in the 'audio.fmq' queue.
* For input streams: the amount of bytes requested by the client to
* read from the hardware into the 'audio.fmq' queue.
* - for output streams: the amount of bytes that the client requests the
* HAL module to use out of the data contained in the 'audio.fmq' queue.
*
* - for input streams: the amount of bytes requested by the client to
* 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
@@ -258,8 +224,44 @@ parcelable StreamDescriptor {
* 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:
*
* - this command only provides updated positions and latency because
* actual audio I/O is done via the 'AudioBuffer.mmap' shared buffer.
* The client does not synchronize reads and writes into the buffer
* with sending of this command.
*
* - the value must always be set to 0.
*/
int fmqByteCount;
int burst;
/**
* See the state machines on the applicability of this command to
* different states.
*/
Void drain;
/**
* See the state machines on the applicability of this command to
* different states.
*
* Note that it's left on the discretion of the HAL implementation to
* assess all the necessary conditions that could prevent hardware from
* being suspended. Even if it can not be suspended, the state machine
* must still enter the 'STANDBY' state for consistency. Since the
* buffer must remain empty in this state, even if capturing hardware is
* still active, captured data must be discarded.
*/
Void standby;
/**
* See the state machines on the applicability of this command to
* different states.
*/
Void pause;
/**
* See the state machines on the applicability of this command to
* different states.
*/
Void flush;
}
MQDescriptor<Command, SynchronizedReadWrite> command;
@@ -293,15 +295,15 @@ parcelable StreamDescriptor {
*/
int status;
/**
* Used with the BURST command only.
* Used with the 'burst' command only.
*
* For output streams: the amount of bytes of data actually consumed
* by the HAL module.
* For input streams: the amount of bytes actually provided by the HAL
* in the 'audio.fmq' queue.
*
* The returned value must not exceed the value passed in the
* 'fmqByteCount' field of the corresponding command or be negative.
* The returned value must not exceed the value passed as the
* argument of the corresponding command, or be negative.
*/
int fmqByteCount;
/**

View File

@@ -23,16 +23,16 @@ digraph stream_in_state_machine {
node [style=dashed] ANY_STATE;
node [fillcolor=lightblue style=filled];
I -> STANDBY;
STANDBY -> IDLE [label="START"]; // producer -> active
IDLE -> STANDBY [label="STANDBY"]; // producer -> passive, buffer is cleared
IDLE -> ACTIVE [label="BURST"]; // consumer -> active
ACTIVE -> ACTIVE [label="BURST"];
ACTIVE -> PAUSED [label="PAUSE"]; // consumer -> passive
ACTIVE -> DRAINING [label="DRAIN"]; // producer -> passive
PAUSED -> ACTIVE [label="BURST"]; // consumer -> active
PAUSED -> STANDBY [label="FLUSH"]; // producer -> passive, buffer is cleared
DRAINING -> DRAINING [label="BURST"];
DRAINING -> ACTIVE [label="START"]; // producer -> active
STANDBY -> IDLE [label="start"]; // producer -> active
IDLE -> STANDBY [label="standby"]; // producer -> passive, buffer is cleared
IDLE -> ACTIVE [label="burst"]; // consumer -> active
ACTIVE -> ACTIVE [label="burst"];
ACTIVE -> PAUSED [label="pause"]; // consumer -> passive
ACTIVE -> DRAINING [label="drain"]; // producer -> passive
PAUSED -> ACTIVE [label="burst"]; // consumer -> active
PAUSED -> STANDBY [label="flush"]; // producer -> passive, buffer is cleared
DRAINING -> DRAINING [label="burst"];
DRAINING -> ACTIVE [label="start"]; // producer -> active
DRAINING -> STANDBY [label="<empty buffer>"]; // consumer deactivates
IDLE -> ERROR [label="<hardware failure>"];
ACTIVE -> ERROR [label="<hardware failure>"];

View File

@@ -24,22 +24,22 @@ digraph stream_out_state_machine {
node [style=dashed] ANY_STATE;
node [fillcolor=lightblue style=filled];
I -> STANDBY;
STANDBY -> IDLE [label="START"]; // consumer -> active
STANDBY -> PAUSED [label="BURST"]; // producer -> active
IDLE -> STANDBY [label="STANDBY"]; // consumer -> passive
IDLE -> ACTIVE [label="BURST"]; // producer -> active
ACTIVE -> ACTIVE [label="BURST"];
ACTIVE -> PAUSED [label="PAUSE"]; // consumer -> passive (not consuming)
ACTIVE -> DRAINING [label="DRAIN"]; // producer -> passive
PAUSED -> PAUSED [label="BURST"];
PAUSED -> ACTIVE [label="START"]; // consumer -> active
PAUSED -> IDLE [label="FLUSH"]; // producer -> passive, buffer is cleared
STANDBY -> IDLE [label="start"]; // consumer -> active
STANDBY -> PAUSED [label="burst"]; // producer -> active
IDLE -> STANDBY [label="standby"]; // consumer -> passive
IDLE -> ACTIVE [label="burst"]; // producer -> active
ACTIVE -> ACTIVE [label="burst"];
ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming)
ACTIVE -> DRAINING [label="drain"]; // producer -> passive
PAUSED -> PAUSED [label="burst"];
PAUSED -> ACTIVE [label="start"]; // consumer -> active
PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared
DRAINING -> IDLE [label="<empty buffer>"];
DRAINING -> ACTIVE [label="BURST"]; // producer -> active
DRAINING -> DRAIN_PAUSED [label="PAUSE"]; // consumer -> passive (not consuming)
DRAIN_PAUSED -> DRAINING [label="START"]; // consumer -> active
DRAIN_PAUSED -> PAUSED [label="BURST"]; // producer -> active
DRAIN_PAUSED -> IDLE [label="FLUSH"]; // buffer is cleared
DRAINING -> ACTIVE [label="burst"]; // producer -> active
DRAINING -> DRAIN_PAUSED [label="pause"]; // consumer -> passive (not consuming)
DRAIN_PAUSED -> DRAINING [label="start"]; // consumer -> active
DRAIN_PAUSED -> PAUSED [label="burst"]; // producer -> active
DRAIN_PAUSED -> IDLE [label="flush"]; // buffer is cleared
IDLE -> ERROR [label="<hardware failure>"];
ACTIVE -> ERROR [label="<hardware failure>"];
DRAINING -> ERROR [label="<hardware failure>"];

View File

@@ -106,101 +106,116 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
return Status::ABORT;
}
StreamDescriptor::Reply reply{};
if (static_cast<int32_t>(command.code) == StreamContext::COMMAND_EXIT &&
command.fmqByteCount == mInternalCommandCookie) {
LOG(DEBUG) << __func__ << ": received EXIT command";
setClosed();
// This is an internal command, no need to reply.
return Status::EXIT;
} else if (command.code == StreamDescriptor::CommandCode::START && command.fmqByteCount >= 0) {
LOG(DEBUG) << __func__ << ": received START read command";
if (mState == StreamDescriptor::State::STANDBY ||
mState == StreamDescriptor::State::DRAINING) {
populateReply(&reply, mIsConnected);
mState = mState == StreamDescriptor::State::STANDBY ? 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;
}
} else if (command.code == StreamDescriptor::CommandCode::BURST && command.fmqByteCount >= 0) {
LOG(DEBUG) << __func__ << ": received BURST read command for " << command.fmqByteCount
<< " bytes";
if (mState == StreamDescriptor::State::IDLE || mState == StreamDescriptor::State::ACTIVE ||
mState == StreamDescriptor::State::PAUSED ||
mState == StreamDescriptor::State::DRAINING) {
if (!read(command.fmqByteCount, &reply)) {
mState = StreamDescriptor::State::ERROR;
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<Tag::hal_reserved_exit>();
cookie == mInternalCommandCookie) {
LOG(DEBUG) << __func__ << ": received EXIT command";
setClosed();
// This is an internal command, no need to reply.
return Status::EXIT;
} else {
LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
}
if (mState == StreamDescriptor::State::IDLE ||
mState == StreamDescriptor::State::PAUSED) {
mState = StreamDescriptor::State::ACTIVE;
} 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.
break;
case Tag::start:
LOG(DEBUG) << __func__ << ": received START read command";
if (mState == StreamDescriptor::State::STANDBY ||
mState == StreamDescriptor::State::DRAINING) {
populateReply(&reply, mIsConnected);
mState = mState == StreamDescriptor::State::STANDBY
? 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;
}
break;
case Tag::burst:
if (const int32_t fmqByteCount = command.get<Tag::burst>(); fmqByteCount >= 0) {
LOG(DEBUG) << __func__ << ": received BURST read command for " << fmqByteCount
<< " bytes";
if (mState == StreamDescriptor::State::IDLE ||
mState == StreamDescriptor::State::ACTIVE ||
mState == StreamDescriptor::State::PAUSED ||
mState == StreamDescriptor::State::DRAINING) {
if (!read(fmqByteCount, &reply)) {
mState = StreamDescriptor::State::ERROR;
}
if (mState == StreamDescriptor::State::IDLE ||
mState == StreamDescriptor::State::PAUSED) {
mState = StreamDescriptor::State::ACTIVE;
} 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.
mState = StreamDescriptor::State::STANDBY;
}
} else {
LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
<< toString(mState);
reply.status = STATUS_INVALID_OPERATION;
}
} 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;
}
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;
}
} else {
LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
<< toString(mState);
reply.status = STATUS_INVALID_OPERATION;
}
} else if (command.code == StreamDescriptor::CommandCode::DRAIN && command.fmqByteCount == 0) {
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;
}
} else if (command.code == StreamDescriptor::CommandCode::PAUSE && command.fmqByteCount == 0) {
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;
}
} else if (command.code == StreamDescriptor::CommandCode::FLUSH && command.fmqByteCount == 0) {
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;
}
} else if (command.code == StreamDescriptor::CommandCode::STANDBY &&
command.fmqByteCount == 0) {
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;
}
} else {
LOG(WARNING) << __func__ << ": invalid command (" << command.toString()
<< ") or count: " << command.fmqByteCount;
reply.status = STATUS_BAD_VALUE;
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;
}
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;
}
break;
}
reply.state = mState;
LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
@@ -253,109 +268,123 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
return Status::ABORT;
}
StreamDescriptor::Reply reply{};
if (static_cast<int32_t>(command.code) == StreamContext::COMMAND_EXIT &&
command.fmqByteCount == mInternalCommandCookie) {
LOG(DEBUG) << __func__ << ": received EXIT command";
setClosed();
// This is an internal command, no need to reply.
return Status::EXIT;
} else if (command.code == StreamDescriptor::CommandCode::START && command.fmqByteCount >= 0) {
LOG(DEBUG) << __func__ << ": received START read command";
switch (mState) {
case StreamDescriptor::State::STANDBY:
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<Tag::hal_reserved_exit>();
cookie == mInternalCommandCookie) {
LOG(DEBUG) << __func__ << ": received EXIT command";
setClosed();
// This is an internal command, no need to reply.
return Status::EXIT;
} else {
LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
}
break;
case Tag::start:
LOG(DEBUG) << __func__ << ": received START write command";
switch (mState) {
case StreamDescriptor::State::STANDBY:
mState = StreamDescriptor::State::IDLE;
break;
case StreamDescriptor::State::PAUSED:
mState = StreamDescriptor::State::ACTIVE;
break;
case StreamDescriptor::State::DRAIN_PAUSED:
mState = StreamDescriptor::State::PAUSED;
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);
}
break;
case Tag::burst:
if (const int32_t fmqByteCount = command.get<Tag::burst>(); fmqByteCount >= 0) {
LOG(DEBUG) << __func__ << ": received BURST write command for " << fmqByteCount
<< " bytes";
if (mState !=
StreamDescriptor::State::ERROR) { // BURST can be handled in all valid states
if (!write(fmqByteCount, &reply)) {
mState = StreamDescriptor::State::ERROR;
}
if (mState == StreamDescriptor::State::STANDBY ||
mState == StreamDescriptor::State::DRAIN_PAUSED) {
mState = StreamDescriptor::State::PAUSED;
} else if (mState == StreamDescriptor::State::IDLE ||
mState == StreamDescriptor::State::DRAINING) {
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;
}
} 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;
break;
case StreamDescriptor::State::PAUSED:
mState = StreamDescriptor::State::ACTIVE;
break;
case StreamDescriptor::State::DRAIN_PAUSED:
mState = StreamDescriptor::State::PAUSED;
break;
default:
LOG(WARNING) << __func__ << ": START command can not be handled in the state "
// 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.
} else {
LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
<< toString(mState);
reply.status = STATUS_INVALID_OPERATION;
}
if (reply.status != STATUS_INVALID_OPERATION) {
populateReply(&reply, mIsConnected);
}
} else if (command.code == StreamDescriptor::CommandCode::BURST && command.fmqByteCount >= 0) {
LOG(DEBUG) << __func__ << ": received BURST write command for " << command.fmqByteCount
<< " bytes";
if (mState != StreamDescriptor::State::ERROR) { // BURST can be handled in all valid states
if (!write(command.fmqByteCount, &reply)) {
mState = StreamDescriptor::State::ERROR;
}
if (mState == StreamDescriptor::State::STANDBY ||
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;
}
break;
case Tag::pause:
LOG(DEBUG) << __func__ << ": received PAUSE write command";
if (mState == StreamDescriptor::State::ACTIVE ||
mState == StreamDescriptor::State::DRAINING) {
populateReply(&reply, mIsConnected);
mState = mState == StreamDescriptor::State::ACTIVE
? 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;
}
break;
case Tag::flush:
LOG(DEBUG) << __func__ << ": received FLUSH write command";
if (mState == StreamDescriptor::State::PAUSED ||
mState == StreamDescriptor::State::DRAIN_PAUSED) {
mState = StreamDescriptor::State::PAUSED;
} else if (mState == StreamDescriptor::State::IDLE ||
mState == StreamDescriptor::State::DRAINING) {
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;
}
} else if (command.code == StreamDescriptor::CommandCode::DRAIN && command.fmqByteCount == 0) {
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.
} else {
LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
<< toString(mState);
reply.status = STATUS_INVALID_OPERATION;
}
} else if (command.code == StreamDescriptor::CommandCode::STANDBY &&
command.fmqByteCount == 0) {
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;
}
} else if (command.code == StreamDescriptor::CommandCode::PAUSE && command.fmqByteCount == 0) {
LOG(DEBUG) << __func__ << ": received PAUSE write command";
if (mState == StreamDescriptor::State::ACTIVE ||
mState == StreamDescriptor::State::DRAINING) {
populateReply(&reply, mIsConnected);
mState = mState == StreamDescriptor::State::ACTIVE
? 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;
}
} else if (command.code == StreamDescriptor::CommandCode::FLUSH && command.fmqByteCount == 0) {
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;
}
} else {
LOG(WARNING) << __func__ << ": invalid command (" << command.toString()
<< ") or count: " << command.fmqByteCount;
reply.status = STATUS_BAD_VALUE;
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;
}
break;
}
reply.state = mState;
LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
@@ -421,9 +450,9 @@ template <class Metadata, class StreamWorker>
void StreamCommon<Metadata, StreamWorker>::stopWorker() {
if (auto commandMQ = mContext.getCommandMQ(); commandMQ != nullptr) {
LOG(DEBUG) << __func__ << ": asking the worker to exit...";
StreamDescriptor::Command cmd;
cmd.code = StreamDescriptor::CommandCode(StreamContext::COMMAND_EXIT);
cmd.fmqByteCount = mContext.getInternalCommandCookie();
auto cmd =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
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

View File

@@ -54,8 +54,6 @@ class StreamContext {
int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>
DataMQ;
// Ensure that this value is not used by any of StreamDescriptor.CommandCode enums
static constexpr int32_t COMMAND_EXIT = -1;
// Ensure that this value is not used by any of StreamDescriptor.State enums
static constexpr int32_t STATE_CLOSED = -1;

View File

@@ -70,6 +70,7 @@ using aidl::android::media::audio::common::AudioPortDeviceExt;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioSource;
using aidl::android::media::audio::common::AudioUsage;
using aidl::android::media::audio::common::Void;
using android::hardware::audio::common::isBitPositionFlagSet;
using android::hardware::audio::common::StreamLogic;
using android::hardware::audio::common::StreamWorker;
@@ -455,7 +456,9 @@ class StreamReaderLogic : public StreamCommonLogic {
LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status);
return Status::ABORT;
}
if (reply.fmqByteCount < 0 || reply.fmqByteCount > command.fmqByteCount) {
if (reply.fmqByteCount < 0 ||
(command.getTag() == StreamDescriptor::Command::Tag::burst &&
reply.fmqByteCount > command.get<StreamDescriptor::Command::Tag::burst>())) {
LOG(ERROR) << __func__
<< ": received invalid byte count in the reply: " << reply.fmqByteCount;
return Status::ABORT;
@@ -532,7 +535,9 @@ class StreamWriterLogic : public StreamCommonLogic {
LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status);
return Status::ABORT;
}
if (reply.fmqByteCount < 0 || reply.fmqByteCount > command.fmqByteCount) {
if (reply.fmqByteCount < 0 ||
(command.getTag() == StreamDescriptor::Command::Tag::burst &&
reply.fmqByteCount > command.get<StreamDescriptor::Command::Tag::burst>())) {
LOG(ERROR) << __func__
<< ": received invalid byte count in the reply: " << reply.fmqByteCount;
return Status::ABORT;
@@ -1551,18 +1556,15 @@ class AudioStream : public AudioCoreModule {
}
void SendInvalidCommandImpl(const AudioPortConfig& portConfig) {
std::vector<StreamDescriptor::Command> commands(6);
commands[0].code = StreamDescriptor::CommandCode(-1);
commands[1].code = StreamDescriptor::CommandCode(
static_cast<int32_t>(StreamDescriptor::CommandCode::START) - 1);
commands[2].code = StreamDescriptor::CommandCode(std::numeric_limits<int32_t>::min());
commands[3].code = StreamDescriptor::CommandCode(std::numeric_limits<int32_t>::max());
// TODO: For proper testing of input streams, need to put the stream into
// a state which accepts BURST commands.
commands[4].code = StreamDescriptor::CommandCode::BURST;
commands[4].fmqByteCount = -1;
commands[5].code = StreamDescriptor::CommandCode::BURST;
commands[5].fmqByteCount = std::numeric_limits<int32_t>::min();
std::vector<StreamDescriptor::Command> commands = {
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
0),
// TODO: For proper testing of input streams, need to put the stream into
// a state which accepts BURST commands.
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(-1),
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(
std::numeric_limits<int32_t>::min()),
};
WithStream<Stream> stream(portConfig);
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
StreamLogicDriverInvalidCommand driver(commands);
@@ -1628,7 +1630,7 @@ TEST_P(AudioStreamOut, RequireOffloadInfo) {
<< "when no offload info is provided for a compressed offload mix port";
}
using CommandAndState = std::pair<StreamDescriptor::CommandCode, StreamDescriptor::State>;
using CommandAndState = std::pair<StreamDescriptor::Command, StreamDescriptor::State>;
class StreamLogicDefaultDriver : public StreamLogicDriver {
public:
@@ -1643,15 +1645,17 @@ class StreamLogicDefaultDriver : public StreamLogicDriver {
bool done() override { return mNextCommand >= mCommands.size(); }
StreamDescriptor::Command getNextCommand(int maxDataSize, int* actualSize) override {
StreamDescriptor::Command command{};
command.code = mCommands[mNextCommand++].first;
const int dataSize = command.code == StreamDescriptor::CommandCode::BURST ? maxDataSize : 0;
command.fmqByteCount = dataSize;
if (actualSize != nullptr) {
// In the output scenario, reduce slightly the fmqByteCount to verify
// that the HAL module always consumes all data from the MQ.
if (command.fmqByteCount > 1) command.fmqByteCount--;
*actualSize = dataSize;
auto command = mCommands[mNextCommand++].first;
if (command.getTag() == StreamDescriptor::Command::Tag::burst) {
if (actualSize != nullptr) {
// In the output scenario, reduce slightly the fmqByteCount to verify
// that the HAL module always consumes all data from the MQ.
if (maxDataSize > 1) maxDataSize--;
*actualSize = maxDataSize;
}
command.set<StreamDescriptor::Command::Tag::burst>(maxDataSize);
} else {
if (actualSize != nullptr) *actualSize = 0;
}
return command;
}
@@ -1673,7 +1677,7 @@ class StreamLogicDefaultDriver : public StreamLogicDriver {
.append(" to ")
.append(toString(reply.state))
.append(" caused by the command ")
.append(toString(lastCommandState.first));
.append(lastCommandState.first.toString());
LOG(ERROR) << __func__ << ": " << s;
mUnexpectedTransition = std::move(s);
return false;
@@ -1970,112 +1974,90 @@ INSTANTIATE_TEST_SUITE_P(AudioStreamOutTest, AudioStreamOut,
android::PrintInstanceNameToString);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut);
static const NamedCommandSequence kReadOrWriteSeq = std::make_pair(
std::string("ReadOrWrite"),
std::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE)});
static const NamedCommandSequence kDrainInSeq = std::make_pair(
std::string("Drain"),
std::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::DRAIN,
StreamDescriptor::State::DRAINING),
std::make_pair(StreamDescriptor::CommandCode::START,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::DRAIN,
StreamDescriptor::State::DRAINING),
// TODO: This will need to be changed once DRAIN starts taking time.
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::STANDBY)});
static const NamedCommandSequence kDrainOutSeq = std::make_pair(
std::string("Drain"),
std::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
// TODO: This will need to be changed once DRAIN starts taking time.
std::make_pair(StreamDescriptor::CommandCode::DRAIN,
StreamDescriptor::State::IDLE)});
static const StreamDescriptor::Command kStartCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::start>(Void{});
static const StreamDescriptor::Command kBurstCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(0);
static const StreamDescriptor::Command kDrainCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::drain>(Void{});
static const StreamDescriptor::Command kStandbyCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::standby>(Void{});
static const StreamDescriptor::Command kPauseCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::pause>(Void{});
static const StreamDescriptor::Command kFlushCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::flush>(Void{});
static const NamedCommandSequence kReadOrWriteSeq =
std::make_pair(std::string("ReadOrWrite"),
std::vector<CommandAndState>{
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<CommandAndState>{
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)});
static const NamedCommandSequence kDrainOutSeq =
std::make_pair(std::string("Drain"),
std::vector<CommandAndState>{
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<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::DRAIN,
StreamDescriptor::State::IDLE)});
static const NamedCommandSequence kStandbySeq = std::make_pair(
std::string("Standby"),
std::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::STANDBY,
StreamDescriptor::State::STANDBY),
// Perform a read or write in order to advance observable position
// (this is verified by tests).
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE)});
static const NamedCommandSequence kPauseInSeq = std::make_pair(
std::string("Pause"),
std::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::PAUSE,
StreamDescriptor::State::PAUSED),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::PAUSE,
StreamDescriptor::State::PAUSED),
std::make_pair(StreamDescriptor::CommandCode::FLUSH,
StreamDescriptor::State::STANDBY)});
static const NamedCommandSequence kPauseOutSeq = std::make_pair(
std::string("Pause"),
std::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::PAUSE,
StreamDescriptor::State::PAUSED),
std::make_pair(StreamDescriptor::CommandCode::START,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::PAUSE,
StreamDescriptor::State::PAUSED),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::PAUSED),
std::make_pair(StreamDescriptor::CommandCode::START,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::PAUSE,
StreamDescriptor::State::PAUSED)});
static const NamedCommandSequence kFlushInSeq = std::make_pair(
std::string("Flush"),
std::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::PAUSE,
StreamDescriptor::State::PAUSED),
std::make_pair(StreamDescriptor::CommandCode::FLUSH,
StreamDescriptor::State::STANDBY)});
std::vector<CommandAndState>{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)});
static const NamedCommandSequence kStandbySeq =
std::make_pair(std::string("Standby"),
std::vector<CommandAndState>{
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<CommandAndState>{
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<CommandAndState>{
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<CommandAndState>{
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::vector<CommandAndState>{
std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
std::make_pair(StreamDescriptor::CommandCode::BURST,
StreamDescriptor::State::ACTIVE),
std::make_pair(StreamDescriptor::CommandCode::PAUSE,
StreamDescriptor::State::PAUSED),
std::make_pair(StreamDescriptor::CommandCode::FLUSH,
StreamDescriptor::State::IDLE)});
std::vector<CommandAndState>{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)});
std::string GetStreamIoTestName(const testing::TestParamInfo<StreamIoTestParameters>& info) {
return android::PrintInstanceNameToString(
testing::TestParamInfo<std::string>{std::get<PARAM_MODULE_NAME>(info.param),