AVAudioEngine多AVAudioInputNodes不完美同步播放

我一直在试图使用AVAudioEngine安排多个audio文件以完美的同步播放,但是当听输出时,input节点之间似乎有一个非常小的延迟。 audio引擎使用以下图表实现:

// //AVAudioPlayerNode1 --> //AVAudioPlayerNode2 --> //AVAudioPlayerNode3 --> AVAudioMixerNode --> AVAudioUnitVarispeed ---> AvAudioOutputNode //AVAudioPlayerNode4 --> | //AVAudioPlayerNode5 --> AudioTap // | //AVAudioPCMBuffers // 

我正在使用下面的代码来加载样本,并在同一时间安排它们:

 - (void)scheduleInitialAudioBlock:(SBScheduledAudioBlock *)block { for (int i = 0; i < 5; i++) { NSString *path = [self assetPathForChannel:i trackItem:block.trackItem]; //this fetches the right audio file path to be played AVAudioPCMBuffer *buffer = [self bufferFromFile:path]; [block.buffers addObject:buffer]; } AVAudioTime *time = [[AVAudioTime alloc] initWithSampleTime:0 atRate:1.0]; for (int i = 0; i < 5; i++) { [inputNodes[i] scheduleBuffer:block.buffers[i] atTime:time options:AVAudioPlayerNodeBufferInterrupts completionHandler:nil]; } } - (AVAudioPCMBuffer *)bufferFromFile:(NSString *)filePath { NSURL *fileURl = [NSURL fileURLWithPath:filePath]; NSError *error; AVAudioFile *audioFile = [[AVAudioFile alloc] initForReading:fileURl commonFormat:AVAudioPCMFormatFloat32 interleaved:NO error:&error]; if (error) { return nil; } AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:audioFile.processingFormat frameCapacity:audioFile.length]; [audioFile readIntoBuffer:buffer frameCount:audioFile.length error:&error]; if (error) { return nil; } return buffer; } 

我注意到这个问题只能在设备上感知,我正在testing一个iPhone5s,但我不明白为什么audio文件播放不同步,任何帮助将不胜感激。

**答案**

我们结束了用下面的代码sorting问题:

 AVAudioTime *startTime = nil; for (AVAudioPlayerNode *node in inputNodes) { if(startTime == nil) { const float kStartDelayTime = 0.1; // sec AVAudioFormat *outputFormat = [node outputFormatForBus:0]; AVAudioFramePosition startSampleTime = node.lastRenderTime.sampleTime + kStartDelayTime * outputFormat.sampleRate; startTime = [AVAudioTime timeWithSampleTime:startSampleTime atRate:outputFormat.sampleRate]; } [node playAtTime:startTime]; } 

这给每个AVAudioInputNode足够的时间来加载缓冲区,并修复我们所有的audio同步问题。 希望这可以帮助别人!

问题:

那么,问题是你在playAt之前的for循环的每一次运行中检索你的player.lastRenderTime

所以,对于每个玩家,你现在都会得到一个不同的时间!

这样做的方式,你可能会启动所有玩家循环播放:playAtTime:无 ! 你会遇到同样的结果丢失同步…

出于同样的原因,你的播放器在不同的设备上以不同的方式失去同步,这取决于机器的速度;-)你现在的时间是随机的魔术数字 – 所以,不要以为他们会一直工作,如果他们只是碰巧在你的情况下工作。 即使是因为繁忙的运行循环或CPU而造成的最小的延迟,也会使您再次失去同步…

解:

你真正要做的就是在循环之前得到一个 now = player.lastRenderTime的独立快照,并使用这个相同的锚来获得所有玩家的批量同步开始。

这样你甚至不需要延迟玩家的开始。 不可否认,系统会剪辑一些主要的框架 – (但当然对于每个玩家来说都是相同的数量;-) – 以补偿您最近设置的( 现在已经是过去和现在)和实际playTime (在不久的将来仍然处于领先地位),但是最终会让所有的播放器完全同步,就好像你现在真的已经开始播放了。 这些剪裁的框架几乎从来没有引人注意,你会有一个安心的响应速度…

如果您碰巧需要这些框架 – 因为在文件/段/缓冲区开始处有可听见的点击或伪影 – 那么通过延迟启动您的播放器将您的现在转移到未来。 但是,当然,在点击开始button之后,你会得到这个小小的延迟 – 尽pipe当然仍然处于完美的同步状态。

结论:

这里的重点是现在有一个单一的参考时间为所有玩家,并立即调用你的playAtTime:now方法后,捕捉这个现在的参考。 间距越大,剪裁的前导帧部分就越大 – 除非你提供了合理的延迟启动时间,并将其添加到当前时间,这在点击启动button之后导致延迟启动forms的无响应。

而且要时刻注意这样一个事实 – 无论audio缓冲机制产生什么样的延迟,都不会影响任何数量播放器的同步性。 它不会延迟你的audio! 只是实际上让你听到你的audio的窗口在稍后的时间点被打开。


请注意:

  • 如果你select了无延迟超级响应 )的开始选项,并且出于任何原因发生了很大的延迟( 现在的捕获和你的球员的实际开始之间 ),你将裁剪一个大的领先部分(高达约300ms / 0.3秒)的audio。 这意味着当你启动你的播放器时,它会马上启动,但是不能从你最近暂停的位置恢复,而是在你的audio之后(最多300ms)。 所以声音的感觉是,暂停播放虽然一切都完全不同步,但是会随时随地切断部分audio。
  • 作为您在playAtTime中提供的启动延迟:now + myProvidedDelay方法调用是一个固定的常量值(在负载较重的情况下,不会dynamic调整以适应缓冲延迟或其他变化参数),即使延迟选项带有如果依赖于设备的准备时间超过了您提供的延迟时间,则提供的延迟时间小于约300ms可能会导致引导audio采样的削波。
  • 裁剪的最大数量(devise上)不会超过这些〜300毫秒。 为了得到certificate只是通过例如加上一个负的延迟时间来迫使一个受控的(采样精确的)前导帧的削波,并且你将通过增大这个负值来感知一个正在增长的削波audio部分。 每个大于300ms的负值会被修正到300ms。 因此,提供的30秒的负延迟将导致与10,6,3或1秒的负值相同的行为,当然还包括负0.8,0.5秒,降至〜0.3

这个例子很适合演示的目的,但不应该在生产代码中使用负的延迟值。


注意:

多人游戏设置中最重要的一点就是让你的播放器保持同步。 截至2016年6月,AVAudioPlayerNode中还没有同步退出策略。

只是一个小方法查找或注销两个player.pause之间的控制台调用可能会迫使后者比以前执行一个或更多的帧/样本(s)。 所以你的玩家实际上不会在相同的相对位置停下来。 而最重要的是 – 不同的设备会产生不同的行为…

如果你现在用上面提到的(同步)方式来启动它们,那么你上一次暂停的那些不同步的当前玩家位置肯定会在每次playAtTime时被强制同步到你新的位置:意味着你在播放器的每一个新的开始时都会将丢失的样本/帧传播到未来。 这当然会加上每一个新的开始/暂停周期,并扩大差距。 做这五十次或一百次,你已经得到一个很好的延迟效果,而不使用效果,audio单元;-)

由于我们没有(通过系统提供的)控制这个因素,所以唯一的补救办法就是把所有的调用都按顺序依次放在player.pause中,而且没有任何内容,就像你可以看到的那样下面的例子。 不要把它们放在一个for-loop或者类似的东西里,这样可以保证在播放器的下一个暂停/启动时结束不同步。

将这些呼叫保持在一起是100%完美的解决scheme,否则任何大的CPU负载下的运行循环都可能会偶然干扰并强制分离彼此的暂停呼叫,并导致帧丢失 – 我不知道 – 至less在几周搞乱了AVAudioNode API我绝不会强迫我的多人游戏集失去同步 – 但是,我仍然不觉得这个非同步, 随机幻数停顿不是很舒服或安全解…


代码示例和替代方法:

如果你的引擎已经运行,你在AVAudioNode中有一个@property lastRenderTime – 你的播放器的超类 – 这是你100%采样帧准确同步的票据…

 AVAudioFormat *outputFormat = [playerA outputFormatForBus:0]; const float kStartDelayTime = 0.0; // seconds - in case you wanna delay the start AVAudioFramePosition now = playerA.lastRenderTime.sampleTime; AVAudioTime *startTime = [AVAudioTime timeWithSampleTime:(now + (kStartDelayTime * outputFormat.sampleRate)) atRate:outputFormat.sampleRate]; [playerA playAtTime: startTime]; [playerB playAtTime: startTime]; [playerC playAtTime: startTime]; [playerD playAtTime: startTime]; [player... 

顺便说一下,您可以使用AVAudioPlayer / AVAudioRecorder类实现相同的100%采样帧准确结果。

 NSTimeInterval startDelayTime = 0.0; // seconds - in case you wanna delay the start NSTimeInterval now = playerA.deviceCurrentTime; NSTimeIntervall startTime = now + startDelayTime; [playerA playAtTime: startTime]; [playerB playAtTime: startTime]; [playerC playAtTime: startTime]; [playerD playAtTime: startTime]; [player... 

在没有startDelayTime的情况下,所有玩家的第一个100-200ms将被截断,因为起始命令实际上需要时间到达运行循环,尽pipe玩家现在已经开始100%同步了。 但是,如果startDelayTime = 0.25,那么你很好。 并且不要忘记准备提前你的玩家,这样在开始的时候不需要额外的缓冲或者设置 – 只需要开始他们;-)

我用AVAudioEngine来解决自己的问题,其中一个问题和你的问题完全一样。 我得到这个代码尝试:

 AudioTimeStamp myAudioQueueStartTime = {0}; UInt32 theNumberOfSecondsInTheFuture = 5; Float64 hostTimeFreq = CAHostTimeBase::GetFrequency(); UInt64 startHostTime = CAHostTimeBase::GetCurrentTime()+theNumberOfSecondsInTheFuture*hostTimeFreq; myAudioQueueStartTime.mFlags = kAudioTimeStampHostTimeValid; myAudioQueueStartTime.mHostTime = startHostTime; AVAudioTime *time = [AVAudioTime timeWithAudioTimeStamp:&myAudioQueueStartTime sampleRate:_file.processingFormat.sampleRate]; 

除了天网时代的播放,而不是将来5秒,它还没有同步两个AVAudioPlayerNodes(当我切换GetCurrentTime()为一些任意值,以实际pipe理播放节点)。

所以不能同时同步两个或更多的节点是一个错误(由苹果支持确认)。 一般来说,如果你不需要使用AVAudioEngine引入的东西(你不知道如何将它转换成AUGraph),我build议使用AUGraph。 实施起来要多一点,但是你有更多的控制权。