CSR SDK APIとは?

CLOVA Speech Recognition API(以下、CSR API)はユーザーの音声がストリーミングの形で入力されると、音声を認識した結果をテキストで返します。CSR APIはユーザーの音声入力を受信するために自主開発したストリーミングプロトコルを使用しています。そのため、HTTPベースのREST APIの形ではなく、AndroidとiOS SDKの形でCSR APIを提供しています。

事前準備

  1. コンソールのAI·Application Service > AI·NAVER API > Applicationでアプリケーションを登録します。(詳しい方法は"Application使用ガイド"をご参考)

  2. AI·Application Service > AI·NAVER API > Applicationで登録したアプリケーションを選択してClient IDとClient Secretの値を確認します。

  3. 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の使用のためには次の手順に従います。

  1. 次の文をapp/build.gradleファイルに追加します。
    repositories {
        jcenter()
    }
    dependencies {
        compile 'com.naver.speech.clientapi:naverspeech-ncp-sdk-android:1.1.6'
    }
  2. 次のようにAndroid Manifestファイル(AndroidManifest.xml)を設定します。
    • パッケージ名 : manifest要素のpackageプロパティ値が事前準備で登録したAndroidアプリパッケージ名と同じでなければなりません。
    • 権限設定 : ユーザーの音声入力はマイクを使って録音し、録音されたデータをサーバに転送しなければなりません。そのため、android.permission.INTERNETandroid.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" />
  3. (選択) 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の使用のためには次の手順に従います。

  1. 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
  2. iOS例題でframework/NaverSpeech.frameworkディレクトリを開発するアプリのEmbedded Binariesに追加します。
  3. 次のようにiOS Bundle Identifierを設定します。
    • Bundle Identifier : 事前準備で登録したiOS Bundle IDと同じでなければなりません。
    • 権限設定 : ユーザーの音声入力はマイクを使って録音し、録音されたデータをサーバに転送しなければなりません。そのため、keyの値を次のように設定します。
      <key>NSMicrophoneUsageDescription</key>
      <string></string>

Note!

  • iOS APIを提供するためにUniversal binary(Fat binary)の形のフレームワークを提供しています。そのため、Build SettingEnable 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) SpeechRecognitionListeneronReady, 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を使用した例題コードです。

// 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を使用した例題コードです。

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)
    }
}

に対する検索結果は~件です。 ""

    に対する検索結果がありません。 ""

    処理中...