AmiVoice Techblog

AmiVoiceの音声認識に関する技術情報・活用方法・組み込み方などを発信するアドバンスト・メディアのテックブログです

Linuxで音声認識を動かしてみた3(ACP+pulseaudio編)


f:id:amivoice_techblog:20210115094116p:plain いちかわちゃん

こんにちは【いちかわちゃん】です。

アドバンスト・メディアという会社で開発をしています。

 

Linux音声認識を動かしてみた」という題材で3回に分けてお話していきます。

いよいよ今回が3回目となりますが、どうぞ最後までお付き合いください。

 

各回で話すことは以下です。

・AmiVoice Cloud Platformのプログラムで音声ファイルから音声認識する。

・AmiVoice Cloud Platformのプログラムで簡易的にマイクから音声認識する。

・AmiVoice Cloud Platformのプログラムでマイクから音声認識する。 ←now

 

今回の目標はACPのC++とPulseAudioライブラリを使ってマイク認識するまでです。

  

続きものの記事のため、前回の記事を見てない人は参照ください。

amivoice-tech.hatenablog.com

 

以下の環境で動かしています。

OS Ubuntu18.04
アーキテクチャ AMD64
GCC 7.5.0

 

手順

 1.PulseAudioを使ってプログラムを書く

 2.ACPのプログラムを改造

 

1.PulseAudioを使ってプログラムを書く

 

プログラムを書く前に、そもそもPulseAudioってなんですかって感じだと思うので雑に説明します。

PulseAudioってなーに?

 Linuxサウンド系のシステムは、ALSAって子が代々サウンドカードとのやり取りを担当してくれてます。でもこの子はサウンドカードを掴んじゃうので、一度に複数のものを再生させたり録音させたりすることができません。
 そんな中で登場したのがPulseAudioです。この子がサウンドを使うアプリとALSAを仲介してくれることで、複数のアプリがサウンドカードを操作できるようになります。

 


ではこれからPulseAudioのプログラムを書きます。

作業ディレクトリのファイル構成は以下のようになっていると嬉しいです。

 work/
 ├ acp/
 │ ├ Wrp
 │ ├ Hrp
 │ ├ audio
 │ ├ curl-ca-bundle.crt
 │ └ readme.txt
 └ poco/

 

今回はPulseAudioをラップしたものを使います。

 

github.com 

以下のコマンドでビルド&実行出来ます。

$ 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;
}

録音するにはami_pulseaudio_createを呼んだ後、ami_pulseaudio_startするだけです。わりと簡単ですね。

 

 

ラップしてないシンプルAPIのサンプルとドキュメントは公式に乗っているので、そっちを見てください

freedesktop.org

もしちゃんと実装するなら非同期APIを使った方が色々出来て良いです。

以下にサンプル的なものを作ったので参考にしてください。

github.com

  

 2.ACPのプログラムを改造

作業ディレクトリのファイル構成は以下のようになっていると嬉しいです。

 work/
 ├ acp/
 │ ├ Wrp
 │ ├ Hrp
 │ ├ audio
 │ ├ curl-ca-bundle.crt
 │ └ readme.txt
 ├ poco/
 └ ami_pulseaudio/
 

 

 acp/Wrp/cpp/WrpSimpleTester.cppを19行目と402行目あたりを適当なエディタで修正します。

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 $< -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やら組み込みやらで問い合わせすると高確率でエンカウントするモンスター。