Я разрабатываю приложение для iPad, которое использует AUSampler AudioUnit в сочетании с другими AudioUnit для воспроизведения звука. AUSampler загружает пресет из файла SoundFont2.
Когда одновременно воспроизводится слишком много нот или ноты воспроизводятся слишком быстро, звук начинает ненадолго пропадать, а затем полностью пропадает. Затем сэмплер становится непригодным для использования до тех пор, пока пресет не будет перезагружен из SoundFont. Это происходит только с некоторыми инструментами (в частности, с пресетами 88, 90 и 94, которые в General MIDI являются пэдом 1 (новый век), пэдом 3 (полисинт) и пэдом 7 (гало) соответственно).
Включив точку останова All Exceptions в XCode, я вижу, что проблема возникает, когда AudioToolbox пытается создать новую VoiceZone для воспроизводимой ноты. Однако это исключение перехватывается где-то ниже в стеке вызовов ниже моего кода, и AudioUnitRender не возвращает никаких ошибок.
Мое лучшее предположение состоит в том, что инструменты «пэд» используют более длинные сэмплы, и поэтому быстрое воспроизведение многих нот / в сочетании требует больше памяти, чем доступно для AudioToolbox.
Кто-нибудь сталкивался с этой проблемой раньше, и если да, то есть ли способ избежать пропадания звука или, по крайней мере, обнаружить их, чтобы изящно с ними справиться?
Некоторые детали:
- SoundFont весит около 150 МБ и содержит множество различных пресетов/инструментов, соответствующих стандарту General MIDI.
- AUSampler находится во главе цепочки блоков эффектов, которые соединяются в AUMultiChannelMixer (
me.masterMixerUnit
).audioOutputCallback
устанавливается как входной обратный вызов выходного элемента модуля RemoteIO.
Backtrace (на AURemoteIO::IOThread):
* thread #14: tid = 0x3203, 0x368c8498 libc++abi.dylib`__cxa_throw, stop reason = breakpoint 1.2
frame #0: 0x368c8498 libc++abi.dylib`__cxa_throw
frame #1: 0x35d46a60 AudioToolbox`VoiceZone::operator new(unsigned long) + 340
frame #2: 0x35d46b6a AudioToolbox`VoiceZone::NewVoiceZone(SamplerNote*, ZoneState*, float, float, unsigned long) + 86
frame #3: 0x35d3f846 AudioToolbox`SamplerNote::Configure(InstrumentState*) + 1106
frame #4: 0x35d3ff2c AudioToolbox`non-virtual thunk to SamplerNote::Attack(MusicDeviceNoteParams const&) + 24
frame #5: 0x35d551ea AudioToolbox`SynthNote::AttackNote(SynthPartElement*, SynthGroupElement*, unsigned long, unsigned long long, unsigned long, MusicDeviceNoteParams const&) + 58
frame #6: 0x35d5485a AudioToolbox`SynthGroupElement::NoteOn(SynthNote*, SynthPartElement*, unsigned long, unsigned long, MusicDeviceNoteParams const&) + 70
frame #7: 0x35d3d220 AudioToolbox`SamplerElement::StartNote(SynthPartElement*, unsigned long, unsigned long, MusicDeviceNoteParams const&) + 344
frame #8: 0x35d3c4fa AudioToolbox`Sampler::RealTimeStartNote(SynthGroupElement*, unsigned long, unsigned long, MusicDeviceNoteParams const&) + 94
frame #9: 0x35d530a0 AudioToolbox`AUInstrumentBase::PerformEvents(AudioTimeStamp const&) + 164
frame #10: 0x35d5313c AudioToolbox`AUInstrumentBase::Render(unsigned long&, AudioTimeStamp const&, unsigned long) + 20
frame #11: 0x35d3c564 AudioToolbox`Sampler::Render(unsigned long&, AudioTimeStamp const&, unsigned long) + 68
frame #12: 0x35c2435a AudioToolbox`AUBase::DoRenderBus(unsigned long&, AudioTimeStamp const&, unsigned long, AUOutputElement*, unsigned long, AudioBufferList&) + 210
frame #13: 0x35c24184 AudioToolbox`AUBase::DoRender(unsigned long&, AudioTimeStamp const&, unsigned long, unsigned long, AudioBufferList&) + 496
frame #14: 0x35c23f8a AudioToolbox`AUMethodRender(void*, unsigned long*, AudioTimeStamp const*, unsigned long, unsigned long, AudioBufferList*) + 50
... render methods of other AudioUnits in chain ...
frame #32: 0x00081aaa SoundBrush 2`audioOutputCallback + 194 at SoundEngine.mm:1375
Исключение возникает во время вызова AudioUnitRender в моем обратном вызове ниже:
static OSStatus audioOutputCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
//get a reference to the SoundEngine
SoundEngine *me = (__bridge SoundEngine *)inRefCon;
// render the sound
OSStatus err = AudioUnitRender(me.masterMixerUnit,
ioActionFlags,
inTimeStamp,
kAudioUnitBus_Output,
inNumberFrames,
ioData);
if (err != noErr) NSLog(@"AudioUnitRender failed [%ld]", err);
// ... do some processing ...
return err;
}