通过智能语音交互 iOS NUI SDK,可以在 iOS 应用中接入一句话识别,将音频转换为文本。SDK 提供初始化、识别控制、音频数据供给和结果回调接口。
前提条件
-
已创建项目并获取 Appkey,详情请参见管理项目。
-
已获取有效的 NLS Token。
Token 的获取方式,请参见获取Token。
一句话识别使用 Appkey 和 NLS Token 鉴权。生产环境应由服务端获取并下发 Token,不要将 AccessKey ID 和 AccessKey Secret 保存在应用代码或移动设备中。
下载与集成
SDK 和示例工程的下载入口,请参见移动端 SDK 选择与下载。
2.5.14 版本后的 iOS SDK 使用纯 Objective-C 接口,不再使用 Objective-C++ 混合接口。以下集成说明以 2.7.4 版本的包结构为例。
-
解压 SDK 包,将
Release/nuisdk.xcframework添加到 Xcode 工程。在 General > Frameworks, Libraries, and Embedded Content 中将其设置为 Embed & Sign,并确认 Build Phases > Link Binary With Libraries 中包含该库。不要同时链接 XCFramework 和独立的nuisdk.framework。 -
在源文件中导入头文件:
#import <nuisdk/NeoNui.h> -
使用麦克风采集音频时,在
Info.plist中配置NSMicrophoneUsageDescription,并在开始录音前申请、检查麦克风权限。 -
参考随包 Demo 中的
SpeechRecognizerViewController接入识别流程。示例工程提供AudioController录音模块及文件操作等工具类,可以按业务需要复用或替换。
XCFramework 分别包含真机和模拟器的库。2.7.4 包提供 ios-arm64 和 ios-arm64-simulator,应选择与目标平台一致的构建目标。该包的模拟器库支持 arm64 架构。
调用流程
-
创建 SDK 实例和录音实例,设置代理并初始化 SDK。
-
按业务需要设置识别参数。
-
调用
nui_dialog_start,使用MODE_P2T开始识别。 -
根据
onNuiAudioStateChanged回调控制录音,在onNuiNeedAudioData回调中填充音频数据。 -
开启中间结果时,通过
EVENT_ASR_PARTIAL_RESULT获取中间结果;通过EVENT_ASR_RESULT获取最终结果。 -
调用
nui_dialog_cancel:NO停止识别并等待任务结束;开启在线 VAD 时,也可以由服务端自动判断句子结束。 -
不再使用 SDK 时,调用
nui_release释放资源。
不要在 UI 线程中调用可能阻塞的初始化接口。不要在 SDK 事件回调中同步调用初始化、开始、结束或释放等控制接口,以免死锁;需要触发这些操作时,转交给应用的工作队列。
代码示例
以下为 2.7.4 随包 Demo 的关键接入片段,依赖示例工程中的 AudioController。该录音模块可按应用需要替换;完整工程、界面及录音模块见 SDK 包中的 Demo。示例使用 16 kHz、16 bit、单声道 PCM 音频,录音配置需与识别采样率一致。
初始化
在控制器中实现 NeoNuiSdkDelegate 和录音模块的 ConvVoiceRecorderDelegate,持有 SDK 实例、录音实例及音频缓冲区:
#import <nuisdk/NeoNui.h>
#import "audio/AudioController.h"
@interface SpeechRecognizerViewController () <NeoNuiSdkDelegate, ConvVoiceRecorderDelegate>
@property(nonatomic, strong) NeoNui *nui;
@property(nonatomic, strong) AudioController *audioController;
@property(nonatomic, strong) NSMutableData *recordedVoiceData;
@end
get_instance 用于获取单例;需要多个实例时,可以使用 [[NeoNui alloc] init]。同一实例再次初始化前,应先调用 nui_release。
配置服务地址:
NSString *gateway = @"wss://nls-gateway.cn-shanghai.aliyuncs.com:443/ws/v1";
将 <Appkey> 和 <NLS Token> 替换为项目 Appkey 和服务端下发的有效 Token,在工作队列中执行初始化:
_nui = [NeoNui get_instance];
_nui.delegate = self;
_recordedVoiceData = [NSMutableData data];
_audioController = [[AudioController alloc] init:only_recorder];
_audioController.delegate = self;
NSDictionary *initParams = @{
@"app_key": @"<Appkey>",
@"token": @"<NLS Token>",
@"url": gateway,
@"service_mode": @"4"
};
NSData *initData = [NSJSONSerialization dataWithJSONObject:initParams options:0 error:nil];
NSString *initJSON = [[NSString alloc] initWithData:initData encoding:NSUTF8StringEncoding];
NuiResultCode result = [_nui nui_initialize:initJSON.UTF8String
logLevel:NUI_LOG_LEVEL_NONE
saveLog:NO];
if (result != 0) {
NSLog(@"nui_initialize failed: %d", result);
return;
}
自 2.6.2 起,纯云端功能可以不设置 workspace 和 device_id。旧版本需要 workspace 时,将其设置为 Resources.bundle 的资源路径。device_id 用于辅助定位问题,不要求使用广告标识符。
调试时可将日志级别设置为 NUI_LOG_LEVEL_VERBOSE。启用 saveLog 时,需设置可写的 debug_path;save_wav 用于控制是否保存调试音频。日志文件没有大小上限,应及时清理,避免占满存储空间。
设置参数
通过 JSON 设置识别参数。enable_intermediate_result 为可选参数;需要中间结果时设置为 YES。
NSMutableDictionary *nlsConfig = [@{
@"enable_intermediate_result": @YES,
@"sample_rate": @16000,
@"sr_format": @"pcm"
} mutableCopy];
NSDictionary *params = @{
@"service_type": @(SERVICE_TYPE_ASR),
@"nls_config": nlsConfig
};
NSData *paramsData = [NSJSONSerialization dataWithJSONObject:params options:0 error:nil];
NSString *paramsJSON = [[NSString alloc] initWithData:paramsData encoding:NSUTF8StringEncoding];
NuiResultCode result = [_nui nui_set_params:paramsJSON.UTF8String];
if (result != 0) {
NSLog(@"nui_set_params failed: %d", result);
return;
}
01B 版本不包含本地 VAD 模块;带唤醒功能的 029 版本包含本地 VAD 模块。需要自动判断一句话结束时,可以在上述 nlsConfig 创建后、序列化前添加在线 VAD 参数,开始识别时仍使用 MODE_P2T:
nlsConfig[@"enable_voice_detection"] = @YES;
nlsConfig[@"max_start_silence"] = @10000;
nlsConfig[@"max_end_silence"] = @800;
标点预测、逆文本规整、采样率、音频传输格式等参数的含义及取值,请参见接口说明。
如果使用 HTTPDNS,可通过顶层参数 direct_ip 设置解析得到的 IP。对于服务支持、但 SDK 尚未单独列出的参数,可通过顶层对象 extend_config 传入。
开始识别
NuiResultCode result = [_nui nui_dialog_start:MODE_P2T dialogParam:"{}"];
if (result != 0) {
NSLog(@"nui_dialog_start failed: %d", result);
}
需要为当前识别任务更新 Appkey 或 Token 时,将 app_key、token 放入 dialogParam 的 JSON 字符串。下一轮任务未传入这些临时参数时,继续使用初始化时的参数。
处理回调
录音状态与数据供给
根据 SDK 音频状态启动或停止录音,将录音模块回调的数据存入缓冲区,再通过 onNuiNeedAudioData 提供给 SDK。
- (void)onNuiAudioStateChanged:(NuiAudioState)state {
if (state == STATE_OPEN) {
@synchronized (self) {
[_recordedVoiceData setLength:0];
}
[_audioController startRecorder];
} else if (state == STATE_CLOSE || state == STATE_PAUSE) {
[_audioController stopRecorder:NO];
}
}
- (void)voiceDidFail:(NSError *)error {
NSLog(@"Recording failed: %@", error.localizedDescription);
}
- (void)voiceRecorded:(unsigned char *)buffer Length:(int)len {
@synchronized (self) {
[_recordedVoiceData appendBytes:buffer length:(NSUInteger)len];
}
}
- (int)onNuiNeedAudioData:(char *)audioData length:(int)len {
@synchronized (self) {
NSUInteger count = MIN(_recordedVoiceData.length, (NSUInteger)len);
if (count == 0) return 0;
[_recordedVoiceData getBytes:audioData length:count];
[_recordedVoiceData replaceBytesInRange:NSMakeRange(0, count)
withBytes:NULL length:0];
return (int)count;
}
}
识别结果与错误
asr_result 包含识别结果或错误信息,task_id 可用于排查请求问题。finish 为真表示本轮任务结束,可能是正常完成,也可能是发生错误。
- (void)onNuiEventCallback:(NuiCallbackEvent)event
dialog:(long)dialog
kwsResult:(const char *)wuw
asrResult:(const char *)asrResult
ifFinish:(BOOL)finish
retCode:(int)code {
NSString *message = asrResult ? [NSString stringWithUTF8String:asrResult] : @"";
if (event == EVENT_ASR_PARTIAL_RESULT || event == EVENT_ASR_RESULT) {
NSLog(@"ASR result: %@", message);
} else if (event == EVENT_ASR_ERROR || event == EVENT_MIC_ERROR) {
NSLog(@"ASR error: %d, %@", code, message);
}
if (finish) {
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面状态,允许开始下一轮识别。
});
}
}
结束识别与释放资源
正常停止时使用 NO,等待最终结果和任务结束回调。没有有效语音时,最终识别文本可能为空。
[_nui nui_dialog_cancel:NO];
不再需要结果时,可以使用 nui_dialog_cancel:YES 强制结束,并由应用执行清理操作。不再使用 SDK 时,在 SDK 回调之外释放资源并解除代理绑定:
[_audioController stopRecorder:NO];
[_nui nui_release];
_nui.delegate = nil;
_audioController.delegate = nil;
SDK 接口与回调
初始化与资源管理
- (NuiResultCode)nui_initialize:(const char *)parameters
logLevel:(NuiSdkLogLevel)level
saveLog:(BOOL)save_log;
- (NuiResultCode)nui_release;
- (const char *)nui_get_version;
|
接口 |
说明 |
|
|
使用 JSON 字符串初始化 SDK。 |
|
|
释放 SDK 资源。 |
|
|
返回字符串形式的 SDK 版本。 |
识别控制
- (NuiResultCode)nui_set_params:(const char *)params;
- (NuiResultCode)nui_dialog_start:(NuiVadMode)vad_mode
dialogParam:(const char *)dialog_params;
- (NuiResultCode)nui_dialog_cancel:(BOOL)force;
|
接口 |
说明 |
|
|
使用 JSON 字符串设置识别参数。 |
|
|
开始识别。 |
|
|
结束识别。 |
以上返回值为 NuiResultCode 的接口使用 0 表示调用成功。识别结果通过事件回调返回。
事件与音频回调
- (void)onNuiEventCallback:(NuiCallbackEvent)nuiEvent
dialog:(long)dialog
kwsResult:(const char *)wuw
asrResult:(const char *)asr_result
ifFinish:(BOOL)finish
retCode:(int)code;
- (int)onNuiNeedAudioData:(char *)audioData length:(int)len;
- (void)onNuiAudioStateChanged:(NuiAudioState)state;
- (void)onNuiRmsChanged:(float)rms;
事件回调中的 asr_result 提供识别结果或错误信息,finish 表示本轮任务是否结束,code 提供错误码。dialog 和 wuw 不用于此识别场景。
|
事件 |
说明 |
|
|
检测到人声起点。 |
|
|
检测到人声尾点。 |
|
|
语音识别中间结果。 |
|
|
语音识别最终结果。 |
|
|
识别错误,根据错误码和错误信息排查。 |
|
|
录音错误。连续 2 秒未收到音频时,检查录音设备及音频供给。 |
-
onNuiNeedAudioData:向audioData填充最多len字节的音频,并返回实际填充字节数。 -
onNuiAudioStateChanged:根据STATE_OPEN、STATE_PAUSE、STATE_CLOSE控制录音。 -
onNuiRmsChanged:获取音频能量,范围为 -160~0。
需要读取当前事件的完整 JSON 信息时,使用 nui_get_all_response:
- (const char *)nui_get_all_response;
常见问题
找不到 NeoNui.h,如何处理?
确认 SDK 已添加到工程并参与链接,头文件使用 #import <nuisdk/NeoNui.h>,不要使用 #import "nuisdk.framework/Headers/NeoNui.h"。动态库还需设置 Embed & Sign。
移动端 SDK 能否用于专有云?
移动端 SDK 可以调用公共云的 ASR、TTS 服务,也可以用于专有云环境。专有云安装包默认不包含移动端 SDK,可从对应服务的文档下载,例如实时语音识别的Android SDK和iOS SDK。
是否支持后台处理?
SDK 本身不限制前后台,后台录音还受 iOS 音频会话及应用配置约束。需要后台录音时:
-
在
Info.plist的 Required background modes(UIBackgroundModes)中添加audio,对应 App plays audio or streams audio/video using AirPlay。 -
检查录音模块的后台处理,避免进入后台时主动停止录音或停用音频会话。旧版
NLSVoiceRecorder.m的_appResignActive方法需去掉AudioSessionSetActive(NO)调用。
2.7.4 Demo 使用 AudioController 录音模块,已配置 UIBackgroundModes=audio,且后台处理不调用 AudioSessionSetActive(NO)。
真机提示 no suitable image found,如何处理?
先确认链接的是适用于真机的库,再尝试删除设备上的应用、清理构建产物并重新安装。同时检查应用和动态库的签名、证书及 provisioning profile 是否匹配。若确认使用的企业证书无效,重新制作有效证书和 provisioning profile,并重新签名、打包。
出现 EVENT_MIC_ERROR,如何处理?
检查麦克风权限、录音设备是否被其他模块占用,以及 onNuiNeedAudioData 是否持续提供音频。连续 2 秒没有音频数据会触发该事件。
提示库同时面向 iOS 和 iOS Simulator,如何处理?
出现 Building for iOS, but the linked and embedded framework ... was built for iOS + iOS Simulator 时,检查是否使用了旧版混合平台的 Framework。改为集成 SDK 包中的 nuisdk.xcframework,由 Xcode 根据构建目标选择对应平台的库,不要将模拟器库嵌入真机应用。
App Store 提示 Unsupported Architectures,如何处理?
如果旧版 Framework 包含 x86_64、i386 等模拟器架构,可通过 lipo -info <Framework二进制文件> 检查,并在真机分发包中移除模拟器架构。使用 XCFramework 时,应归档真机构建目标,由 Xcode 选择真机库。
旧工程需要 Legacy Build System 才能构建,如何处理?
对于仍提供 Validate Workspace 配置的旧版 Xcode 工程,可尝试将其设置为 Yes 后重新编译。新工程应按当前 SDK 包结构集成 XCFramework。