Restful API
REST api được dùng để nhận dạng 1 file hoàn chỉnh & nhận về kết quả nhận dạng cuối cùng.
Hiện tại API hỗ trợ 3 định dạng file audio: wav, mp3, PCM (phải gửi kèm thông tin audio trong request), sử dụng giao thức http POST với các thông tin như sau:
- API URL: https://viettelgroup.ai/voice/api/asr/v1/rest/decode_file
- Các tùy chọn cho request được chỉ định trong HEADER như sau:
'token': (string) token dùng để xác thực người dùng. Bỏ trống nếu bạn muốn request nặc danh.
- Các tùy chọn cho request được chỉ định trong url parameter như sau:
'model': (string) mã mô hình nhận dạng.
Với định dạng PCM, client phải cung cấp đủ các thông tin về audio trong header như sau:'sample_rate': (float) sample rate (tần số lấy mẫu) của file audio. 'format': (string) format của audio ví dụ S16LE là *signed 16 bit little endian* (tham khảo PCM format: https://trac.ffmpeg.org/wiki/audio%20types) 'num_of_channels': (integer) số channel của audio.
- Request: multipart upload file với thông tin:
'name'='file', 'filename'=$FILE_PATH
CURL
curl --request POST --cacert $CERT_FILE -H "token: $YOUR_TOKEN" -F "file=@$PATH_TO_FILE" https://viettelgroup.ai/voice/api/asr/v1/rest/decode_file
Trong đó:
- CERT_FILE: đường dẫn đến file chứng chỉ của https://viettelgroup.ai (Nếu CURL của bạn đã cập nhật chứng chỉ này thì có thể bỏ qua).
- PATH_TO_FILE: đường dẫn đến file cần nhận dạng.
Nếu muốn nhận dạng file PCM, bạn phải cung cấp thêm các thông tin cần thiết vào Header như sau:
`-H "sample_rate:$SAMPLE_RATE" -H "format:$FORMAT" -H "num_of_channels:$CHANNELS"`
Trong đó:
- SAMPLE_RATE: sample rate (tần số lấy mẫu) của file audio.
- FORMAT: format của audio ví dụ S16LE là *signed 16 bit little endian* (tham khảo https://trac.ffmpeg.org/wiki/audio%20types)
- CHANNELS: số channel của audio
PYTHON
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Import the json library
import requests
url = "https://viettelgroup.ai/voice/api/asr/v1/rest/decode_file"
headers = {
'token': 'anonymous',
#'sample_rate': 16000,
#'format':'S16LE',
#'num_of_channels':1,
#'asr_model': 'model code'
}
s = requests.Session()
files = {'file': open($AUDIO_PATH,'rb')}
response = requests.post(url,files=files, headers=headers, verify=$CERT_PATH)
print(response.text)
Trong đó:
- AUDIO_PATH: đường dẫn file audio muốn nhận dạng
- CERT_PATH: đường dẫn tới file chứng chỉ của viettelgroup.ai : cert file
WebSocket API
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.
PYTHON
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
JAVA
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
Sample code:
#!/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);
}
}
}