本文介绍如何使用听悟开发套件提供的实时语音推流C++ SDK,包括SDK的安装方法及SDK代码示例。
前提条件
在使用语音推流C++ SDK之前,请先阅读开发参考。
源码下载
下载tingwu-client-demo-cpp.zip,该文件为听悟demo源码工程,需要参考下文编译方式编译运行。
编译运行
听悟源码工程目前支持在Linux平台编译,编译运行步骤如下:
安装编译工具。
安装依赖项。
在源码根目录下,进入Linux终端,运行如下脚本,运行听悟demo。
./build_linux.sh cd build/ ./tingwu-client
示例代码
说明
示例中使用的音频文件为16000Hz采样率。
示例为读取本地文件模拟单路会议,如场景为线上多路语音识别会议,可参考实时记录语音推流步骤2中protobuf的MultiAudioFrame数据结构构造每帧语音流。
完整示例工程,参见demo压缩包中源码。
//AsrAssistant类实现
#include "AsrAssistant.h"
#include <algorithm>
#include <iostream>
#include <sstream>
#include <sys/time.h>
#include <uuid/uuid.h>
#include "HttpPopClient.h"
#include "logger.h"
#include "msg.pb.h"
#include "nlsClient.h"
#include "nlsEvent.h"
#include "json/json.h"
#include "speechTranscriberRequest.h"
using namespace AlibabaNls;
using namespace std;
namespace {
#define OPERATION_TIMEOUT_S 5
// 自定义线程参数。
struct ParamStruct {
std::string fileName;
std::string token;
std::string appkey;
};
//@brief 调用start(),成功与云端建立连接,SDK内部线程上报started事件。
//@param cbEvent 回调事件结构,详见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL。可以根据需求自定义参数。
void onTranscriptionStarted(NlsEvent *cbEvent, void *cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
// 演示如何打印/使用用户自定义参数示例。
printf("onTranscriptionStarted: %d\n", tmpParam->userId);
// 当前任务的task id,方便定位问题。
printf("onTranscriptionStarted: status code=%d, task id=%s\n", cbEvent->getStatusCode(), cbEvent->getTaskId());
// 获取服务端返回的全部信息
printf("onTranscriptionStarted: all response=%s\n", cbEvent->getAllResponse());
// 通知发送线程start()成功, 可以继续发送数据
pthread_mutex_lock(&(tmpParam->mtxWord));
pthread_cond_signal(&(tmpParam->cvWord));
pthread_mutex_unlock(&(tmpParam->mtxWord));
}
//@brief 服务端检测到了一句话的开始,SDK内部线程上报SentenceBegin事件。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL,可以根据需求自定义参数。
void onSentenceBegin(NlsEvent *cbEvent, void *cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
// 演示如何打印/使用用户自定义参数示例。
printf("onSentenceBegin: %d\n", tmpParam->userId);
printf("onSentenceBegin: status code=%d, task id=%s, index=%d, time=%d\n", cbEvent->getStatusCode(),
cbEvent->getTaskId(),
cbEvent->getSentenceIndex(), //句子编号,从1开始递增。
cbEvent->getSentenceTime() //当前已处理的音频时长,单位:毫秒。
);
// 获取服务端返回的全部信息
printf("onSentenceBegin: all response=%s\n", cbEvent->getAllResponse());
}
//@brief 服务端检测到了一句话结束,SDK内部线程上报SentenceEnd事件。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL,可以根据需求自定义参数。
void onSentenceEnd(NlsEvent *cbEvent, void *cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
// 演示如何打印/使用用户自定义参数示例。
printf("onSentenceEnd: %d\n", tmpParam->userId);
printf("onSentenceEnd: status code=%d, task id=%s, index=%d, time=%d, begin_time=%d, result=%s\n",
cbEvent->getStatusCode(), cbEvent->getTaskId(),
cbEvent->getSentenceIndex(), //句子编号,从1开始递增。
cbEvent->getSentenceTime(), //当前已处理的音频时长,单位:毫秒。
cbEvent->getSentenceBeginTime(), // 对应的SentenceBegin事件的时间。
cbEvent->getResult() // 当前句子的完整识别结果。
);
// 获取服务端返回的全部信息
printf("onSentenceEnd: all response=%s\n", cbEvent->getAllResponse());
}
//@brief 识别结果发生了变化,SDK在接收到云端返回的最新结果时,其内部线程上报ResultChanged事件。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL,可以根据需求自定义参数。
void onTranscriptionResultChanged(NlsEvent *cbEvent, void *cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
// 演示如何打印/使用用户自定义参数示例。
printf("onTranscriptionResultChanged: %d\n", tmpParam->userId);
printf("onTranscriptionResultChanged: status code=%d, task id=%s, index=%d, time=%d, result=%s\n",
cbEvent->getStatusCode(), cbEvent->getTaskId(),
cbEvent->getSentenceIndex(), //句子编号,从1开始递增。
cbEvent->getSentenceTime(), //当前已处理的音频时长,单位:毫秒。
cbEvent->getResult() // 当前句子的完整识别结果
);
// 获取服务端返回的全部信息
printf("onTranscriptionResultChanged: all response=%s\n", cbEvent->getAllResponse());
}
//@brief 服务端停止实时音频流识别时,SDK内部线程上报Completed事件。
//@note 上报Completed事件之后,SDK内部会关闭识别连接通道。此时调用sendAudio会返回-1,请停止发送。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL,可以根据需求自定义参数。
void onTranscriptionCompleted(NlsEvent *cbEvent, void *cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
// 演示如何打印/使用用户自定义参数示例。
printf("onTranscriptionCompleted: %d\n", tmpParam->userId);
printf("onTranscriptionCompleted: status code=%d, task id=%s\n", cbEvent->getStatusCode(),
cbEvent->getTaskId());
}
//@brief 识别过程(包含start()、send()、stop())发生异常时,SDK内部线程上报TaskFailed事件。
//@note 上报TaskFailed事件之后,SDK内部会关闭识别连接通道。此时调用sendAudio会返回-1,请停止发送。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL,可以根据需求自定义参数。
void onTaskFailed(NlsEvent *cbEvent, void *cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
// 演示如何打印/使用用户自定义参数示例。
printf("onTaskFailed: %d\n", tmpParam->userId);
printf("onTaskFailed: status code=%d, task id=%s, error message=%s\n", cbEvent->getStatusCode(),
cbEvent->getTaskId(), cbEvent->getErrorMessage());
// 获取服务端返回的全部信息
//printf("onTaskFailed: all response=%s\n", cbEvent->getAllResponse());
}
//@brief SDK内部线程上报语音表单结果事件
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL,可以根据需求自定义参数。
void onSentenceSemantics(NlsEvent *cbEvent, void *cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
// 演示如何打印/使用用户自定义参数示例。
printf("onSentenceSemantics: %d\n", tmpParam->userId);
// 获取服务端返回的全部信息。
printf("onSentenceSemantics: all response=%s\n", cbEvent->getAllResponse());
}
//@brief 识别结束或发生异常时,会关闭连接通道,SDK内部线程上报ChannelCloseed事件。
//@param cbEvent 回调事件结构,详情参见nlsEvent.h。
//@param cbParam 回调自定义参数,默认为NULL,可以根据需求自定义参数。
void onChannelClosed(NlsEvent *cbEvent, void *cbParam) {
if (cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
//通知发送线程, 最终识别结果已经返回, 可以调用stop()
pthread_mutex_lock(&(tmpParam->mtxWord));
pthread_cond_signal(&(tmpParam->cvWord));
pthread_mutex_unlock(&(tmpParam->mtxWord));
delete tmpParam; //识别流程结束,释放回调参数。
}
}
/**
* @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;
std::cout << "onMessage: msg tyep:"
<< cbEvent->getMsgType() << std::endl;
// 这里需要解析json
int result = cbEvent->parseJsonMsg(true);
if (result) {
std::cout << "onMessage: parseJsonMsg failed:"
<< result << std::endl;
} else {
if (cbParam) {
ParamCallBack *tmpParam = (ParamCallBack *) cbParam;
switch (cbEvent->getMsgType()) {
case AlibabaNls::NlsEvent::TaskFailed:
break;
case AlibabaNls::NlsEvent::TranscriptionStarted:
// 通知发送线程start()成功, 可以继续发送数据
pthread_mutex_lock(&(tmpParam->mtxWord));
pthread_cond_signal(&(tmpParam->cvWord));
pthread_mutex_unlock(&(tmpParam->mtxWord));
break;
case AlibabaNls::NlsEvent::Close:
//通知发送线程, 最终识别结果已经返回, 可以调用stop()
pthread_mutex_lock(&(tmpParam->mtxWord));
pthread_cond_signal(&(tmpParam->cvWord));
pthread_mutex_unlock(&(tmpParam->mtxWord));
break;
}
}
}
}
}// namespace
namespace {
bool urlIsOk(const std::string& url) {
LOGD("url: %s ", url.c_str());
if (url.size() < 4) {
return false;
}
std::string str = url.substr(0, 2).c_str();
transform(str.begin(), str.end(), str.begin(), ::tolower);
return "ws" == str;
}
} // namespace
std::string InitParams::toString() const {
std::stringstream ss;
ss << "{\"accesskey\":\"" << _accesskey << "\",\"secret\":\"" << _access_secret
<< "\",\"sdk_log_name\":\"" << _sdk_log_name<< "\",\"sdk_log_level\":" << _sdk_log_level
<< ",\"version\":\""<< _version << "\",\"domain\":\"" << _domain << "\",\"resource_path\":\"" << _resource_path
<< "\",\"region\":\""<< _region << "\",\"scheme\":\"" << _scheme << "\"}";
return ss.str();
}
std::string AsrAssistant::generateUuid() {
uuid_t uu;
uuid_generate(uu);
char buf[1024];
uuid_unparse(uu, buf);
std::string uuid = buf;
return uuid;
}
void AsrAssistant::sendTranslateConfigUpdateDirective(){
Json::Value header;
header["name"] = "UpdateConfg";
Json::Value payload;
payload["translate_result_enabled"] = false;
payload["translate_languages"] = "en";
payload["instruction_id"] = generateUuid();
Json::Value root;
root["header"] = header;
root["payload"] = payload;
Json::FastWriter writer;
std::string json_str=writer.write(root);
std::cout<<"json_str: "<<json_str<<std::endl;
//会中控制翻译开关,发送自定义消息
_request->control(json_str.c_str());
}
bool AsrAssistant::start(const std::string& doc_json_str,const std::string& first_frame_time) {
LOGD("%s : AsrAssistant::start", _session_id.c_str());
long start_time = NOW;
for (int i = 0; i < 3; ++i) {
_url = HttpPopClient::getInstance().getAsrUrl(doc_json_str,meetingId);
if (urlIsOk(_url)) {
break;
}
}
if (!urlIsOk(_url)) {
LOGE("%s : get url fail", _session_id.c_str());
return false;
}
LOGD("%s : get url took: %ld ", _session_id.c_str(), NOW - start_time);
LOGD("%s : url: %s ", _session_id.c_str(), _url.c_str());
//LOGD("%s : namespace: %s ", _session_id.c_str(), RFASR_NAMESPACE);
_request = NlsClient::getInstance()->createTranscriberRequest("cpp",true);
if (nullptr == _request) {
LOGE("%s : createTranscriberRequest failed", _session_id.c_str());
return false;
}
// 退出线程前释放
cbParam = new ParamCallBack();
if (!cbParam) {
return NULL;
}
// _request->setOnTranscriptionStarted(onTranscriptionStarted, cbParam); // 设置识别启动回调函数
// _request->setOnTranscriptionResultChanged(onTranscriptionResultChanged, cbParam); // 设置识别结果变化回调函数
// _request->setOnTranscriptionCompleted(onTranscriptionCompleted, cbParam); // 设置语音转写结束回调函数
// _request->setOnSentenceBegin(onSentenceBegin, cbParam); // 设置一句话开始回调函数
// _request->setOnSentenceEnd(onSentenceEnd, cbParam); // 设置一句话结束回调函数
// _request->setOnTaskFailed(onTaskFailed, cbParam); // 设置异常识别回调函数
// _request->setOnChannelClosed(onChannelClosed, cbParam); // 设置识别通道关闭回调函数
// 设置所有服务端返回信息回调函数
_request->setOnMessage(onMessage, cbParam);
// 开启所有服务端返回信息回调函数, 其他回调(除了OnBinaryDataRecved)失效
_request->setEnableOnMessage(true);
// 暂停再继续识别,且在创建会议时设置了AudioOutputEnabled为false或未设置时,需要设置继续识别的会议时间偏移(单位毫秒)
// std::string json_first_frame_time="{\"tw_time_offset\": \"xxx\"}";
// _request->setPayloadParam(json_first_frame_time.c_str());
// 在启动识别前可进行翻译配置修改
Json::FastWriter writer;
bool translateResultEnabled = true;
if (translateResultEnabled) {
Json::Value json_translate_result_enabled;
json_translate_result_enabled["translate_result_enabled"] = true;
std::string str_json_translate_result_enabled = writer.write(json_translate_result_enabled);
_request->setPayloadParam(str_json_translate_result_enabled.c_str());
Json::Value json_translate_languages;
json_translate_languages["translate_languages"] = "en";
std::string str_json_translate_languages = writer.write(json_translate_languages);
_request->setPayloadParam(str_json_translate_languages.c_str());
} else {
Json::Value json_translate_result_enabled;
json_translate_result_enabled["translate_result_enabled"] = false;
std::string str_json_translate_result_enabled = writer.write(json_translate_result_enabled);
_request->setPayloadParam(str_json_translate_result_enabled.c_str());
}
_request->setUrl(_url.c_str());
start_time = NOW;
if (_request->start() < 0) {
LOGE("%s : start fail", _session_id.c_str());
NlsClient::getInstance()->releaseTranscriberRequest(_request);
return false;
}
LOGD("%s : : AsrAssistant::start success, took:: %ld ", _session_id.c_str(),
NOW - start_time);
// 等待started事件返回, 在发送
std::cout << "wait started callback." << std::endl;
/*
* 语音服务器存在来不及处理当前请求, 10s内不返回任何回调的问题,
* 然后在10s后返回一个TaskFailed回调, 所以需要设置一个超时机制.
*/
struct timespec outtime;
struct timeval now;
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + OPERATION_TIMEOUT_S;
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;
pthread_mutex_unlock(&(cbParam->mtxWord));
_request->cancel();
NlsClient::getInstance()->releaseTranscriberRequest(_request);
return false;
}
pthread_mutex_unlock(&(cbParam->mtxWord));
return true;
}
void AsrAssistant::stop() {
if (nullptr != _request) {
int ret = _request->stop();
LOGD("%s : stop res:%d", _session_id.c_str(), ret);
/*
* 识别结束, 释放request对象
*/
if (ret == 0) {
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 'StopTranscriber'!"
* 所以需要设置一个超时机制.
*/
struct timespec outtime;
struct timeval now;
gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + OPERATION_TIMEOUT_S;
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));
}
pthread_mutex_unlock(&(cbParam->mtxWord));
} else {
std::cout << "ret is " << ret << std::endl;
}
NlsClient::getInstance()->releaseTranscriberRequest(_request);
_request=nullptr;
}
HttpPopClient::getInstance().stopMeetingTrans(meetingId);
}
AsrAssistant::AsrAssistant(const std::string& session_id)
: _session_id(session_id) {}
AsrAssistant::~AsrAssistant() {}
bool AsrAssistant::sendAudio(const PB::MultiAudioFrame& frame) {
if (nullptr == _request) return false;
std::string pb_str;
if (!frame.SerializeToString(&pb_str)) {
LOGE("%s generate pb package fail", _session_id.c_str());
return false;
}
long start = NOW;
int ret =
_request->sendAudio((const uint8_t*)pb_str.c_str(), pb_str.size());
long end = NOW;
if (ret < 0) {
LOGE("%s sendaudio,ret:%d,start:%ld,end:%ld,diff:%ld,request:%p",
_session_id.c_str(), ret, start, end, end - start, _request);
}
return ret >= 0;
}
bool AsrAssistant::sendAudio(const char* data,size_t length) {
if (nullptr == _request) return false;
long start = NOW;
int ret =
_request->sendAudio((const uint8_t*)data,length);
long end = NOW;
if (ret < 0) {
LOGE("%s sendaudio,ret:%d,start:%ld,end:%ld,diff:%ld,request:%p",
_session_id.c_str(), ret, start, end, end - start, _request);
}
return ret >= 0;
}
bool AsrAssistant::init(InitParams param) {
LOGD("AsrAssistant::init(%s)", param.toString().c_str());
LogLevel log_level = LogLevel::LogInfo;
switch (param._sdk_log_level) {
case 1:
log_level = LogLevel::LogError;
break;
case 2:
log_level = LogLevel::LogWarning;
break;
case 4:
log_level = LogLevel::LogDebug;
break;
}
if (-1 == NlsClient::getInstance()->setLogConfig(param._sdk_log_name.c_str(),
log_level, 2000)) {
return false;
}
NlsClient::getInstance()->startWorkThread(1);
HttpPopClient::getInstance().setVersion(param._version);
HttpPopClient::getInstance().setScheme(param._scheme);
HttpPopClient::getInstance().setDomain(param._domain);
//HttpPopClient::getInstance().setResourcePath("/api/brk/meeting/create/");
HttpPopClient::getInstance().init(param._accesskey, param._access_secret,
param._region);
return true;
}
void AsrAssistant::unInit() { NlsClient::releaseInstance(); }
void AsrAssistant::errorHappened(int state_code, const std::string& msg) {
if (nullptr != _error_callback) {
_error_callback(_session_id, state_code, msg);
}
}
AsrAssistant& AsrAssistant::setAudioFormat(const std::string& format) {
_format = format;
return *this;
}
AsrAssistant& AsrAssistant::setSampleRate(int sample_rate) {
_sample_rate = sample_rate;
return *this;
}
AsrAssistant& AsrAssistant::setFrameTime(int frame_time) {
_frame_time = frame_time;
return *this;
}
AsrAssistant& AsrAssistant::setErrorCallBack(
std::function<void(std::string session_id, int status_code,
std::string msg)>
callback) {
_error_callback = callback;
return *this;
}
#单路实时会议识别
void func(int thread_id, int id) {
std::string session_id = std::string("test_lala_") +
std::to_string(thread_id) + "_" + std::to_string(id);
AsrAssistant asr(session_id);
int sample_rate = 16000;
int p_time = 100; // ms
int process_size = sample_rate * 2 * p_time / 1000;
int num = data_len / process_size;
if (data_len % process_size != 0) {
++num;
}
std::string uuid = generateUuid();
Json::Value root;
root["MeetingKey"] = uuid;
root["AudioFormat"] = "pcm";
root["AudioSampleRate"] = 16000;
root["AudioBitRate"] = 16;
root["AudioLanguage"] = "cn";
root["AppKey"] = appkey;
asr.setFrameTime(p_time);
asr.setSampleRate(sample_rate);
// if (send_buffer_limit > 0) {
// asr.setSendBufferLimit(send_buffer_limit);
// }
Json::FastWriter writer;
std::string first_frame_time=std::to_string(getCurrentTime());
if (!asr.start(writer.write(root),first_frame_time)) {
std::cout << "start fail" << std::endl;
return;
}
int tstCount = 0;
int speaker = 0;
for (int j = 0; j < num; ++j) {
int size = process_size;
if (j == num - 1) {
if (data_len % process_size > 0) {
size = data_len % process_size;
}
}
if (asr.sendAudio(data + process_size * j,size)) {
std::this_thread::sleep_for(milliseconds(p_time));
} else {
LOGE("sessionid:%s,uuid:%s send failed,exit,time:%ld", session_id.c_str(),
uuid.c_str(), NOW);
std::this_thread::sleep_for(milliseconds(p_time));
break;
}
// 在识别过程中可按需进行翻译配置修改,注意不要循环调用,该处仅为示例在语音推流过程中可进行调用
//asr.sendTranslateConfigUpdateDirective();
}
LOGD("sessionid:%s,uuid:%s prepare stop,time:%ld", session_id.c_str(),
uuid.c_str(), NOW);
asr.stop();
}
文档内容是否对您有帮助?