Section 6/151 menit
6. Speech Framework — Pengenalan Suara
6. Speech Framework — Pengenalan Suara
On-Device Speech Recognition
swift
import Speech
import AVFoundation
class SpeechRecognizer: NSObject, ObservableObject {
@Published var transcript = ""
@Published var isRecording = false
@Published var error: Error?
private var recognitionTask: SFSpeechRecognitionTask?
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private let audioEngine = AVAudioEngine()
private var speechRecognizer: SFSpeechRecognizer?
override init() {
super.init()
speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id-ID"))
}
func requestPermission() async -> Bool {
await withCheckedContinuation { continuation in
SFSpeechRecognizer.requestAuthorization { status in
continuation.resume(returning: status == .authorized)
}
}
}
@MainActor
func startRecording() throws {
// Stop existing task
recognitionTask?.cancel()
recognitionTask = nil
// Configure audio session
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
// Create recognition request
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let recognitionRequest else { throw SpeechError.requestCreationFailed }
recognitionRequest.shouldReportPartialResults = true
recognitionRequest.requiresOnDeviceRecognition = true // iOS 13+ on-device
// Custom vocabulary untuk domain-spesifik (iOS 16+)
// recognitionRequest.customizedLanguageModel = customModel
// Start recognition task
guard let speechRecognizer, speechRecognizer.isAvailable else {
throw SpeechError.recognizerUnavailable
}
recognitionTask = speechRecognizer.recognitionTask(
with: recognitionRequest
) { [weak self] result, error in
Task { @MainActor [weak self] in
if let result {
self?.transcript = result.bestTranscription.formattedString
}
if error != nil || result?.isFinal == true {
self?.stopRecording()
}
}
}
// Tap audio input
let inputNode = audioEngine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in
self?.recognitionRequest?.append(buffer)
}
audioEngine.prepare()
try audioEngine.start()
isRecording = true
}
@MainActor
func stopRecording() {
audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: 0)
recognitionRequest?.endAudio()
recognitionTask?.cancel()
recognitionTask = nil
isRecording = false
try? AVAudioSession.sharedInstance().setActive(false)
}
}
Custom Language Model (iOS 17+)
swift
// Speech: custom language model untuk domain-spesifik (medical, legal, tech)
import Speech
class CustomLanguageModelBuilder {
func buildMedicalLanguageModel() async throws -> SFCustomLanguagueModel {
let configuration = SFCustomLanguageModel.Configuration()
// Vocabulary domain-spesifik
let medicalTerms: Set<String> = [
"hipertensi", "aritmia", "echokardiografi",
"angioplasty", "kateterisasi jantung",
"troponin", "BNP", "creatinine"
]
// Contoh frasa yang sering digunakan
let samplePhrases: [String] = [
"pasien mengalami nyeri dada sejak kemarin",
"tekanan darah 140 per 90",
"denyut jantung 72 per menit",
]
return try await SFCustomLanguagueModel.prepare(
assetPath: URL.documentsDirectory.appendingPathComponent("MedicalLM"),
configuration: configuration,
customVocabulary: medicalTerms,
customPhrases: samplePhrases
)
}
}