首先想强调一下“语音识别”四个字字面意义上的需求:用户说话然后马上把用户说的话转成文字显示!,这才是开发者真正需要的功能。

做需求之前其实是先谷歌百度一下看有没有造好的轮子直接用,结果真的很呵呵,都是标着这个库深入学习的标题,里面调用一下api从URL里取出一个本地语音文件进行识别,这就没了? 最基本的需求都没法实现。

今天整理下对于此功能的两种实现方式:

首先看下识别请求的API有两种 SFSpeechAudioBufferRecognitionRequest 和 SFSpeechURLRecognitionRequest ,并且实现解析的方式也有两种 block 和 delegate。 我就相互组合下两种方法把这些内容都能涵盖。

在开发之前需要先在info.plist注册用户隐私权限,虽然大家都已经知道了我还是说一嘴为了本文的完整性。

Privacy - Microphone Usage Description
Privacy - Speech Recognition Usage Description  

再使用requestAuthorization来请求使用权限

    [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
// 对结果枚举的判断
}];

关于麦克风的权限在首次开始录音时也会提出权限选择。

一、 SFSpeechAudioBufferRecognitionRequest 加上 block的方式

用这种方式实现主要分为以下几个步骤

①多媒体引擎的建立

成员变量需要添加以下几个属性,便于开始结束释放等

@property(nonatomic,strong)SFSpeechRecognizer *bufferRec;
@property(nonatomic,strong)SFSpeechAudioBufferRecognitionRequest *bufferRequest;
@property(nonatomic,strong)SFSpeechRecognitionTask *bufferTask;
@property(nonatomic,strong)AVAudioEngine *bufferEngine;
@property(nonatomic,strong)AVAudioInputNode *buffeInputNode;

初始化建议写在启动的方法里,便于启动和关闭,如果准备使用全局的也可以只初始化一次

    self.bufferRec = [[SFSpeechRecognizer alloc]initWithLocale:[NSLocale localeWithLocaleIdentifier:@"zh_CN"]];
self.bufferEngine = [[AVAudioEngine alloc]init];
self.buffeInputNode = [self.bufferEngine inputNode];

②创建语音识别请求

    self.bufferRequest = [[SFSpeechAudioBufferRecognitionRequest alloc]init];
self.bufferRequest.shouldReportPartialResults = true;

shouldReportPartialResults 其中这个属性可以自行设置开关,是等你一句话说完再回调一次,还是每一个散碎的语音片段都会回调。

③建立任务,并执行任务

    // block外的代码也都是准备工作,参数初始设置等
self.bufferRequest = [[SFSpeechAudioBufferRecognitionRequest alloc]init];
self.bufferRequest.shouldReportPartialResults = true;
__weak ViewController *weakSelf = self;
self.bufferTask = [self.bufferRec recognitionTaskWithRequest:self.bufferRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
// 接收到结果后的回调
}]; // 监听一个标识位并拼接流文件
AVAudioFormat *format =[self.buffeInputNode outputFormatForBus:0];
[self.buffeInputNode installTapOnBus:0 bufferSize:1024 format:format block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[weakSelf.bufferRequest appendAudioPCMBuffer:buffer];
}]; // 准备并启动引擎
[self.bufferEngine prepare];
NSError *error = nil;
if (![self.bufferEngine startAndReturnError:&error]) {
NSLog(@"%@",error.userInfo);
};
self.showBufferText.text = @"等待命令中.....";

对runloop稍微了解过的人都知道,block外面的代码是在前一个运行循环先执行的, 正常的启动流程是 先初始化参数 然后 启动引擎,然后会不断地调用拼接buffer的这个回调方法,然后一个单位的buffer攒够了后会回调一次上面的语音识别结果的回调,有时候没声音也会调用buffer的方法,但是不会调用上面的resulthandler回调,这个方法内部应该有个容错(音量power没到设定值会自动忽略)。

④接收到结果的回调

结果的回调就是在上面resultHandler里面的block里了,执行后返回的参数就是result和error了,可以针对这个结果做一些操作。

        if (result != nil) {
self.showBufferText.text = result.bestTranscription.formattedString;
}
if (error != nil) {
NSLog(@"%@",error.userInfo);
}

这个结果类型SFSpeechRecognitionResult可以看看里面的属性  ,有最佳结果,还有备选结果的数组。如果想做精确匹配的应该得把备选数组的答案也都过滤一遍。

⑤结束监听

    [self.bufferEngine stop];
[self.buffeInputNode removeTapOnBus:0];
self.showBufferText.text = @"";
self.bufferRequest = nil;
self.bufferTask = nil;

这个中间的bus是临时标识的节点,大概理解和端口的概念差不多。

二、SFSpeechURLRecognitionRequest 和 delegate的 方法

block和delegate的主要区别是,block方式使用简洁, delegate则可以有更多的自定义需求的空间,因为里面有更多的结果回调生命周期方法。

这五个方法也没什么好说的,都是顾名思义。 要注意的一点是第二个方法会调用多次,第三个方法会在一句话说完时调用一次。

// Called when the task first detects speech in the source audio
- (void)speechRecognitionDidDetectSpeech:(SFSpeechRecognitionTask *)task; // Called for all recognitions, including non-final hypothesis
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didHypothesizeTranscription:(SFTranscription *)transcription; // Called only for final recognitions of utterances. No more about the utterance will be reported
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishRecognition:(SFSpeechRecognitionResult *)recognitionResult; // Called when the task is no longer accepting new audio but may be finishing final processing
- (void)speechRecognitionTaskFinishedReadingAudio:(SFSpeechRecognitionTask *)task; // Called when the task has been cancelled, either by client app, the user, or the system
- (void)speechRecognitionTaskWasCancelled:(SFSpeechRecognitionTask *)task; // Called when recognition of all requested utterances is finished.
// If successfully is false, the error property of the task will contain error information
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishSuccessfully:(BOOL)successfully;

这种实现的思路是,先实现一个录音器(可以手动控制开始结束,也可以是根据音调大小自动开始结束的同步录音器类似于会说话的汤姆猫),然后将录音文件存到一个本地目录,然后使用URLRequest的方式读取出来进行翻译。步骤分解如下

①建立同步录音器

需要以下这些属性

/** 录音设备 */
@property (nonatomic, strong) AVAudioRecorder *recorder;
/** 监听设备 */
@property (nonatomic, strong) AVAudioRecorder *monitor;
/** 录音文件的URL */
@property (nonatomic, strong) NSURL *recordURL;
/** 监听器 URL */
@property (nonatomic, strong) NSURL *monitorURL;
/** 定时器 */
@property (nonatomic, strong) NSTimer *timer;

属性的初始化

    // 参数设置
NSDictionary *recordSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithFloat: 14400.0], AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleIMA4], AVFormatIDKey,
[NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey,
nil]; NSString *recordPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"record.caf"];
_recordURL = [NSURL fileURLWithPath:recordPath]; _recorder = [[AVAudioRecorder alloc] initWithURL:_recordURL settings:recordSettings error:NULL]; // 监听器
NSString *monitorPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"monitor.caf"];
_monitorURL = [NSURL fileURLWithPath:monitorPath];
_monitor = [[AVAudioRecorder alloc] initWithURL:_monitorURL settings:recordSettings error:NULL];
_monitor.meteringEnabled = YES;

其中参数设置的那个字典里,的那些常量大家不用过于上火,这是之前写的代码直接扒来用的,上文中设置的最优语音质量。

②开始与结束

要想通过声音大小来控制开始结束的话,需要在录音器外再额外设置个监听器用来 查看语音的大小 通过peakPowerForChannel 方法查看当前话筒环境的声音环境音量。并且有个定时器来控制音量检测的周期。大致代码如下

- (void)setupTimer {
[self.monitor record];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateTimer) userInfo:nil repeats:YES]; //董铂然博客园
} // 监听开始与结束的方法
- (void)updateTimer { // 不更新就没法用了
[self.monitor updateMeters]; // 获得0声道的音量,完全没有声音-160.0,0是最大音量
float power = [self.monitor peakPowerForChannel:0]; // NSLog(@"%f", power);
if (power > -20) {
if (!self.recorder.isRecording) {
NSLog(@"开始录音");
[self.recorder record];
}
} else {
if (self.recorder.isRecording) {
NSLog(@"停止录音");
[self.recorder stop];
[self recognition];
}
}
}

③语音识别的任务请求

- (void)recognition {
// 时钟停止
[self.timer invalidate];
// 监听器也停止
[self.monitor stop];
// 删除监听器的录音文件
[self.monitor deleteRecording]; //创建语音识别操作类对象
SFSpeechRecognizer *rec = [[SFSpeechRecognizer alloc]initWithLocale:[NSLocale localeWithLocaleIdentifier:@"zh_CN"]];
// SFSpeechRecognizer *rec = [[SFSpeechRecognizer alloc]initWithLocale:[NSLocale localeWithLocaleIdentifier:@"en_ww"]]; //董铂然博客园 //通过一个本地的音频文件来解析
SFSpeechRecognitionRequest * request = [[SFSpeechURLRecognitionRequest alloc]initWithURL:_recordURL];
[rec recognitionTaskWithRequest:request delegate:self];
}

这段通过一个本地文件进行识别转汉字的代码,应该是网上传的最多的,因为不用动脑子都能写出来。 但是单有这一段代码基本是没有什么卵用的。(除了人家微信现在有个长按把语音转文字的功能, 其他谁的App需求 我真想不到会直接拿出一个本地音频文件来解析,自动生成mp3歌词?周杰伦的歌解析难度比较大,还有语音识别时间要求不能超过1分钟)

④结果回调的代理方法

- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishRecognition:(SFSpeechRecognitionResult *)recognitionResult
{
NSLog(@"%s",__FUNCTION__);
NSLog(@"%@",recognitionResult.bestTranscription.formattedString);
[self setupTimer];
}

用的最多的就这个方法了,另外不同时刻的回调方法可以按需添加,这里也就是简单展示,可以看我的demo程序里有更多功能。

https://github.com/dsxNiubility/SXSpeechRecognitionTwoWays

   

iOS10在语音相关识别相关功能上有了一个大的飞跃,主要体现在两点 一点就是上面的语音识别,另一点是sirikit可以实现将外部的信息透传到App内进行操作,但是暂时局限性比较明显,只能够实现官网所说 叫车,发信息 等消息类型,甚至连“打开美团 搜索烤鱼店”这种类型 都还不能识别,所以暂时也无法往下做过多研究,等待苹果之后的更新吧。





【iOS10 SpeechRecognition】语音识别 现说现译的最佳实践的更多相关文章

  1. 现学现卖】IntelliJ+EmmyLua 开发调试Unity中Xlua

    http://blog.csdn.net/u010019717/article/details/77510066?ref=myread http://blog.csdn.NET/u010019717 ...

  2. 『现学现忘』Git对象 — 16、Tree对象详解

    目录 1.Tree对象介绍 2.Tree对象说明 (1)初始化一个新的本地版本库 (2)创建一个树对象(重点) (3)创建第二个文件(重点) (4)将第一个树对象加入暂存区,使其成为新的树对 3.总结 ...

  3. Markdown的使用---现学现用

    Markdown 是一种轻量级的标记语言,它的的优点很多,在这就不重复说了,最吸引人的应该是它清新简洁的界面,并且语法简单易学.最为常用的语法不会很多,用多了便自然而然的记住了. 可选工具 网上说如果 ...

  4. Django学习笔记(现学现写,实时更新)

    说明:我是先上手做一些简单的例子,然后在尝试的过程中理解Django的原理,笔记也是按这个思路来的. 一.Django结构与基本文件介绍 1. django-admin.py 工程管理工具,主要用于创 ...

  5. angularjs现学现记之—$apply()和$digest()

    angularjs的双向数据绑定是个重要的特性,它让我们的代码简洁了许多,然而它又是如何知道数据发生了变化并改变页面的呢.最近看了一篇介绍觉得十分有用 首先,在angularjs中是有$watch事件 ...

  6. 现学现卖——Keil uVision 使用教程

    Keil uVision 使用教程 1.如果有旧的工程在,先关闭旧工程.Project -> Close Project2.新建工程.Project -> New uVision Proj ...

  7. 现学现卖——VS2013 C#测试

    VS2013 C#测试 首先安装Unit Test Generator.方法为:工具->扩展和更新->联机->搜索“Unit Test Generator”,图标为装有蓝色液体的小试 ...

  8. 程序猿的日常——Mybatis现学现卖

    最近有一个小项目需求,需要用spring mvc + mybatis实现一个复杂的配置系统.其中遇到了很多不太常见的问题,在这里特意记录下: 主要涉及的内容有 事务 多表删除 插入并返回主键 1 sp ...

  9. java高并发系列 - 第27天:实战篇,接口性能成倍提升,让同事刮目相看,现学现用

    这是java高并发系列第27篇文章. 开发环境:jdk1.8. 案例讲解 电商app都有用过吧,商品详情页,需要给他们提供一个接口获取商品相关信息: 商品基本信息(名称.价格.库存.会员价格等) 商品 ...

随机推荐

  1. ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”

    在<ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式>中,我们通过几个简单的实例演示了如何呈现一个错误页面,这些错误页面的呈现分别由三个对应的中间件来完成,接下来我们将 ...

  2. ES5对Array增强的9个API

    为了更方便的对Array进行操作,ES5规范在Array的原型上新增了9个方法,分别是forEach.filter.map.reduce.reduceRight.some.every.indexOf ...

  3. tLinux 2.2下安装Mono 4.8

    Tlinux2.2发行版基于CentOS 7.2.1511研发而成,内核版本与Tlinux2.0发行版保持完全一致,更加稳定,并保持对Tlinux2.0的完全兼容.Mono 4版本要求CentOS 7 ...

  4. 从零开始编写自己的C#框架(28)——建模、架构与框架

    文章写到这里,我一直在犹豫是继续写针对中小型框架的设计还是写些框架设计上的进阶方面的内容?对于中小型系统来说,只要将前面的内容进行一下细化,写上二三十章具体开发上的细节,来说明这个通用框架怎么开发的就 ...

  5. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  6. [C#] 简单的 Helper 封装 -- SecurityHelper 安全助手:封装加密算法(MD5、SHA、HMAC、DES、RSA)

    using System; using System.IO; using System.Security.Cryptography; using System.Text; namespace Wen. ...

  7. 深入浅出JavaScript之原型链&继承

    Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instanc ...

  8. [LintCode]——目录

    Yet Another Source Code for LintCode Current Status : 232AC / 289ALL in Language C++, Up to date (20 ...

  9. javascript高性能编程-算法和流程控制

          代码整体结构是执行速度的决定因素之一. 代码量少不一定运行速度快, 代码量多也不一定运行速度慢. 性能损失与代码组织方式和具体问题解决办法直接相关.       倒序循环可以提高性能,如: ...

  10. Android Studio 编译单个module

    前期自己要把gradle环境变量配置好 在Terminal中gradle命令行编译apk 输入gradle assembleRelease 会编译全部module编译单个modulecd ./xiru ...