本文介紹如何使用阿里云智能語音服務提供的C++ SDK,包括SDK的安裝方法及SDK代碼示例。
前提條件
下載安裝
SDK下載
您可通過以下兩種方法獲取SDK。
方法一:從GitHub獲取最新源碼,詳細編譯和運行方式可見下文,或查看源碼中的
readme.md
。git clone --depth 1 https://github.com/aliyun/alibabacloud-nls-cpp-sdk
方法二:直接從下文表中選取需要的SDK包進行下載。其中SDK源碼包為SDK原始代碼,需要通過下文編譯方法生成集成所需的庫文件。其他對應平臺的SDK包內含相關庫文件、頭文件,無需編譯。
最新SDK包
平臺
MD5
SDK源碼
054d6f9f3226f689dbc68f8ed970fe3b
Linux x86_64
1ebb39ec10993583a645c0de8eebf654
說明以上Linux-x86_64版本使用gcc8.4.0 _GLIBCXX_USE_CXX11_ABI=0編譯。可用源碼包根據readme.md說明重新編譯。
其中:
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平臺為例,如下表所示。
文件名
描述
speechRecognizerDemo.cpp
一句話識別示例。
speechSynthesizerDemo.cpp
語音合成示例。
speechTranscriberDemo.cpp
實時語音識別示例。
flowingSynthesizerDemo.cpp
流式文本語音合成示例。
fileTransferDemo.cpp
錄音文件識別示例。
resource目錄:SDK源碼中,語音服務范例音頻,可用于功能測試,如下表所示。
文件名
描述
test0.wav
test1.wav
test2.wav
test3.wav
測試音頻(16k采樣頻率、16bit采樣位數的音頻文件)。
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
實時音頻流識別。
flowingSynthesizerRequest.h
流式文本語音合成。
FileTrans.h
錄音文件識別。
lib
:SDK庫文件。readme.md
:SDK說明。release.log
:版本更新說明。version
:版本號。
編譯運行(Linux平臺編譯)
安裝工具的最低版本要求如下:
CMake 3.0
Glibc 2.5
Gcc 4.8.5
在Linux終端運行如下腳本。
進入SDK源碼的根目錄。
生成SDK庫文件和可執行程序:srDemo(一句話識別)、stDemo(實時語音識別)、syDemo(語音合成)、daDemo(語音對話)、fsDemo(流式文本語音合成)。
./scripts/build_linux.sh
查看范例使用方式。
cd build/demo ./fsDemo
關鍵接口
基礎接口
NlsClient:語音處理客戶端,利用該客戶端可以進行一句話識別、實時語音識別和語音合成的語音處理任務。該客戶端為線程安全,建議全局僅創建一個實例。
接口名
啟用版本
功能描述
getInstance
2.x
獲取(創建)
NlsClient
實例。setLogConfig
2.x
設置日志文件與存儲路徑。
setDirectHost
3.x
跳過
DNS
域名解析直接設置服務器IPV4
地址,若調用則需要在startWorkThread
之前。setAddrInFamily
3.1.12
設置套接口地址結構的類型,默認為
AF_INET
僅返回IPV4
相關的地址信息,需要在startWorkThread
之前調用。setUseSysGetAddrInfo
3.1.13
若
libevent
的DNS
無法滿足,無法完成DNS
,可調用此接口切換成系統的接口,需要在startWorkThread
之前調用。calculateUtf8Chars
3.1.14
統計文本內容字符數,需要傳入
UTF-8
編碼的文本內容,其中1個漢字、1個英文字母或1個標點均算作1個字符。setSyncCallTimeout
3.1.17
設置同步調用模式的超時時間(ms), 0則為關閉同步模式,默認0。此模式
start()
后收到服務端結果再return出去,stop()
后收到close()
回調再return
出去。startWorkThread
3.x
啟動工作線程數,默認1即啟動一個線程,若-1則啟動CPU核數的線程數。在高并發的情況下建議選擇-1。可以理解NlsClient實例初始化,必須調用。
getVersion
3.x
獲取SDK版本號。
releaseInstance
2.x
銷毀
NlsClient
對象實例。createFlowingSynthesizerRequest
3.2
創建流式文本語音合成對象,線程安全,支持高并發請求。
releaseFlowingSynthesizerRequest
3.2
銷毀流式文本語音合成對象,需要在當前請求的closed事件后調用。
NlsToken:創建Token對象,用于申請獲取Token ID。申請新Token時需要先獲取有效時間戳,若超過有效時間則再申請。若在有效時間內多次申請Token會導致Token ID錯誤而無法使用。
接口名
功能描述
setAccessKeyId
設置阿里云賬號AccessKey ID。
setKeySecret
設置阿里云賬號AccessKey Secret。
setDomain
設置域名,非必填。
setServerVersion
設置API版本,非必填。
setServerResourcePath
設置服務路徑,非必填。
setRegionId
設置服務的確ID,非必填。
setAction
設置功能,非必填。
applyNlsToken
申請獲取Token ID。
getToken
獲取Token ID。
getExpireTime
獲取Token有效期時間戳(秒)。
getErrorMsg
獲得錯誤信息
NlsEvent:事件對象,您可以從中獲取Request狀態碼、云端返回結果、失敗信息等。
接口名
功能描述
getStatusCode
獲取狀態碼,正常情況為0或者20000000,失敗時對應失敗的錯誤碼。
getErrorMessage
在
TaskFailed
回調中,獲取NlsRequest
操作過程中出現失敗時的錯誤信息。getTaskId
獲取任務的
TaskId
。getBinaryData
獲取云端返回的二進制數據。
getAllResponse
獲取云端返回的結果。
流式文本合成接口
接口說明以flowingSynthesizerRequest.h
內容為準。
接口名 | 啟用版本 | 功能描述 |
setOnSynthesisStarted | 3.2 | 設置流式文本語音合成啟動回調函數。 |
setOnSynthesisCompleted | 3.2 | 設置語音合成結束回調函數。 |
setOnChannelClosed | 2.x | 設置通道關閉回調函數。 |
setOnTaskFailed | 2.x | 設置錯誤回調函數。 |
setOnSentenceBegin | 3.2 | 服務端檢測到了一句話的開始的回調函數。 |
setOnSentenceEnd | 3.2 | 服務端檢測到了一句話的結束,返回該句的全量時間戳的回調函數。 |
setOnBinaryDataReceived | 2.x | 設置語音合成二進制音頻數據接收回調函數。 |
setOnSentenceSynthesis | 3.2 | 增量返回語音合成的結果,包含最新的音頻和時間戳、句內全量、句間增量的回調函數。 |
setOnMessage | 3.1.16 | 設置服務端 |
setAppKey | 2.x | 設置AppKey。 |
setToken | 2.x | 口令認證。所有的請求都必須通過 |
setUrl | 2.x | 設置服務URL地址。非必填。 |
sendText | 3.2 | 在同一個流式TTS會話中,單次合成不超過5000字,總計不超過10萬字,其中1個漢字、1個英文字母、1個標點或1個句子中間空格均算作1個字符。 |
setVoice | 2.x | 發音人voice設置。 |
setVolume | 2.x | 音量volume設置。 |
setFormat | 2.x | 設置音頻數據編碼格式(默認是PCM,支持的格式:PCM、WAV、MP3)。 |
setSampleRate | 2.x | 音頻采樣率設置。 |
setSpeechRate | 2.x | 語速設置。 |
setPitchRate | 2.x | 語調設置。 |
setEnableSubtitle | 2.x | 是否開啟字幕功能。 |
setPayloadParam | 2.x | 參數設置,入參為JSON格式字符串。 |
setTimeout | 2.x | 設置鏈接超時時間,默認5000ms。 |
setContextParam | 2.x | 設置用戶自定義參數,入參為JSON格式字符串。 |
AppendHttpHeaderParam | 2.x | 設置用戶自定義ws階段 |
setSendTimeout | 3.1.14 | 設置發送超時時間,默認5000ms。 |
setEnableOnMessage | 3.1.16 | 設置開啟服務器返回消息回調。 |
getTaskId | 3.1.17 | 獲得當前請求的 |
start | 2.x | 啟動 |
stop | 3.2 | 結束合成任務,后續需要等待合成完畢。 |
cancel | 2.x | 不會與服務端確認關閉,直接關閉語音合成過程。 |
代碼示例
示例中將合成的音頻保存在文件中,如果您需要播放音頻且對實時性要求較高,建議使用流式播放,即邊接收語音數據邊播放,減少延時,而無需等待合成結束后再處理語音流。
完整示例,參見SDK壓縮包中demo目錄的flowingSynthesizerRequest.cpp文件。
調用接口前,需配置環境變量,通過環境變量讀取訪問憑證。智能語音交互的
AccessKey ID
、AccessKey Secret
和AppKey
的環境變量名:NLS_AK_ENV
、NLS_SK_ENV
、NLS_APPKEY_ENV
。
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctime>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "nlsClient.h"
#include "nlsEvent.h"
#include "nlsToken.h"
#include "flowingSynthesizerRequest.h"
#define SAMPLE_RATE_16K 16000
#define DEFAULT_STRING_LEN 512
#define AUDIO_TEXT_LENGTH 2048
/**
* 全局維護一個服務鑒權token和其對應的有效期時間戳,
* 每次調用服務之前,首先判斷token是否已經過期,
* 如果已經過期,則根據AccessKey ID和AccessKey
* Secret重新生成一個token,并更新這個全局的token和其有效期時間戳。
*
* 注意:不要每次調用服務之前都重新生成新token,只需在token即將過期時重新生成即可。所有的服務并發可共用一個token。
*/
// 自定義線程參數
struct ParamStruct {
char text[AUDIO_TEXT_LENGTH];
char token[DEFAULT_STRING_LEN];
char appkey[DEFAULT_STRING_LEN];
char url[DEFAULT_STRING_LEN];
pthread_mutex_t mtx;
};
// 自定義事件回調參數
struct ParamCallBack {
public:
explicit ParamCallBack(ParamStruct* param) {
tParam = param;
pthread_mutex_init(&mtxWord, NULL);
pthread_cond_init(&cvWord, NULL);
};
~ParamCallBack() {
tParam = NULL;
pthread_mutex_destroy(&mtxWord);
pthread_cond_destroy(&cvWord);
};
pthread_mutex_t mtxWord;
pthread_cond_t cvWord;
ParamStruct* tParam;
};
std::string g_appkey = "";
std::string g_akId = "";
std::string g_akSecret = "";
std::string g_token = "";
std::string g_url = "";
std::string g_voice = "xiaoyun";
int g_threads = 1;
bool g_save_audio = true;
std::string g_text = "";
std::string g_format = "wav";
long g_expireTime = -1;
static int sample_rate = SAMPLE_RATE_16K;
static bool enableSubtitle = false;
std::vector<std::string> splitString(
const std::string& str, const std::vector<std::string>& delimiters) {
std::vector<std::string> result;
size_t startPos = 0;
// 查找字符串中的每個分隔符
while (startPos < str.length()) {
size_t minPos = std::string::npos;
size_t delimiterLength = 0;
for (std::vector<std::string>::const_iterator it = delimiters.begin();
it != delimiters.end(); ++it) {
std::size_t position = str.find(*it, startPos);
// 查找最近的分隔符
if (position != std::string::npos &&
(minPos == std::string::npos || position < minPos)) {
minPos = position;
delimiterLength = it->size();
}
}
// 如果找到分隔符,則提取前面的字符串
if (minPos != std::string::npos) {
result.push_back(str.substr(startPos, minPos - startPos));
startPos = minPos + delimiterLength;
} else {
// 沒有更多分隔符,剩下的全部是一個部分
result.push_back(str.substr(startPos));
break;
}
}
return result;
}
/**
* 根據AccessKey ID和AccessKey Secret重新生成一個token,并獲取其有效期時間戳
*/
int generateToken(std::string akId, std::string akSecret, std::string* token,
long* expireTime) {
AlibabaNlsCommon::NlsToken nlsTokenRequest;
nlsTokenRequest.setAccessKeyId(akId);
nlsTokenRequest.setKeySecret(akSecret);
int retCode = nlsTokenRequest.applyNlsToken();
/*獲取失敗原因*/
if (retCode < 0) {
std::cout << "Failed error code: " << retCode
<< " error msg: " << nlsTokenRequest.getErrorMsg() << std::endl;
return retCode;
}
*token = nlsTokenRequest.getToken();
*expireTime = nlsTokenRequest.getExpireTime();
return 0;
}
void OnSynthesisStarted(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
std::cout << "OnSynthesisStarted:"
<< " status code: " << cbEvent->getStatusCode()
<< " task id: " << cbEvent->getTaskId()
<< " all response:" << cbEvent->getAllResponse() << std::endl;
}
/**
* @brief sdk在接收到云端返回合成結束消息時, sdk內部線程上報Completed事件
* @note 上報Completed事件之后,SDK內部會關閉識別連接通道.
* @param cbEvent 回調事件結構, 詳見nlsEvent.h
* @param cbParam 回調自定義參數,默認為NULL, 可以根據需求自定義參數
* @return
*/
void OnSynthesisCompleted(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
std::cout
<< "OnSynthesisCompleted: "
<< ", status code: "
<< cbEvent
->getStatusCode() // 獲取消息的狀態碼,成功為0或者20000000,失敗時對應失敗的錯誤碼
<< ", task id: "
<< cbEvent->getTaskId() // 當前任務的task id,方便定位問題,建議輸出
<< std::endl;
std::cout << "OnSynthesisCompleted: All response:"
<< cbEvent->getAllResponse()
<< std::endl; // 獲取服務端返回的全部信息
}
/**
* @brief 合成過程發生異常時, sdk內部線程上報TaskFailed事件
* @note 上報TaskFailed事件之后,SDK內部會關閉識別連接通道.
* @param cbEvent 回調事件結構, 詳見nlsEvent.h
* @param cbParam 回調自定義參數,默認為NULL, 可以根據需求自定義參數
* @return
*/
void OnSynthesisTaskFailed(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
FILE* failed_stream = fopen("synthesisTaskFailed.log", "a+");
if (failed_stream) {
char outbuf[1024] = {0};
snprintf(outbuf, sizeof(outbuf),
"OnSynthesisTaskFailed status code:%d task id:%s error mesg:%s\n",
cbEvent->getStatusCode(), cbEvent->getTaskId(),
cbEvent->getErrorMessage());
std::cout << outbuf << std::endl;
fwrite(outbuf, strlen(outbuf), 1, failed_stream);
fclose(failed_stream);
}
}
/**
* @brief 識別結束或發生異常時,會關閉連接通道,
* sdk內部線程上報ChannelCloseed事件
* @param cbEvent 回調事件結構, 詳見nlsEvent.h
* @param cbParam 回調自定義參數,默認為NULL, 可以根據需求自定義參數
* @return
*/
void OnSynthesisChannelClosed(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
ParamCallBack* tmpParam = static_cast<ParamCallBack*>(cbParam);
if (tmpParam) {
std::cout << "OnSynthesisChannelClosed: "
<< ", All response: " << cbEvent->getAllResponse()
<< std::endl; // 獲取服務端返回的全部信息
}
}
/**
* @brief 文本上報服務端之后, 收到服務端返回的二進制音頻數據,
* SDK內部線程通過BinaryDataRecved事件上報給用戶
* @param cbEvent 回調事件結構, 詳見nlsEvent.h
* @param cbParam 回調自定義參數,默認為NULL, 可以根據需求自定義參數
* @return
* @notice 此處切記不可做block操作,只可做音頻數據轉存. 若在此回調中做過多操作,
* 會阻塞后續的數據回調和completed事件回調.
*/
void OnBinaryDataRecved(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
std::vector<unsigned char> data =
cbEvent->getBinaryData(); // getBinaryData() 獲取文本合成的二進制音頻數據
std::cout
<< " OnBinaryDataRecved: status code: "
<< cbEvent
->getStatusCode() // 獲取消息的狀態碼,成功為0或者20000000,失敗時對應失敗的錯誤碼
<< ", taskId: "
<< cbEvent->getTaskId() // 當前任務的task id,方便定位問題,建議輸出
<< ", data size: " << data.size() // 數據的大小
<< std::endl;
if (g_save_audio && data.size() > 0) {
// 以追加形式將二進制音頻數據寫入文件
std::string dir = "./tts_audio";
if (access(dir.c_str(), 0) == -1) {
mkdir(dir.c_str(), S_IRWXU);
}
char file_name[256] = {0};
snprintf(file_name, 256, "%s/%s.%s", dir.c_str(), cbEvent->getTaskId(),
g_format.c_str());
FILE* tts_stream = fopen(file_name, "a+");
if (tts_stream) {
fwrite((char*)&data[0], data.size(), 1, tts_stream);
fclose(tts_stream);
}
}
}
void OnSentenceBegin(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
std::cout
<< "OnSentenceBegin "
<< "Response: "
<< cbEvent
->getAllResponse() // 獲取消息的狀態碼,成功為0或者20000000,失敗時對應失敗的錯誤碼
<< std::endl;
}
void OnSentenceEnd(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
std::cout
<< "OnSentenceEnd "
<< "Response: "
<< cbEvent
->getAllResponse() // 獲取消息的狀態碼,成功為0或者20000000,失敗時對應失敗的錯誤碼
<< std::endl;
}
/**
* @brief 返回 tts 文本對應的日志信息,增量返回對應的字幕信息
* @param cbEvent 回調事件結構, 詳見nlsEvent.h
* @param cbParam 回調自定義參數,默認為NULL, 可以根據需求自定義參數
* @return
*/
void OnSentenceSynthesis(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
std::cout
<< "OnSentenceSynthesis "
<< "Response: "
<< cbEvent
->getAllResponse() // 獲取消息的狀態碼,成功為0或者20000000,失敗時對應失敗的錯誤碼
<< std::endl;
}
/**
* @brief 服務端返回的所有信息會通過此回調反饋,
* @param cbEvent 回調事件結構, 詳見nlsEvent.h
* @param cbParam 回調自定義參數,默認為NULL, 可以根據需求自定義參數
* @return
*/
void onMessage(AlibabaNls::NlsEvent* cbEvent, void* cbParam) {
std::cout << "onMessage: All response:" << cbEvent->getAllResponse()
<< std::endl;
}
/**
* @brief 短鏈接模式下工作線程
* 以 createFlowingSynthesizerRequest <----|
* | |
* request->start() |
* | |
* request->sendText() |
* | |
* request->stop() |
* | |
* 收到OnSynthesisChannelClosed回調 |
* | |
* releaseFlowingSynthesizerRequest(request) --|
* 進行循環。
*/
void* pthreadFunc(void* arg) {
int testCount = 0; // 運行次數計數,用于超過設置的loop次數后退出
bool timedwait_flag = false;
// 從自定義線程參數中獲取token, 配置文件等參數.
ParamStruct* tst = static_cast<ParamStruct*>(arg);
if (tst == NULL) {
std::cout << "arg is not valid." << std::endl;
return NULL;
}
pthread_mutex_init(&(tst->mtx), NULL);
// 初始化自定義回調參數
ParamCallBack cbParam(tst);
/*
* 1. 創建流式文本語音合成FlowingSynthesizerRequest對象.
*
* 流式文本語音合成文檔詳見:
* http://bestwisewords.com/zh/isi/developer-reference/streaming-text-to-speech-synthesis/
*/
AlibabaNls::FlowingSynthesizerRequest* request =
AlibabaNls::NlsClient::getInstance()->createFlowingSynthesizerRequest();
if (request == NULL) {
std::cout << "createFlowingSynthesizerRequest failed." << std::endl;
return NULL;
}
/*
* 2. 設置用于接收結果的回調
*/
// 設置音頻合成可以開始的回調函數
request->setOnSynthesisStarted(OnSynthesisStarted, &cbParam);
// 設置音頻合成結束回調函數
request->setOnSynthesisCompleted(OnSynthesisCompleted, &cbParam);
// 設置音頻合成通道關閉回調函數
request->setOnChannelClosed(OnSynthesisChannelClosed, &cbParam);
// 設置異常失敗回調函數
request->setOnTaskFailed(OnSynthesisTaskFailed, &cbParam);
// 設置文本音頻數據接收回調函數
request->setOnBinaryDataReceived(OnBinaryDataRecved, &cbParam);
// 設置字幕信息
request->setOnSentenceSynthesis(OnSentenceSynthesis, &cbParam);
// 一句話開始
request->setOnSentenceBegin(OnSentenceBegin, &cbParam);
// 一句話結束
request->setOnSentenceEnd(OnSentenceEnd, &cbParam);
// 設置所有服務端返回信息回調函數
// request->setOnMessage(onMessage, &cbParam);
// 開啟所有服務端返回信息回調函數, 其他回調(除了OnBinaryDataRecved)失效
// request->setEnableOnMessage(true);
/*
* 3. 設置request的相關參數
*/
// 發音人, 包含"xiaoyun", "ruoxi", "xiaogang"等. 可選參數, 默認是xiaoyun
request->setVoice(g_voice.c_str());
// 音量, 范圍是0~100, 可選參數, 默認50
request->setVolume(50);
// 音頻編碼格式, 可選參數, 默認是wav. 支持的格式pcm, wav, mp3
request->setFormat("wav");
// 音頻采樣率, 包含8000, 16000. 可選參數, 默認是16000
request->setSampleRate(sample_rate);
// 語速, 范圍是-500~500, 可選參數, 默認是0
request->setSpeechRate(0);
// 語調, 范圍是-500~500, 可選參數, 默認是0
request->setPitchRate(0);
// 開啟字幕
request->setEnableSubtitle(enableSubtitle);
// 設置AppKey, 必填參數, 請參照官網申請
if (strlen(tst->appkey) > 0) {
request->setAppKey(tst->appkey);
}
// 設置賬號校驗token, 必填參數
if (strlen(tst->token) > 0) {
request->setToken(tst->token);
}
if (strlen(tst->url) > 0) {
request->setUrl(tst->url);
}
// 設置鏈接超時500ms
// request->setTimeout(500);
// 獲取返回文本的編碼格式
// const char* output_format = request->getOutputFormat();
// std::cout << "text format: " << output_format << std::endl;
/*
* 4.
* start()為異步操作。成功則開始返回BinaryRecv事件。失敗返回TaskFailed事件。
*/
std::cout << "start -> pid " << pthread_self() << std::endl;
struct timespec outtime;
struct timeval now;
int ret = request->start();
testCount++;
if (ret < 0) {
std::cout << "start failed. pid:" << pthread_self() << std::endl;
const char* request_info = request->dumpAllInfo();
if (request_info) {
std::cout << " all info: " << request_info << std::endl;
}
AlibabaNls::NlsClient::getInstance()->releaseFlowingSynthesizerRequest(
request); // start()失敗,釋放request對象
return NULL;
} else {
std::cout << "start success. pid " << pthread_self() << std::endl;
/*
* 等待started事件返回表示start()成功, 然后再發送音頻數據。
* 語音服務器存在來不及處理當前請求的情況, 10s內不返回任何回調的問題,
* 然后在10s后返回一個TaskFailed回調, 所以需要設置一個超時機制。
*/
std::cout << "wait started callback." << std::endl;
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + 10;
outtime.tv_nsec = now.tv_usec * 1000;
pthread_mutex_lock(&(cbParam.mtxWord));
if (ETIMEDOUT == pthread_cond_timedwait(&(cbParam.cvWord),
&(cbParam.mtxWord), &outtime)) {
std::cout << "start timeout" << std::endl;
timedwait_flag = true;
pthread_mutex_unlock(&(cbParam.mtxWord));
// start()調用超時,cancel()取消當次請求。
request->cancel();
return NULL;
}
pthread_mutex_unlock(&(cbParam.mtxWord));
}
/*
* 5. 模擬大模型流式返回文本結果,進行逐個語音合成
*/
std::string text_str(tst->text);
if (!text_str.empty()) {
const char* delims[] = {"。", "!", ";", "?", "\n"};
std::vector<std::string> delimiters(
delims, delims + sizeof(delims) / sizeof(delims[0]));
std::vector<std::string> sentences = splitString(text_str, delimiters);
for (std::vector<std::string>::const_iterator it = sentences.begin();
it != sentences.end(); ++it) {
std::cout << "sendText: " << *it << std::endl;
ret = request->sendText(it->c_str());
if (ret < 0) {
break;
}
usleep(500 * 1000);
} // for
if (ret < 0) {
std::cout << "sendText failed. pid:" << pthread_self() << std::endl;
const char* request_info = request->dumpAllInfo();
if (request_info) {
std::cout << " all info: " << request_info << std::endl;
}
AlibabaNls::NlsClient::getInstance()->releaseFlowingSynthesizerRequest(
request); // start()失敗,釋放request對象
return NULL;
}
}
/*
* 6. start成功,開始等待接收完所有合成數據。
* stop()為無意義接口,調用與否都會跑完全程.
* cancel()立即停止工作, 且不會有回調返回, 失敗返回TaskFailed事件。
*/
// ret = request->cancel();
ret = request->stop();
/*
* 開始等待接收完所有合成數據。
*/
if (ret == 0) {
/*
* 等待started事件返回表示start()成功, 然后再發送音頻數據。
* 語音服務器存在來不及處理當前請求的情況, 10s內不返回任何回調的問題,
* 然后在10s后返回一個TaskFailed回調, 所以需要設置一個超時機制。
*/
// 等待closed事件后再進行釋放, 否則會出現崩潰
std::cout << "wait closed callback." << std::endl;
/*
* 語音服務器存在來不及處理當前請求, 10s內不返回任何回調的問題,
* 然后在10s后返回一個TaskFailed回調, 錯誤信息為:
* "Gateway:IDLE_TIMEOUT:Websocket session is idle for too long time,
* the last directive is 'XXXX'!" 所以需要設置一個超時機制.
*/
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + 30;
outtime.tv_nsec = now.tv_usec * 1000;
// 等待closed事件后再進行釋放, 否則會出現崩潰
pthread_mutex_lock(&(cbParam.mtxWord));
if (ETIMEDOUT == pthread_cond_timedwait(&(cbParam.cvWord),
&(cbParam.mtxWord), &outtime)) {
std::cout << "stop timeout" << std::endl;
pthread_mutex_unlock(&(cbParam.mtxWord));
return NULL;
}
pthread_mutex_unlock(&(cbParam.mtxWord));
} else {
std::cout << "ret is " << ret << ", pid " << pthread_self() << std::endl;
}
gettimeofday(&now, NULL);
std::cout << "current request task_id:" << request->getTaskId() << std::endl;
std::cout << "stop finished. pid " << pthread_self() << " tv: " << now.tv_sec
<< std::endl;
/*
* 7. 完成所有工作后釋放當前請求。
* 請在closed事件(確定完成所有工作)后再釋放, 否則容易破壞內部狀態機,
* 會強制卸載正在運行的請求。
*/
const char* request_info = request->dumpAllInfo();
if (request_info) {
std::cout << " all info: " << request_info << std::endl;
}
AlibabaNls::NlsClient::getInstance()->releaseFlowingSynthesizerRequest(
request);
std::cout << "release Synthesizer success. pid " << pthread_self()
<< std::endl;
pthread_mutex_destroy(&(tst->mtx));
return NULL;
}
/**
* 合成多個文本數據;
* sdk多線程指一個文本數據對應一個線程, 非一個文本數據對應多個線程.
* 示例代碼為同時開啟4個線程合成4個文件;
* 免費用戶并發連接不能超過2個;
*/
#define AUDIO_TEXT_NUMS 4
#define AUDIO_FILE_NAME_LENGTH 32
int flowingSynthesizerMultFile(const char* appkey, int threads) {
/**
* 獲取當前系統時間戳,判斷token是否過期
*/
std::time_t curTime = std::time(0);
if (g_token.empty()) {
if (g_expireTime - curTime < 10) {
std::cout << "the token will be expired, please generate new token by "
"AccessKey-ID and AccessKey-Secret."
<< std::endl;
int ret = generateToken(g_akId, g_akSecret, &g_token, &g_expireTime);
if (ret < 0) {
std::cout << "generate token failed" << std::endl;
return -1;
} else {
if (g_token.empty() || g_expireTime < 0) {
std::cout << "generate empty token" << std::endl;
return -2;
}
std::cout << "token: " << g_token << std::endl;
}
}
}
/* 不要超過AUDIO_TEXT_LENGTH */
const char texts[AUDIO_TEXT_LENGTH] = {
"唧唧復唧唧,木蘭當戶織。不聞機杼聲,唯聞女嘆息。問女何所思,問女何所憶。"
"女亦無所思,女亦無所憶。昨夜見軍帖,可汗大點兵。軍書十二卷,卷卷有爺名。"
"阿爺無大兒,木蘭無長兄。愿為市鞍馬,從此替爺征。東市買駿馬,西市買鞍韉,"
"南市買轡頭,北市買長鞭。旦辭爺娘去,暮宿黃河邊。不聞爺娘喚女聲,但聞黃河"
"流水鳴濺濺。旦辭黃河去,暮至黑山頭。不聞爺娘喚女聲,但聞燕山胡騎鳴啾啾。"
" 萬里赴戎機,關山度若飛。朔氣傳金柝,寒光照鐵衣。將軍百戰死,壯士十年歸"
"。 "
"歸來見天子,天子坐明堂。策勛十二轉,賞賜百千強。可汗問所欲,木蘭不用尚書"
"郎,愿馳千里足,送兒還故鄉。 "
"爺娘聞女來,出郭相扶將;阿姊聞妹來,當戶理紅妝;小弟聞姊來,磨刀霍霍向豬"
"羊。開我東閣門,坐我西閣床。脫我戰時袍,著我舊時裳。當窗理云鬢,對鏡帖花"
"黃。出門看火伴,火伴皆驚忙:同行十二年,不知木蘭是女郎。 "
"雄兔腳撲朔,雌兔眼迷離;雙兔傍地走,安能辨我是雄雌?"};
ParamStruct pa[threads];
for (int i = 0; i < threads; i++) {
memset(pa[i].token, 0, DEFAULT_STRING_LEN);
memcpy(pa[i].token, g_token.c_str(), g_token.length());
memset(pa[i].appkey, 0, DEFAULT_STRING_LEN);
memcpy(pa[i].appkey, appkey, strlen(appkey));
memset(pa[i].text, 0, AUDIO_TEXT_LENGTH);
if (g_text.empty()) {
memcpy(pa[i].text, texts, strlen(texts));
} else {
memcpy(pa[i].text, g_text.data(), strlen(g_text.data()));
}
memset(pa[i].url, 0, DEFAULT_STRING_LEN);
if (!g_url.empty()) {
memcpy(pa[i].url, g_url.c_str(), g_url.length());
}
}
std::vector<pthread_t> pthreadId(threads);
// 啟動四個工作線程, 同時識別四個音頻文件
for (int j = 0; j < threads; j++) {
pthread_create(&pthreadId[j], NULL, &pthreadFunc, (void*)&(pa[j]));
}
std::cout << "start pthread_join..." << std::endl;
for (int j = 0; j < threads; j++) {
pthread_join(pthreadId[j], NULL);
}
std::cout << "flowingSynthesizerMultFile exit..." << std::endl;
return 0;
}
int main(int argc, char* argv[]) {
std::string g_appkey = getenv("NLS_APPKEY_ENV");
g_akId = getenv("NLS_AK_ENV");
g_akSecret = getenv("NLS_SK_ENV");
std::cout << " appKey: " << g_appkey << std::endl;
std::cout << " akId: " << g_akId << std::endl;
std::cout << " akSecret: " << g_akSecret << std::endl;
std::cout << " voice: " << g_voice << std::endl;
std::cout << "\n" << std::endl;
int ret = AlibabaNls::NlsClient::getInstance()->setLogConfig(
"log-flowingSynthesizer", AlibabaNls::LogDebug, 400, 50, NULL);
if (ret < 0) {
std::cout << "set log failed." << std::endl;
return -1;
}
// 設置運行環境需要的套接口地址類型, 默認為AF_INET
// 必須在startWorkThread()前調用
// AlibabaNls::NlsClient::getInstance()->setAddrInFamily("AF_INET");
// 私有云部署的情況下進行直連IP的設置
// 必須在startWorkThread()前調用
// AlibabaNls::NlsClient::getInstance()->setDirectHost("xxx.xxx.xxx.xxx");
std::cout << "startWorkThread begin... " << std::endl;
// 啟動工作線程, 在創建請求和啟動前必須調用此函數
// 入參為負時, 啟動當前系統中可用的核數
// 高并發的情況下推薦4, 單請求的情況推薦為1
// 若高并發CPU占用率較高, 則可填-1啟用所有CPU核
AlibabaNls::NlsClient::getInstance()->startWorkThread(1);
std::cout << "startWorkThread finish" << std::endl;
// 合成多個文本
ret = flowingSynthesizerMultFile(g_appkey.c_str(), g_threads);
if (ret) {
std::cout << "flowingSynthesizerMultFile failed." << std::endl;
AlibabaNls::NlsClient::releaseInstance();
return -2;
}
// 所有工作完成,進程退出前,釋放nlsClient.
// 請注意, releaseInstance()非線程安全.
std::cout << "releaseInstance -> " << std::endl;
AlibabaNls::NlsClient::releaseInstance();
std::cout << "releaseInstance done." << std::endl;
return 0;
}
狀態碼
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編碼器創建失敗。 | 建議重新初始化。 |
-104 | InvalidEncoderType | encoder類型無效。 | 編譯時可能關閉OPUS但是又使用,或請檢查 |
-150 | EventClientEmpty | 主工作線程空指針, 已釋放。 | 建議重新初始化,即 |
-151 | SelectThreadFailed | 工作線程選擇失敗, 未初始化。 | 建議重新初始化,即 |
-160 | StartCommandFailed | 發送start命令失敗。 | 建議重新嘗試 |
-161 | InvokeStartFailed | 請求狀態機不對, 導致start失敗。 | 請檢查當前請求是否未創建或者已經完成。 |
-162 | InvokeSendAudioFailed | 請求狀態機不對, 導致sendAudio失敗。 | 請檢查當前請求是否已經啟動(即收到started事件回調)或者已經完成。 |
-163 | InvalidOpusFrameSize | opus幀長無效, 默認為640字節。 | OPU編碼模式下, |
-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事件中消息為空。 |
|
-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 | EvutilSocketFalied | 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無效。 | 請檢查輸入的Region Id是否為空。 |
-500 | InvalidFileLink | 無效的錄音文件鏈接 | 錄音文件轉寫文件鏈接為空。 |
-501 | ErrorStatusCode | 錯誤的狀態碼。 | 錄音文件轉寫返回錯誤,詳見錯誤碼。 |
-502 | IconvOpenFailed | 申請轉換描述失敗。 | UTF8與GBK轉換失敗。 |
-503 | IconvFailed | 編碼轉換失敗。 | UTF8與GBK轉換失敗。 |
-504 | ClientRequestFaild | 賬號客戶端請求失敗。 | 錄音文件轉寫返回失敗。 |
-999 | NlsMaxErrorCode | 無 | 無 |
其他狀態碼
狀態碼 | 狀態消息 | 原因 | 解決方案 |
10000001 |
|
| 建議重新初始化。 |
10000002 |
|
| 建議重新嘗試。 |
| |||
10000003 |
| 系統錯誤 | 根據系統反饋的錯誤信息進行處理。 |
10000004 |
|
| 傳入的URL為空, 請重新填寫正確URL。 |
10000005 |
|
| 傳入的URL格式錯誤, 請重新填寫正確URL。 |
10000007 |
|
| JSON格式異常, 請通過日志查看具體的錯誤點。 |
10000008 |
|
| 聯網失敗,請檢查本機DNS解析和URL是否有效。 |
10000009 |
|
| 與云端連接失敗,請檢查網絡后,重試。 |
10000010 |
| 內存不足 | 請檢查內存是否充足。 |
10000015 |
|
| 聯網失敗,請檢查本機DNS解析和URL是否有效。 |
10000100 |
|
| 鏈接被拒,請檢查賬號特別是token是否過期。 |
10000101 |
|
| libevent發送event超時,請檢查回調中是否有耗時任務,或并發過大導致無法及>時處理事件。 |
10000102 |
|
| libevent接收event超時,請檢查回調中是否有耗時任務,或并發過大導致無法及>時處理事件。 |
10000103 |
|
| 未知的libevent事件,建議重新嘗試。 |
10000104 |
|
| 鏈接正在進行中,建議重新嘗試。 |
10000105 |
|
| pipe處理不過來,建議重新嘗試。 |
10000110 |
|
| 請更新Token。 |
10000111 |
|
| 請檢查Token的有效性。 |
10000112 |
|
| 此發音人無權使用。 |
10000113 |
|
| 請檢查賬號是否有權限,或并發是否在限度內。 |
10000120 |
|
| UTF-8轉碼失敗,常為系統問題,建議重新嘗試。 |
20000000 |
| 成功 |
服務端響應狀態碼
關于服務狀態碼,請參見服務狀態碼。