如何从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
相关产品推荐
相关产品推荐

