- 인쇄
- PDF
Mobile SDK
- 인쇄
- PDF
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.8' }
다음과 같이 Android Manifest 파일(AndroidManifest.xml)을 설정합니다.
- 패키지 이름 :
manifest
요소의package
속성 값이 사전 준비사항에서 등록한 안드로이드 앱 패키지 이름과 같아야 합니다. - 권한 설정 : 사용자의 음성 입력을 마이크를 통해 녹음해야 하고 녹음된 데이터를 서버로 전송해야 합니다. 따라서,
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 *; }
네이버 Open API는 Android SDK 버전 10 이상을 지원합니다. 따라서, build.gradle 파일의 minSdkVersion
값을 이에 맞게 설정해야 합니다.
클라이언트는 "준비", "녹음", "중간결과 출력", "끝점 추출", "최종결과 출력"과 같은 일련의 이벤트 흐름을 수행합니다. 애플리케이션 개발자는 SpeechRecognitioinListener
인터페이스를 상속받아 해당 이벤트가 발생할 때 처리할 동작을 구현하면 됩니다.
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>
- iOS API를 제공하기 위해 Universal binary(Fat binary) 형태의 프레임워크를 제공하고 있습니다. 따라서 Build Setting에서 Enable Bitcode 옵션을 사용할 수 없으므로 No로 설정해야 합니다.
- 네이버 Open API는 iOS 버전 8 이상을 지원합니다. 따라서, Deployment Target 값을 이에 맞게 설정해야 합니다.
클라이언트는 "준비", "중간결과 출력", "끝점 추출", "최종결과 출력"과 같은 일련의 이벤트 흐름을 수행합니다. 애플리케이션 개발자는 해당 이벤트가 발생할 때 원하는 동작을 수행하도록 NSKRecognizerDelegate
protocol을 구현하면 됩니다.
API에 대한 자세한 설명은 http://naver.github.io/naverspeech-sdk-ios/Classes/NSKRecognizer.html를 참고합니다.
UX 고려사항
일반적으로 사용자는 음성 인식 버튼을 누르자마자 발화를 시작하려고 할 것입니다. 하지만 음성 인식을 시작하는 recognize()
메서드를 호출하면 음성 인식을 위한 메모리 할당, 마이크 자원 할당, 음성 인식 서버 접속 및 인증 등의 준비 과정을 수행해야 하기 때문에 사용자의 발화 일부가 누락될 수 있습니다. 따라서, 앱은 모든 준비가 완료된 후 사용자에게 발화해도 좋다는 정보를 전달해야 합니다. 이 방법은 다음과 같이 처리할 수 있습니다.
- 모든 준비가 완료되면
onReady
callback 메서드가 호출됩니다. onReady
callback 메서드가 호출되기 전까지 "준비 중입니다."와 같은 메시지를 표시하거나 준비 중임을 나타내는 UI 표시를 해야 합니다.onReady
callback 메서드가 호출되면 "이야기해주세요."와 같은 메시지를 표시하거나 사용 가능함을 나타내는 UI를 표시해야 합니다.
- (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 {
// 금융기관을 위한 음성인식을 사용하기 위해서는 SpeechRecognizer가 아닌 SpeechFinRecognizer의 instance를 생성해야 합니다.
mRecognizer = new SpeechFinRecognizer(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
- 설명
- recognizer를 동작시키기 위한 각 기관별 configuration객체를 생성하여 사용합니다.
- Objective-C
// 민간기관 NSKRecognizerConfiguration *configuration = [NSKRecognizerConfiguration configurationWithClientID:kClientID]; // 금융기관 NSKRecognizerConfiguration *configuration = [NSKRecognizerConfiguration finConfigurationWithClientID:kClientID];
- Swift
// 민간기관 let configuration = NSKRecognizerConfiguration(clientID: ClientID) // 금융기관 let configuration = NSKRecognizerConfiguration.finConfiguration(withClientID: ClientID)
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)
}
}