Ios AVAudioEngine多个AVAudioInputNodes不能完全同步播放
我一直在尝试使用AVAudioEngine来安排多个音频文件完美同步播放,但在收听输出时,输入节点之间似乎有一个非常轻微的延迟。音频引擎使用下图实现:Ios AVAudioEngine多个AVAudioInputNodes不能完全同步播放,ios,objective-c,iphone,ipad,core-audio,Ios,Objective C,Iphone,Ipad,Core Audio,我一直在尝试使用AVAudioEngine来安排多个音频文件完美同步播放,但在收听输出时,输入节点之间似乎有一个非常轻微的延迟。音频引擎使用下图实现: // //AVAudioPlayerNode1 --> //AVAudioPlayerNode2 --> //AVAudioPlayerNode3 --> AVAudioMixerNode --> AVAudioUnitVarispeed ---> AvAudioOutputNode //AVAudioPlayerN
//
//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;
}
这给了每个AVAudioInputNode足够的时间来加载缓冲区,并修复了所有音频同步问题。希望这能帮助别人 我用苹果开发者支持票来解决我自己在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()
以获得一些任意值以实际管理播放节点时)
因此,无法将两个或更多节点同步在一起是一个bug(经苹果支持部门确认)。一般来说,如果您不必使用AVAudioEngine中引入的内容(并且您不知道如何将其转换为AUGraph),我建议您改用AUGraph。它的实现开销稍大,但您可以对其进行更多的控制。问题:
好吧,问题是您在playAt:之前的for循环的每次运行中都要检索您的播放器
所以,你现在实际上会得到一个不同的,-每个玩家的时间
这样做的话,你最好用play:或playatime:nil开始循环中的所有玩家!!!如果同步丢失,您将体验到相同的结果
出于同样的原因,您的播放器在不同的设备上以不同的方式失去同步,这取决于机器的速度;-)你的现在-时间是随机的幻数-因此,如果它们恰好在你的场景中起作用,不要假设它们总是有效的。即使是由于繁忙的运行循环或CPU造成的最小延迟也会使您再次失去同步
解决方案:
您真正需要做的是在循环之前获得一个now=player.lastRenderTime的离散快照,并使用这个非常相同的锚,以便为所有玩家获得批量同步启动
这样,你甚至不需要延迟你的球员的开始。诚然,该系统将剪辑一些主要帧(当然每个玩家的剪辑量相同;-),以补偿您最近设置的现在(实际上已经过去并消失)和实际播放时间(在不久的将来仍在前方)之间的差异但最终,你的所有播放器都会同步启动,就好像你真的在现在启动了一样。这些剪裁过的框架几乎从来都不引人注意,你会对响应性感到安心
如果您碰巧需要这些帧(由于文件/段/缓冲区开始时发出的咔嗒声或伪影),那么,通过延迟启动播放机,将您的现在转移到未来。但是,当然,你会得到这个小滞后后,点击开始按钮-虽然当然仍然在完美的同步
结论:
这里的要点是为所有玩家提供一个单一的参考now-时间,并在捕获此now-参考后尽快调用您的PlayaTime:now方法。间隙越大,剪辑的导帧部分将越大-除非您提供合理的开始延迟并将其添加到您的now-时间中,这当然会导致在按下开始按钮后以延迟启动的形式出现无响应
并且始终要意识到这样一个事实,即无论音频缓冲机制在任何设备上产生什么延迟,如果以上述正确方式进行,它都不会影响任何数量的播放器的同步性!它也不会延迟您的音频!只是让你真正听到你的音频的窗口在稍后的时间点被打开
请注意:
- 如果您选择取消延迟(超级响应)启动选项,并且 无论出于何种原因,都会产生较大的延迟(在 现在捕获和玩家的实际开始),您将 剪掉一个大的前导部分(约300毫秒/0.3秒) 音频这意味着当你启动你的播放器时,它将立即启动 但不是从您最近暂停的位置恢复,而是 (高达300毫秒)稍后在音频中播放。所以声学感知是这样的 暂停播放会在移动中减少一部分音频,尽管一切都完全同步
- 作为您在PlayaTime:now中提供的开始延迟+ myProvidedDelay方法调用是一个固定的常量值(它不会得到 动态调整以适应缓冲延迟或其他变化 重系统负载下的参数)即使是延迟选项 如果提供的延迟时间小于约300ms,则会导致 如果与设备相关,则剪辑前导音频样本 时间超过您提供的延迟时间
- 最大剪裁量(按设计)不会大于
这大约300毫秒。要获得证明,只需强制进行受控(样本精确)剪裁
通过例如将负延迟时间添加到now
通过增加这一点,您将感受到越来越多的剪辑音频部分
负值。大于~300ms的每个负值都会
校正至~300ms。因此,将提供30秒的负延迟
导致与负值10、6、3或1相同的行为
秒数,以及秒数
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];
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...
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...