草庐IT

iOS基于Speech框架的语音识别波浪动图实现

CC Tech 2023-03-28 原文
者 | 伍新爽,家庭运营中心

Labs 导读

App开发中经常会遇到波浪式动画语音识别转文字的需求,那么实际是如何实现这样的功能的,本文将从技术框架和视觉实现层面进行Speech框架方案的详细介绍。

1Speech框架及使用流程

目前App中的语音识别功能主要分为本地识别及网络在线识别两种情况。网络在线识别依赖于平台对语音的数据处理能力,其识别准确度较高,优点明显,缺点是识别的稳定性及效率略低;而本地识别方案识别的稳定性及效率较高,但识别的准确度不及网络在线识别方式。本文要介绍的Speech框架属于语音本地识别的一种成熟框架,适用于对识别精度要求不高,但识别效率较高的场景。

为了便于功能维护和调用方便,工程中建议对该框架的识别能力进行管理器模块化封装,如下可定义一个Speech框架识别能力的管理器:

@interface HYSpeechVoiceDetectManager : NSObject
-(void)isHasVoiceRecodingAuthority:(authorityReturnBlock)hasAuthorityBlock;
+(void)isHasSpeechRecognizerAuthority:(authorityReturnBlock)hasAuthorityBlock;
- (void)setupTimer;
-(void)startTransfer;
-(void)endTransfer;

从以上代码可知封装函数包含的是整个的语音识别流程:1.判定语音及Speech框架的使用权限(isHasVoiceRecodingAuthority和isHasSpeechRecognizerAuthority) 2.语音识别参数及相关类初始化(setupTimer) 3.开启语音识别并实时接受识别后的文字信息(setupTimer和startTransfer) 4.强制销毁Speech框架数据及重置音频配置数据(endTransfer),下文将按上述四步展开讲述。

1.1 判定语音及Speech框架的使用权限

因为识别能成功的首要条件就是已经获取到语音和Speech框架的使用权限,因此在初始化框架功能时候需要进行权限的获取操作,Speech框架的使用权限获取代码参考如下:

-(void)isHasSpeechRecognizerAuthority{
if (@available(iOS 10.0, *)) {
SFSpeechRecognizerAuthorizationStatus authorizationStatus = [SFSpeechRecognizer authorizationStatus];
if (authorizationStatus == SFSpeechRecognizerAuthorizationStatusNotDetermined) {
[SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
}];
}else if(authorizationStatus == SFSpeechRecognizerAuthorizationStatusDenied || authorizationStatus == SFSpeechRecognizerAuthorizationStatusRestricted) {
} else{
}
}else{
}
}

以上代码中获取到SFSpeechRecognizerAuthorizationStatus后就能获知Sepeech框架权限信息。语音的使用权限获取操作,相关代码参考如下:

-(void)isHasVoiceRecodingAuthority:(authorityReturnBlock)hasAuthorityBlock{
if ([[[UIDevice currentDevice]systemVersion]floatValue] >= 7.0) {
AVAuthorizationStatus videoAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
if (videoAuthStatus == AVAuthorizationStatusNotDetermined) {
} else if(videoAuthStatus == AVAuthorizationStatusRestricted || videoAuthStatus == AVAuthorizationStatusDenied) {
} else{
}
}
}

通过如上代码中的videoAuthStatus可以获知语音权限信息,实际使用时候首先应该获取语音的权限然后在此基础上再获取Sepeech框架权限信息,只有用户在拥有两个权限的前提下才能进入到下一个“语音识别参数及相关类初始化”环节。

1.2 语音识别参数及相关类初始化

Sepeech框架初始化前需要建立一个音频流的输入通道,AVAudioEngine为这个输入通道不可或缺的节点,通过AVAudioEngine可以生成和处理音频信号,执行音频信号的输入操作,AVAudioInputNode 为音频流的拼接通道,该通道可以实时拼接一段一段的语音,以此完成动态化的音频流数据。AVAudioEngine和AVAudioInputNode 配合使用完成音频流数据获取的准备工作,AVAudioEngine执行方法- (void)prepare后初始化工作才能生效,相关代码如下所示:

AVAudioEngine *bufferEngine = [[AVAudioEngine alloc]init];
AVAudioInputNode *buffeInputNode = [bufferEngine inputNode];
SFSpeechAudioBufferRecognitionRequest *bufferRequest = [[SFSpeechAudioBufferRecognitionRequest alloc]init];
AVAudioFormat *format =[buffeInputNode outputFormatForBus:0];
[buffeInputNode installTapOnBus:0 bufferSize:1024 format:format block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[bufferRequest appendAudioPCMBuffer:buffer];
}];
[bufferEngine prepare];

如上代码中通过buffeInputNode回调接口可以获取到SFSpeechAudioBufferRecognitionRequest完整实时音频数据流信息。

Sepeech框架使用时需要一个关键类-录音器(AVAudioRecorder),该类用来设定语音采集的格式,音频通道,比特率,数据缓存路径等重要信息并完成语音的采集等功能,只有调用AVAudioRecorder的- (BOOL)record方法,语音识别功能才能正常开始,参考如下代码:

[self endTransfer];
NSDictionary *recordSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithFloat: 14400.0], AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleIMA4], AVFormatIDKey,
[NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey,
nil];
NSString *monitorPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"monitor.caf"];
NSURL *monitorURL = [NSURL fileURLWithPath:monitorPath];
AVAudioRecorder *monitor = [[AVAudioRecorder alloc] initWithURL:_monitorURL settings:recordSettings error:NULL];
monitor.meteringEnabled = YES;
[monitor record];

如上代码中语音识别初始化过程中的第一行代码应该首先执行endTransfer,该接口主要功能是初始化音频参数为默认状态:强制销毁音频流输入通道,清除语音录音器,清除音频识别任务。其中初始化音频参数属于较为重要的一个步骤,其目的是为了防止工程中其它功能模块对音频输入参数等信息修改引起的语音识别异常,下文将会进行相关细节讲述。

1.3 开启语音识别并实时接受识别后的文字信息

识别转化函数-(void)startTransfer会一直输出语音识别转换的文字信息,该能力主要依赖于SFSpeechRecognizer类,我们可以从回调接口返回的实时参数SFSpeechRecognitionResult  _Nullable result中获取到识别转化后的文字信息,需要注意的是只有回调无错时输出的文字信息才是有效的,参考代码如下所示:

SFSpeechAudioBufferRecognitionRequest *bufferRequest = [[SFSpeechAudioBufferRecognitionRequest alloc]init];
SFSpeechRecognizer *bufferRec = [[SFSpeechRecognizer alloc]initWithLocale:[NSLocale localeWithLocaleIdentifier:@"zh_CN"]];
[bufferRec recognitionTaskWithRequest:bufferRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
if (error == nil) {
NSString *voiceTextCache = result.bestTranscription.formattedString;
}else{
}
}];

如上代码中的bufferRec为Sepeech框架中的语音识别器,通过该类即可完成对音频数据进行本地化文字信息转化,其中voiceTextCache即为语音实时转化后的信息。

1.4 强制销毁Speech框架数据及重置音频配置数据

当识别结束的时候,需调用-(void)endTransfer方法完成强制关闭音频流通道,删除语音缓冲文件,停止语音监听器等工作,其中还需要对音频模式及参数进行默认重置处理,相关代码参考如下:

-(void)endTransfer{
[bufferEngine stop];
[buffeInputNode removeTapOnBus:0];
bufferRequest = nil;
bufferTask = nil;
[monitor stop];
[monitor deleteRecording];
NSError *error = nil;
[[AVAudioSession sharedInstance] setActive:NO error:&error];
if (error != nil) {
return;
}
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
if (error != nil) {
return;
}
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeDefault error:&error];
if (error != nil) {
return;
}
[[AVAudioSession sharedInstance] setActive:YES error:&error];
if (error != nil) {
return;
}
}

2语音识别波浪效果实现原理

2.1语音识别动画效果

ios语音识别需求经常会要求实时展示语音识别的动画过程,常见的动画效果是根据语音识别音量大小实时展示不同幅度的平移波浪效果,最终效果如下图所示:

如上最终效果图中一共有32个波浪圆点,其中前6个和后6个属于固定静止的圆点,只有中间20个圆点属于随音量大小类似波浪上下浮动的长柱图。

2.2 语音识别动画实现原理

对于上节图中的动态效果实际实现方式存在两种:一、ios系统传统CoreAnimation框架, 二、动态更新圆点frame。如果采用传统CoreAnimation框架那么对于每一个圆点都要做一个浮动效果的动画,在实现流程和系统开销两方面显得得不偿失,而如果采用简单的动态更新圆点frame的方式则事半功倍,本文内容也将基于动态更新圆点frame的方式进行讲述。

对于上图中的动画效果很容易就会联想到数学中的正弦函数图,因此可以把波浪图的横向定义为正弦X轴(实际上ios的坐标也是基于此概念),纵向即为正弦Y轴(高度为音量对坐标的映射值)。首先对于32个浮动圆点本地初始化对应出32个X轴映射坐标x,坐标间隙等值设定,当实时语音音量voluem数据传输过来后通过正弦函数y=F*sin(pi*x-pi*t)可以算出映射的振幅y,其中语音音量限定了最大的音量数据,该数据和正弦函数振幅数据F强相关,相关实现原理图参考如下:

那么实时语音音量voluem数据是如何获取的?实际上前文中的监听器(AVAudioRecorder)就提供了语音音量数据的封装接口- (float)peakPowerForChannel:(NSUInteger)channelNumber,channelNumber为音频输入/输出的通道号,该函数返回的数据即为分贝数值,取值的范围是 -160~0 ,声音峰值越大,越接近0。当获取到实时语音音量voluem数据后进行相应的量化处理就能得到振幅y的映射值。相关代码可参考如下:

AVAudioRecorder *monitor
[monitor updateMeters];
float power = [monitor peakPowerForChannel:0];

2.3 动态更新圆点的frame代码层面的实现

首先生成32个的圆点view,同时添加到当前图层,相关代码如下所示:

self.sinXViews = [NSMutableArray new];
for (NSUInteger i = 0; i < sinaXNum+12; i++) {
UIView *sinView = [[UIView alloc]initWithFrame:CGRectMake(offsetX, offsetY, 3, 3)];
sinView.layer.cornerRadius = 1.5f;
sinView.layer.masksToBounds = YES;
[self.sinXViews addObject:sinView];
[self addSubview:sinView];
}

其中代码中的sinXViews是缓存32个圆点view的数组,该数组是为了方便重置圆点的frame而设定,offsetX为圆点在图层中的具体坐标,需要根据需求进行详细计算获得。

将32个圆点添加到对应图层后核心需要解决的问题就是如何获取圆点振幅了,因为需求实现的最终效不仅是圆点正弦波浪效果,还需要同时对整个波浪进行右边平移,因此正弦函数y=F*sin(pi*x-pi*t)输入数据应该包含圆点坐标x和格式化的时间戳数据t,具体实现代码参考如下:

-(double)fSinValueWithOriginX:(CGFloat)originX timeS:(NSTimeInterval)timeStamp voluem:(CGFloat)voluem{
CGFloat sinF ;
double sinX ;
double fSin = sinF *(4/(pow(sinX,4)+4))*sin(3.14*sinX-3.14*timeStamp);
return fabs(fSin);
}

其中代码中的sinF是根据视觉映射出的圆点y轴振幅,sinX是根据入参originX及圆点x轴坐标间隔值计算得出,timeStamp为音量实时采集时间戳格式化数据。

经过以上步骤实现就只剩更新圆点frame这一步了,这一步相对简单,代码实现参考如下:

for (NSUInteger i = 0; i < self.sinXViews.count; i++) {
if (i > 5 && i < self.sinaXNum+6) {
UIView *sinView = (UIView *)self.sinXViews[i];
CGRect frame = sinView.frame;
double _viewHeight = [self fSinValueWithOriginX:frame.origin.x timeS:timeStamp voluem:Voluem];
double viewHeight ;
frame.size.height = viewHeight;
if (viewHeight == 0) {
return;
}
frame.origin.y = (self.frame.size.height-viewHeight)/2;
[sinView setFrame:frame];
}
}

从以上代码可知遍历self.sinXViews获取到当前圆点view后先取出frame数据,然后通过计算得到当前时刻圆点的振幅viewHeight,最后用viewHeight去更新当前时刻圆点的frame,此时即完成了某个时刻20个圆点的正弦振动动态效果图。

以上为本人工程中的技术实现经验,现做一个简单的总结归纳,如有错误之处请不吝赐教,谢谢阅读!

参考资料

[1] Speech框架资料:https://developer.apple.com/documentation/speech

[2] ios录音功能资料:https://developer.apple.com/documentation/avfaudio

有关iOS基于Speech框架的语音识别波浪动图实现的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  3. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  4. 报告回顾丨模型进化狂飙,DetectGPT能否识别最新模型生成结果? - 2

    导读语言模型给我们的生产生活带来了极大便利,但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。在3月9日智源Live第33期活动「DetectGPT:判断文本是否为机器生成的工具」中,主讲人Eric为我们讲解了DetectGPT工作背后的思路——一种基于概率曲率检测的用于检测模型生成文本的工具,它可以帮助我们更好地分辨文章的来源和可信度,对保护信息真实、防止欺诈等方面具有重要意义。本次报告主要围绕其功能,实现和效果等展开。(文末点击“阅读原文”,查看活动回放。)Ericmitchell斯坦福大学计算机系四年级博士生,由ChelseaFinn和Chri

  5. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  6. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  7. [Vuforia]二.3D物体识别 - 2

    之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐