You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP-IDF框架下ESP32无正常音频输出问题求助

问题描述

我尝试让Raspiaudio Muse Proto播放测试用正弦波,但仅听到非常微弱的脉动,和预期输出完全不符。以下是完整代码:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "driver/i2s.h"
#include <math.h>
#include <vector>
#include <sstream>

#define I2S_BCLK    5
#define I2S_LRC    25
#define I2S_DOUT   26
#define I2S_DIN    35

/** This is necessary for the linker to find the firmware's entry point */
extern "C" {
    void app_main(void);
}

#define SAMPLE_RATE     (44100)
const i2s_port_t I2S_NUM = I2S_NUM_0;
#define PI              (3.14159265)
#define FREQUENCY       (440)
#define SAMPLES         (SAMPLE_RATE / FREQUENCY)

typedef std::vector<int16_t> VectorBuffer;

VectorBuffer generate_sine_wave(
    float sample_rate_Hz,
    float duration_s = 1.0f,
    float frequency = 440.0f,
    float amplitude = 1.0f
) {
    const auto samples_count = (int)(sample_rate_Hz * duration_s * 2);
    VectorBuffer buffer(samples_count);

    for (int i = 0; i < samples_count - 1; i++) {
        const auto sample = (int16_t)(amplitude * sin((2 * PI * i * frequency) / sample_rate_Hz));
        buffer.at(i) = sample;
        buffer.at(i + 1) = sample;
    }

    return buffer;
}

// Should play a F note for half a second every second
void i2s_task(void *pvParameter) {
    while (1) {
        auto buffer = generate_sine_wave(
            SAMPLE_RATE,
            1.0f,
            440.0f,
            floor(INT16_MAX / 3)
        );

        ESP_LOGI("I2S_TASK", "Playing sine wave of %d samples", buffer.size());

        size_t bytes_written;

        for (;;) {
            if (buffer.empty()) {
                ESP_LOGD("I2S_TASK", "Buffer is empty, good.");
                break;
            }

            esp_err_t e = i2s_write(I2S_NUM, buffer.data(), std::fmin(buffer.size(), 1024), &bytes_written, portMAX_DELAY);
            if (e != ESP_OK) {
                ESP_LOGE("I2S_TASK", "Error writing to I2S: %d", e);
                break;
            }

            if (bytes_written == 0) {
                // this should not happen
                ESP_LOGE("I2S_TASK", "No bytes written");
                break;
            }

            ESP_LOGI("I2S_TASK", "Wrote %d bytes", bytes_written);

            /* Remove too verbose logging
            std::ostringstream oss;
            for (int i = 0; i < bytes_written; i++) {
                oss << buffer.at(i) << " ";
            }
            ESP_LOGD("I2S_TASK", "Buffer: %s", oss.str().c_str());
            // */
            buffer.erase(buffer.begin(), buffer.begin() + bytes_written);
        };

        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void hello_task(void *pvParameter) {
    while (1) {
        ESP_LOGI("HELLO_TASK", "hello");
        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
}

void app_main() {
    // UART configuration
    const uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };

    uart_param_config(UART_NUM_0, &uart_config);
    uart_driver_install(UART_NUM_0, 1024 * 2, 0, 0, NULL, 0);

    const auto mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);

    // I2S configuration
    i2s_config_t i2s_config = {
        mode,
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S | I2S_COMM_FORMAT_STAND_MSB),
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 3,
        .dma_buf_len = 1024,
        .use_apll = false,
        .tx_desc_auto_clear = true,
        .fixed_mclk = -1,
        .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
        .bits_per_chan = I2S_BITS_PER_CHAN_16BIT,
    };

    // I2S pin configuration
    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_BCLK,
        .ws_io_num = I2S_LRC,
        .data_out_num = I2S_DOUT,
        .data_in_num = I2S_DIN, // default Copilot was: I2S_PIN_NO_CHANGE // maybe 35 https://github.com/RASPIAUDIO/Simple_Bluetooth_Speaker_ESP32/blob/884c7f3700ec92dcf000dca75934860833559df7/speaker/speaker.ino#L171
    };

    // Install and start I2S driver
    const auto e_i = i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    if (e_i != ESP_OK) {
        ESP_LOGE("APP_MAIN", "Error installing I2S driver: %d", e_i);
        return;
    }

    const auto e_p = i2s_set_pin(I2S_NUM, &pin_config);
    if (e_p != ESP_OK) {
        ESP_LOGE("APP_MAIN", "Error setting I2S pin: %d", e_p);
        return;
    }

    const auto e_s = i2s_start(I2S_NUM);
    if (e_s != ESP_OK) {
        ESP_LOGE("APP_MAIN", "Error starting I2S: %d", e_s);
        return;
    }

    // Create the hello_task
    xTaskCreate(&hello_task, "hello_task", 2048, NULL, 5, NULL);

    // Create the i2s_task
    xTaskCreate(&i2s_task, "i2s_task", 2048, NULL, 5, NULL);
}
排查思路
  • 修正I2S写入的字节数处理逻辑
    i2s_write的第三个参数是字节数,但你传入的是vector<int16_t>的元素个数,每个元素占2字节,导致实际写入数据只有预期的一半。正确写法应该是:
    esp_err_t e = i2s_write(I2S_NUM, buffer.data(), std::fmin(buffer.size() * sizeof(int16_t), 1024), &bytes_written, portMAX_DELAY);
    
    同时buffer.erase时要把写入的字节数转换成元素个数:
    buffer.erase(buffer.begin(), buffer.begin() + (bytes_written / sizeof(int16_t)));
    
  • 修复正弦波生成的逻辑错误
    当前循环条件i < samples_count - 1会导致最后一个数组位置未被赋值,且重复写入两次样本的逻辑会导致音频数据冗余。修正后的生成逻辑:
    const auto samples_per_channel = (int)(sample_rate_Hz * duration_s);
    VectorBuffer buffer(samples_per_channel * 2); // 双声道总样本数
    
    for (int i = 0; i < samples_per_channel; i++) {
        const auto sample = (int16_t)(amplitude * sin((2 * PI * i * frequency) / sample_rate_Hz));
        buffer[2*i] = sample;     // 左声道
        buffer[2*i + 1] = sample; // 右声道
    }
    
  • 核对I2S引脚与设备匹配
    你设置的data_in_num = 35但仅使用TX模式,应改为I2S_PIN_NO_CHANGE避免占用不必要的引脚。同时再次确认Raspiaudio Muse Proto的官方引脚定义,确保BCLK、LRC、DOUT的引脚编号正确。
  • 调整I2S配置参数
    开启use_apll = true,APLL能提供更精准的采样率,避免因采样率不匹配导致的音频失真;可尝试调整dma_buf_count = 6、dma_buf_len = 512优化缓存性能。
  • 测试基础音频输出
    暂时替换正弦波生成逻辑,直接写入固定幅值的音频数据(比如int16_t test_sample = INT16_MAX / 3;)填充整个buffer,若能听到稳定声音,说明问题出在正弦波生成逻辑;若仍微弱,排查I2S配置或硬件连接。
  • 检查声道格式匹配
    当前设置I2S_CHANNEL_FMT_RIGHT_LEFT双声道,若Muse Proto实际为单声道设备,可尝试切换为I2S_CHANNEL_FMT_ONLY_LEFT或I2S_CHANNEL_FMT_ONLY_RIGHT测试。

内容的提问来源于stack exchange,提问作者djfm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:58:32