SwiftWhisper实战教程:构建一个完整的iOS语音备忘录应用

发布时间:2026/7/6 17:50:17

SwiftWhisper实战教程:构建一个完整的iOS语音备忘录应用 SwiftWhisper实战教程构建一个完整的iOS语音备忘录应用【免费下载链接】SwiftWhisper The easiest way to transcribe audio in Swift项目地址: https://gitcode.com/gh_mirrors/sw/SwiftWhisper 想要为你的iOS应用添加语音转文字功能吗SwiftWhisper是Swift开发者的终极解决方案这个强大的开源库让语音识别变得简单快捷无需复杂的配置即可实现高质量的音频转录。在本篇完整指南中我将带你从零开始使用SwiftWhisper构建一个功能齐全的语音备忘录应用。什么是SwiftWhisperSwiftWhisper是一个基于OpenAI Whisper模型的Swift语音识别库专为iOS和macOS开发者设计。它提供了简单易用的API让你能够轻松地将语音转换为文字支持多种语言和实时转录功能。这个库的核心优势在于其简洁的接口和强大的性能。通过封装whisper.cpp的底层实现SwiftWhisper为Swift开发者提供了原生友好的API同时保持了高效的转录速度。项目环境准备1. 安装SwiftWhisper首先在你的iOS项目中添加SwiftWhisper依赖。打开Xcode项目选择File → Add Packages然后输入仓库地址https://github.com/exPHAT/SwiftWhisper.git或者如果你使用Package.swift文件管理依赖dependencies: [ .package(url: https://github.com/exPHAT/SwiftWhisper.git, from: 1.0.0) ]2. 下载语音模型SwiftWhisper需要预训练的语音模型才能工作。前往Hugging Face模型仓库下载适合的模型文件。对于移动设备推荐使用tiny.bin或base.bin模型它们在性能和准确度之间取得了良好平衡。将下载的模型文件添加到Xcode项目的Resources文件夹中确保在构建时包含这些文件。构建语音备忘录应用创建录音功能首先我们需要创建音频录制功能。使用iOS的AVFoundation框架来录制音频import AVFoundation class AudioRecorder: NSObject { private var audioRecorder: AVAudioRecorder? private var audioEngine AVAudioEngine() func startRecording() throws { let audioSession AVAudioSession.sharedInstance() try audioSession.setCategory(.record, mode: .default) try audioSession.setActive(true) let settings [ AVFormatIDKey: Int(kAudioFormatLinearPCM), AVSampleRateKey: 16000.0, AVNumberOfChannelsKey: 1, AVLinearPCMBitDepthKey: 16, AVLinearPCMIsFloatKey: false, AVLinearPCMIsBigEndianKey: false ] as [String : Any] let tempURL FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension(wav) audioRecorder try AVAudioRecorder(url: tempURL, settings: settings) audioRecorder?.record() } func stopRecording() - URL? { audioRecorder?.stop() return audioRecorder?.url } }集成SwiftWhisper转录功能现在让我们集成SwiftWhisper来实现语音转文字功能import SwiftWhisper class TranscriptionManager { private var whisper: Whisper? func setupWhisper() throws { guard let modelURL Bundle.main.url(forResource: tiny, withExtension: bin) else { throw NSError(domain: TranscriptionManager, code: 1, userInfo: [NSLocalizedDescriptionKey: Model file not found]) } whisper Whisper(fromFileURL: modelURL) whisper?.delegate self } func transcribeAudio(from url: URL) async throws - String { guard let whisper whisper else { throw NSError(domain: TranscriptionManager, code: 2, userInfo: [NSLocalizedDescriptionKey: Whisper not initialized]) } // 将音频文件转换为16kHz PCM格式 let audioFrames try await convertAudioToPCM(url: url) // 开始转录 let segments try await whisper.transcribe(audioFrames: audioFrames) // 合并所有片段文本 let fullText segments.map { $0.text }.joined(separator: ) return fullText } private func convertAudioToPCM(url: URL) async throws - [Float] { // 这里需要实现音频转换逻辑 // 可以使用AudioKit或手动转换 return [] // 返回转换后的PCM数据 } } extension TranscriptionManager: WhisperDelegate { func whisper(_ aWhisper: Whisper, didUpdateProgress progress: Double) { print(转录进度: \(Int(progress * 100))%) } func whisper(_ aWhisper: Whisper, didProcessNewSegments segments: [Segment], atIndex index: Int) { for segment in segments { print(新转录片段: \(segment.text)) } } func whisper(_ aWhisper: Whisper, didCompleteWithSegments segments: [Segment]) { print(转录完成共\(segments.count)个片段) } func whisper(_ aWhisper: Whisper, didErrorWith error: Error) { print(转录错误: \(error.localizedDescription)) } }设计用户界面创建一个简洁美观的用户界面包含录音按钮、转录结果显示和备忘录列表import SwiftUI struct VoiceMemoApp: App { var body: some Scene { WindowGroup { ContentView() .environmentObject(MemoStore()) } } } struct ContentView: View { StateObject private var recorder AudioRecorder() StateObject private var transcriber TranscriptionManager() EnvironmentObject var memoStore: MemoStore State private var isRecording false State private var transcriptionText State private var isLoading false var body: some View { NavigationView { VStack(spacing: 20) { // 录音控制区域 VStack { Button(action: toggleRecording) { Circle() .fill(isRecording ? Color.red : Color.blue) .frame(width: 80, height: 80) .overlay( Image(systemName: isRecording ? stop.fill : mic.fill) .foregroundColor(.white) .font(.system(size: 30)) ) } Text(isRecording ? 正在录音... : 点击开始录音) .foregroundColor(.secondary) } .padding(.top, 40) // 转录结果显示 if !transcriptionText.isEmpty { VStack(alignment: .leading, spacing: 10) { Text(转录结果) .font(.headline) ScrollView { Text(transcriptionText) .padding() .frame(maxWidth: .infinity, alignment: .leading) .background(Color(.systemGray6)) .cornerRadius(10) } .frame(height: 200) Button(保存备忘录) { saveMemo() } .buttonStyle(.borderedProminent) } .padding() } // 加载状态 if isLoading { ProgressView(正在转录...) .padding() } Spacer() // 备忘录列表 List { ForEach(memoStore.memos) { memo in VStack(alignment: .leading, spacing: 5) { Text(memo.title) .font(.headline) Text(memo.content.prefix(100) ...) .font(.caption) .foregroundColor(.secondary) Text(memo.date, style: .date) .font(.caption2) .foregroundColor(.gray) } } } .listStyle(.plain) } .navigationTitle(语音备忘录) .onAppear { do { try transcriber.setupWhisper() } catch { print(初始化失败: \(error)) } } } } private func toggleRecording() { if isRecording { // 停止录音并开始转录 if let audioURL recorder.stopRecording() { isRecording false isLoading true Task { do { transcriptionText try await transcriber.transcribeAudio(from: audioURL) } catch { transcriptionText 转录失败: \(error.localizedDescription) } isLoading false } } } else { // 开始录音 do { try recorder.startRecording() isRecording true transcriptionText } catch { print(录音失败: \(error)) } } } private func saveMemo() { let memo VoiceMemo( title: 语音备忘录 \(Date()), content: transcriptionText, date: Date() ) memoStore.addMemo(memo) transcriptionText } }数据模型和存储创建数据模型来存储语音备忘录import Foundation struct VoiceMemo: Identifiable, Codable { let id: UUID var title: String var content: String var date: Date init(id: UUID UUID(), title: String, content: String, date: Date) { self.id id self.title title self.content content self.date date } } class MemoStore: ObservableObject { Published var memos: [VoiceMemo] [] private let saveKey voiceMemos init() { loadMemos() } func addMemo(_ memo: VoiceMemo) { memos.insert(memo, at: 0) saveMemos() } func deleteMemo(at offsets: IndexSet) { memos.remove(atOffsets: offsets) saveMemos() } private func loadMemos() { if let data UserDefaults.standard.data(forKey: saveKey), let decoded try? JSONDecoder().decode([VoiceMemo].self, from: data) { memos decoded } } private func saveMemos() { if let encoded try? JSONEncoder().encode(memos) { UserDefaults.standard.set(encoded, forKey: saveKey) } } }高级功能扩展实时转录实现SwiftWhisper支持实时转录功能让你的应用能够在录音过程中实时显示转录结果class RealTimeTranscriber { private var whisper: Whisper private var audioBuffer: [Float] [] private let bufferSize 16000 * 30 // 30秒的缓冲区 init(modelURL: URL) { whisper Whisper(fromFileURL: modelURL) setupRealTimeProcessing() } private func setupRealTimeProcessing() { // 配置实时处理参数 var params WhisperParams(strategy: .greedy) params.no_context true params.single_segment true whisper.params params } func processAudioChunk(_ chunk: [Float]) async - String? { audioBuffer.append(contentsOf: chunk) // 保持缓冲区大小 if audioBuffer.count bufferSize { audioBuffer.removeFirst(audioBuffer.count - bufferSize) } // 每5秒处理一次 if audioBuffer.count 16000 * 5 { let framesToProcess Array(audioBuffer.prefix(16000 * 5)) do { let segments try await whisper.transcribe(audioFrames: framesToProcess) return segments.first?.text } catch { print(实时转录错误: \(error)) return nil } } return nil } }多语言支持SwiftWhisper支持多种语言的转录。你可以根据用户选择设置语言enum SupportedLanguage: String, CaseIterable { case english en case chinese zh case spanish es case french fr case german de case japanese ja case korean ko var displayName: String { switch self { case .english: return English case .chinese: return 中文 case .spanish: return Español case .french: return Français case .german: return Deutsch case .japanese: return 日本語 case .korean: return 한국어 } } } class MultiLanguageTranscriber { private var whisper: Whisper init(modelURL: URL) { whisper Whisper(fromFileURL: modelURL) } func setLanguage(_ language: SupportedLanguage) { whisper.params.language WhisperLanguage(rawValue: language.rawValue) ?? .auto } func transcribeWithLanguage(_ audioFrames: [Float], language: SupportedLanguage) async throws - String { setLanguage(language) let segments try await whisper.transcribe(audioFrames: audioFrames) return segments.map { $0.text }.joined(separator: ) } }性能优化技巧使用合适的模型大小tiny.bin最快适合实时应用base.bin平衡速度和准确度small.bin更高准确度适合离线转录启用CoreML加速 如果你的应用需要iOS设备上的最佳性能可以使用CoreML模型func loadCoreMLModel() throws - Whisper { guard let modelURL Bundle.main.url(forResource: tiny, withExtension: bin), let coreMLURL Bundle.main.url(forResource: tiny-encoder, withExtension: mlmodelc) else { throw NSError(domain: ModelLoader, code: 1, userInfo: [NSLocalizedDescriptionKey: Model files not found]) } // SwiftWhisper会自动检测并使用CoreML模型 return Whisper(fromFileURL: modelURL) }批量处理优化 对于较长的录音可以分段处理以提高内存效率func transcribeLongAudio(_ audioFrames: [Float], chunkSize: Int 16000 * 30) async throws - String { var results: [String] [] for i in stride(from: 0, to: audioFrames.count, by: chunkSize) { let end min(i chunkSize, audioFrames.count) let chunk Array(audioFrames[i..end]) let segments try await whisper.transcribe(audioFrames: chunk) let chunkText segments.map { $0.text }.joined(separator: ) results.append(chunkText) print(处理进度: \(end)/\(audioFrames.count)) } return results.joined(separator: ) }错误处理和用户体验优化完善的错误处理enum TranscriptionError: LocalizedError { case modelNotFound case audioConversionFailed case transcriptionFailed(Error) case insufficientStorage var errorDescription: String? { switch self { case .modelNotFound: return 语音模型未找到请确保模型文件已添加到项目中 case .audioConversionFailed: return 音频转换失败请检查录音格式 case .transcriptionFailed(let error): return 转录失败: \(error.localizedDescription) case .insufficientStorage: return 存储空间不足无法保存录音 } } } extension TranscriptionManager { func safeTranscribe(from url: URL) async - ResultString, TranscriptionError { do { let text try await transcribeAudio(from: url) return .success(text) } catch let error as NSError where error.domain NSURLErrorDomain { return .failure(.insufficientStorage) } catch { return .failure(.transcriptionFailed(error)) } } }用户反馈和进度指示struct TranscriptionProgressView: View { let progress: Double let status: TranscriptionStatus enum TranscriptionStatus { case preparing case processing(Double) case completed case failed(Error) var description: String { switch self { case .preparing: return 正在准备转录... case .processing(let progress): return 正在转录: \(Int(progress * 100))% case .completed: return 转录完成 case .failed(let error): return 转录失败: \(error.localizedDescription) } } } var body: some View { VStack(spacing: 10) { ProgressView(value: progress) .progressViewStyle(LinearProgressViewStyle()) .frame(width: 200) Text(status.description) .font(.caption) .foregroundColor(.secondary) } .padding() .background(Color(.systemBackground)) .cornerRadius(10) .shadow(radius: 2) } }应用发布准备隐私权限配置在Info.plist中添加必要的权限描述keyNSMicrophoneUsageDescription/key string需要麦克风权限来录制语音备忘录/string keyNSSpeechRecognitionUsageDescription/key string需要语音识别权限来转录语音内容/string应用图标和启动画面为你的语音备忘录应用设计专业的图标和启动画面确保符合Apple的人机界面指南。测试和优化设备兼容性测试在不同型号的iPhone和iPad上测试测试不同iOS版本验证内存使用情况性能优化使用Instruments分析内存泄漏优化音频处理管道实现后台处理支持用户体验测试测试录音质量验证转录准确度收集用户反馈总结通过本教程你已经学会了如何使用SwiftWhisper构建一个完整的iOS语音备忘录应用。从基本的录音功能到高级的实时转录SwiftWhisper为Swift开发者提供了强大而简单的语音识别解决方案。关键要点SwiftWhisper提供了简洁的API易于集成支持多种语言和实时转录可以通过CoreML加速获得更好的性能完善的错误处理和用户反馈机制现在你可以进一步扩展这个应用添加更多功能如语音备忘录分类和标签搜索和过滤功能iCloud同步支持分享和导出功能自定义语音命令开始构建你的语音备忘录应用吧SwiftWhisper让语音识别变得前所未有的简单为你的iOS应用增添强大的语音功能。记得在实际发布前进行充分的测试确保应用在各种场景下都能稳定运行。祝你开发顺利【免费下载链接】SwiftWhisper The easiest way to transcribe audio in Swift项目地址: https://gitcode.com/gh_mirrors/sw/SwiftWhisper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻