swift AVAudioEngine 和 AVAudioSinkNode sampleRate 转换

Posted

技术标签:

【中文标题】swift AVAudioEngine 和 AVAudioSinkNode sampleRate 转换【英文标题】:swift AVAudioEngine and AVAudioSinkNode sampleRate convert 【发布时间】:2020-03-05 22:54:50 【问题描述】:

我一直有这个问题,并编写了以下可以作为应用程序的主视图控制器文件运行的 swift 文件。执行后,它将播放一个 1kHz 正弦波的短脉冲。它将同时从音频接口的输入端录制。

目前我已将输出插入输入进行测试。但这也可能是内置扬声器和内置麦克风的计算机(只需在运行应用程序之前检查系统设置中的音量,因为它会自动播放)

我无法得到这个给我一个准确的结果:

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)
    

如果我运行这个应用程序,控制台将打印出创建的两个 wav 的 url(一个是生成的正弦波,另一个是录制的麦克风输入)。如果我在 daw 中检查这些,我会得到以下信息。您可以看到两个正弦波不保持同步。这让我相信采样率是不同的,但是打印到控制台的格式告诉我它们并没有什么不同。

最初 inputNode 直接指向 micSinkNode,但是我插入了一个 AVAudioMixerNode 来尝试在使用 AVAudioSinkNode 之前转换格式。

目的是能够使用任何使用其自己的设置运行的 sampleRate 硬件,并将样本保存到应用首选的“本机设置”。 (即应用程序将在 48kHz 处进行数字处理。我希望能够使用 96k 硬件和不同的通道数)。

任何人都可以建议为什么这不能正常工作吗?

【问题讨论】:

你找到解决办法了吗? 【参考方案1】:

AVAudioSinkNode支持格式转换。

它必须处理硬件输入格式的数据。

参考:来自 WWDC 2019 的演讲,大约 3 分 55 秒: https://developer.apple.com/videos/play/wwdc2019/510/

附:我目前正在努力在设备上设置 非默认 采样率(我假设硬件支持它,因为我从可用采样率列表中选择它),但即使在这种情况下非默认采样率,接收器完全停止工作(没有崩溃,只是没有被调用)。

【讨论】:

以上是关于swift AVAudioEngine 和 AVAudioSinkNode sampleRate 转换的主要内容,如果未能解决你的问题,请参考以下文章

AVAudioEngine MIDI 文件播放(当前进度+MIDI 结束回调)Swift

swift AVAudioEngine 和 AVAudioSinkNode sampleRate 转换

Swift AVAudioEngine:更改MacOS的音频输入设备

使用 Swift 使用 AVAudioEngine 实时进行音高转换

在 Swift 中使用 AVAudioEngine 点击麦克风输入

Swift AVAudioEngine - 如何使本地麦克风静音