OSX和Windows 10上的低延迟等级



我试图通过高延迟的高速USB 2输出(编程生成(的等质数据。理想情况下,大约1-2毫秒。在Windows上,我正在使用winusb,在OSX上,我正在使用iokit。

我想到了两种方法。我想知道哪个是最好的。

1帧转移

winusb在允许的范围内非常限制,并且需要每个同调传递为全数帧(1帧= 1 ms(。因此,以最小化延迟使用一个框架的延迟传输,类似这样的循环:

for (;;)
{
    // Submit a 1-frame transfer ASAP.
    WinUsb_WriteIsochPipeAsap(..., &overlapped[i]);
    // Wait for the transfer from 2 frames ago to complete, for timing purposes. This
    // keeps the loop in sync with the USB frames.
    WinUsb_GetOverlappedResult(..., &overlapped[i-2], block=true);
}

这效果很好,延迟2毫秒。在OSX上,我可以做类似的事情,尽管它要复杂得多。这是代码的要旨 - 完整的代码太长在此处发布:

uint64_t frame = ...->GetBusFrameNumber(...) + 1;
for (;;)
{
    // Submit at the next available frame.
    for (a few attempts)
    {
        kr = ...->LowLatencyWriteIsochPipeAsync(...
                                            frame, // Start on this frame.
                                            &transfer[i]); // Callback
        if (kr == kIOReturnIsoTooOld)
            frame++; // Try the next frame.
        else if (kr == kIOReturnSuccess)
            break;
        else
            abort();
    }
    // Above, I pass a callback with a reference to a condition_variable. When
    // the transfer completes the condition_variable is triggered and wakes this up:
    transfer[i-5].waitForResult();
    // I have to wait for 5 frames ago on OSX, otherwise it skips frames.
}

再次,这种作品的延迟约为3.5毫秒。但这不是超级可靠的。

竞赛内核

OSX的低延迟等级功能允许您提交长传输(例如64帧(,然后定期(最大一次每毫秒一次(更新框架列表,该帧列表说内核在读取写入缓冲器时必须需要的位置。

我认为您的想法是您以某种方式唤醒了每个N毫秒(或微秒(,阅读框架列表,确定需要写入的位置并做到这一点。我还没有为此编写代码,但是我不确定如何进行,也找不到我能找到的示例。

更新框架列表时似乎没有提供回调,因此我想您必须使用自己的计时器-CFRunLoopTimerCreate()并从该回调中读取帧列表?

我也想知道Winusb是否允许类似的事情,因为它也迫使您注册缓冲区,因此内核和用户空间可以同时访问它。我找不到任何示例明确说您可以在内核阅读时写入缓冲区。您是要在常规回调中使用WinUsb_GetCurrentFrameNumber来算出内核在转移中所需的位置吗?

这需要在Windows上进行定期回调,这似乎有些棘手。我见过的唯一方法是使用最短时间为1毫秒的多媒体计时器(除非您使用无证件(NtSetTimerResolution?(。

所以我的问题是:我可以改进" 1帧转移"方法,还是应该切换到试图竞争内核的1 kHz回调。示例代码非常感谢!

(太长时间评论,所以…(

我只能解决事物的OS X侧。问题的这一部分:

我认为您的想法是您以某种方式醒来每毫秒(或 微秒(,读取框架列表,确定您需要写的位置 做到这一点。我还没有为此编写代码,但我不是 完全确定如何进行,也没有我能找到的例子。

更新框架列表时似乎没有提供回调 所以我想您必须使用自己的计时器-Cfrunlooptimercreate(( 并从该回调中读取帧列表?

让我挠头您要做的事情。您的数据来自哪里,延迟至关重要,但数据源在准备就绪时尚未通知您?

这个想法是您的数据是从某些源流出的,并且一旦任何数据可用,大概是在调用该数据源的某些完成时,您将所有可用的数据写入用户/内核共享数据缓冲区适当的位置。

所以也许您可以更详细地解释您要做的事情,我也许可以提供帮助。

最新更新