CSR SDK APIとは?
CLOVA Speech Recognition API(以下、CSR API)はユーザーの音声がストリーミングの形で入力されると、音声を認識した結果をテキストで返します。CSR APIはユーザーの音声入力を受信するために自主開発したストリーミングプロトコルを使用しています。そのため、HTTPベースのREST APIの形ではなく、AndroidとiOS SDKの形でCSR APIを提供しています。
事前準備
コンソールのAI·Application Service > AI·NAVER API > Applicationでアプリケーションを登録します。(詳しい方法は"Application使用ガイド"をご参考)
AI·Application Service > AI·NAVER API > Applicationで登録したアプリケーションを選択してClient IDとClient Secretの値を確認します。
AI·Application Service > AI·NAVER API > Applicationの変更画面でCLOVA Speech Recognitionが選択されているのかを確認します。選択されていないと429 (Quota Exceed)が発生するので、ご注意ください。
APIの使用
CSR APIはAndroid向けとiOS向けのSDKを経由して提供されています。ここでは各プラットフォーム別のCSR APIの使用方法について説明します。
Android APIの使用
Android APIの使用のためには次の手順に従います。
- 次の文を
app/build.gradle
ファイルに追加します。repositories { jcenter() } dependencies { compile 'com.naver.speech.clientapi:naverspeech-ncp-sdk-android:1.1.6' }
- 次のようにAndroid Manifestファイル(AndroidManifest.xml)を設定します。
- パッケージ名 :
manifest
要素のpackage
プロパティ値が事前準備で登録したAndroidアプリパッケージ名と同じでなければなりません。 - 権限設定 : ユーザーの音声入力はマイクを使って録音し、録音されたデータをサーバに転送しなければなりません。そのため、
android.permission.INTERNET
とandroid.permission.RECORD_AUDIO
に対する権限が必ず必要です。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.naver.naverspeech.client" android:versionCode="1" android:versionName="1.0" > <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- パッケージ名 :
- (選択) proguard-rules.proファイルに次を追加します。次のコードはアプリをより軽くて安全に作ってくれます。
-keep class com.naver.speech.clientapi.SpeechRecognizer { protected private *; }
Note!
NAVER Open APIはAndroid SDKバージョン10以上をサポートします。従って、build.gradleファイルのminSdkVersion
の値をそれに合わせて設定しなければなりません。
クライアントは"準備", "録音", "中間結果出力", "エンドポイント抽出", "最終結果出力"のような一連のイベントの流れを実行します。アプリケーションデベロッパーはSpeechRecognitioinListener
インターフェースを継承し、該当のイベントが発生した時処理する動作を具現します。
Note!
APIに関する詳しい説明はhttps://github.com/NaverCloudPlatform/naverspeech-sdk-androidをご参考ください。
iOS APIの使用
iOS APIの使用のためには次の手順に従います。
- iOS向けの例題をcloneするか、Zipファイルでダウンロードして圧縮を解除します。
git clone https://github.com/NaverCloudPlatform/naverspeech-sdk-ios.git または wget https://github.com/NaverCloudPlatform/naverspeech-sdk-ios/archive/ncp.zip unzip ncp.zip
- iOS例題で
framework/NaverSpeech.framework
ディレクトリを開発するアプリのEmbedded Binariesに追加します。 - 次のようにiOS Bundle Identifierを設定します。
- Bundle Identifier : 事前準備で登録したiOS Bundle IDと同じでなければなりません。
- 権限設定 : ユーザーの音声入力はマイクを使って録音し、録音されたデータをサーバに転送しなければなりません。そのため、
key
の値を次のように設定します。<key>NSMicrophoneUsageDescription</key> <string></string>
Note!
- iOS APIを提供するためにUniversal binary(Fat binary)の形のフレームワークを提供しています。そのため、Build SettingでEnable Bitcodeオプションを使用できないので、Noに設定しなければなりません。
- NAVER Open APIはiOSバージョン8以上をサポートします。従って、Deployment Targetの値をそれに合わせて設定しなければなりません。
クライアントは"準備", "中間結果出力", "エンドポイント抽出", "最終結果出力"のような一連のイベントの流れを実行します。アプリケーション開発者は該当のイベントが発生した時、ご希望の動作を実行するようにNSKRecognizerDelegate
protocolを具現します。
Note!
APIに関する詳しい説明はhttp://naver.github.io/naverspeech-sdk-ios/Classes/NSKRecognizer.htmlをご参考ください。
UX考慮事項
一般的にユーザーは音声認識ボタンを押したらすぐ発話を始めようとします。しかし、音声認識を始めるrecognize()
メソッドを呼び出すと、音声認識のためのメモリの割り当て、マイクリソースの割り当て、音声認識サーバへのアクセスおよび認証などの準備プロセスを実行しなければならないため、ユーザーの発話の一部が抜ける可能性があります。そのため、アプリはすべての準備が完了した後、ユーザーに発話してもいいという情報を伝える必要があります。その方法については、次のように処理できます。
- すべての準備が完了すると
onReady
callbackメソッドが呼び出されます。 onReady
callbackメソッドが呼び出される前まで"準備中です。"のようなメッセージを表示するか、準備中であることを表すUI表示をしなければなりません。onReady
callbackメソッドが呼び出されたら"話してください。"のようなメッセージを表示するか、使用可能であることを表すUI表示をしなければなりません。
Note!
- (Android API)
SpeechRecognitionListener
のonReady
,onRecord
などのcallbackメソッドはWorker Threadから呼び出されるメソッドであり、Handlerに登録して使用しなければなりません。 - (iOS API)
cancel()
メソッドを呼び出すと、呼び出した時点からdelegationメソッドが呼び出されません。従って、音声認識が終わった際に処理すべき作業はcancel()
メソッドを呼び出した後、別途行わなければなりません。
エラーの処理
CSR APIを使用する際、様々な原因によりエラーが発生する可能性があります。その時、エラーcallback関数を使ったエラーコードが送られます。エラーコードを分析すれば、原因を分析したりエラーを処理することができます。CSR APIのエラーcallback関数が送るエラーは次の通りです。
エラー名 | エラーコード | 説明 |
---|---|---|
ERROR_NETWORK_INITIALIZE |
10 | ネットワークリソース初期化エラー |
ERROR_NETWORK_FINALIZE |
11 | ネットワークリソース解除エラー |
ERROR_NETWORK_READ |
12 | ネットワークデータ受信エラー。クライアント機器のネットワーク環境が遅くてTimeoutが発生した場合に主に発生します。 |
ERROR_NETWORK_WRITE |
13 | ネットワークデータ転送エラー。クライアント機器のネットワーク環境が遅くてTimeoutが発生した場合に主に発生します。 |
ERROR_NETWORK_NACK |
14 | 音声認識サーバエラー。遅いネットワーク環境によりクライアントがサーバに音声パケットを時間内に送れないと、サーバはTimeoutを発生させます。その時発生するエラーです。 |
ERROR_INVALID_PACKET |
15 | 有効でないパケット転送によるエラー |
ERROR_AUDIO_INITIALIZE |
20 | オーディオリソース初期化エラー。オーディオ使用権限があるのか確認します。 |
ERROR_AUDIO_FINALIZE |
21 | オーディオリソース解除エラー |
ERROR_AUDIO_RECORD |
22 | 音声入力(録音)エラー。オーディオ使用権限があるのか確認します。 |
ERROR_SECURITY |
30 | 認証権限エラー |
ERROR_INVALID_RESULT |
40 | 認識結果エラー |
ERROR_TIMEOUT |
41 | 一定時間以上サーバに音声を転送できなかったり、認識結果を受信できない。 |
ERROR_NO_CLIENT_RUNNING |
42 | クライアントが音声認識を実行していない状況で、特定の音声認識関連のイベントが検知される。 |
ERROR_UNKNOWN_EVENT |
50 | クライアント内部に規定されていないイベントが検知される。 |
ERROR_VERSION |
60 | プロトコルバージョンエラー |
ERROR_CLIENTINFO |
61 | クライアント情報エラー |
ERROR_SERVER_POOL |
62 | 音声認識可用サーバ不足 |
ERROR_SESSION_EXPIRED |
63 | 音声認識サーバのセッション満了 |
ERROR_SPEECH_SIZE_EXCEEDED |
64 | 音声パケットのサイズ超過 |
ERROR_EXCEED_TIME_LIMIT |
65 | 認証用のタイプスタンプ(time stamp)の不良 |
ERROR_WRONG_SERVICE_TYPE |
66 | 正しいサービスタイプ(service type)ではない。 |
ERROR_WRONG_LANGUAGE_TYPE |
67 | 正しい言語タイプ(language type)ではない。 |
ERROR_OPENAPI_AUTH |
70 | Open API認証エラー。Client IDと登録されたpackage名(Android)またはBundle IDの情報(iOS)が間違っていた時発生します。 |
ERROR_QUOTA_OVERFLOW |
71 | 定められたAPI呼び出しの制限量(quota)を全部使う。 |
上記のエラーコード以外にも次のようなエラーが発生したり、問い合わせが入る可能性があります。
現象または問い合わせ | 原因または解決方法 |
---|---|
UnsatifiedLinkError 発生 | CSR APIはarmeabiとarmeabi-v7aでビルドされたライブラリを提供します。開発するアプリで使用するライブラリの中でarmeabiとarmeabi-v7aをサポートしないものがある場合、このようなエラーが発生する可能性があります。 |
android fatal signal 11 (sigsegv)エラー発生 | CSR APIを使って音声の入力を受ける前にまずリソースを準備しなければなりません。recognize() メソッドを呼び出す前にinitialize() メソッドがきちんと呼び出されるのかを確認しなければなりません。また、release() メソッドも呼び出せる必要があります。 |
認識結果として""(null)が返されます。 | ユーザーが非常に小さい声で発話したり、周辺の音によって声が認識されていない場合に発生する可能性があります。まれに発生しますが、認識結果がnullの場合も例外処理することをお勧めします。 |
オーディオファイルの認識 | CSR APIはオーディオファイルの認識をサポートしません。 |
低仕様のスマートフォンできちんと動作しません。 | CSR APIはAndroid SDKバージョン10以上とiOSバージョン8以上の機器をサポートしています。 |
具現の例題
次はプラットフォーム別のCSR API具現の例題です。
Android具現の例題
次はAndroidでCSR APIを使用した例題コードです。
- CSR API - Android用の例題
- 例題コードのストレージ : https://github.com/NaverCloudPlatform/naverspeech-sdk-android
- 説明
- Main Activity クラス :
SpeechRecognitionListener
を初期化し、以降イベントをhandleMessageで受信して処理します。 - SpeechRecognitionListenerを継承したクラス : 音声認識サーバとの接続、音声送信、認識結果発生などのイベントによる結果処理方法を定義します。
- Main Activity クラス :
// 1. Main Activityクラス
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String CLIENT_ID = "YOUR CLIENT ID"; // "マイアプリケーション"でClient IDを確認し、ここに入力してください。
private RecognitionHandler handler;
private NaverRecognizer naverRecognizer;
private TextView txtResult;
private Button btnStart;
private String mResult;
private AudioWriterPCM writer;
// Handle speech recognition Messages.
private void handleMessage(Message msg) {
switch (msg.what) {
case R.id.clientReady: // 音声認識準備可能
txtResult.setText("Connected");
writer = new AudioWriterPCM(Environment.getExternalStorageDirectory().getAbsolutePath() + "/NaverSpeechTest");
writer.open("Test");
break;
case R.id.audioRecording:
writer.write((short[]) msg.obj);
break;
case R.id.partialResult:
mResult = (String) (msg.obj);
txtResult.setText(mResult);
break;
case R.id.finalResult: // 最終認識結果
SpeechRecognitionResult speechRecognitionResult = (SpeechRecognitionResult) msg.obj;
List<String> results = speechRecognitionResult.getResults();
StringBuilder strBuf = new StringBuilder();
for(String result : results) {
strBuf.append(result);
strBuf.append("\n");
}
mResult = strBuf.toString();
txtResult.setText(mResult);
break;
case R.id.recognitionError:
if (writer != null) {
writer.close();
}
mResult = "Error code : " + msg.obj.toString();
txtResult.setText(mResult);
btnStart.setText(R.string.str_start);
btnStart.setEnabled(true);
break;
case R.id.clientInactive:
if (writer != null) {
writer.close();
}
btnStart.setText(R.string.str_start);
btnStart.setEnabled(true);
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtResult = (TextView) findViewById(R.id.txt_result);
btnStart = (Button) findViewById(R.id.btn_start);
handler = new RecognitionHandler(this);
naverRecognizer = new NaverRecognizer(this, handler, CLIENT_ID);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(!naverRecognizer.getSpeechRecognizer().isRunning()) {
mResult = "";
txtResult.setText("Connecting...");
btnStart.setText(R.string.str_stop);
naverRecognizer.recognize();
} else {
Log.d(TAG, "stop and wait Final Result");
btnStart.setEnabled(false);
naverRecognizer.getSpeechRecognizer().stop();
}
}
});
}
@Override
protected void onStart() {
super.onStart(); // 音声認識サーバの初期化はここで
naverRecognizer.getSpeechRecognizer().initialize();
}
@Override
protected void onResume() {
super.onResume();
mResult = "";
txtResult.setText("");
btnStart.setText(R.string.str_start);
btnStart.setEnabled(true);
}
@Override
protected void onStop() {
super.onStop(); // 音声認識サーバ終了
naverRecognizer.getSpeechRecognizer().release();
}
// Declare handler for handling SpeechRecognizer thread's Messages.
static class RecognitionHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
RecognitionHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
}
// 2. SpeechRecognitionListenerを継承したクラス
class NaverRecognizer implements SpeechRecognitionListener {
private final static String TAG = NaverRecognizer.class.getSimpleName();
private Handler mHandler;
private SpeechRecognizer mRecognizer;
public NaverRecognizer(Context context, Handler handler, String clientId) {
this.mHandler = handler;
try {
mRecognizer = new SpeechRecognizer(context, clientId);
} catch (SpeechRecognitionException e) {
e.printStackTrace();
}
mRecognizer.setSpeechRecognitionListener(this);
}
public SpeechRecognizer getSpeechRecognizer() {
return mRecognizer;
}
public void recognize() {
try {
mRecognizer.recognize(new SpeechConfig(LanguageType.KOREAN, EndPointDetectType.AUTO));
} catch (SpeechRecognitionException e) {
e.printStackTrace();
}
}
@Override
@WorkerThread
public void onInactive() {
Message msg = Message.obtain(mHandler, R.id.clientInactive);
msg.sendToTarget();
}
@Override
@WorkerThread
public void onReady() {
Message msg = Message.obtain(mHandler, R.id.clientReady);
msg.sendToTarget();
}
@Override
@WorkerThread
public void onRecord(short[] speech) {
Message msg = Message.obtain(mHandler, R.id.audioRecording, speech);
msg.sendToTarget();
}
@Override
@WorkerThread
public void onPartialResult(String result) {
Message msg = Message.obtain(mHandler, R.id.partialResult, result);
msg.sendToTarget();
}
@Override
@WorkerThread
public void onEndPointDetected() {
Log.d(TAG, "Event occurred : EndPointDetected");
}
@Override
@WorkerThread
public void onResult(SpeechRecognitionResult result) {
Message msg = Message.obtain(mHandler, R.id.finalResult, result);
msg.sendToTarget();
}
@Override
@WorkerThread
public void onError(int errorCode) {
Message msg = Message.obtain(mHandler, R.id.recognitionError, errorCode);
msg.sendToTarget();
}
@Override
@WorkerThread
public void onEndPointDetectTypeSelected(EndPointDetectType epdType) {
Message msg = Message.obtain(mHandler, R.id.endPointDetectTypeSelected, epdType);
msg.sendToTarget();
}
}
iOS具現の例題
次はiOSでCSR APIを使用した例題コードです。
- CSR API - iOS用の例題
- 例題コードのストレージ : https://github.com/NaverCloudPlatform/naverspeech-sdk-ios
import UIKit
import NaverSpeech
import Common
let ClientID = "YOUR_CLIENT_ID"
class AutoViewController: UIViewController {
required init?(coder aDecoder: NSCoder) { // NSKRecognizer初期化に必要なNSKRecognizerConfigurationを作成
let configuration = NSKRecognizerConfiguration(clientID: ClientID)
configuration?.canQuestionDetected = true
self.speechRecognizer = NSKRecognizer(configuration: configuration)
super.init(coder: aDecoder)
self.speechRecognizer.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
self.setupLanguagePicker()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
if self.isViewLoaded && self.view.window == nil {
self.view = nil
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let x = languagePickerButton.frame.minX
let y = languagePickerButton.frame.maxY
self.pickerView.frame = CGRect.init(x: x, y: y, width: languagePickerButton.bounds.size.width, height: self.pickerView.bounds.size.height)
}
@IBAction func languagePickerButtonTapped(_ sender: Any) {
self.pickerView.isHidden = false
}
@IBAction func recognitionButtonTapped(_ sender: Any) { // ボタンを押すと音声認識開始
if self.speechRecognizer.isRunning {
self.speechRecognizer.stop()
} else {
self.speechRecognizer.start(with: self.languages.selectedLanguage)
self.recognitionButton.isEnabled = false
self.statusLabel.text = "Connecting......"
}
}
@IBOutlet weak var languagePickerButton: UIButton!
@IBOutlet weak var recognitionResultLabel: UILabel!
@IBOutlet weak var recognitionButton: UIButton!
@IBOutlet weak var statusLabel: UILabel!
fileprivate let speechRecognizer: NSKRecognizer
fileprivate let languages = Languages()
fileprivate let pickerView = UIPickerView()
}
extension AutoViewController: NSKRecognizerDelegate { //NSKRecognizerDelegate protocol具現
public func recognizerDidEnterReady(_ aRecognizer: NSKRecognizer!) {
print("Event occurred: Ready")
self.statusLabel.text = "Connected"
self.recognitionResultLabel.text = "Recognizing......"
self.setRecognitionButtonTitle(withText: "Stop", color: .red)
self.recognitionButton.isEnabled = true
}
public func recognizerDidDetectEndPoint(_ aRecognizer: NSKRecognizer!) {
print("Event occurred: End point detected")
}
public func recognizerDidEnterInactive(_ aRecognizer: NSKRecognizer!) {
print("Event occurred: Inactive")
self.setRecognitionButtonTitle(withText: "Record", color: .blue)
self.recognitionButton.isEnabled = true
self.statusLabel.text = ""
}
public func recognizer(_ aRecognizer: NSKRecognizer!, didRecordSpeechData aSpeechData: Data!) {
print("Record speech data, data size: \(aSpeechData.count)")
}
public func recognizer(_ aRecognizer: NSKRecognizer!, didReceivePartialResult aResult: String!) {
print("Partial result: \(aResult)")
self.recognitionResultLabel.text = aResult
}
public func recognizer(_ aRecognizer: NSKRecognizer!, didReceiveError aError: Error!) {
print("Error: \(aError)")
self.setRecognitionButtonTitle(withText: "Record", color: .blue)
self.recognitionButton.isEnabled = true
}
public func recognizer(_ aRecognizer: NSKRecognizer!, didReceive aResult: NSKRecognizedResult!) {
print("Final result: \(aResult)")
if let result = aResult.results.first as? String {
self.recognitionResultLabel.text = "Result: " + result
}
}
}
extension AutoViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.languages.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return languages.languageString(at: row)
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
languages.selectLanguage(at: row)
languagePickerButton.setTitle(languages.selectedLanguageString, for: .normal)
self.pickerView.isHidden = true
if self.speechRecognizer.isRunning { //音声認識中、言語を変更すると音声認識を直ちに中止(cancel)
self.speechRecognizer.cancel()
self.recognitionResultLabel.text = "Canceled"
self.setRecognitionButtonTitle(withText: "Record", color: .blue)
self.recognitionButton.isEnabled = true
}
}
}
fileprivate extension AutoViewController {
func setupLanguagePicker() {
self.view.addSubview(self.pickerView)
self.pickerView.dataSource = self
self.pickerView.delegate = self
self.pickerView.showsSelectionIndicator = true
self.pickerView.backgroundColor = UIColor.white
self.pickerView.isHidden = true
}
func setRecognitionButtonTitle(withText text: String, color: UIColor) {
self.recognitionButton.setTitle(text, for: .normal)
self.recognitionButton.setTitleColor(color, for: .normal)
}
}