如何借助AudioKit捕获Objective-C应用音频输出并录制至磁盘
嗨,很高兴能帮你搞定这个音频录制的需求!既然你已经给Objective-C应用搭好了Swift桥接并接入了AudioKit,那咱们先从AudioKit专属方案说起(这也是最贴合你现有架构的选择),之后再补充纯Swift/OC的替代方案,同时覆盖你想要混合应用输出+系统输入的需求~
一、基于AudioKit的实现方案(优先推荐)
AudioKit的节点化设计天生适合处理多音频源混合与录制,核心思路是把「应用音频输出节点」和「系统输入节点」都接入混音器,再录制混音器的输出。
1. 核心步骤
(1)配置音频会话与权限
首先得确保音频会话权限和配置正确,要同时支持播放和录制:
import AudioKit func setupAudioSession() throws { let session = AVAudioSession.sharedInstance() try session.setCategory(.playAndRecord, mode: .default) try session.activate() // 别忘了在Info.plist中添加NSMicrophoneUsageDescription权限描述 }
(2)准备输入源与混音器
- 应用音频输出:假设你的应用音频是通过AudioKit节点生成的(比如
AKPlayer、AKSynthesizer,或者自定义的音频处理节点),直接用这个节点即可;如果是非AudioKit生成的音频(比如系统AVAudioPlayer),可以通过AKInputNode捕获,或者把该音频的输出接入AudioKit混音链。 - 系统默认输入:用
AKMicrophone获取麦克风输入 - 混音器:把两个输入源混合到一个节点
// 替换为你的应用音频输出节点 let appAudioNode: AKNode = AKPlayer(url: Bundle.main.url(forResource: "app-audio", withExtension: "mp3")!) // 系统麦克风输入 let microphone = AKMicrophone() // 混合两个输入源 let mixer = AKMixer(appAudioNode, microphone)
(3)初始化录制器并启动
用AKNodeRecorder录制混音器的输出,指定保存路径:
// 生成保存路径(比如Documents目录下的recorded.caf) let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let outputURL = documentsDir.appendingPathComponent("recorded.caf") var recorder: AKNodeRecorder? do { try setupAudioSession() // 初始化录制器 recorder = try AKNodeRecorder(node: mixer, fileURL: outputURL) // 启动AudioKit和录制 try AudioKit.start() try recorder?.record() print("开始录制,文件将保存到:\(outputURL.path)") } catch { print("启动失败:\(error.localizedDescription)") }
(4)停止录制与后续处理
func stopRecording() { recorder?.stop() AudioKit.stop() // 可以对录制的文件进行格式转换(比如转MP3) if let recordedFile = recorder?.audioFile { let mp3URL = outputURL.deletingPathExtension().appendingPathExtension("mp3") do { try recordedFile.exportAsynchronously(to: mp3URL, format: .mp3) { status in if status == .completed { print("MP3文件已保存:\(mp3URL.path)") } } } catch { print("格式转换失败:\(error)") } } }
2. 桥接Objective-C调用
把上述Swift代码封装成一个@objc类,就能在Objective-C中调用:
@objc class AudioKitRecorder: NSObject { // 把上面的setup、start、stop方法都封装成@objc方法 @objc func startRecording() { /* 实现 */ } @objc func stopRecording() { /* 实现 */ } }
然后在Objective-C的桥接头文件中导入该类的头文件,即可直接实例化调用。
二、纯Swift/Objective-C方案(不依赖AudioKit)
如果不想依赖AudioKit,用AVFoundation的AVAudioEngine也能实现相同需求,适合原生音频场景。
Swift示例
import AVFoundation @objc class NativeAudioRecorder: NSObject { private let engine = AVAudioEngine() private let mixer = AVAudioMixerNode() private var recorder: AVAudioRecorder? @objc func setupRecording(outputURL: URL) throws { // 配置音频会话 try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default) try AVAudioSession.sharedInstance().activate() // 获取系统输入节点 let inputNode = engine.inputNode // 替换为你的应用音频输出节点(比如AVAudioPlayer的节点) let appAudioNode: AVAudioNode = AVAudioPlayerNode() // 挂载节点并连接 engine.attach(mixer) engine.attach(appAudioNode) engine.connect(inputNode, to: mixer, format: inputNode.inputFormat(forBus: 0)) engine.connect(appAudioNode, to: mixer, format: appAudioNode.outputFormat(forBus: 0)) // 配置录制参数(AAC格式) let settings: [String: Any] = [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 44100.0, AVNumberOfChannelsKey: 2, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] recorder = try AVAudioRecorder(url: outputURL, settings: settings) recorder?.prepareToRecord() // 给混音器加Tap,将音频数据写入录制器 engine.connect(mixer, to: engine.mainMixerNode, format: mixer.outputFormat(forBus: 0)) engine.mainMixerNode.installTap(onBus: 0, bufferSize: 1024, format: engine.mainMixerNode.outputFormat(forBus: 0)) { [weak self] buffer, _ in self?.recorder?.append(buffer) } } @objc func startRecording() throws { try engine.start() recorder?.record() } @objc func stopRecording() { recorder?.stop() engine.stop() engine.reset() } }
Objective-C示例
#import <AVFoundation/AVFoundation.h> @interface OCAudioRecorder : NSObject - (BOOL)setupRecordingWithOutputURL:(NSURL *)outputURL error:(NSError **)error; - (BOOL)startRecordingWithError:(NSError **)error; - (void)stopRecording; @end @implementation OCAudioRecorder { AVAudioEngine *_engine; AVAudioMixerNode *_mixer; AVAudioRecorder *_recorder; } - (instancetype)init { self = [super init]; if (self) { _engine = [[AVAudioEngine alloc] init]; _mixer = [[AVAudioMixerNode alloc] init]; } return self; } - (BOOL)setupRecordingWithOutputURL:(NSURL *)outputURL error:(NSError **)error { // 配置音频会话 AVAudioSession *session = [AVAudioSession sharedInstance]; NSError *sessionError; if (![session setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeDefault error:&sessionError]) { if (error) *error = sessionError; return NO; } if (![session activateWithError:&sessionError]) { if (error) *error = sessionError; return NO; } // 准备输入节点 AVAudioInputNode *inputNode = _engine.inputNode; AVAudioNode *appAudioNode = /* 你的应用音频输出节点 */; [_engine attachNode:_mixer]; [_engine attachNode:appAudioNode]; [_engine connect:inputNode to:_mixer format:[inputNode inputFormatForBus:0]]; [_engine connect:appAudioNode to:_mixer format:[appAudioNode outputFormatForBus:0]]; // 配置录制参数 NSDictionary *settings = @{ AVFormatIDKey: @(kAudioFormatMPEG4AAC), AVSampleRateKey: @44100.0, AVNumberOfChannelsKey: @2, AVEncoderAudioQualityKey: @(AVAudioQualityHigh) }; _recorder = [[AVAudioRecorder alloc] initWithURL:outputURL settings:settings error:error]; if (!_recorder) return NO; [_recorder prepareToRecord]; // 挂载Tap捕获音频 [_engine connect:_mixer to:_engine.mainMixerNode format:[_mixer outputFormatForBus:0]]; __weak typeof(self) weakSelf = self; [_engine.mainMixerNode installTapOnBus:0 bufferSize:1024 format:[_engine.mainMixerNode outputFormatForBus:0] block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) { [weakSelf->_recorder appendBuffer:buffer]; }]; return YES; } - (BOOL)startRecordingWithError:(NSError **)error { NSError *engineError; if (![_engine startAndReturnError:&engineError]) { if (error) *error = engineError; return NO; } return [_recorder record]; } - (void)stopRecording { [_recorder stop]; [_engine stop]; [_engine reset]; } @end
三、关键注意事项
- 权限配置:必须在
Info.plist中添加NSMicrophoneUsageDescription(使用麦克风时),否则应用会崩溃。 - 音频会话冲突:如果应用中有其他音频播放逻辑,要确保会话配置不会冲突,比如避免同时设置多个不同的category。
- 后台录制:如果需要后台录制,还要在
Info.plist中开启audio后台模式,并配置会话的options为mixWithOthers或allowBluetooth等。 - 格式兼容性:AudioKit默认生成CAF格式文件,如需MP3、M4A等格式,需要用AVAssetExportSession或AudioKit的
AKAudioFile进行转码。
内容的提问来源于stack exchange,提问作者James T Snell
相关产品推荐
相关产品推荐

