メインコンテンツまでスキップ

WebSocket インタフェース

WebSocket の接続が確立した後、テキストメッセージで音声認識のリクエストを行い、逐次レスポンスを受け取れます。リアルタイムで録音されている音声ストリームなど音声データを少しずつ送信し、認識結果を逐次取得できます。

以下のようなステップで処理を行います。

  1. WebSocketの接続
  2. 認識要求の開始
  3. 音声の送信
  4. 状態イベントの取得
  5. 認識結果の取得
  6. 認識要求の終了
  7. 接続のクローズ
注記

WebSocket インタフェース の詳細を隠蔽して簡単にリアルタイム音声認識アプリケーションを作るためのクライアントライブラリを提供しています。詳細は、Wrpを参照してください。

利用の方法

1. WebSocketの接続

WebSocket で音声認識サーバに接続します。このとき、以下の2つのエンドポイントのどちらかを選択することで、ログ保存を許可するかどうかを選べます。

wss://acp-api.amivoice.com/v1/     (ログ保存あり)
wss://acp-api.amivoice.com/v1/nolog/ (ログ保存なし)

ログ保存については、ログ保存を参照してください。

サーバとの通信はテキストメッセージで行われます。ここではPythonを使ったコードで説明しますが、他の言語でも同様にWebSocket接続が確立した後はテキストメッセージの送受信を行うことでリアルタイムの音声認識を行うことができます。

ここではWebSocketを簡単に扱うためにPythonのwebsocket-clientライブラリを使います。AmiVoice APIのWebSocketインタフェースのログ保存ありのエンドポイントに WebSocket で接続します。サーバと WebSocket の接続が確立したときにon_openが呼び出され、サーバからメッセージを受け取ったときにon_messageが呼び出されます。このメソッドに処理を追加していきながら、音声認識サーバとの通信を説明します。

import websocket
import logging


logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(threadName)s %(message)s")


def on_open(ws):
logger.info("open")

def on_message(ws, message):
logger.info(f"message: {message}")

def on_close(ws):
logger.info("close")


ws = websocket.WebSocketApp('wss://acp-api.amivoice.com/v1/',
on_open=on_open,
on_message=on_message,
on_close=on_close)
ws.run_forever()
注意

基盤システムが非常に混雑した場合、ごく稀にWebSocket接続が失敗してしまうことがあります。この場合、接続に成功するまで何度かリトライを試みてください。

2. 認識要求の開始

WebSocket の接続に成功したら、sコマンドを送信します。sコマンドは以下の形式です。

s <audio_format> <grammar_file_names> <key>=<value> ...

audio_formatにはそのセッションで送信する音声の音声フォーマットを指定します。grammar_file_nameには、リクエストパラメータの接続エンジン名を指定します。続けて<key>=<value>の形式で、認証情報 (authorization)をauthorization={APPKEY}のように設定します。

サンプルに同梱している音声ファイル(test.wav)を汎用エンジン(-a-general)を使って書き起こしすることを考えます。音声のサンプリングレートは16kHzですので、audio_format16Kを、grammar_file_name-a-generalを設定します。WebSocket 接続時のハンドラon_openには、以下のようなコードを追加します。

APPKEY='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

def on_open(ws):
logger.info("open")
command = f"s 16K -a-general authorization={APPKEY}"
logger.info(f"send> {command}")
ws.send(command)

その他のリクエストパラメータを設定するには、<key1>=<value1> <key2>=<value2>...形式でsコマンドに追加します。ここでは「あのー」や「えーっと」などの不要語をあえて表示するkeepFillerToken、アップデートイベントの送信タイミングresultUpdatedIntervalを変更して 1 秒に設定するように、sコマンド行に2つのパラメータを追加します。

APPKEY='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

def on_open(ws):
logger.info("open")
command = f"s 16K -a-general authorization={APPKEY} keepFillerToken=1 resultUpdatedInterval=1000"
logger.info(f"send> {command}")
ws.send(command)

詳細はリファレンスのsコマンドパケットを参照してください。

レスポンス

音声認識のリクエストを受け付けると、サーバはテキストメッセージ、sコマンド応答パケット、を返します。

成功したとき
s
失敗したとき

s の後ろに半角スペースを挟んでエラーメッセージを受け取ります。エラーの種別については、リファレンスのsコマンド応答パケットを参照してください。

s エラーメッセージ

例:

s received unsupported audio format
注意

基盤システムが非常に混雑した場合、ごく稀に以下のようなエラーを返すことがあります。この場合、sコマンドが成功するまで何度かリトライを試みてください。 エラーメッセージの詳細は、リファレンスのsコマンドパケット サーバエラーを参照してください。

s can’t connect to recognizer server (can’t connect to server)

sコマンドの応答を処理するために、on_messageに以下のコードを追加します。

def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
if content != "":
logger.error(content)
return
# sコマンドに成功した

3. 音声の送信

sコマンドに成功すると、音声ファイルを送信できます。pコマンドを使って音声データのバイナリを送信します。pコマンドは以下の形式です。

p<audio_data>

audio_dataは、音声データのバイナリです。セッション開始時のsコマンドで指定した音声フォーマットの音声データを設定してください。

sコマンドのレスポンスに対するハンドラに、サンプルに同梱している音声ファイル(test.wav)を送信します。on_messageに以下のように追加します。ここでは、マイクから音声を録音したケースを模して実時間と同じタイミングで送信するためにsleepしています。

import time
import threading


def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
if content != "":
logger.error(content)
return

# sコマンドに成功した
# 要求に成功していた場合は音声ファイルのデータをサーバに送信する
def send_audio(*args):
with open(filename, mode='rb') as file:
buf = file.read(audio_block_size)
while buf:
logger.info("send> p [..({} bytes)..]".format(len(buf)))
ws.send(b'p' + buf,
opcode=websocket.ABNF.OPCODE_BINARY)
buf = file.read(audio_block_size)
# test.wavは、16bit、16kHzなので32,000バイト/sec。16,000ずつ送信するので0.5秒待つと実時間と同じ送り方になる
time.sleep(0.5)
logger.info("send> e")
# 音声の送信を終えた後、eコマンドを送信する
ws.send('e')
threading.Thread(target=send_audio).start()

4. 状態イベントの取得

音声を送信するとG、発話の開始(S)、発話の終了(E)、音声認識処理が開始されたことを示すイベント(C)を受け取ります。Gはサーバ側で生成した情報を通知するイベントですが、無視してください。SEイベントとともに音声の先頭を 0 とした相対時間がミリ秒単位で得られます。

例として、音声ファイル(test.wav)を送信したときにサーバから受け取るイベントは以下のようになります。

G 
S 250
C
E 8800

状態イベントを処理するためにon_messageに以下のようにコードを追加します。ここで追加したコードは何も作用しませんが、発話の開始、発話の終了の時間を利用する場合は適切に処理を追加します。

def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
# ...略...
elif event == 'G':
pass
elif event == 'S':
starttime = int(content)
elif event == 'E':
endtime = int(content)
elif event == 'C':
pass

5. 認識結果の取得

音声認識サーバは処理途中の結果をUイベントとして通知します。ここではsコマンドでresultUpdatedInterval=1000のように設定しているので、約1秒ごとにUイベントが得られます。処理が完了して結果が確定するとAイベントが得られます。結果の詳細は音声認識の結果を参照してください。test.wavの音声に対する一連の結果は、サンプルコードの結果を参照してください。

def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
# ...略...
elif event == 'G':
pass
elif event == 'S':
starttime = int(content)
elif event == 'E':
endtime = int(content)
elif event == 'C':
pass
elif event == 'U':
raw = json.loads(content) if content else ''
elif event == 'A' or event == 'R':
raw = json.loads(content) if content else ''

6. 認識要求の終了

音声をすべて送信し終わったら、eコマンドを送信することで音声認識セッションを終了できます。

e

以下のコードでは、音声ファイルのデータをすべて送信した後、eコマンドを送信しています。

def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
if content != "":
logger.error(content)
return

# sコマンドに成功した
# 要求に成功していた場合は音声ファイルのデータをサーバに送信する
def send_audio(*args):
with open(filename, mode='rb') as file:
buf = file.read(audio_block_size)
while buf:
logger.info("send> p [..({} bytes)..]".format(len(buf)))
ws.send(b'p' + buf,
opcode=websocket.ABNF.OPCODE_BINARY)
buf = file.read(audio_block_size)
# test.wavは、16bit、16kHzなので32,000バイト/sec。16,000ずつ送信するので0.5秒待つと実時間と同じ送り方になる
time.sleep(0.5)
# 音声の送信を終えた後、eコマンドを送信する
logger.info("send> e")
ws.send('e')
threading.Thread(target=send_audio).start()

eコマンドを送信すると、音声認識サーバでは受信した音声をすべて処理し、結果をすべて返した後に、eコマンドのレスポンスを返します。送信した音声の長さによって完了までに時間がかかりますので、すべての結果を得るためにeコマンドの応答を待ってください。

注意

通信の障害やサーバ側の遅延など想定外の状況に対応するため、適切な通信タイムアウトを設定し、音声認識サーバからの応答がない場合でも、アプリケーションが正常に動作するようにしてください。

ここでは、eコマンドのレスポンスが得られたら WebSocket をクローズしています。

def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
# ...略...
elif event == 'G':
pass
elif event == 'S':
starttime = int(content)
elif event == 'E':
endtime = int(content)
elif event == 'C':
pass
elif event == 'U':
raw = json.loads(content) if content else ''
elif event == 'A' or event == 'R':
raw = json.loads(content) if content else ''
elif event == 'e':
logger.info("close>")
ws.close()

サンプルコード

ここまでの Python コードの全体を示します。

websocket-sample.py
import time
import websocket
import json
import threading
import logging


logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(threadName)s %(message)s")

server = 'wss://acp-api.amivoice.com/v1/'
filename = 'test.wav'
codec = "16K"
audio_block_size = 16000
grammar_file_names = "-a-general"
options = {
"profileId" : "",
"profileWords" : "",
"keepFillerToken": "",
"resultUpdatedInterval" : "1000",
"authorization" : 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
}


def on_open(ws):
logger.info("open")
def start(*args):
command = "s {} {}".format(codec, grammar_file_names)
for k, v in options.items():
if v != "":
if k == 'profileWords':
v = '"' + v.replace('"', '""') + '"'
command += f" {k}={v}"
logger.info("send> {command}")
ws.send(command)
threading.Thread(target=start).start()


def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")

if event == 's':
if content == "can't connect to recognizer server":
logger.error(content)
return

def send_audio(*args):
with open(filename, mode='rb') as file:
buf = file.read(audio_block_size)
while buf:
logger.debug("send> p [..({} bytes)..]".format(len(buf)))
ws.send(b'p' + buf,
opcode=websocket.ABNF.OPCODE_BINARY)
buf = file.read(audio_block_size)
time.sleep(0.5)
logger.info("send> e")
ws.send('e')
threading.Thread(target=send_audio).start()

elif event == 'G':
pass
elif event == 'S':
starttime = int(content)
elif event == 'E':
endtime = int(content)
elif event == 'C':
pass
elif event == 'U':
raw = json.loads(content) if content else ''
elif event == 'A' or event == 'R':
raw = json.loads(content) if content else ''
elif event == 'e':
logger.info("close>")
ws.close()


def on_close(ws):
logger.info("close")


logger.info("open> {}".format(server))
ws = websocket.WebSocketApp(server,
on_open=on_open,
on_message=on_message,
on_close=on_close)
ws.run_forever()

実行

以下のように実行できます。

$ python websocket-sample.py

結果

音声ファイル(test.wav)を送信したときの動作ログは以下のようになります。プログラム開始からのミリ秒単位の時間、スレッド名、メッセージが表示されています。

Thread-1スレッドで音声を送信していることを示す(send> p [..(16000 bytes)..])が続き、途中結果(message: U)が約1秒単位で得られていることが確認できます。また、最後に確定した結果(message: A)が得られています。

         4  MainThread   open> wss://acp-api.amivoice.com/v1/
94 MainThread open
94 MainThread send> s LSB16K -a-general resultUpdatedInterval=1000 authorization={APPKEY}
133 MainThread message: s
134 Thread-1 send> p [..(16000 bytes)..]
174 MainThread message: G
637 Thread-1 send> p [..(16000 bytes)..]
668 MainThread message: S 250
668 MainThread message: C
1139 Thread-1 send> p [..(16000 bytes)..]
1639 Thread-1 send> p [..(16000 bytes)..]
2144 Thread-1 send> p [..(16000 bytes)..]
2647 Thread-1 send> p [..(16000 bytes)..]
3148 Thread-1 send> p [..(16000 bytes)..]
3174 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2..."}
3649 Thread-1 send> p [..(16000 bytes)..]
4153 Thread-1 send> p [..(16000 bytes)..]
4179 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"\u306f"},{"written":"\u3001"},{"written":"\u3072\u3068\u3068\u304d"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u3072\u3068\u3068\u304d..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u3072\u3068\u3068\u304d..."}
4656 Thread-1 send> p [..(16000 bytes)..]
5157 Thread-1 send> p [..(16000 bytes)..]
5184 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"\u306f"},{"written":"\u3001"},{"written":"\u4eba"},{"written":"\u3068"},{"written":"\u6a5f\u68b0"},{"written":"\u3068"},{"written":"\u306e"},{"written":"\u81ea\u7136"},{"written":"\u306a"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a..."}
5658 Thread-1 send> p [..(16000 bytes)..]
6159 Thread-1 send> p [..(16000 bytes)..]
6187 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"\u306f"},{"written":"\u3001"},{"written":"\u4eba"},{"written":"\u3068"},{"written":"\u6a5f\u68b0"},{"written":"\u3068"},{"written":"\u306e"},{"written":"\u81ea\u7136"},{"written":"\u306a"},{"written":"\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3"},{"written":"\u3092"},{"written":"\u6301"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u6301..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u6301..."}
6660 Thread-1 send> p [..(16000 bytes)..]
7161 Thread-1 send> p [..(16000 bytes)..]
7185 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"\u306f"},{"written":"\u3001"},{"written":"\u4eba"},{"written":"\u3068"},{"written":"\u6a5f\u68b0"},{"written":"\u3068"},{"written":"\u306e"},{"written":"\u81ea\u7136"},{"written":"\u306a"},{"written":"\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3"},{"written":"\u3092"},{"written":"\u5b9f\u73fe"},{"written":"\u3057"},{"written":"\u3001"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001..."}
7662 Thread-1 send> p [..(16000 bytes)..]
8164 Thread-1 send> p [..(16000 bytes)..]
8199 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"\u306f"},{"written":"\u3001"},{"written":"\u4eba"},{"written":"\u3068"},{"written":"\u6a5f\u68b0"},{"written":"\u3068"},{"written":"\u306e"},{"written":"\u81ea\u7136"},{"written":"\u306a"},{"written":"\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3"},{"written":"\u3092"},{"written":"\u5b9f\u73fe"},{"written":"\u3057"},{"written":"\u3001"},{"written":"\u8c4a\u304b"},{"written":"\u306a"},{"written":"\u672a\u6765"},{"written":"\u3092"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092..."}
8668 Thread-1 send> p [..(16000 bytes)..]
9171 Thread-1 send> p [..(16000 bytes)..]
9188 MainThread message: E 8800
9190 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"\u306f"},{"written":"\u3001"},{"written":"\u4eba"},{"written":"\u3068"},{"written":"\u6a5f\u68b0"},{"written":"\u3068"},{"written":"\u306e"},{"written":"\u81ea\u7136"},{"written":"\u306a"},{"written":"\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3"},{"written":"\u3092"},{"written":"\u5b9f\u73fe"},{"written":"\u3057"},{"written":"\u3001"},{"written":"\u8c4a\u304b"},{"written":"\u306a"},{"written":"\u672a\u6765"},{"written":"\u3092"},{"written":"\u5275\u9020"},{"written":"\u3057\u3066"},{"written":"\u3044\u304f"},{"written":"\u3053\u3068"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092\u5275\u9020\u3057\u3066\u3044\u304f\u3053\u3068..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092\u5275\u9020\u3057\u3066\u3044\u304f\u3053\u3068..."}
9390 MainThread message: U {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2"},{"written":"\u306f"},{"written":"\u3001"},{"written":"\u4eba"},{"written":"\u3068"},{"written":"\u6a5f\u68b0"},{"written":"\u3068"},{"written":"\u306e"},{"written":"\u81ea\u7136"},{"written":"\u306a"},{"written":"\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3"},{"written":"\u3092"},{"written":"\u5b9f\u73fe"},{"written":"\u3057"},{"written":"\u3001"},{"written":"\u8c4a\u304b"},{"written":"\u306a"},{"written":"\u672a\u6765"},{"written":"\u3092"},{"written":"\u5275\u9020"},{"written":"\u3057\u3066"},{"written":"\u3044\u304f"},{"written":"\u3053\u3068"},{"written":"\u3092"},{"written":"\u76ee\u6307\u3057"},{"written":"\u307e\u3059"},{"written":"\u3002"},{"written":"..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092\u5275\u9020\u3057\u3066\u3044\u304f\u3053\u3068\u3092\u76ee\u6307\u3057\u307e\u3059\u3002..."}],"text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092\u5275\u9020\u3057\u3066\u3044\u304f\u3053\u3068\u3092\u76ee\u6307\u3057\u307e\u3059\u3002..."}
9471 MainThread message: A {"results":[{"tokens":[{"written":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2","confidence":1.00,"starttime":522,"endtime":1578,"spoken":"\u3042\u3069\u3070\u3093\u3059\u3068\u3081\u3067\u3043\u3042"},{"written":"\u306f","confidence":1.00,"starttime":1578,"endtime":1866,"spoken":"\u306f"},{"written":"\u3001","confidence":0.74,"starttime":1866,"endtime":2026,"spoken":"_"},{"written":"\u4eba","confidence":1.00,"starttime":2026,"endtime":2314,"spoken":"\u3072\u3068"},{"written":"\u3068","confidence":1.00,"starttime":2314,"endtime":2426,"spoken":"\u3068"},{"written":"\u6a5f\u68b0","confidence":1.00,"starttime":2426,"endtime":2826,"spoken":"\u304d\u304b\u3044"},{"written":"\u3068","confidence":1.00,"starttime":2826,"endtime":2954,"spoken":"\u3068"},{"written":"\u306e","confidence":1.00,"starttime":2954,"endtime":3082,"spoken":"\u306e"},{"written":"\u81ea\u7136","confidence":1.00,"starttime":3082,"endtime":3434,"spoken":"\u3057\u305c\u3093"},{"written":"\u306a","confidence":1.00,"starttime":3434,"endtime":3530,"spoken":"\u306a"},{"written":"\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3","confidence":1.00,"starttime":3530,"endtime":4378,"spoken":"\u3053\u307f\u3085\u306b\u3051\u30fc\u3057\u3087\u3093"},{"written":"\u3092","confidence":1.00,"starttime":4378,"endtime":4458,"spoken":"\u3092"},{"written":"\u5b9f\u73fe","confidence":1.00,"starttime":4458,"endtime":4922,"spoken":"\u3058\u3064\u3052\u3093"},{"written":"\u3057","confidence":1.00,"starttime":4922,"endtime":5434,"spoken":"\u3057"},{"written":"\u3001","confidence":1.00,"starttime":5434,"endtime":5546,"spoken":"_"},{"written":"\u8c4a\u304b","confidence":1.00,"starttime":5546,"endtime":5994,"spoken":"\u3086\u305f\u304b"},{"written":"\u306a","confidence":1.00,"starttime":5994,"endtime":6090,"spoken":"\u306a"},{"written":"\u672a\u6765","confidence":1.00,"starttime":6090,"endtime":6490,"spoken":"\u307f\u3089\u3044"},{"written":"\u3092","confidence":1.00,"starttime":6490,"endtime":6554,"spoken":"\u3092"},{"written":"\u5275\u9020","confidence":0.93,"starttime":6554,"endtime":7050,"spoken":"\u305d\u3046\u305e\u3046"},{"written":"\u3057\u3066","confidence":0.99,"starttime":7050,"endtime":7210,"spoken":"\u3057\u3066"},{"written":"\u3044\u304f","confidence":1.00,"starttime":7210,"endtime":7418,"spoken":"\u3044\u304f"},{"written":"\u3053\u3068","confidence":1.00,"starttime":7418,"endtime":7690,"spoken":"\u3053\u3068"},{"written":"\u3092","confidence":1.00,"starttime":7690,"endtime":7722,"spoken":"\u3092"},{"written":"\u76ee\u6307\u3057","confidence":0.77,"starttime":7722,"endtime":8090,"spoken":"\u3081\u3056\u3057"},{"written":"\u307e\u3059","confidence":0.76,"starttime":8090,"endtime":8506,"spoken":"\u307e\u3059"},{"written":"\u3002","confidence":0.82,"starttime":8506,"endtime":8794,"spoken":"_"}],"confidence":0.998,"starttime":250,"endtime":8794,"tags":[],"rulename":"","text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092\u5275\u9020\u3057\u3066\u3044\u304f\u3053\u3068\u3092\u76ee\u6307\u3057\u307e\u3059\u3002"}],"utteranceid":"20220620/ja_ja-amivoicecloud-16k-user01@01817dce7ba30a301ccf8536-0620_061133","text":"\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u30fb\u30e1\u30c7\u30a3\u30a2\u306f\u3001\u4eba\u3068\u6a5f\u68b0\u3068\u306e\u81ea\u7136\u306a\u30b3\u30df\u30e5\u30cb\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u73fe\u3057\u3001\u8c4a\u304b\u306a\u672a\u6765\u3092\u5275\u9020\u3057\u3066\u3044\u304f\u3053\u3068\u3092\u76ee\u6307\u3057\u307e\u3059\u3002","code":"","message":""}
9495 MainThread message: G
9672 Thread-1 send> p [..(2980 bytes)..]
10174 Thread-1 send> e
10225 MainThread message: e
10225 MainThread close>

エラー状態

クライアントが送信したsコマンド、pコマンド、eコマンドが何らかの原因で失敗した場合、例えば、コマンドの送信手順に誤りがあった場合、制限事項に抵触した場合、基板側の問題が起きた場合など、応答にエラーメッセージが含まれるエラーレスポンスを返すことがあります。

成功レスポンス

s
p
e

エラーレスポンス

s エラーメッセージ
p エラーメッセージ
e エラーメッセージ

エラーレスポンスの場合、sコマンドを送信する前の初期状態に戻ります。再度sコマンドからリクエストを送信してください。詳細はパケットおよび状態遷移の音声供給状態遷移図を参照してください。

エラーメッセージを参照し、クライアントエラーの場合は原因を修正してから、AmiVoice API基板側のサーバエラーの場合はしばらく待ってから再度リクエストを送信してください。制限事項によるエラーについては制限事項を参照してください。エラーメッセージの詳細は以下を参照してください。

sコマンドのサーバエラーや予期せぬネットワークの送信エラーなどの問題などに対応するために、sコマンドが成功するまでリトライすることが有効です。その際、リングバッファなどを使ってストリーミング音源からの音声を失わないような対策を行うことでより頑健なアプリケーションを作成することができます。

セッションの維持

音声をリアルタイムに送信している場合、600 秒間音声が検出されない(無音の音声を送り続ける)と、サーバからセッションが切断されてしまいます。このとき以下のようなpコマンド応答パケットを受け取ります。

p can't feed audio data to recognizer server

また、60 秒間、通信がないとサーバ側からセッションがクローズされてしまいます。このとき以下のようなeコマンド応答パケットを受け取ります。

e timeout occurred while recognizing audio data from client

これらの応答を受け取ったら、再度接続をやり直してから音声を送信してください。

その他のドキュメント

  • API リファレンスは、WebSocket インタフェースを参照してください。
  • WebSocket インタフェース を利用する際の通信処理や手順をクラスライブラリ化し、音声認識アプリケーションに必要なインタフェースを実装するだけで簡単に音声認識アプリケーションを作成できるクライアントライブラリ (Wrp)を提供しています。まずはサンプルプログラム WrpTesterを動かしてみてください。Wrp クライアントライブラリのインタフェース仕様については、クライアントライブラリの Wrp(WebSocket インタフェース クライアント) を参照してください。