# K230 Audio 模块接口文档

## Audio 模块概述

### K230 音频模块详解

`K230 Audio` 模块深度整合 K230 开发板的硬件音频能力，通过抽象化封装形成一套完整的音频开发接口。该模块覆盖了从音频采集（输入）到播放（输出）的全流程功能，让开发者无需关注硬件实现即可快速上手。核心特性如下：

#### 一、多通路音频接口支持
1. **I2S 通路**
   - **双向传输能力**：既支持音频输入（采集）也支持输出（播放），默认适配板载硬件资源。
   - **模拟链路（内置 Codec）**：通过板载模拟麦克风采集声音、耳机接口输出音频，内置编解码器（Codec）负责完成模数/数模转换，简化模拟音频设备的接入流程。
   - **数字链路（可选）**：禁用内置 Codec 时可直接传输 I2S 数字信号，支持外接专业数字音频设备（如数字麦克风、DAC 模块）。

2. **PDM 通路**
   - 专为数字麦克风设计，支持外接 PDM 音频子板，最多可实现 **8 声道同时采集**，适用于多声源场景（如阵列麦克风）。

#### 二、核心功能特性
1. **全链路音频处理**
   - 支持录音（输入）与播放（输出）双向操作，兼容常见音频参数（采样率 8000~96000Hz、16/24/32 位采样精度、单/立体声）。
   - 硬件编解码器（Codec）自动控制，简化模拟信号与数字信号的转换流程。

2. **音频增强能力**
   - **3A 处理**：集成 ANS（噪声抑制）、AGC（自动增益控制）、AEC（回声消除），提升录音清晰度（仅输入流支持）。
   - **音量调节**：支持左右声道独立音量控制（0~100 级），可快速适配不同播放设备。
   - **声道管理**：提供左右声道交换功能，灵活适配音频场景需求。

#### 三、硬件适配与应用场景
- **板载资源**：配备模拟麦克风（I2S 通路输入）和耳机输出接口（I2S 通路输出），开箱即可完成基础录音与播放验证。
- **扩展能力**：通过 PDM 子板扩展多声道数字麦克风，满足多声源采集场景（如语音阵列、环境声监测）。
- **典型应用**：
  - 单路语音录制（如语音助手、录音笔）
  - 音频播放（如背景音乐、提示音输出）
  - 多声道音频采集（如会议记录、声场分析）

## API 接口调用流程

### 录音流程
1. 创建 `ArduinoAudio` 管理对象
2. 调用 `open()` 方法创建输入流（`input=true`）
3. （可选）配置音频参数：`volume()` 调节音量、`enable_audio3a()` 启用 3A 功能
4. 调用 `start_stream()` 启动录音
5. 循环调用 `read()` 读取音频数据
6. 录音完成后调用 `stop_stream()` 停止流
7. 调用 `close()` 释放资源

### 播放流程
1. 创建 `ArduinoAudio` 管理对象
2. 调用 `open()` 方法创建输出流（`output=true`）
3. （可选）调用 `volume()` 调节播放音量
4. 调用 `start_stream()` 启动播放
5. 循环调用 `write()` 写入音频数据
6. 播放完成后调用 `stop_stream()` 停止流
7. 调用 `close()` 释放资源

### 录音和播放同时进行流程
1. 创建 `ArduinoAudio` 管理对象
2. 分别调用 `open()` 方法创建输入流和输出流
3. （可选）分别配置输入流和输出流参数（音量、3A 功能等）
4. 分别调用两个流的 `start_stream()` 启动流
5. 创建两个线程：
   - 录音线程：循环调用输入流的 `read()` 获取音频数据
   - 播放线程：循环调用输出流的 `write()` 播放获取的音频数据
6. 结束时分别调用两个流的 `stop_stream()` 停止流
7. 调用 `close()` 释放两个流的资源

## 类定义

### AudioStream 基类

音频流的基类，定义了输入输出流的通用接口。

```cpp
class AudioStream {
public:
    AudioStream(int rate, int channels, AudioFormat format, bool input, bool output,
               int input_device_index, int output_device_index, bool enable_codec,
               int frames_per_buffer, bool start);
    virtual ~AudioStream() = default;

    virtual void start_stream() = 0;
    virtual void stop_stream() = 0;
    virtual int read(std::vector<uint8_t>& data, int chn = 0, bool block = true) = 0;
    virtual int write(const std::vector<uint8_t>& data) = 0;
    virtual void close() = 0;
    virtual int volume(int vol = -1, Channel channel = LEFT_RIGHT) = 0;
    virtual int swap_left_right() = 0;
    virtual int enable_audio3a(int audio3a_value) = 0;
    virtual int audio3a_send_far_echo_frame(const std::vector<uint8_t>& frame_data, int data_len) = 0;

    bool is_running() const;
    bool is_input() const;
    bool is_output() const;
    int get_rate() const;
    int get_channels() const;
    int get_format() const;
};
```

### WriteStream 输出流类

音频输出流，用于播放音频数据。

```cpp
class WriteStream : public AudioStream {
public:
    WriteStream(int rate, int channels, AudioFormat format, bool input, bool output,
                int input_device_index, int output_device_index, bool enable_codec,
                int frames_per_buffer, bool start);
    ~WriteStream();

    void start_stream();
    void stop_stream();
    int read(std::vector<uint8_t>& data, int chn = 0, bool block = true);
    int write(const std::vector<uint8_t>& data);
    void close();
    int volume(int vol = -1, Channel channel = LEFT_RIGHT);
    int swap_left_right();
    int enable_audio3a(int audio3a_value);
    int audio3a_send_far_echo_frame(const std::vector<uint8_t>& frame_data, int data_len);
};
```

### ReadStream 输入流类

音频输入流，用于录制音频数据。

```cpp
class ReadStream : public AudioStream {
public:
    ReadStream(int rate, int channels, AudioFormat format, bool input, bool output,
               int input_device_index, int output_device_index, bool enable_codec,
               int frames_per_buffer, bool start);
    ~ReadStream();

    void start_stream();
    void stop_stream();
    int read(std::vector<uint8_t>& data, int chn = 0, bool block = true);
    int write(const std::vector<uint8_t>& data);
    void close();
    int volume(int vol = -1, Channel channel = LEFT_RIGHT);
    int swap_left_right();
    int enable_audio3a(int audio3a_value);
    int audio3a_send_far_echo_frame(const std::vector<uint8_t>& frame_data, int data_len);
};
```

### ArduinoAudio 管理类

音频系统管理类，负责创建和管理音频流。

```cpp
class ArduinoAudio {
public:
    ArduinoAudio();
    ~ArduinoAudio();

    AudioStream* open(int rate, int channels, AudioFormat format,
                                bool input = false, bool output = false,
                                int input_device_index = -1, int output_device_index = -1,
                                bool enable_codec = true, int frames_per_buffer = 1024,
                                bool start = true);
    void close(AudioStream* stream);
    int get_sample_size(int format);
    int get_format_from_width(int width);
};
```

## 枚举类型说明

### AudioFormat 音频格式

```cpp
enum AudioFormat {
    paInt16 = 0,   // 16位采样
    paInt24 = 1,   // 24位采样
    paInt32 = 2    // 32位采样
};
```

### Channel 音频通道

```cpp
enum Channel {
    LEFT = 1,       // 左音频通道
    RIGHT = 2,      // 右音频通道
    LEFT_RIGHT = 3  // 左右双通道
};
```

### Audio3AEnable 3A 功能使能

```cpp
enum Audio3AEnable {
    AUDIO_3A_ENABLE_NONE = 0,  // 不启用 3A 功能
    AUDIO_3A_ENABLE_ANS = 1,   // 启用主动噪声抑制
    AUDIO_3A_ENABLE_AGC = 2,   // 启用自动增益控制
    AUDIO_3A_ENABLE_AEC = 4    // 启用回声消除
};
```

### DeviceType 设备类型

```cpp
enum DeviceType {
    DEVICE_I2S = 0,  // I2S 音频设备
    DEVICE_PDM = 1   // PDM 音频设备
};
```

## 核心接口说明

### ArduinoAudio::open - 创建音频流

```cpp
AudioStream* open(int rate, int channels, AudioFormat format,
                  bool input = false, bool output = false,
                  int input_device_index = -1, int output_device_index = -1,
                  bool enable_codec = true, int frames_per_buffer = 1024,
                  bool start = true);
```

**功能：**
创建并配置音频输入或输出流。

**参数说明：**

| 参数名                 | 类型            | 描述                     | 默认值     |
| ------------------- | ------------- | ---------------------- | ------- |
| rate                | `int`         | 采样率（Hz）               | 无       |
| channels            | `int`         | 通道数（1-单声道，2-立体声）     | 无       |
| format              | `AudioFormat` | 音频采样格式                | 无       |
| input               | `bool`        | 是否为输入流               | false   |
| output              | `bool`        | 是否为输出流               | false   |
| input_device_index  | `int`         | 输入通路索引 [0,1]，默认值为 -1（使用默认通路 0）。0：I2S 通路（由 enable_codec 决定具体链路：启用时为内置音频 codec 的模拟通路，禁用时为 I2S 数字通路）；1：PDM 数字通路 | -1      |
| output_device_index | `int`         | 输出通路索引 [0,1]，默认值为 -1（使用默认通路 0）。0：I2S 通路（由 enable_codec 决定具体链路：启用时为内置音频 codec 的模拟通路，禁用时为 I2S 数字通路）；1：固定为 I2S 数字通路       | -1      |
| enable_codec        | `bool`        | 是否启用内置音频codec          | true    |
| frames_per_buffer   | `int`         | 每个缓冲区的帧数              | 1024    |
| start               | `bool`        | 是否立即启动流              | true    |

**返回值：**
- 成功：指向 `AudioStream` 对象的指针
- 失败：抛出 `std::exception` 异常

**注意事项：**
- `input` 和 `output` 必须有一个为 true，且不能同时为 true
- 常用采样率：8000、16000、24000、32000、44100、48000、96000 Hz

### AudioStream::start_stream - 启动音频流

```cpp
void start_stream();
```

**功能：**
启动音频流，开始录音或播放。

**异常：**
- 启动失败：`std::runtime_error`

### AudioStream::stop_stream - 停止音频流

```cpp
void stop_stream();
```

**功能：**
停止音频流，结束录音或播放。

**异常：**
- 停止失败：`std::runtime_error`

### ReadStream::read - 读取音频数据

```cpp
int read(std::vector<uint8_t>& data, int chn = 0, bool block = true);
```

**功能：**
从输入流读取音频数据。

**参数说明：**

| 参数名   | 类型       | 描述                          | 默认值  |
| ----- | -------- | --------------------------- | ---- |
| data  | `vector<uint8_t>&` | 接收音频数据的缓冲区              | 无    |
| chn   | `int`    | 通道索引（PDM 设备支持多通道）     | 0    |
| block | `bool`   | 是否阻塞等待数据                 | true |

**返回值：**
- 成功：0
- 失败：-1

### WriteStream::write - 写入音频数据

```cpp
int write(const std::vector<uint8_t>& data);
```

**功能：**
向输出流写入音频数据进行播放。

**参数说明：**

| 参数名  | 类型                  | 描述         |
| ---- | ------------------- | ---------- |
| data | `const vector<uint8_t>&` | 要播放的音频数据 |

**返回值：**
- 成功：0
- 失败：-1

### AudioStream::volume - 音量控制

```cpp
int volume(int vol = -1, Channel channel = LEFT_RIGHT);
```

**功能：**
设置或获取音量。

**参数说明：**

| 参数名     | 类型        | 描述                          | 默认值        |
| ------- | --------- | --------------------------- | ---------- |
| vol     | `int`     | 音量值（0-100），-1 表示获取当前音量 | -1         |
| channel | `Channel` | 要操作的通道                    | LEFT_RIGHT |

**返回值：**
- 设置音量：成功返回 0，失败返回 -1
- 获取音量：返回当前音量值（0-100）

### AudioStream::swap_left_right - 声道交换

```cpp
int swap_left_right();
```

**功能：**
交换左右声道。

**返回值：**
- 成功：0
- 失败：-1

---

### ReadStream::enable_audio3a - 启用 3A 功能

```cpp
int enable_audio3a(int audio3a_value);
```

**功能：**
启用音频 3A 处理功能（仅输入流支持）。

**参数说明：**

| 参数名           | 类型   | 描述                                       |
| ------------- | ---- | ---------------------------------------- |
| audio3a_value | `int` | 3A 功能位掩码，可使用 `AUDIO_3A_ENABLE_*` 枚举组合 |

**返回值：**
- 成功：0
- 失败：-1

**示例：**
```cpp
// 启用噪声抑制和自动增益控制
stream->enable_audio3a(AUDIO_3A_ENABLE_ANS | AUDIO_3A_ENABLE_AGC);
```

## 使用示例

### i2s音频录制示例

该示例程序展示了 K230 开发板的i2s通路音频采集功能。

```cpp
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
#include <signal.h>
#include <atomic>

#include <k230_audio.h>
#include <k230_wave.h>

static std::atomic<bool> g_exit_requested(false);

// Signal handler for graceful exit
static void signal_handler(int signal) {
    std::cout << "Received interrupt signal, stopping..." << std::endl;
    g_exit_requested.store(true);
}

// Record audio and save as WAV file
static void record_audio(const std::string& filename, int duration_seconds) {
    const int CHUNK = 44100 / 25;  // Audio chunk size
    const AudioFormat FORMAT = paInt16;  // 16-bit sample precision
    const int CHANNELS = 2;        // Stereo
    const int RATE = 44100;        // Sample rate

    try {
        ArduinoAudio audio;

        // Create audio input stream
        AudioStream* stream = audio.open(RATE, CHANNELS, FORMAT,
                                   true, false,  // Input stream
                                   -1, -1,       // Default device
                                   true,         // Enable codec
                                   CHUNK,        // Frames per buffer
                                   false);       // Do not start immediately

        // Set volume
        stream->volume(70, LEFT);
        stream->volume(85, RIGHT);
        std::cout << "Input volume set" << std::endl;

        // Enable audio 3A feature: Automatic Noise Suppression
        stream->enable_audio3a(AUDIO_3A_ENABLE_ANS);
        std::cout << "Enabled audio 3A: ANS" << std::endl;

        // Start stream
        stream->start_stream();

        std::vector<std::vector<uint8_t>> frames;
        int total_chunks = (RATE / CHUNK) * duration_seconds;

        std::cout << "Start recording for " << duration_seconds << " seconds..." << std::endl;

        // Collect audio data
        for (int i = 0; i < total_chunks && !g_exit_requested.load(); ++i) {
            std::vector<uint8_t> data;
            if (stream->read(data, 0, true) == 0) {
                frames.push_back(std::move(data));
            }

            // Display progress
            if (i % 25 == 0) {
                std::cout << "Recording... " << (i * 100 / total_chunks) << "%" << std::endl;
            }
        }

        // Stop stream
        stream->stop_stream();
        std::cout << "Recording finished, saving to file..." << std::endl;

        // Save as WAV file
        auto wf = open_write(filename);
        wf->set_channels(CHANNELS);
        wf->set_sampwidth(audio.get_sample_size(FORMAT));
        wf->set_framerate(RATE);

        // Merge all frame data
        std::vector<char> all_data;
        for (const auto& frame : frames) {
            all_data.insert(all_data.end(),
                          reinterpret_cast<const char*>(frame.data()),
                          reinterpret_cast<const char*>(frame.data() + frame.size()));
        }

        wf->write_frames(all_data);
        wf->close();

        // Clean up resources
        audio.close(stream);

        std::cout << "Audio saved to: " << filename << std::endl;

    } catch (const std::exception& e) {
        std::cerr << "Error in record_audio: " << e.what() << std::endl;
    }
}


void setup()
{
    record_audio("/data/test.wav", 15); // Record WAV file

}

void loop()
{

}
```

### i2s音频播放示例

该示例程序展示了 K230 开发板的i2s通路音频播放功能。

```cpp
// audio_demo.cpp
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
#include <signal.h>
#include <atomic>

#include <k230_audio.h>
#include <k230_wave.h>

static std::atomic<bool> g_exit_requested(false);

// Signal handler for graceful exit
static void signal_handler(int signal) {
    std::cout << "Received interrupt signal, stopping..." << std::endl;
    g_exit_requested.store(true);
}

// Play WAV file
static void play_audio(const std::string& filename) {
    try {
        // Open WAV file
        auto wf = open_read(filename);
        int channels = wf->get_channels();
        int sampwidth = wf->get_sampwidth();
        int framerate = wf->get_framerate();
        int chunk = framerate / 25;  // Calculate appropriate chunk size

        ArduinoAudio audio;

        // Create audio output stream
        AudioFormat format = static_cast<AudioFormat>(audio.get_format_from_width(sampwidth));
        AudioStream* stream = audio.open(framerate, channels, format,
                                   false, true,   // Output stream
                                   -1, -1,        // Default device
                                   true,          // Enable codec
                                   chunk,         // Frames per buffer
                                   false);        // Do not start immediately

        // Set volume
        stream->volume(85);
        std::cout << "Output volume set to 85" << std::endl;

        // Start stream
        stream->start_stream();

        std::cout << "Start playing: " << filename << std::endl;

        // Play audio data
        auto data = wf->read_frames(chunk);
        while (!data.empty() && !g_exit_requested.load()) {
            // Convert char data to uint8_t
            std::vector<uint8_t> audio_data(data.begin(), data.end());
            stream->write(audio_data);

            data = wf->read_frames(chunk);
        }

        // Stop stream
        stream->stop_stream();

        // Clean up resources
        audio.close(stream);
        wf->close();

        std::cout << "Playback finished" << std::endl;

    } catch (const std::exception& e) {
        std::cerr << "Error in play_audio: " << e.what() << std::endl;
    }
}

void setup()
{
    play_audio("/data/test.wav");  //Play WAV file
}

void loop()
{

}
```

### pdm音频多声道录制示例

该示例程序展示了 K230 开发板的pdm通路8声道音频录制功能。

```cpp
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
#include <signal.h>
#include <atomic>
#include <iomanip>

#include <k230_audio.h>
#include <k230_wave.h>

static void record_audio_pdm(const std::string& base_filename, int duration, int num_channels) {
    const int CHUNK = 44100 / 25;  // Audio buffer size
    AudioFormat format = paInt16;   // Sample precision
    const int RATE = 44100;         // Sample rate
    int pdm_chn_cnt = num_channels / 2;

    /*
    * Initialize PDM audio IO
    *
    * Configure GPIO pin functions for PDM audio acquisition on the Lushanpai development board:
    * - Map PDM clock line and data lines to specified physical pins
    * - Set pin directions (input/output mode)
    *
    * Specific pin assignments:
    * - Pin 26: PDM clock line (PDM_CLK)  → Output mode
    * - Pin 27: PDM data 0 line (PDM_IN0) → Input mode
    * - Pin 35: PDM data 1 line (PDM_IN1) → Input mode
    * - Pin 36: PDM data 2 line (PDM_IN2) → Input mode
    * - Pin 34: PDM data 3 line (PDM_IN3) → Input mode
    */
    if (0 != ArduinoAudio::pdm_audio_pin_config(26, 27, 35, 36, 34)){
        printf("pdm_audio_pin_config failed\n");
        return ;
    }

    try {
        ArduinoAudio audio;

        // Open audio input stream
        AudioStream* stream = audio.open(
            RATE,
            num_channels,
            format,
            true,   // Input stream
            false,  // Not output stream
            1,      // PDM device index
            -1,     // Do not use output device
            false,  // Enable hardware codec
            CHUNK,  // Frames per buffer
            false   // Start stream immediately
        );

        if (!stream || !stream->is_input()) {
            throw std::runtime_error("Failed to create input audio stream");
        }

        // Start the stream
        stream->start_stream();

        // Initialize audio frame storage for each channel
        std::vector<std::vector<uint8_t>> channel_frames(pdm_chn_cnt);

        // Calculate total number of frames
        int total_frames = static_cast<int>(RATE / CHUNK * duration);

        std::cout << "Starting recording of " << pdm_chn_cnt << " groups of "
                  << num_channels << " channels PDM audio, duration " << duration << " seconds..." << std::endl;

        // Collect audio data
        for (int i = 0; i < total_frames; ++i) {
            for (int ch = 0; ch < pdm_chn_cnt; ++ch) {
                std::vector<uint8_t> data;
                if (stream->read(data, ch, true) == 0) {
                    // Append read data to corresponding channel buffer
                    channel_frames[ch].insert(channel_frames[ch].end(),
                                            data.begin(), data.end());
                }
            }

            // Print progress every 25 frames
            if (i % 25 == 0) {
                float progress = (static_cast<float>(i) / total_frames) * 100;
                std::cout << "Recording progress: " << std::fixed << std::setprecision(2) << progress << "%" << std::endl;
            }
        }

        stream->stop_stream();

        std::cout << "Recording progress: " << std::fixed << std::setprecision(2) << 100 << "%" << std::endl;

        // Save each channel's data to WAV file
        for (int ch = 0; ch < pdm_chn_cnt; ++ch) {
            std::string filename = base_filename + "_ch" + std::to_string(ch) + ".wav";

            try {
                WaveWrite wf(filename);
                wf.set_channels(2);  // Save stereo in each file
                wf.set_sampwidth(audio.get_sample_size(format));  // Set sample precision
                wf.set_framerate(RATE);  // Set sample rate

                // Convert uint8_t vector to char vector
                std::vector<char> char_data(channel_frames[ch].begin(),
                                         channel_frames[ch].end());
                wf.write_frames(char_data);

                std::cout << "Saved channels " << ch*2 << "," << ch*2+1
                          << " to " << filename << std::endl;
            } catch (const WaveError& e) {
                std::cerr << "Failed to save WAV file: " << e.what() << std::endl;
            }
        }

        // Clean up resources
        audio.close(stream);

    } catch (const std::exception& e) {
        std::cerr << "Error occurred: " << e.what() << std::endl;
    }

    std::cout << "Recording completed, resources released" << std::endl;
}


void setup()
{
    record_audio_pdm("/data/pdm", 15, 8);
}

void loop()
{

}
```