logo单片机在线
音频测试平台

ESP32 / STM32 HTTP Audio Streaming Test Platform

ESP32 STM32 HTTP/1.1 MicroPython Arduino

📖 平台简介

本平台专为ESP32、STM32等微控制器提供在线音频文件访问服务,用于测试和开发基于HTTP协议的网络音频播放功能。

✨ 特性

🎯 适用场景

🚀 快速开始

💡 提示: 所有音频文件均为单声道WAV格式,支持三种采样率。根据您的硬件性能选择合适的采样率。

基本使用流程

  1. 选择合适的音频文件(查看下方音频列表)
  2. 复制音频文件的URL地址
  3. 在您的微控制器代码中使用HTTP GET请求获取音频数据
  4. 将音频数据发送到DAC或音频解码芯片进行播放

URL格式

http://audio.wuhanqing.cn/sound/[文件名].wav

示例URL

示例
// 11025Hz 采样率
http://audio.wuhanqing.cn/sound/Sparkle_mono_11025.wav

// 22050Hz 采样率
http://audio.wuhanqing.cn/sound/Sparkle_mono_22050.wav

// 44100Hz 采样率(高质量)
http://audio.wuhanqing.cn/sound/Sparkle_mono_44100.wav

📱 ESP32 使用示例

1️⃣ MicroPython + I2S DAC 示例

⚠️ 硬件要求: ESP32开发板、I2S DAC模块(如PCM5102A、MAX98357A等)、喇叭或耳机

🔌 接线示意图(PCM5102A)

ESP32 GPIO25 (BCK) ──→ PCM5102A BCK
ESP32 GPIO26 (WS) ──→ PCM5102A LCK
ESP32 GPIO22 (SD) ──→ PCM5102A DIN
ESP32 GND ──→ PCM5102A GND
ESP32 3.3V ──→ PCM5102A VIN
PCM5102A SCK ──→ GND (接地)
PCM5102A OUT ──→ 喇叭/耳机

📝 MicroPython 完整代码

Python (MicroPython)
import urequests
import network
from machine import I2S, Pin
import time

# WiFi配置
SSID = "你的WiFi名称"
PASSWORD = "你的WiFi密码"

# 音频文件URL
AUDIO_URL = "http://audio.wuhanqing.cn/sound/Sparkle_mono_22050.wav"

# I2S配置
SCK_PIN = 25  # BCK (Bit Clock)
WS_PIN = 26   # WS (Word Select / LRCK)
SD_PIN = 22   # SD (Serial Data)
SAMPLE_RATE = 22050  # 采样率需要与音频文件匹配
BITS_PER_SAMPLE = 16

def connect_wifi():
    """连接WiFi"""
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    
    if not wlan.isconnected():
        print('正在连接WiFi...')
        wlan.connect(SSID, PASSWORD)
        
        timeout = 0
        while not wlan.isconnected() and timeout < 20:
            time.sleep(1)
            timeout += 1
            print('.', end='')
        
        if wlan.isconnected():
            print('\nWiFi连接成功!')
            print('IP地址:', wlan.ifconfig()[0])
            return True
        else:
            print('\nWiFi连接失败!')
            return False
    return True

def parse_wav_header(data):
    """解析WAV文件头"""
    if data[0:4] != b'RIFF':
        raise ValueError("不是有效的WAV文件")
    
    # 查找data chunk
    pos = 12
    while pos < len(data):
        chunk_id = data[pos:pos+4]
        chunk_size = int.from_bytes(data[pos+4:pos+8], 'little')
        
        if chunk_id == b'data':
            return pos + 8  # 返回音频数据开始位置
        
        pos += 8 + chunk_size
    
    raise ValueError("未找到音频数据")

def play_audio_stream():
    """播放在线音频"""
    try:
        # 配置I2S
        audio_out = I2S(
            0,
            sck=Pin(SCK_PIN),
            ws=Pin(WS_PIN),
            sd=Pin(SD_PIN),
            mode=I2S.TX,
            bits=BITS_PER_SAMPLE,
            format=I2S.MONO,
            rate=SAMPLE_RATE,
            ibuf=20000
        )
        
        print('开始下载音频文件...')
        print('URL:', AUDIO_URL)
        
        # 发送HTTP GET请求
        response = urequests.get(AUDIO_URL, stream=True)
        
        if response.status_code != 200:
            print(f'HTTP错误: {response.status_code}')
            return
        
        print('开始播放音频...')
        
        # 读取并跳过WAV文件头
        header_data = response.raw.read(200)
        data_start = parse_wav_header(header_data)
        
        # 如果data_start在已读取的数据范围内,调整位置
        if data_start < 200:
            audio_data = header_data[data_start:]
            audio_out.write(audio_data)
        
        # 流式播放音频数据
        chunk_size = 4096
        total_bytes = 0
        
        while True:
            audio_data = response.raw.read(chunk_size)
            if not audio_data:
                break
            
            audio_out.write(audio_data)
            total_bytes += len(audio_data)
            
            if total_bytes % (chunk_size * 10) == 0:
                print(f'已播放: {total_bytes} 字节')
        
        print(f'播放完成! 总计: {total_bytes} 字节')
        
        # 清理资源
        audio_out.deinit()
        response.close()
        
    except Exception as e:
        print('播放错误:', e)
        import sys
        sys.print_exception(e)

# 主程序
if __name__ == '__main__':
    print('=== ESP32 网络音频播放器 ===')
    
    if connect_wifi():
        time.sleep(2)
        play_audio_stream()
    else:
        print('无法连接WiFi,程序退出')

2️⃣ Arduino + VS1053 解码器示例

🔌 接线示意图(VS1053)

ESP32 GPIO18 (SCK) ──→ VS1053 SCK
ESP32 GPIO23 (MOSI) ──→ VS1053 MOSI
ESP32 GPIO19 (MISO) ──→ VS1053 MISO
ESP32 GPIO5 (XCS) ──→ VS1053 XCS
ESP32 GPIO4 (XDCS) ──→ VS1053 XDCS
ESP32 GPIO2 (DREQ) ──→ VS1053 DREQ
ESP32 GND ──→ VS1053 GND
ESP32 5V ──→ VS1053 5V

📝 Arduino 完整代码

C++ (Arduino)
#include <WiFi.h>
#include <HTTPClient.h>
#include <VS1053.h>  // 需要安装 VS1053 库

// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";

// 音频URL
const char* audioURL = "http://audio.wuhanqing.cn/sound/Sparkle_mono_22050.wav";

// VS1053引脚定义
#define VS1053_CS     5   // XCS
#define VS1053_DCS    4   // XDCS
#define VS1053_DREQ   2   // DREQ

VS1053 player(VS1053_CS, VS1053_DCS, VS1053_DREQ);

void setup() {
  Serial.begin(115200);
  Serial.println("\n=== ESP32 网络音频播放器 ===");
  
  // 初始化VS1053
  SPI.begin();
  player.begin();
  player.setVolume(80);  // 音量 0-100
  
  // 连接WiFi
  connectWiFi();
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    playAudioFromURL(audioURL);
    delay(5000);  // 播放完成后等待5秒
  } else {
    Serial.println("WiFi未连接,正在重连...");
    connectWiFi();
  }
}

void connectWiFi() {
  Serial.print("正在连接WiFi: ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  int timeout = 0;
  while (WiFi.status() != WL_CONNECTED && timeout < 20) {
    delay(500);
    Serial.print(".");
    timeout++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi连接成功!");
    Serial.print("IP地址: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("\nWiFi连接失败!");
  }
}

void playAudioFromURL(const char* url) {
  HTTPClient http;
  
  Serial.println("开始下载音频...");
  Serial.print("URL: ");
  Serial.println(url);
  
  http.begin(url);
  int httpCode = http.GET();
  
  if (httpCode == HTTP_CODE_OK) {
    WiFiClient* stream = http.getStreamPtr();
    int totalBytes = http.getSize();
    int bytesRead = 0;
    
    Serial.println("开始播放音频...");
    Serial.print("文件大小: ");
    Serial.print(totalBytes);
    Serial.println(" 字节");
    
    // 跳过WAV文件头(44字节)
    uint8_t header[44];
    stream->readBytes(header, 44);
    bytesRead += 44;
    
    // 流式播放音频数据
    const int bufferSize = 32;
    uint8_t buffer[bufferSize];
    
    while (http.connected() && (bytesRead < totalBytes || totalBytes == -1)) {
      size_t available = stream->available();
      
      if (available) {
        int c = stream->readBytes(buffer, min((size_t)bufferSize, available));
        
        // 发送到VS1053
        player.playChunk(buffer, c);
        
        bytesRead += c;
        
        if (bytesRead % 10240 == 0) {
          Serial.print("已播放: ");
          Serial.print(bytesRead);
          Serial.print(" / ");
          Serial.print(totalBytes);
          Serial.println(" 字节");
        }
      }
      
      delay(1);
    }
    
    Serial.println("播放完成!");
  } else {
    Serial.print("HTTP错误: ");
    Serial.println(httpCode);
  }
  
  http.end();
}

💻 STM32 使用示例

1️⃣ STM32 + ESP8266 + PWM DAC 示例

⚠️ 硬件要求: STM32F103C8T6、ESP8266 WiFi模块、RC滤波器(用于PWM DAC)、喇叭

🔌 接线示意图

ESP8266 (WiFi模块):
STM32 PA9 (TX) ──→ ESP8266 RX
STM32 PA10 (RX) ──→ ESP8266 TX
STM32 3.3V ──→ ESP8266 VCC & CH_PD
STM32 GND ──→ ESP8266 GND

PWM DAC 输出:
STM32 PA8 (TIM1_CH1) ──→ 10kΩ电阻 ──→ 100nF电容 ──→ GND
└──→ 喇叭正极
喇叭负极 ──→ GND

📝 STM32 HAL库代码(主要部分)

C (STM32 HAL)
/* main.c */
#include "main.h"
#include "string.h"
#include "stdio.h"

// UART和TIM句柄
UART_HandleTypeDef huart1;  // ESP8266通信
UART_HandleTypeDef huart2;  // 调试串口
TIM_HandleTypeDef htim1;    // PWM for DAC

// 配置
#define WIFI_SSID "你的WiFi名称"
#define WIFI_PASSWORD "你的WiFi密码"
#define AUDIO_URL "audio.wuhanqing.cn"
#define AUDIO_PATH "/sound/Sparkle_mono_11025.wav"

// 缓冲区
#define RX_BUFFER_SIZE 1024
uint8_t rxBuffer[RX_BUFFER_SIZE];
uint16_t rxIndex = 0;

// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_TIM1_Init(void);
void ESP8266_Init(void);
void ESP8266_SendCommand(char* cmd, char* expected, uint32_t timeout);
void PlayAudioFromHTTP(void);

int main(void) {
  HAL_Init();
  SystemClock_Config();
  
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  MX_TIM1_Init();
  
  // 启动PWM
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  
  printf("=== STM32 网络音频播放器 ===\r\n");
  
  // 初始化ESP8266
  ESP8266_Init();
  
  // 播放音频
  while(1) {
    PlayAudioFromHTTP();
    HAL_Delay(5000);
  }
}

void ESP8266_Init(void) {
  printf("初始化ESP8266...\r\n");
  
  // 复位模块
  ESP8266_SendCommand("AT+RST\r\n", "OK", 2000);
  HAL_Delay(3000);
  
  // 设置为Station模式
  ESP8266_SendCommand("AT+CWMODE=1\r\n", "OK", 1000);
  
  // 连接WiFi
  char cmd[100];
  sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", WIFI_SSID, WIFI_PASSWORD);
  ESP8266_SendCommand(cmd, "OK", 10000);
  
  printf("WiFi连接成功!\r\n");
}

void ESP8266_SendCommand(char* cmd, char* expected, uint32_t timeout) {
  memset(rxBuffer, 0, RX_BUFFER_SIZE);
  rxIndex = 0;
  
  HAL_UART_Transmit(&huart1, (uint8_t*)cmd, strlen(cmd), 1000);
  
  uint32_t startTime = HAL_GetTick();
  while (HAL_GetTick() - startTime < timeout) {
    if (HAL_UART_Receive(&huart1, &rxBuffer[rxIndex], 1, 10) == HAL_OK) {
      rxIndex++;
      if (strstr((char*)rxBuffer, expected) != NULL) {
        return;
      }
    }
  }
}

void PlayAudioFromHTTP(void) {
  char cmd[200];
  
  printf("开始下载音频...\r\n");
  
  // 建立TCP连接
  sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",80\r\n", AUDIO_URL);
  ESP8266_SendCommand(cmd, "OK", 5000);
  
  // 构建HTTP GET请求
  char httpRequest[300];
  sprintf(httpRequest, 
    "GET %s HTTP/1.1\r\n"
    "Host: %s\r\n"
    "Connection: close\r\n\r\n",
    AUDIO_PATH, AUDIO_URL);
  
  // 发送HTTP请求
  sprintf(cmd, "AT+CIPSEND=%d\r\n", strlen(httpRequest));
  ESP8266_SendCommand(cmd, ">", 1000);
  HAL_UART_Transmit(&huart1, (uint8_t*)httpRequest, strlen(httpRequest), 2000);
  
  printf("开始播放音频...\r\n");
  
  // 接收并播放音频数据
  uint8_t audioByte;
  uint32_t bytesReceived = 0;
  int headerSkipped = 0;
  int headerBytes = 0;
  
  while (1) {
    if (HAL_UART_Receive(&huart1, &audioByte, 1, 100) == HAL_OK) {
      
      // 跳过HTTP头和WAV头
      if (!headerSkipped) {
        headerBytes++;
        if (headerBytes > 500) {  // 简单处理:跳过前500字节
          headerSkipped = 1;
          printf("开始输出音频\r\n");
        }
        continue;
      }
      
      // 使用PWM输出音频(8位DAC)
      __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, audioByte);
      
      bytesReceived++;
      
      if (bytesReceived % 1000 == 0) {
        printf("已播放: %lu 字节\r\n", bytesReceived);
      }
      
      // 简单的采样率控制(11025Hz约为90us间隔)
      HAL_Delay(0);  // 可根据实际调整
      
    } else {
      // 接收超时,认为传输完成
      if (headerSkipped && bytesReceived > 1000) {
        break;
      }
    }
  }
  
  printf("播放完成! 总计: %lu 字节\r\n", bytesReceived);
  
  // 关闭连接
  ESP8266_SendCommand("AT+CIPCLOSE\r\n", "OK", 1000);
}

// printf重定向到USART2
int fputc(int ch, FILE *f) {
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 100);
  return ch;
}
💡 提示: 完整的STM32CubeMX配置文件和工程文件较大,以上代码展示了核心逻辑。您需要使用STM32CubeMX配置相应的外设(UART1、UART2、TIM1等)。

🎵 可用音频文件列表

以下是当前服务器上所有可用的音频文件。点击任意文件可复制其URL。

✅ 实时更新: 此列表会自动从服务器获取最新的音频文件,无需手动刷新。

正在加载音频文件列表...

📊 按采样率分类

音频名称 11025 Hz 22050 Hz 44100 Hz
正在加载...

📚 API 文档

HTTP GET 请求音频文件

请求格式

HTTP
GET /sound/[filename].wav HTTP/1.1
Host: audio.wuhanqing.cn
Connection: close

响应

状态码 说明
200 OK 请求成功,返回音频文件内容
404 Not Found 文件不存在

音频文件命名规范

[歌曲名]_mono_[采样率].wav

示例:
- Sparkle_mono_11025.wav  (11.025 kHz)
- Sparkle_mono_22050.wav  (22.050 kHz)
- Sparkle_mono_44100.wav  (44.100 kHz)

采样率选择建议

采样率 带宽需求 音质 适用场景
11025 Hz 基础 低速网络、简单提示音
22050 Hz 中等 良好 一般应用、语音播报
44100 Hz 高质量 音乐播放、高质量音频

cURL 测试示例

Bash
# 下载音频文件
curl -O http://audio.wuhanqing.cn/sound/Sparkle_mono_22050.wav

# 查看HTTP头信息
curl -I http://audio.wuhanqing.cn/sound/Sparkle_mono_22050.wav

# 流式下载并播放(需要支持WAV的播放器)
curl http://audio.wuhanqing.cn/sound/Sparkle_mono_22050.wav | aplay
⚠️ 注意事项:
  • 所有音频文件均为单声道(MONO)WAV格式
  • 建议在代码中添加错误处理和重试机制
  • 大文件播放建议使用流式传输,避免一次性加载全部数据
  • 请根据硬件性能选择合适的采样率

📧 联系我们

如有任何问题、建议或合作需求,欢迎通过以下方式与我联系:

📧
电子邮箱
💬
微信
Daniel_Qinghan
点击复制
✉️ 期待您的反馈: 无论是bug报告、功能建议还是使用心得,都欢迎分享!