TTS与ASR功能封装架构图
由于需求文档里面已经从用户角度描述了TTS(语音合成)与ASR(语音识别)的交互时序图虚拟恋人2.0.0 语音消息产品需求文档,这里主要从代码架构的角度描述TTS与ASR功能架构图
-
XHSpeechEngine是基于火山云SpeechEngineToB 语音识别与语音合成SDK封装的不鸽私有Pod库。主要封装绝大部份的语音识别与语音合成的实现的技术细节,仅暴露与业务必须关联的参数给UI业务方配置。XHSpeechEngine又通过XHTTSSpeechEngine与XHASRSpeechEngine两个子模块分别封装语音合成与语音识别功能。
-
XHSpeechEngine(语音合成),具有两个能力:1)将Text合成为aac音频文件;2)将Text合成语音后直接播放。Text合成为音频文件用于实现用户语音消息多次重复播放时,不需要每次都调用TTS语音合成sdk;Text合成语音后直接播放用于AI语音通话,及时播放及时销毁即可,不需要缓存音频文件。语音合成操作通过传入一个XHTTSReqModel发起语音合成请求,合成结束后返回一个XHTTSResModel结果信息,XHTTSReqModel与XHTTSResModel描述如下:
-
///语音合成请求 @interface XHTTSReqModel : NSObject @property (nonatomic, copy, nonnull) NSString *text;//待合成文本 @property (nonatomic, copy) NSString *voiceType;//不同角色的音色 @property (nonatomic, copy) NSString *fileName;//缓存的语音文件名 @property (nonatomic, assign) BOOL shouldAutoPlay;//是否自动播放 @property (nonatomic, assign) BOOL shouldSaveAudio;//是否合成为语音文件 @end ///语音合成返回结果 @interface XHTTSResModel : NSObject @property (nonatomic, strong, nullable) NSError* error;//错误信息 @property (nonatomic, assign) NSTimeInterval speechDuration;//音频时长 @property (nonatomic, copy, nullable) NSString *speechFileLocalPath;//音频文件路径 @end
通过以上XHTTSReqModel的描述信息,可以知道text与voiceType为公共参数配置,当将语音消息的Text合成为音频文件时,需要设置缓存的音频文件名fileName参数,设置shouldAutoPlay为NO,设置shouldSaveAudio为YES;当将语音通话中AI回复的文本合成语音自动播放时,不需要设置fileName参数,设置shouldAutoPlay为YES,设置shouldSaveAudio为NO即可;XHSpeechEngine根据XHTTSReqModel请求的配置,实现将text文本转成语音文件或者自动播放text合成的音频。语音合成完成后通过block将XHTTSResModel信息返回给业务UI层。只有语音消息合成结果才有speechDuration与speechFileLocalPath信息返回,语音通话业务只需要实时播放合成的音频。
-
-
XHTTSAudioFilePathManager是XHSpeechEngine(语音合成)功能中持有的一个专门管理音频文件及其所在文件夹的管理类。由他负责创建TTS音频文件夹,并根据XHTTSReqModel请求中的音频文件名,返回对应的音频路径并返回是否已经存在该音频文件。
-
XHTTSAudioFileConverter也是XHSpeechEngine(语音合成)功能的一部份,负责将语音合成的pcm数据流转化为aac数据后写入指定的音频文件路径。每次发起一次语音合成音频文件的请求,都会根据传入的fileName参数,分配对应的音频文件路径filePath,根据该音频文件的filePath重新生成一个XHTTSAudioFileConverter实例。在XHTTSAudioFileConverter实例中会根据火山团队提供的参数初始化一个AudioStreamBasicDescription的原始音频数据描述,后面的pcm数据都按照该参数配置转成aac数据后写入filePath对应的音频文件。
-
asbd.mSampleRate = 24000;//tts原始的pcm数据采样率,tts sdk决定的 asbd.mFormatID = kAudioFormatLinearPCM; asbd.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; asbd.mBytesPerPacket = 2; asbd.mFramesPerPacket = 1; asbd.mBytesPerFrame = 2; asbd.mChannelsPerFrame = 1;//单声道 asbd.mBitsPerChannel = 16;//每通道的比特数 asbd.mReserved = 0;
-
-
XHASRSpeechEngine(语音识别),具有自动采集设备麦克风输入并自动判断麦克风输入结束,并将输入的用户声音转化为Text文本的能力。使用比较简单,查看该类的头文件即可知道该怎么使用。
AI私聊发送消息流程图
- XHAIChatBaseCellPresenter:上述流程图里面提到每条消息都有一个自己独立控制器都是基于该类的派生子类实例化。该基类通过持有一个XHAIChatTableContext的对象,保存对当前聊天列表控制器上下文环境的引用。该类还有一个cellHeight的属性,不同类型消息的子类控制器,通过重载caculateCellHeight方法计算自己需要展示的UI高度。XHAIChatBaseCellPresenter还有个configWithCell:的方法供子类重载,实现自己控制的消息UI刷新逻辑。
- XHAIChatTableContext:持有一个当前聊天列表控制器的协议描述对象与一个当前页面唯一的音频播放器实例,该描述协议主要包含当前AI角色信息与一个UITableView的弱引用。主要方便后面的XHAIChatBaseCellPresenter子类对象直接访问当前聊天页面的全局性资源。我们的每个XHAIChatXXXCellPresenter生成的时候都会带上当前页面唯一的XHAIChatTableContext实例。比如当我们需要播放音频消息的音频时,直接调用当前音频消息所属的XHAIChatVoiceCellPresenter对象的XHAIChatTableContext里面的shareAudioPlayer播放该音频文件即可。由于整个页面共用一个XHAIChatTableContext的shareAudioPlayer,所以也很方便的实现音频消息的互斥播放与音频消息Cell重用UI状态的刷新与重置逻辑。
- XHAIChatCellPresenterFactory:消息控制器生成工厂类,根据msgType消息类型生成不同的消息控制器XHAIChatxxxCellPresenter。消息列表UITable的数据来源也是通过访问XHAIChatxxxCellPresenter数组来展示不同类型消息UI。
- XHAIChatTextCellPresenter:文本消息控制器,通过在文本消息XHAIChatTextModel数据模型中的shouldTyping字段控制是否需要展示打印机形式。只有AI回复的消息第一次展示的时候才需要展示打印机形式。该类持有一个XHAIChatTypingPresenter打印机实例,打印机实例只需要初始化传入全文本内容,与文本展示的font,color,maxWidth,即可自己实现虚拟的打印效果,生成每个打印阶段的图文混排富文本。XHAIChatTextCellPresenter控制在收到XHAIChatTypingPresenter打印回调后,只需要将当前的图文混排富文本更新到自己控制的TextCell;并判断如果所需展示高度有刷新的话,重新计算cellHeight属性后,通过XHAIChatTableContext中的tableView刷新当前消息列表。
- XHAIChatVoiceCellPresenter:语音消息控制器,通过重载configWithCell:方法,刷新语音消息Cell状态信息。当语音消息的音频时长为0时,需要调用TTS功能合成音频文件,并将语音消息Cell切换为loading状态。合成完音频文件后,再次调用configWithCell:方法,刷新语音消息CellUI。当用户点击播放当前音频时,根据当前语音消息的音频文件名,用上面提到XHTTSAudioFilePathManager类生成对应的音频文件地址。再用当前控制持有的XHAIChatTableContext里面的全局音频播放器播放该语音消息的音频。
- XHAIChatCallCellPresenter:用于控制一条语音通话消息Cell的展示。
- XHAIChatPlaceHolderPresenter:用于展示一个AI占位Loading中的Cell。
综上,只所有采用这种每条消息都有自己独立的Presenter控制器,目标就是为了将不同类型消息与当前聊天页面控制器彻底的解耦合出来,每条消息都能在自己独立的Presenter中完成所有UI刷新与交互逻辑。
发起语音通话流程图
发起语音通话接口返回code码:
- 200:接口验证通过
- 1062:首次购买语音通话
- 1063:非首次购买语音通话
- 208:咕咕豆余额不足
语音通话流程图
语音通话消息发送接口返回code码:
- 200:接口验证通过
- 1060:触发了风控
- -1:语音通话时长超时