如何在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
相关产品推荐
相关产品推荐

