韓国のSTREET WOMAN FIGHTERを観てビヨンセのRun The World(Girl)が頭から離れなくなって困っていますw
この続きになります
さて、どうやってTTSさせる?
まぁそんなことはいいとして、最初に思いついたのがGoogleTTSを使った方法です。
上のブログは予め読んでいたので、このライブラリを使って生成したURLをESP8266AudioのストリーミングURLに食わせればTTS完了っと
うはwwよゆーじゃんwww
っと思ってた時期もありました。
が…実際にやってみると再生はするんですがストリーミングが止まらずノイズを再生し続けてクラッシュします。ESP8266Audioのissuesにも載っていました。
理由はわかりませんが、どうやらGoogle TTSの仕様のようなので諦めます。代替のサービスを探しているとSound Of Textというものを見つけました。
Sound Of Textとは
web上でテキストを入力してmp3を生成できるサイトです。どうやら生成にはGoogleTTSを利用しているようで、そこそこ有名らしいです。多言語対応という点で〇。実際生成したURLをESP8266Audioに食わせてみた(https->httpへ変えた)ところ、ちゃんと再生できていました。イケそうです!
しかもこのサイトはAPIを公開しています。
流れとしては
- POST /soundsでID取得、mp3生成要求
- GET /sounds/:idでmp3のURLを取得
- URLをESP8266Audioに渡してストリーミング再生
です。一つずつ実装していけば大丈夫そうです
HTTPSリクエストの準備
JSONでリクエストしてJSONで受け取るので、そんなに難しくはないですが、このAPIはhttpsなんですよね…ダメもとでhttpに変えてRESTでリクエストしてみたところ
308 Permanent Redirectでhttpsにリダイレクトされています。ということは結局、ESP32でhttps接続をしないとならないです。esp32でhttps接続は遅いし、証明書は入れとかないとダメとかで、気が進みませんが仕方がありません。
ESP8266の時もhttpsでいい思い出がありません…ハァ…まずはルート証明書を取得することから始めます。
上の記事を参考にSoundOfTextの証明書をブラウザから取得します。ちなみに私はESETのウイルス対策ソフトを入れているのですが、証明書がESETになっており、本来の証明書ではないものを使ってハマり時間が溶けました。
ほかのウイルス対策ソフトはわかりませんが、上の記事を参考にESETであれば無効にしときましょう。
後から知ったのですが、上の記事の「WiFiClientSecure用の簡単な証明書の取得方法」でルート証明書を取る方法の方が簡単でいいです。先にこの記事を読んでれば…とりあえず取得できた証明書は以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
const char* root_ca= \ "-----BEGIN CERTIFICATE-----\n" \ "MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw\n" \ "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw\n" \ "WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n" \ "RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" \ "AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP\n" \ "R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx\n" \ "sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm\n" \ "NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg\n" \ "Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG\n" \ "/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC\n" \ "AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB\n" \ "Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA\n" \ "FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw\n" \ "AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw\n" \ "Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB\n" \ "gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W\n" \ "PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl\n" \ "ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz\n" \ "CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm\n" \ "lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4\n" \ "avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2\n" \ "yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O\n" \ "yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids\n" \ "hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+\n" \ "HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv\n" \ "MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX\n" \ "nLRbwHOoq7hHwg==\n" \ "-----END CERTIFICATE-----\n"; |
再生コードの実装
めんどいので、いきなり全コードいきますw、SPIFFSには0004.mp3と0001.mp3を入れときます(効果音ならなんでも)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
#include <Arduino.h> #ifdef ESP32 #include <WiFi.h> #include "SPIFFS.h" #else #include <ESP8266WiFi.h> #endif #include "AudioFileSourceICYStream.h" #include "AudioFileSourceBuffer.h" #include "AudioGeneratorMP3.h" #include "AudioOutputI2SNoDAC.h" #include "AudioOutputI2S.h" #include "AudioFileSourceSPIFFS.h" #include "AudioFileSourceID3.h" #include <ArduinoJson.h> //SPIFFSに入れておく効果音。地震警報用なので2ベルと1ベルの効果音を用意 #define EFFECT_TWO_BELL "/0004.mp3" #define EFFECT_ONE_BELL "/0001.mp3" //Wifiの接続SSIDとパス const char* ssid = "ssid"; const char* password = "pass"; const String msg = "緊急地震速報、予報。函館、震度2。最大震度、震度5強。マグニチュード、5.5。震源、宮城県沖。最終報待ちです"; //SoundOfTextのURL const char *host = "https://api.soundoftext.com/sounds"; StaticJsonDocument<1024> json_request; char buffer[512]; const char* root_ca= \ "-----BEGIN CERTIFICATE-----\n" \ "MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw\n" \ "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw\n" \ "WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n" \ "RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" \ "AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP\n" \ "R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx\n" \ "sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm\n" \ "NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg\n" \ "Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG\n" \ "/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC\n" \ "AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB\n" \ "Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA\n" \ "FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw\n" \ "AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw\n" \ "Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB\n" \ "gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W\n" \ "PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl\n" \ "ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz\n" \ "CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm\n" \ "lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4\n" \ "avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2\n" \ "yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O\n" \ "yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids\n" \ "hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+\n" \ "HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv\n" \ "MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX\n" \ "nLRbwHOoq7hHwg==\n" \ "-----END CERTIFICATE-----\n"; bool tts_flag = false; String location = ""; bool isPlaying = false; String effect_mp3 = ""; String tts_text = ""; int tts_state = 0; // 0:待機 1:mp3url生成リクエスト中 2:再生中 AudioGeneratorMP3 *mp3; AudioFileSourceICYStream *file; AudioFileSourceBuffer *buff; //AudioOutputI2SNoDAC *out; AudioOutputI2S *out; AudioFileSourceSPIFFS *file_effect; AudioFileSourceID3 *id3_effect; //プロトタイプ宣言 String requestCreateTTS(String text); String isPreparedTTSMp3(String id,String& location); bool playEffect(); void subProcess(void * pvParameters); // Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc. void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string) { /* const char *ptr = reinterpret_cast<const char *>(cbData); (void) isUnicode; // Punt this ball for now // Note that the type and string may be in PROGMEM, so copy them to RAM for printf char s1[32], s2[64]; strncpy_P(s1, type, sizeof(s1)); s1[sizeof(s1)-1]=0; strncpy_P(s2, string, sizeof(s2)); s2[sizeof(s2)-1]=0; Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2); Serial.flush(); */ } // Called when there's a warning or error (like a buffer underflow or decode hiccup) void StatusCallback(void *cbData, int code, const char *string) { /* const char *ptr = reinterpret_cast<const char *>(cbData); // Note that the string may be in PROGMEM, so copy it to RAM for printf char s1[64]; strncpy_P(s1, string, sizeof(s1)); s1[sizeof(s1)-1]=0; Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1); Serial.flush(); */ } void initTTS(){ /* out = new AudioOutputI2SNoDAC(); out->SetPinout(26,25,5); // bclkPin = 26 wclkPin = 25 doutPin = 5 out->SetGain(3.999); // <4.0 out->SetRate(44100); out->SetBitsPerSample(16); out->SetChannels(1); */ out = new AudioOutputI2S(); out->SetPinout(26,25,15); // bclkPin = 26 wclkPin = 25 doutPin = 15 out->SetGain(0.03); // <4.0 0.15Def out->SetRate(44100); out->SetBitsPerSample(16); out->SetChannels(1); xTaskCreatePinnedToCore(subProcess, "subProcess", 8192, NULL, 25, NULL, 0); //Core0でもCore1でも優先度も適宜変更 } void speak_text(String text,String effect_f){ //再生していれば停止をする isPlaying = false; //tts_state = 0 になるまで待機 Serial.print("[TTS] Wait tts_state reset"); while(tts_state != 0){ Serial.print("."); delay(100); } Serial.println(""); Serial.println("[TTS] tts_state reset ok"); //効果音と発話内容をセット effect_mp3 = effect_f; tts_text = text; //SoundOfTestへリクエスト開始 tts_state = 1; } bool playEffect(){ file_effect = new AudioFileSourceSPIFFS(effect_mp3.c_str()); id3_effect = new AudioFileSourceID3(file_effect); mp3 = new AudioGeneratorMP3(); mp3->begin(id3_effect, out); delay(50); } //戻り値 Pedding:生成中 Done:完了=>locationにmp3のurl Error:生成エラー // https://soundoftext.com/docs参照 String isPreparedTTSMp3(String id,String& location){ String ret = ""; String url = String(host) + "/" + id; HTTPClient http; http.begin(url,root_ca); int status_code = http.GET(); //Serial.print("status_code = "); //Serial.println(status_code); if( status_code == 200 ){ Stream* resp = http.getStreamPtr(); DynamicJsonDocument json_response(255); deserializeJson(json_response, *resp); ret = (const char*)json_response["status"]; if(json_response.containsKey("location")){ location = (const char*)json_response["location"]; }else{ location = ""; } //serializeJson(json_response, Serial); //Serial.println(""); } http.end(); return ret; } //TTSのidを生成。空文字列ならサーバーエラーか生成エラー(適宜リトライ) String requestCreateTTS(String text){ String ret = ""; //postで渡すjson生成 json_request.clear(); json_request["engine"] = "Google"; JsonObject obj = json_request.createNestedObject("data"); obj["text"] = text; obj["voice"] = "ja-JP"; //obj["voice"] = "en-US"; //serializeJson(json_request, Serial); //Serial.println(""); serializeJson(json_request, buffer, sizeof(buffer)); HTTPClient http; http.begin(host,root_ca); http.addHeader("Content-Type", "application/json"); int status_code = http.POST((uint8_t*)buffer, strlen(buffer)); //Serial.print("status_code = "); //Serial.println(status_code); if( status_code == 200 ){ Stream* resp = http.getStreamPtr(); DynamicJsonDocument json_response(255); deserializeJson(json_response, *resp); if(json_response.containsKey("id")){ ret = (const char*)json_response["id"]; } //serializeJson(json_response, Serial); //Serial.println(""); } http.end(); return ret; } int createMp3FromSoundText(){ isPlaying = true; String id = ""; int counter = 0; while(id.length()<=0) { id = requestCreateTTS(tts_text); yield(); if(counter >= 5 ){ Serial.println("[requestCreateTTS] request over 5 retry"); return 0; }else{ counter++; } } Serial.print("[createMp3FromSoundText] success create mp3url id : "); Serial.println(id); if(!isPlaying) return 0; String ret; do{ ret = isPreparedTTSMp3(id,location); Serial.print("ret = "); Serial.println(ret); } while ( ret.compareTo("Pending") == 0 ); //Done以外ならエラー if( ret.compareTo("Done") != 0 ){ Serial.print("[isPreparedTTSMp3] error : ret = "); Serial.println(ret); return 0; } //https -> httpへ(Esp8266Audioはhttpしかダメなため) location.replace("https", "http"); Serial.print("[createMp3FromSoundText] success create mp3url : "); Serial.println(location); if(isPlaying){ //effect_mp3の指定がなければ効果音なしでTTS再生 Serial.println("[TTS] start TTS play"); if(effect_mp3.compareTo("") == 0){ tts_flag = true; file = new AudioFileSourceICYStream(location.c_str()); file->RegisterMetadataCB(MDCallback, (void*)"ICY"); buff = new AudioFileSourceBuffer(file, 2048); buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); mp3 = new AudioGeneratorMP3(); mp3->RegisterStatusCB(StatusCallback, (void*)"mp3"); mp3->begin(buff, out); delay(100); }else{ //effect_mp3の効果音再生後、TTS再生 tts_flag = false; playEffect(); } return 2; }else{ Serial.println("[TTS] cancel before TTS play"); return 0; } } void subProcess(void * pvParameters) { while (1) { if(tts_state == 0){ // 0 待機状態 }else if(tts_state == 1){ // 1 SoundTextからURLを生成 tts_state = createMp3FromSoundText(); }else if(tts_state == 2){ // 2 効果音とTTS再生 if ( mp3 != NULL ){ if (mp3->isRunning()) { if(!isPlaying){ mp3->stop(); Serial.println("[TTS] cancel TTS"); }else if(!mp3->loop()){ mp3->stop(); } } else { delete mp3; mp3 = NULL; if(!tts_flag){ //効果音の再生完了 //Serial.println("tts_flag false"); file_effect->close(); delete file_effect; delete id3_effect; file_effect = NULL; id3_effect = NULL; //再生がキャンセルされてなければ次にTTSを再生 if(!isPlaying){ tts_flag = false; tts_state = 0; Serial.println("[TTS] finish TTS play (only effect)"); }else{ file = new AudioFileSourceICYStream(location.c_str()); file->RegisterMetadataCB(MDCallback, (void*)"ICY"); buff = new AudioFileSourceBuffer(file, 2048); buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); mp3 = new AudioGeneratorMP3(); mp3->RegisterStatusCB(StatusCallback, (void*)"mp3"); mp3->begin(buff, out); tts_flag = true; delay(100); } }else{ //TTSが再生完了 buff->close(); file->close(); delete buff; delete file; buff = NULL; file = NULL; //初期待機状態に戻す tts_flag = false; isPlaying = false; tts_state = 0; Serial.println("[TTS] finish TTS"); } } } } delay(1); } } void setup() { Serial.begin(115200); delay(1000); Serial.println("Connecting to WiFi"); WiFi.disconnect(); WiFi.softAPdisconnect(true); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.println("...Connecting to WiFi"); delay(1000); } Serial.println("Connected"); //SPIFFS初期化 SPIFFS.begin(); //tts初期化 initTTS(); //発話(効果音EFFECT_TWO_BELL後にTTS) speak_text(msg,EFFECT_TWO_BELL); } void loop(){ } |
一応、コードのベースはESP8266AudioのサンプルソースのStreamMP3FromHTTP.inoに付け加える感じで書きています。各種コールバックは邪魔なら消してもOKです。ログが汚れるのでコメントアウトしています
サンプルと違うところは、loop()ではなく別タスクを用意してそれをTTS専用として動かしています。メインでは地震監視処理と画像合成を行う予定の為、別タスクにしました。マルチタスクについては以下の記事を参考にしてください。
ttsのみさせる場合はloop()に直書きでもokと思います。
ESP8266Audioで用いる変数でメモリリークが発生してしまうようなので再生完了後はdeleteさせる必要があります(Closeさせてdeleteしている部分)
ストリーミング時のバッファサイズは大きければより安定しますので、PSRAMがあるESP32-wroverシリーズでは積極的にPSRAMに確保したほうがよさそうです。PSRAMなら遠慮なしに確保できますね
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//PSRAMに確保するTTS用バッファ uint32_t spiBufferSize = 1024 * 300; //300kB程度確保(TTSの長さをで判断) byte* spiBuffer; void initTTS(){ ~略~ //PSRAM上にバッファ確保 spiBuffer = (byte*)ps_malloc(spiBufferSize); if (spiBuffer == NULL) { Serial.println("spiBuffer : ps_malloc error"); ESP.restart(); delay(3000); return; } } //この部位を書き換える buff = new AudioFileSourceBuffer(file, spiBuffer, spiBufferSize); //buff = new AudioFileSourceBuffer(file, 2048); |
スピーカーやI2SDACの接続は前の記事の接続編を読んでください。うまくいけば下のように再生できると思います。TFTやらSDカードついてますがTTSだけなら不要です