keywords: 音声合成 AquesTalk ESP32 I2S
2018/08/09 AquesTalk-ESP32とArduino-ESP32のVer.upに伴い、ソースコードやライブラリのセットアップ方法は、次の記事を参照してください。「AquesTalk-ESP32 Ver.2.0」を使う
概要
AquesTalk pico for ESP32 (以下、AquesTalk-ESP)は、ESP32向けの音声合成ライブラリです。
これを使って、ESP-WROOM-32の内臓のDA(I2S経由)から合成音声を出力します。
アンプ・スピーカーを内蔵しているM5Stackなら、ソフトだけで簡単に音声合成できます。
AquesTalk pico for ESP32 (AquesTalk-ESP)の入手
アクエストのサイトからダウンロードできます。
・アクエスト > ダウンロード
https://www.a-quest.com/download.html
ダウンロードしたZIPの中身は、ヘッダファイル aquestalk.h、ライブラリ libaquestalk.a、あとはドキュメントファイルです。
なお、ここでいうライブラリとはコンパイルしたオブジェクトを指し、ArduinoIDEのライブラリ(ソースコード等から構成されるもの)ではありません。また、AquesTalk-ESPは有償ソフトウェアであり、評価版ではナ行とマ行がすべてヌと発声される制限があります。
ビルド準備 ArduinoIDEの場合
ArduinoIDEによる開発環境上に、AquesTalk-ESPをインストールする手順を以下に示します。ArduinoIDEや、ArduinoIDEでESP32をビルドするための「Arduino core for the ESP32」はすでにインストールされているものとします。
1. 次の場所にライブラリとヘッダをコピーします。
{PATH_Arduino_ESP32}¥tools¥sdk¥lib¥libaquestalk.a
{PATH_Arduino_ESP32}¥tools¥sdk¥include¥aquestalk¥aquestalk.h
aquestalk¥aquestalk.hのaquestalkフォルダは作成してください。
ここで、{PATH_Arduino_ESP32}は、「Arduino core for the ESP32」をインストールした場所です。 私の環境では次のとおりでした。
C:¥Program Files (x86)¥arduino¥hardware¥espressif¥esp32
2018/06/28以降のArduino-ESP32では次のように変わってました。
C:¥Users¥{ユーザ名}¥AppData¥Local¥Arduino15¥packages¥esp32¥hardware¥esp32¥1.0.0
2. 次の場所にplatform.local.txtを作成します。同じ場所にplatform.txtがあるはずです。
{PATH_Arduino_ESP32}¥platform.local.txt
platform.local.txtには、以下を記述します。これは、コンパイル時のインクルードパスにaquestalkを追加し、リンク時にlibaquestalk.aを加える指定です。
compiler.c.extra_flags="-I{compiler.sdk.path}/include/aquestalk" compiler.cpp.extra_flags="-I{compiler.sdk.path}/include/aquestalk" compiler.c.elf.libs=-lgcc -lopenssl -lbtdm_app -lfatfs -lwps -lcoexist -lwear_levelling -lhal -lnewlib -ldriver -lbootloader_support -lpp -lsmartconfig -ljsmn -lwpa -lethernet -lphy -lapp_trace -lconsole -lulp -lwpa_supplicant -lfreertos -lbt -lmicro-ecc -lcxx -lxtensa-debug-module -lmdns -lvfs -lsoc -lcore -lsdmmc -lcoap -ltcpip_adapter -lc_nano -lrtc -lspi_flash -lwpa2 -lesp32 -lapp_update -lnghttp -lspiffs -lespnow -lnvs_flash -lesp_adc_cal -llog -lexpat -lm -lc -lheap -lmbedtls -llwip -lnet80211 -lpthread -ljson -lstdc++ -laquestalk
compiler.c.elf.libsの部分は、Arduino core for the ESP32のバージョンによって異なるかもしれません異なります。必ずplatform.txtからcompiler.c.elf.libsの部分をコピペして、最後に -laquestalk を加えます。
以上で準備は終わりです。
ビルド準備 ESP-IDFの場合
ESP-IDFの場合は、ArduinoIDEの場合とは異なり、アプリのプロジェクトの中にAquesTalkのライブラリとヘッダをコピーします。
具体的には次のようにプロジェクトのファイルを構成します(ここではプロジェクト名をhello_aquestalkとしています。~はmingw32ターミナル上でのホームディレクトリ)。
[ダウンロード hello_aquestalk.zip]
~/esp/hello_aquestalk/
Makefile
main/
main.c
aquestalk.h
libaquestalk.a
component.mk
なお、私のWindows上では以下のディレクトリになりました。
C:¥msys32¥home¥{USER_NAME}¥esp¥hello_aquestalk
MakefileはESP32-IDFの通常のままで、AquesTalkを使う上で特別な部分はありません。次の2行を記述します。
PROJECT_NAME := hello_aquestalk include $(IDF_PATH)/make/project.mk
mainディレクトリを作成し、そのなかに、main.c, aquestalk.h,libaquestalk.a,component.mkを配置します。
component.mkには以下の記述を追加します。これは、リンク時にlibaquestalk.aを追加する指定です。この指定を忘れると、make時にAquesTalkの関数が未定義とのエラーになります。
COMPONENT_ADD_LDFLAGS += $(COMPONENT_PATH)/libaquestalk.a
以上で準備は終わりです。
サンプルコード
サンプルプログラムは、3つのメッセージを連続して音声出力するものです。
ArduinoIDEとESP-IDFの2つ用意していますが、ソースコードはほとんど同じです。
上のビルド準備が整っていれば、ArduinoIDEでは [マイコンボードに書き込む]、ESP-IDFでは
$ make flash monitor で、GPIO25かGPIO26端子から音声信号が出力されるはずです。
・サンプルコードのダウンロード
helllo_aquestalk(ArduinoIDE用)
helllo_aquestalk(ESP-IDF用)
コード説明
以下はサンプルコードの抜粋です。
5| #define LEN_FRAME 32 6| uint32_t workbuf[AQ_SIZE_WORKBUF]; ... 8| void setup() { ... 12| Serial.println("Initialize AquesTalk"); 13| iret = CAqTkPicoF_Init(workbuf, LEN_FRAME, "XXX-XXX-XXX"); ... 17| 18| DAC_Create(); ... 21| Play("konnnichiwa."); ... 26| DAC_Release(); ... 28| } ... 34| void Play(const char *koe) 35| { ... 39| int iret = CAqTkPicoF_SetKoe((const uint8_t*)koe, 100, 0xffffU); ... 42| for(;;){ 43| int16_t wav[LEN_FRAME]; 44| uint16_t len; 45| iret = CAqTkPicoF_SyntheFrame(wav, &len); 46| if(iret) break; // EOD 47| 48| DAC_Write((int)len, wav); 49| } 50| } ... 53| //i2s configuration 54| const int i2s_num = 0; // i2s port number 55| i2s_config_t i2s_config = { 56| .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), 57| .sample_rate = 24000, ... 65| }; 66| 67| void DAC_Create() 68| { 69| AqResample_Reset(); 70| 71| i2s_driver_install((i2s_port_t)i2s_num, &i2s_config, 0, NULL); 72| i2s_set_pin((i2s_port_t)i2s_num, NULL); 73| } ... 81| int DAC_Write(int len, int16_t *wav) 82| { 83| int i; 84| for(i=0;i<len;i++){ 85| // upsampling x3 86| int16_t wav3[3]; 87| AqResample_Conv(wav[i], wav3); 88| 89| // write to I2S DMA buffer 90| for(int k=0;k<3; k++){ 91| uint16_t sample[2]; 92| uint16_t us = ((uint16_t)wav3[k])^0x8000U; // signed -> unsigned data 内蔵DA Only 93| sample[0]=sample[1]=us; // mono -> stereo 94| int iret = i2s_push_sample((i2s_port_t)i2s_num, (const char *)sample, 100); 95| if(iret<0) return iret; // -1:ESP_FAIL 96| if(iret==0) break; // 0:TIMEOUT 97| } 98| } ...
CAqTkPicoF_Init()でAquesTalkを初期化します(13行目)。
引数にはワークバッファ、フレーム長、ライセンスキーを指定します。
ワークバッファは、AquesTalkが内部処理で使うメモリで、サイズが AQ_SIZE_WORKBUF の uint32_t 配列を指定します。AQ_SIZE_WORKBUF は aquestalk.h 内で定義されています。ワークバッファは音声合成処理を行っている時に必要で、一連の処理後は解放できます。これによりRAMを効率よく利用できます。
AquesTalkは、非同期処理にも対応できるように分割して音声を生成します。フレーム長とは、このときに一回に生成する音声波形の長さ(サンプル数)で、初期化時に指定します。指定可能な値の範囲は30-300で、ここでは32を指定しています。
最後の引数にはライセンスキーを指定し、評価版として使用するときはNULLを指定します。
AqTkPicoF_SetKoe()で、発声するメッセージ(音声記号列)を指定します(39行目)。
第2引数は発話速度でデフォルトが100。値を大きくするほど早い発声になります。
第3引数は最後の無音区間の長さです。通常デフォルトの0xFFFFUを指定すれば良いでしょう。
CAqTkPicoF_SyntheFrame()で、1フレームの音声データを取得します(45行目)。
サンプリング周波数が8KHzで、フレーム長に32サンプルを指定したので、32/8KHz=4ms毎に音声を生成することになります。戻り値が0以外なるまでこの関数を複数回呼び出します。
第1引数には16bit/PCM/モノラルの音声データが返されます。
第2引数は生成したデータのサンプル数が返ります。通常はフレーム長に等しく、最後のフレームだけフレーム長より小さくなることがあります。
DAC_Create()で、I2Sの初期化をします(67行目)。この中で、I2S経由で内蔵DACから出力する設定を指定しています。
AquesTalk picoの出力音声データは8KHzのサンプリング周波数ですが、AquesTalk-ESP32ライブラリには、これを3倍にアップサンプリングするサンプリング周波数変換関数(AqResample)が含まれています。
アップサンプリングにより、ローパスフィルタが使われていないオーディオ系に出力するときに、チロチロとした折り返し雑音を抑制することができます。もし、DA出力側にfc4KHz程度のLPFを入れられるのならば、アップサンプリングをせず8KHzのまま出力したほうがCPU負荷は少なくなります。
AqResample_Reset()は、アップサンプリング処理の初期化関数です(69行目)。
また、アップサンプリングに伴い、i2s_driver_install()で指定するconfigurationのサンプリング周波数には8KHzでなく24KHzを指定しています(57行目)。
DAC_Write()で、1フレーム(32サンプル)の音声をI2Sに出力しています(81行目)。
AqResample_Conv()のアップサンプリング処理により、1サンプルのデータが3サンプルに変換されます。これを1サンプルづつi2s_push_sample()でI2Sのバッファに出力します。
I2Sには符号無しデータを与える?!(内蔵DACの例外)
本来、I2Sのデータは符号付きです。16bitデータの場合、値は-32768〜32767の範囲で、センターの値は0となります。ところが、内臓DAの場合は、0-65535(下位8bitは無視)の範囲で、センターの値は32768にする必要があります。サンプルコードではこの変換を行っていますが(92行目)、外部接続のI2C-DACを使うときは、この変換をしてはNGです。
外付けDACを使うには
内蔵DACを使うと、I2Sの初期化時と、音声出力の最初にポップノイズが発生しています。原因の予想はついていますが、これについてはもう少しちゃんと調べてから書こうと思います。
コードを少し変更することで、ESP32の内蔵DACの代わりにPCM5102A搭載などの外付けI2S-DACを用いることもできます。
変更点は、
- i2s_config.modeでI2S_MODE_DAC_BUILT_INフラグを外す
- i2s_set_pin()で使用するピンの設定を行なう
- 符号無しデータに変換している処理(92行目)をしない。
実際にRaspberry Pi用のPCM5102A搭載32bit 384kHz DAC完成基板を用いて動かしてみました。ただ、DACの設定がI2Sモードでは動かず、left justifiedにする必要があり、ちょっとモヤモヤしています。
ライセンスキーの入手
AquesTalk-ESPの評価版はナ行とマ行がすべてヌと発声される制限があります。ライセンスキーを購入することでこの制限が解除されます。
ライセンスキーの入手は、アクエストOnline Storeで「AquesTalk pico for ESP32 使用ライセンス」を購入します。このとき備考欄に使用するESP32モジュールのMACアドレスを記載する必要があります。その後に郵送されるライセンス証にライセンスキーが記載されており、このライセンスキーをCAqTkPicoF_Init()に指定します。
MACアドレスが不明の場合は、次のスケッチを実行します。
・アクエストOnline Store「AquesTalk pico for ESP32 使用ライセンス」
[GetMAC.ino]
// Get default MAC Address void setup() { Serial.begin(115200); uint8_t mac[6]; esp_efuse_mac_get_default(mac); Serial.printf("¥nMAC Address = %02X:%02X:%02X:%02X:%02X:%02X¥r¥n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } void loop() { }
最後に
AquesTalk picoは、Flash(ROM)28KB、RAM:500B という極めて小さなリソースなので、他の処理に大きな負担をかけずに音声合成できます。アラートなどアプリのちょっとした部分に使用することもできると思います。
今回のコードは音声出力している間は他の処理ができない同期処理ですが、コードの書き方で音声出力と同時に表示を行うなど非同期の処理も可能です。この非同期で使う方法は次記事にする予定です。
次記事 「AquesTalk-ESPを簡単に使うクラス(プログラム)」
http://blog-yama.a-quest.com/?eid=970191
リンク
・AquesTalk pico for ESP32(AquesTalk-ESP)
https://www.a-quest.com/products/aquestalk_pico_esp32.html
・Arduino core for the ESP32 のインストール方法
https://www.mgo-tec.com/arduino-core-esp32-install
・ESP-IDF ( ESP32 開発環境 ) の使い方
https://www.mgo-tec.com/esp32-idf-howto-01
・ESP32でI2S+DACを使う
https://qiita.com/h_nari/items/b52c525f0c5b35aaf692
・ESP-IDF Programming Guide - Build System
http://esp-idf.readthedocs.io/en/latest/api-guides/build-system.html