使用Audio Queue Services录制M4A音频失败:文件无法打开或崩溃
解决Audio Queue Services录制M4A(AAC编码)文件的问题
核心问题根源
Audio Queue从麦克风获取的是原始PCM数据,无法直接录制AAC压缩格式;同时你在创建文件时混淆了「编码格式ID」和「文件容器类型ID」,这是导致文件无法打开或崩溃的主要原因。AVAudioRecorder内部帮你封装了PCM转AAC的编码流程,但Audio Queue需要手动实现这一步骤。
关键修正点
- 文件类型ID错误:
AudioFileCreateWithURL的第二个参数必须是kAudioFileM4AType(M4A文件容器),而非kAudioFormatMPEG4AAC(编码格式ID)。 - 缺少编码步骤:必须通过
AudioConverterRef将录制的PCM数据编码为AAC格式,再写入M4A文件。 - AAC格式配置不完整:AAC的
AudioStreamBasicDescription需通过系统API获取默认配置,不能手动随意设置字段。
完整实现代码
以下是可直接运行的Swift示例,包含PCM录制、AAC编码、M4A文件写入的完整流程:
import AVFoundation class AACAudioRecorder { private var audioQueue: AudioQueueRef? private var outputAudioFile: AudioFileID? private var audioConverter: AudioConverterRef? // 录制的PCM格式(与你之前的LinearPCM配置一致) private let recordingPCMFormat = AudioStreamBasicDescription( mFormatID: kAudioFormatLinearPCM, mSampleRate: 16000, mChannelsPerFrame: 1, mBitsPerChannel: 16, mBytesPerPacket: 2, mBytesPerFrame: 2, mFramesPerPacket: 1, mFormatFlags: kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked ) private var targetAACFormat = AudioStreamBasicDescription() init() { setupAACFormat() setupOutputAudioFile() setupAudioConverter() setupRecordingQueue() } // 获取AAC编码器的默认配置 private func setupAACFormat() { var propSize = UInt32(MemoryLayout<AudioStreamBasicDescription>.size) var formatID = kAudioFormatMPEG4AAC CheckError( AudioFormatGetProperty( kAudioFormatProperty_FormatInfo, 0, &formatID, &propSize, &targetAACFormat ), "获取AAC格式配置失败" ) // 匹配录制的采样率和通道数 targetAACFormat.mSampleRate = recordingPCMFormat.mSampleRate targetAACFormat.mChannelsPerFrame = recordingPCMFormat.mChannelsPerFrame } // 创建M4A输出文件 private func setupOutputAudioFile() { let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let outputURL = documentsDir.appendingPathComponent("recording.m4a") CheckError( AudioFileCreateWithURL( outputURL as CFURL, kAudioFileM4AType, &targetAACFormat, .eraseFile, &outputAudioFile ), "创建M4A文件失败" ) } // 创建PCM转AAC的编码器 private func setupAudioConverter() { CheckError( AudioConverterNew( &recordingPCMFormat, &targetAACFormat, &audioConverter ), "创建音频编码器失败" ) // 设置编码质量(对应AVAudioQuality.high) var quality = Int32(AVAudioQuality.high.rawValue) let propSize = UInt32(MemoryLayout<Int32>.size) CheckError( AudioConverterSetProperty( audioConverter!, kAudioConverterCodecQuality, propSize, &quality ), "设置编码质量失败" ) } // 初始化录制队列 private func setupRecordingQueue() { let bufferSize = calculateOptimalBufferSize() CheckError( AudioQueueNewInput( &recordingPCMFormat, recordingCallback, Unmanaged.passUnretained(self).toOpaque(), nil, nil, 0, &audioQueue ), "创建录制队列失败" ) // 分配3个缓冲区循环使用 for _ in 0..<3 { var buffer: AudioQueueBufferRef? CheckError( AudioQueueAllocateBuffer(audioQueue!, bufferSize, &buffer), "分配队列缓冲区失败" ) CheckError( AudioQueueEnqueueBuffer(audioQueue!, buffer!, 0, nil), "加入队列缓冲区失败" ) } } // 计算0.5秒PCM数据对应的缓冲区大小,避免卡顿 private func calculateOptimalBufferSize() -> UInt32 { let seconds: Double = 0.5 let bytesPerSecond = recordingPCMFormat.mSampleRate * Double(recordingPCMFormat.mBytesPerFrame) return UInt32(seconds * bytesPerSecond) } // 录制回调:PCM转AAC后写入文件 private let recordingCallback: AudioQueueInputCallback = { userData, queue, buffer, _, packetCount, _ in let recorder = Unmanaged<AACAudioRecorder>.fromOpaque(userData!).takeUnretainedValue() guard packetCount > 0, let converter = recorder.audioConverter, let file = recorder.outputAudioFile else { AudioQueueEnqueueBuffer(queue, buffer!, 0, nil) return } // 封装PCM输入数据 var inputData = AudioConverterInputData( data: buffer!.mAudioData, size: buffer!.mAudioDataByteSize, packetCount: packetCount ) // 准备AAC输出缓冲区 var outputPacketCount = UInt32(1) var outputBuffer = [UInt8](repeating: 0, count: 1024) // 执行PCM转AAC编码 let status = AudioConverterFillComplexBuffer( converter, { _, outPacketCount, outData, _, inputDataPtr in let inputData = inputDataPtr!.load(as: AudioConverterInputData.self) outData?.mAudioData = inputData.data outData?.mAudioDataByteSize = inputData.size outPacketCount.pointee = inputData.packetCount return noErr }, &inputData, &outputPacketCount, &outputBuffer, nil ) // 将编码后的AAC数据写入文件 if status == noErr && outputPacketCount > 0 { var bytesWritten = UInt32(0) AudioFileWriteBytes(file, false, 0, &bytesWritten, outputBuffer) } // 缓冲区重新入队继续录制 AudioQueueEnqueueBuffer(queue, buffer!, 0, nil) } // 开始录制 func start() { CheckError(AudioQueueStart(audioQueue!, nil), "启动录制失败") } // 停止录制并释放资源 func stop() { AudioQueueStop(audioQueue!, true) AudioQueueDispose(audioQueue!, true) if let file = outputAudioFile { AudioFileClose(file) } } } // 辅助结构体:传递编码输入数据 fileprivate struct AudioConverterInputData { var data: UnsafeMutableRawPointer? var size: UInt32 var packetCount: UInt32 } // 错误检查工具函数 func CheckError(_ status: OSStatus, _ message: String) { guard status != noErr else { return } print("\(message):错误码 \(status)") fatalError() }
使用说明
- 初始化
AACAudioRecorder实例,调用start()开始录制,stop()停止录制。 - 录制的M4A文件会保存在应用的Documents目录下。
- 实际项目中可优化错误处理逻辑,避免直接
fatalError。
内容的提问来源于stack exchange,提问作者Ryan H
相关产品推荐
相关产品推荐

