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

如何在macOS上通过Audio Unit获取设备音频输入并用于录音回调?

解决Audio Unit麦克风输入与录音回调配置问题

我明白你在Audio Unit麦克风输入配置上卡壳有多头疼——这部分的API细节确实容易绕进去!先看看你给出的代码片段,咱们一步步排查问题,补上缺失的关键步骤:

首先,容易漏掉的前置步骤:麦克风权限与音频会话配置

在iOS上,必须先请求麦克风权限并正确配置音频会话,否则后续的Audio Unit操作会失败:

// 在调用setupMicInput前先执行这部分
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *sessionError = nil;
// 设置为播放+录音模式,允许和其他音频混合(可选,根据需求调整)
[session setCategory:AVAudioSessionCategoryPlayAndRecord 
                mode:AVAudioSessionModeDefault 
             options:AVAudioSessionCategoryOptionMixWithOthers 
               error:&sessionError];
if (sessionError) {
    NSLog(@"音频会话配置失败:%@", sessionError);
    return sessionError.code;
}

// 请求麦克风权限
[session requestRecordPermission:^(BOOL granted) {
    if (!granted) {
        NSLog(@"麦克风权限被拒绝!请在设置中开启权限");
        // 这里需要处理权限拒绝的业务逻辑
    }
}];

⚠️ 别忘了在Info.plist中添加NSMicrophoneUsageDescription键,描述使用麦克风的原因,否则权限请求会直接失败。

完善后的setupMicInput实现

你原来的代码只完成了获取默认输入设备的第一步,还需要实例化Audio Unit、配置IO、设置流格式和回调:

// 记得在类中声明AudioUnit成员变量:@property (nonatomic, assign) AudioUnit audioUnit;

- (OSStatus) setupMicInput {
    // 1. 获取默认输入设备(你原来的代码部分,补充错误处理)
    AudioObjectPropertyAddress addr;
    UInt32 size = sizeof(AudioDeviceID);
    AudioDeviceID deviceID = 0;
    addr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
    addr.mScope = kAudioObjectPropertyScopeGlobal;
    addr.mElement = kAudioObjectPropertyElementMaster;
    
    OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &size, &deviceID);
    if (err != noErr) {
        NSLog(@"获取默认输入设备失败:%d", (int)err);
        return err;
    }

    // 2. 定义Audio Unit描述,选择输入类型的Unit
    AudioComponentDescription inputDesc;
    inputDesc.componentType = kAudioUnitType_Input;
    // 可选:kAudioUnitSubType_RemoteIO(基础IO)或kAudioUnitSubType_VoiceProcessingIO(带语音处理)
    inputDesc.componentSubType = kAudioUnitSubType_RemoteIO;
    inputDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
    inputDesc.componentFlags = 0;
    inputDesc.componentFlagsMask = 0;

    // 3. 查找并实例化Audio Unit
    AudioComponent inputComponent = AudioComponentFindNext(NULL, &inputDesc);
    err = AudioComponentInstanceNew(inputComponent, &_audioUnit);
    if (err != noErr) {
        NSLog(@"实例化Audio Unit失败:%d", (int)err);
        return err;
    }

    // 4. 启用输入IO通道
    UInt32 enableInput = 1;
    size = sizeof(enableInput);
    err = AudioUnitSetProperty(_audioUnit, 
                               kAudioOutputUnitProperty_EnableIO, 
                               kAudioUnitScope_Input, 
                               1, // 输入总线编号
                               &enableInput, 
                               size);
    if (err != noErr) {
        NSLog(@"启用输入IO失败:%d", (int)err);
        return err;
    }

    // 5. 设置音频流格式(必须和回调处理的格式一致)
    AudioStreamBasicDescription streamFormat;
    memset(&streamFormat, 0, sizeof(streamFormat));
    streamFormat.mSampleRate = 44100.0; // 常用采样率
    streamFormat.mFormatID = kAudioFormatLinearPCM;
    streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    streamFormat.mFramesPerPacket = 1;
    streamFormat.mChannelsPerFrame = 1; // 单声道,可改为2立体声
    streamFormat.mBitsPerChannel = 16;
    streamFormat.mBytesPerPacket = streamFormat.mChannelsPerFrame * (streamFormat.mBitsPerChannel / 8);
    streamFormat.mBytesPerFrame = streamFormat.mBytesPerPacket;

    err = AudioUnitSetProperty(_audioUnit, 
                               kAudioUnitProperty_StreamFormat, 
                               kAudioUnitScope_Output, 
                               1, // 输入总线的输出端(给回调的格式)
                               &streamFormat, 
                               sizeof(streamFormat));
    if (err != noErr) {
        NSLog(@"设置流格式失败:%d", (int)err);
        return err;
    }

    // 6. 配置录音回调函数
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = micRecordingCallback; // 外部定义的回调函数
    callbackStruct.inputProcRefCon = (__bridge void *)self; // 传递当前类实例供回调使用

    err = AudioUnitSetProperty(_audioUnit, 
                               kAudioOutputUnitProperty_SetInputCallback, 
                               kAudioUnitScope_Global, 
                               1, 
                               &callbackStruct, 
                               sizeof(callbackStruct));
    if (err != noErr) {
        NSLog(@"设置输入回调失败:%d", (int)err);
        return err;
    }

    // 7. 初始化并启动Audio Unit
    err = AudioUnitInitialize(_audioUnit);
    if (err != noErr) {
        NSLog(@"初始化Audio Unit失败:%d", (int)err);
        return err;
    }

    err = AudioOutputUnitStart(_audioUnit);
    if (err != noErr) {
        NSLog(@"启动Audio Unit失败:%d", (int)err);
        return err;
    }

    return noErr;
}

// 录音回调函数(需定义在类外部或作为静态函数)
OSStatus micRecordingCallback(void *inRefCon, 
                              AudioUnitRenderActionFlags *ioActionFlags, 
                              const AudioTimeStamp *inTimeStamp, 
                              UInt32 inBusNumber, 
                              UInt32 inNumberFrames, 
                              AudioBufferList *ioData) {
    // 转换回当前类实例
    YourClassName *self = (__bridge YourClassName *)inRefCon;
    
    // 这里处理采集到的麦克风数据,ioData包含PCM音频数据
    // 示例:打印采集帧数,或把数据写入文件/处理
    NSLog(@"采集到 %d 帧音频数据", (int)inNumberFrames);
    
    return noErr;
}

几个关键注意点

  • 资源释放:不用录音时,记得依次调用AudioOutputUnitStop(_audioUnit)、AudioUnitUninitialize(_audioUnit)、AudioComponentInstanceDispose(_audioUnit)释放资源,避免内存泄漏。
  • 流格式一致性:确保设置的AudioStreamBasicDescription和回调中处理的格式完全匹配,否则会出现杂音或数据错误。
  • 总线编号:RemoteIO的输入总线是1,输出总线是0,配置时不要搞混。

内容的提问来源于stack exchange,提问作者akaiz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:16:49