WebSocket 인터페이스
WebSocket 연결이 확립된 후, 텍스트 메시지로 음성 인식 요청을 수행하고 순차적으로 응답을 받을 수 있습니다. 실시간으로 녹음되는 음성 스트림 등 음성 데이터를 조금씩 전송하고 인식 결과를 순차적으로 얻을 수 있습니다.
다음과 같은 단계로 처리를 수행합니다.
WebSocket 인터페이스의 세부사항을 숨기고 간단하게 실시간 음성 인식 애플리케이션을 만들기 위한 클라이언트 라이브러리를 제공하고 있습니다. 사용 방법은 실시간 음성 인식 라이브러리 Wrp 사용법을 참조하십시오.
음성 스트리밍을 사용한 애플리케이션은 WebSocket 연결 후 명령어와 명령어 응답(s
, p
, e
), 서버 처리에 따른 이벤트(S
, E
, C
, U
, A
)를 받아 구현을 수행합니다. 일반적인 흐름은 다음과 같습니다.
이하에서는 단계별로 구현 방법을 설명합니다. 명령어와 명령어 응답(s
, p
, e
), 서버 처리에 따른 이벤트(S
, E
, C
, U
, A
)의 세부 사항, 응답에 대해서는 스트리밍 응답을 참조하십시오.
사용 방법
1. WebSocket 연결
WebSocket으로 음성 인식 서버에 연결합니다. 이때 다음 두 개의 엔드포인트 중 하나를 선택하여 로그 저장을 허용할지 여부를 선택할 수 있습니다.
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바이트/초입니다. 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바이트/초입니다. 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 사용법을 참조하십시오.