N.Yamazaki's blog

主に音声合成について思ったことを書いてみようと思います。
<< サンプリング周波数変換(リサンプリング)技術 - 応用編 | main | ESP32でサウンド出力時のクリックノイズ対策(I2S+内蔵DAC) >>
ESP32で音声合成(AquesTalk pico for ESP32)

keywords: 音声合成 AquesTalk ESP32  I2S 
 

概要


 

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

ここで、{PATH_Arduino_ESP32}は、「Arduino core for the ESP32」をインストールした場所です。 私の環境では次のとおりでした。

C:¥Program Files (x86)¥arduino¥hardware¥espressif¥esp32

aquestalk¥aquestalk.hのaquestalkフォルダは作成してください。

 

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


リンク


 

・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
 

| AquesTalk | 19:11 | - | - |
PROFILE
Follow
CATEGORIES
LATEST ENTRIES
SEARCH THIS SITE
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND
SONY MDR-CD900ST
SONY MDR-CD900ST (JUGEMレビュー »)

普段これで開発しています。
RECOMMEND
RECOMMEND
RECOMMEND
RECOMMEND