文档

C++ SDK

更新时间:
一键部署

本文介绍如何使用听悟开发套件提供的实时语音推流C++ SDK,包括SDK的安装方法及SDK代码示例。​

前提条件

在使用语音推流C++ SDK之前,请先阅读开发参考

源码下载

下载tingwu-client-demo-cpp.zip,该文件为听悟demo源码工程,需要参考下文编译方式编译运行。

编译运行

听悟源码工程目前支持在Linux平台编译,编译运行步骤如下:

  1. 安装编译工具。

    • CMake(3.1以上版本)

    • Glibc(2.5以上版本)

    • GCC( 4.1.2以上版本)

  2. 安装依赖项。

    • 阿里云开放SDK, 只需要编译和安装该项目里面的core模块。

    • 阿里云智能语音服务SDK

    • google protobuf(3.14.0版本)。

  3. 在源码根目录下,进入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();
}
  • 本页导读 (0)
文档反馈