iOS SFSpeechRecognizer 语音识别

news2024/11/23 9:47:56

SFSpeechRecognizer 属于 Speech 框架,在 iOS 10 首次出现,并在 iOS13 中进行了比较重大的更新,在 iOS 13 上支持离线语音识别以及语音分析。WWDC2019 展示了其在 AI 领域的进步,其中 iOS 13 设备内置语音识别就是一项比较不错功能。
请添加图片描述

1. 都做了哪些升级

移动端上的离线语音识别模型,减少用户泄露风险,增加了用户隐私。新 API 支持了很多新功能,例如使用语音分析指标跟踪语音质量和语音模式。

同时设备上的离线模型,可以支持无限期地语音识别。与iOS 10 的早期版本只能识别一分钟相比,这是一个很巨大的提升。当然也存在一丢丢的弊端,它并不会像云端模型一样可以在线学习。会导致设备的准确性有些降低。

iOS 13 SFSpeechRecognizer 相比也智能了很多,可以识别语音中的标点符号。比如说句号,它会识别一个句号,同样其他符号也可以支持识别,比如逗号、破折号等等。但是还存在不足的一点就是目前它还不能自己帮助识别的文字添加标点符号,不过这一点已经在 iOS 16 上得到了处理,准确率也做了相应提升,已经可以替换很多付费的语音识别框架了。

2. 实现效果

可以先看效果,可以注意一下,屏幕截图是在飞行模式下拍摄的,完全离线。
这次是通过麦克风进行语音识别的,SFSpeechRecognizer 也可以支持语音文件来识别,有兴趣可以自己了解一下。
请添加图片描述

3. 工作原理

Speech Recognition Flow
上图是实时语音识别的内部实现,语音识别依赖于以下四个:

  • AVAudioEngine
  • SFSpeechRecognizer
  • SFRecognitionTask
  • SFSpeechAudioBufferRecognitionRequest

接下来,我们看看这些是如何工作的。

对于新手而言,我先介绍一下用户隐私权限相关的内容:

添加隐私使用说明
这里需要增加麦克风和语音识别的隐私使用说明, 如下所示。
请添加图片描述

请求权限
SFSpeechRecognizer.requestAuthorization { authStatus in
switch authStatus {
case .authorized:
case .restricted:
case .notDetermined:
case .denied:
}
}

初始化
var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: “zh-CN”))

4. 实现

4.1 配置 AVAudioEngine

AVAudioEngine 负责采集来自麦克风的音频信号,然后将音频信号传入 SFSpeechAudioBufferRecognitionRequest。

let audioEngine = AVAudioEngine()
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)

let inputNode = audioEngine.inputNode

// 在安装 tap 之前先移除上一个 否则可能报
// "*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: nullptr == Tap()"之类的错误
inputNode.removeTap(onBus: 0)
let recordingFormat = inputNode.outputFormat(forBus: 0)

// bufferSize:传入缓冲区的请求大小
// 创建一个“tap”来记录/监视/观察节点的输出
// bus:连接tap的节点输出总线
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
    self.recognitionRequest?.append(buffer)
}

audioEngine.prepare()
try audioEngine.start()

上面的代码在 上安装了一个 tap ,并设置了输出的缓冲区大小,这个缓冲区用来缓存说话或录音时的音频信号。
一旦该缓冲区大小被填满,就会被发送到 SFSpeechAudioBufferRecognitionRequest。

现在我们看看 SFSpeechAudioBufferRecognitionRequest 是如何使用 SFSpeechRecognizer 和 SFSpeechRecognitionTask 将语音转录为文本。

4.2 启用设备语音识别

recognitionRequest.requiresOnDeviceRecognition = true

设置为 false 将使用 Apple Cloud 进行语音识别。需要注意一下,requiresOnDeviceRecognition 仅适用于 iOS 13、macOS Catalina 及更高版本的设备。它需要 Apple 的 A9 或新的处理器,在 iOS 中 iPhone6s 及以上设备支持。

4.3 创建 SFSpeechRecognitionTask

SFSpeechRecognitionTask 是用来运行 SFSpeechAudioBufferRecognitionRequest 和 SFSpeechRecognizer. 这里有 block 和 delegate 两种方式,下面这个例子是使用了 block 方式,它回调了一个 result,我们通过 result 访问不同的语音属性。bestTranscription 标识置信度最高的一个选项

recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest) { result, error in
    // 处理识别结果
    if let result = result {
        DispatchQueue.main.async {
            let transcribedString = result.bestTranscription.formattedString
            self.transcribedText.text = (transcribedString)
        }
    }
    // 异常处理
    if error != nil {
        self.audioEngine.stop()
        inputNode.removeTap(onBus: 0)
        self.recognitionRequest = nil
        self.recognitionTask = nil
    }
}

4.4 SFVoiceAnalytics

SFVoiceAnalytics 是新引入的类,它包含一组语音指标,用于跟踪语音结果中的音高、闪烁和抖动等特征。 可以从 transcription 的 segments 属性访问它们:

for segment in result.bestTranscription.segments {
    guard let voiceAnalytics = segment.voiceAnalytics else { continue }
    
    let pitch = voiceAnalytics.pitch
    let voicing = voiceAnalytics.voicing.acousticFeatureValuePerFrame
    let jitter = voiceAnalytics.jitter.acousticFeatureValuePerFrame
    let shimmer = voiceAnalytics.shimmer.acousticFeatureValuePerFrame
}

4.5 语音识别

在上面我们提到了四个组件,下面代码看看他整体是如何工作的

private let audioEngine = AVAudioEngine()
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "zh-CN"))
private var recognitionTask: SFSpeechRecognitionTask?

func startRecording() throws {

        recognitionTask?.cancel()
        self.recognitionTask = nil

        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        
        let inputNode = audioEngine.inputNode
        inputNode.removeTap(onBus: 0)
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            self.recognitionRequest?.append(buffer)
        }
        
        audioEngine.prepare()
        try audioEngine.start()
        
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        guard let recognitionRequest = recognitionRequest else { fatalError("Unable to create a SFSpeechAudioBufferRecognitionRequest object") }
        // 分部返回结果,每次识别完成就会回调部分结果
        recognitionRequest.shouldReportPartialResults = true
        if #available(iOS 16, *) {
            // iOS 16 已经支持自动添加标点符号,不需要再喊标点符号了
            recognitionRequest.addsPunctuation = true
        }
        if #available(iOS 13, *) {
            if speechRecognizer?.supportsOnDeviceRecognition ?? false{
                recognitionRequest.requiresOnDeviceRecognition = true
            }
        }

        recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest) { result, error in
            if let result = result {
                DispatchQueue.main.async {
                    // 获取置信度最高的 transcription,并格式化为字符串
                    let transcribedString = result.bestTranscription.formattedString
                    self.transcribedText.text = (transcribedString)
                }
            }
            if error != nil {
                self.audioEngine.stop()
                inputNode.removeTap(onBus: 0)
                self.recognitionRequest = nil
                self.recognitionTask = nil
            }
        }
    }

上面的代码比较长,我们讲解一下上面的代码中:

  • 在按开始录音识别的时,先取消任何先前的识别任务。

  • 使用 SFSpeechRecognizer 和 SFSpeechAudioBufferRecognitionRequest 创建识别任务 SFSpeechRecognitionTask。

  • 设置 shouldReportPartialResults 为 true 可以允许在识别期间访问中间结果。

  • result.bestTranscription 会返回具有最高置信度的识别转录,formattedString 属性会给出转录文本。

  • 还可以访问其他属性,例如 speakingRate、averagePauseDuration或 segments 等等。

5. 总结

这基本上就是通过麦克风将语音识别为文本的整个过程了,所使用的 SFSpeechAudioBufferRecognitionRequest是 SFSpeechRecognitionRequest 的子类,它还有另外一个子类 SFSpeechURLRecognitionRequest,它可以通过本地录音文件路径进行识别,有兴趣的自己可以再看一下。

由于再实际应用场景中,还有可能使用第三方 SDK 来提供 AudioBuffer 音频信息的,而我们的示例是使用 AVAudioEngine 来提供 AudioBuffer ,其实也可以通过 recognitionRequest 自己添加 buffer,如果有遇到的可以试一试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/457344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第二届中国抗衰老化妆品产业发展论坛暨国粹国妆品牌与文化交流会在京召开

消费日报网讯(记者 王儒)4月18日,以“国粹国妆 抗衰美丽“为主题的第二届中国抗衰老化妆品产业发展论坛暨国粹国妆品牌与文化交流会在北京召开。大会旨在挖掘国粹力量,搭建沟通交流合作的平台,推动中国高端化妆品品牌建…

网工神器:PNETLab模拟器踩坑过程

目录 0、前言 1、PNETLab介绍 2、下载安装 2.1 下载 2.2 导入 2.3 启动 2.4 注册和登录 3、汉化 4、镜像 5、控制台 5.1、HTML控制台 5.2、默认控制台 6、总结 0、前言 由于工作需要,想测试一下SD-WAN,手边既没有测试环境又没有测试设备。突然想…

八年软件测试生涯,是时候做出改变了

五年前,我在南方的大城市:广州,做着一个快乐的游戏测试,工作不太忙,对一切技术充满了好奇心。测试工作不专业,也不受重视。但我有自己的快乐。工作不忙的时候,我今天学学Python,明天…

uniapp + vue3开发中组合式函数必须是一个同步函数

目录 vue3中的组合式函数用法: 官网示例异步组合式函数:同步函数写法 改造成导出async组合式函数时: uniapp无法使用async组合式函数的原因: vue3中的组合式函数使用时,导出的组合式函数必须是一个同步函数。 vue3…

塔望3W消费战略全案丨元力参堂:从0到1,超级大单品跨越式增长

元力参堂 客户:上海弥富生物科技有限公司 品牌:元力参堂 服务:3W消费战略 品牌全案 项目背景 2020年初,一场突如其来的疫情让我们按下了暂停键,大家经历着疫情的考验。长时间的隔离、封闭影响到生产、消费、投资、物…

ChatGPT3分钟写的千字福尔摩斯小说,老师都分辨不出真假

AI写小说!感受ChatGPT3分钟写的千字福尔摩斯小说!逆天! ChatGPT写悬疑小说 其实在最初,测试者要求ChatGPT写一个5000字的故事,但AI居然直接罢工,还言简意赅:“不,太长了。” 于是…

MySQL库和表的操作

1 什么是数据库?什么是SQL? 科学的组织和存储数据,如何高效获取和维护数据 2 一条SQL语句的执行过程 SQL语句就是一个数据库能够识别的指令语言 在实际操作过程中,创建连接,连接MySQL的server mysql -uroot -P330…

PMP证书备考攻略+PMP知识点汇总

一,考PMP好处多 1.能力提升 大型项目,领导专业团队 2.升职加薪 晋升管理岗,优先升职加薪 3.招投标加分 具有PMP证书,企业招标有加分 4.转型利器 助力转型,拓宽职业发展 5.公司支持 企业鼓励学习,报销费用 6…

LVS负载均衡-DR

1.DR模式中每台主机都有一个VIP地址 虚拟网址放在lo网卡上(回环网卡) arp_ignore1 Arp_announce2 系统不使用IP包的源地址来设置ARP请求的源地址,而选择发送接口的IP地址 2.内核参数修改 3.vim /etc/rc.conf 开机自启动 Chmod x /etc/rc.d…

Spring Security实战(六)—— 跨域与CORS

跨域是一种浏览器同源安全策略,即浏览器单方面限制脚本的跨域访问。 一、认识跨域 跨域(Cross-Origin)指的是在Web开发中,当一个网页的内容要从不同源(即不同的域名、协议或端口)获取时,就会发…

时序分析与时序约束知识总结

文章目录 时序分析如何查看时序报告时序分析的分类和任务HOLD违例修复:SETUP违例修复:时序违例的修复 时序约束约束的分类时序约束的作用SDF文件OCVPVT共同路径悲观效应(CPP)setup time与hold time和什么有关clock Jitter与clock Skewsetup和hold裕度计算…

10.java程序员必知必会类库之邮件

前言 邮件功能在当前互联网应用中已经是很成熟的功能,也是作为java程序员应该掌握的技能。常见使用场景有: 电商软件开电子发票,需要发到用户邮箱里面生产实时报警,需要发到邮箱里面银行软件申请的征信报告,电子账单…

Django框架之Admin站点管理

Django的强大体现在其内置的Admin模块可以使得开发人员在不做任何编码的情况下就拥有网站后台管理功能。 概述 内容发布:负责添加、修改、删除内容 内容访问查看 配置admin应用 在settings.py中添加django.contrib.admin 默认已添加 创建管理员账户 python man…

九、1~8文章的阶段案例

一、案例 现在我们来做一个相对综合一点的练习:书籍购物车 案例说明: 1.在界面上以表格的形式,显示一些书籍的数据;2.在底部显示书籍的总价格;3.点击或者-可以增加或减少书籍数量(如果为1,那…

【论文精读】ISBI 2022 - Retinal Vessel Segmentation with Pixel-wise Adaptive Filters

【论文精读】ISBI 2022 - Retinal Vessel Segmentation with Pixel-wise Adaptive Filters 【论文原文】:Retinal Vessel Segmentation with Pixel-wise Adaptive Filters 【作者信息】:Li, Mingxing and Zhou, Shenglong and Chen, Chang and Zhang, …

【Linux】线程-线程控制

线程控制 线程控制线程创建线程终止线程等待分离线程 线程控制 使用线程需要注意的是,需要引入头文件pthread.h,并且在编译的时候,需要使用-lpthread 线程创建 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*…

QT中TCP的学习

文章目录 qt中TCP的实现 qt中TCP的实现 学习视频 QT中可以通过TCP协议让服务器和客户端之间行通信。服务器和客户端的具体流程 下方的信号都是系统提供的,我们只需要写相应的槽函数 A、服务器: 创建QTcpServer对象启动服务器(监听&…

Flutter ListView组件详解

今天是2023年4月24日 今天重新复习了一下关于ListView的内容,现在就重新整理一下关于ListView的内容和理解 : (1)ListView和Column之间有什么区别? 在我理解中ListView和Column都是可以有很多子组件的组件,它们之间区别在于它们排列的形式和…

python实现AI写歌词GUI版本【文末源码】

**引言:**自然语言处理作为人工智能的一个重要分支,在我们的生活中得到了广泛应用。其中RNN算法作为自然语言处理的经典算法之一,是文本生成的重要手段。而今天我们就将利用RNN算法建立一个写歌词的软件。其中的界面如下: RNN指的…

使用binding时,LayoutSubscribeFragmentBinding报错

LayoutRecommendFragmentBinding是一个DataBinding类,它由编译器自动生成,用于访问布局文件中的视图。如果你在代码中看到LayoutRecommendFragmentBinding报红(提示未解析的引用),可能有以下原因: 1. 检查…