iOS SDK

更新时间:
复制 MD 格式

通过智能语音交互 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 版本的包结构为例。

  1. 解压 SDK 包,将 Release/nuisdk.xcframework 添加到 Xcode 工程。在 General > Frameworks, Libraries, and Embedded Content 中将其设置为 Embed & Sign,并确认 Build Phases > Link Binary With Libraries 中包含该库。不要同时链接 XCFramework 和独立的 nuisdk.framework。

  2. 在源文件中导入头文件:

    #import <nuisdk/NeoNui.h>
  3. 使用麦克风采集音频时,在 Info.plist 中配置 NSMicrophoneUsageDescription,并在开始录音前申请、检查麦克风权限。

  4. 参考随包 Demo 中的 SpeechRecognizerViewController 接入识别流程。示例工程提供 AudioController 录音模块及文件操作等工具类,可以按业务需要复用或替换。

XCFramework 分别包含真机和模拟器的库。2.7.4 包提供 ios-arm64 和 ios-arm64-simulator,应选择与目标平台一致的构建目标。该包的模拟器库支持 arm64 架构。

调用流程

  1. 创建 SDK 实例和录音实例,设置代理并初始化 SDK。

  2. 按业务需要设置识别参数。

  3. 调用 nui_dialog_start,使用 MODE_P2T 开始识别。

  4. 根据 onNuiAudioStateChanged 回调控制录音,在 onNuiNeedAudioData 回调中填充音频数据。

  5. 开启中间结果时,通过 EVENT_ASR_PARTIAL_RESULT 获取中间结果;通过 EVENT_ASR_RESULT 获取最终结果。

  6. 调用 nui_dialog_cancel:NO 停止识别并等待任务结束;开启在线 VAD 时,也可以由服务端自动判断句子结束。

  7. 不再使用 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;

接口

说明

nui_initialize

使用 JSON 字符串初始化 SDK。level 设置日志级别,值越小输出越多;save_log 控制是否保存日志。再次初始化同一实例前先释放资源。

nui_release

释放 SDK 资源。

nui_get_version

返回字符串形式的 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;

接口

说明

nui_set_params

使用 JSON 字符串设置识别参数。

nui_dialog_start

开始识别。vad_mode 使用 MODE_P2T;dialog_params 可传入本轮任务的临时参数,无临时参数时传入 {}。

nui_dialog_cancel

结束识别。force=NO 停止并等待完整结果;force=YES 强制结束并忽略最终结果。

以上返回值为 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 不用于此识别场景。

事件

说明

EVENT_VAD_START

检测到人声起点。

EVENT_VAD_END

检测到人声尾点。

EVENT_ASR_PARTIAL_RESULT

语音识别中间结果。

EVENT_ASR_RESULT

语音识别最终结果。

EVENT_ASR_ERROR

识别错误,根据错误码和错误信息排查。

EVENT_MIC_ERROR

录音错误。连续 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 音频会话及应用配置约束。需要后台录音时:

  1. 在 Info.plist 的 Required background modes(UIBackgroundModes)中添加 audio,对应 App plays audio or streams audio/video using AirPlay。

  2. 检查录音模块的后台处理,避免进入后台时主动停止录音或停用音频会话。旧版 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。