基于AVAudioUnitSampler的AUv3扩展未注册、GarageBand无法识别问题
方案可行性评估
你提到的通过installTapOnBus缓存缓冲区再在渲染回调中读取的方案不可行,核心问题如下:
- 实时音频渲染线程不允许访问ObjC对象(你用到的
timeToBuffer字典读写是非线程安全的,会触发锁、自动释放池开销,轻则爆音重则崩溃) - 渲染回调和Tap回调的调度时序不完全对齐,时间戳匹配误差会导致丢帧、音频断裂
- 缓冲区堆积的内存问题无有效解法,长时间运行必然出现内存告警、应用闪退
插件不被GarageBand识别的修复方案
首先修正你的配置错误,这是插件无法被识别的核心原因:
- aumu(乐器类)AUv3扩展的
NSExtensionPrincipalClass必须指向AUAudioUnit子类,不能是视图控制器,视图控制器仅负责UI层逻辑,音频处理的主类是AUAudioUnit的实现类 AudioComponents条目必填项必须完整:type固定为aumu,subtype填4个独一无二的自定义字符(如MSam),manufacturer填4个开发者标识字符(如Cjed),name填插件对外显示名称,版本号必须大于0- 扩展目标的签名、团队ID必须和宿主App完全一致,部署目标不要高于iOS14,保证和GarageBand兼容
- 测试流程:先运行宿主App到设备,确认App已被系统信任(设置-通用-VPN与设备管理中校验),再重启GarageBand即可在乐器列表找到插件
- 无需手动注册插件,配置正确的前提下系统会在安装宿主App时自动注册AU扩展
正确的AUAudioUnit子类实现
你不需要手动缓存缓冲区,直接在AUAudioUnit子类中桥接内部AVAudioEngine的输出即可,核心实现代码如下:
接口定义
@interface AVSampler : AUAudioUnit @property (nonatomic, strong) AVAudioEngine *engine; @property (nonatomic, strong) AVAudioMixerNode *mainMixer; @property (nonatomic, strong) NSArray<AVAudioUnitSampler *> *samplers; @end
核心方法实现
初始化方法
- (instancetype)initWithComponentDescription:(AudioComponentDescription)componentDescription options:(AudioComponentInstantiationOptions)options error:(NSError **)outError { self = [super initWithComponentDescription:componentDescription options:options error:outError]; if (!self) return nil; // 初始化音频引擎、混音器 _engine = [[AVAudioEngine alloc] init]; _mainMixer = [AVAudioMixerNode new]; [_engine attachNode:_mainMixer]; // 初始化16个MIDI通道对应的采样器 NSMutableArray *samplerArr = [NSMutableArray arrayWithCapacity:16]; AVAudioFormat *format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:44100 channels:2 interleaved:NO]; for (int i = 0; i < 16; i++) { AVAudioUnitSampler *sampler = [AVAudioUnitSampler new]; [_engine attachNode:sampler]; [_engine connect:sampler to:_mainMixer format:format]; [samplerArr addObject:sampler]; } _samplers = samplerArr.copy; [_engine connect:_mainMixer to:_engine.outputNode format:format]; // 配置输出总线 self.outputBusses = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self busType:AUAudioUnitBusTypeOutput busses:@[[[AUAudioUnitBus alloc] initWithFormat:format error:nil]]]; self.maximumFramesToRender = 4096; return self; }
资源分配/释放方法
- (BOOL)allocateRenderResourcesAndReturnError:(NSError **)outError { if (![super allocateRenderResourcesAndReturnError:outError]) return NO; NSError *engineError = nil; if (![_engine startAndReturnError:&engineError]) { *outError = engineError; return NO; } return YES; } - (void)deallocateRenderResources { [_engine stop]; [super deallocateRenderResources]; }
渲染回调实现
- (AUInternalRenderBlock)internalRenderBlock { __weak AVSampler *weakSelf = self; AVAudioMixerNode *mixer = _mainMixer; return ^AUAudioUnitStatus(AudioUnitRenderActionFlags *actionFlags, const AudioTimeStamp *timestamp, AVAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList *outputBufferListPtr, const AURenderEvent *realtimeEventListHead, AURenderPullInputBlock pullInputBlock) { // 分发实时MIDI事件到对应通道的采样器 const AURenderEvent *event = realtimeEventListHead; while (event) { if (event->head.eventType == AURenderEventMIDI) { UInt8 status = event->MIDI.data[0]; UInt8 channel = status & 0x0F; if (channel < 16) { AVAudioUnitSampler *sampler = weakSelf.samplers[channel]; MusicDeviceMIDIEvent(sampler.audioUnit, status, event->MIDI.data[1], event->MIDI.data[2], 0); } } event = event->head.next; } // 直接拉取混音器输出到AU的输出缓冲区 AudioUnitRenderActionFlags renderFlags = 0; return AudioUnitRender(mixer.audioUnit, &renderFlags, timestamp, 0, frameCount, outputBufferListPtr); }; }
如果需要自定义UI,在AVSampler类中实现- (UIViewController *)provideViewControllerWithSize:(CGSize)maximumSize方法,返回你已有的MultiSamplerViewController实例即可。
内容的提问来源于stack exchange,提问作者cjed
相关产品推荐
相关产品推荐

