WebSocket インタフェース
WebSocket の接続が確立した後、テキストメッセージで音声認識のリクエストを行い、逐次レスポンスを受け取れます。リアルタイムで録音されている音声ストリームなど音声データを少しずつ送信し、認識結果を逐次取得できます。
以下のようなステップで処理を行います。
WebSocket インタフェース の詳細を隠蔽して簡単にリアルタイム音声認識アプリケーションを作るためのクライアントライブラリを提供しています。使い方は、リアルタイム音声認識ライブラリ Wrpの使い方を参照してください。
音声ストリーミングを使ったアプリケーションは、WebSocketの接続後、コマンドとコマンドの応答(s
、p
、e
)、サーバの処理に応じたイベント(S
、E
、C
、U
、A
)を受け取り、実装を行います。一般的な流れは以下のようになります。
以下ではステップバイステップで実装の方法を説明します。コマンドとコマンドの応答(s
、p
、e
)、サーバの処理に応じたイベント(S
、E
、C
、U
、A
)の詳細、レスポンスについては、ストリーミングの応答を参照してください。
利用の方法
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}
のように設定します。<key>=<value>
にはその他のリクエストパラメータを設定することができます。
サンプルに同梱している音声ファイル(test.wav)を汎用エンジン(-a-general
)を使って書き起こしすることを考えます。この音声ファイルはサンプリングレート16kHz、wavコンテナファイルですので、audio_format
に16K
を指定します。詳細はヘッダありの音声ファイルの場合を参照してください。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つのパラメータを追加します。
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)
リクエストパラメータに設定する値に空白が含まれる場合は、valueを"value"
のようにダブルクォートで囲んでください。例えば、segmenterProperties
に複数のパラメータを設定する場合は以下のようにします。
segmenterProperties="useDiarizer=1 diarizerAlpha=1e-20 diarizerTransitionBias=1e-10"
on_open
は以下のようになります。
def on_open(ws):
logger.info("open")
command = f"s 16K -a-general authorization={APPKEY} segmenterProperties=\"useDiarizer=1 diarizerAlpha=1e-20 diarizerTransitionBias=1e-10\""
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
コマンドで指定した音声フォーマットにあった音声データを送信してください。もしフォーマットが異なっても、エラーにはなりませんが、レスポンスに非常に時間がかかったり、認識結果が正しく得られません。
- 1回の
p
コマンドで送信できる音声データのサイズは最大で16MBです。データサイズがそれ以上であれば、分割してください。 - 音声データの分割は、任意の箇所で行って問題ありません。wavのチャンクや、mp3/ogg/opusなどのフレーム境界を意識する必要はありません。
- 途中で送信するデータのフォーマットを変更することはできません。音声フォーマットを変更する場合は、
e
コマンドで終了し、s
から新しく音声認識のリクエストをしてください。ヘッダありの音声ファイルの場合も同様にファイル毎にe
コマンドで終了し、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
はサーバ側で生成した情報を通知するイベントですが、無視してください。S
とE
イベントとともに音声の先頭を 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
コマンドの応答を待ってください。
通信の障害やサーバ側の遅延など想定外の状況に対応するため、適切な通信タイムアウトを設定し、音声認識サーバからの応答がない場合でも、アプリケーションが正常に動作するようにしてください。
7. WebSocketの切断
ここでは、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 コードの全体を示します。
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>
セッションの維持
音声をリアルタイムに送信している場合、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
)を提供しています。まずはリアルタイム音声認識ライブラリ Wrpの使い方を参照してください。