swift AVAudioEngine и AVAudioSinkNode sampleRate convert - PullRequest
0 голосов
/ 09 ноября 2019

У меня были проблемы с этим в течение некоторого времени, и я написал следующий файл swift, который может быть запущен как файл контроллера основного вида для приложения. После выполнения он будет воспроизводить короткий импульс синусоидальной волны 1 кГц. Он будет одновременно записывать со входа аудиоинтерфейса.

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

Я не могу получить это, чтобы дать мнеточный результат:

import UIKit
import AVFoundation

var globalSampleRate = 48000


class ViewController: UIViewController {
    var micBuffer:[Float] = Array(repeating:0, count:10000)
    var referenceBuffer:[Float] = Array(repeating:0, count:10000)
    var running:Bool = false
    var engine = AVAudioEngine()

    override func viewDidLoad() {
        super.viewDidLoad()

        let syncQueue = DispatchQueue(label:"Audio Engine")
        syncQueue.sync{
            initializeAudioEngine()
            while running == true {
            }
            engine.stop()
            writetoFile(buff: micBuffer, name: "Mic Input")
            writetoFile(buff: referenceBuffer, name: "Reference")

        }
    }

    func initializeAudioEngine(){

        var micBufferPosition:Int = 0
        var refBufferPosition:Int = 0
        let frequency:Float = 1000.0
        let amplitude:Float = 1.0
        let signal = { (time: Float) -> Float in
            return amplitude * sin(2.0 * Float.pi * frequency * time)
        }

        let deltaTime = 1.0 / Float(globalSampleRate)
        var time: Float = 0

        let micSinkNode = AVAudioSinkNode() { (timeStamp, frames, audioBufferList) ->
          OSStatus in

            let ptr = audioBufferList.pointee.mBuffers.mData?.assumingMemoryBound(to: Float.self)
            var monoSamples = [Float]()
            monoSamples.append(contentsOf: UnsafeBufferPointer(start: ptr, count: Int(frames)))
            for frame in 0..<frames {
              self.micBuffer[micBufferPosition + Int(frame)] = monoSamples[Int(frame)]
            }
            micBufferPosition += Int(frames)

            if micBufferPosition > 8000 {
                self.running = false
            }

            return noErr
        }


        let srcNode = AVAudioSourceNode { _, _, frameCount, audioBufferList -> OSStatus in
            let ablPointer = UnsafeMutableAudioBufferListPointer(audioBufferList)
            for frame in 0..<Int(frameCount) {
                let value = signal(time)
                time += deltaTime
                for buffer in ablPointer {
                    let buf: UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(buffer)
                    buf[frame] = value
                    self.referenceBuffer[refBufferPosition + frame] = value
                }

            }
            refBufferPosition += Int(frameCount)
            return noErr
        }

        let inputFormat = engine.inputNode.inputFormat(forBus: 0)
        let outputFormat = engine.outputNode.outputFormat(forBus: 0)
        let nativeFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32,
            sampleRate: Double(globalSampleRate),
            channels: 1,
            interleaved: false)

        let formatMixer  = AVAudioMixerNode()
        engine.attach(formatMixer)
        engine.attach(micSinkNode)
        engine.attach(srcNode)
        //engine.connect(engine.inputNode, to: micSinkNode, format: inputFormat)
        engine.connect(engine.inputNode, to: formatMixer, format: inputFormat)
        engine.connect(formatMixer, to: micSinkNode, format: nativeFormat)
        engine.connect(srcNode, to: engine.mainMixerNode, format: nativeFormat)
        engine.connect(engine.mainMixerNode, to: engine.outputNode, format: outputFormat)
        print("micSinkNode Format is \(micSinkNode.inputFormat(forBus: 0))")
        print("inputNode Format is \(engine.inputNode.inputFormat(forBus: 0))")
        print("outputNode Format is \(engine.outputNode.outputFormat(forBus: 0))")
        print("formatMixer Format is \(formatMixer.outputFormat(forBus: 0))")

        engine.prepare()
        running = true
        do {
            try engine.start()
        } catch {
            print("Error")
        }
    }

}


func writetoFile(buff:[Float], name:String){

    let outputFormatSettings = [
        AVFormatIDKey:kAudioFormatLinearPCM,
        AVLinearPCMBitDepthKey:32,
        AVLinearPCMIsFloatKey: true,
        AVLinearPCMIsBigEndianKey: true,
        AVSampleRateKey: globalSampleRate,
        AVNumberOfChannelsKey: 1
        ] as [String : Any]

    let fileName = name
    let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)


    let url = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("wav")
    print("FilePath: \(url.path)")

    let audioFile = try? AVAudioFile(forWriting: url, settings: outputFormatSettings, commonFormat: AVAudioCommonFormat.pcmFormatFloat32, interleaved: false)

    let bufferFormat = AVAudioFormat(settings: outputFormatSettings)

    let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat!, frameCapacity: AVAudioFrameCount(buff.count))

    for i in 0..<buff.count {
        outputBuffer?.floatChannelData!.pointee[i] = Float(( buff[i] ))
    }
    outputBuffer!.frameLength = AVAudioFrameCount( buff.count )

    do{
        try audioFile?.write(from: outputBuffer!)

    } catch let error as NSError {
        print("error:", error.localizedDescription)
    }
}

Если я запустите это приложение, консоль распечатает URL двух созданных волн (один - сгенерированная синусоида, а другой - записанный микрофонный вход). Если я проверю это в галку, я получу следующее. Вы можете видеть, что две синусоидальные волны не остаются синхронизированными. Это заставляет меня полагать, что частоты дискретизации отличаются, однако форматы, напечатанные на консоли, показывают, что они не отличаются.

Первоначально inputNode был прямым для micSinkNode, однако я вставил AVAudioMixerNode, чтобы попытаться преобразовать формат перед использованием AVAudioSinkNode.

Цель состоит в том, чтобы иметь возможность использовать любое оборудование sampleRate, работающее с использованием своих собственных настроек, и сохранять сэмплы в приложениях, предпочитаемых «родными настройками». (т. е. приложение будет работать с частотой 48 кГц. Я бы хотел использовать аппаратное обеспечение 96 КБ и различное количество каналов).

Кто-нибудь может подсказать, почему это не работает так, как должно?

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...