如何将声音播放到 USB 音频接口上的特定输出通道?

cod*_*ken 4 audio core-audio avfoundation avaudioengine avaudioplayernode

我有一个 motu UltraLite mk4 USB 音频接口连接到运行 MacOS 10.13.3 的 Mac。

我正在尝试播放三个立体声文件,每个文件都有自己的立体声扬声器。

为了降低复杂性,我只是试图让一个 AVAudioPlayerNode 的立体声从默认输出通道 0 和 1 以外的东西中出来。

USB音频接口正面

USB 音频接口背面

我希望通过 AVAudioEngine + 一些低级别的 Core Audio 来实现这一点。

到目前为止,通过配置 AVAudioEngine 的输出节点,我已经成功地将音频播放到我的 USB 音频接口:

AudioUnit audioUnit = [[self.avAudioEngine outputNode] audioUnit];
OSStatus error = AudioUnitSetProperty(audioUnit,
    kAudioOutputUnitProperty_CurrentDevice,
    kAudioUnitScope_Global,
    0,
    &deviceID,
    sizeof(deviceID));
if (error) {
    NSLog(@"Failed to set desired output audio device: %d", (int)
}
Run Code Online (Sandbox Code Playgroud)

我还成功地设置了一个通道映射,它允许一个播放器将立体声文件播放到我的 USB 音频接口的 4+5 通道,如 Apple 开发论坛中的 theanalogkid 所述

通道映射的问题在于它全局应用于 AVAudioEngine 的输出,并且会影响所有 AVAudioPlayerNode。所以这不是解决方案。

因此,我没有在 AVAudioEngine 的 outputNode 的 AudioUnit 上使用该通道映射,而是尝试使用自定义通道布局创建一个 AVAudioFormat,该布局指定了离散通道索引 4 和 5:

// Create audio channel layout struct with two channels
int numChannels = 2;
AudioChannelLayout *audioChannelLayout = calloc(1, sizeof(AudioChannelLayout) + (numChannels - 1) * sizeof(AudioChannelDescription));
audioChannelLayout->mNumberChannelDescriptions = numChannels;
audioChannelLayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
audioChannelLayout->mChannelBitmap = 0;

// Configure channel descriptions
audioChannelLayout->mChannelDescriptions[0].mChannelFlags = kAudioChannelFlags_AllOff;
audioChannelLayout->mChannelDescriptions[0].mChannelLabel = kAudioChannelLabel_Discrete_4;
audioChannelLayout->mChannelDescriptions[0].mCoordinates[0] = 0;
audioChannelLayout->mChannelDescriptions[0].mCoordinates[1] = 0;
audioChannelLayout->mChannelDescriptions[0].mCoordinates[2] = 0;

audioChannelLayout->mChannelDescriptions[1].mChannelFlags = kAudioChannelFlags_AllOff;
audioChannelLayout->mChannelDescriptions[1].mChannelLabel = kAudioChannelLabel_Discrete_5;
audioChannelLayout->mChannelDescriptions[1].mCoordinates[0] = 0;
audioChannelLayout->mChannelDescriptions[1].mCoordinates[1] = 0;
audioChannelLayout->mChannelDescriptions[1].mCoordinates[2] = 0;

// Create AVAudioChannelLayout
AVAudioChannelLayout *avAudioChannelLayout = [[AVAudioChannelLayout alloc] initWithLayout:audioChannelLayout];

// Create AVAudioFormat
AVAudioOutputNode *outputNode = engine.avAudioEngine.outputNode;
AVAudioFormat *outputHWFormat = [outputNode outputFormatForBus:0];
AVAudioFormat *targetFormat = [[AVAudioFormat alloc] initWithStreamDescription:player.avAudioFile.processingFormat.streamDescription
                                                                 channelLayout:avAudioChannelLayout];
Run Code Online (Sandbox Code Playgroud)

然后我将播放器的混音器连接到引擎的主混音器,使用输出硬件格式:

[engine connect:speakerMixer to:[engine mainMixerNode] format:outputHWFormat];
Run Code Online (Sandbox Code Playgroud)

和播放器到它的混音器,使用自定义 AVAudioFormat 和调整后的通道布局:

[engine connect:player to:speakerMixer format:targetFormat];
Run Code Online (Sandbox Code Playgroud)

结果?声音播放,但仍然只在 USB 音频接口的默认 0 和 1 通道之外。

如果我将 targetFormat 应用于混音器到主混音器的连接,则 USB 音频接口根本没有声音。

我还尝试将通道映射应用于 AVAudioMixerNode 的底层 AVAudioUnit,但是我似乎无法从 AVAudioUnit 获取底层裸机 AudioUnit 以应用通道映射。也许这对 AVAudioEngine 来说是不可能的。如果是这样,您是否知道有其他方法可以做到这一点?

可能我忽略了一些关键的东西。我花了很多天研究这个,感觉卡住了。感谢我能得到的任何帮助。

cod*_*ken 5

许多年前,在 AVAudioEngine 的文档中有一个警告,一个应用程序应该只有一个实例。

显然,这个限制消失了,我已经成功创建了多个实例,每个实例都有自己的通道映射应用于引擎的 outputNode 的 AudioUnit。

文档仍然声称引擎的 outputNode 是一个“单例”,尽管我在 Mac 上的测试显示每个 AVAudioEngine 实例都有自己的输出节点实例,并且可以在一个实例上设置通道映射而不影响另一个实例的输出.

这不是我一直在寻找的,尽管它似乎确实有效。我仍然更喜欢一种解决方案,它可以将通道从播放器或混音器节点路由到音频硬件的特定输出通道,并使用一个 AVAudioEngine 实例完成所有操作。另一方面,现在我很难想出一个很好的理由,为什么运行多个 AVAudioEngine 实例会很糟糕。