Воспроизведение стереоаудиобуфера из памяти с помощью AVAudioEngine

Я пытаюсь воспроизвести аудиобуфер стерео из памяти (не из файла) в моем приложении iOS, но мое приложение вылетает, когда я пытаюсь прикрепить AVAudioPlayerNode 'playerNode' к AVAudioEngine 'audioEngine'. Код ошибки, который я получаю, выглядит следующим образом:

Thread 1: Exception: "required condition is false: _outputFormat.channelCount == buffer.format.channelCount"

Я не знаю, связано ли это с тем, как я объявил AVAudioEngine, AVAudioPlayerNode, если что-то не так с создаваемым мной буфером, или я неправильно прикрепляю узлы (или что-то еще!). У меня такое чувство, что это как-то связано с тем, как я создаю новый буфер. Я пытаюсь создать стереобуфер из двух отдельных массивов «моно», и, возможно, его формат неверен.

Я объявил audioEngine: AVAudioEngine! и playerNode: AVAudioPlayerNode! глобально:

var audioEngine: AVAudioEngine!
var playerNode: AVAudioPlayerNode!

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

    // Read audio file
    let audioFileFormat = audioFile.processingFormat
    let frameCount = UInt32(audioFile.length)
    let audioBuffer = AVAudioPCMBuffer(pcmFormat: audioFileFormat, frameCapacity: frameCount)!
    
    // Read audio data into buffer
    do {
        try audioFile.read(into: audioBuffer)
    } catch let error {
        print(error.localizedDescription)
    }
    // Convert buffer to array of floats
    let input: [Float] = Array(UnsafeBufferPointer(start: audioBuffer.floatChannelData![0], count: Int(audioBuffer.frameLength)))

Затем массив отправляется в функцию свертки дважды, которая каждый раз возвращает новый массив. Это связано с тем, что исходный моно-файл должен стать стереофоническим звуковым буфером:

    maxSignalLength = input.count + 256
    let leftAudioArray: [Float] = convolve(inputAudio: input, impulse: normalisedLeftImpulse)
    let rightAudioArray: [Float] = convolve(inputAudio: input, impulse: normalisedRightImpulse)

Переменная maxSignalLength в настоящее время представляет собой длину входного сигнала + длину импульсного отклика (normalisedImpulseResponse), с которым выполняется свертка, которая на данный момент равна 256. В какой-то момент это станет подходящей переменной.

Затем я объявляю и загружаю новый буфер и его формат, у меня такое ощущение, что ошибка где-то здесь, так как это будет буфер, который воспроизводится:

    let bufferFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: hrtfSampleRate, channels: 2, interleaved: false)!
    let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: AVAudioFrameCount(maxSignalLength))!

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

        for ch in 0 ..< 2 {
        for i in 0 ..< maxSignalLength {
            
            var val: Float!
            
            if ch == 0 { // Left
                
                val = leftAudioArray[i]
                // Limit
                if val > 1 {
                    val = 1
                }
                if val < -1 {
                    val = -1
                }
                
            } else if ch == 1 { // Right
                
                val = rightAudioArray[i]
                // Limit
                if val < 1 {
                    val = 1
                }
                if val < -1 {
                    val = -1
                }
            }
            
            outputBuffer.floatChannelData![ch][i] = val
        }
    }

Аудио также ограничено значениями от -1 до 1.

Затем я, наконец, прихожу (пытаюсь) загрузить буфер в аудиоузел, присоединяю аудиоузел к аудио движку, запускаю аудио движок и затем воспроизводю узел.

    let frameCapacity = AVAudioFramePosition(outputBuffer.frameCapacity)
    let frameLength = outputBuffer.frameLength
    
    playerNode.scheduleBuffer(outputBuffer, at: nil, options: AVAudioPlayerNodeBufferOptions.interrupts, completionHandler: nil)
    playerNode.prepare(withFrameCount: frameLength)
    let time = AVAudioTime(sampleTime: frameCapacity, atRate: hrtfSampleRate)
    
    audioEngine.attach(playerNode)
    audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: outputBuffer.format)
    audioEngine.prepare()
    do {
        try audioEngine.start()
    } catch let error {
        print(error.localizedDescription)
    }
    
    playerNode.play(at: time)

Ошибка, которую я получаю во время выполнения:

AVAEInternal.h:76    required condition is false: [AVAudioPlayerNode.mm:712:ScheduleBuffer: (_outputFormat.channelCount == buffer.format.channelCount)]

Строка, в которой возникает эта ошибка, не отображается. Я застрял на этом некоторое время и пробовал много разных вещей, но, похоже, нет очень четкой информации о воспроизведении звука из памяти, а не из файлов с помощью AVAudioEngine из того, что я мог найти. Любая помощь будет принята с благодарностью.

Спасибо!

Редактировать # 1: Лучшее название

Изменить № 2: ОБНОВЛЕНИЕ - я выяснил, почему получаю ошибку. Похоже, это было вызвано настройкой playerNode перед его подключением к audioEngine. Смена порядка остановила сбой программы и выдачу ошибки:

    let frameCapacity = AVAudioFramePosition(outputBuffer.frameCapacity)
    let frameLength = outputBuffer.frameLength
    
    audioEngine.attach(playerNode)
    audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: outputBuffer.format)
    audioEngine.prepare()
    
    playerNode.scheduleBuffer(outputBuffer, at: nil, options: AVAudioPlayerNodeBufferOptions.interrupts, completionHandler: nil)
    playerNode.prepare(withFrameCount: frameLength)
    let time = AVAudioTime(sampleTime: frameCapacity, atRate: hrtfSampleRate)
    
    do {
        try audioEngine.start()
    } catch let error {
        print(error.localizedDescription)
    }

    playerNode.play(at: time)

Однако у меня нет звука. После создания массива с плавающей запятой в outputBuffer с помощью того же метода, который использовался для входного сигнала, и просмотра его содержимого с точкой останова он кажется пустым, поэтому я также должен неправильно сохранять данные в outputBuffer.


person Maxadax    schedule 31.08.2020    source источник


Ответы (2)


Возможно, вы неправильно создаете и заполняете буфер. Попробуйте сделать это так:

let fileURL = Bundle.main.url(forResource: "my_file", withExtension: "aiff")!
let file = try! AVAudioFile(forReading: fileURL)
let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: UInt32(file.length))!
try! file.read(into: buffer)
person Eric    schedule 02.09.2020
comment
Да, я думаю, что это проблема инициализации буфера, но это решение не сработает для меня, потому что мне нужен стереовыход, а формат аудиофайла, используемого для ввода, - моно. Я получаю сообщение об ошибке при загрузке в буфер, когда пробовал это, вероятно, потому что буфер не принимает несколько каналов - person Maxadax; 02.09.2020

Я исправил проблему!

Я попробовал множество решений и в итоге полностью переписал раздел звукового движка моего приложения, и теперь у меня есть AVAudioEngine и AVAudioPlayerNode, объявленные в классе ViewController следующим образом:

class ViewController: UIViewController {

var audioEngine: AVAudioEngine = AVAudioEngine()
var playerNode: AVAudioPlayerNode = AVAudioPlayerNode()

...

Мне до сих пор неясно, лучше ли объявить их глобально или как переменные класса в iOS, однако я могу подтвердить, что мое приложение воспроизводит звук с ними, объявленными в классе ViewController. Я знаю, что их не следует объявлять в функции, поскольку ​​они исчезнут и перестанут воспроизводиться, когда функция сработает выходит за рамки.

Однако я по-прежнему не получал никакого звука, пока я не установил AVAudioPCMBuffer.frameLength в frameCapacity.

Я смог найти в Интернете очень мало информации о создании нового AVAudioPCMBuffer из массива с плавающей запятой, но это, похоже, недостающий шаг, который мне нужно было сделать, чтобы мой outputBuffer стал воспроизводимым. До того, как я установил это, по умолчанию он был равен 0.

Член frameLength не требуется в объявлении класса AVAudioFormat. Но это важно, и мой буфер нельзя было воспроизвести, пока я не установил его вручную, и после объявления экземпляра класса:

let bufferFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: hrtfSampleRate, channels: 2, interleaved: false)!
let frameCapacity = UInt32(audioFile.length)
guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: frameCapacity) else {
    fatalError("Could not create output buffer.")
}
outputBuffer.frameLength = frameCapacity // Important!

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

person Maxadax    schedule 04.09.2020
comment
У меня была такая же проблема. Оказалось, что format, который я поставлял своему AVAudioEngine экземпляру, не соответствует предоставленному формату буфера. Поэтому мне просто нужно было создать движок после того, как я настроил свои буферы PCM и предоставил согласованный формат для движка из них, например engine.connect(player, to: engine.mainMixerNode, format: formatUsedByMyPCMBuffers). Но теперь я получаю еще одну ошибку: ca_require: ValidFormat(inScope, inElement, newDesc) InvalidFormat - person Pixel; 25.09.2020