AVAudioEngine播放多声道音频

3

简单问题。如何使用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提供了我必须包含的一个功能,因此我被卡住了。

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

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

2个回答

2

我在Swift中遇到了类似的问题。错误是'com.apple.coreaudio.avfaudio',原因是:'required condition is false:!nodeimpl->SslEngineImpl()'。

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

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


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的某些内容(就像我一样),那么就很难了。


请详细说明您提到的错误。不可能使用AVAudioPlayerNodescheduleBuffer:atTime:options:completionHandler为所有三个节点指定相同的AVAudioTime吗? - Mark
1
在撰写此问题和答案时,即使您将相同的 AVAudioTime 应用于所有三个节点,仍会存在轻微的声音延迟。不知道他们是否已经修复了这个问题。 - Kegluneq

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接