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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 03:45:06