You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何借助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

三、关键注意事项
  1. 权限配置:必须在Info.plist中添加NSMicrophoneUsageDescription(使用麦克风时),否则应用会崩溃。
  2. 音频会话冲突:如果应用中有其他音频播放逻辑,要确保会话配置不会冲突,比如避免同时设置多个不同的category。
  3. 后台录制:如果需要后台录制,还要在Info.plist中开启audio后台模式,并配置会话的options为mixWithOthers或allowBluetooth等。
  4. 格式兼容性:AudioKit默认生成CAF格式文件,如需MP3、M4A等格式,需要用AVAssetExportSession或AudioKit的AKAudioFile进行转码。

内容的提问来源于stack exchange,提问作者James T Snell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:09:19