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

