移动端IOS推流

本文介绍了如何使用阿里云智能语音服务提供的iOS NUI SDK,包括SDK下载安装、关键接口及代码示例。

前提条件

SDK关键接口

  • nui_initialize:初始化SDK。

    /**
     * 初始化SDK,SDK为单例,请先释放后再次进行初始化。请勿在UI线程调用,可能引起阻塞。
     * @param parameters: 初始化参数,参考如下说明。
     * @param level: log打印级别,值越小打印越多
     * @param save_log: 是否保存log为文件,存储目录为parameter中的debug_path字段值。注意,log文件无上限,请注意持续存储导致磁盘存满。
     * @return 参见错误码
     */
    -(NuiResultCode) nui_initialize:(const char *)parameters
                           logLevel:(NuiSdkLogLevel)level
                            saveLog:(BOOL)save_log;
    • parameters详细说明:

      参数

      类型

      是否必选

      说明

      workspace

      String

      工作目录路径,SDK从该路径读取配置文件。

      app_key

      String

      必须填“default”。

      token

      String

      必须填“default”。

      url

      String

      创建听悟实时记录任务时返回的会议MeetingJoinUrl作为音频流推送地址,在后续实时音频流识别时通过该地址进行推流。

      service_mode

      String

      必须填“1”,表示启用在线功能。

      device_id

      String

      设备标识,唯一表示一台设备(如Mac地址/SN/UniquePsuedoID等)。

      debug_path

      String

      debug目录。当初始化SDK时的save_log参数取值为true时,该目录用于保存日志文件。

      save_wav

      String

      当初始化SDK时的save_log参数取值为true时,该参数生效。表示是否保存音频debug,该数据保存在debug目录中,需要确保debug_path有效可写。

      注意,音频文件无上限,请注意持续存储导致磁盘存满。

  • nui_set_params:以JSON格式设置SDK参数。

    /**
     * 以JSON格式设置参数。接口需要在nui_initialize之后nui_dialog_start之前调用。
     * @param params: 参数信息请参见如下说明。
     * @return 参见错误码
     */
    -(NuiResultCode) nui_set_params:(const char *)params;
    • params详细说明:

      参数

      类型

      是否必选

      说明

      service_type

      Int

      必须填“4”。此为需要请求的语音服务类型,听悟实时推流为“4”。

      nls_config

      JsonObject

      访问语音服务相关的参数配置,详见如下。

      nls_config.sr_format

      String

      必须填“pcm”。对应的《CreateTask - 创建听悟任务》中,创建听悟任务时也请指定音频流数据的编码格式为pcm。

      nls_config.sample_rate

      Integer

      音频采样率,默认值:16000Hz。对应的《CreateTask - 创建听悟任务》中,创建听悟任务时也请指定音频流数据的采样率,当前支持 8000 和 16000。

  • nui_dialog_start:开始识别。

    /**
     * 开始识别
     * @param vad_mode: 多种模式,对于识别场景,请使用P2T。
     * @param dialog_params: 设置识别参数,可不设置直接传入空JSON字符串。
     * @return 参见错误码
     */
    -(NuiResultCode) nui_dialog_start:(NuiVadMode)vad_mode
                          dialogParam:(const char *)dialog_params;
  • nui_dialog_cancel:结束识别。

    /**
     * 结束识别,调用该接口后,服务端将返回最终识别结果并结束任务
     * @param force: 是否强制结束而忽略最终结果,false表示停止但是等待完整结果返回
     * @return 参见错误码
     */
    -(NuiResultCode) nui_dialog_cancel:(BOOL)force;
  • nui_release:释放SDK。

    /**
     * 释放SDK资源
     * @return 参见错误码
     */
    -(NuiResultCode) nui_release;
  • nui_get_version:获得当前SDK版本信息。

    /**
     * 获得当前SDK版本信息
     * @return 字符串形式的SDK版本信息
     */
    -(const char*) nui_get_version;
  • nui_get_all_response:获得当前事件回调的完整信息。

    /**
     * onNuiEventCallback回调中获得当前事件回调的完整信息
     * @return json字符串形式的完整事件信息
     */
    -(const char*) nui_get_all_response;
  • NeoNuiSdkDelegate

    onNuiEventCallback:SDK事件回调。

    /**
     * SDK主要事件回调
     * @param event: 回调事件,参见如下事件列表
     * @param dialog: 会话编号,暂不使用
     * @param wuw: 语音唤醒功能使用(暂不支持)
     * @param asr_result: 语音识别结果和翻译结果
     * @param finish: 本轮识别是否结束标志
     * @param resultCode: 参见错误码,在出现EVENT_ASR_ERROR事件时有效
     */
    -(void) onNuiEventCallback:(NuiCallbackEvent)nuiEvent
                        dialog:(long)dialog
                     kwsResult:(const char *)wuw
                     asrResult:(const char *)asr_result
                      ifFinish:(BOOL)finish
                       retCode:(int)code;

    NuiCallbackEvent事件列表:

    名称

    说明

    EVENT_VAD_START

    检测到人声起点.

    EVENT_VAD_END

    检测到人声尾点。

    EVENT_ASR_PARTIAL_RESULT

    语音识别中间结果。

    EVENT_ASR_RESULT

    语音识别最终结果。

    EVENT_ASR_ERROR

    根据错误码信息判断出错原因。

    EVENT_MIC_EEROR

    录音错误,表示SDK连续2秒未收到任何音频,可检查录音系统是否正常。

    EVENT_SENTENCE_START

    实时语音识别事件,检测到一句话开始。

    EVENT_SENTENCE_END

    实时语音识别事件,检测一句话结束,返回一句的完整结果。

    EVENT_SENTENCE_SEMANTICS

    暂不使用。

    EVENT_RESULT_TRANSLATED

    翻译结果。

    EVENT_TRANSCRIBER_COMPLETE

    停止语音识别后上报。

    onNuiNeedAudioData:获取音频。

    /**
     * 开始识别时,此回调被连续调用,App需要在回调中进行语音数据填充
     * @param audioData:  填充语音的存储区
     * @param len: 需要填充语音的字节数
     * @return 实际填充的字节数
     */
    -(int) onNuiNeedAudioData:(char *)audioData length:(int)len;

    onNuiAudioStateChanged:根据音频状态进行录音功能的开关。

    /**
     * 当start/stop/cancel等接口调用时,SDK通过此回调通知App进行录音的开关操作
     * @param state:录音需要的状态(打开/关闭)
     */
    -(void) onNuiAudioStateChanged:(NuiAudioState)state;

    onNuiRmsChanged:音频能量事件。

    /**
     * SDK运行过程中收到音频的实时音频能量值。
     * @param rms: 音频能量值,范围为-160至0
     */
    -(void) onNuiRmsChanged:(float) rms;

调用步骤

重要

请下载后在听悟的样例初始化代码中将Appkey和Token置为default,url置为您创建听悟实时记录返回的会议MeetingJoinUrl。

  1. 初始化SDK、录音实例。

  2. 根据业务需求配置参数。

  3. 调用nui_dialog_start开始识别。

  4. 根据音频状态回调onNuiAudioStateChanged,打开录音机。

  5. 在onNuiNeedAudioData回调中提供录音数据。

  6. 在EVENT_ASR_PARTIAL_RESULT和EVENT_SENTENCE_END事件回调中获取识别结果,在EVENT_RESULT_TRANSLATED事件回调中获得翻译结果。

  7. 调用nui_dialog_cancel结束识别。

  8. 结束调用,使用nui_release接口释放SDK资源。

代码示例

说明

接口默认采用get_instance方式获得单例,您如果有多例需求,也可以直接alloc对象进行使用。

NUI SDK初始化

BOOL save_log = NO;
NSString * initParam = [self genInitParams];
[_nui nui_initialize:[initParam UTF8String] logLevel:NUI_LOG_LEVEL_VERBOSE saveLog:save_log];

其中,genInitParams生成为String JSON字符串,包含资源目录和用户信息。其中用户信息包含如下字段。

-(NSString*) genInitParams {
    NSString *strResourcesBundle = [[NSBundle mainBundle] pathForResource:@"Resources" ofType:@"bundle"];
    NSString *bundlePath = [[NSBundle bundleWithPath:strResourcesBundle] resourcePath];
    NSString *debug_path = [_utils createDir];

    // 1. 接口与实现:https://help.aliyun.com/zh/tingwu/interface-and-implementation?spm=a2c4g.11186623.0.0.2b045060jFsVFw
    //    按文档步骤,首先创建AccessKey和创建项目
    //    然后需要用户在自己的服务端调用CreateTask接口创建实时记录,获得MeetingJoinUrl
    //    此MeetingJoinUrl即为下方url
    NSMutableDictionary *dictM = [NSMutableDictionary dictionary];

    //账号和项目创建
    //  ak_id ak_secret 如何获得,请查看https://help.aliyun.com/document_detail/72138.html
    [dictM setObject:@"default" forKey:@"app_key"]; // 必填,不改动
    [dictM setObject:@"default" forKey:@"token"]; // 必填,不改动

    // url中填入生成的MeetingJoinUrl。
    // 由于MeetingJoinUrl生成过程涉及ak/sk,移动端不可存储账号信息,故需要在服务端生成,并下发给移动端。
    // 详细请看: https://help.aliyun.com/zh/tingwu/api-tingwu-2023-09-30-createtask
    [dictM setObject:@"wss://tingwu-realtime-cn-hangzhou-pre.aliyuncs.com/api/ws/v1?XXXX" forKey:@"url"]; // 必填
    //工作目录路径,SDK从该路径读取配置文件
    [dictM setObject:bundlePath forKey:@"workspace"]; // 必填

    //当初始化SDK时的save_log参数取值为true时,该参数生效。表示是否保存音频debug,该数据保存在debug目录中,需要确保debug_path有效可写
    [dictM setObject:save_wav ? @"true" : @"false" forKey:@"save_wav"];
    //debug目录。当初始化SDK时的save_log参数取值为true时,该目录用于保存中间音频文件
    [dictM setObject:debug_path forKey:@"debug_path"];
    
    //FullCloud = 1 // 在线实时语音识别可以选这个
    [dictM setObject:@"1" forKey:@"service_mode"]; // 必填,不改动

    NSString *id_string = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
    TLog(@"id: %s", [id_string UTF8String]);
    [dictM setObject:id_string forKey:@"device_id"]; // 必填, 推荐填入具有唯一性的id, 方便定位问题

    NSData *data = [NSJSONSerialization dataWithJSONObject:dictM options:NSJSONWritingPrettyPrinted error:nil];
    NSString * jsonStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    return jsonStr;
}

参数设置

以JSON字符串形式进行设置。

-(NSString*) genParams {
    NSMutableDictionary *nls_config = [NSMutableDictionary dictionary];
    [nls_config setValue:@16000 forKey:@"sample_rate"];
    [nls_config setValue:@"pcm" forKey:@"sr_format"];
    NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
    [dictM setObject:nls_config forKey:@"nls_config"];
    [dictM setValue:@(SERVICE_TYPE_SPEECH_TRANSCRIBER) forKey:@"service_type"]; // 必填
    
    NSData *data = [NSJSONSerialization dataWithJSONObject:dictM options:NSJSONWritingPrettyPrinted error:nil];
    NSString * jsonStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    return jsonStr;
}

NSString * parameters = [self genParams];
[_nui nui_set_params:[parameters UTF8String]];

开始识别

通过nui_dialog_start接口开启监听。

-(NSString*) genDialogParams {
    NSMutableDictionary *dialog_params = [NSMutableDictionary dictionary];    
    NSData *data = [NSJSONSerialization dataWithJSONObject:dialog_params options:NSJSONWritingPrettyPrinted error:nil];
    NSString * jsonStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    return jsonStr;
}

NSString * parameters = [self genDialogParams];
[_nui nui_dialog_start:MODE_P2T dialogParam:[parameters UTF8String]];

回调处理

  • onNuiAudioStateChanged:录音状态回调,SDK内部维护录音状态,调用时根据该状态的回调进行录音机的开关操作。

    -(void)onNuiAudioStateChanged:(NuiAudioState)state{
        TLog(@"onNuiAudioStateChanged state=%u", state);
        if (state == STATE_CLOSE || state == STATE_PAUSE) {
            // 旧版本示例工程提供的录音模块,仅做参考,可根据自身业务重写录音模块。
            // [_voiceRecorder stop:YES];
    
            // 新版本示例工程提供了新的录音模块,仅做参考,可根据自身业务重写录音模块。
            [_audioController stopRecorder:NO];
        } else if (state == STATE_OPEN){
            self.recordedVoiceData = [NSMutableData data];
            // 旧版本示例工程提供的录音模块,仅做参考,可根据自身业务重写录音模块。
            // [_voiceRecorder start];
    
            // 新版本示例工程提供了新的录音模块,仅做参考,可根据自身业务重写录音模块。
            [_audioController startRecorder];
        }
    }
  • onNuiNeedAudioData:录音数据回调,在该回调中填充录音数据。

    -(int)onNuiNeedAudioData:(char *)audioData length:(int)len {
        static int emptyCount = 0;
        @autoreleasepool {
            @synchronized(_recordedVoiceData){
                if (_recordedVoiceData.length > 0) {
                    int recorder_len = 0;
                    if (_recordedVoiceData.length > len)
                        recorder_len = len;
                    else
                        recorder_len = _recordedVoiceData.length;
                    NSData *tempData = [_recordedVoiceData subdataWithRange:NSMakeRange(0, recorder_len)];
                    [tempData getBytes:audioData length:recorder_len];
                    tempData = nil;
                    NSInteger remainLength = _recordedVoiceData.length - recorder_len;
                    NSRange range = NSMakeRange(recorder_len, remainLength);
                    [_recordedVoiceData setData:[_recordedVoiceData subdataWithRange:range]];
                    emptyCount = 0;
                    return recorder_len;
                } else {
                    if (emptyCount++ >= 50) {
                        TLog(@"_recordedVoiceData length = %lu! empty 50times.", (unsigned long)_recordedVoiceData.length);
                        emptyCount = 0;
                    }
                    return 0;
                }
    
            }
        }
        return 0;
    }
  • onNuiEventCallback:NUI SDK事件回调,请勿在事件回调中调用SDK的接口,可能引起死锁。

    -(void)onNuiEventCallback:(NuiCallbackEvent)nuiEvent
                       dialog:(long)dialog
                    kwsResult:(const char *)wuw
                    asrResult:(const char *)asr_result
                     ifFinish:(bool)finish
                      retCode:(int)code {
        TLog(@"onNuiEventCallback event %d finish %d", nuiEvent, finish);
        if (nuiEvent == EVENT_ASR_PARTIAL_RESULT || nuiEvent == EVENT_SENTENCE_END) {
            // asr_result在此包含task_id,task_id有助于排查问题,请用户进行记录保存。
            TLog(@"ASR RESULT %s finish %d", asr_result, finish);
            NSString *result = [NSString stringWithUTF8String:asr_result];
        } else if (nuiEvent == EVENT_RESULT_TRANSLATED) {
            // 获得翻译结果
            NSString *result = [NSString stringWithUTF8String:asr_result];
        } else if (nuiEvent == EVENT_ASR_ERROR) {
            // asr_result在EVENT_ASR_ERROR中为错误信息,搭配错误码code和其中的task_id更易排查问题,请用户进行记录保存。
            TLog(@"EVENT_ASR_ERROR error[%d], error mesg[%s]", code, asr_result);
    
            // 可通过nui_get_all_response获得完整的信息
            const char *response = [_nui nui_get_all_response];
            if (response != NULL) {
                TLog(@"GET ALL RESPONSE: %s", response);
            }
        } else if (nuiEvent == EVENT_MIC_ERROR) {
            TLog(@"MIC ERROR");
            // 旧版本示例工程提供的录音模块,仅做参考,可根据自身业务重写录音模块。
            // [_voiceRecorder stop:YES];
            // [_voiceRecorder start];
    
            // 新版本示例工程提供了新的录音模块,仅做参考,可根据自身业务重写录音模块。
            [_audioController stopRecorder:NO];
            [_audioController startRecorder];
        }
    
        //finish 为真(可能是发生错误,也可能是完成识别)表示一次任务生命周期结束,可以开始新的识别
        if (finish) {
        }
        
        return;
    }

结束识别

[_nui nui_dialog_cancel:NO];