我试图通过高延迟的高速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(( 并从该回调中读取帧列表?
让我挠头您要做的事情。您的数据来自哪里,延迟至关重要,但数据源在准备就绪时尚未通知您?
这个想法是您的数据是从某些源流出的,并且一旦任何数据可用,大概是在调用该数据源的某些完成时,您将所有可用的数据写入用户/内核共享数据缓冲区适当的位置。
所以也许您可以更详细地解释您要做的事情,我也许可以提供帮助。