我正在尝试通过高速 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 毫秒的多媒体计时器NtSetTimerResolution
(除非您使用未记录的( ?)。
所以我的问题是:我可以改进“1 帧传输”方法,还是应该切换到尝试与内核竞争的 1 kHz 回调。示例代码非常感谢!