Синхронизация AVAudioEngine для MIDI воспроизведения и записи

голоса
1

Вопрос 1 Мой первый вопрос касается воспроизведения синхронизации при использовании AVAudioPlayerNodeи AVAudioSequencerдля MIDI. В основном я пытаюсь играть что - то более MIDI, но они должны быть идеально синхронизированы.

Я знаю , есть синхронизация методы AVAudioPlayerNodeс, но секвенсор не кажется, что - то вроде этого.

В настоящее время я пытался использовать CAMediaTime() + delayи usleepна отдельные темы, но они , кажется, не очень хорошо работают.

Вопрос 2 Я использую кран на , engine.inputNodeчтобы получить записи, отдельно от воспроизведения музыки. Тем не менее, похоже , начинается раньше запись. Когда сравнивать записанные данные с первоначальным воспроизведения, разница составляет около 300 мс. Я мог бы начать запись 300 мс позже, но даже тогда, что не гарантирует точную синхронизацию и, вероятно, будет машина в зависимости.

Так что мой вопрос, что бы быть хорошим способом, чтобы гарантировать, что запись начинается именно в тот момент, начинается воспроизведение?

Задан 20/10/2018 в 05:35
источник пользователем
На других языках...                            


2 ответов

голоса
3

Для синхронизации аудио Ио, часто лучше всего создать отсчет время, а затем использовать это время для всех связанных с временными расчетами.

AVAudioPlayerNode.play (в :) , что вам нужно для игрока. Для крана необходимо отфильтровать (частичные) буфера вручную , используя время , представленное в закрытии. AVAudioSequencer , к сожалению , не имеет средства для запуска в определенное время, но вы можете получить начало отсчета время коррелировал с тактом с уже играет секвенсор с помощью hostTime (forBeats) . Если я правильно помню, вы не можете установить секвенсор в отрицательную позицию, так что это не является идеальным.

Вот Hacky обходного пути, который должен дать очень точные результаты:

AVAudioSequencer должен быть запущен до получения отсчета времени, смещение всех ваших миди данных на 1, запустите секвенсор, а затем сразу же получить начало отсчета времени коррелировали бить 1, а затем синхронизировать начало проигрывателя к этому времени, а также использовать его чтобы отфильтровать нежелательные аудиофайлы крана.

func syncStart() throws {
    //setup
    sequencer.currentPositionInBeats = 0
    player.scheduleFile(myFile, at: nil)
    player.prepare(withFrameCount: 4096)

    // Start and get reference time of beat 1
    try sequencer.start()
    // Wait until first render cycle completes or hostTime(forBeats) will err - AVAudioSequencer is fragile :/
    while (self.sequencer.currentPositionInBeats <= 0) { usleep(UInt32(0.001 * 1000000.0)) }
    var nsError: NSError?
    let hostTime = sequencer.hostTime(forBeats: 1, error: &nsError)
    let referenceTime = AVAudioTime(hostTime: hostTime)

    // AVAudioPlayer is great for this.
    player.play(at: referenceTime)

    // This just rejects buffers that come too soon. To do this right you need to record partial buffers.
    engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: nil) { (buffer, audioTime) in
        guard audioTime.hostTime >= referenceTime.hostTime else { return }
        self.recordBuffer(buffer: buffer)
    }
}
Ответил 24/10/2018 в 01:43
источник пользователем

голоса
-1

Ответ dave234 в , к сожалению , не работает для меня , потому что hostTime(forBeats:error:)терпел крах , даже после запуска секвенсера первого. (Это было работать , когда я направил асинхронно после некоторой задержки, но это приведет к дальнейшему усложнению). Тем не менее, он обеспечивает ценную информацию о методах синхронизации, и вот что я сделал:

var refTime: AVAudioTime

if isMIDIPlayer {
    sequencer!.tracks.forEach { $0.offsetTime = 1 }
    sequencer!.currentPositionInBeats = 0

    let sec = sequencer!.seconds(forBeats: 1)
    let delta = AVAudioTime.hostTime(forSeconds: sec) + mach_absolute_time()
    refTime = AVAudioTime(hostTime: delta)

    try sequencer!.start()
} else {
    player!.prepare(withFrameCount: 4096)

    let delta = AVAudioTime.hostTime(forSeconds: 0.5) + mach_absolute_time()
    refTime = AVAudioTime(hostTime: delta)

    player!.play(at: refTime)
}

mixer.installTap(
    onBus: 0,
    bufferSize: 8,
    format: mixer.outputFormat(forBus: 0)
) { [weak self] (buffer, time) in
    guard let strongSelf = self else { return }
    guard time.hostTime >= refTime.hostTime else { print("NOPE"); return }

    do {
        try strongSelf.recordFile!.write(from: buffer)
    } catch {
        // TODO: Handle error
        print(error)
    }
}

Некоторые пояснения о фрагменте кода:

  • Я сделал общий , AudioPlayerкоторый может играть как MIDI и другие файлы песни, а код из метода внутри AudioPlayer.
  • sequencer используется для воспроизведения MIDI.
  • player используются для других файлов песни.

Синхронизация воспроизведения MIDI использует аналогичный метод, например, так:

midi!.sequencer!.tracks.forEach { $0.offsetTime = 1 }

let sec = midi!.sequencer!.seconds(forBeats: 1)
let delta = AVAudioTime.hostTime(forSeconds: sec) + mach_absolute_time()
let refTime = AVAudioTime(hostTime: delta)

do {
    try midi!.play()
} catch {
    // TODO: Add error handler
    print(error)
}

song2.playAtTime(refTime)

Здесь, midiявляется AVAudioSequencerобъектом, и song2это , AVAudioPlayerNodeчто играет регулярную песню.

Работает как шарм!

Ответил 26/10/2018 в 06:11
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more