/*++ THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. Module Name: StreamEngine.cpp Abstract: Virtual Streaming Engine - this module controls streaming logic for the device. Environment: Kernel mode --*/ #include "private.h" #include "public.h" #include #include #include #include #include "streamengine.h" #ifndef __INTELLISENSE__ #include "streamengine.tmh" #endif _Use_decl_annotations_ PAGED_CODE_SEG CStreamEngine::CStreamEngine( _In_ ACXSTREAM Stream, _In_ ACXDATAFORMAT StreamFormat, _In_ BOOL Offload, _In_opt_ CSimPeakMeter *CircuitPeakmeter ) : m_PacketsCount(0), m_PacketSize(0), m_FirstPacketOffset(0), m_NotificationTimer(NULL), m_CurrentState(AcxStreamStateStop), m_CurrentPacket(0), m_Position(0), m_Stream(Stream), m_StreamFormat(StreamFormat), m_StartTime(0), m_StartPosition(0), m_GlitchAdjust(0), m_ToneFrequency(DEFAULT_FREQUENCY), m_Offload(Offload), m_pCircuitPeakmeter(CircuitPeakmeter) { PAGED_CODE(); KeQueryPerformanceCounter(&m_PerformanceCounterFrequency); RtlZeroMemory(m_Packets, sizeof(m_Packets)); } _Use_decl_annotations_ PAGED_CODE_SEG CStreamEngine::~CStreamEngine() { PAGED_CODE(); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::AllocateRtPackets( _In_ ULONG PacketCount, _In_ ULONG PacketSize, _Out_ PACX_RTPACKET* Packets ) { NTSTATUS status = STATUS_SUCCESS; PACX_RTPACKET packets = NULL; PVOID packetBuffer = NULL; ULONG i; ULONG packetAllocSizeInPages = 0; ULONG packetAllocSizeInBytes = 0; ULONG firstPacketOffset = 0; size_t packetsSize = 0; PAGED_CODE(); if (PacketCount > MAX_PACKET_COUNT) { ASSERT(FALSE); status = STATUS_INVALID_PARAMETER; goto exit; } status = RtlSizeTMult(PacketCount, sizeof(ACX_RTPACKET), &packetsSize); if (!NT_SUCCESS(status)) { ASSERT(FALSE); goto exit; } packets = (PACX_RTPACKET)ExAllocatePool2(POOL_FLAG_NON_PAGED, packetsSize, DeviceDriverTag); if (!packets) { status = STATUS_NO_MEMORY; ASSERT(FALSE); goto exit; } // // We need to allocate page-aligned buffers, to ensure no kernel memory leaks // to user space. Round up the packet size to page aligned, then calculate // the first packet's buffer offset so packet 0 ends on a page boundary and // packet 1 begins on a page boundary. // status = RtlULongAdd(PacketSize, PAGE_SIZE - 1, &packetAllocSizeInPages); if (!NT_SUCCESS(status)) { ASSERT(FALSE); goto exit; } packetAllocSizeInPages = packetAllocSizeInPages / PAGE_SIZE; packetAllocSizeInBytes = PAGE_SIZE * packetAllocSizeInPages; firstPacketOffset = packetAllocSizeInBytes - PacketSize; for (i = 0; i < PacketCount; ++i) { PMDL pMdl = NULL; ACX_RTPACKET_INIT(&packets[i]); packetBuffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, packetAllocSizeInBytes, DeviceDriverTag); if (packetBuffer == NULL) { status = STATUS_NO_MEMORY; goto exit; } pMdl = IoAllocateMdl(packetBuffer, packetAllocSizeInBytes, FALSE, TRUE, NULL); if (pMdl == NULL) { status = STATUS_NO_MEMORY; goto exit; } MmBuildMdlForNonPagedPool(pMdl); WDF_MEMORY_DESCRIPTOR_INIT_MDL(&((packets)[i].RtPacketBuffer), pMdl, packetAllocSizeInBytes); packets[i].RtPacketSize = PacketSize; if (i == 0) { packets[i].RtPacketOffset = firstPacketOffset; } else { packets[i].RtPacketOffset = 0; } m_Packets[i] = packetBuffer; packetBuffer = NULL; } *Packets = packets; packets = NULL; m_PacketsCount = PacketCount; m_PacketSize = PacketSize; m_FirstPacketOffset = firstPacketOffset; exit: if (packetBuffer) { ExFreePoolWithTag(packetBuffer, DeviceDriverTag); } if (packets) { FreeRtPackets(packets, PacketCount); } return status; } _Use_decl_annotations_ PAGED_CODE_SEG VOID CStreamEngine::FreeRtPackets( _Frees_ptr_ PACX_RTPACKET Packets, _In_ ULONG PacketCount ) { ULONG i; PVOID buffer; PAGED_CODE(); for (i = 0; i < PacketCount; ++i) { if (Packets[i].RtPacketBuffer.u.MdlType.Mdl) { buffer = MmGetMdlVirtualAddress(Packets[i].RtPacketBuffer.u.MdlType.Mdl); IoFreeMdl(Packets[i].RtPacketBuffer.u.MdlType.Mdl); ExFreePool(buffer); } } ExFreePool(Packets); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::PrepareHardware() { NTSTATUS status = STATUS_UNSUCCESSFUL; WDF_TIMER_CONFIG timerConfig; WDF_OBJECT_ATTRIBUTES timerAttributes; PSTREAM_TIMER_CONTEXT timerCtx; PAGED_CODE(); // // If already in this state, do nothing. // if (m_CurrentState == AcxStreamStatePause) { // Nothing to do. status = STATUS_SUCCESS; goto exit; } if (m_CurrentState != AcxStreamStateStop) { // Error out. status = STATUS_INVALID_STATE_TRANSITION; goto exit; } // // Stop to Pause. // WDF_TIMER_CONFIG_INIT(&timerConfig, CStreamEngine::s_EvtStreamPassCallback); timerConfig.AutomaticSerialization = TRUE; timerConfig.UseHighResolutionTimer = WdfTrue; timerConfig.Period = 0; WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT); timerAttributes.ParentObject = m_Stream; status = WdfTimerCreate(&timerConfig, &timerAttributes, &m_NotificationTimer); if (!NT_SUCCESS(status)) { goto exit; } timerCtx = GetStreamTimerContext(m_NotificationTimer); timerCtx->StreamEngine = this; m_CurrentState = AcxStreamStatePause; status = STATUS_SUCCESS; exit: return status; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::ReleaseHardware() { PAGED_CODE(); // // If already in this state, do nothing. // if (m_CurrentState == AcxStreamStateStop) { // Nothing to do. goto exit; } // // Just assert we are in the correct state. // On the way down we always want to succeed. // ASSERT(m_CurrentState == AcxStreamStatePause); // // Pause to Stop. // if (m_NotificationTimer) { WdfTimerStop(m_NotificationTimer, TRUE); WdfObjectDelete(m_NotificationTimer); m_NotificationTimer = NULL; } KeFlushQueuedDpcs(); m_Position = 0; m_GlitchAdjust = 0; m_CurrentPacket = 0; m_CurrentState = AcxStreamStateStop; exit: return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::Pause() { NTSTATUS status = STATUS_UNSUCCESSFUL; PAGED_CODE(); if (m_CurrentState == AcxStreamStatePause) { // Nothing to do. status = STATUS_SUCCESS; goto exit; } if (m_CurrentState != AcxStreamStateRun) { // Error out. status = STATUS_INVALID_STATE_TRANSITION; goto exit; } m_PeakMeter.StopStream(); if (m_pCircuitPeakmeter) { m_pCircuitPeakmeter->StopStream(); } // // Run to Pause. // WdfTimerStop(m_NotificationTimer, TRUE); // Save the position we paused at. UpdatePosition(); m_CurrentState = AcxStreamStatePause; status = STATUS_SUCCESS; exit: return status; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::Run() { NTSTATUS status = STATUS_UNSUCCESSFUL; PAGED_CODE(); if (m_CurrentState == AcxStreamStateRun) { // Nothing to do. status = STATUS_SUCCESS; goto exit; } if (m_CurrentState != AcxStreamStatePause) { status = STATUS_INVALID_STATE_TRANSITION; goto exit; } m_PeakMeter.StartStream(); if (m_pCircuitPeakmeter) { m_pCircuitPeakmeter->StartStream(); } // // Pause to Run. // // Save the time and position - if we ran and paused previously, the StartTime and StartPosition will allow // us to continue scheduling packet completions correctly, while still reporting absolute position from the // start of the stream. // m_StartTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL)); m_StartPosition = m_Position; // Reset time we've lost to glitches m_GlitchAdjust = 0; ScheduleNextPass(); m_CurrentState = AcxStreamStateRun; status = STATUS_SUCCESS; exit: return status; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::GetPresentationPosition( _Out_ PULONGLONG PositionInBlocks, _Out_ PULONGLONG QPCPosition ) { ULONG blockAlign; LARGE_INTEGER qpc; PAGED_CODE(); blockAlign = AcxDataFormatGetBlockAlign(m_StreamFormat); // Update the position based on the current time UpdatePosition(); qpc = KeQueryPerformanceCounter(NULL); *PositionInBlocks = m_Position / blockAlign; *QPCPosition = (ULONGLONG)qpc.QuadPart; return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::GetLinearBufferPosition( _Out_ PULONGLONG Position ) { UNREFERENCED_PARAMETER(Position); PAGED_CODE(); return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::SetCurrentWritePosition( _In_ ULONG Position ) { UNREFERENCED_PARAMETER(Position); PAGED_CODE(); return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::SetLastBufferPosition( _In_ ULONG Position ) { UNREFERENCED_PARAMETER(Position); PAGED_CODE(); return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::AssignDrmContentId( ULONG DrmContentId, PACXDRMRIGHTS DrmRights ) { PAGED_CODE(); UNREFERENCED_PARAMETER(DrmContentId); UNREFERENCED_PARAMETER(DrmRights); // // At this point the driver should enforce the new DrmRights. // // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP. // // From MSDN: // // This sample doesn't forward protected content, but if your driver uses // lower layer drivers or a different stack to properly work, please see the // following info from MSDN: // // "Before allowing protected content to flow through a data path, the system // verifies that the data path is secure. To do so, the system authenticates // each module in the data path beginning at the upstream end of the data path // and moving downstream. As each module is authenticated, that module gives // the system information about the next module in the data path so that it // can also be authenticated. To be successfully authenticated, a module's // binary file must be signed as DRM-compliant. // // Two adjacent modules in the data path can communicate with each other in // one of several ways. If the upstream module calls the downstream module // through IoCallDriver, the downstream module is part of a WDM driver. In // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject // function to provide the system with the device object representing the // downstream module. (If the two modules communicate through the downstream // module's content handlers, the upstream module calls AcxDrmAddContentHandlers // instead.) // // For more information, see MSDN's DRM Functions and Interfaces. // return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::GetHWLatency( _Out_ ULONG* FifoSize, _Out_ ULONG* Delay ) { PAGED_CODE(); *FifoSize = 128; *Delay = 0; return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG CSimPeakMeter * CStreamEngine::GetPeakMeter() { PAGED_CODE(); return &m_PeakMeter; } _Use_decl_annotations_ VOID CStreamEngine::s_EvtStreamPassCallback( _In_ WDFTIMER Timer ) { CStreamEngine* This; PSTREAM_TIMER_CONTEXT timerCtx; // Get our stream engine pointer from the timer context timerCtx = GetStreamTimerContext(Timer); This = timerCtx->StreamEngine; // Call the StreamPassCallback for the engine This->StreamPassCallback(); } // This is run every time the stream timer fires _Use_decl_annotations_ VOID CStreamEngine::StreamPassCallback() { ULONGLONG completedPacket; ULONGLONG qpcCompleted; // Process the packet (e.g. save render to file/generate capture data) ProcessPacket(); // We've completed a packet! Increment our currently active packet completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_CurrentPacket) - 1; // Save the time at which we moved to the next packet qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; InterlockedExchange64(&m_LastPacketStart.QuadPart, m_CurrentPacketStart.QuadPart); InterlockedExchange64(&m_CurrentPacketStart.QuadPart, qpcCompleted); // Tell ACX we've completed the packet. (void)AcxRtStreamNotifyPacketComplete(m_Stream, completedPacket, qpcCompleted); // Schedule when our new current packet will finish ScheduleNextPass(); } _Use_decl_annotations_ VOID CStreamEngine::ScheduleNextPass() { LONGLONG delay = 0; ULONG bytesPerSecond; ULONGLONG nextPacket = 0; ULONGLONG nextPacketStartPosition = 0; ULONGLONG nextPacketPositionFromLastPause = 0; ULONGLONG nextPacketTimeFromLastPauseHns = 0; ULONGLONG nextPacketTime = 0; ULONGLONG currentTime; BOOLEAN inTimerQueue = FALSE; // Get the number of bytes per second from our stored stream format bytesPerSecond = GetBytesPerSecond(); // Calculate the absolute position of the beginning of the next packet from the beginning of the stream nextPacket = m_CurrentPacket + 1; nextPacketStartPosition = nextPacket * m_PacketSize; // Adjust next packet position to account for the last time we resumed from Pause nextPacketPositionFromLastPause = nextPacketStartPosition - m_StartPosition; // Convert from bytes to HNS (to prevent truncation, multiply first then divide) nextPacketTimeFromLastPauseHns = nextPacketPositionFromLastPause * HNS_PER_SEC / bytesPerSecond; // Next packet time is Time @ resume from Pause, offset for lost time due to glitch, with next packet time added nextPacketTime = m_StartTime + m_GlitchAdjust + nextPacketTimeFromLastPauseHns; currentTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL)); // Determine how long we want to wait, in HNS. Negative since it's a relative wait delay = -(LONGLONG)(nextPacketTime - currentTime); // If the delay isn't negative, this means we lost some time (e.g. broken into kernel debugger). Update // our glitch adjust to account for that lost time, and attempt to schedule again if (delay >= 0) { // Glitch!!! // Update the glitch adjustment and set the new delay. m_GlitchAdjust += delay; StreamPassCallback(); return; } // Start the timer for our next pass! Note the timer isn't periodic. inTimerQueue = WdfTimerStart(m_NotificationTimer, delay); // We shouldn't be scheduling our next pass if the timer was previously still pending ASSERT(inTimerQueue == FALSE); } _Use_decl_annotations_ VOID CStreamEngine::UpdatePosition() { ULONGLONG currentTime; ULONG bytesPerSecond; if (m_CurrentState != AcxStreamStateRun) { return; } bytesPerSecond = GetBytesPerSecond(); currentTime = KSCONVERT_PERFORMANCE_TIME(m_PerformanceCounterFrequency.QuadPart, KeQueryPerformanceCounter(NULL)); // Update position m_Position = m_StartPosition - m_GlitchAdjust + (currentTime - m_StartTime) * bytesPerSecond / HNS_PER_SEC; } _Use_decl_annotations_ ULONG CStreamEngine::GetBytesPerSecond() { ULONG bytesPerSecond; bytesPerSecond = AcxDataFormatGetAverageBytesPerSec(m_StreamFormat); return bytesPerSecond; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CStreamEngine::GetCurrentPacket( _Out_ PULONG CurrentPacket ) { ULONG currentPacket; PAGED_CODE(); currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); *CurrentPacket = currentPacket; return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG CRenderStreamEngine::CRenderStreamEngine( _In_ ACXSTREAM Stream, _In_ ACXDATAFORMAT StreamFormat, _In_ BOOL Offload, _In_ CSimPeakMeter * CircuitPeakmeter ) : CStreamEngine(Stream, StreamFormat, Offload, CircuitPeakmeter) { PAGED_CODE(); } _Use_decl_annotations_ PAGED_CODE_SEG CRenderStreamEngine::~CRenderStreamEngine() { PAGED_CODE(); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CRenderStreamEngine::PrepareHardware() { NTSTATUS status = STATUS_SUCCESS; PAGED_CODE(); status = CStreamEngine::PrepareHardware(); if (!NT_SUCCESS(status)) { goto exit; } status = m_SaveData.SetDataFormat((PKSDATAFORMAT)AcxDataFormatGetKsDataFormat(m_StreamFormat)); if (!NT_SUCCESS(status)) { status = STATUS_SUCCESS; goto exit; } status = m_SaveData.Initialize(m_Offload); if (!NT_SUCCESS(status)) { status = STATUS_SUCCESS; goto exit; } status = m_SaveData.SetMaxWriteSize(m_PacketSize * m_PacketsCount * 16); if (!NT_SUCCESS(status)) { status = STATUS_SUCCESS; goto exit; } exit: return status; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CRenderStreamEngine::ReleaseHardware() { PAGED_CODE(); m_SaveData.WaitAllWorkItems(); m_SaveData.Cleanup(); return CStreamEngine::ReleaseHardware(); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CRenderStreamEngine::AssignDrmContentId( ULONG DrmContentId, PACXDRMRIGHTS DrmRights ) { PAGED_CODE(); UNREFERENCED_PARAMETER(DrmContentId); // // At this point the driver should enforce the new DrmRights. // The sample driver handles DrmRights per stream basis, and // stops writing the stream to disk, if CopyProtect = TRUE. // // HDMI render: if DigitalOutputDisable or CopyProtect is true, enable HDCP. // Loopback: if CopyProtect is true, disable loopback stream. // // // Sample writes each stream seperately to disk. If the rights for this // stream indicates that the stream is CopyProtected, stop writing to disk. // m_SaveData.Disable(DrmRights->CopyProtect); // // From MSDN: // // This sample doesn't forward protected content, but if your driver uses // lower layer drivers or a different stack to properly work, please see the // following info from MSDN: // // "Before allowing protected content to flow through a data path, the system // verifies that the data path is secure. To do so, the system authenticates // each module in the data path beginning at the upstream end of the data path // and moving downstream. As each module is authenticated, that module gives // the system information about the next module in the data path so that it // can also be authenticated. To be successfully authenticated, a module's // binary file must be signed as DRM-compliant. // // Two adjacent modules in the data path can communicate with each other in // one of several ways. If the upstream module calls the downstream module // through IoCallDriver, the downstream module is part of a WDM driver. In // this case, the upstream module calls the AcxDrmForwardContentToDeviceObject // function to provide the system with the device object representing the // downstream module. (If the two modules communicate through the downstream // module's content handlers, the upstream module calls AcxDrmAddContentHandlers // instead.) // // For more information, see MSDN's DRM Functions and Interfaces. // return STATUS_SUCCESS; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CRenderStreamEngine::SetRenderPacket( _In_ ULONG Packet, _In_ ULONG Flags, _In_ ULONG EosPacketLength ) { NTSTATUS status = STATUS_SUCCESS; ULONG currentPacket; UNREFERENCED_PARAMETER(Flags); UNREFERENCED_PARAMETER(EosPacketLength); PAGED_CODE(); currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); if (Packet <= currentPacket) { //ASSERT(FALSE); status = STATUS_DATA_LATE_ERROR; } else if (Packet > currentPacket + 1) { //ASSERT(FALSE); status = STATUS_DATA_OVERRUN; } return status; } _Use_decl_annotations_ VOID CRenderStreamEngine::ProcessPacket() { ULONG currentPacket; ULONG packetIndex; PBYTE packetBuffer; currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); packetIndex = currentPacket % m_PacketsCount; packetBuffer = (PBYTE)m_Packets[packetIndex]; // Packet 0 starts at an offset if the size isn't a multiple of page_size if (packetIndex == 0) { packetBuffer += m_FirstPacketOffset; } m_SaveData.WriteData(packetBuffer, m_PacketSize); } _Use_decl_annotations_ PAGED_CODE_SEG CCaptureStreamEngine::CCaptureStreamEngine( _In_ ACXSTREAM Stream, _In_ ACXDATAFORMAT StreamFormat ) : CStreamEngine(Stream, StreamFormat, FALSE, NULL), m_EnableWaveCapture(0) { PAGED_CODE(); m_CurrentPacketStart.QuadPart = 0; m_LastPacketStart.QuadPart = 0; RtlInitUnicodeString(&m_HostCaptureFileName, NULL); RtlInitUnicodeString(&m_LoopbackCaptureFileName, NULL); } _Use_decl_annotations_ PAGED_CODE_SEG CCaptureStreamEngine::~CCaptureStreamEngine() { PAGED_CODE(); RtlFreeUnicodeString(&m_HostCaptureFileName); RtlFreeUnicodeString(&m_LoopbackCaptureFileName); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CCaptureStreamEngine::PrepareHardware() { NTSTATUS status = STATUS_SUCCESS; PWAVEFORMATEXTENSIBLE pwfext = NULL; PAGED_CODE(); status = CStreamEngine::PrepareHardware(); if (!NT_SUCCESS(status)) { goto exit; } (void)ReadRegistrySettings(); pwfext = (PWAVEFORMATEXTENSIBLE)AcxDataFormatGetWaveFormatExtensible(m_StreamFormat); if (pwfext == NULL) { // Cannot initialize reader or generator with a format that's not understood status = STATUS_NO_MATCH; ASSERT(FALSE); goto exit; } if (m_EnableWaveCapture) { status = m_WaveReader.Init(pwfext, &m_HostCaptureFileName); if (!NT_SUCCESS(status)) { m_EnableWaveCapture = FALSE; } } if (!m_EnableWaveCapture) { status = m_ToneGenerator.Init(m_ToneFrequency, pwfext); } exit: return status; } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CCaptureStreamEngine::ReleaseHardware() { PAGED_CODE(); if (m_EnableWaveCapture) { m_WaveReader.WaitAllWorkItems(); } return CStreamEngine::ReleaseHardware(); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CCaptureStreamEngine::GetCapturePacket( _Out_ ULONG* LastCapturePacket, _Out_ ULONGLONG* QPCPacketStart, _Out_ BOOLEAN* MoreData ) { NTSTATUS status = STATUS_SUCCESS; ULONG currentPacket; LONGLONG qpcPacketStart; PAGED_CODE(); currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); qpcPacketStart = InterlockedCompareExchange64(&m_LastPacketStart.QuadPart, -1, -1); *LastCapturePacket = currentPacket - 1; *QPCPacketStart = (ULONGLONG)qpcPacketStart; *MoreData = FALSE; return status; } _Use_decl_annotations_ VOID CCaptureStreamEngine::ProcessPacket() { ULONG currentPacket; ULONG packetIndex; PBYTE packetBuffer; currentPacket = (ULONG)InterlockedCompareExchange((LONG*)&m_CurrentPacket, -1, -1); packetIndex = currentPacket % m_PacketsCount; packetBuffer = (PBYTE)m_Packets[packetIndex]; // Packet 0 starts at an offset if the size isn't a multiple of page_size if (packetIndex == 0) { packetBuffer += m_FirstPacketOffset; } if (m_EnableWaveCapture) { m_WaveReader.ReadWaveData(packetBuffer, m_PacketSize); } else { m_ToneGenerator.GenerateSine(packetBuffer, m_PacketSize); } } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CCaptureStreamEngine::ReadRegistrySettings() { NTSTATUS status; PDRIVER_OBJECT DriverObject; HANDLE DriverKey; RTL_QUERY_REGISTRY_TABLE paramTable[] = { // QueryRoutine Flags Name EntryContext DefaultType DefaultData DefaultLength { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"EnableWaveCapture", &m_EnableWaveCapture, (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD, &m_EnableWaveCapture, sizeof(DWORD) }, { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"HostCaptureFileName", &m_HostCaptureFileName, (REG_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_SZ, &m_HostCaptureFileName, sizeof(UNICODE_STRING) }, { NULL, RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, L"LoopbackCaptureFileName", &m_LoopbackCaptureFileName, (REG_SZ << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_SZ, &m_LoopbackCaptureFileName, sizeof(UNICODE_STRING) }, { NULL, 0, NULL, NULL, 0, NULL, 0 } }; PAGED_CODE(); DriverObject = WdfDriverWdmGetDriverObject(WdfGetDriver()); DriverKey = NULL; status = IoOpenDriverRegistryKey(DriverObject, DriverRegKeyParameters, KEY_READ, 0, &DriverKey); if (!NT_SUCCESS(status)) { ASSERT(FALSE); goto exit; } status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE, (PCWSTR) DriverKey, ¶mTable[0], NULL, NULL); if (DriverKey) { ZwClose(DriverKey); } exit: if (!NT_SUCCESS(status)) { m_EnableWaveCapture = FALSE; } return status; } _Use_decl_annotations_ PAGED_CODE_SEG CBufferedCaptureStreamEngine::CBufferedCaptureStreamEngine( _In_ ACXSTREAM Stream, _In_ ACXDATAFORMAT StreamFormat, _In_ CKeywordDetector* KeywordDetector ) : CCaptureStreamEngine(Stream, StreamFormat), m_KeywordDetector(KeywordDetector) { PAGED_CODE(); } _Use_decl_annotations_ PAGED_CODE_SEG CBufferedCaptureStreamEngine::~CBufferedCaptureStreamEngine() { PAGED_CODE(); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CBufferedCaptureStreamEngine::Pause() { PAGED_CODE(); m_KeywordDetector->Stop(); return CCaptureStreamEngine::Pause(); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CBufferedCaptureStreamEngine::Run() { PAGED_CODE(); m_KeywordDetector->Run(); return CCaptureStreamEngine::Run(); } _Use_decl_annotations_ PAGED_CODE_SEG NTSTATUS CBufferedCaptureStreamEngine::GetCapturePacket( _Out_ ULONG* LastCapturePacket, _Out_ ULONGLONG* QPCPacketStart, _Out_ BOOLEAN* MoreData ) { PAGED_CODE(); // retrieve the packet from the fifo queue return m_KeywordDetector->GetReadPacket(m_PacketsCount, m_PacketSize, m_Packets, LastCapturePacket, QPCPacketStart, MoreData); } _Use_decl_annotations_ VOID CBufferedCaptureStreamEngine::ProcessPacket() { LARGE_INTEGER qpc; LARGE_INTEGER qpcFrequency; qpc = KeQueryPerformanceCounter(&qpcFrequency); // Add the next packet to the fifo queue m_KeywordDetector->DpcRoutine(qpc.QuadPart, qpcFrequency.QuadPart); } //Streamengine callbacks VOID EvtStreamDestroy( _In_ WDFOBJECT Object ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; ctx = GetStreamEngineContext((ACXSTREAM)Object); streamEngine = (CStreamEngine*)ctx->StreamEngine; ctx->StreamEngine = NULL; delete streamEngine; } PAGED_CODE_SEG NTSTATUS EvtStreamGetHwLatency( _In_ ACXSTREAM Stream, _Out_ ULONG* FifoSize, _Out_ ULONG* Delay ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->GetHWLatency(FifoSize, Delay); } PAGED_CODE_SEG NTSTATUS EvtStreamAllocateRtPackets( _In_ ACXSTREAM Stream, _In_ ULONG PacketCount, _In_ ULONG PacketSize, _Out_ PACX_RTPACKET* Packets ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->AllocateRtPackets(PacketCount, PacketSize, Packets); } PAGED_CODE_SEG VOID EvtStreamFreeRtPackets( _In_ ACXSTREAM Stream, _In_ PACX_RTPACKET Packets, _In_ ULONG PacketCount ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->FreeRtPackets(Packets, PacketCount); } PAGED_CODE_SEG NTSTATUS EvtStreamPrepareHardware( _In_ ACXSTREAM Stream ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->PrepareHardware(); } PAGED_CODE_SEG NTSTATUS EvtStreamReleaseHardware( _In_ ACXSTREAM Stream ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->ReleaseHardware(); } PAGED_CODE_SEG NTSTATUS EvtStreamRun( _In_ ACXSTREAM Stream ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->Run(); } PAGED_CODE_SEG NTSTATUS EvtStreamPause( _In_ ACXSTREAM Stream ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->Pause(); } PAGED_CODE_SEG NTSTATUS EvtStreamAssignDrmContentId( _In_ ACXSTREAM Stream, _In_ ULONG DrmContentId, _In_ PACXDRMRIGHTS DrmRights ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine * streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = (CStreamEngine*)ctx->StreamEngine; return streamEngine->AssignDrmContentId(DrmContentId, DrmRights); } PAGED_CODE_SEG NTSTATUS EvtStreamGetCurrentPacket( _In_ ACXSTREAM Stream, _Out_ PULONG CurrentPacket ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = static_cast(ctx->StreamEngine); return streamEngine->GetCurrentPacket(CurrentPacket); } PAGED_CODE_SEG NTSTATUS EvtStreamGetPresentationPosition( _In_ ACXSTREAM Stream, _Out_ PULONGLONG PositionInBlocks, _Out_ PULONGLONG QPCPosition ) { PSTREAMENGINE_CONTEXT ctx; CStreamEngine* streamEngine = NULL; PAGED_CODE(); ctx = GetStreamEngineContext(Stream); streamEngine = static_cast(ctx->StreamEngine); return streamEngine->GetPresentationPosition(PositionInBlocks, QPCPosition); }