Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1f182cb authored by Mikhail Naganov's avatar Mikhail Naganov Committed by Gerrit Code Review
Browse files

Merge changes Ibed7f4c3,I8a9f4f0b

* changes:
  audio: Add pause, resume, and standby stream operations
  audio: Add 'join' method to StreamWorker
parents 38b17aec cce8e5f3
Loading
Loading
Loading
Loading
+23 −2
Original line number Original line Diff line number Diff line
@@ -39,15 +39,34 @@ parcelable StreamDescriptor {
  int frameSizeBytes;
  int frameSizeBytes;
  long bufferSizeFrames;
  long bufferSizeFrames;
  android.hardware.audio.core.StreamDescriptor.AudioBuffer audio;
  android.hardware.audio.core.StreamDescriptor.AudioBuffer audio;
  const int COMMAND_BURST = 1;
  const int LATENCY_UNKNOWN = -1;
  @FixedSize @VintfStability
  @FixedSize @VintfStability
  parcelable Position {
  parcelable Position {
    long frames;
    long frames;
    long timeNs;
    long timeNs;
  }
  }
  @Backing(type="int") @VintfStability
  enum State {
    STANDBY = 1,
    IDLE = 2,
    ACTIVE = 3,
    PAUSED = 4,
    DRAINING = 5,
    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
  @FixedSize @VintfStability
  parcelable Command {
  parcelable Command {
    int code;
    android.hardware.audio.core.StreamDescriptor.CommandCode code = android.hardware.audio.core.StreamDescriptor.CommandCode.START;
    int fmqByteCount;
    int fmqByteCount;
  }
  }
  @FixedSize @VintfStability
  @FixedSize @VintfStability
@@ -57,6 +76,8 @@ parcelable StreamDescriptor {
    android.hardware.audio.core.StreamDescriptor.Position observable;
    android.hardware.audio.core.StreamDescriptor.Position observable;
    android.hardware.audio.core.StreamDescriptor.Position hardware;
    android.hardware.audio.core.StreamDescriptor.Position hardware;
    int latencyMs;
    int latencyMs;
    int xrunFrames;
    android.hardware.audio.core.StreamDescriptor.State state = android.hardware.audio.core.StreamDescriptor.State.STANDBY;
  }
  }
  @VintfStability
  @VintfStability
  union AudioBuffer {
  union AudioBuffer {
+6 −0
Original line number Original line Diff line number Diff line
@@ -263,6 +263,9 @@ interface IModule {
     * be completing with an error, although data (zero filled) will still be
     * be completing with an error, although data (zero filled) will still be
     * provided.
     * provided.
     *
     *
     * After the stream has been opened, it remains in the STANDBY state, see
     * StreamDescriptor for more details.
     *
     * @return An opened input stream and the associated descriptor.
     * @return An opened input stream and the associated descriptor.
     * @param args The pack of arguments, see 'OpenInputStreamArguments' parcelable.
     * @param args The pack of arguments, see 'OpenInputStreamArguments' parcelable.
     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
@@ -325,6 +328,9 @@ interface IModule {
     * StreamDescriptor will be completing with an error, although the data
     * StreamDescriptor will be completing with an error, although the data
     * will still be accepted and immediately discarded.
     * will still be accepted and immediately discarded.
     *
     *
     * After the stream has been opened, it remains in the STANDBY state, see
     * StreamDescriptor for more details.
     *
     * @return An opened output stream and the associated descriptor.
     * @return An opened output stream and the associated descriptor.
     * @param args The pack of arguments, see 'OpenOutputStreamArguments' parcelable.
     * @param args The pack of arguments, see 'OpenOutputStreamArguments' parcelable.
     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
     * @throws EX_ILLEGAL_ARGUMENT In the following cases:
+247 −30
Original line number Original line Diff line number Diff line
@@ -33,6 +33,72 @@ import android.hardware.common.fmq.SynchronizedReadWrite;
 * internal components of the stream while serving commands invoked via the
 * internal components of the stream while serving commands invoked via the
 * stream's AIDL interface and commands invoked via the command queue of the
 * stream's AIDL interface and commands invoked via the command queue of the
 * descriptor.
 * descriptor.
 *
 * There is a state machine defined for the stream, which executes on the
 * thread handling the commands from the queue. The states are defined based
 * on the model of idealized producer and consumer connected via a ring buffer.
 * For input streams, the "producer" is hardware, the "consumer" is software,
 * for outputs streams it's the opposite. When the producer is active, but
 * the buffer is full, the following actions are possible:
 *  - if the consumer is active, the producer blocks until there is space,
 *    this behavior is only possible for software producers;
 *  - if the consumer is passive:
 *    - the producer can preserve the buffer contents—a s/w producer can
 *      keep the data on their side, while a h/w producer can only drop captured
 *      data in this case;
 *    - or the producer overwrites old data in the buffer.
 * Similarly, when an active consumer faces an empty buffer, it can:
 *  - block until there is data (producer must be active), only possible
 *    for software consumers;
 *  - walk away with no data; when the consumer is hardware, it must emit
 *    silence in this case.
 *
 * The model is defined below, note the asymmetry regarding the 'IDLE' state
 * between input and output streams:
 *
 *  Producer | Buffer state | Consumer | Applies | State
 *  active?  |              | active?  | to      |
 * ==========|==============|==========|=========|==============================
 *  No       | Empty        | No       | Both    | STANDBY
 * ----------|--------------|----------|---------|-----------------------------
 *  Yes      | Filling up   | No       | Input   | IDLE, overwrite behavior
 * ----------|--------------|----------|---------|-----------------------------
 *  No       | Empty        | Yes†     | Output  | IDLE, h/w emits silence
 * ----------|--------------|----------|---------|-----------------------------
 *  Yes      | Not empty    | Yes      | Both    | ACTIVE, s/w x-runs counted
 * ----------|--------------|----------|---------|-----------------------------
 *  Yes      | Filling up   | No       | Input   | PAUSED, drop behavior
 * ----------|--------------|----------|---------|-----------------------------
 *  Yes      | Filling up   | No†      | Output  | PAUSED, s/w stops writing once
 *           |              |          |         | the buffer is filled up;
 *           |              |          |         | h/w emits silence.
 * ----------|--------------|----------|---------|-----------------------------
 *  No       | Not empty    | Yes      | Both    | DRAINING
 * ----------|--------------|----------|---------|-----------------------------
 *  No       | Not empty    | No†      | Output  | DRAIN_PAUSED,
 *           |              |          |         | h/w emits silence.
 *
 * † - note that for output, "buffer empty, h/w consuming" has the same outcome
 *     as "buffer not empty, h/w not consuming", but logically these conditions
 *     are different.
 *
 * State machines of both input and output streams start from the 'STANDBY'
 * 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.
 *
 * 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
 * commands (from the enum 'CommandCode') trigger state changes. The full list
 * of states and commands is defined by constants of the 'State' enum. Note that
 * the 'CLOSED' state does not have a constant in the interface because the
 * client can never observe a stream with a functioning command queue in this
 * state. The 'ERROR' state is a special state which the state machine enters
 * when an unrecoverable hardware error is detected by the HAL module.
 */
 */
@JavaDerive(equals=true, toString=true)
@JavaDerive(equals=true, toString=true)
@VintfStability
@VintfStability
@@ -55,12 +121,110 @@ parcelable StreamDescriptor {
        long timeNs;
        long timeNs;
    }
    }


    @VintfStability
    @Backing(type="int")
    enum State {
        /**
         * 'STANDBY' is the initial state of the stream, entered after
         * opening. Since both the producer and the consumer are inactive in
         * this state, it allows the HAL module to put associated hardware into
         * "standby" mode to save power.
         */
        STANDBY = 1,
        /**
         * In the 'IDLE' state the audio hardware is active. For input streams,
         * the hardware is filling buffer with captured data, overwriting old
         * contents on buffer wraparounds. For output streams, the buffer is
         * still empty, and the hardware is outputting zeroes. The HAL module
         * must not account for any under- or overruns as the client is not
         * expected to perform audio I/O.
         */
        IDLE = 2,
        /**
         * The active state of the stream in which it handles audio I/O. The HAL
         * module can assume that the audio I/O will be periodic, thus inability
         * of the client to provide or consume audio data on time must be
         * considered as an under- or overrun and indicated via the 'xrunFrames'
         * field of the reply.
         */
        ACTIVE = 3,
        /**
         * In the 'PAUSED' state the consumer is inactive. For input streams,
         * the hardware stops updating the buffer as soon as it fills up (this
         * is the difference from the 'IDLE' state). For output streams,
         * "inactivity" of hardware means that it does not consume audio data,
         * but rather emits silence.
         */
        PAUSED = 4,
        /**
         * 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.
         */
        DRAINING = 5,
        /**
         * Used for output streams only, pauses draining. This state is similar
         * to the 'PAUSED' state, except that the client is not adding any
         * new data. If it emits a 'BURST' command, this brings the stream
         * into the regular 'PAUSED' state.
         */
        DRAIN_PAUSED = 6,
        /**
         * The ERROR state is entered when the stream has encountered an
         * irrecoverable error from the lower layer. After entering it, the
         * stream can only be closed.
         */
        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 command used for audio I/O, see 'AudioBuffer'. For MMap No IRQ mode
         * The BURST command used for audio I/O, see 'AudioBuffer'. Differences
     * this command only provides updated positions and latency because actual
         * for the MMap No IRQ mode:
     * audio I/O is done via the 'AudioBuffer.mmap' shared buffer.
         *
         *  - 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.
         */
         */
    const int COMMAND_BURST = 1;
        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
     * Used for sending commands to the HAL module. The client writes into
@@ -71,12 +235,16 @@ parcelable StreamDescriptor {
    @FixedSize
    @FixedSize
    parcelable Command {
    parcelable Command {
        /**
        /**
         * One of COMMAND_* codes.
         * The code of the command.
         */
         */
        int code;
        CommandCode code = CommandCode.START;
        /**
        /**
         * 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.
         *
         * For output streams: the amount of bytes that the client requests the
         * For output streams: the amount of bytes that the client requests the
         *   HAL module to read from the 'audio.fmq' queue.
         *   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
         * For input streams: the amount of bytes requested by the client to
         *   read from the hardware into the 'audio.fmq' queue.
         *   read from the hardware into the 'audio.fmq' queue.
         *
         *
@@ -95,6 +263,12 @@ parcelable StreamDescriptor {
    }
    }
    MQDescriptor<Command, SynchronizedReadWrite> command;
    MQDescriptor<Command, SynchronizedReadWrite> command;


    /**
     * The value used for the 'Reply.latencyMs' field when the effective
     * latency can not be reported by the HAL module.
     */
    const int LATENCY_UNKNOWN = -1;

    /**
    /**
     * Used for providing replies to commands. The HAL module writes into
     * Used for providing replies to commands. The HAL module writes into
     * the queue, the client reads. The queue can only contain a single reply,
     * the queue, the client reads. The queue can only contain a single reply,
@@ -107,17 +281,22 @@ parcelable StreamDescriptor {
         * One of Binder STATUS_* statuses:
         * One of Binder STATUS_* statuses:
         *  - STATUS_OK: the command has completed successfully;
         *  - STATUS_OK: the command has completed successfully;
         *  - STATUS_BAD_VALUE: invalid value in the 'Command' structure;
         *  - STATUS_BAD_VALUE: invalid value in the 'Command' structure;
         *  - STATUS_INVALID_OPERATION: the mix port is not connected
         *  - STATUS_INVALID_OPERATION: the command is not applicable in the
         *                              to any producer or consumer, thus
         *                              current state of the stream, or to this
         *                              positions can not be reported;
         *                              type of the stream;
         *  - STATUS_NO_INIT: positions can not be reported because the mix port
         *                    is not connected to any producer or consumer, or
         *                    because the HAL module does not support positions
         *                    reporting for this AudioSource (on input streams).
         *  - STATUS_NOT_ENOUGH_DATA: a read or write error has
         *  - STATUS_NOT_ENOUGH_DATA: a read or write error has
         *                            occurred for the 'audio.fmq' queue;
         *                            occurred for the 'audio.fmq' queue;
         *
         */
         */
        int status;
        int status;
        /**
        /**
         * For output streams: the amount of bytes actually consumed by the HAL
         * Used with the BURST command only.
         *   module from the 'audio.fmq' queue.
         *
         * 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
         * For input streams: the amount of bytes actually provided by the HAL
         *   in the 'audio.fmq' queue.
         *   in the 'audio.fmq' queue.
         *
         *
@@ -126,10 +305,18 @@ parcelable StreamDescriptor {
         */
         */
        int fmqByteCount;
        int fmqByteCount;
        /**
        /**
         * It is recommended to report the current position for any command.
         * If the position can not be reported, the 'status' field must be
         * set to 'NO_INIT'.
         *
         * For output streams: the moment when the specified stream position
         * For output streams: the moment when the specified stream position
         *   was presented to an external observer (i.e. presentation position).
         *   was presented to an external observer (i.e. presentation position).
         * For input streams: the moment when data at the specified stream position
         * For input streams: the moment when data at the specified stream position
         *   was acquired (i.e. capture position).
         *   was acquired (i.e. capture position).
         *
         * The observable position must never be reset by the HAL module.
         * The data type of the frame counter is large enough to support
         * continuous counting for years of operation.
         */
         */
        Position observable;
        Position observable;
        /**
        /**
@@ -138,9 +325,22 @@ parcelable StreamDescriptor {
         */
         */
        Position hardware;
        Position hardware;
        /**
        /**
         * Current latency reported by the hardware.
         * Current latency reported by the hardware. It is recommended to
         * report the current latency for any command. If the value of latency
         * can not be determined, this field must be set to 'LATENCY_UNKNOWN'.
         */
         */
        int latencyMs;
        int latencyMs;
        /**
         * Number of frames lost due to an underrun (for input streams),
         * or not provided on time (for output streams) for the **previous**
         * transfer operation.
         */
        int xrunFrames;
        /**
         * The state that the stream was in while the HAL module was sending the
         * reply.
         */
        State state = State.STANDBY;
    }
    }
    MQDescriptor<Reply, SynchronizedReadWrite> reply;
    MQDescriptor<Reply, SynchronizedReadWrite> reply;


@@ -170,42 +370,59 @@ parcelable StreamDescriptor {
    @VintfStability
    @VintfStability
    union AudioBuffer {
    union AudioBuffer {
        /**
        /**
         * The fast message queue used for all modes except MMap No IRQ.  Both
         * The fast message queue used for BURST commands in all modes except
         * reads and writes into this queue are non-blocking because access to
         * MMap No IRQ. Both reads and writes into this queue are non-blocking
         * this queue is synchronized via the 'command' and 'reply' queues as
         * because access to this queue is synchronized via the 'command' and
         * described below. The queue nevertheless uses 'SynchronizedReadWrite'
         * 'reply' queues as described below. The queue nevertheless uses
         * because there is only one reader, and the reading position must be
         * 'SynchronizedReadWrite' because there is only one reader, and the
         * shared.
         * reading position must be shared.
         *
         * Note that the fast message queue is a transient buffer, only used for
         * data transfer. Neither of the sides can use it to store any data
         * outside of the 'BURST' operation. The consumer must always retrieve
         * all data available in the fast message queue, even if it can not use
         * it. The producer must re-send any unconsumed data on the next
         * transfer operation. This restriction is posed in order to make the
         * fast message queue fully transparent from the latency perspective.
         *
         *
         * For output streams the following sequence of operations is used:
         * For output streams the following sequence of operations is used:
         *  1. The client writes audio data into the 'audio.fmq' queue.
         *  1. The client writes audio data into the 'audio.fmq' queue.
         *  2. The client writes the 'BURST' command into the 'command' queue,
         *  2. The client writes the BURST command into the 'command' queue,
         *     and hangs on waiting on a read from the 'reply' queue.
         *     and hangs on waiting on a read from the 'reply' queue.
         *  3. The high priority thread in the HAL module wakes up due to 2.
         *  3. The high priority thread in the HAL module wakes up due to 2.
         *  4. The HAL module reads the command and audio data.
         *  4. The HAL module reads the command and audio data. According
         *     to the statement above, the HAL module must always read
         *     from the FMQ all the data it contains. The amount of data that
         *     the HAL module has actually consumed is indicated to the client
         *     via the 'reply.fmqByteCount' field.
         *  5. The HAL module writes the command status and current positions
         *  5. The HAL module writes the command status and current positions
         *     into 'reply' queue, and hangs on waiting on a read from
         *     into 'reply' queue, and hangs on waiting on a read from
         *     the 'command' queue.
         *     the 'command' queue.
         *  6. The client wakes up due to 5. and reads the reply.
         *  6. The client wakes up due to 5. and reads the reply.
         *
         *
         * For input streams the following sequence of operations is used:
         * For input streams the following sequence of operations is used:
         *  1. The client writes the 'BURST' command into the 'command' queue,
         *  1. The client writes the BURST command into the 'command' queue,
         *     and hangs on waiting on a read from the 'reply' queue.
         *     and hangs on waiting on a read from the 'reply' queue.
         *  2. The high priority thread in the HAL module wakes up due to 1.
         *  2. The high priority thread in the HAL module wakes up due to 1.
         *  3. The HAL module writes audio data into the 'audio.fmq' queue.
         *  3. The HAL module writes audio data into the 'audio.fmq' queue.
         *     The value of 'reply.fmqByteCount' must be the equal to the amount
         *     of data in the queue.
         *  4. The HAL module writes the command status and current positions
         *  4. The HAL module writes the command status and current positions
         *     into 'reply' queue, and hangs on waiting on a read from
         *     into 'reply' queue, and hangs on waiting on a read from
         *     the 'command' queue.
         *     the 'command' queue.
         *  5. The client wakes up due to 4.
         *  5. The client wakes up due to 4.
         *  6. The client reads the reply and audio data.
         *  6. The client reads the reply and audio data. The client must
         *     always read from the FMQ all the data it contains.
         *
         */
         */
        MQDescriptor<byte, SynchronizedReadWrite> fmq;
        MQDescriptor<byte, SynchronizedReadWrite> fmq;
        /**
        /**
         * MMap buffers are shared directly with the DSP, which operates
         * MMap buffers are shared directly with the DSP, which operates
         * independently from the CPU. Writes and reads into these buffers
         * independently from the CPU. Writes and reads into these buffers are
         * are not synchronized with 'command' and 'reply' queues. However,
         * not synchronized with 'command' and 'reply' queues. However, the
         * the client still uses the 'BURST' command for obtaining current
         * client still uses the same commands for controlling the audio data
         * positions from the HAL module.
         * exchange and for obtaining current positions and latency from the HAL
         * module.
         */
         */
        MmapBufferDescriptor mmap;
        MmapBufferDescriptor mmap;
    }
    }
+42 −0
Original line number Original line Diff line number Diff line
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// To render: dot -Tpng stream-in-sm.gv -o stream-in-sm.png
digraph stream_in_state_machine {
    node [shape=doublecircle style=filled fillcolor=black width=0.5] I;
    node [shape=point width=0.5] F;
    node [shape=oval width=1];
    node [fillcolor=lightgreen] STANDBY;  // buffer is empty
    node [fillcolor=tomato] CLOSED;
    node [fillcolor=tomato] ERROR;
    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
    DRAINING -> STANDBY [label="<empty buffer>"];  // consumer deactivates
    IDLE -> ERROR [label="<hardware failure>"];
    ACTIVE -> ERROR [label="<hardware failure>"];
    PAUSED -> ERROR [label="<hardware failure>"];
    ANY_STATE -> CLOSED [label="→IStream*.close"];
    CLOSED -> F;
}
+48 −0
Original line number Original line Diff line number Diff line
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// To render: dot -Tpng stream-out-sm.gv -o stream-out-sm.png
digraph stream_out_state_machine {
    node [shape=doublecircle style=filled fillcolor=black width=0.5] I;
    node [shape=point width=0.5] F;
    node [shape=oval width=1];
    node [fillcolor=lightgreen] STANDBY;  // buffer is empty
    node [fillcolor=lightgreen] IDLE;     // buffer is empty
    node [fillcolor=tomato] CLOSED;
    node [fillcolor=tomato] ERROR;
    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
    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
    IDLE -> ERROR [label="<hardware failure>"];
    ACTIVE -> ERROR [label="<hardware failure>"];
    DRAINING -> ERROR [label="<hardware failure>"];
    ANY_STATE -> CLOSED [label="→IStream*.close"];
    CLOSED -> F;
}
Loading