AVAudioEngine: одновременный запуск набора узлов AVAudioPlayerNode

Я работаю над приложением, которое запускает несколько циклов одновременно и должно поддерживать их синхронизацию.

Используя предыдущие, наивные подходы к проблеме (не используя AVAudioEngine), я обнаружил, что программный запуск ряда аудиоплееров в последовательности дает достаточную задержку между вызовами, что делает результаты бесполезными; удары были слышны не синхронно.

Могу ли я достичь такой функциональности с помощью AVAudioEngine?

В настоящее время я подключаю AVAudioPlayerNodes к микшеру, и я прикрепил к ним буферы и управляю вводом оттуда. Но могу ли я их все запустить одновременно?

Кажется, что узлы не начинают воспроизводить звук до тех пор, пока я не вызову play, а этого нельзя сделать до запуска двигателя...

3 ответа

Для более подробного объяснения взгляните на мой ответ в

AVAudioEngine несколько AVAudioInputNodes не воспроизводятся в идеальной синхронизации


Вот вкратце:

Если ваш движок уже запущен, вы получили @property lastRenderTime в AVAudioNode - суперклассе вашего плеера - это ваш билет на 100% точную синхронизацию кадров сэмплов...

AVAudioFormat *outputFormat = [playerA outputFormatForBus:0];

const float kStartDelayTime = 0.0; // seconds - in case you wanna delay the start

AVAudioFramePosition startSampleTime = playerA.lastRenderTime.sampleTime;

AVAudioTime *startTime = [AVAudioTime timeWithSampleTime:(startSampleTime + (kStartDelayTime * outputFormat.sampleRate)) atRate:outputFormat.sampleRate];

[playerA playAtTime: startTime];
[playerB playAtTime: startTime];
[playerC playAtTime: startTime];
[playerD playAtTime: startTime];

[player...

Между прочим - вы можете достичь того же 100% точного результата сэмплированного кадра с классом AVAudioPlayer...

NSTimeInterval startDelayTime = 0.0; // seconds - in case you wanna delay the start
NSTimeInterval now = playerA.deviceCurrentTime;

NSTimeIntervall startTime = now + startDelayTime;

[playerA playAtTime: startTime];
[playerB playAtTime: startTime];
[playerC playAtTime: startTime];
[playerD playAtTime: startTime];

[player...

Без startDelayTime первые 100-200 мсек всех игроков будут обрезаны, потому что команда запуска фактически отводит время циклу выполнения, хотя игроки уже начали (ну, по расписанию) на 100% в синхронизации в настоящее время. Но с startDelayTime = 0,25 вы готовы. И никогда не забывайте заранее готовить проигрыватели к своим игрокам, чтобы в начальный момент не требовалось выполнять дополнительную буферизацию или настройку - просто запустите их, ребята;-)

Правильный способ сделать это - использовать аудиоустройство микшера. Минимально у вас будет график с микшером и удаленным интерфейсом.

Создайте микшер с двумя входами. Вытягивающая архитектура аудиосистемы iOS будет одновременно воспроизводить два ваших аудио буфера.

Я добился нужного эффекта, просматривая экземпляры AVAudioPlayerNode, которые должны быть синхронизированы, и для каждого из них:

  1. Называть "играть"
  2. Планирование запуска соответствующего буфера в будущем

Код выглядит примерно так:

    for (AVAudioPlayerNode *playerNode in playerNodes) {
        [playerNode play];
        [playerNode scheduleBuffer:loopBufferForThisPlayer atTime:startTime options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
    }

Время, указанное в startTime, - это время в ближайшем будущем (300 мс или около того), чтобы все игровые узлы были готовы к игре. startTime должно основываться на времени хоста; основывайте время на вашем образце, и все будет не синхронизировано.

Все эти узлы были присоединены к AVAudioMixerNode, как описано в вопросе.

Я не заинтересован в использовании задержки таким образом; Я бы предпочел, чтобы мои узлы сообщали мне, когда они были готовы к работе... но это работает.

Другие вопросы по тегам