AVAudioEngine播放多声道音频

时间:2014-12-02 13:11:17

标签: ios objective-c audio ios8 avfoundation

简单的问题。如何使用AVAudioEngine播放多声道音频文件(> 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

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

2 个答案:

答案 0 :(得分:2)

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

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

我发现在我创建的函数中我有audioEngine.attachNode(audioPlayerNode),这意味着audioPlayerNode被附加到audioEngine一次,然后退出。所以我将此附件移动到viewDidLoad()函数,以便每次都传递它。

答案 1 :(得分:1)

所以这就是我到目前为止所管理的内容。它远非完美,但它有点奏效。

要获得所有频道,您需要使用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复制某些东西(比如我自己),那么,运气不好。