开发者学堂课程【三节课带你走进智能交互:公共云语音转文本能力介绍及使用说明】学习笔记,与课程紧密联系,让用户快速学习知识
课程地址:https://developer.aliyun.com/learning/course/296/detail/3451
课时1:公共云语音转文本能力介绍及使用说明
内容介绍:
一.一句话识别
二.实时语音识别
三.录音文件识别
本节主要介绍语音识别相关的内容。
先来了解一下语音识别相关的一些概念。简单来说,语音识别就是将一段音频数据转换为对应的一个文件,有语义理解以及语音合成这些步骤。
下图是一个语音识别的简单的系统结构。这是一个语音识别系统的一个大致的示意图:
从最左侧的语音数据开始,经过特征提取,再经过解码搜索,最后产生了一个识别结果,一般这个识别结果是以一种Ndex的一种方式给出的,也就是搜索之后得到的一个排名。但是大多数场景下,只会用到一个得分最高的识别结果,这样其实已经能够满足绝大部分场景的需求。
这个系统里有三个很重要的要素,第一个要素就是发音词典。它决定了语音识别系统里能够输出哪些词或哪些字。在现在的版本的语音识别系统里面,词典的规模一般是比较大的。包括一些常用词,很多的生词。基本不会出现没有覆盖词的情况。
然后是声学模型。声学模型是在大量的语音数据上训练产生的,它的主要作用就是将输入的音频进行一些切片,确定每一片对应的一个音素,音素可以理解为学的拼音。但是在训练语音模型的时候,这个音素会在拼音基础上做一些更加优化的处理。
第三个是语言模型。语言模型主要是文本相关的一个模型,是在相当于在声学模型后面的一个处理步骤。在声学模型给出一些可能的发音的前提下,语言模型会进一步去看这些发音之间的相互的依赖关系,最终给出一个词的序列。通过这个系列,就相当于拿到了最终的识别结果。
下面需要了解一些常见的音频格式:
大概可以将音频格式分为两类,一类可以做实时音频识别。后面介绍的一些接口很大程度上分成实时和离线这两类,所以需要首先了解一下这些概念。
对实时音频格式,它需要让音频格式支持边发送边识别,所以它对格式会有些要求。常见的实时音频格式大概有四种:
一个是PCMPCM,是未经压缩的一种音频格式,里面存储了音频流里面的音频数据里的每个采样点的信息。
wav格式是在PCM上做了一个文件头。
Opus的压缩格式是一个相对较新的压缩格式,也是推荐大家使用的。这种格式既能够提供比较高保证度的压缩,又能够支持实时识别。比如在常见的配置下,opus可以做到10%的压缩比,也就是可以节省90%的带宽消耗。它还支持实时传输、实时识别。
speex是一个相对旧一点的格式,在opus出来之前,speex是主流,但是后来的opus在同样压缩比情况下的音质上面会有明显的优势,因此opus慢慢地取代了speex。
另外一类就是离线文件格式。离线文件格式的种类比较多,比如MP3,MP4,还有aac这样的一些文件格式。平时也会把这些格式称为流媒体,但是它相对于实时音频是有些区别的。例如MP4这种格式,它会有一些索引数据放在文件的结尾,所以这个文件流式在传输的过程中,是无法对这个文件进行分帧处理,也就无法识别。因此这种格式一般适用于离线文件。用户会提供一个完整的文件,可以是文件的地址,这样就能获取整个文件再进行识别。
接下来介绍产品的一些具体功能。
阿里云的智能语音交互大概提供了三种形态的接口,包括录音文件识别、实时语音识别和一句话识别。
一. 一句话识别
先来看一句话识别。一句话识别的接口相对更直观一些,它主要是针对一些比较短的语言,大部分针对于十秒以内的一句话,接口也考虑了一些说话相对常见的场景,因此接口的限定大概在60秒左右。它主要用于语音搜索,语音指令这些相对较短语音流的场景中。还可以集成在各类APP和一些设备上面,比如平时使用的高德地图的接口。用户可以采用语音唤醒的方式,比如他会对手机说“小德小德”,这时候就开启了一句话识别功能。在这个识别过程中,这个音频流是以数据包的形式流失的,发送到服务端,服务端收数据之后立马做一个识别,检测到用户说话结束的时候,服务端可以很快的把最终的结果反馈回来,用户体验就会比较好。
下面看到的是一句话识别的文档。
这个文档链接是可以通过控制台那边调整过来的。这里对一句话的结构做了一些比较详细的说明。
因为它是一个流式传输的接口,所以它的格式相对少一些,主要是PCM,Wav等等。
在语言场景下,采样率主要是8000采样率或16000采样率,时长限制不超过60秒。
可以设置一些附加一些条件,比如说是否返回中间识别结果,因为有的设备上需要中间结果,比如高德助手,如果人在说话的时候同时有一个中间的识别结果返回,交互上就会感觉好一些。也有场景是不需要的,比如设备端,有时候它的界面会做成动态的效果,这个效果已经表示在听的状态,就不需要返回中心结果。比如用户说“帮我打开空调”,这个中间结果是没有多大必要展示出来的,只展示最终命令执行的结果就可以了。有的场景下是需要添加标点的,所以提供了添加标点的功能。
服务访问地址是大概是这样:
这些在SD卡里都有内置的SDK,因此一般都不需要进行特殊的配置。它是一个双向的流式交互,这里有一个示意图:
大概介绍交互的过程。这些交互的过程背后有一套基于web socket协议来实现的,使用SDK不需要太注意细节,大概了解流程即可。
主要的流式分为几个阶段,第一个阶段是执行start操作。start操作是用SDK发送start recognition指令,这个指令会收到服务端,服务端会去针对这个执行的参数做一些初始化设置,设置完成之后会返回一个recognition started的事件。这个启动的操作就完成了。这个时候,端上就可以开始发送音频流的数据包的方式,边采集边发送到服务端,服务端同步会进行这个音频的流式的识别。
识别过程中,如果用户开启了反馈中间结果的参数选项,中间结果也会被以流式的结果反馈回去。当客户端检测到语音的位点,或者用户通过点击屏幕的方式表示说话已经结束的时候,这时SDK发送一个stop recognition的指令到服务端,服务端完成最后的识别返回一个recognition completed的事件。以上,一次的识别过程就就结束了。
另外,一句话接口是支持云端微粒的语音检测,也就是服务端会通过叫做VD的算法来判断有没有人说话,因为对有的智能设备比如音箱,它是通过语音唤醒来进行会话的开始。但是会话的结束需要另外一种算法,就是刚才提到的VD的语音检测算法。它需要去判断是否有人说话,如果静音达到了800毫秒或1000毫秒,就会结束会话,返回一个最终的结果。
这个算法很多情况是在端上做的,但是因为受限于端上的计算量,模型就相对会小一些,所以它效果不一定很好。因此云端提供了一种VD能力,它可以通过参数来打开,之后服务端就会做VD的计算。
如果检测到用户一段时间没有说话,就像刚才流程图里的结束事件,服务端会自行返回,则不需要端上发停止键。这是针对于这种助手或智能设备的增强的功能,同时,有一些关于危机算法的参数也是可以通过请求来设置的。
以上就是一句话接口的大概说明。因为它是一个web socket的接口,相对编程还是麻烦一些,所以提供了多种语言的SDK。可以在这里找到每个语言的SDK的说明,每一个SDK都会提供一些相关的实例代码的下载。
下面介绍实时语音转写。
二. 实时语音识别
一句话接口其实是相对简单一点的接口,用户送来一个相对较短的语音流,返回一个识别结果。
还有另一类场景是一句话识别无法满足的,比如像实时会议记录,或者实时的直播字幕,这种情况下一个会议可能持续几十分钟,一场直播可能持续一两个小时,一个法庭的庭审持续的时间就更长了。
这时需要提供一种超长连接的接口,也就是实时语音识别接口,这个接口内部会通过一种语音的算法对语音进行切片,内部会启动相对较短的一些识别的流程。但是对于终端用户来说,使用比较简单。建立好连接之后就可以一直把设备上采集的音频包发送到服务端,而不需要去考虑这个时长的问题。
相当于接口大幅的简化用户的操作,用户一直发送即可。还有一类客服记录场景也可以通过这种来实现,因为这个实施的结果会反馈一些事件。客户端知道什么时候人开始说话,什么时候说话结束,在客服这边其实可以对客服人员和客户的交流做一些判断。
实时语音识别的接口文档介绍,实时语音识别是对于语音数据流进行识别,适用于会议、演讲、视频直播等长时间不间断识别的场景。因为它是一种识别,它的支持的编码相对也会少一些。
支持音频采样率跟之前是一样的。支持反馈结果的格式基本上也是跟一句话是相似的。服务访问地址也是基本上是在SDK设置好的。
交互流程跟一句话识别有一些类似的地方,start、stop基本是一样的,中间服务端返回的消息会有一些差别:多出了sentence begin sentence end的这种事件。实时转写的这些参数相对于一句话识别,参数会多一些,有一个比较重要的参数是speech_noise_threshold。
这个参数是用来判断断句的灵敏度的。因为在一个实时音频流中可能会存在一些背景噪音,这些背景噪音可能会包含一些类似于人声的部分,这样会导致实施转写的一些断句不是那么精准。有可能就会需要用户来根据场景对这个值进行一些调整,它的区间范围是从-1到1。数值越大,算法判定的就更严格,就更不容易把背景噪声判断为有效的语音。
当然也会造成一些误判,如果数值设置过高的话,可能会有些音量比较小的正确的人的声音被当做噪音切掉。反之也是一样的,如果设置的值特别小的话,有可能会导致一些背景噪音被当成有效语音切进来。这是需要根据自己的业务进行优化和调整的。
识别结果的TrancriptionResultChanged事件,跟一句话接口的RecognitionResultChanged基本是对应的。它也需要一个参数来控制是否返回中间的一些实际情况,如果这个enable intermediate result设置成false的时候,这个消息就不会出现,就只会有这个since begin的事件返回,当然send也会带有证据的一个识别结果。以上就是实时语音转写的这个结构的介绍。
接下来看一下录音文件识别的接口。
三. 录音文件识别
录音文件识别针对的完全是另外一种场景。在语音的应用场景里有很多需求,它不需要实时,比如客服质检,每天客服人员的录音数据收集完成之后,不一定需要当天拿到识别结果。这个时候就可以通过这种录音文件识别的方式来进入这种业务,允许用户一次批量的提交很多录音文件。用户可以直接把它存储好的录音文件的连接地址发给服务,服务会拉取它的识别结果,整个录音文件的识别结果相对于实时接口简单很多。基本由两部组成,一个提交任务,一个长期任务。
下面看一下接口的说明。因为它是一种针对离线文件的识别,它支持的这个录音格式就相对更多了。支持单轨和双轨就是一个录音文件里可以存两个声音,两个通道的方式保存。
比如客服场景下,一个通道用来存储客服人员,另一个通道用来存储客户的声音。在经过文件转写的时候,识别结果就会包含这个通道的信息,用户就可以知道一个人说的话是客服人员说的还是客户说的。
识别的格式也会多一些,比如支持MP3的录音格式,实时接口是不支持的。
调用方式也是相对简单一些,只需要一个简单的类似于HTTP的调用接口来提交任务,再通过另外一个HTTP的接口来查询任务即可。
除此之外也支持回调的方式,回调的方式一般是针对一些高级用户来使用的。一些大客户的请求量会比较大,他同时可能会提交1万个任务,如果轮调效率会相对低,所以提供了一种回调的方式,它可以制定一个回调的地址,识别完成之后,会主动把结果推送给这个地方。同时,出于安全方面的考虑,提交完地址会有一些限制。
整个文件转写的接口对外承诺的大小是512MB,只要这个文件在这个范围以内都是可以进行识别的。另外,文件专业接口也支持一些比如MP4这样的视频,视频文件很容易突破512MB的限制。如果是短视频,大家可以直接把视频提交过来进行识别;如果是一些相对较大的视频,建议大家给予出来。这样的可以大幅压缩文件的大小,整体上的速度会提高,带宽的这个消耗也会减少。
录音文件转写的任务提交之后,承诺的时效是6个小时,也就是保证在六个小时以内完成识别,并且把最终结果保存起来。这个结果的保存有24小时的有效期,在转写完成之后,用户可以在24小时内任何时间来获取这个识别结果,而且是可以重复获取的。
下面是一个文件转写的一个流程图:
接口的调用方式是通过阿里云的开放平台,也就是open api的方式对外提供,可以使用这个open api这个sdk来进行。使用阿里云这一统一api,专业的接口支持的sdk的种类就会比较多,因为整个阿里云方面所有的api都是统一的。
如果使用一些这里没有列出的语言,可以参考open api或官方的标准来实现简易的SDK。因为接口的本质是HTTP接口的形态,唯一需要做的事情就是计算安全的签名。签名的计算方式在开放API的平台上有详细的介绍。
接口有一点是关于version的概念,因为在几年之前就开始建设语音识别的这个云产品,目前这个文件记录已经到4.0的版本了。参数是需要来进行填写的。
接下来就开始进行演示的环节。演示过程中使用到的示例代码都可以在官方进行下载。
先来看一下一句话识别的接口:
public static void main(String[] args) {
String appKey = "qBavZtUcU2XZQTe8";
String token ="8d3bdc4b654248cbbfcac4a7df51c63f";
String url ="";// 默认即可,默认值:wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1
App key是在控制台上创建的,token其实是通过阿里云的access key和access key secret来进行置换。token是有24小时的有效期的,这也是为了安全的考虑。string的流程大概包含两个关键点,
SpeechRecognizer recognizer=nu: try {
//传递用户自定义参数
string myParam ="user-param"; int my0rder=1234;
SpeechRecognizerListener
listener=getRecoonizerListener(myrder,myParam);
recognizer=new
SpeechRecognizer(client,listener); recognizer.setAppKey(appKev);
//设置音频编码格式,如果是opus文件,请设为InputFormatEnum.OPUS
recognizer,setFormat(InputFormatEnum.pcn);//设置音频采样率
if (sampleRate =16000) {...} else if (sampleRate == 8000) {...}//设置是否返回中间
识别结果
recognizer.setEnableIntermediateResult(false);//...
long now=SystemcurrentTimeMillis(); recognizer.start();
logger.info("ASR start latency:"+(System.currentTimeMiliis()-now)+" ms")
File file=new File(filepath);
首先创建了一个listener对象,它是用来监听服务端返回的事件。重要的事件有两个,第一个是resultchanged,在打开中间结果的时候,回调是在每次服务端返回中间结果出发,另外一个是recognition completed,这个事件是在整个识别过程完成了之后才会返回。它发生了之后返回的结果是最终的识别结果。listener创建完成之后,就可以创建 recognizer的对象,对recognizer进行一些参数设置,比如设置appkey,再设置音频格式,再进行选择是否打开中心结果。
先看一个所有选项都关闭的情况下,最简单的一个情况。参数设置好了之后,会调用start的方法,开始识别:
File file =new File(filepath);
FileInputStream fis=newFileInputstream(file): byte[] b=new byte[3200]; int len;
while ((len = fis.read(b)) > 0) {
recognizer.send(b,len);
//重要提示:这里是用读取本地文件的形式模拟实时获取语音流并发送的,因为read很快,所以这里需要sleep// 如果是真正的实时获取语音,则无需sleep,如果是8k采样率语音,第二个参数改为8000
//8000采样率情况下,3200byte字节建议sleep200ms,16000采样率情况下,3200byte字节建议
sleep100ms int deltaSleep = getsleepDelta(len, sampleRate); Thread.sleep(deltaSleep);}
//通知服务端语音数据发送完毕,等待服务端处理完成 now=system.currentTimeMillis();
// 计算实际延迟:stop返回之后一般即是识别结果返回时间 logger.info("ASR wait for complete"); recognizer.stop();
logger.info("ASR stop latency :"+(System.currentTimeMillis() - now) + " ms");
fis.close();
catch (Exception e){
System.err.println(e.getMessage()); finally{//关闭连接
if (null!=recognizer){
recognizer.close()
}
返回之后就可以开始发送音频了。这里是用一个文件来模拟一个实时流,所以这里会有sleep事件,如果这是在具体的应用场景下,这个音频流应该不是使用这种read的方式进入的,而可能是一种,比如在移动端,可能是一个底层录音API不断地往外抛这个音频锚,发送到服务端,所以采用这种相当于文件的方式来模拟。当发送完数据之后,会调用这个recognizer stops操作,完成之后,等stop返回完成之后,最终结果就相当于已经拿到了。在finally里面会把recognizer关掉,相当于把这个连接点释放掉。下面来执行一下这段代码:
name: RecognitionCompleted, status: 20000000, result: 北京的天气
以上就是执行结果。
因为没有开启中间结果,所以在ASR请求启动之后,只收到了一次消息,这个时候可以把中间结果打开。再执行一遍。
这次执行随着音频流的发送,会不断的有中间结果返回,且是以递增的方式出现,最后会有一个最终结果出来。
有的展示情况是需要加标点的,所以也可以试一下把标点模块打开。
现在执行的时候,这句话已经能够进行标记的添加了。因为这句话比较简单,所以只是添加了一个句号:
如果句子相对长一点,中间可能会产生逗号,最终也可能会产生一些问号。这就是一句话接口的演示。
接下来再看实时语音转写的演示
private
static
SpeechTranscriberListener getTranscriberListener() {
SpeechTranscriberListener
listener
=new SpeechTranscriberListener(){
//识别出中间结果.服务端识别出一个字或词时会返回此消息.仅当setEnableIntermediateResult(true)时,才会有此类消息返回
@Override
public
void onTranscriptionResultchange(SpeechTranscriberResponse response) {...}
@Override
public void onTranscriberstart(SpeechTranscriberResponse response) {...}
@Override
public void onSentenceBegin(SpeechTranscriberResponse response){...}
//识别出一句话.服务端会智能断句,当识别到一句话结束时会返回此消息
@Override
public void onSentenceEnd(SpeechTranscriberResponse response){...}
//识别完毕
@Override
public void onTranscriptionComplete(SpeechTranscriberResponse response){...}
@Override
public void onFail(SpeechTranscriberResponse response) {...}
同样是使用appkey和token。这里面类似的也是有也是有两个关键点,第一是listener创建。实时语音转写这个listener相对复杂一些,除了compete和resultchanged之外,它会多出sentencebegin和sentenceend用来标识一句话的开始和一句话的结束。begin里面有用的是时间戳的信息,表示这句话是从哪个时间开始的。而sentenceend里面会反回这句话完整的识别结果。
如果打开中间结果显示,这个时间也会随着服务端的返回结果进行出发。complete这个事件在实时转写的意义就不是很大了,因为最终结果其实是通过sentenceend的时间返回的。
中间代码的第二个点也是trancriper的创建过程。跟一句话结构类似,同样的通过指定的listener创建一个transcriper,创建之后设置各种各样的参数,之后再调用start方法开启识别,用文件模拟音频流。最后调用SQL接口,调用stop操作来完成识别过程。这里用的文件比较短,实际的过程中也可以是一个持续几小时的音频流。
下面来执行一下代码,相当于整个识别过程。这个是sentence begin消息,相当于在音频流输送的过程中,首先会检测到有人说话,返回这样一个消息。这个消息里面的payload会带有这个句子的编号,比如说这是开始的第几句,这句话是在720秒的音频流偏移开始的。
再搜一下sentenceend的事件,表示的是一句话的结束,跟sentence begin是配对出现的。它的payloud里的index1表示第一句,同样会有时间戳,这个时间戳表示的是这句话结束的时间。同时会返回一个跟begin time对应的time,相当于在这个消息里会计算出这句话是在哪个时间范围出现的。这样,对于一些直播场景就可以做字幕对齐的操作。以上就是实时语音识别的demo。
接下来看一下文件转写的演示。这个接口相对简单一些,可以写一个非常串行的代码,不需要考虑类似异步的回调的方式。
这里使用的是临时申请的一个a secret,因为它是基于一个阿里云的开放平台的结构,所以会统一采用阿里云的access key的鉴权方式。
public static void main(String[] args) throws Exception {
//1.创建POP客户端
DefaultProfile profile = DefaultProfile.getProfile(REGION, AK_ID, AK_SECRET);
DefaultAcsClient client=new DefaultAcsClient(profile);
//2.提交转写任务
CommonRequest request=new CommonRequest();
request.setDomain("filetrans.cn-shanghai.aliyuncs.com"): request.setVersion(“2018-08-17"); request.setAction("SubmitTask");
//3.等待转写任务完成 while (true){
request=new CommonRequest();
request.setDomain("filetrans.cn-shanghai.aliyuncs.com") request.setVersion("2018-08-17"); request.setAction("GetTaskResult"); request.setMethod(MethodType.GET);
request.putQueryParameter( name:"TaskId",taskId);
这里是准备的一个演示的录音文件,整个识别过程大概可以分为三步,第一个是创建pop的官方的open APP ID client。完成之后开始第二步,提交这个文件转写的任务。这里需要填一些撰写API的信息,对提交接口来说都是固定的,里面记录APP信息以及文件的下载地址。将这个请求发送到pop的接口上面,就会拿到这次文件撰写的任务的标识。在查询接口里就会可以通过这个任务来进行轮询的操作查询结果,查询结果里会有statustext字段,可以根据这个字段来判断任务是否进行。执行过程大概有两个阶段,当这两个阶段都做过之后,就可以获取到最终结果。
可以试着执行一下代码:任务已经提交,等待三秒去查询结果。因为目前文件转写的效率还是很高的,因此第一次上线就拿到了结果,可以看到它是真实的结构。相对短的会做演示用,只sleep了三秒钟的时间,但实际上在使用的时候,如果提交的任务特别多,每个任务又做螺旋的话,这个查询操作可能会达到一个比较高的QPS。
目前系统的限制是200,所以如果任务数量比较多的时候,是需要考虑轮询螺旋策略的优化。比如螺旋一些相对比较早的有可能完成的任务,按照一个有序的一个方式进行或者直接使用回调的方式来进行。以上就是文件转写的接口的演示。
一个相对更方便的接口叫做restful API,是一句话识别接口的简化的计算方式。因为使用SDK相对来说需要做一些基层工作,比如可能会遇到一些代码库的冲突,接下来可能就比较麻烦,所以提供了这种更简单的方式,其实就是HTTP的请求。
可以使用开源的一些通用的HTTP库来完成这个行动。因为它是一个HTTP请求,所以就没有那种中间结果的返回能力,这是它的一个缺点。但是进入也很简单,做一个简单的curl命令就可以完成语音识别的调用。
curl\
-X POST \
-H "X-NLS-Token: 8d3bdc4b654248cbbfcac4a7dfslc63f”\
--data-binary @test.wav \
"http://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/asr?appkey=qBavZtUcU2XZQTe86sample_rate=160008format=wav"
kai@kai-macbook~/Desktop/rest-api
这个命令由token字段和一个appkey字段组成。通过curl自带的data binary参数指定一个录音的文件,这样就相当于把这个文件post到了请求地址上,然后就会对这个文件进行识别,这样就拿到了识别结果。如果是通过一些相对脚本化的,比如python这样的语言来进行编程,可能直接通过进程调用就可以完成这样的一个操作。