简体   繁体   English

AVAudioEngine播放多声道音频

[英]AVAudioEngine playing multi channel audio

Simple question. 简单的问题。 How do I play multi channel audio files (>2 channels) using AVAudioEngine so that I can hear all channels on default 2-channel output (headphones/speaker). 如何使用AVAudioEngine播放多声道音频文件(> 2声道),以便我可以在默认的2声道输出(耳机/扬声器)上听到所有声道。 Following code (stripped of error checking for presenting) plays the file first two channels but I can only hear it when headphones are plugged in. 下面的代码(剥离错误检查以显示)播放前两个频道的文件,但我只能在插入耳机时听到它。

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];

I tried a lot of combinations with formats for both player->mixer and mixer->output connections but they either result with same thing as code above or more likely a crash with either: 我尝试了很多与播放器 - >混音器和混音器 - >输出连接格式的组合,但它们要么与上面的代码相同,要么更可能是以下两种情况的崩溃:

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'

or AUSetFormat OSStatus code -10868 (which is format not supported if I'm not mistaken). 或者AUSetFormat OSStatus代码-10868(如果我没有记错的话,这种格式不受支持)。 Using file.processingFormat for any connection crashes app with the first error above. 对任何连接使用file.processingFormat会崩溃应用程序与上面的第一个错误。 Also, the crash occurs on [player play]; 此外,崩溃发生在[player play];

There must be some way of playing it like I want because I'm able to do it without problem using AUGraph, however, since AVAudioEngine provides one feature I have to include, I'm stuck with it. 必须有一些像我想要的那样播放它,因为我能够毫无问题地使用AUGraph,但是,因为AVAudioEngine提供了我必须包含的一个功能,所以我坚持使用它。

Multi channel audio files I use to check my code can be found here 我可以在这里找到用于检查我的代码的多声道音频文件

UPDATE Ok, so hearing audio in headphones only was probably due to me forgetting setting audio session to active in my test app. 更新好的,所以只听到耳机中的音频可能是因为我忘了在我的测试应用程序中将音频会话设置为活动状态。 But I still only hear first two channels... 但我仍然只听到前两个频道......

I had a similar issue in Swift. 我在Swift中遇到过类似的问题。 The error was 'com.apple.coreaudio.avfaudio', reason: 'required condition is false:!nodeimpl->SslEngineImpl()'. 错误是'com.apple.coreaudio.avfaudio',原因是:'required condition is false:!nodeimpl-> SslEngineImpl()'。

The task was to play two audio files, one after another. 任务是一个接一个地播放两个音频文件。 If I hit stop after playing the first audio file and then played the second audio file, the system crashed. 如果我在播放第一个音频文件然后播放第二个音频文件后点击停止,则系统崩溃。

I found that in a function I created I had audioEngine.attachNode(audioPlayerNode) which means that the audioPlayerNode was being attached to the audioEngine once and then exited. 我发现在我创建的函数中我有audioEngine.attachNode(audioPlayerNode) ,这意味着audioPlayerNode被连接到audioEngine一次然后退出。 So I moved this attachment to the viewDidLoad() function so that it gets passed every time. 所以我将此附件移动到viewDidLoad()函数,以便每次都传递它。

So here's what I managed so far. 所以这就是我到目前为止所管理的内容。 It's far from perfect but it somewhat works. 它远非完美,但它有点奏效。

To get all channels you need to use AVAudioPCMBuffer and store two channels from file in each. 要获得所有通道,您需要使用AVAudioPCMBuffer并在每个通道中存储两个通道。 Also, for each channel pair you need separate AVAudioPlayerNode, then just connect each player to AVAudioMixerNode and we're done. 此外,对于每个通道对,您需要单独的AVAudioPlayerNode,然后将每个播放器连接到AVAudioMixerNode,我们就完成了。 Some simple code for 6-channel audio: 一些简单的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];

Now this solution is far from perfect since there will be a delay between pairs of channels because of calling play for each player at different time. 现在这个解决方案远非完美,因为在不同时间为每个玩家调用play ,对通道之间会有延迟。 I also still can't play 8-channel audio from my test files (see link in OP). 我还是无法从我的测试文件中播放8声道音频(参见OP中的链接)。 The AVAudioFile processing format has 0 for channel count and even if I create my own format with correct number of channels and layout, I get error on buffer read. AVAudioFile处理格式的通道数为0,即使我使用正确的通道数和布局创建自己的格式,我也会收到缓冲区读取错误。 Note that I can play this file perfectly fine using AUGraph. 请注意,我可以使用AUGraph完美​​地播放此文件。

So I will wait before accepting this answer, if you have better solution please share. 所以我会在接受这个答案之前等待,如果你有更好的解决方案请分享。

EDIT 编辑

So it appears that both my unable to sync nodes problem and not being able to play this particular 8-channel audio are bugs (confirmed by Apple developer support). 因此,似乎无法同步节点问题而无法播放此特定8声道音频都是错误(Apple开发人员支持确认)。

So little advice for people meddling with audio on iOS. 对于在iOS上干扰音频的人来说,这么少的建议。 While AVAudioEngine is fine for simple stuff, you should definitely go for AUGraph with more complicated stuff, even stuff that's suppose to work with AVAudioEngine. 虽然AVAudioEngine对于简单的东西来说很好,但你应该选择AUGraph来处理更复杂的东西,甚至可以选择与AVAudioEngine一起使用的东西。 And if you don't know how to replicate certain things from AVAudioEngine in AUGraph (like myself), well, tough luck. 如果你不知道如何从AUGraph中的AVAudioEngine复制某些东西(比如我自己),那么,运气不好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM