iOS/Swift:如何通过AVAudioEngine在所有硬件上录制16kHz音频
实现全硬件兼容的16kHz单声道Int16音频录制(Swift + AVAudioEngine)
嘿,我来帮你搞定这个全硬件兼容的16kHz音频录制需求!你的基础代码方向是对的,但要覆盖所有iOS设备,得处理几个关键的兼容性细节——比如不同硬件原生采样率不一致、权限检查、格式转换可靠性这些问题。下面是优化后的完整实现方案:
核心优化点与完整代码
1. 先搞定麦克风权限
iOS对麦克风权限管得很严,必须先确保用户授权了才能录制,不然直接启动引擎会崩溃:
import AVFoundation // 检查并请求麦克风权限 func checkMicrophonePermission(completion: @escaping (Bool) -> Void) { switch AVAudioSession.sharedInstance().recordPermission { case .granted: completion(true) case .denied: completion(false) case .undetermined: AVAudioSession.sharedInstance().requestRecordPermission { granted in completion(granted) } @unknown default: completion(false) } }
2. 兼容所有硬件的录制实现
关键思路:不要强制要求硬件直接输出16kHz格式——不同iOS设备的麦克风原生采样率可能是44.1kHz、48kHz甚至其他值,硬指定会导致部分设备崩溃。我们用AVAudioConverter在回调里做可靠的软件转换,同时正确配置音频会话:
class AudioRecorder { private let audioEngine = AVAudioEngine() private let desiredFormat: AVAudioFormat private var converter: AVAudioConverter? init() { // 我们最终需要的格式:16kHz、单声道、Int16 PCM desiredFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000.0, channels: 1, interleaved: false)! } func startRecording() throws { // 配置音频会话为录制模式,避免和其他音频应用冲突 let session = AVAudioSession.sharedInstance() try session.setCategory(.record, mode: .default, options: []) try session.setActive(true) let inputNode = audioEngine.inputNode let hardwareInputFormat = inputNode.inputFormat(forBus: 0) // 初始化格式转换器:把硬件原生格式转成我们要的16kHz格式 converter = AVAudioConverter(from: hardwareInputFormat, to: desiredFormat) // 直接给输入节点加tap,不需要额外的mixer(除非你要混音其他音频) inputNode.installTap(onBus: 0, bufferSize: 2048, format: hardwareInputFormat) { [weak self] buffer, time in guard let self = self, let converter = self.converter else { return } // 准备对应目标格式的buffer guard let convertedBuffer = AVAudioPCMBuffer(pcmFormat: self.desiredFormat, frameCapacity: AVAudioFrameCount(buffer.frameLength * self.desiredFormat.sampleRate / hardwareInputFormat.sampleRate)) else { return } // 执行格式转换 var error: NSError? converter.convert(to: convertedBuffer, error: &error) { inNumPackets, outStatus in outStatus.pointee = .haveData return buffer } if let error = error { print("格式转换出错:\(error.localizedDescription)") return } // 这里拿到的convertedBuffer就是符合要求的16kHz、Int16、单声道音频数据 self.processAudioData(from: convertedBuffer) } // 启动音频引擎 try audioEngine.start() } func stopRecording() { audioEngine.stop() audioEngine.inputNode.removeTap(onBus: 0) do { try AVAudioSession.sharedInstance().setActive(false) } catch { print("停止音频会话失败:\(error.localizedDescription)") } } private func processAudioData(from buffer: AVAudioPCMBuffer) { // 示例:把Int16格式的PCM数据转成Data,方便后续处理(比如上传、保存) guard let channelData = buffer.int16ChannelData?[0] else { return } let frameCount = Int(buffer.frameLength) let audioData = Data(bytes: channelData, count: frameCount * MemoryLayout<Int16>.stride) // 这里可以根据需求处理音频数据,比如打印日志、发送到服务器等 print("收到\(audioData.count)字节的16kHz音频数据") } }
全硬件兼容的关键细节
- 适配硬件原生格式:不同设备麦克风支持的原生采样率不一样,直接要求硬件输出16kHz会触发崩溃,用
AVAudioConverter做软件转换是最稳妥的方式。 - 音频会话配置:必须设置
.record类别,并且启动/停止录制时要对应激活/停用会话,避免和其他音频应用抢资源。 - 错误处理:所有涉及
try的操作都要捕获错误,包括音频引擎启动、会话配置等,避免意外崩溃。 - 避免循环引用:在tap回调里用
[weak self],防止AudioRecorder实例无法被释放。
使用示例
let audioRecorder = AudioRecorder() checkMicrophonePermission { granted in if granted { do { try audioRecorder.startRecording() // 模拟录制5秒后停止 DispatchQueue.main.asyncAfter(deadline: .now() + 5) { audioRecorder.stopRecording() } } catch { print("启动录制失败:\(error.localizedDescription)") } } else { print("麦克风权限未授权,请前往设置开启权限") } }
内容的提问来源于stack exchange,提问作者Simon Hessner




