AVAudioEngine 播放多声道音频

Posted

技术标签:

【中文标题】AVAudioEngine 播放多声道音频【英文标题】:AVAudioEngine playing multi channel audio 【发布时间】:2014-12-02 13:11:16 【问题描述】:

简单的问题。如何使用 文件(>2 声道),以便我可以听到默认 2 声道输出(耳机/扬声器)上的所有声道。以下代码(去除了错误检查以进行呈现)播放文件的前两个通道,但我只能在插入耳机时听到它。

AVAudioFile *file = [[AVAudioFile alloc] initForReading:[[NSBundle mainBundle] URLForResource:@"nums6ch" withExtension:@"wav"] error:nil];
AVAudioEngine *engine = [[AVAudioEngine alloc] init];
AVAudioPlayerNode *player = [[AVAudioPlayerNode alloc] init];
AVAudioMixerNode *mixer = [[AVAudioMixerNode alloc] init];
[engine attachNode:player];
[engine attachNode:mixer];
AVAudioFormat *processingFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:file.processingFormat.streamDescription->mSampleRate channels:2 interleaved:false];
[engine connect:player to:mixer format:processingFormat];
[engine connect:mixer to:engine.outputNode format:processingFormat];
[engine startAndReturnError:nil];
[player scheduleFile:file atTime:nil completionHandler:nil];
[player play];

我为播放器->混音器和混音器->输出连接尝试了很多格式组合,但它们要么导致与上述代码相同的结果,要么更可能导致崩溃:

ERROR:     [0x19c392310] AVAudioNode.mm:495: AUGetFormat: required condition is false: nil != channelLayout && channelLayout.channelCount == asbd.mChannelsPerFrame
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: nil != channelLayout && channelLayout.channelCount == asbd.mChannelsPerFrame'

或 AUSetFormat OSStatus 代码 -10868(如果我没记错的话,这是不支持的格式)。对任何连接使用 file.processingFormat 会使应用程序崩溃,并出现上述第一个错误。此外,崩溃发生在[player play];

必须有某种方式可以按照我的意愿播放它,因为我可以使用 AUGraph 毫无问题地做到这一点,但是,由于 AVAudioEngine 提供了一个我必须包含的功能,所以我坚持使用它。

我用来检查我的代码的多声道音频文件可以找到here

更新 好的,所以只在耳机中听到音频可能是因为我忘记在我的测试应用程序中将音频会话设置为活动状态。但我仍然只听到前两个频道...

【问题讨论】:

【参考方案1】:

我在 Swift 中遇到过类似的问题。错误是 'com.apple.coreaudio.avfaudio',原因:'所需条件为 false:!nodeimpl->SslEngineImpl()'。

任务是一个接一个地播放两个音频文件。如果我在播放第一个音频文件后点击停止,然后再播放第二个音频文件,系统就会崩溃。

我发现在我创建的函数中我有audioEngine.attachNode(audioPlayerNode),这意味着audioPlayerNode 被附加到audioEngine 一次然后退出。所以我把这个附件移到了viewDidLoad()函数,这样每次都能通过。

【讨论】:

【参考方案2】:

这就是我到目前为止所管理的。它远非完美,但它有点工作。

要获取所有通道,您需要使用 AVAudioPCMBuffer 并在每个通道中存储两个来自文件的通道。此外,对于每个通道对,您需要单独的 AVAudioPlayerNode,然后只需将每个播放器连接到 AVAudioMixerNode,我们就完成了。 6声道音频的一些简单代码:

AVAudioFormat *outputFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:file.processingFormat.sampleRate channels:2 interleaved:false];
AVAudioFile *file = [[AVAudioFile alloc] initForReading:[[NSBundle mainBundle] URLForResource:@"nums6ch" withExtension:@"wav"] error:nil];
AVAudioPCMBuffer *wholeBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat frameCapacity:(AVAudioFrameCount)file.length];
AVAudioPCMBuffer *buffer1 = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)file.length];
AVAudioPCMBuffer *buffer2 = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)file.length];
AVAudioPCMBuffer *buffer3 = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat frameCapacity:(AVAudioFrameCount)file.length];
memcpy(buffer1.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize);
memcpy(buffer1.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[1].mDataByteSize);
buffer1.frameLength = wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize/sizeof(UInt32);
memcpy(buffer2.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[2].mData, wholeBuffer.audioBufferList->mBuffers[2].mDataByteSize);
memcpy(buffer2.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[3].mData, wholeBuffer.audioBufferList->mBuffers[3].mDataByteSize);
buffer2.frameLength = wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize/sizeof(UInt32);
memcpy(buffer3.audioBufferList->mBuffers[0].mData, wholeBuffer.audioBufferList->mBuffers[4].mData, wholeBuffer.audioBufferList->mBuffers[4].mDataByteSize);
memcpy(buffer3.audioBufferList->mBuffers[1].mData, wholeBuffer.audioBufferList->mBuffers[5].mData, wholeBuffer.audioBufferList->mBuffers[5].mDataByteSize);
buffer3.frameLength = wholeBuffer.audioBufferList->mBuffers[0].mDataByteSize/sizeof(UInt32);

AVAudioEngine *engine = [[AVAudioEngine alloc] init];
AVAudioPlayerNode *player1 = [[AVAudioPlayerNode alloc] init];
AVAudioPlayerNode *player2 = [[AVAudioPlayerNode alloc] init];
AVAudioPlayerNode *player3 = [[AVAudioPlayerNode alloc] init];
AVAudioMixerNode *mixer = [[AVAudioMixerNode alloc] init];
[engine attachNode:player1];
[engine attachNode:player2];
[engine attachNode:player3];
[engine attachNode:mixer];
[engine connect:player1 to:mixer format:outputFormat];
[engine connect:player2 to:mixer format:outputFormat];
[engine connect:player3 to:mixer format:outputFormat];
[engine connect:mixer to:engine.outputNode format:outputFormat];
[engine startAndReturnError:nil];

[player1 scheduleBuffer:buffer1 completionHandler:nil];
[player2 scheduleBuffer:buffer2 completionHandler:nil];
[player3 scheduleBuffer:buffer3 completionHandler:nil];
[player1 play];
[player2 play];
[player3 play];

现在这个解决方案远非完美,因为在不同的时间为每个玩家调用play 会导致通道对之间存在延迟。我仍然无法从我的测试文件中播放 8 声道音频(请参阅 OP 中的链接)。 AVAudioFile 处理格式的通道数为 0,即使我使用正确的通道数和布局创建自己的格式,缓冲区读取也会出错。请注意,我可以使用 AUGraph 完美播放此文件。

所以我会在接受这个答案之前等待,如果您有更好的解决方案,请分享。

编辑

因此,我无法同步节点问题和无法播放这个特定的 8 通道音频似乎都是错误(由 Apple 开发人员支持确认)。

对于在 ios 上干预音频的人来说,建议很少。虽然 AVAudioEngine 适用于简单的东西,但您绝对应该使用 AUGraph 来处理更复杂的东西,即使是那些应该与 AVAudioEngine 一起使用的东西。如果你不知道如何在 AUGraph 中复制 AVAudioEngine 中的某些内容(比如我自己),那么,运气不好。

【讨论】:

请详细说明您提到的错误。是不是不能用AVAudioPlayerNodescheduleBuffer:atTime:options:completionHandler给三个节点都指定同一个AVAudioTIme 在撰写此问答时,即使您为所有三个节点设置相同的 AVAudioTime,声音之间仍然会有轻微的延迟。不知道他们修不修。

以上是关于AVAudioEngine 播放多声道音频的主要内容,如果未能解决你的问题,请参考以下文章

AVAudioEngine 实时音频播放问题

使用 AVAudioEngine 从 AVAudioPCMBuffer 播放音频

单声道多声道播放器

两个不同的音频文件在左声道和右声道播放与pygame

AVPlayer 播放单声道音频立体声->单声道

AVAudioFile 不能在 AVAudioEngine 中播放