Простое воспроизведение аудио с низкой задержкой в ​​iOS Swift

Я новичок в iOS и пытаюсь разработать приложение для игры на ударных с помощью Swift. Я разработал вид с помощью одной кнопки и написал код ниже, но у него есть некоторые проблемы:

  1. Некоторые звуки теряются, когда я быстро касаюсь кнопки, как барабанная дробь.
  2. Все еще в "барабанной дроби" звук прерывается при каждом касании кнопки, вместо того, чтобы позволить семплу воспроизводиться до его окончания. Например, в кимвалах это ужасно. Мне бы хотелось, чтобы каждый семпл звучал полностью, даже если я снова нажму кнопку.
  3. Существует задержка между касанием и звуком. я знаю это AVAudioPlayer не лучший выбор для аудио с низкой задержкой, но как новичку, это трудно учиться OpenAL, AudioUnit без примеров кода или учебных пособий в Swift, Проблема заключается в следующем: какую платформу я должен использовать для воспроизведения аудиофайлов (WAV, MP3, AIFF) в iOS с низкой задержкой?,

Код:

override func viewDidLoad() {
    super.viewDidLoad()

    // Enable multiple touch for the button
    for v in view.subviews {
        if v.isKindOfClass(UIButton) {
            v.multipleTouchEnabled = true
        }
    }

    // Init audio
    audioURL = NSBundle.mainBundle().URLForResource("snareDrum", withExtension: "wav")!
    do {
        player = try AVAudioPlayer(contentsOfURL: audioURL)
        player?.prepareToPlay()
    } catch {
        print("AVAudioPlayer Error")
    }
}

override func viewDidDisappear(animated: Bool) {
    super.viewDidDisappear(animated)

    player?.stop()
    player = nil
}

@IBAction func playSound(sender: UIButton) {
    player?.currentTime = 0
    player?.play()
}

2 ответа

Решение

Если вам нужна чрезвычайно низкая задержка, я обнаружил очень простое решение, доступное в синглтоне AVAudioSession (которое создается автоматически при запуске приложения):

Во-первых, получите ссылку на синглтон AVAudioSession вашего приложения, используя этот метод класса:

(из ссылки на класс AVAudioSession):

Получение общего аудио сеанса

Декларация SWIFT

class func sharedInstance() -> AVAudioSession

Затем попытайтесь установить предпочтительную длительность буфера ввода-вывода на очень короткое значение (например, 002), используя этот метод экземпляра:

Устанавливает предпочтительную продолжительность буфера аудио ввода / вывода в секундах.

Декларация SWIFT

func setPreferredIOBufferDuration(_ duration: NSTimeInterval) throws

параметры

duration Продолжительность буфера аудио ввода / вывода, в секундах, которую вы хотите использовать.

outError На входе указатель на объект ошибки. Если возникает ошибка, указатель устанавливается на объект NSError, который описывает ошибку. Если вы не хотите получать информацию об ошибке, введите nil. Возвращаемое значение true, если запрос был успешно выполнен, или false в противном случае.

обсуждение

Этот метод запрашивает изменение длительности буфера ввода / вывода. Чтобы определить, вступит ли изменение в силу, используйте свойство IOBufferDuration. Для получения дополнительной информации см. Настройка аудиосеанса.


Имейте в виду примечание непосредственно над IOBufferDuration свойство фактически установлено на значение, переданное в func setPrefferedIOBufferDuration(_ duration: NSTimeInterval) throws метод, зависит от функции, не возвращающей ошибку, и других факторов, которые мне не совсем понятны. Кроме того, в моем тестировании я обнаружил, что если вы установите это значение на чрезвычайно низкое значение, значение (или что-то близкое к нему) действительно будет установлено, но при воспроизведении файла (например, с использованием AVAudioPlayerNode) звук не будет играть Нет ошибки, просто нет звука. Это очевидно проблема. И я не обнаружил, как проверить эту проблему, кроме как заметил отсутствие звука, попавшего мне в ухо во время тестирования на реальном устройстве. Я посмотрю на это. Но сейчас я бы порекомендовал установить предпочтительную продолжительность не менее 0,002 или 0,0015. Значение.0015, похоже, работает для iPad Air (модель A1474), на котором я тестирую. В то время как всего лишь.0012, кажется, хорошо работает на моем iPhone 6S.

И еще одна вещь, которую следует учитывать с точки зрения загрузки процессора, - это формат аудиофайла. Несжатые форматы будут иметь очень низкую нагрузку на процессор при воспроизведении. Apple рекомендует использовать файлы CAF для наивысшего качества и минимальных накладных расходов. Для сжатых файлов и минимальных накладных расходов вы должны использовать сжатие IMA4:

(Из Руководства по программированию мультимедиа для iOS):

Предпочтительные форматы аудио в iOS Для несжатого (высочайшего качества) звука используйте 16-битные линейные аудиоданные PCM с прямым порядком байтов, упакованные в файл CAF. Вы можете преобразовать аудиофайл в этот формат в Mac OS X, используя инструмент командной строки afconvert, как показано здесь:

/usr/bin/afconvert -f caff -d LEI16 {INPUT} {OUTPUT}

Для меньшего использования памяти при необходимости одновременного воспроизведения нескольких звуков используйте сжатие IMA4 (IMA/ADPCM). Это уменьшает размер файла, но влечет за собой минимальное влияние на процессор при распаковке. Как и в случае с линейными данными PCM, упакуйте данные IMA4 в файл CAF.

Вы также можете конвертировать в IMA4, используя afconvert:

/usr/bin/afconvert -f AIFC -d ima4 [file]

Я провел день, пытаясь решить эту проблему, играя с AVAudioPlayer и AVAudioSession, но я ничего не мог с этим поделать. (К сожалению, установка продолжительности буфера ввода-вывода в соответствии с предложенным здесь принятым ответом, похоже, не помогла.) Я также попробовал AudioToolbox, но обнаружил, что результирующая производительность была почти такой же - явно ощутимая задержка между соответствующими действиями пользователя. и аудио.

Поработав в Интернете немного больше, я наткнулся на это:

www.rockhoppertech.com/blog/swift-avfoundation/

Раздел о AVAudioEngine оказался очень полезным. Код ниже представляет собой небольшую переработку:

import UIKit
import AVFoundation

class ViewController: UIViewController {

var engine = AVAudioEngine()
var playerNode = AVAudioPlayerNode()
var mixerNode: AVAudioMixerNode?
var audioFile: AVAudioFile?

@IBOutlet var button: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    engine.attach(playerNode)
    mixerNode = engine.mainMixerNode

    engine.connect(playerNode, to: mixerNode!, format: mixerNode!.outputFormat(forBus: 0))

    do {
        try engine.start()
    }

    catch let error {
        print("Error starting engine: \(error.localizedDescription)")
    }

    let url = Bundle.main.url(forResource: "click_04", withExtension: ".wav")

    do {
        try audioFile = AVAudioFile(forReading: url!)
    }

    catch let error {
        print("Error opening audio file: \(error.localizedDescription)")
    }
}

@IBAction func playSound(_ sender: Any) {

    engine.connect(playerNode, to: engine.mainMixerNode, format: audioFile?.processingFormat)
    playerNode.scheduleFile(audioFile!, at: nil, completionHandler: nil)

    if engine.isRunning{
        playerNode.play()
    } else {
        print ("engine not running")
    }
}

}

Это может быть не идеально, так как я новичок в Swift и раньше не использовал AVAudioEngine. Это, кажется, работает, хотя!

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