발음평가 API 란?
최신 인공지능 기술에 기반하여 한국인을 비롯한 비원어민 영어 발성 및 외국인의 한국어 음성에 대해 높은 정확도의 음성인식 엔진과 높은 수준의 분석 알고리즘에 기반하여 문장별 발음 수준을 측정하여 제공합니다. 발음평가 API는 HTTP 기반의 REST API 인터페이스로 JSON 포맷 기반의 입력 및 출력을 지원하며 ETRI에서 제공하는 API Key 인증을 통해 사용할 수 있는 Open API 입니다.
최신 인공지능 기술에 기반하여 한국인을 비롯한 비원어민 영어 발성 및 외국인의 한국어 음성에 대해 높은 정확도의 음성인식 엔진과 높은 수준의 분석 알고리즘에 기반하여 문장별 발음 수준을 측정하여 제공합니다. 발음평가 API는 HTTP 기반의 REST API 인터페이스로 JSON 포맷 기반의 입력 및 출력을 지원하며 ETRI에서 제공하는 API Key 인증을 통해 사용할 수 있는 Open API 입니다.
기술명 | API명 | 1일 허용량 |
---|---|---|
발음평가 기술 | 영어 발음평가 API | 1,000건/일 (60초 이내/건당) |
한국어 발음평가 API | 1,000건/일 (60초 이내/건당) |
발음평가 API는 REST API이며, 발음평가에 사용하기 위해 샘플링 주파수(sampling rate 또는 sampling frequency) 16kHz로 녹음된 음성 파일을 Base64로 Encoding 하여 HTTP 통신으로 ETRI Open API 서버에 전달하면 됩니다. 서버가 제공하는 REST API의 URI는 다음과 같으며 POST 방식으로 호출해야 합니다.
http://aiopen.etri.re.kr:8000/WiseASR/Pronunciation
HTTP 요청으로 발음평가를 요청할 때 사전 준비 사항에서 발급받은 Access key 정보를 요청 본문에 포함시켜야 합니다. 다음은 HTTP 요청 메시지 예입니다.
[HTTP Request Header]
"Authorization" : "YOUR_ACCESS_KEY"
[HTTP Request Body]
{
"request_id": "reserved field",
"argument": {
"language_code": "LANGUAGE_CODE",
"script": "PRONUNCIATION_SCRIPT",
"audio": "BASE64_OF_AUDIO_DATA",
}
}
위와 같은 HTTP 요청을 ETRI Open API 서버로 전달하면 서버는 JSON 형태의 Text 데이터를 HTTP 응답 메시지로 반환합니다. 다음은 HTTP 응답 예제 입니다.
[HTTP Response Header]
Access-Control-Allow-Origin:*
Connection:close
Content-Length:0
Content-Type:application/json; charset=UTF-8
[HTTP Response Body]
{
"request_id": "reserved field",
"result": 0,
"return_object": {발음평가 결과 JSON}
}
안드로이드 앱 제작에 참고가 가능한 소스코드 환경을 android studio 개발 환경으로 gitHub에 오픈하였으니 오픈 API를 사용한 app개발 시에 참고하시면 됩니다.
(Git에 올라와 있는 소스 코드는 리뉴얼 되기 전 버전입니다. 참고하시어 관련 소스 수정 하시기를 바랍니다.)
https://github.com/ETRISLP/EtriOpenASR
JSON parsing을 위해 Gson 라이브러리를 사용하여 제공하고 있습니다. Gson 라이브러리에 대한 자세한 설명은 https://github.com/google/gson 에서 확인 하실 수 있습니다.
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
public class Example {
static public void main ( String[] args ) {
String openApiURL = "http://aiopen.etri.re.kr:8000/WiseASR/Pronunciation"; // 영어
//String openApiURL = "http://aiopen.etri.re.kr:8000/WiseASR/PronunciationKor"; //한국어
String accessKey = "YOUR_ACCESS_KEY"; // 발급받은 API Key
String languageCode = "LANGUAGE_CODE"; // 언어 코드
String script = "PRONUNCIATION_SCRIPT"; // 평가 대본
String audioFilePath = "AUDIO_FILE_PATH"; // 녹음된 음성 파일 경로
String audioContents = null;
Gson gson = new Gson();
Map<String, Object> request = new HashMap<>();
Map<String, String> argument = new HashMap<>();
try {
Path path = Paths.get(audioFilePath);
byte[] audioBytes = Files.readAllBytes(path);
audioContents = Base64.getEncoder().encodeToString(audioBytes);
} catch (IOException e) {
e.printStackTrace();
}
argument.put("language_code", languageCode);
argument.put("script", script);
argument.put("audio", audioContents);
request.put("argument", argument);
URL url;
Integer responseCode = null;
String responBody = null;
try {
url = new URL(openApiURL);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
con.setRequestProperty("Authorization", accessKey);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
wr.write(gson.toJson(request).getBytes("UTF-8"));
wr.flush();
wr.close();
responseCode = con.getResponseCode();
InputStream is = con.getInputStream();
byte[] buffer = new byte[is.available()];
int byteRead = is.read(buffer);
responBody = new String(buffer);
System.out.println("[responseCode] " + responseCode);
System.out.println("[responBody]");
System.out.println(responBody);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
<?php
$openApiURL = "http://aiopen.etri.re.kr:8000/WiseASR/Pronunciation"; // 영어
//$openApiURL = "http://aiopen.etri.re.kr:8000/WiseASR/PronunciationKor"; //한국어
$accessKey = "YOUR_ACCESS_KEY";
$languageCode = "LANGUAGE_CODE";
$script = "PRONUNCIATION_SCRIPT";
$audioFilePath = "AUDIO_FILE_PATH";
$audioContents = base64_encode( file_get_contents($audioFilePath ) );
$request = array(
"argument" => array (
"language_code" => $languageCode,
"script" => $script,
"audio" => $audioContents
)
);
try {
$server_output = "";
$ch = curl_init();
$header = array(
"Content-Type:application/json; charset=UTF-8",
"Authorization":{$accessKey}
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_URL, $openApiURL);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode ( $request) );
$server_output = curl_exec ($ch);
if($server_output === false) {
echo "Error Number:".curl_errno($ch)."\n";
echo "Error String:".curl_error($ch)."\n";
}
curl_close ($ch);
} catch ( Exception $e ) {
echo $e->getMessage ();
}
echo "result = " . var_dump($server_output);
?>
JSON parsing을 위해 jsoncpp 라이브러리를 사용하여 제공하고 있습니다. jsoncpp 라이브러리에 대한 자세한 설명은 https://github.com/open-source-parsers/jsoncpp 에서 확인 하실 수 있습니다.
HTTP 통신을 위해 curl 라이브러리를 사용하여 제공하고 있습니다. curl 라이브러리에 대한 자세한 설명은 https://curl.haxx.se/libcurl 에서 확인 하실 수 있습니다.
컴파일을 위해서는 아래와 같이 추가된 LIB에 대한 옵션을 추가해야 합니다.
g++ (c++파일명) (JSONCPP)/json/json.h (JSONCPP)/json/json-forwards.h (JSONCPP)/jsoncpp.cpp -I(CURL)/include -lcurl
#include <curl/curl.h>
#include <json/json.h>
#include <iostream>
#include <string>
#include <memory.h>
#include <fstream>
#include <math.h>
#define LENFRAME 800
using namespace std;
size_t writeDataFunc(void *ptr, size_t size, size_t nmemb, string stream);
string base64_encode(unsigned char const* , unsigned int len);
static const string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
int main() {
char* openApiURL = (char*)"http://aiopen.etri.re.kr:8000/WiseASR/Pronunciation"; // 영어
//char* openApiURL = (char*)"http://aiopen.etri.re.kr:8000/WiseASR/PronunciationKor"; //한국어
char* audioFilePath = (char*)"AUDIO_FILE_PATH";
string accessKey = "YOUR_ACCESS_KEY";
string script = "PRONUNCIATION_SCRIPT";
string languageCode = "LANGUAGE_CODE";
Json::Value request;
Json::Value argument;
FILE* fp = fopen(audioFilePath, "rt" );
unsigned char sbuf[LENFRAME];
unsigned int nRead = 0;
unsigned int nWrite = 0;
unsigned char* audioBytes;
unsigned char* newAudioBytes;
while ( 0 < ( nRead = fread( sbuf, sizeof( char ), LENFRAME, fp ) ) ) {
newAudioBytes = new unsigned char[nWrite + nRead];
if ( 0 < nWrite ) {
memcpy(newAudioBytes, audioBytes, nWrite * sizeof(char));
}
memcpy(newAudioBytes + nWrite, sbuf, nRead * sizeof(char));
nWrite = nWrite + nRead;
audioBytes = new unsigned char[nWrite];
memcpy(audioBytes, newAudioBytes, nWrite * sizeof(char));
}
fclose(fp);
argument["language_code"] = languageCode;
argument["script"] = script;
argument["audio"] = base64_encode( audioBytes , nWrite);
request["argument"] = argument;
CURL *curl;
curl_slist* responseHeaders = NULL;
curl = curl_easy_init();
if( curl == NULL ) {
} else {
responseHeaders = curl_slist_append( responseHeaders , "Content-Type: application/json; charset=UTF-8" ) ;
responseHeaders = curl_slist_append(responseHeaders , ("Authorization: " + accessKey).c_str() ) ;
string requestJson = request.toStyledString();
long statusCode;
string response;
curl_easy_setopt(curl, CURLOPT_URL, openApiURL);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, responseHeaders ) ;
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestJson.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeDataFunc);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
curl_easy_cleanup(curl);
cout << "[responseCode] " << statusCode << endl;
cout << "[responBody]" << endl;
cout << response << endl;
}
return 0;
}
size_t writeDataFunc(void *ptr, size_t size, size_t nmemb, string stream) {
size_t realsize = size * nmemb;
string temp(static_cast<const char*>(ptr), realsize);
stream.append(temp);
return realsize;
}
string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i) {
for(j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while((i++ < 3))
ret += '=';
}
return ret;
}
python 3.0을 기준으로 작성되었습니다.
HTTP 통신을 위해 urllib3 라이브러리를 사용하여 제공하고 있습니다. Python 3.0 이하의 버전에서 예제를 실행하기 위해서는 별도로 urllib3의 설치가 필요합니다. 설치에 대한 설명은 https://pypi.python.org/pypi/urllib3 를 참고하시기 바랍니다. urllib3 라이브러리에 대한 자세한 설명은 https://urllib3.readthedocs.io/en/latest/ 에서 확인 하실 수 있습니다.
#-*- coding:utf-8 -*-
import urllib3
import json
import base64
openApiURL = "http://aiopen.etri.re.kr:8000/WiseASR/Pronunciation" # 영어
# openApiURL = "http://aiopen.etri.re.kr:8000/WiseASR/PronunciationKor" # 한국어
accessKey = "YOUR_ACCESS_KEY"
audioFilePath = "AUDIO_FILE_PATH"
languageCode = "LANGUAGE_CODE"
script = "PRONUNCIATION_SCRIPT"
file = open(audioFilePath, "rb")
audioContents = base64.b64encode(file.read()).decode("utf8")
file.close()
requestJson = {
"argument": {
"language_code": languageCode,
"script": script,
"audio": audioContents
}
}
http = urllib3.PoolManager()
response = http.request(
"POST",
openApiURL,
headers={"Content-Type": "application/json; charset=UTF-8","Authorization": accessKey},
body=json.dumps(requestJson)
)
print("[responseCode] " + str(response.status))
print("[responBody]")
print(str(response.data,"utf-8"))
var fs = require('fs');
var openApiURL = 'http://aiopen.etri.re.kr:8000/WiseASR/Pronunciation'; //영어
//var openApiURL = 'http://aiopen.etri.re.kr:8000/WiseASR/PronunciationKor'; //한국어
var accessKey = 'YOUR_ACCESS_KEY';
var languageCode = 'LANGUAGE_CODE';
var script = 'PRONUNCIATION_SCRIPT';
var audioFilePath = 'AUDIO_FILE_PATH';
var audioData;
var audioData = fs.readFileSync(audioFilePath);
var requestJson = {
'argument': {
'language_code': languageCode,
'script': script,
'audio': audioData.toString('base64')
}
};
var request = require('request');
var options = {
url: openApiURL,
body: JSON.stringify(requestJson),
headers: {'Content-Type':'application/json,'Authorization':access_key}
};
request.post(options, function (error, response, body) {
console.log('responseCode = ' + response.statusCode);
console.log('responseBody = ' + body);
});
발음평가 API에 필요한 요청 본문에 다음과 같은 파라미터를 작성해야 합니다.
[HTTP Request Header]
"Authorization" : "YOUR_ACCESS_KEY"
[HTTP Request Body]
{
"argument": {
"language_code": "LANGUAGE_CODE",
"script": "PRONUNCIATION_SCRIPT",
"audio": "BASE64_OF_AUDIO_DATA"
}
}
다음은 파라미터에 대한 설명입니다.
Field 명 | 타입 | 필수 여부 | 설명 |
---|---|---|---|
access_key | String | O | API 사용을 위해 ETRI에서 발급한 사용자 API Key 입니다. |
argument | Object | O | API 사용 요청 시 분석을 위해 전달할 내용입니다. |
language_code | String | O | 발음평가의
입력 음성 언어 코드입니다. 요청할 수 있는 언어 코드는 아래와 같습니다.
|
script | String | 녹음된 음성파일의 제시 문장입니다. API 요청 시 script가 포함되지 않는 경우 비원어민 영어 음성인식을 수행한 이후 인식 결과에 대한 발음평가 점수를 제공합니다. | |
audio | String | O | 발음평가를 할 녹음된 음성파일의 base64-encoded string 입니다. |
발음평가 API는 요청된 어휘의 동음이의어 정보 결과를 JSON 형태의 Text 데이터로 반환합니다.
다음은 정상적인 요청 처리에 대한 HTTP 응답 예입니다.
[HTTP Response Header]
Access-Control-Allow-Origin:*
Connection:close
Content-Length:0
Content-Type:application/json; charset=UTF-8
[HTTP Response Body]
{
"request_id": "reserved field",
"result": 0,
"return_object": {발음평가 결과 JSON}
}
다음은 오류가 발생한 요청 처리에 대한 HTTP 응답 예입니다.
[HTTP Response Header]
Access-Control-Allow-Origin:*
Connection:close
Content-Length:0
Content-Type:application/json; charset=UTF-8
[HTTP Response Body]
{
"request_id": "reserved field",
"result": -1,
"reason": {오류 메시지}
}
분석된 결과는 다음과 같은 내용이 포함되어 있습니다.
JSON Key 이름 | 설명 |
---|---|
recognized | 음성 언어 코드에 따른 발음평가 결과 |
score | 발음 평가 점수로 1~5점까지 제공 |
발음평가 API의 오류 코드 목록은 다음과 같습니다.
http status code | result | reason | 설명 |
---|---|---|---|
403 | -1 | Empty Auth Header | Authorization 헤더가 없는 경우 |
403 | -1 | Invalid Key | KEY
API 키가 없는 경우 |
403 |
-1 | Blocked KEY | API
키가 관리자에 의해서 차단된 경우 |
403 |
-1 | Daily Limit Exceeded | 일간 호출 제한에 걸린 경우 |
403 |
-1 | Monthly
Limit Exceeded |
월간 호출 제한에 걸린 경우 |
403 |
-1 | Yearly
Limit Exceeded |
연간 호출 제한에 걸린 경우 |
403 |
-1 | Too Many Keys | 같은 IP에서 여러 API 키가 사용된 경우 |
403 |
-1 | Too Many IPs | 하나의 API 키를 여러 IP 에서 사용한 경우 |
403 |
-1 | Not Allowed IP | API
호출 가능한 IP 가 아닌경우 (API 설정에서 허용된 IP가 아닌경우) |
403 |
-1 | Not Allowed Subpath | 하위경로 접근 제한이 되어 있는 경우 |
403 | -1 | Invalid
API |
등록되지
않은 API를 요청한 경우 |
408 | -1 |
Request
Timeout |
서버의
요청 대기가 시간을 초과한 경우 |
413 |
-1 | Body
Size Limit Exceeded |
요청
바디가 설정된 값보다 큰 경우 |
429 |
-1 | Concurrent
Limit Exceeded |
연속호출
허용 범위를 넘어서 호출한 경우 |
500 | -1 | Internal Server Error | 내부 오류 발생한 경우 |