
文章目录本篇摘要一.语音识别asr服务环境搭建即客户端模块封装如何获取语音识别服务通过客户端api实现简单调用测试效果测试代码asr客户端api二次封装二.语音识别子服务SpeechRecoginzeServer模块设计及实现实现主要功能总体概括操作流程图实现过程详细整体流程图纯手绘proto文件server端实现client端实现使用CMake进行项目构建测试效果展示源码汇总本篇小结本篇摘要本文详细介绍了语音识别子服务的全流程实现从百度智能云获取ASR服务并完成环境搭建与SDK测试到封装客户端API再基于BRPCETCD设计语音识别子服务通过Protobuf定义接口C实现服务端与客户端完成语音转文字功能开发与验证最终成功实现SpeechRecognizeServer。一.语音识别asr服务环境搭建即客户端模块封装如何获取语音识别服务下面以百度智能云平台作为服务商来进行语音识别服务的支撑访问链接点击速览。详细步骤首先进行登录然后它就会让你实名认证绑定账号然后登录即可。然后就会来到主页面这里其实默认平台已经送了你一定额度免费调用语音识别次数。下面创建对应应用。此时就创建了一个应用下面会有对应的AppID API Key Secret Key这三个数据后面调用的时候需要用到也就是这就是asr客户端进行调用对应asr服务端的凭证。此时就完成了调用百度智能云的语音识别服务的凭证创建后面只需要拿着这些调用对应api进行创建即可。下载对应SDK即可然后把对应包拖到对应目录即可后面只需要包含对应的speech.h这个头文件就可以使用。安装 sdk 所需依赖安装jsoncpp开发库sudoaptinstalllibjsoncpp-dev安装curl工具sudoaptinstallcurlOpenSSLUbuntu 22.04默认已安装无需操作# sudo apt install openssl # 注释表示该步骤跳过注意事项所有命令需要管理员权限通过sudo执行建议按顺序执行前两个安装命令第三个步骤是注释说明实际不需要运行如果系统没有预装openssl可取消该行注释手动安装对应百度的SDK包里面内容只要包含对应base目录下的speech.h即可调用api。下面把对应短语音-http-SDK的语音包案例下载下方便后续进行识别测试https://platform.bj.bcebos.com/sdk/asr/asr_doc/doc_download_files/public.zip点击即可解压后可以看到对应内容下面测试就以16k.pcm为例。下面查看下对应官方文档CAPI的教程可以看到对应用法下面就可以参考来进行客户端api书写完成调用https://cloud.baidu.com/doc/SPEECH/s/dlbxfrs5o点击跳转通过客户端api实现简单调用客户端借助服务器调用对应asr-api接口完成向asr服务端调用解析并返回对应文字的流程图此时对应服务器视角作为语音识别服务的客户端就是拿着asr-api接口完成调用接收应答的过程。此时我们就已经准备好了对应SDK包相关的speech.h代码以及对应测试用例下面根据官方文档的api格式进行书写测试即可。测试效果此时可以看到对应通过asr服务解析出来收到的文字内容和本来这个语音包文件输入进去的语音内容是一样的故测试成功。测试代码#includeaip-cpp-sdk-4.16.7/speech.hvoidASR(aip::Speech*client){std::mapstd::string,std::stringoptions;// options[dev_pid] 1537; // 普通话识别std::string file_content;aip::get_file_content(./16k.pcm,file_content);Json::Value resultclient-recognize(file_content,pcm,16000,options);std::cout语音识别本地文件结果:std::endlresult.toStyledString();if(result[err_no].asInt()!0){std::coutresult[err_msg].asString()std::endl;return;}std::string messageresult[result][0].asString();std::coutmessage :messagestd::endl;}intmain(){// 务必替换百度云控制台中新建百度语音应用的 Api Key 和 Secret Keyaip::Speech*clientnewaip::Speech(你的App-id,你的 Api Key ,你的 Secret Key);ASR(client);// 测试common asr.hpp:return0;}asr客户端api二次封装封装思想简述封装语音识别客户端类构造函数传入平台key信息提供接收语音数据并返回转换文字的接口。asr.hpp代码#pragmaonce#include../ready_example/auto_speech_recognize/aip-cpp-sdk-4.16.7/speech.h#includelog.hppclassASRclient{public:usingptrstd::shared_ptrASRclient;ASRclient(conststd::stringapp_id,conststd::stringapi_key,conststd::stringsecret_key):_client(app_id,api_key,secret_key){}std::stringRecognize(conststd::stringspeech_data,std::stringerr){std::mapstd::string,std::stringoptions;Json::Value result_client.recognize(speech_data,pcm,16000,options);// std::cout 语音识别本地文件结果: std::endl result.toStyledString();if(result[err_no].asInt()!0){errresult[err_msg].asString();returnstd::string();}std::string messageresult[result][0].asString();returnmessage;}private:aip::Speech _client;};说白了其实就是照搬下测试用例稍作修改即可因为是照搬修改故测试效果也是相同的。此时就完成了对应asr-api接口的简单封装方便后续项目直接可以调用来提供方便。二.语音识别子服务SpeechRecoginzeServer模块设计及实现实现主要功能总体概括这个服务是用来把语音变成文字的。用了一个叫做“语音识别SDK”的工具能把客户发过来的语音消息转成文字然后把这些文字传回去。这个服务就只有一个主要功能把语音消息转换成文字。操作流程图操作过程简述首先向brpc服务端注册对应的语音识别接口的函数服务然后把对应的brpc服务端etcd客户端asr语音识别客户端进行封装最后向etcd注册完服务以及brpc完成后就启动对应brpc服务端。然后客户端进行查询etcd注册中心通过回调函数完成对应信道注册最后拿着对应信道构建对应brpc请求进行发送语音文件。rpc服务端收到后通过封装的asr客户端进行转发给对应的语音识别服务端进行识别后转发回来再次构建rsp发给brpc客户端。实现过程详细整体流程图纯手绘proto文件syntax proto3; package proto_module; option cc_generic_services true; message SpeechRecognitionReq { string request_id 1; //请求ID bytes speech_content 2; //语音数据 optional string user_id 3; //用户ID optional string session_id 4; //登录会话ID -- 网关进行身份鉴权 } message SpeechRecognitionRsp { string request_id 1; //请求ID bool success 2; //请求处理结果标志 optional string errmsg 3; //失败原因 optional string recognition_result 4; //识别后的文字数据 } //语音识别Rpc服务及接口的定义 service SpeechService { rpc SpeechRecognition(SpeechRecognitionReq) returns (SpeechRecognitionRsp); }这里就提供对应的语音解析请求以及应答即可然后就是对应RPC相关接口的生成对应的proto结构即可方便后面继承基类实现对接口重写。server端实现首先还是继承对应的之前proto文件写的speechservice的基类然后完成对应服务函数的重写即可。代码如下详细见注释://对应的给rpc注册的服务也就是收到rpc请求调用的函数classRpcSpeechService:publicproto_module::SpeechService{public:RpcSpeechService(ASRclient::ptrpclient):_pclient(pclient){}// 通过子类外部调用基类对象方式进行调用重写的Echo私有也能行voidSpeechRecognition(google::protobuf::RpcController*controller,const::proto_module::SpeechRecognitionReq*request,::proto_module::SpeechRecognitionRsp*response,::google::protobuf::Closure*done){LOG_DEBUG(收到语音转文字请求);// 创建一个 brpc::ClosureGuard对象用于管理 done的生命周期。当 rpc_guard对象析构时会自动调用 done通知框架RPC处理完成。brpc::ClosureGuardrpc_guard(done);// 这里析构的时候调用对应的done-run构建对应答复信息来通知请求方可以进行自己的回调处理了std::string err;std::string res_pclient-Recognize(request-speech_content(),err);if(res.empty()){LOG_ERROR({} 语音识别失败,request-request_id());response-set_request_id(request-request_id());response-set_success(false);response-set_errmsg(语音识别失败:err);return;}// 组织响应response-set_request_id(request-request_id());response-set_success(true);response-set_recognition_result(res);}private:ASRclient::ptr _pclient;// 这里是成员变量不直接在函数里防止每次调用SpeechRecognition都创建_pclient对象麻烦添加服务时候只需要构建一次对应RpcSpeechService对象// 然后每次接受到对应请求就去转化通过基类特定接口再调用它的SpeechRecognition接口直接获取成员变量进行语音识别 即可无需每次创建语音识别对象};这里就是当给brpc服务端进行服务注册构建对应继承的子类对象给它传进去然后当客户端发来对应请求时候它底层内部是一个基类指针然后拿到的是子类对应的指针因此访问的就是子类的服务函数也就是之前重写的因此就解析请求进行业务处理然后构建请求自动发送回去即可。代码如下由于服务端是多模块组合构成的server如有asr-client模块etcd-client模块brpc-server模块等因此可以采取建造者模式进行一一构建然后返回对应speechrecognizeserver即可。classSpeechServerBuilder{public:usingptrstd::shared_ptrSpeechServerBuilder;SpeechServerBuilder(){}voidmake_asr(conststd::stringapp_id,conststd::stringapi_key,conststd::stringsecret_key){_asr_clientstd::make_sharedASRclient(app_id,api_key,secret_key);}// brpc里面依赖asr服务因此先构建对应asr再搭建rpc服务voidmake_brpc(uint16_tport,int32_ttimeout,uint8_tnum_threads){_rpc_serverstd::make_sharedbrpc::Server();// RpcSpeechService rss(_asr_client); 这里局部对象传给对应brpc-server里面就被析构,保存的地址此时就出问题了RpcSpeechService*speech_servicenewRpcSpeechService(_asr_client);// 堆上创建直接传递对应地址即可intret_rpc_server-AddService(speech_service,brpc::ServiceOwnership::SERVER_OWNS_SERVICE);// 告诉brpc-server对象自己析构的时候来析构speech_serviceif(ret-1){LOG_ERROR(添加Rpc服务失败);abort();}// 进行rpc服务部署brpc::ServerOptions options;options.idle_timeout_sectimeout;options.num_threadsnum_threads;ret_rpc_server-Start(port,options);if(ret-1){LOG_ERROR(服务启动失败);abort();}}voidmake_reg(conststd::stringhost,conststd::string servervice,conststd::string servervice_host){// 往哪里注册_reg_clientstd::make_sharedRegistry(host);// 注册什么 服务名称对应要部署的ip-port_reg_client-RegistryDatas(servervice,servervice_host);}SpeechServer::ptrbuild(){if(!_asr_client){LOG_ERROR(还未初始化语音识别模块);abort();}if(!_reg_client){LOG_ERROR(还未初始化服务注册模块);abort();}if(!_rpc_server){LOG_ERROR(还未初始化RPC服务器模块);abort();}SpeechServer::ptr serverstd::make_sharedSpeechServer(_asr_client,_reg_client,_rpc_server);returnserver;}private:ASRclient::ptr _asr_client;Registry::Ptr _reg_client;std::shared_ptrbrpc::Server_rpc_server;};需要注意的细节见注释。client端实现实现思路进行向信道模块设置关心的服务以及注册对应的回调函数向etcd发现客户端中当etcd服务端有内容或者内容有变化的话就会进行回调刚刚注册进信道模块中的函数完成对应拿取value进行构建对应rpc服务信道的操作最后拿着对应信道构建brpc客户端进行对应服务接口函数请求即可。代码如下详细见注释// //通过gflags完成对应参数解析google::ParseCommandLineFlags(argc,argv,true);//勿忘初始化日志对象init_logger(FLAGS_run_mode,FLAGS_log_file,FLAGS_log_level);// 客户端调用rpc接口通过信道与rpc服务端进行通信ServiceManager::Ptr smstd::make_sharedServiceManager();//告诉服务管理者关系哪些服务sm-Cared(/service/speech_service);// 绑定对应etcd监控的服务变化的回调函数(一旦有服务变化的话对应的回调函数就会完成services的维护工作)autoput_cbbind(ServiceManager::OnlineService,sm.get(),std::placeholders::_1,std::placeholders::_2);autodel_cbbind(ServiceManager::UnonlineService,sm.get(),std::placeholders::_1,std::placeholders::_2);Discovery::Ptr disstd::make_sharedDiscovery(FLAGS_etcd_host,FLAGS_base_service,put_cb,del_cb);autopchannelsm-ChooseService(/service/speech_service).get();if(!pchannel){std::this_thread::sleep_for(std::chrono::seconds(1));return-1;}// 构建SpeechService_Stub对象用于向服务端rpc发请求proto_module::SpeechService_Stubstub(pchannel);brpc::Controller*cntlnewbrpc::Controller();proto_module::SpeechRecognitionRsp*rspnewproto_module::SpeechRecognitionRsp();proto_module::SpeechRecognitionReq req;//进行读取对应语音数据进行发送std::string content;aip::get_file_content(16k.pcm,content);req.set_speech_content(content);stub.SpeechRecognition(cntl,req,rsp,nullptr);// 接收到rpc的答复就往下走if(cntl-Failed()true){deletecntl;deletersp;LOG_DEBUG(接收应答失败);return-1;}std::cout收到语音响应内容: rsp-recognition_result()std::endl;使用CMake进行项目构建CMakeLists.txt实现思路收集指定路径下的所有proto文件进行向构建目录使用命令输出对应的.h / .cc文件设置对应检查如果当前目录存在就不用cmake重新构建浪费时间了然后收集各个目录的.cc 以及对应源文件sever.cc client.cc等指定头文件搜索路径最后对他俩进行一一链接库生成可执行文件当然了这里只有一个speech.proto文件因此就只需要用protoc编译这一个即可。CMakeLists.txt文件源码点击速览测试效果展示当前build构建目录不存在对应的proto的.cc 或者.h 文件因此直接构建出来。进行make 生成对应文件可以看到生成了对应程序。存在对应proto依赖文件再次cmake就不会在打印重新生成proto依赖文件提示了。启动对应语音识别brpc服务。客户端然后把对应的测试音频文件放在对应目录接着去读取这个文件发送对应brpc语音识别请求最后收到对应正确应答。源码汇总点击速览本篇小结本文围绕语音识别服务展开先从百度智能云获取ASR服务并完成SDK环境搭建与测试随后封装客户端API再设计语音识别子服务基于BRPCETCD实现服务注册与调用通过Protobuf定义接口C完成服务端与客户端开发最终测试验证语音转文字功能。