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

如何从Django服务器向带SD卡的ESP32传输WAV文件?

问题分析

当前方案的核心问题是把WAV文件转成Base64编码后通过文本格式传输,不仅让数据体积膨胀33%,还会迫使ESP32一次性加载整个大 payload 到内存,触发资源不足错误(errno:11就是内存/进程资源耗尽的表现)。下面是针对性的修复方案:


1. 修改Django后端:直接返回二进制WAV数据

不需要Base64编码,直接返回合成的原始音频二进制内容,让ESP32可以流式下载,避免一次性加载全部数据。

修改后的Python代码:

def text_to_wav(voice_name: str, text: str):
    language_code = "-".join(voice_name.split("-")[:2])
    text_input = tts.SynthesisInput(text=text)
    voice_params = tts.VoiceSelectionParams(
        language_code=language_code, name=voice_name
    )
    audio_config = tts.AudioConfig(audio_encoding=tts.AudioEncoding.LINEAR16)

    client = tts.TextToSpeechClient()
    response = client.synthesize_speech(
        input=text_input,
        voice=voice_params,
        audio_config=audio_config,
    )

    # 直接返回二进制音频内容,取消Base64编码
    return response.audio_content


@api_view(["POST"])
def tts_view(request: Request):
    voice_name = request.data.get("voice_name")
    text = request.data.get("text")

    if not voice_name or not text:
        print("Missing voice_name or text")
        return JsonResponse({"error": "Missing voice_name or text"}, status=400)

    audio_data = text_to_wav(voice_name, text)
    # 返回二进制WAV,设置正确的Content-Type
    response = HttpResponse(audio_data, content_type="audio/wav")
    # 可选:设置文件名,方便ESP32保存时命名(截断长文本避免文件名非法)
    response["Content-Disposition"] = f"attachment; filename={text[:10].replace(' ', '_')}.wav"
    return response

2. 修改ESP32端:流式下载并写入SD卡

放弃http.getString()一次性加载全部内容的方式,改用分块读取+直接写入SD卡的流式处理,彻底避免内存溢出。

修改后的C++代码:

// 全局定义缓冲区,大小根据ESP32内存调整(比如512字节)
uint8_t download_buffer[512];

void request_tts(String text, fs::FS &SD) {
    HTTPClient http;

    String server = SERVER_NAME;
    server += "tts/create-speech/";
    
    if (!http.begin(server.c_str())) {
        Serial.println("HTTP连接失败");
        return;
    }

    // 构造请求体,注意转义文本中的双引号(如果有)
    String body = "{\"text\":\"" + text + "\", \"voice_name\":\"es-US-Neural2-B\"}";

    http.addHeader("Content-Type", "application/json");
    http.addHeader("Authorization", "Api-Key 0ZfAfvpE.y498gbIbw2QdKCCcJCIETRal1q9mjPy9");

    int response_code = http.POST(body.c_str());

    if (response_code != HTTP_CODE_OK) {
        Serial.printf("请求失败,状态码:%d\n", response_code);
        http.end();
        return;
    }

    // 打开SD卡文件准备写入(覆盖已有文件)
    File audio_file = SD.open("/tts_output.wav", FILE_WRITE);
    if (!audio_file) {
        Serial.println("SD卡文件打开失败");
        http.end();
        return;
    }

    // 流式读取响应并写入SD卡
    WiFiClient *stream = http.getStreamPtr();
    while (http.connected() && (stream->available() || http.connected())) {
        if (stream->available()) {
            size_t read_bytes = stream->readBytes(download_buffer, sizeof(download_buffer));
            audio_file.write(download_buffer, read_bytes);
        }
        delay(1); // 给系统留处理时间
    }

    audio_file.close();
    http.end();
    Serial.println("WAV文件已成功保存到SD卡");
}

3. 异步库编译失败的替代方案

没必要纠结第三方异步库的编译问题,上面的同步流式下载已经能解决核心的内存不足问题。如果确实需要异步处理,可以尝试:

  • 使用Arduino官方的AsyncWiFiClient+AsyncHttpClient组合,依赖更稳定
  • 切换到ESP-IDF框架,使用原生的esp_http_client组件实现异步下载

额外优化建议

  • 限制合成音频长度:如果文本太长,生成的WAV文件过大,建议后端限制输入文本长度,或者分段合成后再传输
  • 切换音频格式:把Google TTS的audio_encoding改成MP3,能大幅减小文件体积,降低传输压力
  • 增加容错处理:比如检查SD卡剩余空间、设置HTTP连接超时、写入失败时重试等

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 07:37:10