iOS能否同时生成触觉反馈并录制其声音?异步振动失效问题排查
解决iOS振动与录音同时运行时振动失效的问题
问题原因分析
- Timer运行环境异常:后台队列的RunLoop默认未启动,你在后台队列中创建的
Timer无法按时触发playVibration方法,导致振动实际未执行。 - 音频会话配置缺失:未正确设置AVAudioSession类别,录音启动时可能占用音频资源干扰振动;同时未申请录音权限,可能引发录音初始化异常间接影响振动功能。
- 振动实现方式可靠性不足:使用
AudioServicesPlaySystemSound重复播放系统振动音效,这种方式并非为持续振动场景设计,且与音频录制的兼容性较差。
修复方案
1. 修正Timer的RunLoop配置
将振动Timer放到主队列的RunLoop中(主队列RunLoop默认持续运行),确保Timer能正常触发:
func startVibration() { print("Vibration starts") DispatchQueue.main.async { self.vibrationTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.playVibration), userInfo: nil, repeats: true) } }
若坚持使用后台队列,需手动启动RunLoop(注意此操作会阻塞当前线程):
func startVibration() { print("Vibration starts") let timer = Timer(timeInterval: 0.1, target: self, selector: #selector(playVibration), userInfo: nil, repeats: true) RunLoop.current.add(timer, forMode: .common) self.vibrationTimer = timer RunLoop.current.run() }
2. 配置音频会话并申请权限
在viewDidLoad中完成录音权限申请和音频会话配置,确保录音与振动可同时运行:
override func viewDidLoad() { super.viewDidLoad() // 申请录音权限 AVAudioSession.sharedInstance().requestRecordPermission { granted in if !granted { print("录音权限未授权") } } // 配置音频会话为播放+录制模式 do { try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default) try AVAudioSession.sharedInstance().setActive(true) } catch { print("音频会话配置失败:\(error.localizedDescription)") } }
3. 使用Core Haptics实现稳定持续振动
Core Haptics是苹果官方推荐的触觉反馈框架,比重复播放系统音效更稳定,且与音频操作兼容性更好:
首先导入框架:
import CoreHaptics
添加相关属性与方法:
var hapticEngine: CHHapticEngine? var hapticPlayer: CHHapticPatternPlayer? func startContinuousVibration() { guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { print("设备不支持Core Haptics") return } do { hapticEngine = try CHHapticEngine() try hapticEngine?.start() // 创建3秒持续振动的触觉模式 let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0) let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) let event = CHHapticEvent(eventType: .hapticContinuous, parameters: [intensity, sharpness], relativeTime: 0, duration: 3) let pattern = try CHHapticPattern(events: [event], parameters: []) hapticPlayer = try hapticEngine?.makePlayer(with: pattern) try hapticPlayer?.start(atTime: CHHapticTimeImmediate) print("Core Haptics振动启动") // 3秒后同步停止振动与录音 DispatchQueue.main.asyncAfter(deadline: .now() + 3) { self.hapticPlayer?.stop(atTime: CHHapticTimeImmediate) self.stopRecording() print("Core Haptics振动停止") } } catch { print("Core Haptics操作失败:\(error.localizedDescription)") } }
4. 补充录音停止逻辑
在振动结束时同步停止录音,完善流程:
func stopRecording() { audioRecorder?.stop() print("Recording stopped") // 此处可调用音频分析方法 // analyzeRecordedVibration(audioURL: audioRecorder?.url ?? getDocumentsDirectory()) }
完整修复代码示例
import UIKit import AVFoundation import CoreHaptics class ViewController: UIViewController, AVAudioRecorderDelegate { var audioRecorder: AVAudioRecorder? var hapticEngine: CHHapticEngine? var hapticPlayer: CHHapticPatternPlayer? override func viewDidLoad() { super.viewDidLoad() // 申请录音权限 AVAudioSession.sharedInstance().requestRecordPermission { granted in if !granted { print("录音权限未授权") } } // 配置音频会话 do { try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default) try AVAudioSession.sharedInstance().setActive(true) } catch { print("音频会话配置失败:\(error.localizedDescription)") } } @IBAction func testButtonPressed(_ sender: UIButton) { playVibrationAndRecord() } func playVibrationAndRecord() { DispatchQueue.global(qos: .background).async { self.startContinuousVibration() } DispatchQueue.global(qos: .background).async { self.startRecording() } } func startContinuousVibration() { guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { print("设备不支持Core Haptics") return } do { hapticEngine = try CHHapticEngine() try hapticEngine?.start() let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0) let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) let event = CHHapticEvent(eventType: .hapticContinuous, parameters: [intensity, sharpness], relativeTime: 0, duration: 3) let pattern = try CHHapticPattern(events: [event], parameters: []) hapticPlayer = try hapticEngine?.makePlayer(with: pattern) try hapticPlayer?.start(atTime: CHHapticTimeImmediate) print("Core Haptics振动启动") DispatchQueue.main.asyncAfter(deadline: .now() + 3) { self.hapticPlayer?.stop(atTime: CHHapticTimeImmediate) self.stopRecording() print("Core Haptics振动停止") } } catch { print("Core Haptics操作失败:\(error.localizedDescription)") } } func startRecording() { print("Recording started") let audioFilename = getDocumentsDirectory().appendingPathComponent("recording.m4a") let settings = [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 44100, AVNumberOfChannelsKey: 1, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] do { audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings) audioRecorder?.delegate = self audioRecorder?.record() } catch { print("Error recording audio: \(error.localizedDescription)") } } func stopRecording() { audioRecorder?.stop() print("Recording stopped") } func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) { if flag { print("录音完成") } else { print("录音失败") } } }
内容的提问来源于stack exchange,提问作者Cyclone
相关产品推荐
相关产品推荐

