/*++

Copyright (c) 1996-2000 Microsoft Corporation

Module Name:

    hawk.CPP

Abstract:


Environment:

    kernel mode

Revision History:


--*/

//*****************************************************************************
// I N C L U D E S
//*****************************************************************************
#include "hawk2.h"

#include "xvocver.h"

#if DBG || DEBUG_LOG

    ULONG               HawkDebugLevel=2;     // Level of debug output

#endif

//------------------------------------------------------------------------------
//  Declaration of static tables
//------------------------------------------------------------------------------
XHawkMediaObject *XHawkMediaObject::sm_pMicrophones = NULL;
XHawkMediaObject *XHawkMediaObject::sm_pHeadphones = NULL;
USHORT            XHawkMediaObject::sm_AvailableHeadphoneResources = 0;
USHORT            XHawkMediaObject::sm_AvailableMicrophoneResources = 0;
PHAWK_STREAMING_RESOURCES XHawkMediaObject::sm_pFreeStreamingResources = NULL;
HAWK_SAMPLE_RATE_INFORMATION XHawkMediaObject::sm_HawkSampleRates[HAWK_SAMPLE_RATE_COUNT] = 
{
    { 8000, 16, 0},     // 8.000 kHz
    {11025, 22, 40},    //11.025 kHz
    {16000, 32, 0},     //16.000 kHz
    {22050, 44, 20},    //22.050 kHz
    {24000, 48, 0}      //24.000 kHz
};
PUCHAR XHawkMediaObject::sm_pRateIndices = NULL;
PUCHAR XHawkMediaObject::sm_pRateIndexRefCounts = NULL;

//------------------------------------------------------------------------------
//  Declare Hawk types and class.
//------------------------------------------------------------------------------
DECLARE_XPP_TYPE(XDEVICE_TYPE_VOICE_MICROPHONE)
DECLARE_XPP_TYPE(XDEVICE_TYPE_VOICE_HEADPHONE)
USB_DEVICE_TYPE_TABLE_BEGIN(Hawk)
USB_DEVICE_TYPE_TABLE_ENTRY(XDEVICE_TYPE_VOICE_MICROPHONE),
USB_DEVICE_TYPE_TABLE_ENTRY(XDEVICE_TYPE_VOICE_HEADPHONE)
USB_DEVICE_TYPE_TABLE_END()
USB_CLASS_DRIVER_DECLARATION(Hawk, USB_DEVICE_CLASS_AUDIO, 0xFF, 0xFF)
USB_CLASS_DRIVER_DECLARATION_DUPLICATE(Hawk, 1, XBOX_DEVICE_CLASS_AUDIO_DEVICE, 0xFF, 0xFF)

#pragma data_seg(".XPP$ClassHawk")
USB_CLASS_DECLARATION_POINTER(Hawk)
USB_CLASS_DECLARATION_POINTER_DUPLICATE(Hawk, 1)
#pragma data_seg(".XPP$Data")

USB_RESOURCE_REQUIREMENTS HAWK_gResourceRequirements = 
    {USB_CONNECTOR_TYPE_HIGH_POWER, 0, 2, 1, 0, 0, 0, 0, 2, HAWK_ISOCH_MAX_ATTACH_BUFFERS};

//------------------------------------------------------------------------------
// USB Class Driver Interface
//------------------------------------------------------------------------------
#pragma code_seg(".XPPCINIT")
EXTERNUSB VOID
HawkInit (IUsbInit *pUsbInit)
{
    DBGPRINT(3, ("enter: HawkInit\n"));
    ULONG MicrophoneCount;
    ULONG HeadphoneCount; 
    
    //sentry against double init, required because we have two driver
    //table entries (look in the tables above) - this is still in there to support some older firmware.
    #pragma BUGBUG("Supporting USB_DEVICE_CLASS_AUDIO for old Hawk FW, we don't support any USB audio, so this must go at some point.")
    static BOOL fInitialized = FALSE;
    if(fInitialized) return;
    fInitialized = TRUE;

    
    if(pUsbInit->UseDefaultCount())
    {
        MicrophoneCount = HAWK_DEFAULT_MAX_OPENED;
        HeadphoneCount = HAWK_DEFAULT_MAX_OPENED;
    } else
    {
        MicrophoneCount = pUsbInit->GetMaxDeviceTypeCount(XDEVICE_TYPE_VOICE_MICROPHONE);
        HeadphoneCount = pUsbInit->GetMaxDeviceTypeCount(XDEVICE_TYPE_VOICE_HEADPHONE);
        ULONG ulMaxPorts = XGetPortCount();
        RIP_ON_NOT_TRUE_WITH_MESSAGE(MicrophoneCount <= ulMaxPorts, "XInitDevices: requested more XDEVICE_TYPE_VOICE_MICROPHONE than available ports.");
        if(MicrophoneCount > ulMaxPorts) MicrophoneCount = ulMaxPorts;
        RIP_ON_NOT_TRUE_WITH_MESSAGE(HeadphoneCount <= ulMaxPorts, "XInitDevices: requested more XDEVICE_TYPE_VOICE_HEADPHONE than available ports.");
        if(HeadphoneCount > ulMaxPorts) HeadphoneCount = ulMaxPorts;
    }
    //
    //  Register for microphones and headphone
    //
    HAWK_gResourceRequirements.MaxDevices = (UCHAR)MicrophoneCount;
    pUsbInit->RegisterResources(&HAWK_gResourceRequirements);
    HAWK_gResourceRequirements.MaxDevices = (UCHAR)HeadphoneCount;
    pUsbInit->RegisterResources(&HAWK_gResourceRequirements);
    
    XHawkMediaObject::InitializeClass(MicrophoneCount, HeadphoneCount);

    DBGPRINT(3, ("exit:  HawkInit\n"));    
    return;
}
#pragma code_seg(".XPPCODE")

EXTERNUSB VOID
HawkAddDevice(
     IN IUsbDevice *Device
     )
{
	const USB_ENDPOINT_DESCRIPTOR   *pEndpointDescriptor;
    XHawkMediaObject *pHawkMediaObject = NULL;
    DWORD dwPort;

    //
	//  Get the device port and slot
	//

    dwPort = Device->GetPort();
    if(dwPort >= 16)
    {
        DBGPRINT(2,("AddDevice: dwPort %d, bottom slot, not supported!\n",  dwPort));
        Device->AddComplete(USBD_STATUS_UNSUPPORTED_DEVICE);
        return;
    }

    //
    //  get an endpoint descriptor for the isoch endpoint
    //  (we don't know the direction (microphone or
    //  headphone).  Try input first.
    //

    pEndpointDescriptor = Device->GetEndpointDescriptor(
                                USB_ENDPOINT_TYPE_ISOCHRONOUS,
                                TRUE,
                                0);
    if(pEndpointDescriptor)
    {
        pHawkMediaObject = &XHawkMediaObject::sm_pMicrophones[dwPort];
        pHawkMediaObject->m_fMicrophone = TRUE;
    }
    else {
        pEndpointDescriptor = Device->GetEndpointDescriptor(
                                USB_ENDPOINT_TYPE_ISOCHRONOUS,
                                FALSE,
                                0);
        if(pEndpointDescriptor)
        {
            pHawkMediaObject = &XHawkMediaObject::sm_pHeadphones[dwPort];
            pHawkMediaObject->m_fMicrophone = FALSE;
        } else 
        {
            ASSERT(FALSE && "Isoch endpoint not found");
            Device->AddComplete(USBD_STATUS_UNSUPPORTED_DEVICE);
            return;
        }
    }
    
    //
    //  Check and initialize the state 
    //
    pHawkMediaObject->m_fConnected = TRUE;

    //
    // Use XHawkMediaObject as the device extension
    //
    
    Device->SetExtension(pHawkMediaObject);
    pHawkMediaObject->m_pDevice = Device;
    
    //
    //  Record the endpoint address, and wMaxPacket
    //

    pHawkMediaObject->m_bEndpointAddress = pEndpointDescriptor->bEndpointAddress;
    pHawkMediaObject->m_wMaxPacket = pEndpointDescriptor->wMaxPacketSize;

    DBGPRINT(3,("IsochMaxPacket = %x, bEndpointAddress = 0x%02x\n, Interface = %d\n", 
                     (ULONG)pEndpointDescriptor->wMaxPacketSize,
                     (ULONG)pEndpointDescriptor->bEndpointAddress,
                     Device->GetInterfaceNumber()
                     ));

    //
    // Set the class specific type so it can be enumerated
    //
    Device->SetClassSpecificType(
                pHawkMediaObject->m_fMicrophone ? HAWK_MICROPHONE_CLASS_SPECIFIC_TYPE : HAWK_HEADPHONE_CLASS_SPECIFIC_TYPE
                );

    Device->AddComplete(USBD_STATUS_SUCCESS);
}


EXTERNUSB VOID
HawkRemoveDevice (
    IN IUsbDevice *Device
    )
{
    XHawkMediaObject *pHawkMediaObject = (XHawkMediaObject *)Device->GetExtension();
 
    //
    //  Mark the device as not connected 
    //

    pHawkMediaObject->m_fConnected = FALSE;

    //
    //  If the device is opened, we cannot call
    //  remove complete, because there is
    //  an endpoint open (or in the process
    //  of being closed.)
    //

    //
    //  Mark the media object remove pending,
    //
    pHawkMediaObject->m_fRemovePending = TRUE;

    //
    //  If the device is ready, we should close the endpoints
    //
    if(pHawkMediaObject->m_fReady)
    {
        //
        //  If the endpoint is not in the process
        //  of being closed, then start that process
        //
        pHawkMediaObject->CloseEndpoint();
    } else
    //
    //  The media object wasn't opened, so we can
    //  call remove complete
    //
    {
        //
        //  If a close is pending then we 
        //  cannot call RemoveComplete, but
        //  when the close completes it will
        //  see the m_fRemovePending flag.
        //
        if(!pHawkMediaObject->m_fClosePending)
        {
            pHawkMediaObject->m_pDevice = NULL;
            Device->SetExtension(NULL);
            Device->RemoveComplete();
            pHawkMediaObject->m_fRemovePending = FALSE;
        }
    }
}    

//------------------------------------------------------------------------------
//  We do not include the CRT, but we need new.
//  we declare it static, so we don't conflict on linking
//  with other modules overriding new.
//------------------------------------------------------------------------------
#pragma warning(disable:4211)
static __inline void * __cdecl operator new(size_t memSize)
    {
        return ExAllocatePoolWithTag(
                            memSize,
                            'kwah'
                            );
    }
static __inline void __cdecl operator delete(void *pMemory)
    {
        ExFreePool(pMemory);
    }
#pragma warning(default:4211)


//------------------------------------------------------------------------------
// XHawkMediaObject
//------------------------------------------------------------------------------
#pragma code_seg(".XPPCINIT")
void XHawkMediaObject::InitializeClass(ULONG ulMicrophoneCount, ULONG ulHeadphoneCount)
{
    //
    //  Allocate a microphone and a headphone
    //  instance for each of the ports up front.
    //
    ULONG ulPortCount = XGetPortCount();
    ULONG ulTotalStreamingResources = ulHeadphoneCount + ulMicrophoneCount;
    PHAWK_STREAMING_RESOURCES pStreamingResourceArray;
    
    //
    //  Handle insane case, where the user requested no microphone or headphones, but
    //  some how we are here anyway.
    //
    if(ulTotalStreamingResources)
        pStreamingResourceArray = new HAWK_STREAMING_RESOURCES[ulTotalStreamingResources];

    
    sm_pHeadphones = new XHawkMediaObject[ulPortCount];
    sm_pMicrophones = new XHawkMediaObject[ulPortCount];
    sm_pRateIndices = new UCHAR[ulPortCount];
    sm_pRateIndexRefCounts = new UCHAR[ulPortCount];
    
    RIP_ON_NOT_TRUE_WITH_MESSAGE(sm_pHeadphones, "XInitDevices failed due to insufficent memory.  There is no graceful recovery.");
    RIP_ON_NOT_TRUE_WITH_MESSAGE(sm_pMicrophones, "XInitDevices failed due to insufficent memory.  There is no graceful recovery.");
    RIP_ON_NOT_TRUE_WITH_MESSAGE(sm_pRateIndices, "XInitDevices failed due to insufficent memory.  There is no graceful recovery.");
    RIP_ON_NOT_TRUE_WITH_MESSAGE(sm_pRateIndexRefCounts, "XInitDevices failed due to insufficent memory.  There is no graceful recovery.");
    RIP_ON_NOT_TRUE_WITH_MESSAGE(pStreamingResourceArray, "XInitDevices failed due to insufficent memory.  There is no graceful recovery.");
    
    //
    //  Make a free list out of pStreamingResourceArray
    //
    sm_pFreeStreamingResources = NULL;
    for(ULONG i=0; i < ulTotalStreamingResources; i++)
    {
        pStreamingResourceArray[i].pNextFree = sm_pFreeStreamingResources;
        sm_pFreeStreamingResources = &pStreamingResourceArray[i];
    }
    //
    //  Initialize the number of available microphone and headphone resources
    //
    sm_AvailableMicrophoneResources = (USHORT)ulMicrophoneCount;
    sm_AvailableHeadphoneResources = (USHORT)ulHeadphoneCount;


    //
    //  Initially none of the rates are set.
    //
    RtlZeroMemory(sm_pRateIndexRefCounts, sizeof(UCHAR)*ulPortCount);
    
    //
    //  We could loop over them and morph them into
    //  microphones and headphones, but we might
    //  as well leave that until an AddDevice.
    //

}
#pragma code_seg(".XPPCODE")

STDMETHODIMP_(ULONG) XHawkMediaObject::AddRef (void)
{
	return (ULONG) InterlockedIncrement(&m_lRefCount);
}

STDMETHODIMP_(ULONG) XHawkMediaObject::Release (void)
{
	LONG lRefCount;
	lRefCount = InterlockedDecrement(&m_lRefCount);
	//
	//	Check for decrement below zero
	//
	if(0>lRefCount)
	{
		DBGPRINT(1, ("XHawkMediaObject: Reference Count Error: Count went below 0!\n"));
		return 0;
	} else if (0==lRefCount)
	{
        FreeStreamingResources();
	}
	return (ULONG)lRefCount;
}

STDMETHODIMP
XHawkMediaObject::GetInfo (
    OUT PXMEDIAINFO pInfo
    )
{
    //
    //  If this assert fails the reference count is hosed
    //  or the caller called us after releasing the last
    //  reference.
    //
    ASSERT(m_pStreamingResources);
    
    //
    // align values to our block size. In gets CEILed, out gets FLOORed
    //
    if (m_fMicrophone) {
        pInfo->dwFlags =
	        XMO_STREAMF_FIXED_SAMPLE_SIZE |
            XMO_STREAMF_OUTPUT_ASYNC;
        pInfo->dwOutputSize = 
            m_pStreamingResources->BytesPerUSBFrame * HAWK_MIN_PACKET_FRAMES;
        pInfo->dwInputSize = 0;
    } else {
        pInfo->dwFlags =
	        XMO_STREAMF_FIXED_SAMPLE_SIZE |
            XMO_STREAMF_INPUT_ASYNC;
        pInfo->dwInputSize = 
            m_pStreamingResources->BytesPerUSBFrame * HAWK_MIN_PACKET_FRAMES;
        pInfo->dwOutputSize = 0;
    }
    
    //
    //  Look ahead is 0.
    //
    pInfo->dwMaxLookahead = 0;
    
    return S_OK;
}

STDMETHODIMP 
XHawkMediaObject::Flush (void)
{
    return S_OK;
}
        

STDMETHODIMP 
XHawkMediaObject::Discontinuity (void)
{
    return S_OK;
}
        

STDMETHODIMP
XHawkMediaObject::GetStatus (
            OUT DWORD *pdwFlags
            )
{
    RIP_ON_NOT_TRUE("XHawkMediaObject::GetStatus", NULL!=pdwFlags);

    KIRQL oldIrql = KeRaiseIrqlToDpcLevel();
    
    ASSERT(m_pStreamingResources);
    if( m_fReady && m_pStreamingResources->Free.GetHead())
    {
        if(m_fMicrophone)
        {
           *pdwFlags = XMO_STATUSF_ACCEPT_OUTPUT_DATA;
        } else
        {
           *pdwFlags = XMO_STATUSF_ACCEPT_INPUT_DATA;            
        }
    } else
    {
        *pdwFlags = 0;        
    }
    
    KeLowerIrql(oldIrql);
    
    return S_OK;
}
        
HRESULT
XHawkMediaObject::Process (
    IN LPCXMEDIAPACKET pInputPacket,
    IN LPCXMEDIAPACKET pOutputPacket
    )
{
    HRESULT hr = S_OK;
    LPCXMEDIAPACKET pMediaPacket;
    
    if(m_fMicrophone)
    {
        RIP_ON_NOT_TRUE("Hawk Microphone XMediaObject::Process", NULL==pInputPacket);
        RIP_ON_NOT_TRUE("Hawk Microphone XMediaObject::Process", NULL!=pOutputPacket);
        RIP_ON_NOT_TRUE("Hawk Microphone XMediaObject::Process", 
                         pOutputPacket->dwMaxSize > (DWORD)m_pStreamingResources->BytesPerUSBFrame * HAWK_MIN_PACKET_FRAMES);
        RIP_ON_NOT_TRUE("Hawk Microphone XMediaObject::Process", 
                         0 == pOutputPacket->dwMaxSize%HAWK_BYTES_PER_SAMPLE);
        pMediaPacket = pOutputPacket;
         
    } else 
    {
        RIP_ON_NOT_TRUE("Hawk Headphone XMediaObject::Process", NULL!=pInputPacket);
        RIP_ON_NOT_TRUE("Hawk Headphone XMediaObject::Process", NULL==pOutputPacket);
        RIP_ON_NOT_TRUE("Hawk Headphone XMediaObject::Process", 
                         pInputPacket->dwMaxSize > (DWORD)m_pStreamingResources->BytesPerUSBFrame * HAWK_MIN_PACKET_FRAMES);
        RIP_ON_NOT_TRUE("Hawk Headphone XMediaObject::Process", 
                         0 == pInputPacket->dwMaxSize%HAWK_BYTES_PER_SAMPLE);
        pMediaPacket = pInputPacket;
    }

    // Raise IRQL after rips
    KIRQL oldIrql = KeRaiseIrqlToDpcLevel();
    if(m_fReady)
    {

        PHAWK_PACKET_CONTEXT pPacketContext = m_pStreamingResources->Free.RemoveHead();    
        if(pPacketContext)
        {

            //
            //  Initialize the packet
            //
            
            XMOAcceptPacket(pMediaPacket);

            //
            //  Copy the media packet into the hawk packet context.
            //  The structure is identical except hawk has one extra
            //  field, for queuing
            //
            
            RtlCopyMemory(&pPacketContext->MediaPacket, pMediaPacket, sizeof(XMEDIAPACKET));
                
            //
            //  Insert the packet onto the pending programming queue.
            //

            m_pStreamingResources->PendingProgram.InsertTail(pPacketContext);
                
            //
            //  Jog the programming routine.
            //

            ProgramTransfer();
            
        } else
        {
            hr = HRESULT_FROM_WIN32(ERROR_BUSY);
        }
    } else
    {
        hr = HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED);
    }

    if(FAILED(hr))
    {
        XMOCompletePacket(pMediaPacket, 0, m_pfnCallback, m_pvContext, XMEDIAPACKET_STATUS_FAILURE);
    }

    KeLowerIrql(oldIrql);
    return hr;

}

EXTERN_C HRESULT WINAPI
XVoiceCreateMediaObjectInternal(
      IN PXPP_DEVICE_TYPE XppDeviceType,
      IN DWORD dwPort,
      IN DWORD dwMaxAttachedPackets,
      IN LPWAVEFORMATEX pwfxFormat,
      IN PFNXMEDIAOBJECTCALLBACK    pfnCallback,
      IN PVOID                      pvContext,
      OUT XMediaObject **ppXmediaObject
      )
{
    HRESULT hr;
    XHawkMediaObject *pHawkMediaObject = NULL;

    hr = XVoiceCreateMediaObject(
          XppDeviceType,
          dwPort,
          dwMaxAttachedPackets,
          pwfxFormat,
          ppXmediaObject
          );

    if (SUCCEEDED(hr)) {

        //
        // save callback and context
        //

        pHawkMediaObject = (XHawkMediaObject *) *ppXmediaObject;
        pHawkMediaObject->m_pvContext = pvContext;
        pHawkMediaObject->m_pfnCallback = pfnCallback;

    }


    return hr;

}


XBOXAPI EXTERN_C HRESULT WINAPI
XVoiceCreateMediaObject(
      IN PXPP_DEVICE_TYPE XppDeviceType,
      IN DWORD dwPort,
      IN DWORD dwMaxAttachedPackets,
      IN PWAVEFORMATEX  pwfxFormat  OPTIONAL,
      OUT XMediaObject **ppXmediaObject
      )
/*++
    Routine Description:
        This is the factory method for XHawkMediaObjects.
        It is an exported API for games.
--*/
{
    //
    //  We need the class Id to find the device.
    //
	
    KIRQL		     oldIrql;
    PNP_CLASS_ID     classId;
    HRESULT          hr = S_OK;
    XHawkMediaObject *pHawkMediaObject = NULL;
    UCHAR            ucRateIndex=2; //default rate is 16 kHz, at index 2 in the rate table.
    
    //
    //  Verify that XInitDevices has been called.
    //
    RIP_ON_NOT_TRUE_WITH_MESSAGE(XPP_XInitDevicesHasBeenCalled, "XVoiceCreateMediaObject: XInitDevices must be called first!");

    //
    //  Verify port
    //
    RIP_ON_NOT_TRUE(XVoiceCreateMediaObject, dwPort<XGetPortCount());

    //
    //  Verify that a reasonable dwMaxAttachedPackets was passed.
    //
    RIP_ON_NOT_TRUE(XVoiceCreateMediaObject, (dwMaxAttachedPackets > 1));

    //
    //  Use raised Irql to synchronize the allocation
    //  and opening of instances
    //

    oldIrql = KeRaiseIrqlToDpcLevel();

    //
    //  Claim an existing instance of XHawkMediaObject
    //
	
    if(XDEVICE_TYPE_VOICE_MICROPHONE == XppDeviceType)
	{
        if(0==XHawkMediaObject::sm_AvailableMicrophoneResources)
        {
            KeLowerIrql(oldIrql);
            RIP("XVoiceCreateMediaObject: attempt to exceed number of mircophones requested in XInitDevices.\n");
            return E_OUTOFMEMORY;
        }
        pHawkMediaObject = &XHawkMediaObject::sm_pMicrophones[dwPort];
    } else if(XDEVICE_TYPE_VOICE_HEADPHONE == XppDeviceType)
    {
        if(0==XHawkMediaObject::sm_AvailableHeadphoneResources)
        {
            KeLowerIrql(oldIrql);
            RIP("XVoiceCreateMediaObject: attempt to exceed number of headphones requested in XInitDevices.\n");
            return E_OUTOFMEMORY;
        }
        pHawkMediaObject = &XHawkMediaObject::sm_pHeadphones[dwPort];
    }
#if DBG    
    else {
        KeLowerIrql(oldIrql);
        RIP("XVoiceCreateMediaObject: XppDeviceType is not a valid type for XVoiceCreateMediaObject\n");
	}
#endif //DBG

    //
    //  Check the wave format
    //
    if(pwfxFormat)
    {
        //
        //  Verify that the setting are supported
        //
#if DBG
        KeLowerIrql(oldIrql);
        RIP_ON_NOT_TRUE(XVoiceCreateMediaObject, pwfxFormat->wFormatTag==WAVE_FORMAT_PCM);
        RIP_ON_NOT_TRUE(XVoiceCreateMediaObject, pwfxFormat->nChannels==1);
        RIP_ON_NOT_TRUE(XVoiceCreateMediaObject, 2*pwfxFormat->nSamplesPerSec==pwfxFormat->nAvgBytesPerSec);
        RIP_ON_NOT_TRUE(XVoiceCreateMediaObject, pwfxFormat->wBitsPerSample==16);
        RIP_ON_NOT_TRUE(XVoiceCreateMediaObject, pwfxFormat->nBlockAlign == 2);
        oldIrql = KeRaiseIrqlToDpcLevel();
#endif

        //
        //  First validate rate against allowed rates.
        //
        for(ucRateIndex=0; ucRateIndex < HAWK_SAMPLE_RATE_COUNT; ucRateIndex++)
        {
            if(pwfxFormat->nSamplesPerSec == (DWORD)XHawkMediaObject::sm_HawkSampleRates[ucRateIndex].nSamplePerSecond)
            {
                break;
            }
        }
#if DBG
        if(HAWK_SAMPLE_RATE_COUNT == ucRateIndex)
        {
            KeLowerIrql(oldIrql);
            RIP("XVoiceCreateMediaObject: pWaveFormatEx->nSamplesPerSec has illegal value");
        }
#endif //DBG
    }

    if( pHawkMediaObject->m_fOpened )
    {
        pHawkMediaObject = NULL;
        hr = HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION);
    } else if (pHawkMediaObject->m_fConnected)
    {
        //
        //  Set the sample rate
        //
        if(XHawkMediaObject::sm_pRateIndexRefCounts[dwPort])
        //
        //  If the reference count is non-zero, then the
        //  other half of this device has set the rate
        //  (microphone and headphone must have the same rate)
        //
        {
            XHawkMediaObject::sm_pRateIndexRefCounts[dwPort]++;
            //
            //  Verify that the rates match
            //
            if(XHawkMediaObject::sm_pRateIndices[dwPort]!=ucRateIndex)
            {
                // Be kinder here, return an error, I could vaguely see this slipping through testing.
                KeLowerIrql(oldIrql);
                RIP("XVoiceCreateMediaObject: Attempt to open microphone and headphone of same device with different sampling rates.");
                return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
            }
        } else
        //
        //  The rate hasn't been set yet, so set it
        //
        {
            XHawkMediaObject::sm_pRateIndexRefCounts[dwPort] = 1;
            XHawkMediaObject::sm_pRateIndices[dwPort]=ucRateIndex;
            hr = pHawkMediaObject->SetSampleRate(ucRateIndex, oldIrql);
			hr = pHawkMediaObject->SetAGC(HAWK_AGC_ON, oldIrql);
        }

        //
        //  Allocate the streaming resources.
        //
        if(SUCCEEDED(hr))
        {
            hr = pHawkMediaObject->AllocateStreamingResources(dwMaxAttachedPackets, ucRateIndex);
        }
        if(SUCCEEDED(hr))
        {
            pHawkMediaObject->m_fOpened = TRUE;
            pHawkMediaObject->m_fReady = TRUE;
            pHawkMediaObject->m_lRefCount = 1;
        } else
        {
           pHawkMediaObject = NULL;
           XHawkMediaObject::sm_pRateIndexRefCounts[dwPort]--;
        }
    } else
    {
        pHawkMediaObject = NULL;
        hr = HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED);
    }

    // Record that we opened a device
    if(pHawkMediaObject)
    {
        if(pHawkMediaObject->m_fMicrophone) XHawkMediaObject::sm_AvailableMicrophoneResources--;
        else XHawkMediaObject::sm_AvailableHeadphoneResources--;
    }

    //
    //  Restore Irql since the stuff that needs
    //  to be synchronous is done.
    //
    KeLowerIrql(oldIrql);
    
    *ppXmediaObject = pHawkMediaObject;	
    return hr;
}
            
HRESULT XHawkMediaObject::SetSampleRate(UCHAR ucRateIndex, KIRQL BaseIrql)
/*++
    Routine Description:
        Sends a command to hawk to change the sample rate.  This routine opens the
        default pipe. Sends a vendor specific SetFeature to change the rate.  The closes
        the default pipe.  The routine is synchronous, which means it must drop to below
        DISPATCH_LEVEL.
    Parameters:
        ucRateIndex - rate index to send to the device.
        BaseIrql - the lowest irql we are permitted to drop to.
    Comments:
        Must be called at DPC level, BaseIrql must be lower than DPC.
--*/
{
    USBD_STATUS usbdStatus;
    
    //
    //  Open the default endpoints
    //
    URB Urb;
    USB_BUILD_OPEN_DEFAULT_ENDPOINT(&Urb.OpenEndpoint);
    usbdStatus = m_pDevice->SubmitRequest(&Urb);
    if(USBD_ERROR(usbdStatus))
    {
        return HRESULT_FROM_WIN32(IUsbDevice::Win32FromUsbdStatus(usbdStatus));
    }

    //
    //  While still at DPC, set the m_fClosePending flag.  This will prevent HawkRemoveDevice
    //  from calling RemoveComplete on us.  We take on the burden of checking for m_fRemovePending
    //  when clearing this flag.
    //

    m_fClosePending = TRUE;   
    //
    //  Drop to BaseIrql so we can start doing the asynchronous parts synchronously.
    //

    ASSERT(BaseIrql < DISPATCH_LEVEL);
    KeLowerIrql(BaseIrql);

    //
    //  Send the SET_FEATURE(SampleRate) command.
    //

    USB_BUILD_CONTROL_TRANSFER(
        &Urb.ControlTransfer,
        NULL,   //Use the default endpoint
        NULL,   //No transfer buffer
        0,      
        0,
        NULL,   //Do this transfer synchronously
        0,      
        FALSE,  //Short Transfer NOT OK
        USB_VENDOR_COMMAND | USB_COMMAND_TO_INTERFACE | USB_HOST_TO_DEVICE,
        USB_REQUEST_SET_FEATURE,
        (USHORT) 0x0100 | ucRateIndex, // DD: ucRateIndex, //Rate Index to set, see Talon Specification.
        HAWK_VENDOR_FEATURE_SAMPLE_RATE,
        0); //Transfer length is zero

    usbdStatus = m_pDevice->SubmitRequest(&Urb);

    //
    //  Close the default endpoint
    //
    USB_BUILD_CLOSE_DEFAULT_ENDPOINT(&Urb.CloseEndpoint, NULL, NULL);
    m_pDevice->SubmitRequest(&Urb);

    //
    //  Now all the synchronous stuff is done we can
    //  go back up to DISPATCH_LEVEL
    //

    KeRaiseIrqlToDpcLevel();

    //
    //  We are not really open yet, nor are we pending a close.
    //

    m_fOpened = FALSE; 
    m_fClosePending = FALSE; 

    //
    //  Check the remove pending flag, and complete the remove
    //  it one was pending.
    //  Also fail this request with ERROR_DEVICE_NOT_CONNECTED.
    //
    if(m_fRemovePending)
    {
        m_pDevice->SetExtension(NULL);
        m_pDevice->RemoveComplete();
        m_pDevice = NULL;
        m_fRemovePending = FALSE;
        return HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED);
    }
    
    //
    //  If we are supporting fixed rate devices
    //  tolerate a failure, if the
    //  requested rate was 16 ksamples/second
    #ifdef HAWK_SUPPORT_FIXED_RATE
    if(ucRateIndex==2)
    {
        usbdStatus = USBD_STATUS_SUCCESS;
    }
    #endif //HAWK_SUPPORT_FIXED_RATE

    //
    //  Return the status of the request to set the data rate.  
    //

    return HRESULT_FROM_WIN32(IUsbDevice::Win32FromUsbdStatus(usbdStatus));
}

HRESULT XHawkMediaObject::SetAGC(UCHAR ucAGC, KIRQL BaseIrql)
/*++
    Routine Description:
        Sends a command to hawk to change the sample rate.  This routine opens the
        default pipe. Sends a vendor specific SetFeature to change the rate.  The closes
        the default pipe.  The routine is synchronous, which means it must drop to below
        DISPATCH_LEVEL.
    Parameters:
        ucAGC - rate index to send to the device.
        BaseIrql - the lowest irql we are permitted to drop to.
    Comments:
        Must be called at DPC level, BaseIrql must be lower than DPC.
--*/
{
    USBD_STATUS usbdStatus;
    
	//
    //  Open the default endpoints
    //
    URB Urb;
    USB_BUILD_OPEN_DEFAULT_ENDPOINT(&Urb.OpenEndpoint);
    usbdStatus = m_pDevice->SubmitRequest(&Urb);
    if(USBD_ERROR(usbdStatus))
    {
        return HRESULT_FROM_WIN32(IUsbDevice::Win32FromUsbdStatus(usbdStatus));
    }

    //
    //  While still at DPC, set the m_fClosePending flag.  This will prevent HawkRemoveDevice
    //  from calling RemoveComplete on us.  We take on the burden of checking for m_fRemovePending
    //  when clearing this flag.
    //

    m_fClosePending = TRUE;   
    //
    //  Drop to BaseIrql so we can start doing the asynchronous parts synchronously.
    //

    ASSERT(BaseIrql < DISPATCH_LEVEL);
    KeLowerIrql(BaseIrql);

    //
    //  Send the SET_FEATURE(SampleRate) command.
    //

    USB_BUILD_CONTROL_TRANSFER(
        &Urb.ControlTransfer,
        NULL,   //Use the default endpoint
        NULL,   //No transfer buffer
        0,      
        0,
        NULL,   //Do this transfer synchronously
        0,      
        FALSE,  //Short Transfer NOT OK
        USB_VENDOR_COMMAND | USB_COMMAND_TO_INTERFACE | USB_HOST_TO_DEVICE,
        USB_REQUEST_SET_FEATURE,
        (USHORT)ucAGC, //ON/OFF, see Talon Specification.
        (USHORT)HAWK_VENDOR_FEATURE_AGC, 
        0); //Transfer length is zero

    usbdStatus = m_pDevice->SubmitRequest(&Urb);

    //
    //  Close the default endpoint
    //
    USB_BUILD_CLOSE_DEFAULT_ENDPOINT(&Urb.CloseEndpoint, NULL, NULL);
    m_pDevice->SubmitRequest(&Urb);

    //
    //  Now all the synchronous stuff is done we can
    //  go back up to DISPATCH_LEVEL
    //

    KeRaiseIrqlToDpcLevel();

    //
    //  We are not really open yet, nor are we pending a close.
    //

    m_fOpened = FALSE; 
    m_fClosePending = FALSE; 

    //
    //  Check the remove pending flag, and complete the remove
    //  it one was pending.
    //  Also fail this request with ERROR_DEVICE_NOT_CONNECTED.
    //
    if(m_fRemovePending)
    {
        m_pDevice->SetExtension(NULL);
        m_pDevice->RemoveComplete();
        m_pDevice = NULL;
        m_fRemovePending = FALSE;
        return HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED);
    }
    
    //
    //  Return the status of the request to set the data rate.  
    //
    return HRESULT_FROM_WIN32(IUsbDevice::Win32FromUsbdStatus(usbdStatus));
}


HRESULT XHawkMediaObject::AllocateStreamingResources(ULONG ulMaxAttachedPackets, ULONG ulRateIndex)
/*++
    Routine Description:
        This is a helper function called by Factory Method,
        XVoiceCreateMediaObject, to allocate resources for
        providing streaming.
--*/
{
    ULONG i;
    
    //
    //  Allocate memory for Attached Packets
    //
    PHAWK_PACKET_CONTEXT pPacketContextArray  = new HAWK_PACKET_CONTEXT[ulMaxAttachedPackets];
    if(!pPacketContextArray) return E_OUTOFMEMORY;
    RtlZeroMemory(pPacketContextArray, sizeof(HAWK_PACKET_CONTEXT)*ulMaxAttachedPackets);

    //
    //  Grab a free streaming resources  (there has to be one
    //  at this point or there is a bug somewhere in this driver.)
    //
    PHAWK_STREAMING_RESOURCES pResources = sm_pFreeStreamingResources;
    ASSERT(pResources);
    sm_pFreeStreamingResources = pResources->pNextFree;
    RtlZeroMemory(pResources, sizeof(HAWK_STREAMING_RESOURCES));
    
    //
    // Populate the free list of packet contexts
    //
    pResources->m_pContextArray = pPacketContextArray;
    for(i=0; i < ulMaxAttachedPackets; i++)
    {
        pResources->Free.InsertTail(pPacketContextArray + i);
    }

    //
    //  Copy the sample rate parameters from the rate table into the
    //  pResources structure for quick access.
    //

    pResources->BytesPerUSBFrame = sm_HawkSampleRates[ulRateIndex].BytesPerUSBFrame;
    pResources->ExtraSampleInterval = sm_HawkSampleRates[ulRateIndex].ExtraSampleInterval;
    pResources->AvailableTransferContexts = HAWK_TRANSFER_CONTEXT_BOTH;
    //pResources->ProgrammedFrame = 0; //Not necessary since the memory was zeroed.
    pResources->TransferContext[0].pHawkMediaObject =
    pResources->TransferContext[1].pHawkMediaObject = this;
    pResources->TransferContext[0].ucContextNumber = HAWK_TRANSFER_CONTEXT_0;
    pResources->TransferContext[1].ucContextNumber = HAWK_TRANSFER_CONTEXT_1;

    //
    //  Open the isoch endpoint
    //

    URB_ISOCH_OPEN_ENDPOINT OpenUrb;
    USB_BUILD_ISOCH_OPEN_ENDPOINT(
                &OpenUrb,
                m_bEndpointAddress,
                m_wMaxPacket,
                0);
    USBD_STATUS status = m_pDevice->SubmitRequest((PURB)&OpenUrb);
    
    if(USBD_ERROR(status))
    {
        delete [] pPacketContextArray;
        pResources->pNextFree = sm_pFreeStreamingResources;
        sm_pFreeStreamingResources = pResources;
        return HRESULT_FROM_WIN32(IUsbDevice::Win32FromUsbdStatus(status));
    }
    pResources->EndpointHandle = OpenUrb.EndpointHandle;

    //
    //  start the stream (since we don't have a start API)
    //

    URB_ISOCH_START_TRANSFER StartTransferUrb;
    USB_BUILD_ISOCH_START_TRANSFER(
        &StartTransferUrb,
        pResources->EndpointHandle,
        0,
        URB_FLAG_ISOCH_START_ASAP
        );
    status = m_pDevice->SubmitRequest((PURB)&StartTransferUrb);
    ASSERT(USBD_SUCCESS(status));
        
    //
    //  The stream should be ready to go, we just need to
    //  attach transfers.
    //

    m_pStreamingResources = pResources;

    return S_OK;
}

void XHawkMediaObject::CloseEndpoint( void )
{
    ASSERT(m_pStreamingResources);
    ASSERT(m_pStreamingResources->EndpointHandle);
    
    //
    //  The device is not ready, if the endpoint is not open.
    //

    m_fReady = FALSE;
    
    if(m_fClosingEndpoint) return;
    m_fClosingEndpoint = TRUE;

    //
    //  Build and submit the close URB for the Isoch endpoint
    //

    USB_BUILD_ISOCH_CLOSE_ENDPOINT(
            &m_pStreamingResources->CloseUrb,
            m_pStreamingResources->EndpointHandle,
            (PURB_COMPLETE_PROC)&XHawkMediaObject::CloseEndpointComplete,
            (PVOID)this
            );
    m_pDevice->SubmitRequest((PURB)&m_pStreamingResources->CloseUrb);
}

void XHawkMediaObject::CloseEndpointComplete (PURB pUrb, XHawkMediaObject *pThis)
/*++
    Routine Description:
        This is the call back when an isoch endpoint is closed.
        This could happen if a close is pending, or if 
        a remove is pending, or both.

--*/
{
    DWORD dwPort;
    ASSERT(USBD_SUCCESS(pUrb->Header.Status));

    //
    //  Now mark the endpoint closed.
    //
    pThis->m_pStreamingResources->EndpointHandle = NULL;

    //
    //  Decrement the reference count on the rate, for
    //  which we need the port index.
    //

    if(pThis->m_fMicrophone)
    {
        dwPort = (DWORD)(pThis - XHawkMediaObject::sm_pMicrophones);
    } else
    {
        dwPort = (DWORD)(pThis - XHawkMediaObject::sm_pHeadphones);
    }
    XHawkMediaObject::sm_pRateIndexRefCounts[dwPort]--;
    
    
    //
    //  Abort outstanding packets.
    //
    pThis->AbortMediaPackets();

    //
    //  Deal with the remove pending case.
    //

    if(pThis->m_fRemovePending)
    {
        pThis->m_pDevice->SetExtension(NULL);
        pThis->m_pDevice->RemoveComplete();
        pThis->m_pDevice = NULL;
        pThis->m_fRemovePending = FALSE;
    }

    //
    //  If this is the close pending case,
    //  then there is an event that we need to set
    //
    if(pThis->m_fClosePending)
    {
        //
        //  We have to clear the m_fClosePending
        //  and m_fOpened flags while still at
        //  DPC for synchronization purposes.
        //

        pThis->m_fClosePending = FALSE;
        pThis->m_fOpened = FALSE;

        //
        //  Signal the close event so that
        //  FreeStreamingResources can continue
        //  to clean up.
        //

        KeSetEvent(
            &pThis->m_pStreamingResources->CloseEvent,
            IO_NO_INCREMENT,
            FALSE
            );
    }
    pThis->m_fClosingEndpoint = FALSE;
}

void XHawkMediaObject::FreeStreamingResources (void)
{
    KIRQL oldIrql = KeRaiseIrqlToDpcLevel();

    if(m_fConnected && m_pStreamingResources->EndpointHandle)
    {

        //
        //  Mark that this object is pending close
        //  and initialize the close event.
        //

        m_fClosePending = TRUE;
        KeInitializeEvent(
            &m_pStreamingResources->CloseEvent,
            SynchronizationEvent,
            FALSE);

        //
        //  If it is already pending remove
        //  then we can just wait for
        //  the endpoint to close, other
        //  wise we must initiate closing
        //   it.
        //
        
        if(!m_fRemovePending)
        {
            CloseEndpoint();
        }

        //
        //  Lower Irql and let the endpoint close.
        //

        KeLowerIrql(oldIrql);
        
        //
        // The endpoint close is under way,
        // wait for it to complete.
        //

        KeWaitForSingleObject (
            &m_pStreamingResources->CloseEvent,
            Executive,
            KernelMode,
            FALSE,
            NULL
            );

    } else
    {
        m_fOpened = FALSE;
        KeLowerIrql(oldIrql);
    }
    
    if(m_fMicrophone) sm_AvailableMicrophoneResources++;
    else sm_AvailableHeadphoneResources++;

    //
    //  Now we can cleanup what is left of the streaming resources
    //
    delete [] m_pStreamingResources->m_pContextArray;
    m_pStreamingResources->pNextFree = sm_pFreeStreamingResources;
    sm_pFreeStreamingResources = m_pStreamingResources;
    m_pStreamingResources = NULL;
    
}

void XHawkMediaObject::ProgramTransfer()
/*++
    Routine Description:
        This routine translates a portion of a packet into USB Isochronous
        Transfer Requests.

    Note: Sometimes this routine in called at PASSIVE_LEVEL and sometimes
    at DISPATCH_LEVEL.  This routine is not inherently reentrant so we raise
    the IRQL to DISPATCH_LEVEL all the time.
--*/
{
    KIRQL oldIrql = KeRaiseIrqlToDpcLevel();   
    USBD_ISOCH_BUFFER_DESCRIPTOR isochBufferDescriptor;
    PHAWK_TRANSFER_CONTEXT pTransferContext = NULL;
    int i;

    //
    //  Claim a context
    //
    if(TEST_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_0))
    {
        CLEAR_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_0);
        pTransferContext = &m_pStreamingResources->TransferContext[0];
    } else if(TEST_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_1))
    {
        CLEAR_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_1);
        pTransferContext = &m_pStreamingResources->TransferContext[1];
    }

    isochBufferDescriptor.TransferComplete = (PFNUSBD_ISOCH_TRANSFER_COMPLETE)XHawkMediaObject::TransferComplete;

    while(pTransferContext)
    {
        PHAWK_PACKET_CONTEXT pPacketContext = m_pStreamingResources->PendingProgram.GetHead();
        
        //
        //  If there are no more packet contexts pending programming, break the
        //  loop, we are done.
        //

        if(!pPacketContext)
        {
            SET_FLAG(m_pStreamingResources->AvailableTransferContexts, pTransferContext->ucContextNumber);
            break;
        }

        //
        //  Fill out the transfer context
        //

        pTransferContext->pPacketContext = pPacketContext;
        pTransferContext->pucTransferBuffer = (PUCHAR) pPacketContext->MediaPacket.pvBuffer;
        pTransferContext->ulTransferBytes = 0;
        pTransferContext->cFirstFrame = m_pStreamingResources->ProgrammedFrame;
        pTransferContext->fWrapBuffer = FALSE;
        pTransferContext->fLastTransfer = FALSE;

        //
        //  Walk through each frame, up to HAWK_USB_MAX_FRAMES,
        //

        for(i=0; i < HAWK_USB_MAX_FRAMES; i++)
        {   
            isochBufferDescriptor.Pattern[i] = m_pStreamingResources->BytesPerUSBFrame;
            if(
                m_pStreamingResources->ExtraSampleInterval && 
                (++m_pStreamingResources->ProgrammedFrame == m_pStreamingResources->ExtraSampleInterval)
            )
            {
                isochBufferDescriptor.Pattern[i] += 2;
                m_pStreamingResources->ProgrammedFrame=0;
            }
            pTransferContext->ulTransferBytes += isochBufferDescriptor.Pattern[i];
            
            if(pTransferContext->ulTransferBytes > pPacketContext->MediaPacket.dwMaxSize)
            {
                m_pStreamingResources->WrapBuffer.ulSecondPacketBytes = 
                        pTransferContext->ulTransferBytes - pPacketContext->MediaPacket.dwMaxSize;
                pTransferContext->ulTransferBytes = pPacketContext->MediaPacket.dwMaxSize;
                pTransferContext->fWrapBuffer = TRUE;
                //
                //  If this is for a headphones copy the output bytes
                //  for this packet.
                //
                if(!m_fMicrophone)
                {
                    RtlCopyMemory(
                        m_pStreamingResources->WrapBuffer.Buffer,
                        pPacketContext->MediaPacket.pvBuffer,
                        pTransferContext->ulTransferBytes);
                }
            }
            if(pTransferContext->ulTransferBytes == pPacketContext->MediaPacket.dwMaxSize)
            {
                pTransferContext->fLastTransfer = TRUE;
                i++;
                break;
            }
        }
        ASSERT(i==8 || pTransferContext->fLastTransfer);
        pPacketContext->MediaPacket.dwMaxSize -= pTransferContext->ulTransferBytes;
        isochBufferDescriptor.Context = pTransferContext;
        isochBufferDescriptor.FrameCount = i;
        isochBufferDescriptor.TransferBuffer = pPacketContext->MediaPacket.pvBuffer;
        pPacketContext->MediaPacket.pvBuffer = ((PUCHAR)pPacketContext->MediaPacket.pvBuffer) + pTransferContext->ulTransferBytes;

        //
        //  If we are done with the current packet, then pop it off the pending program queue.
        //
        if(pTransferContext->fLastTransfer)
        {
            m_pStreamingResources->PendingProgram.RemoveHead();
        }

        //
        //  If the wrap buffer is in use than we need to get the next
        //  packet and start programming it in the beginning of the wrap buffer.
        //
        if(pTransferContext->fWrapBuffer)
        {
            DBGPRINT(1,("W"));
            PHAWK_WRAP_BUFFER pWrapBuffer = &m_pStreamingResources->WrapBuffer;
            isochBufferDescriptor.TransferBuffer = pWrapBuffer->Buffer;
            pPacketContext = m_pStreamingResources->PendingProgram.GetHead();
            if(pPacketContext)
            {
                pWrapBuffer->pSecondPacket = pPacketContext;
                pWrapBuffer->pucTransferBuffer = (PUCHAR)pPacketContext->MediaPacket.pvBuffer;
                ASSERT(pWrapBuffer->ulSecondPacketBytes <= pPacketContext->MediaPacket.dwMaxSize);
                if(!m_fMicrophone)
                {
                    RtlCopyMemory(
                        pWrapBuffer->Buffer+pTransferContext->ulTransferBytes,
                        pWrapBuffer->pucTransferBuffer,
                        pWrapBuffer->ulSecondPacketBytes);
                }
                pPacketContext->MediaPacket.pvBuffer = pWrapBuffer->pucTransferBuffer + pWrapBuffer->ulSecondPacketBytes;
                pPacketContext->MediaPacket.dwMaxSize -= pWrapBuffer->ulSecondPacketBytes; 
            } else
            //
            //  No more packets pending
            //
            {
                pWrapBuffer->pSecondPacket = NULL;
                pWrapBuffer->pucTransferBuffer = NULL;
                if(!m_fMicrophone)
                {
                    RtlZeroMemory(
                        pWrapBuffer->Buffer+pTransferContext->ulTransferBytes,
                        pWrapBuffer->ulSecondPacketBytes);
                }
            }
        }

        //
        //  Build and submit a USB request
        //
        
        URB_ISOCH_ATTACH_BUFFER Urb;
        USB_BUILD_ISOCH_ATTACH_BUFFER(
            &Urb,
            m_pStreamingResources->EndpointHandle,
            USBD_DELAY_INTERRUPT_0_MS,
            &isochBufferDescriptor
            );
        
        USBD_STATUS usbdStatus = m_pDevice->SubmitRequest((PURB)&Urb);
        ASSERT(USBD_SUCCESS(usbdStatus));
        
        //
        //  Try to claim another context
        //  
        if(TEST_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_0))
        {
            CLEAR_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_0);
            pTransferContext = &m_pStreamingResources->TransferContext[0];
        } else if(TEST_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_1))
        {
            CLEAR_FLAG(m_pStreamingResources->AvailableTransferContexts, HAWK_TRANSFER_CONTEXT_1);
            pTransferContext = &m_pStreamingResources->TransferContext[1];
        } else
        {   //
            //  no more contexts
            //
            pTransferContext = NULL;
        }
    }

    KeLowerIrql(oldIrql);
}


void XHawkMediaObject::TransferComplete(PUSBD_ISOCH_TRANSFER_STATUS Status,  PVOID pvContext)
/*++
    Routine Description:
        This is the completion routine whenever an isoch transfer completes.
        It continues to program the current media packet, releases the media
        packet when it is complete, and moves on to the next packet.
--*/
{
    PHAWK_TRANSFER_CONTEXT pTransferContext = (PHAWK_TRANSFER_CONTEXT)pvContext;
    XHawkMediaObject *pThis = pTransferContext->pHawkMediaObject;
    PHAWK_STREAMING_RESOURCES pStreamingResources = pThis->m_pStreamingResources;
    PHAWK_PACKET_CONTEXT pPacketContext = pTransferContext->pPacketContext;
    CHAR extraSampleClock = pThis->m_pStreamingResources->ExtraSampleInterval;
    if(extraSampleClock) extraSampleClock--;
   
    //
    //  Post processing of data is only useful\needed for microphones.
    //
    if(pThis->m_fMicrophone)
    {
        ULONG bytesThisFrame;
        PUSHORT pBufferPosition;
        if(pTransferContext->fWrapBuffer)
        {
            pBufferPosition = (PUSHORT)pStreamingResources->WrapBuffer.Buffer;
        } else
        {
            pBufferPosition = (PUSHORT)pTransferContext->pucTransferBuffer;
        }
        //
        //  First handle overflow and underflow conditions
        //
        
        for(ULONG ulFrameIndex = 0; ulFrameIndex < Status->FrameCount; ulFrameIndex++)
        {
            bytesThisFrame = pStreamingResources->BytesPerUSBFrame;
            if(
                extraSampleClock &&
                (ulFrameIndex + pTransferContext->cFirstFrame == (ULONG)extraSampleClock)
            )
            {
                bytesThisFrame += 2;
            }
            //
            //  Fill in short frames with data copied from the previous samples(s)
            //
            if(USBD_ISOCH_STATUS_DATA_UNDERRUN == Status->PacketStatus[ulFrameIndex].ConditionCode)
            {
                //Copy bytes from the previous sample
                if(Status->PacketStatus[ulFrameIndex].BytesRead)
                {
                    #if DBG
                    if(0!=Status->PacketStatus[ulFrameIndex].BytesRead%2)
                    {
                        DBGPRINT(1,("Hardware violated sample boundary"));
                    }
                    #endif
                    ULONG samplesToDuplicate = (bytesThisFrame - Status->PacketStatus[ulFrameIndex].BytesRead) >> 1;
                    pBufferPosition = (PUSHORT)(((ULONG_PTR)pBufferPosition) + Status->PacketStatus[ulFrameIndex].BytesRead-2);
                    USHORT sample = *pBufferPosition++;
                    while(samplesToDuplicate--)
                    {
                        *pBufferPosition++ = sample;
                    }
                } else
                //
                //  This handles devices that mute by sending zero length data packets.
                //
                {
                    RtlZeroMemory(pBufferPosition,bytesThisFrame);
                    pBufferPosition  = (PUSHORT)((ULONG_PTR)pBufferPosition + bytesThisFrame); 
                }
            } else
            {
                pBufferPosition  = (PUSHORT)((ULONG_PTR)pBufferPosition + bytesThisFrame); 
            }
            //
            //  Check for data overrun to try to adapt to 
            //  small clock differences
            //
            if(
                extraSampleClock &&
                (USBD_ISOCH_STATUS_DATA_OVERRUN == Status->PacketStatus[ulFrameIndex].ConditionCode)
            )
            {
                CHAR clockDifference = extraSampleClock - (pTransferContext->cFirstFrame+(CHAR)ulFrameIndex);
                pStreamingResources->ProgrammedFrame += clockDifference;
            }
        }
        //
        //  If the wrap buffer was in use, then we need to copy the bytes back to their
        //  original buffer
        //
        if(pTransferContext->fWrapBuffer)
        {
            //
            //  First copy the part for the first packet
            //
            RtlCopyMemory(
                pTransferContext->pucTransferBuffer,
                pStreamingResources->WrapBuffer.Buffer, 
                pTransferContext->ulTransferBytes
                );
            //
            //  Then copy the part for the second packet
            //
            if(pStreamingResources->WrapBuffer.pSecondPacket)
            {
                RtlCopyMemory(
                    pStreamingResources->WrapBuffer.pucTransferBuffer,
                    pStreamingResources->WrapBuffer.Buffer + pTransferContext->ulTransferBytes,
                    pStreamingResources->WrapBuffer.ulSecondPacketBytes
                    );
            }
        }
    } else
    {
        #if DBG
        for(ULONG ulFrameIndex = 0; ulFrameIndex < Status->FrameCount; ulFrameIndex++)
        {
            if(Status->PacketStatus[ulFrameIndex].ConditionCode!=USBD_STATUS_SUCCESS)
            {
                DBGPRINT(1,("Failed Write: Frame=%d, CC=0x%0.1x, BytesWritten=0x%0.3x", 
                        ulFrameIndex,
                        (DWORD)Status->PacketStatus[ulFrameIndex].ConditionCode,
                        (DWORD)Status->PacketStatus[ulFrameIndex].BytesRead
                        ));
            }
        }
        #endif
    }
    
    //
    //  Post processing of data is complete, update completed size
    // 
    
    if(pPacketContext->MediaPacket.pdwCompletedSize)
        *pPacketContext->MediaPacket.pdwCompletedSize += pTransferContext->ulTransferBytes;
    if( pTransferContext->fWrapBuffer&&
        pStreamingResources->WrapBuffer.pSecondPacket&&
        pStreamingResources->WrapBuffer.pSecondPacket->MediaPacket.pdwCompletedSize
    )
    {
        *pStreamingResources->WrapBuffer.pSecondPacket->MediaPacket.pdwCompletedSize += 
            pStreamingResources->WrapBuffer.ulSecondPacketBytes;
    }
    //
    //  Mark the pTransferContext as unused
    //
    BOOL fLastTransfer = pTransferContext->fLastTransfer;
    SET_FLAG(
        pStreamingResources->AvailableTransferContexts,
        pTransferContext->ucContextNumber
        );

    //
    //  Complete packet if necessary
    //
    if(fLastTransfer)
    {
        //
        //  Cache the media packet info on the stack
        //  so we can free the packet context
        //  before calling XMOCompletePacket.
        //
        XMEDIAPACKET mediaPacket;
        DWORD       dwCompletedSize = 0;
        RtlCopyMemory(&mediaPacket, &pPacketContext->MediaPacket, sizeof(XMEDIAPACKET));
        if(mediaPacket.pdwCompletedSize)
            dwCompletedSize = *mediaPacket.pdwCompletedSize;
        

        //
        //  Place the packet back on the free list  (do this before
        //  calling XMOCompletePacket) so that a packet context
        //  is available during the callback (if a callaback is
        //  used) to program the next packet.
        //
        pStreamingResources->Free.InsertTail(pPacketContext);

        XMOCompletePacket(&mediaPacket,
                          dwCompletedSize, 
                          pThis->m_pfnCallback,
                          pThis->m_pvContext,
                          XMEDIAPACKET_STATUS_SUCCESS);

    }

    //
    //  Jog the program loop (if the device is still opened and ready).
    //
    if(pThis->m_fReady)
    {
        pThis->ProgramTransfer();
    }
}

void XHawkMediaObject::AbortMediaPackets()
{

    PHAWK_PACKET_CONTEXT pPacketContext = NULL;
    if(!TEST_FLAG(m_pStreamingResources->AvailableTransferContexts,HAWK_TRANSFER_CONTEXT_0))
    {
        //
        //  Complete the packet (it cannot be on the pending program list)
        //
        if(m_pStreamingResources->TransferContext[0].fLastTransfer)
        {
            pPacketContext = m_pStreamingResources->TransferContext[0].pPacketContext;
        }
    }
    if((NULL==pPacketContext )&& !TEST_FLAG(m_pStreamingResources->AvailableTransferContexts,HAWK_TRANSFER_CONTEXT_1))
    {
        //
        //  Complete the packet (it cannot be on the pending program list)
        //
        if(m_pStreamingResources->TransferContext[1].fLastTransfer)
        {
            pPacketContext = m_pStreamingResources->TransferContext[1].pPacketContext;
        }
    }
    if(NULL==pPacketContext)
    {
        pPacketContext = m_pStreamingResources->PendingProgram.RemoveHead();
    }

    while(pPacketContext)
    {
        //
        //  Cache the media packet info on the stack
        //  so we can free the packet context
        //  before calling XMOCompletePacket.
        //
        XMEDIAPACKET mediaPacket;
        RtlCopyMemory(&mediaPacket, &pPacketContext->MediaPacket, sizeof(XMEDIAPACKET));

        //
        //  Place the packet back on the free list  (do this before
        //  calling XMOCompletePacket) so that a packet context
        //  is available during the callback (if a callaback is
        //  used) to program the next packet.
        //
        m_pStreamingResources->Free.InsertTail(pPacketContext);

        XMOCompletePacket(&mediaPacket,
                          0, 
                          m_pfnCallback,
                          m_pvContext,
                          XMEDIAPACKET_STATUS_FLUSHED);

        //
        //  Get the next packet.
        //

        pPacketContext = m_pStreamingResources->PendingProgram.RemoveHead();
    }
}




//-------------------------------------------------------------------------
// CHawkPacketQueue implementation
//-------------------------------------------------------------------------

void CHawkPacketQueue::InsertTail(PHAWK_PACKET_CONTEXT pPacketContext)
{
    pPacketContext->pNextPacket = NULL;
    if(m_pTail)
    {
        m_pTail->pNextPacket = pPacketContext;
    } else
    {
        m_pHead = pPacketContext;
    }
    m_pTail = pPacketContext;
}

PHAWK_PACKET_CONTEXT CHawkPacketQueue::RemoveHead()
{
    PHAWK_PACKET_CONTEXT pRetVal;
    if(m_pHead)
    {
         pRetVal = m_pHead;
         m_pHead = m_pHead->pNextPacket;
         if(!m_pHead) m_pTail = NULL;
    } else
    {
        pRetVal = NULL;
    }
    return pRetVal;
}
