API WebSocket hỗ trợ nhận dạng online. Do không có thông tin về audio nên bạn cần cung cấp thông tin này tới API thông qua uri query string:
content-type=audio/x-raw,+layout=(string)interleaved,+rate=(int)16000,+format=(string)S16LE,+channels=(int)1
Trong đó:
Field | Mô tả |
---|---|
content-type | trường lưu trữ thông tin của audio, các tham số dưới đây được lưu trong trường này |
rate | sample rate của audio |
format | định dạng PCM của audio. Tham khảo: PCM format |
channel | số kênh của audio |
Để sử dụng API với account của riêng mình, bạn cần thêm thông tin token của mình thông qua trường 'token' trong uri query string: token=$YOUR_TOKEN
Để nhận dạng sử dụng API WebSocket, bạn cần:
Tạo kết nối WebSocket tới API với các thông tin cần thiết đã nêu ở trên.
Gửi data là mảng byte của luồng audio cần nhận dạng với tần suất 4 lần/giây, mỗi lần gửi với số byte = (byte rate của audio)/4. Ví dụ Audio có rate =16000, format S16LE (16bit <=> 2 byte/sample) có byte rate = 16000*2 = 32000 => Mỗi cần lần gửi lên 8000 byte.
Khi kết thúc audio, client nên gửi lên byte của string 'EOS' encode UTF-8 để server biết & kết thúc nhận dạng. Hoặc server nhận dạng sẽ tự kết thúc nhận dạng sau một khoảng thời gian ngắn sau khi giọng nói cuối cùng được gửi lên.
Đóng kết nối tới API. Kết thúc.
!!! notice "Lưu ý" Do một số client chưa được cập nhật chứng chỉ của https://viettelgroup.ai, để có thể sử dụng được kết nối bảo mật.
Có thể sử dụng file chứng chỉ trực tiếp trong code, xem toàn bộ code tại đây: Speech to text WebSocket
Bạn cần import chứng chỉ vào truststore thông qua công cụ keytool đi kèm với Java
$JAVA_HOME/bin/keytool -import -file $VTCC_CERT -alias wwwvtccai -keystore $JAVA_HOME/jre/lib/security/cacerts
Trong đó:
- JAVA_HOME: thư mục JDK trên máy client.
- VTCC_CERT: Đường dẫn đến file chứng chỉ của https://viettelgroup.ai
- $JAVA_HOME/jre/lib/security/cacerts: Vị trí file lưu trữ truststore mặc định của Java
Nếu yêu cầu mật khẩu, vui lòng nhập vào: 'changeit'
Do chứng chỉ sẽ được import vào TrustStore mặc định của JAVA nên bạn chỉ cần import 1 lần duy nhất.
Xem ví dụ đầy đủ tại đây: VTCC Asr WebSocketSample
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import os.path
import sys
import time
from pathlib import Path
from threading import Thread
import websocket
if sys.version_info[0] < 3:
raise Exception("Must be using Python 3.xx")
root = logging.getLogger()
root.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)
dirname = os.path.dirname(__file__)
audio_path = os.path.join(Path(dirname).parent, 'test_audio.wav')
cert_path = os.path.join(Path(dirname).parent.parent, 'vtcc-cert/wwwvtccai.crt')
def rate_limited(max_per_second):
min_interval = 1.0 / float(max_per_second)
def decorate(func):
last_time_called = [0.0]
def rate_limited_function(*args, **kargs):
elapsed = time.clock() - last_time_called[0]
left_to_wait = min_interval - elapsed
if left_to_wait > 0:
time.sleep(left_to_wait)
ret = func(*args, **kargs)
last_time_called[0] = time.clock()
return ret
return rate_limited_function
return decorate
@rate_limited(4)
def send_data(ws, data):
ws.send(data)
def on_message(ws, message):
print(message)
def on_error(ws, error):
logging.warn(error)
def on_close(ws):
logging.info("### closed ###")
ws.close()
class AsrWebSocket:
def __init__(self, sample_rate=16000, audio_format='S16LE', channels=1, token='anonymous',
url='wss://viettelgroup.ai/voice/api/asr/v1/ws/decode_online'):
self.sample_rate = sample_rate
self.format = audio_format
self.channels = channels
self.token = token
self.url = url
self.audio_stream = None
self.ws = None
self.sslopt = {
'ca_certs': cert_path
}
def generate_url_query(self):
query_url = self.url + '?content-type=audio/x-raw,+layout=(string)interleaved,+rate=(int)' + str(
self.sample_rate)
query_url += ',+format=(string)' + self.format + ',+channels=(int)' + str(self.channels) + '&token=' + self.token
return query_url
def on_open(self):
def run():
for block in iter(lambda: self.audio_stream.read(self.sample_rate // 4), b''):
send_data(self.ws, block)
self.ws.send('EOS')
logging.info('Upload thread terminated')
my_thread = Thread(target=run)
my_thread.start()
def recognize(self, audio_steam):
self.audio_stream = audio_steam
self.ws = websocket.WebSocketApp(self.generate_url_query(),
on_message=on_message,
on_error=on_error,
on_close=on_close,
on_open=self.on_open)
self.ws.run_forever(sslopt=self.sslopt)
ws_asr = AsrWebSocket()
ws_asr.recognize(open(audio_path, 'rb'))
package speech.asr.ws;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import speech.asr.utils.PCMFormat;
import java.io.BufferedInputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
public class AsrWebSocketSample {
public static void main(String[] args) throws Exception {
IResponseHandler<WebSocketFrame> handler = new IResponseHandler<WebSocketFrame>() {
@Override
public void onMessage(WebSocketFrame frame) {
if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
System.out.println(textFrame.text());
} else
System.out.println(frame);
}
@Override
public void onFailure(Throwable cause) {
cause.printStackTrace();
}
@Override
public void onComplete() {
System.err.println("completed");
}
};
try (
BufferedInputStream bi = new BufferedInputStream(Files.newInputStream(Paths.get("src/main/resources/test_audio.wav")))
) {
AsrWebSocketClient client = AsrWebSocketClient.newBuilder()
.setSampleRate(16000)
.setAudioFormat(PCMFormat.S16LE)
.setChannels(1)
.setHandler(handler)
.build();
client.recognize(bi);
}
}
}