如何在 CoreAudio 中从多个设备输出音频?

How do I output audio from multiple devices in CoreAudio?

提问人:Чайка 提问时间:10/18/2023 最后编辑:Чайка 更新时间:10/23/2023 访问量:91

问:

Audio Midi Setup允许从多个设备输出单个声源,但是有没有办法做到这一点?CoreAudio

这可以通过将相同的附加到两个来完成吗?我考虑过这样做,但它抛出了一个堆栈跟踪并给了我一个错误。playerNodeAVAudioEngine

import Cocoa
import CoreAudio

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet var window: NSWindow!


    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Get the default output device
        var defaultOutputDeviceID = AudioDeviceID(0)
        var defaultOutputDeviceIDSize = UInt32(MemoryLayout<AudioDeviceID>.size)
        var property: AudioObjectPropertyAddress = AudioObjectPropertyAddress(mSelector:kAudioHardwarePropertyDefaultOutputDevice, mScope: kAudioDevicePropertyScopeOutput, mElement: kAudioObjectPropertyElementMain)
        var getDefaultOutputDeviceIDStatus = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &property, 0, nil, &defaultOutputDeviceIDSize, &defaultOutputDeviceID)
        
        // Get the number of output devices
        var outputDeviceCount = UInt32(0)
        var outputDeviceCountSize = UInt32(MemoryLayout<UInt32>.size)
        property.mSelector = kAudioHardwarePropertyDevices
        
        var getOutputDeviceCountStatus = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &property, 0, nil, &outputDeviceCountSize, &outputDeviceCount)
        
        // Get the IDs of all output devices
        var outputDeviceIDs = [AudioDeviceID](repeating: 0, count: Int(outputDeviceCount))
        var outputDeviceIDsSize = UInt32(MemoryLayout<AudioDeviceID>.size * Int(outputDeviceCount))
        property.mSelector = kAudioHardwarePropertyDevices
        var getOutputDeviceIDsStatus = AudioObjectGetPropertyData(AudioObjectID(kAudioObjectSystemObject), &property, 0, nil, &outputDeviceIDsSize, &outputDeviceIDs)

        // Set the output device IDs to the first two devices
        var outputDeviceIDsToSet = [outputDeviceIDs[0], outputDeviceIDs[1]]
        var outputDeviceIDsToSetSize = UInt32(MemoryLayout<AudioDeviceID>.size * outputDeviceIDsToSet.count)
        property.mSelector = kAudioAggregateDevicePropertyComposition
        var setOutputDeviceIDsStatus = AudioObjectSetPropertyData(defaultOutputDeviceID, &property, 0, nil, outputDeviceIDsToSetSize, &outputDeviceIDsToSet)    }
        }
    }
}
Swift macOS Core-Audio

评论


答:

0赞 Rainy sidewalks 10/18/2023 #1

这解决了你的问题吗

import CoreAudio

// Create an array of output device IDs to include in the aggregate device
let outputDeviceIDs: [AudioDeviceID] = [deviceID1, deviceID2, deviceID3] // Replace with the actual device IDs

// Create an aggregate device
var aggregateDeviceID = AudioDeviceID(0)
var aggregateDeviceIDSize = UInt32(MemoryLayout<AudioDeviceID>.size)
var createAggregateDeviceStatus = withUnsafeMutablePointer(to: &aggregateDeviceID) { deviceIDPtr in
    AudioObjectCreateAggregateDevice(kAudioObjectSystemObject, outputDeviceIDs.count, outputDeviceIDs, deviceIDPtr)
}

// Set the aggregate device as the default output device
var setDefaultOutputDeviceStatus = AudioObjectSetPropertyData(AudioObjectID(kAudioObjectSystemObject), &AudioObjectPropertyAddress(kAudioHardwarePropertyDefaultOutputDevice), 0, nil, aggregateDeviceIDSize, &aggregateDeviceID)

评论

0赞 Чайка 10/18/2023
Xcode说对不起,但真的有效吗?Cannot pass immutable value as inout argument: function call returns immutable valueArgument passed to call that takes no arguments
0赞 Rainy sidewalks 10/18/2023
立即尝试编辑
0赞 Чайка 10/18/2023
对不起,Sonoma 上的 Xcode 15 中也有同样的错误
0赞 Rainy sidewalks 10/18/2023
请分享代码,这样会很有帮助。
0赞 Чайка 10/18/2023
AudioObjectCreateAggregateDevice找不到同样的错误。
2赞 Чайка 10/23/2023 #2

在准备、结构、

public Struct DeviceID {
    let deviceID: AudioObjectID
    let UID: String
}

并由它们组成。Dictionary<String, DeviceID>

代码是

func makeAggregate (devices devs: Array<DeviceID>) -> AudioDeviceID {
    let deviceList = devs.map {
        [
            kAudioSubDeviceUIDKey: $0.UID,
            kAudioSubDeviceDriftCompensationKey : $0.UID == "com.rogueamoeba.Loopback:32B21BC2-536C-43A0-8A7B-8354B85AD4C7" ? 1 : 0
        ]
    }
    
    let description: Dictionary<String, Any> = [
        kAudioAggregateDeviceNameKey: "testDevice",
        kAudioAggregateDeviceUIDKey: UUID().uuidString,
        kAudioAggregateDeviceSubDeviceListKey: deviceList,
        kAudioAggregateDeviceMasterSubDeviceKey: devs.first?.UID as Any,
        kAudioAggregateDeviceClockDeviceKey: devs.first?.UID as Any,
        kAudioAggregateDeviceIsPrivateKey: 1,
        kAudioAggregateDeviceIsStackedKey: 1
    ]

    var aggregateDeviceID: AudioDeviceID = 0
    let status: OSStatus = AudioHardwareCreateAggregateDevice(description as CFDictionary, &aggregateDeviceID)
    print(status)

    return aggregateDeviceID
}

func applicationDidFinishLaunching(_ aNotification: Notification) {
    let devices: AudioDevices = AudioDevices()
    var audioFile: AVAudioFile
    print(devices.outputDevices)

    let deviceToBind: Array<DeviceID> = [devices.outputDevices["Loopback Audio"]!, devices.outputDevices["BoomAudio"]!]
    audioDeviceId = makeAggregate(devices: deviceToBind)
    print("new device \(audioDeviceId)")

    do {
        let fileURL: URL = Bundle.main.url(forResource: "1-03 RTRT", withExtension: "mp3")!
        audioFile = try AVAudioFile(forReading: fileURL)
        engine.attach(player)
        engine.connect(player, to: engine.mainMixerNode, format: nil)
        player.scheduleFile(audioFile, at: nil)
        try engine.outputNode.auAudioUnit.setDeviceID(audioDeviceId)
        try engine.start()
        player.play()
    } catch let error {
        print(error.localizedDescription)
    }
}

func applicationWillTerminate(_ aNotification: Notification) {
    let destroyStatus: OSStatus = AudioHardwareDestroyAggregateDevice(audioDeviceId)
    print("destroy done \(destroyStatus)")
}