同步音频和画布

时间:2016-01-14 18:26:49

标签: javascript jquery html5 canvas

问题

我正在尝试将画布的帧与在后台播放的音频同步 当我使用Vector3? position = null; 时,您会在一秒钟内在不同的浏览器/计算机上获得不稳定的帧数。我也尝试了window.requestAnimationFrame,它比setInterval(function(){ //code }, 1);更接近完美,但它仍然无法正确完成工作。

以下是我的代码的工作方式。

window.requestAnimationFrame每毫秒触发一次,然后检查在x时刻应该生成哪些对象,它还会清除并更新画布。此外,我使用毫秒来计算对象的速度,以便它们在正确的x时间到达画布上的位置。

有没有更好的方法呢? 我将在下面列出我的代码,以及指向我当前CodePen的链接,以便您可以看到它的实际效果。 (单击画布区域以开始动画背景中有音乐要同步,但在点击之前不会启动)

Sonar Song on CodePen

setInterval

1 个答案:

答案 0 :(得分:2)

同步

您不能依赖setInterval或setTimeout,因为它们只会尽可能接近请求的时间,因此对于同步动画和时间等内容非常不可靠。

您需要使用一致的时间来同步。你还必须让你的代码适应迟到。

日期和requestAnimationFrame

首先得到时间。 JavaScript通过Date对象提供以毫秒为单位的时间。

以下将返回当前时间(以前的一些时间)(我不知道1452798031458 ms之前的时间)

var currentTime = new Date().valueOf();

或者,如果您使用window.requestAnimationFrame,则将时间作为第一个参数

//  main update loop
function update(time){
    // time is the millisecond time 
    requestAnimationFrame(update); // request new frame
}
update(new Date().valueOf);

如果您希望在设定的时间内发生某些事情,您必须考虑测试时间的机会之间的时间,因为这可能会有所不同。

定期活动时间

让我们根据帧时间尝试一个计时事件。

首先跟踪时间的一些变量

var frameTime;     // time between calls to update
var lastFrameTime;  // the time of the perviouse call to update
var eventTime;      // the event start time that we want to stay in sync with
var timeInterval = 1000; // when we want sync events

然后开始我们希望与之保持同步的事件。

function startSound(){
     sound.play(); // assuming you have the sound.
     eventTime = new Date().valueOf(); // now
}

更新功能将尽可能接近我们想要的时间做正确的事情。

function update(time){
    frameTime = time - lastFrameTime; // how long since the last call to update     
    var timeSinceEvent = time-eventTime; // get the time since the event started
    var timeSinceLastSync = timeSinceEvent % timeInterval;

    // Several options here so explained below

    requestAnimationFrame(update); // request new frame
    lastFrameTime = time; // remember the last frame time
}
startSound(); // start playing
lastFrameTime = new Date().valueOf;
update(new Date().valueOf);

现在你有一些选择。人类的感知在1/10秒以下的任何事物中分离视觉效果并不是那么好。人类听觉非常善于及时分离事件(当在录音中弹吉他延迟10ms时我感到恼火)所以你有考虑你所呈现的内容。

视觉事件

视觉上是最简单的。您只需在同步时间后尽可能快地触发snyc事件。

if(timeSinceLastSync > timeInterval){
     // do the visual event
     eventTime += timeInterval;  // update the event time to the time this
                                 // event was to have happened.
}

这将在帧时间1/60(分钟)秒内触发视觉事件。您会注意到我添加到eventTime。我没有获得当前时间,因为当前时间可能长达30ms,如果我们添加甚至ms错误,它将很快累积。

音频,高精度事件

对于音频事件,我们通过查看最后一帧时间来抢占事件时间

// make sure we are near the next event time. this is to avoid firing the event 
// twice. Once on the frame before and once on the frame after.
if((timeInterval - timeSinceLastSync) >  timeInterval/2){ 
    // will the time to the new sync event be less than half the last frame time
    // or have we passed that time.
    if((timeInterval - timeSinceLastSync) < frameTime || timeSinceLastSync > timeInterval ){
        // fire the synced event.
        eventTime += timeInterval;  // update the event time to the time this
                                 // event was or will happened.
    }
}

我们在这里做的是使用最后一帧完成的时间并假设当前帧将花费相同的时间。然后我们找到同步事件的持续时间,如果该时间少于帧时间的一半,则触发该事件。或者我们可能已经通过了同步事件时间。无论哪种方式,我们现在都需要开火。

然后再次通过添加来更新当前的同步事件时间。

此方法可让您尽可能接近发布事件的时间。 (假设60fps,在1/120秒内)

改进

你可以改进这一点。 JavaScript阻塞,所以你需要等待更新功能结束。您还可以通过在退出函数之前获取时间来跟踪在更新功能中花费的时间。然后,您将估计要求触发的代码将发生多长时间,并且可以在计算中包含该内容。通常,帧之间的时间为16ms,但更新函数可能仅运行8ms,因此触发事件将发生下一帧前8ms。

效果和微秒

在某些名为performance的浏览器上还有微秒(1 / 1,000,000秒)计时器

// assume no performance API
var usePerformance = false
// at the start of you code check if its available
if(typeof performance === "function"){  // is performance available
    usePerformance = true;
}

为避免每次需要时间时都必须使用API​​

创建单独的函数来测试性能API
var updateP = function(){
}
// normal update function
var updateN = function(){
}
// set the correct update function
var update;
if(usePerformance){
    update = updateP;
}else{
    update = updateN;
}

使用效果

使用性能API

var now = performance.now(); // returns the time in ms with a fractional
                             // representing the microseconds.

事件之间的时间

var n = performance.now();
console.log(performance.now()-n);  // returns 0.021 depending on the system speed

希望这有帮助。