Linuxで音声認識を動かしてみた3(ACP+pulseaudio編)
いちかわちゃん
こんにちは【いちかわちゃん】です。
アドバンスト・メディアという会社で開発をしています。
「Linuxで音声認識を動かしてみた」という題材で3回に分けてお話していきます。
いよいよ今回が3回目となりますが、どうぞ最後までお付き合いください。
各回で話すことは以下です。
・AmiVoice Cloud Platformのプログラムで音声ファイルから音声認識する。
・AmiVoice Cloud Platformのプログラムで簡易的にマイクから音声認識する。
・AmiVoice Cloud Platformのプログラムでマイクから音声認識する。 ←now
今回の目標はACPのC++とPulseAudioライブラリを使ってマイク認識するまでです。
続きものの記事のため、前回の記事を見てない人は参照ください。
以下の環境で動かしています。
OS | Ubuntu18.04 |
アーキテクチャ | AMD64 |
GCC | 7.5.0 |
手順
- PulseAudioを使ってプログラムを書く
- ACPのプログラムを改造
1.PulseAudioを使ってプログラムを書く
プログラムを書く前に、そもそもPulseAudioってなんですかって感じだと思うので雑に説明します。
Linuxのサウンド系のシステムは、ALSAって子が代々サウンドカードとのやり取りを担当してくれてます。でもこの子はサウンドカードを掴んじゃうので、一度に複数のものを再生させたり録音させたりすることができません。
そんな中で登場したのがPulseAudioです。この子がサウンドを使うアプリとALSAを仲介してくれることで、複数のアプリがサウンドカードを操作できるようになります。
ではこれからPulseAudioのプログラムを書きます。
作業ディレクトリのファイル構成は以下のようになっていると嬉しいです。
work/
├ acp/
│ ├ Wrp
│ ├ Hrp
│ ├ audio
│ ├ curl-ca-bundle.crt
│ └ readme.txt
└ poco/
今回はPulseAudioをラップしたものを使います。
以下のコマンドでビルド&実行出来ます。
$ cd ~/work
$ git clone https://github.com/r-ichikawa-amivoice/ami_pulseaudio
$ cd ami_pulseaudio
$ make
$ bash run.sh
マイクの音がスピーカーから出れば成功です。
ami_pulseaudioはマルチスレッドでPulseAudioを使えるようにラップしたものになります。C言語っぽくラップするプログラムを書くのがめっちゃ楽しかったので作りました。
Mainプログラムは以下
#include "ami_pulseaudio.h"
#include <stdio.h>
#include <unistd.h>
void callback(enum AMI_PULSEAUDIO_RESULT_STATE result_state, int data_size, char* data){
switch(result_state){
case AMI_PULSEAUDIO_RESULT_STATE_ACCEPTED:
write(STDOUT_FILENO, data, data_size);
break;
}
}
int main(int argc, char** argv) {
int a = 0;
void* ap = 0;
ap = ami_pulseaudio_create(callback);
ami_pulseaudio_start(ap);
getchar();
ami_pulseaudio_stop(ap);
ami_pulseaudio_free(ap);
ap = 0;
}
録音するには
ラップしてないシンプルAPIのサンプルとドキュメントは公式に乗っているので、そっちを見てください
もしちゃんと実装するなら非同期APIを使った方が色々出来て良いです。
以下にサンプル的なものを作ったので参考にしてください。
2.ACPのプログラムを改造
作業ディレクトリのファイル構成は以下のようになっていると嬉しいです。
work/
├ acp/
│ ├ Wrp
│ ├ Hrp
│ ├ audio
│ ├ curl-ca-bundle.crt
│ └ readme.txt
├ poco/
└ ami_pulseaudio/
acp/Wrp/cpp/WrpSimpleTester.cppのincludeが終わった(19行目あたり)とファイルオープン(402行目あたり)を適当なエディタで修正します。
#include "ami_pulseaudio.h"
int wrp_flag = 1;
com::amivoice::wrp::Wrp* wrp_;
void callback(enum AMI_PULSEAUDIO_RESULT_STATE result_state, int data_size, char* data){
switch(result_state){
case AMI_PULSEAUDIO_RESULT_STATE_ACCEPTED:
if (!wrp_->feedData(data, 0, data_size)) {
printf("%s", wrp_->getLastMessage());
printf("WebSocket 音声認識サーバへの音声データの送信に失敗しました。");
wrp_flag = 0;
break;
}
}
}
...
#if 0
// 音声データファイルのオープン
FILE* audioStream;
if (fopen_s(&audioStream, audioFileName, "rb") == 0) {
// 音声データファイルからの音声データの読み込み
char audioData[4096];
int audioDataReadBytes = (int)fread(audioData, 1, 4096, audioStream);
while (audioDataReadBytes > 0) {
// 認識結果情報待機数が 1 以下になるまでスリープ
int maxSleepTime = 50000;
while (wrp->getWaitingResults() > 1 && maxSleepTime > 0) {
wrp->sleep(100);
maxSleepTime -= 100;
}
// WebSocket 音声認識サーバへの音声データの送信
if (!wrp->feedData(audioData, 0, audioDataReadBytes)) {
print("%s", wrp->getLastMessage());
print("WebSocket 音声認識サーバへの音声データの送信に失敗しました。")
;
break;
}
// 音声データファイルからの音声データの読み込み
audioDataReadBytes = (int)fread(audioData, 1, 4096, audioStream);
}
// 音声データファイルのクローズ
fclose(audioStream);
} else {
print("音声データファイル %s の読み込みに失敗しました。", audioFileName);
}
#else
//2回目で改造したあたり
void* ap = 0;
wrp_ = wrp;
ap = ami_pulseaudio_create(callback);
ami_pulseaudio_start(ap);
while(wrp_flag){
wrp->sleep(100);
}
ami_pulseaudio_stop(ap);
ami_pulseaudio_free(ap);
ap = 0;
#endif
以下のようにWrpSimpleTester.makefileを更新。
PRJ = WrpSimpleTester CPPC = g++ -std=c++11 LD = g++ LDD = ldd -d SRC = \ $(PRJ).cpp LIB = \ -lWrp \ ../../ami_pulseaudio/lib/libami_pulseaudio.so CPPDEFINES = \ -DLINUX \ -DPOSIX \ -D$(if $(debug),_DEBUG,NDEBUG) CPPFLAGS = \ -w \ -O$(if $(debug),0 -g,3) \ -Isrc \ -I../../ami_pulseaudio/src LDFLAGS = \ -L$(OUTDIR) OUTDIR = bin/linux64$(if $(debug),_debug,_release) OBJDIR = obj/linux64$(if $(debug),_debug,_release)/$(PRJ) OUT = $(OUTDIR)/$(PRJ) OBJ = $(patsubst %.cpp,$(OBJDIR)/%.o,$(notdir $(SRC))) DEP = $(patsubst %.cpp,$(OBJDIR)/%.d,$(notdir $(SRC))) VPATH = $(sort $(dir $(SRC))) build: $(OUT) $(OUT): $(OBJ) @mkdir -p $(dir $@) $(LD) $(LDFLAGS) $(OBJ) $(LIB) -o $@ $(OBJ): $(OBJDIR)/%.o: %.cpp @mkdir -p $(dir $@) $(CPPC) $(CPPFLAGS) $(CPPDEFINES) -c -MMD
lt; -o $@
clean:
-rm -f $(OUT) $(OBJ) $(DEP)
-include $(DEP)
以下のようなrun3.shを作ります。
#!/bin/bash
read -p "Please enter AppKey: " AppKey
set -x
export SSL_CERT_FILE=../../curl-ca-bundle.crt
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:bin/linux64_release:../../../ami_pulseaudio/lib/
bin/linux64_release/WrpSimpleTester wss://acp-api.amivoice.com/v1/ ../../audio/test.wav 16K -a-general $AppKey
これでビルドと実行をしてみてください。
$ cd ~/work/acp/Wrp/cpp/
$ bash build
$ bash run3.sh
実行後マイクに向かってしゃべってみて、
以下のような結果が返ってくれば成功です。
Please enter AppKey: [APPKEY]
+ export SSL_CERT_FILE=../../curl-ca-bundle.crt
+ SSL_CERT_FILE=../../curl-ca-bundle.crt
+ export LD_LIBRARY_PATH=/opt/ros/melodic/lib:bin/linux64_release:../../../ami_pulseaudio/lib/
+ LD_LIBRARY_PATH=/opt/ros/melodic/lib:bin/linux64_release:../../../ami_pulseaudio/lib/
+ bin/linux64_release/WrpSimpleTester wss://acp-api.amivoice.com/v1/ ../../audio/test.wav 16K -a-general [APPKEY]
{“results”:[{“tokens”:[{“written”:”\u3053\u3093\u306b\u3061\u306f”,”confidence”:0.98,”starttime”:5506,”endtime”:6306,”spoken”:”\u3053\u3093\u306b\u3061\u306f”},{“written”:”\u3002″,”confidence”:0.84,”starttime”:6306,”endtime”:6642,”spoken”:”_”}],”confidence”:1.000,”starttime”:5250,”endtime”:6642,”tags”:,”rulename”:””,”text”:”\u3053\u3093\u306b\u3061\u306f\u3002″}],”utteranceid”:”20210329/ja_ja-amivoicecloud-16k-hon-ichikawa@01787d13deee0a301c5f8536-0329_172243″,”text”:”\u3053\u3093\u306b\u3061\u306f\u3002″,”code”:””,”message”:””}
-> こんにちは。
{“results”:[{“tokens”:[{“written”:”\u3055\u3088\u3046\u306a\u3089″,”confidence”:0.99,”starttime”:7420,”endtime”:8348,”spoken”:”\u3055\u3088\u3046\u306a\u3089″},{“written”:”\u3002″,”confidence”:0.72,”starttime”:8348,”endtime”:8524,”spoken”:”_”}],”confidence”:0.996,”starttime”:7100,”endtime”:9436,”tags”:,”rulename”:””,”text”:”\u3055\u3088\u3046\u306a\u3089\u3002″}],”utteranceid”:”20210329/ja_ja-amivoicecloud-16k-hon-ichikawa@01787d13deee0a301c5f8536-0329_172245″,”text”:”\u3055\u3088\u3046\u306a\u3089\u3002″,”code”:””,”message”:””}
-> さようなら。
まとめ
今回はACPサンプルを改造してリアルタイム認識をしてみました。
オープンソースでプログラムを書いたのが初めてで自信がないですが、誰かの参考になれれば嬉しいです。
あとバグ報告とかアドバイスとかあれば、ぜひ教えてください。
この記事を書いた人
-
いちかわちゃん
Linuxやら組み込みやらで問い合わせすると高確率でエンカウントするモンスター。