SwiftUI之声音识别
配合MachineLearning功能,判断说话的人是男性还是女性。需要实现这个功能,首先需要有MachineLearning模块,即已经训练过的数据。
苹果自带了CreateML工具,可以自己训练图片,文字,声音,甚至图表,关键在于收集格式合适以及数量可观的数据。

上图是训练音频的软件,使用方法很简单,类似于文本训练,通过文件夹区分男女声,然后在文件夹下放置搜集的男女声音频文件,为了准确性,可以将文件分类为训练用,校验用,以及测试用。即分成各种文件夹,而音频文件越多越好。



然后将文件路径放到训练工具CreateML中

点击▶️,训练即可。训练后的模块准确性跟很多东西有关,用于训练的文件越多自然越好。

从Output中直接拖出训练好的模块,即可放入工程中。
以上只是简单介绍制作方法。
当模块拖到工程后,如图

Inputs为我们需要输入模块的内容,即音频样本(audioSamples),而模块会输出该样本最可能的分类及可能性比例。
因为需要用到音频获取以及声音分析,所以首先

定义一个ObservableObject类型变量
class testMessage: ObservableObject{
@Published var message = "Nothing"
}
因为需要在不同的视图内传递数据,使用了protocol以及notification两种方式传递数据
在contentView中添加以下变量
//另一种数据传递方式测试用
@State var message = "Nothing"
@ObservedObject var myMessage = testMessage()
//AVEngine
private let audioEngine = AVAudioEngine()
//声音识别模块
private var soundClassifier = MySoundClassifier 1()
var inputFormat: AVAudioFormat!
var analyzer: SNAudioStreamAnalyzer!
var resultsObserver = ResultsObserver()
let analysisQueue = DispatchQueue(label: "dongdong")
初始化函数
init(){
resultsObserver.delegate = self
inputFormat = audioEngine.inputNode.inputFormat(forBus: 0)
analyzer = SNAudioStreamAnalyzer(format: inputFormat)
}
启动audio函数
func startAudioEngine() {
do {
let request = try SNClassifySoundRequest(mlModel: soundClassifier.model)
try analyzer.add(request, withObserver: resultsObserver)
} catch {
print("Unable to prepare request: \(error.localizedDescription)")
return
}
audioEngine.inputNode.installTap(onBus: 0, bufferSize: 8192, format: inputFormat) { buffer, time in
self.analysisQueue.async {
self.analyzer.analyze(buffer, atAudioFramePosition: time.sampleTime)
}
}
do{
try audioEngine.start()
}catch( _){
print("error in starting the Audio Engin")
}
}
定义procotol函数内容
数据传递
func displayPredictionResult(identifier: String, confidence: Double) {
DispatchQueue.main.async {
//self.myMessage.message = ("Recognition: \(identifier)\nConfidence \(confidence)")
self.myMessage.message = self.translateString(identifier: identifier)
//print(self.myMessage.message)
}
}
因为模块输出的内容比较单一且训练的时候是英文分类,所以做个简单的处理
func translateString(identifier: String) -> String{
switch identifier{
case "male":
return "男生⛹️👱🏼♂️"
default:
return "女生👩🏻🦳💄"
}
}
在ContentView的所有定义之外,定义类,参考苹果官网对于sound介绍

class ResultsObserver: NSObject, SNResultsObserving {
var delegate: GenderClassifierDelegate?
func request(_ request: SNRequest, didProduce result: SNResult) {
guard let result = result as? SNClassificationResult,
let classification = result.classifications.first else { return }
class ResultsObserver: NSObject, SNResultsObserving {
var delegate: GenderClassifierDelegate?
func request(_ request: SNRequest, didProduce result: SNResult) {
guard let result = result as? SNClassificationResult,
let classification = result.classifications.first else { return }
// Determine the time of this result.
let formattedTime = String(format: "%.2f", result.timeRange.start.seconds)
print("Analysis result for audio at time: \(formattedTime)")
let confidence = classification.confidence * 100.0
delegate?.displayPredictionResult(identifier: classification.identifier, confidence: confidence)
//使用通知传递数据,但是数据只能为[Hasable:Any?]格式
//必须Dispathcqueue
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name("ForMyTest"), object: nil,userInfo: ["性别": classification.identifier,"概率":String(confidence)])
}
}
}
以上代码基本和苹果官网一致,不同之处有协议方式传递数据
delegate?.displayPredictionResult(identifier: classification.identifier, confidence: confidence)
然后测试通过同志传递数据用于测试内容
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name("ForMyTest"), object: nil,userInfo: ["性别": classification.identifier,"概率":String(confidence)])
}
然后加入协议,所有内容之外面
protocol GenderClassifierDelegate {
func displayPredictionResult(identifier: String, confidence: Double)
}
终于进入body部分
简单的代码,见注释
var body: some View {
Text(myMessage.message)
.onAppear{
self.startAudioEngine()
}
.onTapGesture {
self.message = "what"
}
//此处接受通知,处理数据,此时@state message也能更改了
//当有通知内容传递过来后,处理类容,打印出结果.
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ForMyTest"))){
label in
//print(label)
if let new = label.userInfo as? [String:String]
{
self.message = new["性别"]!
print(self.message)
}
}
}
通知传递数据方式相对步骤少一些,此处没有现实出来,只是打印了一下,与protocol方式结果完全一致,无问题,可参考备用.
然后运行程序,然后程序就挂了
因为还没有开麦克风权限,如图,info.list中

然后再运行
识别出声音类型

非常简单的使用到了ML的小程序,苹果自带的CreateML功能还是很强大的,支持很多类型,如

但是在训练模块时,真正深入ML时,CreateML则感觉功能有些不够了,这时候还是的用Python了。