HTML5   发布时间:2022-04-27  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了ios – AVAudioEngine多个AVAudioInputNodes不能完美同步大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
我一直在尝试使用AVAudioENGIne来安排多个音频文件以完美的同步方式播放,但是在收听输出时,输入节点之间似乎有一点点延迟.音频引擎使用以下图表实现:

//
//AVAudioPlayerNode1 -->
//AVAudioPlayerNode2 -->
//AVAudioPlayerNode3 --> AVAu@L_946_0@mixerNode --> AVAudioUnitVarispeed ---> AvAudioOutputNode
//AVAudioPlayerNode4 -->                                            |
//AVAudioPlayerNode5 -->                                        AudioTap
//      |                                                         
//AVAudioPCMBuffers    
//@H_616_8@ 
 

我正在使用以下代码加载示例并同时安排它们:

- (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;
}@H_616_8@ 
 

我注意到这个问题只能在设备上看到,我正在测试iPhone5s,但我无法弄清楚为什么音频文件播放不同步,任何帮助都将非常感激.

**答案**

我们最终使用以下代码对问题进行排序:

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];
}@H_616_8@ 
 

这为每个AVAudioInputNode提供了足够的时间来加载缓冲区并修复了我们所有的音频同步问题.希望这有助于他人!

解决方法

问题:

好吧,问题是你在PLayAt之前的for循环的每次运行中检索你的player.lastRenderTime:

你这样做的方式你也可以通过play来启动循环中的所有玩家:或者playAtTime:nil !!!失去同步会导致相同的结果……

出于同样的原因,你的播放器在不同的设备上以不同的方式不同步,取决于机器的速度;-)你现在的时间是随机的魔术数字 – 所以,不要认为它们总是会工作如果他们恰巧在你的场景中工作.即使是由于繁忙的运行循环或cpu导致的最小延迟也会让您再次失去同步…

解:

你真正需要做的是在循环之前获得Now = player.lastRenderTime的一个离散快照,并使用这个非常相同的锚点,以获得所有玩家的批量同步开始.

这样你甚至不需要延迟你的玩家的开始.不可否认,该系统将剪切一些领先的框架 – (但当然每个玩家的数量相同;-) – 以弥补您最近设置的(实际上已经过去和已经过去)与实际之间的差异playTime(在不久的将来仍然处于领先地位),但最终会使你的所有玩家完全同步,就好像你现在真的已经开始了它们一样.这些剪裁的框架几乎从不会引人注目,您可以放心地回应…

如果您碰巧需要这些帧 – 因为文件/段/缓冲区启动时发出咔嗒声或瑕疵 – 现在,通过延迟启动播放器,将您现在转移到未来.但是当然你在按下开始按钮后会有一点滞后 – 然当然仍然完美同步……

结论:

这里的重点是现在为所有玩家提供一个单一参,并在捕获此现在参后尽快调用playAtTime:Now方法.间隙越大,剪裁的前导框架部分越大 – 除非您提供合理的启动延迟并将其添加到您现在的时间,这当然会在按下您的开始按钮后导致延迟启动时无响应.

并且始终要注意这样一个事实 – 无论设备是由音频缓冲机制产生的任何延迟 – 它都不会影响任何数量的播放器的同步性,如果以正确的,上述方式完成!它也不会延迟你的音频!实际上让您听到音频的窗口会在稍后的时间点打开…

请注意:

>如果您选择未延迟(超响应)启动选项
无论出于何种原因,都会产生很大的延迟
捕获现在和你的玩家的实际开始),你会的
截断你的一个大的领先部分(最多约300毫秒/ 0.3秒)
音频.这意味着当您启动播放器时,它将立即开始
但不是从你最近暂停它的位置恢复而是
(最多约300毫秒)后面的音频.所以声学感知就是这样
暂停播放可以随时随地切断音频的一部分,尽管一切都完全同步.
>作为playAtTime中提供的开始延迟:现在@H_228_54@myProvidedDelay方法调用一个固定的常量值(没有得到
动态调整以适应缓冲延迟或其他变化
系统负载较重时的参数)甚至可以选择延迟选项
提供的延迟时间小于约300ms可能会导致a
如果依赖于设备的准备,则剪切领先的音频样本
时间超过了您提供的延迟时间.
>最大削波量(按设计)不会大于
这些~300ms.要获得证明只是强制控制(样本准确)剪辑
例如,领先的帧到目前为止添加一个负延迟时间
你会通过增加这个来感知不断增长的剪辑音频部分
负值.每个负值大于300毫秒
整理到~300ms.因此提供30秒的负延迟
导致与负值10,6,3或1相同的行为
秒,当然还包括负0.8秒,0.5秒
到~0.3

此示例非常适用于演示目的,但不应在生产代码中使用负延迟值.

注意:

在多人游戏设置中最重要的是保持你的播放器.暂停同步.截至2016年6月,AVAudioPlayerNode中仍然没有同步退出策略.

只需要一个方法查找或在两个播放器之间向控制台注销一些内容.暂停调用可能会强制后者执行一个甚至更多的帧/样本,而不是前一个.所以你的玩家实际上不会及时停在相同的相对位置.最重要的是 – 不同的设备会产生不同的行为……

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

因为我们没有(通过系统提供的)控制这个因素唯一的补救措施是把所有的调用都放在播放器上.暂停一个一个地按顺序排列,而不是它们之间的任何东西,就像你可以看到的那样以下示例.不要将它们放入for循环或任何类似的东西 – 这将保证在播放器的下一个暂停/开始时结束不同步…

是否将这些呼叫保持在一起是100%完美的解决方案,或者任何大cpu负载下的运行循环可能会偶然干扰并强制分离暂停呼叫,导致帧丢失 – 我不知道 – 至少在几周内搞乱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...@H_616_8@ 
 

便说一句 – 您可以使用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...@H_616_8@ 
 

没有startDelayTime,所有玩家的前100-200秒将被截断,因为启动命令实际上花费时间到运行循环,尽管玩家已经开始(好,已经安排)100%同步.但是使用startDelayTime = 0.25你很高兴.并且永远不要忘记准备好提前播放你的玩家,以便在开始时不需要额外的缓冲或设置 – 只需启动他们;-)

大佬总结

以上是大佬教程为你收集整理的ios – AVAudioEngine多个AVAudioInputNodes不能完美同步全部内容,希望文章能够帮你解决ios – AVAudioEngine多个AVAudioInputNodes不能完美同步所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。