How To Stream Isochronous Data Over Universal Serial Bus (317434)
The information in this article applies to:
- Microsoft Windows CE Operating System, Versions 2.12
- Microsoft Windows CE Operating System, Versions 3.0
- Microsoft Windows CE .NET Operating System
This article was previously published under Q317434 SUMMARY
You can achieve isochronous streaming by using universal serial bus (USB). However, it is not clear how to do this in the Windows CE universal serial bus driver (USBD) interface.
The host controller driver attempts to delay the schedule by the amount of elapsed time that the host controller driver calculates to set up the transfer in a worst-case scenario (in this case, 3 milliseconds). For this reason, the following code does not work as expected:
pUSBD->lpIssueIsochTransfer(hPipe, NULL, 0, USB_START_ISOCH_ASAP, 0,
nFrames, aLengths, pData, NULL);
pUSBD->lpIssueIsochTransfer(hPipe, NULL, 0, USB_START_ISOCH_ASAP, 0,
nFrames, aLengths, pData, NULL);
MORE INFORMATION
Although the first transfer occurs as requested, the second transfer occurs at least three frames later, regardless of the priority at which you attempt to run.
This occurs even when you use the USB_NO_WAIT flag if no other transfer is pending. In fact, if the nFrames parameter is less than 4, USBD may refuse to issue the transfer as soon as possible (ASAP) because the IssueIsochTransfer property returns an error, even if another transfer is pending. This is because the USB stack always tries to ensure that it has at least a three-millisecond delay to preprocess the request.
This behavior is deliberate; the goal of the host controller driver is to issue transfers that have a chance to complete. Without the delay, a transfer that is issued may not complete because the transfer misses its start frame. Even ASAP transfers have a (computed) start frame because ASAP is a feature of the Windows CE USBD interface. ASAP is not a feature of USB, nor is it a feature of either of the supported host controller interfaces.
For best results in streaming, it is recommended that you queue transfers so that at least three frames are still pending when you issue the next transfer request. Even when you queue transfers of fewer than four frames, you can schedule the very first transfer to occur several milliseconds later.
You can achieve satisfactory results if you maintain a circular buffer of transfer handles (where each transfer is for a group of frames that immediately follow the last frame of the previous transfer).
The following code demonstrates one way to stream isochronous data over USB.
For simplicity, this code assumes the following:
- A fixed number of frames for each transfer.
- A fixed number of bytes of data for each frame.
Microsoft provides programming examples for illustration only, without warranty either expressed or implied. This includes, but is not limited to, the implied warranties of merchantability or fitness for a particular purpose. This article assumes that you are familiar with the programming language that is being demonstrated and with the tools that are used to create and to debug procedures. Microsoft support engineers can help explain the functionality of a particular procedure, but they will not modify these examples to provide added functionality or construct procedures to meet your specific requirements. Some modification may be necessary to adapt it to a specific purpose.
#define XFR_QUEUE_SZ 4 // size of the queue of pending transfers
#define NFRAMES_PER_XFR 8 // number of frames per transfer
typedef struct _XFR_PARAMS
{
USB_TRANSFER hdl; // valid transfer handle from USBD
PBYTE buf; // data to transmit or receive
DWORD len[NFRAMES_PER_XFR]; // array of lengths for this transfer
} XFR_PARAMS;
typedef struct _STREAM_PARAMS
{
PUSB_FUNCS pUSBD; // from USBD when your driver was loaded
USB_PIPE hPipe; // from OpenPipe
DWORD dwXfrFlags; // for IssueIsochTransfer
LPTRANSFER_NOTIFY_ROUTINE pCbFunc; // for completion notification
DWORD cXfrSz; // size in bytes of all transfers on the pipe
XFR_PARAMS aXfrQ[XFR_QUEUE_SZ]; // the transfer queue
HANDLE hsemQueueSz; // for pacing access to the transfer queue
int iXfrEnq; // index of next available element in aXfrQ
DWORD dwNextFrame; // frame number for next transfer
} STREAM_PARAMS;
STREAM_PARAMS g; // NOTE: this is only an illustration!
// Initialize the stream parameters before you attempt to send or receive data.
void StreamInit (PUSB_FUNCS pUSBD, USB_PIPE hPipe, DWORD dwDir, DWORD cXfrSz, LPTRANSFER_NOTIFY_ROUTINE pCbFunc, DWORD dwStartFrame)
{
g.pUSBD = pUSBD;
g.hPipe = hPipe;
g.dwXfrFlags = USB_NO_WAIT | (dwDir ? USB_IN_TRANSFER : USB_OUT_TRANSFER);
g.pCbFunc = pCbFunc;
g.cXfrSz = cXfrSz;
g.hsemQueueSz = CreateSemaphore(NULL, XFR_QUEUE_SZ, XFR_QUEUE_SZ, NULL);
g.iXfrEnq = 0;
g.dwNextFrame = dwStartFrame;
}
// Release the resources that are needed to implement the algorithm
void StreamCleanup (void)
{
CloseHandle(g.hsemQueueSz);
}
// You may need this code to resynchronize with the device
// if your driver starts to miss frames.
void StreamSetFrame (DWORD dwNextFrame)
{
g.dwNextFrame = dwNextFrame;
}
// Internal completion callback manages the transfer queue.
// Your driver should close the transfer handle as it
// would normally do (with pUSBD->lpCloseTransfer).
static void streamCb (HANDLE h, PVOID param)
{
DWORD iXfr = (DWORD) param;
BOOL r;
g.aXfrQ[iXfr].hdl = h;
g.pCbFunc(h, g.aXfrQ[iXfr].buf);
r = ReleaseSemaphore(g.hsemQueueSz, 1, NULL);
ASSERT(r);
}
// Stream your data. pData is passed to your completion notification
// routine when the transfer completes. Call this as often as you
// like (but as written it is not thread-safe); you block when
// the queue is full and unblock when it ceases to be full.
BOOL StreamData (PBYTE pData)
{
XFR_PARAMS *pXfr;
HANDLE h;
int i;
if (WaitForSingleObject(g.hsemQueueSz, INFINITE) != WAIT_OBJECT_0)
return FALSE;
pXfr = &g.aXfrQ[g.iXfrEnq];
for (i=0; i<NFRAMES_PER_XFR; ++i)
pXfr->len[i] = g.cXfrSz;
h = g.pUSBD->lpIssueIsochTransfer(g.hPipe, streamCb, g.iXfrEnq, g.dwXfrFlags,
g.dwNextFrame, NFRAMES_PER_XFR,
&pXfr->len[0], pData, NULL);
if (h == NULL) {
ReleaseSemaphore(g.hsemQueueSz, 1, NULL);
return FALSE;
}
g.dwNextFrame += NFRAMES_PER_XFR;
g.iXfrEnq = (g.iXfrEnq + 1) % XFR_QUEUE_SZ;
return TRUE;
}
Modification Type: | Major | Last Reviewed: | 6/24/2005 |
---|
Keywords: | kbhowto KB317434 |
---|
|