Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/112.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios AVAudioEngine多个AVAudioInputNodes不能完全同步播放_Ios_Objective C_Iphone_Ipad_Core Audio - Fatal编程技术网

Ios AVAudioEngine多个AVAudioInputNodes不能完全同步播放

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

我一直在尝试使用AVAudioEngine来安排多个音频文件完美同步播放,但在收听输出时,输入节点之间似乎有一个非常轻微的延迟。音频引擎使用下图实现:

//
//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...