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

iOS平台Inter-App Audio接入咨询:可发现性、事件响应与Tempo/Beat获取

Inter-App Audio 适配指南(针对你的AUGraph场景)

我之前在iOS上做过Inter-App Audio的适配,刚好能对应你这三个疑问,咱们逐一解决:

1. 让应用输出节点在Inter-App Audio中被发现(GarageBand可见)

要让你的App成为Inter-App Audio的输出源,需要做两步核心配置:

第一步:Info.plist 配置

首先在Info.plist里添加Inter-App Audio相关的键值对,告诉系统你的App支持IAA:

  • 添加 NSMicrophoneUsageDescription(GarageBand连接时可能会请求录制权限)
  • 添加 AudioComponents 数组,描述你的音频组件:
<key>AudioComponents</key>
<array>
    <dict>
        <key>type</key>
        <string>auou</string> <!-- 输出类型组件标识 -->
        <key>subtype</key>
        <string>YOUR_APP_ID</string> <!-- 自定义唯一ID,比如com.yourapp.iaaoutput -->
        <key>manufacturer</key>
        <string>YOUR_MANU_ID</string> <!-- 厂商ID,比如com.yourapp -->
        <key>name</key>
        <string>Your App IAA Output</string> <!-- GarageBand里显示的名称 -->
        <key>version</key>
        <integer>1</integer>
        <key>description</key>
        <string>Your App's Inter-App Audio Output</string>
    </dict>
</array>

第二步:注册AUGraph输出节点为IAA组件

在你的AUGraph初始化完成后,给输出节点开启IAA支持并注册到系统:

// 从AUGraph中取出你的输出AudioUnit
AudioUnit outputUnit = ...;

// 开启Inter-App Audio支持
UInt32 enableIAA = 1;
AudioUnitSetProperty(outputUnit,
                     kAudioUnitProperty_IsInterAppAudioSupported,
                     kAudioUnitScope_Global,
                     0,
                     &enableIAA,
                     sizeof(enableIAA));

// 注册组件到系统,让GarageBand能扫描到
AudioComponentDescription desc = {0};
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO; // 若用自定义subtype,需和Info.plist一致
desc.componentManufacturer = kAudioUnitManufacturer_Apple;

AudioComponentRegister(&desc);

完成这两步后,GarageBand在扫描Inter-App Audio设备时就能看到你的App了。

2. 响应播放、录制、重置等事件

Inter-App Audio宿主(比如GarageBand)会通过Host Callbacks和MIDI消息发送控制指令,你需要给AudioUnit绑定回调来处理这些事件:

设置Host Callback处理传输状态

定义回调函数并绑定到输出AudioUnit,处理播放、停止、重置等事件:

OSStatus HostCallback(void *inRefCon,
                      AudioUnitPropertyID inID,
                      AudioUnitScope inScope,
                      AudioUnitElement inElement,
                      void *ioData) {
    YourAppContext *context = (__bridge YourAppContext *)inRefCon;
    
    switch (inID) {
        case HostCallback_GetTransportState: {
            AudioUnitHostTransportState *state = (AudioUnitHostTransportState *)ioData;
            if (state->transportState == kAudioUnitHostTransportState_Playing) {
                // 启动你的AUGraph和音频节点
                AUGraphStart(context->graph);
            } else if (state->transportState == kAudioUnitHostTransportState_Stopped) {
                // 暂停AUGraph,重置播放状态
                AUGraphStop(context->graph);
            }
            return noErr;
        }
        case HostCallback_Reset: {
            // 处理重置事件:比如重置FilePlayers的播放头到起始位置
            AudioUnit filePlayer = ...;
            UInt32 startFrame = 0;
            AudioUnitSetProperty(filePlayer, kAudioUnitProperty_CurrentPlayTime, kAudioUnitScope_Global, 0, &startFrame, sizeof(startFrame));
            return noErr;
        }
        default:
            return noErr;
    }
}

// 绑定回调到输出AudioUnit
AudioUnitSetProperty(outputUnit,
                     kAudioUnitProperty_HostCallbacks,
                     kAudioUnitScope_Global,
                     0,
                     &HostCallback,
                     sizeof(HostCallback));

处理MIDI控制消息

宿主也会通过MIDI发送播放/停止指令,你需要设置MIDI回调来捕获这些消息:

void MIDICallback(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
    YourAppContext *context = (__bridge YourAppContext *)readProcRefCon;
    const MIDIPacket *packet = &pktlist->packet[0];
    
    for (UInt32 i = 0; i < pktlist->numPackets; i++) {
        Byte status = packet->data[0] & 0xF0;
        if (status == 0xFA) { // MIDI播放开始消息
            AUGraphStart(context->graph);
        } else if (status == 0xFC) { // MIDI停止消息
            AUGraphStop(context->graph);
        } else if (status == 0xFB) { // MIDI继续消息
            AUGraphStart(context->graph);
        }
        packet = MIDIPacketNext(packet);
    }
}

// 给输出节点绑定MIDI回调
MIDIInputPortCreate(context->midiClient, CFSTR("IAA MIDI Input"), MIDICallback, (__bridge void *)context, &context->midiInputPort);

3. 获取GarageBand的Tempo和Beat

你提到的HostCallback_GetBeatAndTempo和HostCallback_GetMusicalTimeLocation就是获取节拍、速度的核心接口,需要在Host Callback中实现对应的case:

通过Host Callback获取实时信息

OSStatus HostCallback(void *inRefCon,
                      AudioUnitPropertyID inID,
                      AudioUnitScope inScope,
                      AudioUnitElement inElement,
                      void *ioData) {
    YourAppContext *context = (__bridge YourAppContext *)inRefCon;
    
    switch (inID) {
        case HostCallback_GetBeatAndTempo: {
            AudioUnitHostBeatAndTempo *beatTempo = (AudioUnitHostBeatAndTempo *)ioData;
            // 宿主会自动填充当前速度和节拍位置
            context->currentTempo = beatTempo->tempo; // 速度(BPM)
            context->currentBeat = beatTempo->beatPosition; // 当前节拍位置
            return noErr;
        }
        case HostCallback_GetMusicalTimeLocation: {
            AudioUnitHostMusicalTimeLocation *timeLoc = (AudioUnitHostMusicalTimeLocation *)ioData;
            // 获取小节位置、拍号等详细信息
            context->currentBar = timeLoc->barPosition;
            context->timeSigNumerator = timeLoc->timeSignature.numerator;
            context->timeSigDenominator = timeLoc->timeSignature.denominator;
            return noErr;
        }
        // ... 其他case
        default:
            return noErr;
    }
}

主动查询节拍和速度

如果需要主动获取(而非等待宿主触发回调),可以通过AudioUnitGetProperty调用:

AudioUnitHostBeatAndTempo beatTempo = {0};
UInt32 size = sizeof(beatTempo);
AudioUnitGetProperty(outputUnit,
                     HostCallback_GetBeatAndTempo,
                     kAudioUnitScope_Global,
                     0,
                     &beatTempo,
                     &size);
// 使用beatTempo.tempo和beatTempo.beatPosition更新你的应用状态

注意:所有音频相关的回调和属性操作,都要在音频线程或主线程安全的环境下执行,避免线程冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:51:30