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了。