|
贝壳音频流网关的设计、演进及使用场景
贝壳音频流网关的设计、演进及使用场景
张仕华
贝壳产品技术
贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容
2022年03月25日 15:17
介绍随着实时音视频技术的发展,音视频聊天已经走入寻常百姓家,微信2017年每天有4.1亿次的音视频通话。贝壳找房IM作为线上商机来源,也实现了该功能,作为经纪人和客户沟通的便捷工具。除了IM音视频通话,实时音视频技术在贝壳还用到了直播自动审核、经纪人讲盘训练等各种场景,这些场景中都用到了实时音频流网关。以经纪人讲盘训练为例,通过构造一个虚拟人进入音频房间,可以和经纪人进行实时问答,模拟买房和讲盘,提高经纪人作业效率。要达到该效果,虚拟人需要能够识别经纪人讲话并且进行回答或者提问。经纪人讲盘训练整个业务架构如下所示:其中,自动语音识别技术ASR(Automatic Speech Recognition),是一种将人的语音转换为文本的技术,反之,TTS(Text To Speech),可以将文本转换为语音。实时音视频房间可以使用腾讯云或者声网等公司的技术,或者使用webRTC进行自研,本文重点关注图中标色部分的贝壳音频流网关,其功能为将经纪人的语音推送到后端ASR服务,进行识别和分析,之后通过TTS生成一个回答或者提问并通过音频流网关将语音回传给经纪人。本文主要介绍音频流网关的架构、演进、稳定性建设及使用场景。音频基础知识声音是振动产生的声波,经过空气、固体、液体这些介质传播并被人或动物听觉器官所感知的波动现象。音频信号采集需要将声音通过麦克风等转变为模拟信号,然后对模拟信号进行抽样、量化和编码转换成离散的数字信号。PCM(Pulse Code Modulation)就是一种将模拟信号数字化的方法,一般也用来表示未经过封装的音频原始文件。模数转换过程中涉及三个基本概念:采样位深、采样率和通道数。采样位深:每个采样点用多少bit表示,该值越大,能够表达振动幅度的精确程度就越高。例如采样位深为16bit,则意味着可以将振动幅度划分为65536个等级。采样率:每秒的采样点数,一般用 Hz来表示,比如1s如果有48000个采样点,则采样率就是48kHz。因为人耳的听觉范围是20Hz-20KHz,根据奈奎斯特采样定理,模数转换过程中,采样频率大于信号中最高频率的2倍时,采样后的数字信号可以完整地保留原始信号的信息。因此如果采样率为48kHz,可以完整保留24kHz以下频率的完整音频信息。通道数:声音通道个数,常见的为单通道和双通道。双通道可以理解为单通道数据保存两份。人左右耳因为空间位置导致听到声音时间不同,双通道通过播放时模拟这种情况营造声音从不同方向传来的空间感。音频采样过程中持续采样时间称为帧长,可以使用20ms,也可以使用200ms,时间越短延时越小。假设一次采样,采样位深是16bit,采样率为16kHz,单通道,帧长为20ms,使用PCM,则每帧的大小为:帧大小=位深*采样率*帧长*通道/(1000*8)=16*16000*20*1/(1000*8)=640字节贝壳音频流网关处理的每帧大小就是640字节。架构介绍贝壳音频流网关使用实时音视频云平台提供的服务端SDK进入房间、退出房间以及在房间内推拉流,除SDK功能外,主要需要解决如下问题:控制进入、退出房间时机分角色拉取,合并以及重传流有状态服务横向扩展方法服务稳定性建设本文逐个解答这些问题。架构图整体架构为Dispatcher-Worker模式,Dispatcher通过对外暴露http接口管理任务,控制Worker启动停止,以及与ASR、TTS服务交互;每个Worker都是独立的进程,调用实时音视频云平台的服务端SDK进入房间进行推流和拉流。Dispatcher与Worker使用双向管道(PIPE)进行通信,例如推拉流的交互,控制信息的交互。贝壳音频流网关使用Go代码实现,Go标准库的os/exec包可以直接调起一个外部程序执行,并且生成和外部程序的双向管道进行通信,os/exec包的关键结构体及方法如下:typeCmdstruct{Pathstring//程序名称Args[]string//程序参数Env[]string//程序环境变量Dirstring//程序路径Stdinio.Reader//程序标准输入Stdoutio.Writer//程序标准输出Stderrio.Writer//程序标准错误...Process*os.Process//生成的进程...}funcCommand(namestring,arg...string)*Cmd//返回一个Cmd结构体func(c*Cmd)Start()error//启动一个新的进程...func(c*Cmd)StdinPipe()(io.WriteCloser,error)//返回一个pipe,该pipe和新进程的标准输入进行连接func(c*Cmd)StdoutPipe()(io.ReadCloser,error)//返回一个pipe,该pipe和新进程的标准输出进行连接...func(c*Cmd)Wait()error//等待新进程退出...Cmd结构体中的Process成员变量代表新生成的进程,通过向Process发送信号可以控制Worker进程的退出cmd.Process.Signal(syscall.SIGTERM)拉流进入房间音频流网关何时生成一个Worker进程进入房间取决于具体的业务场景,例如在经纪人训练场中,当经纪人点击开始训练之后需要将虚拟人拉入房间,进行交互。Dispatcher通过提供进房接口,由业务方根据业务场景灵活的控制开始拉流的时机,接口及返回值信息如下:接口:POST /task HTTP/1.1返回值:{"errno":0,"errmsg":"success","data":{"address":"10.x.x.x:8888"}}可以看到,返回值中还包含了处理该次请求的机器IP+端口,这是为了能够横向扩展做的一处优化,跟推流有关系,下文详细描述流传输业务方调用进房接口后,Dispatcher会生成一个Worker进入房间开始拉取房间音频流,之后通过管道传输给Dispatcher,Dispatcher再传输给ASR服务。在我们的业务场景中,音频流每帧是20ms,16KHz的采样率,16bit的位深,单声道PCM格式,因此每帧大小为640字节,我们做个简单的计算,假设每个房间中有一个经纪人,同时在线房间数为5000(即有5000个经纪人同时在线训练),则每秒钟的QPS为(1000ms/20ms)*5000 = 250000。为了提高效率,音频流网关会默认累积10帧(200ms)之后传输给ASR服务。此时示例中的QPS相应降为250000/10 = 25000。示意图如下:从图中还可以看出两点信息:最后一个包可能不满10帧每个包传输到ASR时会加入一个Sequence字段,表明包的顺序,最后一个包会将顺序字段取负,表明该次拉流已经结束。Sequence字段会在流重传时使用。流重传ASR服务在某些场景丢包之后无法继续准确识别。因此将包传输到ASR服务时,除了正常的重试策略,还会在音频网关保留最近的30个包。当ASR服务长时间未收到一个包时,可以将该包的Sequence返回到音频流网关,网关启动时会开启一定数量的goroutine,这些goroutine收到重传信息后会从保留包中查找并重传。退出房间退出房间有两种机制:当房间内有用户进入和退出时,实时音视频通道的SDK会有回调信息通知此类事件。因此可以通过设置状态标记和计数器,当房间内所有人都退出后,虚拟人也自动退出提供退房接口,业务方需要退出房间时调用该接口,Dispatcher给Worker进程发送信号,通知其退出。推流推流也通过提供一个接口实现,如下:POST/upstreamHTTP/1.1通过TTS生成语音之后,需要以16bit位深、16kHz的采样率、单声道PCM格式上传,同时需要指定对应的实时音视频房间号。上传成功之后音频流网关启动一个20ms间隔的Ticker(Ticker是Go语言中的一个标准库,经过一个固定时间间隔之后执行某项任务的组件),代码示例如下:delayTicker:=time.NewTicker(20*time.Millisecond)deferdelayTicker.Stop()count:=len(alls)//alls中保存待推送的音频流varerrerrorfori:=0;i
|
|