OSX和Windows 10上的低延迟同步输出

时间:2017-06-01 10:00:27

标签: usb iokit low-latency winusb

我正在尝试以非常低的延迟在高速USB 2上输出等时数据(以编程方式生成)。理想情况下大约1-2毫秒。在Windows上我使用WinUsb,在OSX上我使用的是IOKit。

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

1帧传输

WinUsb在其允许的范围内是相当严格的,并且要求每个等时传输是整数帧(1帧= 1毫秒)。因此,为了最大限度地减少延迟,请使用循环中的每个帧进行传输,如下所示:

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毫秒的multimedia timers(除非您使用未记录的NtSetTimerResolution?)。

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

1 个答案:

答案 0 :(得分:1)

(评论太长了,所以......)

我只能解决OS X方面的问题。这部分问题:

  

我认为这个想法是你以某种方式每N毫秒唤醒(或者   微秒),读取帧列表,找出你需要写的地方   并且这样做。我还没有为此编写代码,但我不是   完全确定如何进行,没有我能找到的例子。

     

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

让我对你正在尝试做的事情摸不着头脑。您的数据来自哪里,延迟是至关重要的,但数据准备好后数据源还没有通知您?

我们的想法是,您的数据正在从某些来源流式传输,并且只要有任何数据可用,可能是在调用该数据源的某些完成时,您将所有可用数据写入用户/内核共享数据缓冲区中适当的位置。

所以也许你可以更详细地解释一下你想做什么,我也许可以提供帮助。