2014-12-02 132 views
1

简单的问题。如何使用AVAudioEngine播放多声道音频文件(> 2声道),以便我可以在默认双声道输出(耳机/扬声器)上听到所有声道。下面的代码(剥离错误检查呈现)播放该文件前两个渠道,但我只能听到它,当插入耳机。AVAudioEngine播放多声道音频

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

UPDATE 好了,听音频耳机只可能是因为我忘了在我的测试应用程序设置音频会话活跃。但我仍然只听到前两个频道...

回答

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声道音效的bug(苹果开发者支持确认)。

对于干扰iOS上音频的人,这么少的建议。虽然AVAudioEngine对于简单的东西来说很好,但你应该为AUGraph带来更复杂的东西,甚至是可以与AVAudioEngine配合使用的东西。如果你不知道如何从AUGraph的AVAudioEngine中复制某些东西(比如我自己),那么,运气好极了。

+0

请详细说明您提到的错误。是不是可以使用'AVAudioPlayerNode'的'scheduleBuffer:atTime:options:completionHandler'来为所有三个节点指定相同的'AVAudioTIme'? – Mark 2016-01-29 18:16:18

+1

在编写本问答时,即使您为所有三个节点放置相同的AVAudioTime,声音之间仍会有轻微的延迟。不知道他们是否修好了。 – Kegluneq 2016-02-01 12:23:23

2

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

该任务是一个接一个播放两个音频文件。如果在播放第一个音频文件后再次播放,然后播放第二个音频文件,则系统会崩溃。

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