C++ SDK
本文介绍如何使用阿里云智能语音服务提供的C++ SDK,包括SDK的安装方法及SDK代码示例。
SDK下载
当前最新版本:3.1.16,支持Linux、Windows及Android平台。发布日期:2023年02月10日。
使用SDK前,请先阅读接口说明,详情请参见接口说明。
该版本C++ SDK API 3.1和上一版本API 2.0(已下线)定义有区别,本文以当前版本为例进行介绍。
您可通过以下两种方法获取SDK。
方法一:从GitHub获取最新源码,详细编译和运行方式可见下文,或查看源码中的readme.md。
git clone --depth 1 https://github.com/aliyun/alibabacloud-nls-cpp-sdk
方法二:直接从下文表中选取需要的SDK包进行下载。其中SDK源码包为SDK原始代码,需要通过下文编译方法生成集成所需的库文件。其他对应平台的SDK包内含相关库文件、头文件,无需编译。
最新SDK包 | 平台 | MD5 |
SDK源码 | 511c69cb2078f3b6ba122a6d027282f0 | |
Linux x86_64 | 7a6e6c8ca4ba51bfe5fbfbdae10c467c | |
Linux aarch64 | 76c34a3ab397d7285963a139b9270ff4 | |
Android | 0107c1ac58c92b94f35f6d98e2cbf6cf | |
Windows | c428e5cf1295d8933d23a4945683e250 |
其中:
alibabacloud-nls-cpp-sdk<version>-master_<github commit id>.zip为SDK源码包。
NlsCppSdk_<平台>_<版本号>_<github commit id>.tar.gz为对应平台下开发需要的SDK包,详见内部readme.md。
SDK包文件说明
scripts/build_linux.sh:SDK源码中,以Linux平台为例的示例编译脚本。
CMakeLists.txt:SDK源码中,以Linux/Android平台为例的示例代码工程CMakeList文件。
demo目录:SDK包中,集成示例代码,以Linux平台为例,如下表所示。
resource目录:SDK源码中,语音服务范例音频,可用于功能测试,如下表所示。
test0.wav
test1.wav
test2.wav
test3.wav
include:SDK源码中,SDK头文件,如下表所示。
文件名
描述
nlsClient.h
SDK实例。
nlsEvent.h
回调事件说明。
nlsGlobal.h
SDK全局头文件。
nlsToken.h
SDK Access Token实例。
iNlsRequest.h
NLS请求基础头文件。
speechRecognizerRequest.h
一句话识别。
speechSynthesizerRequest.h
语音合成、长文本语音合成。
speechTranscriberRequest.h
实时音频流识别。
FileTrans.h
录音文件识别。
lib:SDK库文件。
readme.md:SDK说明。
release.log:版本说明。
version:版本号。
文件名 | 描述 |
speechRecognizerDemo.cpp | 一句话识别示例。 |
speechSynthesizerDemo.cpp | 语音合成示例。 |
speechTranscriberDemo.cpp | 实时语音识别示例。 |
fileTransferDemo.cpp | 录音文件识别示例。 |
文件名 | 描述 |
测试音频(16k采样频率、16bit采样位数的音频文件)。 |
编译运行
Linux平台编译
安装工具的最低版本要求如下:
CMake 3.0
Glibc 2.5
Gcc 4.8.5
在Linux终端运行如下脚本。
进入SDK源码的根目录。
生成SDK库文件和可执行程序:srDemo(一句话识别)、stDemo(实时语音识别)、syDemo(语音合成)、daDemo(语音对话)。
./scripts/build_linux.sh
查看范例使用方式。
cd build/demo ./srDemo
Android平台编译
支持arm64-v8a、armeabi、armeabi-v7a、x86、x86_64编译指令,在SDK源码运行如下编译脚本。
./scripts/build_android.sh #默认增量编译,生成arm64-v8a架构Debug版本
./scripts/build_android.sh all debug arm64-v8a #全量编译,生成arm64-v8a架构Debug版本
./scripts/build_android.sh incr debug arm64-v8a #增量编译,生成arm64-v8a架构Debug版本
./scripts/build_android.sh all release arm64-v8a #全量编译,生成arm64-v8a架构Release版本
./scripts/build_android.sh incr release arm64-v8a #增量编译,生成arm64-v8a架构Release版本
Windows平台编译
推荐直接使用已经编译好的库NlsCppSdk_Windows_<version>_<github commit id>.zip进行集成。若有编译需求,请下载alibabacloud-nls-cpp-sdk<version>-master_<github commit id>.zip并解压到本地,或从GitHub获取最新代码,然后参考其中readme.md的编译步骤。
关键接口
基础接口
NlsClient:语音处理客户端,利用该客户端可以进行一句话识别、实时语音识别和语音合成的语音处理任务。该客户端为线程安全,建议全局仅创建一个实例。
接口名
功能描述
getInstance
获取(创建)NlsClient实例。
setLogConfig
设置日志文件与存储路径。
startWorkThread
启动工作线程数,默认1即启动一个线程,若-1则启动CPU核数的线程数。在高并发的情况下建议选择-1。
releaseInstance
销毁NlsClient对象实例。
getVersion
获取SDK版本号。
createRecognizerRequest
创建一句话识别对象,线程安全,支持高并发请求。
releaseRecognizerRequest
销毁一句话识别对象,需要在当前请求的closed事件后调用。
NlsToken:创建Token对象,用于申请获取TokenId。申请新Token时需要先获取有效时间戳,若超过有效时间则再申请。若在有效时间内多次申请Token会导致TokenId错误而无法使用。
接口名
功能描述
setAccessKeyId
设置阿里云账号AccessKey ID。
setKeySecret
设置阿里云账号AccessKey Secret。
applyNlsToken
申请获取TokenId。
getToken
获取TokenId。
getExpireTime
获取Token有效期时间戳(秒)。
NlsEvent:事件对象,您可以从中获取Request状态码、云端返回结果、失败信息等。
接口名
功能描述
getStatusCode
获取状态码,正常情况为0或者20000000,失败时对应失败的错误码。
getErrorMessage
在TaskFailed回调中,获取NlsRequest操作过程中出现失败时的错误信息。
getTaskId
获取任务的TaskId。
getAllResponse
获取云端返回的识别结果。
getResult
获取中间识别结果和最终结果。
识别接口
SpeechRecognizerRequest:一句话识别请求对象,用于短语音识别。
接口名
功能描述
setOnTaskFailed
设置错误回调函数。
setOnRecognitionStarted
设置一句话识别开始回调函数。
setOnRecognitionResultChanged
设置一句话识别中间结果回调函数。
setOnRecognitionCompleted
设置服务端结束服务回调函数。
setOnChannelClosed
设置通道关闭回调函数。
setAppKey
设置AppKey。
setToken
口令认证。所有的请求都必须通过SetToken方法认证通过,才可以使用。
setUrl
设置服务URL地址。
setIntermediateResult
设置是否返回中间识别结果。
setPunctuationPrediction
设置是否在后处理中添加标点。
setInverseTextNormalization
设置是否在后处理中执行数字转换。
setEnableVoiceDetection
设置是否启动自定义静音检测。
setMaxStartSilence
超出后(即开始识别后多长时间没有检测到声音)服务端将会发送TaskFailed事件,结束本次识别。
setMaxEndSilence
超出时长服务端会发送RecognitionCompleted事件,结束本次识别(需要注意后续的语音将不会进行识别)。
setFormat
设置音频数据编码格式。
setSampleRate
音频采样率设置。
setCustomizationId
设置定制模型。
setVocabularyId
设置泛热词。
setTimeout
设置Socket接收超时时间。
setOutputFormat
设置输出文本的编码格式。
setPayloadParam
参数设置。
setContextParam
设置用户自定义参数。
AppendHttpHeaderParam
设置用户自定义ws阶段http header参数。
start
启动SpeechRecognizerRequest。
stop
会与服务端确认关闭,正常停止链接操作。
cancel
不会与服务端确认关闭,直接关闭链接。
sendAudio
发送语音数据。
C++SDK错误码
状态码 | 状态消息 | 原因 | 解决方案 |
0 | Success | 成功 | |
-10 | DefaultError | 默认错误 | 暂未使用。 |
-11 | JsonParseFailed | 错误的JSON格式 | 请检查传入的JSON字符串是否符合JSON格式。 |
-12 | JsonObjectError | 错误的JSON对象 | 建议重新尝试。 |
-13 | MallocFailed | Malloc失败 | 请检查内存是否充足。 |
-14 | ReallocFailed | Realloc失败 | 请检查内存是否充足。 |
-15 | InvalidInputParam | 传入无效的参数 | 暂未使用。 |
-50 | InvalidLogLevel | 无效日志级别 | 请检查设置的Log级别。 |
-51 | InvalidLogFileSize | 无效日志文件大小 | 请检查设置的Log文件大小参数。 |
-52 | InvalidLogFileNum | 无效日志文件数量 | 请检查设置的Log文件数量参数。 |
-100 | EncoderExistent | NLS的编码器已存在 | 建议重新尝试。 |
-101 | EncoderInexistent | NLS的编码器不存在 | 建议重新初始化。 |
-102 | OpusEncoderCreateFailed | Opus编码器创建失败 | 建议重新初始化。 |
-103 | OggOpusEncoderCreateFailed | OggOpus编码器创建失败 | 建议重新初始化。 |
-150 | EventClientEmpty | 主工作线程空指针, 已释放 | 建议重新初始化,即startWorkThread()。 |
-151 | SelectThreadFailed | 工作线程选择失败, 未初始化 | 建议重新初始化,即startWorkThread()。 |
-160 | StartCommandFailed | 发送start命令失败 | 建议重新尝试。 |
-161 | InvokeStartFailed | 请求状态机不对, 导致start失败 | 请检查当前请求是否未创建或者已经完成。 |
-162 | InvokeSendAudioFailed | 请求状态机不对, 导致sendAudio失败 | 请检查当前请求是否已经启动(即收到started事件回调)或者已经完成。 |
-163 | InvalidOpusFrameSize | opus帧长无效, 默认为640字节 | OPU编码模式下,sendAudio一帧只接收640字节数据。 |
-164 | InvokeStopFailed | 请求状态机不对, 导致stop失败 | 请检查当前请求是否未启动(即收到started事件回调)或者已经完成。 |
-165 | InvokeCancelFailed | 请求状态机不对, 导致stop失败 | 请检查当前请求是否未启动(即收到started事件回调)或者已经完成。 |
-166 | InvokeStControlFailed | 请求状态机不对, 导致stControl失败 | 请检查当前请求是否未启动(即收到started事件回调)或者已经完成。 |
-200 | NlsEventEmpty | NLS事件为空 | SDK内部使用, NlsEvent帧丢失。 |
-201 | NewNlsEventFailed | 创建NlsEvent失败 | SDK内部使用, NlsEvent帧创建失败。 |
-202 | NlsEventMsgEmpty | NLS事件中消息为空 | parseJsonMsg()进行解析时发现消息字符串为空。 |
-203 | InvalidNlsEventMsgType | 无效的NLS事件中消息类型 | SDK内部使用, NlsEvent帧的事件类型不合法。 |
-204 | InvalidNlsEventMsgStatusCode | 无效的NLS事件中消息状态码 | SDK内部使用, NlsEvent帧的事件消息状态不合法。 |
-205 | InvalidNlsEventMsgHeader | 无效的NLS事件中消息头 | SDK内部使用, NlsEvent帧的事件消息头不合法。 |
-250 | CancelledExitStatus | 已调用cancel | 暂未使用。 |
-251 | InvalidWorkStatus | 无效的工作状态 | SDK内部使用, 当前请求内部状态不合法。 |
-252 | InvalidNodeQueue | workThread中NodeQueue无效 | SDK内部使用, 当前待运行的请求不合法,建议释放当前请求重新尝试。 |
-300 | InvalidRequestParams | 请求的入参无效 | sendAudio传入的数据为空。 |
-301 | RequestEmpty | 请求是空指针 | SDK内部使用, 当前请求已经释放,建议释放当前请求重新尝试。 |
-302 | InvalidRequest | 无效的请求 | SDK内部使用, 当前请求已经释放,建议释放当前请求重新尝试。 |
-303 | SetParamsEmpty | 设置传入的参数为空 | 请检查传入的参数是否为空。 |
-350 | GetHttpHeaderFailed | 获得http头失败 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-351 | HttpGotBadStatus | http错误的状态 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-352 | WsResponsePackageFailed | 解析websocket返回包失败 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-353 | WsResponsePackageEmpty | 解析websocket返回包为空 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-354 | WsRequestPackageEmpty | websocket请求包为空 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-355 | UnknownWsFrameHeadType | 未知websocket帧头类型 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-356 | InvalidWsFrameHeaderSize | 无效的websocket帧头大小 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-357 | InvalidWsFrameHeaderBody | 无效的websocket帧头本体 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-358 | InvalidWsFrameBody | 无效的websocket帧本体 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-359 | WsFrameBodyEmpty | 帧数据为空, 常见为收到了脏数据 | SDK内部使用, 根据日志中反馈信息详细定位。 |
-400 | NodeEmpty | node为空指针 | 建议释放当前请求重新尝试。 |
-401 | InvaildNodeStatus | node所处状态无效 | SDK内部使用, 建议释放当前请求重新尝试。 |
-402 | GetAddrinfoFailed | 通过DNS解析地址识别 | SDK内部使用, 请检查当前环境的DNS是否可用。 |
-403 | ConnectFailed | 联网失败 | 请检查当前网络环境是否可用。 |
-404 | InvalidDnsSource | 当前设备无DNS | SDK内部使用, 请检查当前环境的DNS是否可用。 |
-405 | ParseUrlFailed | 无效URL | 请检查设置的URL是否有效。 |
-406 | SslHandshakeFailed | SSL握手失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-407 | SslCtxEmpty | SSL_CTX未空 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-408 | SslNewFailed | SSL_new失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-409 | SslSetFailed | SSL设置参数失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-410 | SslConnectFailed | SSL_connect失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-411 | SslWriteFailed | SSL发送数据失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-412 | SslReadSysError | SSL接收数据收到SYSCALL错误 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-413 | SslReadFailed | SSL接收数据失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-414 | SocketFailed | 创建socket失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-415 | SetSocketoptFailed | 设置socket参数失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-416 | SocketConnectFailed | 进行socket链接失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-417 | SocketWriteFailed | socket发送数据失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-418 | SocketReadFailed | socket接收数据失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-430 | NlsReceiveFailed | NLS接收帧数据失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-431 | NlsReceiveEmpty | NLS接收帧数据为空 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-432 | ReadFailed | 接收数据失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-433 | NlsSendFailed | NLS发送数据失败 | SDK内部使用, 请检查当前网络环境是否可用,并再次尝试。 |
-434 | NewOutputBufferFailed | 创建buffer失败 | SDK内部使用, 请检查内存是否充足。 |
-435 | NlsEncodingFailed | 音频编码失败 | SDK内部使用, 建议释放当前请求重新尝试。 |
-436 | EventEmpty | event为空 | SDK内部使用, 建议释放当前请求重新尝试。 |
-437 | EvbufferTooMuch | evbuffer中数据太多 | SDK内部使用, 发送数据缓存已满(16K音频最大缓存320000,8K音频最大缓存160000),请检查是否发送音频数据过频或一次发送过多数据。 |
-438 | EvutilSocketFailed | evutil设置参数失败 | SDK内部使用, 建议释放当前请求重新尝试。 |
-439 | InvalidExitStatus | 无效的退出状态 | 请检查是否已经cancel了当前请求。 |
-450 | InvalidAkId | 阿里云账号ak id无效 | 请检查阿里云账号ak id是否为空。 |
-451 | InvalidAkSecret | 阿里云账号ak secret无效 | 请检查阿里云账号ak secret是否为空。 |
-452 | InvalidAppKey | 项目appKey无效 | 请检查阿里云项目appKey是否为空。 |
-453 | InvalidDomain | domain无效 | 请检查输入的domain是否为空。 |
-454 | InvalidAction | action无效 | 请检查输入的action是否为空。 |
-455 | InvalidServerVersion | ServerVersion无效 | 请检查输入的ServerVersion是否为空。 |
-456 | InvalidServerResource | ServerResource无效 | 请检查输入的ServerResource是否为空。 |
-457 | InvalidRegionId | RegionId无效 | 请检查输入的RegionId是否为空。 |
-500 | InvalidFileLink | 无效的录音文件链接 | 录音文件转写文件链接为空。 |
-501 | ErrorStatusCode | 错误的状态码 | 录音文件转写返回错误,详见错误码。 |
-502 | IconvOpenFailed | 申请转换描述失败 | UTF8与GBK转换失败。 |
-503 | IconvFailed | 编码转换失败 | UTF8与GBK转换失败。 |
-504 | ClientRequestFaild | 账号客户端请求失败 | 录音文件转写返回失败。 |
-999 | NlsMaxErrorCode |
其他状态码 | 状态消息 | 原因 | 解决方案 |
10000001 | NewSslCtxFailed | SSL: couldn't create a context! | 建议重新初始化。 |
10000002 | DefaultErrorCode | return of SSL_read: error:00000000:lib(0):func(0):reason(0) | 建议重新尝试。 |
return of SSL_read: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init | |||
10000003 | SysErrorCode | 系统错误。 | 根据系统反馈的错误信息进行处理。 |
10000004 | EmptyUrl | URL: The url is empty. | 传入的URL为空, 请重新填写正确URL。 |
10000005 | InvalidWsUrl | Could not parse WebSocket url: | 传入的URL格式错误, 请重新填写正确URL。 |
10000007 | JsonStringParseFailed | JSON: Json parse failed. | JSON格式异常, 请通过日志查看具体的错误点。 |
10000008 | UnknownWsHeadType | WEBSOCKET: unkown head type. | 联网失败,请检查本机DNS解析和URL是否有效。 |
10000009 | HttpConnectFailed | HTTP: connect failed. | 与云端连接失败,请检查网络后,重试。 |
10000010 | MemNotEnough | 内存不足。 | 请检查内存是否充足。 |
10000015 | SysConnectFailed | connect failed. | 联网失败,请检查本机DNS解析和URL是否有效。 |
10000100 | HttpGotBadStatusWith403 | Got bad status host=xxxxx line=HTTP/1.1 403 Forbidden | 链接被拒,请检查账号特别是token是否过期。 |
10000101 | EvSendTimeout | Send timeout. socket error: | libevent发送event超时,请检查回调中是否有耗时任务,或并发过大导致无法及时处理事件。 |
10000102 | EvRecvTimeout | Recv timeout. socket error: | libevent接收event超时,请检查回调中是否有耗时任务,或并发过大导致无法及时处理事件。 |
10000103 | EvUnknownEvent | Unknown event: | 未知的libevent事件,建议重新尝试。 |
10000104 | OpNowInProgress | Operation now in progress | 链接正在进行中,建议重新尝试。 |
10000105 | BrokenPipe | Broken pipe | pipe处理不过来,建议重新尝试。 |
10000110 | TokenHasExpired | Gateway:ACCESS_DENIED:The token 'xxx' has expired! | 请更新Token。 |
10000111 | TokenIsInvalid | Meta:ACCESS_DENIED:The token 'xxx' is invalid! | 请检查token的有效性。 |
10000112 | NoPrivilegeToVoice | Gateway:ACCESS_DENIED:No privilege to this voice! (voice: zhinan, privilege: 0) | 此发音人无权使用。 |
10000113 | MissAuthHeader | Gateway:ACCESS_DENIED:Missing authorization header! | 请检查账号是否有权限,或并发是否在限度内。 |
10000120 | Utf8ConvertError | utf8ToGbk failed | utf8转码失败,常为系统问题,建议重新尝试。 |
20000000 | SuccessStatusCode | 成功 |
服务端响应状态码
关于服务状态码,请参见服务状态码。
代码示例
示例中使用的音频文件为16000Hz采样率,管控台设置的模型为通用模型。如果使用其他音频,请设置为支持该音频场景的模型。关于模型设置,请参见管理项目。
示例中使用了SDK内置的默认一句话识别服务的外网访问服务URL,如果您使用阿里云上海ECS且需要使用内网访问URL,则在创建SpeechRecognizerRequest的对象中设置内网访问的URL。
request->setUrl("ws://nls-gateway.cn-shanghai-internal.aliyuncs.com/ws/v1")
完整示例,参见SDK压缩包中demo目录的speechRecognizerDemo.cpp文件。
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <ctime>
#include <string>
#include <iostream>
#include <vector>
#include <fstream>
#include <sys/time.h>
#include <errno.h>
#include "nlsClient.h"
#include "nlsEvent.h"
#include "nlsToken.h"
#include "speechRecognizerRequest.h"
#define FRAME_SIZE 3200
#define SAMPLE_RATE 16000
using namespace AlibabaNlsCommon;
using AlibabaNls::NlsClient;
using AlibabaNls::NlsEvent;
using AlibabaNls::LogDebug;
using AlibabaNls::LogInfo;
using AlibabaNls::LogError;
using AlibabaNls::SpeechRecognizerRequest;
//自定义线程参数。
struct ParamStruct {
std::string fileName;
std::string appkey;
std::string token;
};
//自定义事件回调参数。
struct ParamCallBack {
public:
ParamCallBack() {
pthread_mutex_init(&mtxWord, NULL);
pthread_cond_init(&cvWord, NULL);
};
~ParamCallBack() {
pthread_mutex_destroy(&mtxWord);
pthread_cond_destroy(&cvWord);
};
int userId;
char userInfo[8];
pthread_mutex_t mtxWord;
pthread_cond_t cvWord;
};
//全局维护一个服务鉴权Token和其对应的有效期时间戳。
//每次调用服务之前,首先判断Token是否已经过期。
//如果已经过期,则根据AccessKey ID和AccessKey Secret重新生成一个Token,并更新这个全局的token和其有效期时间戳。
//获取Token具体操作,请参见:https://help.aliyun.com/document_detail/450514.html
//说明:只需在Token即将过期时进行重新生成。所有的服务并发可共用一个Token。
std::string g_akId = "";
std::string g_akSecret = "";
std::string g_token = "";
long g_expireTime = -1;
struct timeval tv;
struct timeval tv1;
//根据AccessKey ID和AccessKey Secret重新生成一个token,并获取其有效期时间戳
int generateToken(std::string akId, std::string akSecret,
std::string* token, long* expireTime) {
NlsToken nlsTokenRequest;
nlsTokenRequest.setAccessKeyId(akId);
nlsTokenRequest.setKeySecret(akSecret);
int ret = nlsTokenRequest.applyNlsToken();
if (ret < 0) {
// 获取失败原因。
printf("generateToken Failed, error code:%d msg:%s\n",
ret, nlsTokenRequest.getErrorMsg());
return ret;
}
*token = nlsTokenRequest.getToken();
*expireTime = nlsTokenRequest.getExpireTime();
return 0;
}
//@brief 获取sendAudio发送延时时间。
//@param dataSize 待发送数据大小。
//@param sampleRate 采样率:16k/8K。
//@param compressRate 数据压缩率,例如压缩比为10:1的16k OPUS编码,此时为10,非压缩数据则为1。
//@return 返回sendAudio之后需要sleep的时间。
//@note 对于8k pcm 编码数据, 16位采样,建议每发送1600字节 sleep 100 ms.
// 对于16k pcm 编码数据, 16位采样,建议每发送3200字节 sleep 100 ms.
// 对于其它编码格式(OPUS)的数据, 由于传递给SDK的仍然是PCM编码数据,
// 按照SDK OPUS/OPU 数据长度限制, 需要每次发送640字节 sleep 20ms.
unsigned int getSendAudioSleepTime(int dataSize,
int sampleRate,
int compressRate) {
// 仅支持16位采样
const int sampleBytes = 16; // 仅支持单通道
const int soundChannel = 1; // 当前采样率,采样位数下每秒采样数据的大小。
int bytes = (sampleRate * sampleBytes * soundChannel) / 8; // 当前采样率,采样位数下每毫秒采样数据的大小。
int bytesMs = bytes / 1000; // 待发送数据大小除以每毫秒采样数据大小,以获取sleep时间。
int sleepMs = (dataSize * compressRate) / bytesMs;
return sleepMs;
}
//@brief 调用start(),成功与云端建立连接,sdk内部线程上报started事件。
//@param cbEvent 回调事件结构,详见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL。可以根据需求自定义参数。
void OnRecognitionStarted(NlsEvent* cbEvent, void* cbParam) {
ParamCallBack* tmpParam = (ParamCallBack*)cbParam;
// 演示如何打印/使用用户自定义参数。
printf("OnRecognitionStarted: %d, %s\n", tmpParam->userId, tmpParam->userInfo);
// 获取消息的状态码,成功为0或者20000000,失败时对应失败的错误码。
// 当前任务的task id,方便定位问题,作为和服务端交互的唯一标识建议输出。
printf("OnRecognitionStarted: status code=%d, task id=%s\n", cbEvent->getStatusCode(), cbEvent->getTaskId());
// 获取服务端返回的全部信息
//printf("OnRecognitionStarted: all response=%s\n", cbEvent->getAllResponse());
// 通知发送线程start()成功, 可以继续发送数据
pthread_mutex_lock(&(tmpParam->mtxWord));
pthread_cond_signal(&(tmpParam->cvWord));
pthread_mutex_unlock(&(tmpParam->mtxWord));
}
//@brief 设置允许返回中间结果参数,SDK在接收到云端返回到中间结果时,内部线程上报ResultChanged事件。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL。可以根据需求自定义参数。
void OnRecognitionResultChanged(NlsEvent* cbEvent, void* cbParam) {
ParamCallBack* tmpParam = (ParamCallBack*)cbParam;
// 演示如何打印/使用用户自定义参数。
printf("OnRecognitionResultChanged: %d, %s\n", tmpParam->userId, tmpParam->userInfo); // 当前任务的task id,方便定位问题,作为和服务端交互的唯一标识建议输出。
printf("OnRecognitionResultChanged: status code=%d, task id=%s, result=%s\n", cbEvent->getStatusCode(), cbEvent->getTaskId(), cbEvent->getResult());
// 获取服务端返回的全部信息
//printf("OnRecognitionResultChanged: response=%s\n", cbEvent->getAllResponse());
}
//@brief SDK在接收到云端返回识别结束消息时,其线程上报Completed事件。
//@note 上报Completed事件之后,SDK内部会关闭识别连接通道。此时调用sendAudio会返回负值。请停止发送。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL。可以根据需求自定义参数。
void OnRecognitionCompleted(NlsEvent* cbEvent, void* cbParam) {
ParamCallBack* tmpParam = (ParamCallBack*)cbParam;
// 演示如何打印/使用用户自定义参数。
printf("OnRecognitionCompleted: %d, %s\n", tmpParam->userId, tmpParam->userInfo);
// 当前任务的task id,方便定位问题,作为和服务端交互的唯一标识建议输出。
printf("OnRecognitionCompleted: status code=%d, task id=%s, result=%s\n", cbEvent->getStatusCode(), cbEvent->getTaskId(), cbEvent->getResult());
// 获取服务端返回的全部信息。
//printf("OnRecognitionCompleted: response=%s\n", cbEvent->getAllResponse());
}
//@brief 识别过程发生异常时,SDK内部线程上报TaskFailed事件。
//@note 上报TaskFailed事件之后,SDK内部会关闭识别连接通道。此时调用sendAudio会返回负值,请停止发送。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL。可以根据需求自定义参数。
//@return
void OnRecognitionTaskFailed(NlsEvent* cbEvent, void* cbParam) {
ParamCallBack* tmpParam = (ParamCallBack*)cbParam;
// 演示如何打印/使用用户自定义参数。
printf("OnRecognitionTaskFailed: %d, %s\n", tmpParam->userId, tmpParam->userInfo);
// 当前任务的task id,方便定位问题,作为和服务端交互的唯一标识建议输出。
printf("OnRecognitionTaskFailed: status code=%d, task id=%s, error message=%s\n", cbEvent->getStatusCode(), cbEvent->getTaskId(), cbEvent->getErrorMessage());
// 获取服务端返回的全部信息。
//printf("OnRecognitionTaskFailed: response=%s\n", cbEvent->getAllResponse());
}
//@brief 识别结束或发生异常时,SDK会关闭连接通道,其内部线程上报ChannelCloseed事件。
//@param cbEvent 回调事件结构,详见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL。可以根据需求自定义参数。
void OnRecognitionChannelClosed(NlsEvent* cbEvent, void* cbParam) {
ParamCallBack* tmpParam = (ParamCallBack*)cbParam;
// 演示如何打印/使用用户自定义参数。
printf("OnRecognitionChannelClosed: %d, %s\n", tmpParam->userId, tmpParam->userInfo); // 获取服务端返回的全部信息。
printf("OnRecognitionChannelClosed: response=%s\n", cbEvent->getAllResponse());
//通知发送线程, 最终识别结果已经返回, 可以调用stop()
pthread_mutex_lock(&(tmpParam->mtxWord));
pthread_cond_signal(&(tmpParam->cvWord));
pthread_mutex_unlock(&(tmpParam->mtxWord));
}
void* pthreadFunction(void* arg) {
int sleepMs = 0;
int ret = 0;
ParamCallBack *cbParam = NULL;
//初始化自定义回调参数,以下两变量仅作为示例表示参数传递,在示例中不起任何作用。
//回调参数在堆中分配之后,请在退出线程前释放。
cbParam = new ParamCallBack();
cbParam->userId = rand() % 100;
strcpy(cbParam->userInfo, "User.");
// 0:从自定义线程参数中获取token,配置文件等参数。
ParamStruct *tst = (ParamStruct *) arg;
if (tst == NULL) {
printf("arg is not valid\n");
delete cbParam;
return NULL;
}
// 打开音频文件,获取数据。
std::ifstream fs;
fs.open(tst->fileName.c_str(), std::ios::binary | std::ios::in);
if (!fs) {
printf("%s isn't exist..\n", tst->fileName.c_str());
return NULL;
}
//1: 创建一句话识别SpeechRecognizerRequest对象。
SpeechRecognizerRequest *request =
NlsClient::getInstance()->createRecognizerRequest();
if (request == NULL) {
printf("createRecognizerRequest failed\n");
delete cbParam;
return NULL;
}
// 设置start()成功回调函数
request->setOnRecognitionStarted(OnRecognitionStarted, cbParam);
// 设置异常识别回调函数
request->setOnTaskFailed(OnRecognitionTaskFailed, cbParam);
// 设置识别通道关闭回调函数
request->setOnChannelClosed(OnRecognitionChannelClosed, cbParam);
// 设置中间结果回调函数
request->setOnRecognitionResultChanged(OnRecognitionResultChanged, cbParam);
// 设置识别结束回调函数
request->setOnRecognitionCompleted(OnRecognitionCompleted, cbParam);
// 设置appKey。必填参数。
request->setAppKey(tst->appkey.c_str());
// 设置账号校验token, 必填参数
request->setToken(tst->token.c_str());
// 设置音频数据编码格式。可选参数,目前支持PCM/OPUS,默认为PCM。
request->setFormat("pcm");
// 设置音频数据采样率。可选参数,目前支持16000/8000。默认为16000。
request->setSampleRate(SAMPLE_RATE);
// 设置是否返回中间识别结果。可选参数,默认false。
request->setIntermediateResult(true);
// 设置是否在后处理中添加标点。可选参数,默认false。
request->setPunctuationPrediction(true);
// 设置是否在后处理中执行ITN。可选参数,默认false。
request->setInverseTextNormalization(true);
// 是否启动语音检测。可选,默认为False。
//request->setEnableVoiceDetection(true);
// 允许的最大开始静音, 可选, 单位是毫秒,
// 超出后服务端将会发送RecognitionCompleted事件, 结束本次识别.
// 注意: 需要先设置enable_voice_detection为true
//request->setMaxStartSilence(800);
// 允许的最大结束静音, 可选, 单位是毫秒,
// 超出后服务端将会发送RecognitionCompleted事件, 结束本次识别.
// 注意: 需要先设置enable_voice_detection为true
//request->setMaxEndSilence(800);
// 定制语言模型id,可选。
//request->setCustomizationId("TestId_123");
// 定制泛热词id,可选。
//request->setVocabularyId("TestId_456");
// 用于传递某些定制化、高级参数设置,参数格式为JSON格式:{"key": "value"}。
//request->setPayloadParam("{\"vad_model\": \"farfield\"}");
struct timespec outtime;
struct timeval now;
// 2:start()为异步操作。成功返回started事件,失败返回TaskFailed事件。
ret = request->start();
if (ret < 0) {
printf("start() failed. may be can not connect server. please check network or firewalld\n");
NlsClient::getInstance()->releaseRecognizerRequest(request); // start()失败,释放request对象。
delete cbParam;
return NULL;
} else {
// 等待started事件返回, 再发送
printf("wait started callback.\n");
// 语音服务器存在来不及处理当前请求, 10s内不返回任何回调的问题,
// 然后在10s后返回一个TaskFailed回调, 所以需要设置一个超时机制.
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + 5;
outtime.tv_nsec = now.tv_usec * 1000;
pthread_mutex_lock(&(cbParam->mtxWord));
if (ETIMEDOUT == pthread_cond_timedwait(&(cbParam->cvWord), &(cbParam->mtxWord), &outtime)) {
printf("start timeout.\n");
pthread_mutex_unlock(&(cbParam->mtxWord));
request->cancel();
NlsClient::getInstance()->releaseRecognizerRequest(request);
delete cbParam;
return NULL;
}
pthread_mutex_unlock(&(cbParam->mtxWord));
}
while (!fs.eof()) {
uint8_t data[FRAME_SIZE] = {0};
fs.read((char *) data, sizeof(uint8_t) * FRAME_SIZE);
size_t nlen = fs.gcount();
if (nlen <= 0) {
continue;
}
// 3: 发送音频数据。sendAudio为异步操作,返回负值表示发送失败,需要停止发送。
ret = request->sendAudio(data, nlen);
if (ret < 0) {
// 发送失败,退出循环数据发送。
printf("send data fail.\n");
break;
}
// 语音数据发送控制:
// 语音数据是实时的,不用sleep控制速率,直接发送即可。
// 语音数据来自文件(也即本示例代码模拟的语音流发送机制),发送时需要控制速率,使单位时间内发送的数据大小接近单位时间原始语音数据存储的大小。
sleepMs = getSendAudioSleepTime(nlen, SAMPLE_RATE, 1); // 根据发送数据大小、采样率、数据压缩比来获取sleep时间。
// 4: 语音数据发送延时控制
usleep(sleepMs * 1000);
} // while
printf("sendAudio done.\n");
//5: 关闭音频文件
fs.close();
//6: 通知云端数据发送结束
//stop()为异步操作。失败返回TaskFailed事件。
ret = request->stop();
if (ret == 0) {
printf("wait closed callback.\n");
// 语音服务器存在来不及处理当前请求, 10s内不返回任何回调的问题,
// 然后在10s后返回一个TaskFailed回调, 错误信息为:
// "Gateway:IDLE_TIMEOUT:Websocket session is idle for too long time, the last directive is 'StopRecognition'!"
// 所以需要设置一个超时机制.
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + 5;
outtime.tv_nsec = now.tv_usec * 1000;
// 等待closed事件后再进行释放
pthread_mutex_lock(&(cbParam->mtxWord));
if (ETIMEDOUT == pthread_cond_timedwait(&(cbParam->cvWord), &(cbParam->mtxWord), &outtime)) {
printf("stop timeout\n");
pthread_mutex_unlock(&(cbParam->mtxWord));
NlsClient::getInstance()->releaseRecognizerRequest(request);
delete cbParam;
return NULL;
}
pthread_mutex_unlock(&(cbParam->mtxWord));
} else {
printf("stop ret is %d\n", ret);
}
//7: 通知SDK释放request。
NlsClient::getInstance()->releaseRecognizerRequest(request);
delete cbParam;
return NULL;
}
// 识别单个音频数据
int speechRecognizerFile(const char* appkey) {
//获取当前系统时间戳,判断token是否过期。
std::time_t curTime = std::time(0);
if (g_expireTime - curTime < 10) {
printf("the token will be expired, please generate new token by AccessKey-ID and AccessKey-Secret.\n");
if (generateToken(g_akId, g_akSecret, &g_token, &g_expireTime) < 0) {
return -1;
}
}
ParamStruct pa;
pa.token = g_token;
pa.appkey = appkey;
pa.fileName = "test0.wav";
// 启动一个工作线程,用于单次识别。
pthread_t pthreadId;
pthread_create(&pthreadId, NULL, &pthreadFunction, (void *)&pa);
pthread_join(pthreadId, NULL);
return 0;
}
//识别多个音频数据
//SDK多线程指一个音频数据源对应一个线程,非一个音频数据对应多个线程。
//示例代码为同时开启2个线程识别2个文件。
//免费用户并发连接不能超过2个。
#define AUDIO_FILE_NUMS 2
#define AUDIO_FILE_NAME_LENGTH 32
int speechRecognizerMultFile(const char* appkey) {
//获取当前系统时间戳判断token是否过期。
std::time_t curTime = std::time(0);
if (g_expireTime - curTime < 10) {
printf("the token will be expired, please generate new token by AccessKey-ID and AccessKey-Secret.\n");
if (generateToken(g_akId, g_akSecret, &g_token, &g_expireTime) < 0) {
return -1;
}
}
char audioFileNames[AUDIO_FILE_NUMS][AUDIO_FILE_NAME_LENGTH] = {
"test0.wav",
"test1.wav"
};
ParamStruct pa[AUDIO_FILE_NUMS];
for (int i = 0; i < AUDIO_FILE_NUMS; i ++) {
pa[i].token = g_token;
pa[i].appkey = appkey;
pa[i].fileName = audioFileNames[i];
}
// 启动2个工作线程,同时识别2个音频文件。
std::vector<pthread_t> pthreadId(AUDIO_FILE_NUMS);
for (int j = 0; j < AUDIO_FILE_NUMS; j++) {
pthread_create(&pthreadId[j], NULL, &pthreadFunction, (void *)&(pa[j]));
}
for (int j = 0; j < AUDIO_FILE_NUMS; j++) {
pthread_join(pthreadId[j], NULL);
}
return 0;
}
int main(int argc, char* argv[]) {
if (argc < 4) {
printf("params is not valid. Usage: ./demo <your appkey> <your AccessKey ID> <your AccessKey Secret>\n");
return -1;
}
std::string appkey = argv[1];
g_akId = argv[2];
g_akSecret = argv[3];
// 根据需要设置SDK输出日志。可选。
// 此处表示SDK日志输出至log-recognizer.txt。
// LogDebug表示输出所有级别日志,支持LogDebug、LogInfo、LogWarning、LogError。
// 400表示单个文件400MB。50表示50个日志文件循环记录。
int ret = NlsClient::getInstance()->setLogConfig(
"log-recognizer", LogDebug, 400, 50);
if (ret < 0) {
printf("set log failed.\n");
return -1;
}
// 私有云部署的情况下可进行直连IP的设置
// 必须在startWorkThread()前调用
//NlsClient::getInstance()->setDirectHost("106.15.83.44");
// 存在部分设备在设置了dns后仍然无法通过SDK的dns获取可用的IP,
// 可调用此接口主动启用系统的getaddrinfo来解决这个问题.
//NlsClient::getInstance()->setUseSysGetAddrInfo(true);
// 启动工作线程, 在创建请求和启动前必须调用此函数, 可理解为对NlsClient的初始化
// 入参为负时, 启动当前系统中可用的核数。
// 200并发以下推荐入参为1, 更高并发入参推荐可看readme。
NlsClient::getInstance()->startWorkThread(1);
// 识别单个音频数据
speechRecognizerFile(appkey.c_str());
// 并发识别多个音频数据
//speechRecognizerMultFile(appkey.c_str());
// 所有工作完成,进程退出前,释放nlsClient。
// 请注意releaseInstance()非线程安全, 需要确认所有请求都停止工作才可释放。
NlsClient::releaseInstance();
return 0;
}