Буфер расписания с данными AVAudioPCMBuffer int16

Я пытаюсь воспроизвести музыку из массива байтов, поступающего из сети в формате данных pcmInt16.

// formats
let format1 = AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatFloat32, sampleRate: 48000, channels: 1, interleaved: false)!
let format2 = AVAudioFormat(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: 48000, channels: 1, interleaved: false)!

// byte array buffer
var byteArray: [Int16]! // one packet size is 512

...
// 1. create / attach / connect engine
engine.prepare()
try! engine.start()
engine.attach(playerNode)
engine.connect(playerNode, to: engine.mainMixerNode, format: format1)

// 2. fill byteArray with music stream // int16 48kHz 32bit
...

// 3.
var len = 512
let pcmBuffer = AVAudioPCMBuffer(pcmFormat: format2, frameCapacity: AVAudioFrameCount(len))!

// HERE
// How to set the first 512 data from byteArray ?
playerNode.scheduleBuffer(pcmBuffer, completionHandler: nil)

Как установить первые 512 данных из byteArray? я пробовал что-то вроде этого, но это не работает: memcpy(pcmBuffer.audioBufferList.pointee.mBuffers.mData, byteArray[0..<512], len * 2)


person szuniverse    schedule 11.03.2020    source источник


Ответы (2)


AVAudioMixerNode хорош для преобразования sampleRate, но для широких изменений формата, таких как Int16 в Float, вам, вероятно, лучше конвертировать самостоятельно. Для производительности я предлагаю использовать vDSP Accelerate.

import Cocoa
import AVFoundation
import Accelerate
import PlaygroundSupport


let bufferSize = 512
let bufferByteSize = MemoryLayout<Float>.size * bufferSize

var pcmInt16Data: [Int16] = []
var pcmFloatData = [Float](repeating: 0.0, count: bufferSize) // allocate once and reuse


// one buffer of noise as an example
for _ in 0..<bufferSize {
    let value = Int16.random(in: Int16.min...Int16.max)
    pcmInt16Data.append(value)
}


let engine = AVAudioEngine()
let player = AVAudioPlayerNode()

let audioFormat = AVAudioFormat(standardFormatWithSampleRate: 48_000.0, channels: 1)!
let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: UInt32(bufferSize))

let mixer = engine.mainMixerNode

engine.attach(player)
engine.connect(player, to: mixer, format: audioFormat)

engine.prepare()

do {
    try engine.start()
} catch {
    print("Error info: \(error)")
}

player.play()

if let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: UInt32(bufferSize)) {

    let monoChannel = buffer.floatChannelData![0]

    // Int16 ranges from -32768 to 32767 -- we want to convert and scale these to Float values between -1.0 and 1.0
    var scale = Float(Int16.max) + 1.0
    vDSP_vflt16(pcmInt16Data, 1, &pcmFloatData, 1, vDSP_Length(bufferSize)) // Int16 to Float
    vDSP_vsdiv(pcmFloatData, 1, &scale, &pcmFloatData, 1, vDSP_Length(bufferSize)) // divide by scale

    memcpy(monoChannel, pcmFloatData, bufferByteSize)
    buffer.frameLength = UInt32(bufferSize)
    player.scheduleBuffer(buffer, completionHandler: nil) // load more buffers in the completionHandler

}


PlaygroundPage.current.needsIndefiniteExecution = true


Если вместо этого вы хотите воспроизвести AVAudioFile, используйте AVAudioPlayerNode.scheduleFile(). и .scheduleSegment вместо того, чтобы пытаться прочитать Данные Int16 напрямую из WAV/AIFF. Вам следует обратить внимание на параметр AVAudioFile.processingFormat и использовать его для формата соединения плеера с микшером.

import Cocoa
import PlaygroundSupport
import AVFoundation


let engine = AVAudioEngine()
let player = AVAudioPlayerNode()

let playEntireFile = true

func playLocalFile() {

    // file needs to be in ~/Documents/Shared Playground Data
    let localURL = playgroundSharedDataDirectory.appendingPathComponent("MyAwesomeMixtape6.aiff")
    guard let audioFile = try? AVAudioFile(forReading: localURL) else { return }
    let audioFormat = audioFile.processingFormat

    let mixer = engine.mainMixerNode

    engine.attach(player)
    engine.connect(player, to: mixer, format: audioFormat)

    engine.prepare()

    do {
        try engine.start()
    } catch {
        print("Error info: \(error)")
    }

    player.play()

    if playEntireFile {

        player.scheduleFile(audioFile, at: nil, completionHandler: nil)

    } else { // play segment

        let startTimeSeconds = 5.0
        let durationSeconds = 2.0

        let sampleRate = audioFormat.sampleRate
        let startFramePostion = startTimeSeconds * sampleRate
        let durationFrameCount = durationSeconds * sampleRate

        player.scheduleSegment(audioFile, startingFrame: AVAudioFramePosition(startFramePostion), frameCount: AVAudioFrameCount(durationFrameCount), at: nil, completionHandler: nil)

    }

}

playLocalFile()


PlaygroundPage.current.needsIndefiniteExecution = true


Для удаленных файлов попробуйте AVPlayer.

import Cocoa
import AVFoundation
import PlaygroundSupport


var player: AVPlayer?

func playRemoteFile() {

    guard let remoteURL = URL(string: "https://ondemand.npr.org/anon.npr-mp3/npr/me/2020/03/20200312_me_singapore_wins_praise_for_its_covid-19_strategy_the_us_does_not.mp3"
        ) else { return }

    player = AVPlayer(url: remoteURL)

    player?.play()

}

playRemoteFile()

PlaygroundPage.current.needsIndefiniteExecution = true
person lentil    schedule 12.03.2020
comment
спасибо, это сработало нормально, я все еще изучаю основной звук и теперь пытаюсь передискретизировать звук с 44,1 кГц до 48 кГц, stackoverflow.com/questions/60711929/ вы можете мне немного помочь? Я прикрепил пример кода под вопросом. заранее спасибо - person szuniverse; 16.03.2020
comment
Привет, @lentil, меня очень заинтересовал первый пример загрузки данных в буфер PCM. В моем случае мне нужно запустить воспроизведение локального файла в указанное время с начала трека и воспроизводить указанное количество времени. Однако в любой момент можно установить флажок «Воспроизвести до конца», поэтому PCMBuffer будет продолжать загружаться до тех пор, пока не будет достигнут конец файла. Есть ли шанс, что вы могли бы уточнить, как я могу добиться этого, используя ваш первый пример? Я могу создать еще один вопрос, если хотите. - person SouthernYankee65; 15.09.2020
comment
Вместо того, чтобы работать с образцами напрямую, может быть проще использовать второй пример, в котором используется AVAudioPlayerNode.scheduleSegment. Вы можете продолжать планировать сегменты, пока флажок установлен. Не стесняйтесь задавать еще один вопрос, если хотите - легче размещать код и т. д. И вы, скорее всего, получите более широкий спектр предложений. - person lentil; 16.09.2020
comment
А при добавлении дополнительных сегментов вы должны добавить их в обработчик завершения для метода scheduleSegment. Просто нужно немного спланировать свой подход к отправке очередей, а не напрямую обращаться к состоянию пользовательского интерфейса из блока завершения. - person lentil; 16.09.2020

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

var byteArray: [Int16] = [] // one packet size is 512

Насколько я вижу из показанного кода, нет необходимости делать byteArray необязательным.


И Как установить первые данные 512 из byteArray?

Ваш код будет работать с небольшой модификацией:

pcmBuffer.frameLength = AVAudioFrameCount(len)
memcpy(pcmBuffer.audioBufferList.pointee.mBuffers.mData, byteArray, len * 2)

Или вы можете работать с int16ChannelData:

if let channelData = pcmBuffer.int16ChannelData {
    memcpy(channelData[0], byteArray, len * MemoryLayout<Int16>.stride)
    pcmBuffer.frameLength = AVAudioFrameCount(len)
} else {
    print("bad format")
}

Вы можете захотеть загрузить не первые части вашего byteArray, но это уже другая проблема.

person OOPer    schedule 11.03.2020
comment
Спасибо! Я сделал пример здесь: drive.google.com/file/d/1apISFQXBEnB- cYKuSFnnxHkwoYLnFa-E, если вы проверите метод ViewController.start(), я сделал 3 разных решения, первое с AVAudioFile работает отлично, а два других - странно. Звук быстрее и выше, чем должен быть. Что я делаю не так? - person szuniverse; 12.03.2020
comment
Непонятно, что происходит, поскольку я не вижу фактических данных. Ясно одно: wav-файл — это не простая последовательность сэмплов PCM, поэтому Data файла не может быть в правильном формате данных pcmInt16. Если вы хотите узнать, как загрузить сэмплы PCM из wav-файла, это другой вопрос, и вам лучше начать новую тему. - person OOPer; 12.03.2020