为什么我的多通道映射没有正常工作?

7

我最近在关于iOS中使用multiroute的问题上发布了这个问题,并且我认为我解决了它,但是我发现它并没有完全解决:AVAudioEngine多通道映射

我遇到的问题是multiroute仅适用于前两个输出通道。我正在尝试使其适用于4通道音频接口。

我已经成功地使用AVAudioPlayer将音频路由到USB接口的每个输出。

var avplayer = AVAudioPlayer()

@IBAction func avAudioPlayerPlay(_ sender: Any)
{
    let audioSession = AVAudioSession.sharedInstance()
    let route = audioSession.currentRoute

    // set the session category
    do
    {
        //try audioSession.setCategory(.multiRoute)
        try audioSession.setCategory(.multiRoute, options: .mixWithOthers)
    }
    catch
    {
        print("unable to set category", error)
        return
    }

    // activate the audio session - turns on multiroute I believe
    do
    {
        try audioSession.setActive(true)
        //try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
    }
    catch
    {
        print("unable to set active", error)
        return
    }

    //audio interface + headphone jack
    let outputs:[AVAudioSessionChannelDescription] = [
        route.outputs[0].channels![2], // 3rd channel on Audio Interface
        route.outputs[1].channels![1]  // Right Channel of Headphones
    ]

    guard let filePath: String = Bundle.main.path(forResource: "audio", ofType: "m4a") else { return }
    let fileURL: URL = URL(fileURLWithPath: filePath)

    do
    {
        avplayer = try AVAudioPlayer(contentsOf: fileURL)
    }
    catch
    {
        print("play error", error)
        return
    }

    avplayer.channelAssignments = outputs

    let result = avplayer.play()
    print(result)
}

但是我无法使用AVAudioEngine使其工作:

private func getOutputChannelMapIndices(_ names:[String?]) -> [Int]
{
    let session = AVAudioSession.sharedInstance()
    let route = session.currentRoute
    let outputPorts = route.outputs

    var channelMapIndices:[Int] = []

    for name in names
    {
        var chIndex = 0
        for outputPort in outputPorts
        {
            guard let channels = outputPort.channels else
            {
                continue
            }
            for channel in channels
            {
                print(channel.channelName)
                if channel.channelName == name
                {
                    if names.count > channelMapIndices.count
                    {
                        channelMapIndices.append(chIndex)
                    }
                }
                chIndex += 1
            }
        }
    }
    return channelMapIndices
}

@IBAction func nodesPlay(_ sender: Any)
{
    let channelNames = [
        "UMC204HD 192k 3",
        "Headphones Left",
        "Headphones Right",
        nil
    ]

    let audioSession = AVAudioSession.sharedInstance()

    // set the session category
    do
    {
        //try audioSession.setCategory(.multiRoute)
        try audioSession.setCategory(.multiRoute, options: .mixWithOthers)
    }
    catch
    {
        print("unable to set category", error)
        return
    }

    // activate the audio session - turns on multiroute I believe
    do
    {
        try audioSession.setActive(true)
        //try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
    }
    catch
    {
        print("unable to set active", error)
        return
    }

    let channelMapIndices = getOutputChannelMapIndices(channelNames)

    print("channelMapIndices: ", channelMapIndices)

    engine = AVAudioEngine()
    output = engine.outputNode
    mixer = engine.mainMixerNode

    player = AVAudioPlayerNode()

    engine.attach(player)

    guard let filePath: String = Bundle.main.path(forResource: "audio", ofType: "m4a") else { return }
    let fileURL: URL = URL(fileURLWithPath: filePath)
    let file = try! AVAudioFile(forReading: fileURL)

    let outputNumChannels = output.outputFormat(forBus: 0).channelCount
    print("outputNumChannels:" , outputNumChannels)

    var outputChannelMap:[Int] = Array(repeating: -1, count: Int(outputNumChannels))

    let numberOfSourceChannels = file.processingFormat.channelCount
    print("numberOfSourceChannels: ", numberOfSourceChannels)

    var sourceChIndex = 0
    for chIndex in channelMapIndices
    {
        if chIndex < outputNumChannels && sourceChIndex < numberOfSourceChannels
        {
            outputChannelMap[chIndex] = sourceChIndex
            sourceChIndex += 1
        }
    }

    print("outputChannelMap: ", outputChannelMap)

    if let au = output.audioUnit
    {
        let propSize = UInt32(MemoryLayout.size(ofValue: outputChannelMap))
        print("propSize:", propSize)
        let result = AudioUnitSetProperty(au, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Global, 0, &outputChannelMap, propSize)
        print("result: ", result)
    }

    let channelLayout = AVAudioChannelLayout(layoutTag: kAudioChannelLayoutTag_DiscreteInOrder | UInt32(numberOfSourceChannels))
    let format = AVAudioFormat(streamDescription: file.processingFormat.streamDescription, channelLayout: channelLayout)

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

    player.scheduleFile(file, at: nil, completionHandler: nil)

    do
    {
        try engine.start()
    }
    catch
    {
        print("can't start", error)
        return
    }

    player.play()
}

如果有人能够解释为什么我似乎无法在输出3或4播放任何音频,我将不胜感激。
请注意,这段代码的许多内容都是从这里翻译过来的:https://forums.developer.apple.com/thread/15416

在设置kAudioOutputUnitProperty_ChannelMap之前,它的值是多少?输出单元上的kAudioUnitProperty_StreamFormat有多少个通道? - sbooth
在我设置 kAudioOutputUnitProperty_ChannelMap 属性之前,它似乎是空的。如果我在设置后获取它,则似乎只设置了数组的第一个值。输出上似乎有 4 个通道。 - Castles
我发现如果将propSize乘以4,那么获取channelmap将返回正确的结果...但输出仍然不正确。 - Castles
1个回答

5

我相信问题出在这一行

let propSize = UInt32(MemoryLayout.size(ofValue: outputChannelMap))

这将给出数组对象的大小,实际上是指指针的大小,而不是数组中对象的大小。请参阅Apple文档中的讨论
属性的大小应该是数组中包含的通道数乘以Int32的大小,因为AudioUnitSetProperty是一个C API,并且这将是相应的C数组的大小。
let propSize = UInt32(MemoryLayout<Int32>.stride * outputChannelMap.count)

你还需要将outputChannelMap声明为Int32数组,因为这是kAudioOutputUnitProperty_ChannelMap所期望的类型:

var outputChannelMap:[Int32] = Array(repeating: -1, count: Int(outputNumChannels))

另外...这就是outputChannelMap的样子:[-1,-1,0,-1] - Castles
如果我不乘以二,我只能得到两个数组值。 - Castles
我刚刚尝试了使用第二个USB接口,这似乎对propSize有效:let propSize:UInt32 = UInt32(MemoryLayout.size(ofValue: outputChannelMap)) * (outputNumChannels / numberOfSourceChannels) - 这有任何意义吗? - Castles
我指的是用于设置输出映射的propSize。对于造成的混淆,我很抱歉。我现在已经将其更改为以下内容,我认为这实际上是正确的:let propSize:UInt32 = UInt32(MemoryLayout<Int32>.stride * outputChannelMap.count) - Castles
让我们在聊天中继续这个讨论 - Castles
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接