AVFoundation маршрутизирует аудио между двумя несистемными входами и выходами

#swift #avfoundation #audiokit

#swift #avfoundation #audiokit

Вопрос:

Я пытался перенаправить звук с виртуального устройства Soundflower на другой аппаратный динамик. Виртуальное устройство Soundflower — это мой системный вывод. Я хочу, чтобы мой Aveaudio Engine принимал вход и вывод Soundflower на аппаратный динамик.

Однако, проведя исследование, кажется, что AVAudioEngine поддерживает только устройства RIO. Я просмотрел пример AudioKit и разделителя вывода, однако я получал треск и неудовлетворительные результаты. Суть моего кода заключается в следующем

 static func set(device: String, isInput: Bool, toUnit unit: AudioUnit) -> Int {
        let devs = (isInput ? EZAudioDevice.inputDevices() : EZAudioDevice.outputDevices()) as! [EZAudioDevice]
        let mic = devs.first(where: { $0.name == device})!
        var inputID = mic.deviceID  // replace with actual, dynamic value
        AudioUnitSetProperty(unit, kAudioOutputUnitProperty_CurrentDevice,
                                     kAudioUnitScope_Global, 0, amp;inputID, UInt32(MemoryLayout<AudioDeviceID>.size))
        return Int(inputID)
    }
    
let outputRenderCallback: AURenderCallback = {
        (inRefCon: UnsafeMutableRawPointer,
        ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
        inTimeStamp: UnsafePointer<AudioTimeStamp>,
        inBusNumber: UInt32,
        inNumberFrames: UInt32,
        ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus in

        // Get Refs
        let buffer = UnsafeMutableAudioBufferListPointer(ioData)
        let engine = Unmanaged<Engine>.fromOpaque(inRefCon).takeUnretainedValue()

        // If Engine hasn't saved any data yet just output silence
        if (engine.latestSampleTime == nil) {
            //makeBufferSilent(buffer!)
            return noErr
        }

        // Read the latest available Sample
        let sampleTime = engine.latestSampleTime
        if let err = checkErr(engine.ringBuffer.fetch(ioData!, framesToRead: inNumberFrames, startRead: sampleTime!).rawValue) {
            //makeBufferSilent(buffer!)
            return err
        }

        return noErr
    }
    
    private let trailEngine: AVAudioEngine
    private let subEngine: AVAudioEngine
    init() {
        
        subEngine = AVAudioEngine()
        let inputUnit = subEngine.inputNode.audioUnit!
        print(Engine.set(device: "Soundflower (2ch)", isInput: true, toUnit: inputUnit))
        
        trailEngine = AVAudioEngine()
        let outputUnit = trailEngine.outputNode.audioUnit!
        print(Engine.set(device: "Boom 3", isInput: false, toUnit: outputUnit))

        
        subEngine.inputNode.installTap(onBus: 0, bufferSize: 2048, format: nil) { [weak self] (buffer, time) in
            guard let self = self else { return }
            let sampleTime = time.sampleTime
            self.latestSampleTime = sampleTime

            // Write to RingBuffer
            if let _ = checkErr(self.ringBuffer.store(buffer.audioBufferList, framesToWrite: 2048, startWrite: sampleTime).rawValue) {
                //makeBufferSilent(UnsafeMutableAudioBufferListPointer(buffer.mutableAudioBufferList))
            }
        }
        
        var renderCallbackStruct = AURenderCallbackStruct(
            inputProc: outputRenderCallback,
            inputProcRefCon: UnsafeMutableRawPointer(Unmanaged<Engine>.passUnretained(self).toOpaque())
        )
        if let _ = checkErr(
            AudioUnitSetProperty(
                trailEngine.outputNode.audioUnit!,
                kAudioUnitProperty_SetRenderCallback,
                kAudioUnitScope_Global,
                0,
                amp;renderCallbackStruct,
                UInt32(MemoryLayout<AURenderCallbackStruct>.size)
            )
            ) {
            return
        }
        
        subEngine.prepare()
        trailEngine.prepare()
        
        ringBuffer = RingBuffer<Float>(numberOfChannels: 2, capacityFrames: UInt32(4800 * 20))
        
        do {
            try self.subEngine.start()
        } catch {
            print("Error starting the input engine: (error)")
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now()   0.01) {
            do {
                try self.trailEngine.start()
            } catch {
                print("Error starting the output engine: (error)")
            }
        }
    }

  

Для справки реализация RingBuffer находится на:
https://github.com/vgorloff/CARingBuffer
и пример AudioKit
https://github.com/AudioKit/OutputSplitter/tree/master/OutputSplitter

Я использовал AudioKit 4 (однако в примере используются только оболочки устройств AudioKit). Результатом этого кода является очень трескучий звук через динамики, что говорит о том, что сигнал полностью искажается при передаче между двумя движками. Я не слишком беспокоюсь о задержке между двумя движками.