实现用户说话自动启停录音并限制最长录音时长
解决方案:基于音频计量实现自动启停录音+最长时长限制
你说得对,AVAudioRecorderDelegate确实没有直接提供检测语音启停的回调方法,但我们可以利用它的音频计量(Metering)功能来实现这个需求——通过定时采样音频的音量电平,判断用户是否在说话,再结合计时器完成自动启停和最长时长限制。下面是具体的实现步骤和修改后的代码:
第一步:添加必要的属性
在你的ViewController中先定义这些变量,用来控制录音的检测逻辑:
import UIKit import AVFoundation class RecordViewController: UIViewController, AVAudioRecorderDelegate { @IBOutlet weak var recordingLabel: UILabel! @IBOutlet weak var recordButton: UIButton! @IBOutlet weak var stopRecordingButton: UIButton! var audioRecorder: AVAudioRecorder! // 音频检测相关 var soundDetectionTimer: Timer? let silenceThreshold: Float = -40.0 // 静音阈值(可调整,值越小越敏感) let silenceDurationLimit: TimeInterval = 1.5 // 静音持续多久后停止录音 var silenceStartTime: Date? // 最长录音时长 let maxRecordingDuration: TimeInterval = 60.0 // 最长60秒,可调整 var recordingStartTime: Date? }
第二步:修改录音启动逻辑
在recordAudio方法中,启动录音的同时,初始化计时器并开始采样音频电平,同时记录录音开始时间:
@IBAction func recordAudio(_ sender: Any) { recordingLabel.text = "Recording in progress..." recordButton.isEnabled = false stopRecordingButton.isEnabled = true // 配置录音路径 let dirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String let recordingName = "recordedVoice.wav" let pathArray = [dirPath, recordingName] guard let filePath = URL(string: pathArray.joined(separator: "/")) else { print("Failed to create file path") return } // 配置音频会话 let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playAndRecord, mode: .default, options: .defaultToSpeaker) try session.setActive(true) // 初始化录音器(建议添加音频设置,避免默认设置可能的问题) let settings: [String: Any] = [ AVFormatIDKey: Int(kAudioFormatLinearPCM), AVSampleRateKey: 44100.0, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] audioRecorder = try AVAudioRecorder(url: filePath, settings: settings) audioRecorder.delegate = self audioRecorder.isMeteringEnabled = true audioRecorder.prepareToRecord() audioRecorder.record() // 记录录音开始时间,启动检测计时器 recordingStartTime = Date() soundDetectionTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(checkAudioLevel), userInfo: nil, repeats: true) } catch let error { print("Audio session or recorder setup failed: \(error.localizedDescription)") resetRecordingUI() } }
第三步:实现音频电平检测逻辑
这个方法会定时被调用,采样当前音频的音量,判断是否处于静音状态,进而触发自动停止:
@objc func checkAudioLevel() { guard let recorder = audioRecorder, recorder.isRecording else { soundDetectionTimer?.invalidate() return } // 更新计量数据 recorder.updateMeters() let averagePower = recorder.averagePower(forChannel: 0) // 检查是否超过最长录音时长 if let startTime = recordingStartTime, Date().timeIntervalSince(startTime) >= maxRecordingDuration { print("Reached max recording duration") stopRecording(nil) return } // 判断是否静音 if averagePower < silenceThreshold { // 开始计时静音时长 if silenceStartTime == nil { silenceStartTime = Date() } else { let silenceDuration = Date().timeIntervalSince(silenceStartTime!) if silenceDuration >= silenceDurationLimit { print("Silence detected for too long, stopping recording") stopRecording(nil) } } } else { // 检测到声音,重置静音计时器 silenceStartTime = nil } }
第四步:完善停止录音和UI重置逻辑
修改stopRecording方法,确保停止计时器和清理资源:
@IBAction func stopRecording(_ sender: Any?) { soundDetectionTimer?.invalidate() soundDetectionTimer = nil silenceStartTime = nil recordingStartTime = nil recordButton.isEnabled = true stopRecordingButton.isEnabled = false recordingLabel.text = "Tap to record..." if audioRecorder.isRecording { audioRecorder.stop() let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setActive(false) } catch { print("Failed to deactivate audio session: \(error.localizedDescription)") } } } // 重置UI的辅助方法 func resetRecordingUI() { recordButton.isEnabled = true stopRecordingButton.isEnabled = false recordingLabel.text = "Tap to record..." }
第五步:处理录音完成的回调
保持你原有的audioRecorderDidFinishRecording方法,可根据需要扩展:
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { if flag { print("Recording saved successfully at \(recorder.url)") // 这里可以添加录音完成后的逻辑,比如上传、播放等 } else { print("Could not save audio recording!") resetRecordingUI() } }
额外注意事项
- 权限请求:记得在
Info.plist中添加NSMicrophoneUsageDescription,说明使用麦克风的原因,否则App会崩溃。 - 阈值调整:
silenceThreshold和silenceDurationLimit可以根据实际场景调整,比如环境噪音大的话,阈值可以调大(比如-30.0),避免误判。 - 错误处理:我把你原有的
try!换成了do-catch,这样可以捕获错误,避免App崩溃。
内容的提问来源于stack exchange,提问作者John




