2014-12-02 167 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()函數中,以便每次都能通過。