使用 AVFoundation 和 Swift 访问多个音频硬件输出/通道

Posted

技术标签:

【中文标题】使用 AVFoundation 和 Swift 访问多个音频硬件输出/通道【英文标题】:Accessing multiple audio hardware outputs/channels using AVFoundation and Swift 【发布时间】:2016-07-26 23:55:00 【问题描述】:

如何使用 AVFoundation 访问除 1-2 之外的其他音频硬件输出?我正在为 Mac OS-X 应用程序编写 swift 代码,该应用程序通过各种输出设备(USB 接口、dante、soundflower)播放 mp3 文件,如下所示:

myPlayer = AVPlayer(URL: myFilePathURL)
myPlayer.audioOutputDeviceUniqueID = myAudioOutputDevices[1].deviceUID()
myPlayer.play()

但是,我不确定如何将音频文件播放到 1-2 以外的频道。例如,我想播放 mp3 到输出 3-4。

我可以通过 AVPlayer 做到这一点吗?还是我需要去别处看看?也许 AVAudioEngine 以及混音器节点?我查看了 AVAudioEngine 示例,找不到任何地方引用的硬件通道。感谢您的帮助!

【问题讨论】:

我的理解是 AVAudioEngine 只是一个 ios 框架 - 所以这行不通。 iOS 和 OS-X 框架的 Apple 文档非常交织在一起 - 遗憾的是,没有太多解释分别适用于每个平台的内容。 我正在尝试使用 AVAudioEngine 和 AVAudioPlayerNode 以及 mainMixerNode 的另一条路径。我还没有弄清楚是否可以将 mainMixerNode 分配给默认以外的其他硬件通道。也许这比使用 AVPlayer 更好? 按照here 的描述开始使用频道地图,但希望很快。 This post 在使用 Objective-c 中的通道映射的音频通道路由方面也有很大帮助。正在尝试将其转换为 Swift。 还有几个站点包含有关路由通道的信息,包括通过苹果开发者论坛的WWDC session 和this discussion。它们都没有 Swift 代码,但它很好地解释了这些过程。 【参考方案1】:

随着时间的推移,我已经对这段代码进行了迭代 - 但基本大纲将起作用。这是我当前的设置代码,用于将音频发送到多通道设置。我目前正在使用以下代码使用具有 16 个立体声实例流的 Dante Virtual Soundcard 执行此操作:

func setupAudioPath()
    //print("setupAudioPath")

    // get output hardware format
    let output = engine.outputNode
    outputHWFormat = output.outputFormat(forBus: 0)

    //print("outputHWFormat = \(outputHWFormat)")
    //print("outputHWFormat.channelCount = \(outputHWFormat.channelCount)")

    // connect mixer to output
    mixer = engine.mainMixerNode

    //then work on the player end by first attaching the player to the engine
    engine.attach(player)

    engine.connect(mixer, to: output, format: outputHWFormat)

    var channelMap: [sint32] = []
    //UInt32 numOfChannels = fileFormat.NumberChannels();
    let numOfChannels: UInt32 = UInt32(numberOfStreams) * UInt32(2);    // Number of output device channels
    let mapSize: UInt32 = numOfChannels * UInt32(MemoryLayout<sint32>.size);
    for _ in 0...(numOfChannels-1) 
        channelMap.append(-1)
    
    //channelMap[desiredInputChannel] = deviceOutputChannel;
    channelMap[leftChannel - 1] = 0;
    channelMap[leftChannel]     = 1;


    //print(channelMap)
    //print("number of channels in my map: \(channelMap.count)")

    let code: OSStatus = AudioUnitSetProperty((engine.outputNode.audioUnit)!,
                                              kAudioOutputUnitProperty_ChannelMap,
                                              kAudioUnitScope_Global,
                                              1,
                                              channelMap,
                                              mapSize);


    print("osstatus = \(code)")

【讨论】:

【参考方案2】:

我有一个使用 2 个通道设置通道映射属性的 swift 版本。我没有用完整的多通道系统测试过,但原理应该是一样的。

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

func testCode()

    // get output hardware format
    let output = engine.outputNode
    let outputHWFormat = output.outputFormatForBus(0)
    // connect mixer to output
    let mixer = engine.mainMixerNode
    engine.connect(mixer, to: output, format: outputHWFormat)


    //then work on the player end by first attaching the player to the engine
    engine.attachNode(player)


    //find the audiofile
    guard let audioFileURL = NSBundle.mainBundle().URLForResource("tones", withExtension: "wav") else 
        fatalError("audio file is not in bundle.")
    


    var songFile:AVAudioFile?
    do 
        songFile = try AVAudioFile(forReading: audioFileURL)
        print(songFile!.processingFormat)

        // connect player to mixer
        engine.connect(player, to: mixer, format: songFile!.processingFormat)

     catch 
        fatalError("canot create AVAudioFile \(error)")
    



    let channelMap: [Int32] = [0, 1] //left out left, right out right
    //let channelMap: [Int32] = [1, 0] //right out left, left out right


    let propSize: UInt32 = UInt32(channelMap.count) * UInt32(sizeof(sint32))

    let code: OSStatus = AudioUnitSetProperty((engine.inputNode?.audioUnit)!,
                                              kAudioOutputUnitProperty_ChannelMap,
                                              kAudioUnitScope_Global,
                                              1,
                                              channelMap,
                                              propSize);

    print(code)


    do 
        try engine.start()
     catch 
        fatalError("Could not start engine. error: \(error).")
    

    player.scheduleFile(songFile!, atTime: nil) 
        print("done")
        self.player.play()
    

    player.play()




【讨论】:

你知道如何处理输入吗?我有一个 8ch 接口,但只希望我的混音器节点监听接口上的特定通道。 我试过这个例子,它仍然从左右声道播放文件... 我没有在输入端做这个。但我已经将它与输出端一起使用,以将馈送发送到 16 个立体声输出(硬件或通过声音花/虚拟 dante)。 @MichaelSweet 我正在尝试对 15 个单声道文件做类似的事情,并将它们路由到 15 个离散的单声道输出,但我运气不佳。我以您的示例为基础,但我不确定路由是如何工作的。你有这样的工作吗? ***.com/questions/46041563/…

以上是关于使用 AVFoundation 和 Swift 访问多个音频硬件输出/通道的主要内容,如果未能解决你的问题,请参考以下文章

Swift IOS 使用 AVFoundation 录制视频和音频

在 Swift 的 AVFoundation 中使用夜间模式

使用 AVFoundation Swift 保存视频

swift avfoundation AVCapturePhotoCaptureDelegate 捕获方法

Swift 3:如何在使用 AVFoundation 录制视频期间将麦克风静音/取消静音

Swift 4 - 在 mac os 上使用 AVAssetWriter 进行 avfoundation 屏幕和音频录制 - 视频冻结