|
| 1 | +# Audio Processing Modules (`src/module`) |
| 2 | + |
| 3 | +The `src/module` directory and the `src/include/module` headers define the Sound Open Firmware (SOF) modern Audio Processing Module API. This architecture abstracts the underlying OS and pipeline scheduler implementations from the actual audio signal processing logic, allowing modules to be written once and deployed either as statically linked core components or as dynamically loadable Zephyr EXT (LLEXT) modules. |
| 4 | + |
| 5 | +## Architecture Overview |
| 6 | + |
| 7 | +The SOF module architecture is built around three core concepts: |
| 8 | + |
| 9 | +1. **`struct module_interface`**: A standardized set of operations (`init`, `prepare`, `process`, `reset`, `free`, `set_configuration`) that every audio processing module implements. |
| 10 | +2. **`struct processing_module`**: The runtime instantiated state of a module. It holds metadata, configuration objects, memory allocations (`module_resources`), and references to interconnected streams. |
| 11 | +3. **Module Adapter (`src/audio/module_adapter`)**: The system glue layer. It masquerades as a legacy pipeline `comp_dev` to the SOF schedulers, but acts as a sandbox container for the `processing_module`. It intercepts IPC commands, manages the module's state machine, manages inputs/outputs, and safely calls the `module_interface` operations. |
| 12 | + |
| 13 | +```mermaid |
| 14 | +graph TD |
| 15 | + subgraph SOF Core Pipeline Scheduler |
| 16 | + P[Pipeline Scheduler <br> LL/DP Domains] |
| 17 | + end |
| 18 | +
|
| 19 | + subgraph Module Adapter System Layer |
| 20 | + MA[Module Adapter `comp_dev`] |
| 21 | + IPC[IPC Command Dispatch] |
| 22 | + MEM[Memory Resource Manager] |
| 23 | + end |
| 24 | +
|
| 25 | + subgraph Standardized Module Framework API |
| 26 | + INF[`module_interface` Ops] |
| 27 | + SRC[Source API <br> `source_get_data`] |
| 28 | + SNK[Sink API <br> `sink_get_buffer`] |
| 29 | + end |
| 30 | +
|
| 31 | + subgraph Custom Audio Modules |
| 32 | + VOL[Volume] |
| 33 | + EQ[EQ FIR/IIR] |
| 34 | + CUSTOM[Loadable 3rd Party <br> Zephyr LLEXT] |
| 35 | + end |
| 36 | +
|
| 37 | + P <-->|Execute| MA |
| 38 | + IPC -->|Config/Triggers| MA |
| 39 | +
|
| 40 | + MA -->|Invoke| INF |
| 41 | + MA -->|Manage| MEM |
| 42 | +
|
| 43 | + INF --> VOL |
| 44 | + INF --> EQ |
| 45 | + INF --> CUSTOM |
| 46 | +
|
| 47 | + VOL -->|Read| SRC |
| 48 | + VOL -->|Write| SNK |
| 49 | + EQ -->|Read| SRC |
| 50 | + EQ -->|Write| SNK |
| 51 | +``` |
| 52 | + |
| 53 | +## Module State Machine |
| 54 | + |
| 55 | +Every processing module is strictly governed by a uniform runtime state machine managed by the `module_adapter`. Modules must adhere to the transitions defined by `enum module_state`: |
| 56 | + |
| 57 | +1. `MODULE_DISABLED`: The module is loaded but uninitialized, or has been freed. No memory is allocated. |
| 58 | +2. `MODULE_INITIALIZED`: After a successful `.init()` call. The module parses its IPC configuration and allocates necessary local resources (delay lines, coefficient tables). |
| 59 | +3. `MODULE_IDLE`: After a successful `.prepare()` call. Audio stream formats are fully negotiated and agreed upon (Stream params, channels, rate). |
| 60 | +4. `MODULE_PROCESSING`: When the pipeline triggers a `START` command. The `.process()` callback is actively handling buffers. |
| 61 | + |
| 62 | +```mermaid |
| 63 | +stateDiagram-v2 |
| 64 | + [*] --> MODULE_DISABLED: Module Created |
| 65 | +
|
| 66 | + MODULE_DISABLED --> MODULE_INITIALIZED: .init() / IPC NEW |
| 67 | + MODULE_INITIALIZED --> MODULE_DISABLED: .free() / IPC FREE |
| 68 | +
|
| 69 | + MODULE_INITIALIZED --> MODULE_IDLE: .prepare() / Pipeline Setup |
| 70 | + MODULE_IDLE --> MODULE_INITIALIZED: .reset() / Pipeline Reset |
| 71 | +
|
| 72 | + MODULE_IDLE --> MODULE_PROCESSING: .trigger(START) / IPC START |
| 73 | + MODULE_PROCESSING --> MODULE_IDLE: .trigger(STOP/PAUSE) / IPC STOP |
| 74 | +``` |
| 75 | + |
| 76 | +## Data Flows and Buffer Management |
| 77 | + |
| 78 | +Modules do not directly manipulate underlying DMA, ALSA, or Zephyr `comp_buffer` pointers. Instead, they interact via the decoupled **Source and Sink APIs**. This allows the adapter to seamlessly feed data from varying topological sources without changing module code. |
| 79 | + |
| 80 | +The flow operates primarily in a "get -> manipulate -> commit/release" pattern: |
| 81 | + |
| 82 | +```mermaid |
| 83 | +sequenceDiagram |
| 84 | + participant Adapter as Module Adapter |
| 85 | + participant Mod as Processing Module (.process) |
| 86 | + participant Src as Source API (Input) |
| 87 | + participant Snk as Sink API (Output) |
| 88 | +
|
| 89 | + Adapter->>Mod: Process Trigger (sources[], sinks[]) |
| 90 | +
|
| 91 | + Mod->>Src: source_get_data(req_size) |
| 92 | + Src-->>Mod: Provides read_ptr, available_bytes |
| 93 | +
|
| 94 | + Mod->>Snk: sink_get_buffer(req_size) |
| 95 | + Snk-->>Mod: Provides write_ptr, free_bytes |
| 96 | +
|
| 97 | + note over Mod: Execute DSP Algorithm <br> (Read from read_ptr -> Write to write_ptr) |
| 98 | +
|
| 99 | + Mod->>Src: source_release_data(consumed_bytes) |
| 100 | + Mod->>Snk: sink_commit_buffer(produced_bytes) |
| 101 | +
|
| 102 | + Mod-->>Adapter: Return Status |
| 103 | +``` |
| 104 | + |
| 105 | +### Source API (`src/module/audio/source_api.c`) |
| 106 | +- modules request data by calling `source_get_data_s16` or `s32`. This establishes an active read lock. |
| 107 | +- Once done, the module must call `source_release_data()` releasing only the frames actually consumed. |
| 108 | + |
| 109 | +### Sink API (`src/module/audio/sink_api.c`) |
| 110 | +- modules request destination buffers by calling `sink_get_buffer_s16` or `s32`. |
| 111 | +- After processing into the provided memory array, the module marks the memory as valid by calling `sink_commit_buffer()` for the exact number of frames successfully written. |
0 commit comments