精确捕捉循环

问题描述

问题

我正在实现一个带有 FPS/FPM/FPH(秒、分钟、小时)控制的捕获循环。这意味着用户可以正常或延时拍摄某些东西。

这是我的代码

const DDL = ({ options },cid) => {
  console.log(cid);
  const filtered = options.filter((option) => option.catID === cid);
  console.log(filtered);
  return filtered.map((option) => (
    <Dropdown.Item key={option.id} value={option.nev}>
      {option.nev}
    </Dropdown.Item>
  ));
};

export default DDL;

问题在于,如果间隔设置为 60 FPS(16 毫秒),则生成的捕获时间平均为 30 毫秒。但是,如果我将捕获间隔设置为 15 毫秒(> 60 FPS),它会按预期平均在 15 毫秒内。

我想知道为什么会发生这种情况,以及是否可以改进代码


解决方

根据 Alois 的评论,我设法创建了这个扩展类:

private System.Threading.CancellationTokenSource _capturetoken;
private List<long> _timeList = new List<long>();

private void CaptureRun(int interval)
{
    var sw = new Stopwatch();
    
    while (_capturetoken != null && !_capturetoken.IsCancellationRequested)
    {
        sw.Restart();

        //Capture happens here...
        //With or without my capture code,the result is the same (the difference in average time).
        //So I removed this part of the code to make it easier to understand.

        //If behind wait time,wait before capturing again.
        if (sw.ElapsedMilliseconds < interval)
            System.Threading.SpinWait.SpinUntil(() => sw.ElapsedMilliseconds >= interval);

        _timeList.Add(sw.ElapsedMilliseconds);
    }
}


//Code that starts the capture (simplified).
private void StopCapture()
{
    _capturetoken = new System.Threading.CancellationTokenSource();

    Task.Run(() => CaptureRun(16),_capturetoken.Token);
}


//Code that stops the capture.
private void StopCapture()
{
    if (_capturetoken != null)
    {
        _capturetoken.Cancel();
        _capturetoken.dispose();
        _capturetoken = null;
    }
}

只需在 internal class TimerResolution : Idisposable { #region Native [StructLayout(LayoutKind.Sequential)] private readonly struct TimeCaps { internal readonly uint MinimumResolution; internal readonly uint MaximumResolution; }; internal enum TimerResult : uint { NoError = 0,NoCanDo = 97 } [DllImport("winmm.dll",EntryPoint = "timeGetDevCaps",SetLastError = true)] private static extern uint GetDevCaps(ref TimeCaps timeCaps,uint sizeTimeCaps); [DllImport("ntdll.dll",EntryPoint = "NtQueryTimerResolution",SetLastError = true)] private static extern int QueryTimerResolution(out int maximumResolution,out int minimumResolution,out int currentResolution); [DllImport("winmm.dll",EntryPoint = "timeBeginPeriod")] internal static extern uint BeginPeriod(uint uMilliseconds); [DllImport("winmm.dll",EntryPoint = "timeGetTime")] internal static extern uint GetTime(); [DllImport("winmm.dll",EntryPoint = "timeEndPeriod")] internal static extern uint EndPeriod(uint uMilliseconds); #endregion #region Properties /// <summary> /// The target resolution in milliseconds. /// </summary> public uint TargetResolution { get; private set; } /// <summary> /// The current resolution in milliseconds. /// May differ from target resolution based on system limitation. /// </summary> public uint CurrentResolution { get; private set; } /// <summary> /// True if a new resolution was set (target resolution or not). /// </summary> public bool SuccessfullySetResolution { get; private set; } /// <summary> /// True if a new target resolution was set. /// </summary> public bool SuccessfullySetTargetResolution { get; private set; } #endregion /// <summary> /// Tries setting a given target timer resolution to the current thread. /// If the selected resolution can be set,a nearby value will be set instead. /// This must be disposed afterwards (or call EndPeriod() passing the CurrentResolution) /// </summary> /// <param name="targetResolution">The target resolution in milliseconds.</param> public TimerResolution(int targetResolution) { TargetResolution = (uint) targetResolution; //Get system limits. var timeCaps = new TimeCaps(); if (GetDevCaps(ref timeCaps,(uint) Marshal.SizeOf(typeof(TimeCaps))) != (uint) TimerResult.NoError) return; //Calculates resolution based on system limits. CurrentResolution = Math.Min(Math.Max(timeCaps.MinimumResolution,TargetResolution),timeCaps.MaximumResolution); //Begins the period in which the thread will run on this new timer resolution. if (BeginPeriod(CurrentResolution) != (uint) TimerResult.NoError) return; SuccessfullySetResolution = true; if (CurrentResolution == TargetResolution) SuccessfullySetTargetResolution = true; } public void dispose() { if (SuccessfullySetResolution) EndPeriod(CurrentResolution); } } 块中使用扩展类并将其放入任何您想以其他计时器分辨率运行的内容中:

using

解决方法

您会在睡眠 15 毫秒时看到 15 毫秒的延迟,而在睡眠 16 毫秒时会看到 30 毫秒的延迟,因为 SpinWait 在引擎盖下使用 Environment.TickCount,它依赖于系统时钟,而系统时钟显然在您的系统上具有 15 毫秒的分辨率。 您可以使用 timeBeginPeriod 设置系统范围的计时器分辨率。 见

有关时钟分辨率的更多深入信息。您可以使用来自 sysinternals 的 clockres 检查当前系统范围内的计时器分辨率。

查看示例输出:

C:>clockres

Clockres v2.1 - Clock resolution display utility
Copyright (C) 2016 Mark Russinovich
Sysinternals

Maximum timer interval: 15.625 ms
Minimum timer interval: 0.500 ms
**Current timer interval: 15.625 ms**

当 WPF 应用程序正在运行时(例如 Visual Studio)

Maximum timer interval: 15.625 ms
Minimum timer interval: 0.500 ms
**Current timer interval: 1.000 ms**

那么您将获得 1 毫秒的分辨率,因为每个 WPF 应用程序都会将时钟分辨率更改为 1 毫秒。这也被一些人用作“解决”问题的解决方法。