diff options
| author | Daniel Rugerio <[email protected]> | 2025-11-25 11:00:04 -0800 |
|---|---|---|
| committer | Daniel Rugerio <[email protected]> | 2025-12-15 14:00:37 -0800 |
| commit | 6fa87d3bef7972f7fac034adb68cd856f69c8607 (patch) | |
| tree | 90ddf9e506b651c81dd803e0a9cc04af5db24a79 /audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp | |
| parent | 1d89a1513f519f62226f84a3131916ab2fd0c3ef (diff) | |
New SDCAVAD sample.
Diffstat (limited to 'audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp')
| -rw-r--r-- | audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp new file mode 100644 index 00000000..c86af13a --- /dev/null +++ b/audio/SoundWire/Samples/SdcaVad/SdcaVDsp/offloadStreamEngine.cpp @@ -0,0 +1,606 @@ +/*++ + + 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: + + offloadStreamEngine.cpp + +Abstract: + + Virtual Streaming Engine - this module controls offload streaming logic for + the device. + +Environment: + + Kernel mode + +--*/ + +#include "private.h" +#include <ks.h> +#include <mmsystem.h> +#include <ksmedia.h> +#include "offloadStreamEngine.h" + +#ifndef __INTELLISENSE__ +#include "offloadStreamEngine.tmh" +#endif + +_Use_decl_annotations_ +PAGED_CODE_SEG +COffloadStreamEngine::COffloadStreamEngine( + ACXSTREAM Stream, + ACXDATAFORMAT StreamFormat, + CSimPeakMeter *circuitPeakmeter +) :CStreamEngine(Stream, StreamFormat, circuitPeakmeter) +{ + PAGED_CODE(); + + m_BufferReadTimer = NULL; + m_LastBufferTimer = NULL; + m_PacketsWritten = 0; + m_PacketsRead = 0; + m_SinglePacketPosition = 0; +} + +_Use_decl_annotations_ +#pragma code_seg() +COffloadStreamEngine::~COffloadStreamEngine() +{ +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::PrepareHardware() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + RETURN_NTSTATUS_IF_FAILED(CStreamEngine::PrepareHardware()); + // CStreamEngine::PrepareHardware will update state to Pause, but we don't + // want to be in Pause state if any of the below actions fail. + m_CurrentState = AcxStreamStateStop; + + // + // Buffer read callbacks + // + WDF_TIMER_CONFIG timerConfig; + LONG period = (LONG)((ULONGLONG)m_PacketSize * HNS_PER_SEC / (ULONGLONG)GetBytesPerSecond()); + WDF_TIMER_CONFIG_INIT_PERIODIC( + &timerConfig, + COffloadStreamEngine::s_EvtBufferReadTimerCallback, + period / HNSTIME_PER_MILLISECOND + ); + timerConfig.UseHighResolutionTimer = WdfTrue; + + WDF_OBJECT_ATTRIBUTES timerAttributes; + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT); + timerAttributes.ParentObject = m_Stream; + + RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate( + &timerConfig, + &timerAttributes, + &m_BufferReadTimer + )); + + auto bt_free = scope_exit([this]() { + WdfObjectDelete(m_BufferReadTimer); + m_BufferReadTimer = NULL; + }); + + PSTREAM_TIMER_CONTEXT timerCtx; + timerCtx = GetStreamTimerContext(m_BufferReadTimer); + timerCtx->StreamEngine = this; + + // + // Last Buffer read callback + // + WDF_TIMER_CONFIG_INIT( + &timerConfig, + COffloadStreamEngine::s_EvtLastBufferTimerCallback + ); + timerConfig.UseHighResolutionTimer = WdfTrue; + + WDF_OBJECT_ATTRIBUTES_INIT(&timerAttributes); + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&timerAttributes, STREAM_TIMER_CONTEXT); + timerAttributes.ParentObject = m_Stream; + + RETURN_NTSTATUS_IF_FAILED(WdfTimerCreate( + &timerConfig, + &timerAttributes, + &m_LastBufferTimer + )); + + auto lbt_free = scope_exit([this]() { + WdfObjectDelete(m_LastBufferTimer); + m_LastBufferTimer = NULL; + }); + + timerCtx = GetStreamTimerContext(m_LastBufferTimer); + timerCtx->StreamEngine = this; + + RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetDataFormat((PKSDATAFORMAT)AcxDataFormatGetKsDataFormat(m_StreamFormat))); + + RETURN_NTSTATUS_IF_FAILED(m_SaveData.Initialize(TRUE)); + + RETURN_NTSTATUS_IF_FAILED(m_SaveData.SetMaxWriteSize(m_PacketSize * m_PacketsCount * MAX_FILE_WRITE_FRAMES)); + + m_CurrentState = AcxStreamStatePause; + + bt_free.release(); + lbt_free.release(); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::ReleaseHardware() +{ + PAGED_CODE(); + + m_SaveData.WaitAllWorkItems(); + m_SaveData.Cleanup(); + + if (m_BufferReadTimer) + { + WdfTimerStop(m_BufferReadTimer, TRUE); + WdfObjectDelete(m_BufferReadTimer); + m_BufferReadTimer = NULL; + } + + if (m_LastBufferTimer) + { + WdfTimerStop(m_LastBufferTimer, TRUE); + WdfObjectDelete(m_LastBufferTimer); + m_LastBufferTimer = NULL; + } + + m_LinearBufferClock.Stop(); + + m_PacketsWritten = 0; + m_PacketsRead = 0; + + CStreamEngine::ReleaseHardware(); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::Run() +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run"); + + if (m_CurrentState != AcxStreamStatePause) + { + status = STATUS_INVALID_STATE_TRANSITION; + return status; + } + + ULONGLONG bytesPerSec = GetBytesPerSecond(); + + ULONGLONG elapsedTimeWhenPaused = m_LinearBufferClock.GetElapsedTime(NULL); + if (elapsedTimeWhenPaused) + { + // Stream has resumed from pause + // Calculate remaining buffer for next notification + + // Remaining buffer from when stream was paused + // Hardware might have cycled more than the bytes written + // This can happen if there was a glitch and hardware was + // starved + ULONGLONG packetTime = (ULONGLONG)m_PacketSize * HNS_PER_SEC / bytesPerSec; + LONG remainingTime = (LONG)((ULONGLONG)elapsedTimeWhenPaused % packetTime); + WdfTimerStart(m_BufferReadTimer, WDF_REL_TIMEOUT_IN_MS(remainingTime / HNSTIME_PER_MILLISECOND)); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run - Notification Timer Started - first timeout :%d ms", (ULONG)(remainingTime / HNSTIME_PER_MILLISECOND)); + } + else + { + // Run has been called first time on this stream + LONG period = (LONG)((ULONGLONG)m_PacketSize * HNS_PER_SEC / (ULONGLONG)bytesPerSec); + WdfTimerStart(m_BufferReadTimer, WDF_REL_TIMEOUT_IN_MS(period / HNSTIME_PER_MILLISECOND)); + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Run - Notification Timer Started - first timeout :%d ms", (ULONG)(period / HNSTIME_PER_MILLISECOND)); + } + + m_LinearBufferClock.Run(); + m_CurrentState = AcxStreamStateRun; + + m_PeakMeter.StartStream(); + m_pCircuitPeakmeter->StartStream(); + + return status; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::Pause() +{ + PAGED_CODE(); + + NTSTATUS status = STATUS_SUCCESS; + + DrvLogInfo(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::Pause - from %d", m_CurrentState); + + RETURN_NTSTATUS_IF_TRUE(m_CurrentState != AcxStreamStateRun, STATUS_INVALID_STATE_TRANSITION); + + WdfTimerStop(m_BufferReadTimer, TRUE); + + m_LinearBufferClock.Pause(); + + m_PeakMeter.StopStream(); + m_pCircuitPeakmeter->StopStream(); + + m_CurrentState = AcxStreamStatePause; + + return status; +} + +_Use_decl_annotations_ +#pragma code_seg() +NTSTATUS +COffloadStreamEngine::GetPresentationPosition( + PULONGLONG PositionInBlocks, + PULONGLONG QPCPosition +) +{ + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::GetPresentationPosition"); + + ULONG blockAlign; + LARGE_INTEGER qpc; + + blockAlign = AcxDataFormatGetBlockAlign(m_StreamFormat); + qpc = KeQueryPerformanceCounter(NULL); + + ULONGLONG streamPosition = m_LinearBufferClock.GetElapsedTime(NULL); + + // Simulate Presentation position lag by 20 ms + if (streamPosition > (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND)) + { + streamPosition -= (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND); + } + else + { + streamPosition = 0; + } + + *PositionInBlocks = (streamPosition * GetBytesPerSecond() / HNS_PER_SEC) / blockAlign; + + *QPCPosition = (ULONGLONG)qpc.QuadPart; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +#pragma code_seg() +NTSTATUS +COffloadStreamEngine::GetLinearBufferPosition( + PULONGLONG Position +) +{ + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::GetLinearBufferPosition"); + + ULONGLONG bytesPerSecond = GetBytesPerSecond(); + ULONGLONG currentTime = m_LinearBufferClock.GetElapsedTime(NULL); + + // Update position + *Position = currentTime * bytesPerSecond / HNS_PER_SEC; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::SetCurrentWritePosition( + ULONG Position +) +{ + PAGED_CODE(); + + if (m_PacketsCount == 1) + { + return SetCurrentWritePositionSinglePacket(Position); + } + + // Determine buffer + // Designed for ping-pong + // Position == m_PacketSize, ping buffer was just filled + // Position == m_PacketSize * 2, pong buffer was just filled + ULONG packetIndex = Position == m_PacketSize ? 0 : 1; + + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::SetCurrentWritePosition, Position = 0x%08x, PacketIndex :%d", Position, packetIndex); + + // + // Detect if the packet just written is incorrect + // + ULONG packetsRead = (ULONG)InterlockedCompareExchange((LONG *)&m_PacketsRead, -1, -1); + + if (m_CurrentState == AcxStreamStateRun) + { + ULONG expectedPacketIndex = (packetsRead % 2) ? 0 : 1; + if (packetIndex != expectedPacketIndex) + { + DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine incorrect packet write: %d, Packets Read: %08d", packetIndex, packetsRead); + // \TODO: ACX doesn't recover from this error + // Continuing automatically recovers with next call + //return STATUS_DATA_OVERRUN; + } + } + + // + // Catch up to packets read + 1 + // This is to recover from condition when OS was not writing enough data + // + (ULONG)InterlockedExchange((LONG *)&m_PacketsWritten, packetsRead + 1); + + PBYTE packetBuffer = NULL; + 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); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::SetCurrentWritePositionSinglePacket( + ULONG Position +) +{ + PAGED_CODE(); + + // Offload streams are almost always 2-packet. However, it is possible to create an offload stream + // as a timer-driven (single packet) stream. This code will ensure correct behavior in this case. + ULONG packetIndex = 0; + + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::SetCurrentWritePositionSinglePacket, Position = 0x%08x, PacketIndex :%d", Position, packetIndex); + + // + // Detect if the packet just written is incorrect + // + ULONG packetsRead = (ULONG)InterlockedCompareExchange((LONG *)&m_PacketsRead, -1, -1); + + // Position has wrapped, can increment the written count. + if (Position < m_SinglePacketPosition) + { + // + // Catch up to packets read + 1 + // This is to recover from condition when OS was not writing enough data + // + (ULONG)InterlockedExchange((LONG*)&m_PacketsWritten, packetsRead + 1); + } + + PBYTE packetBuffer = NULL; + packetBuffer = (PBYTE)m_Packets[packetIndex]; + // Packet 0 starts at an offset if the size isn't a multiple of page_size + // For single-packet the offset should be 0. + packetBuffer += m_FirstPacketOffset; + + // AudioKSE adds 1 to the position + Position -= 1; + Position %= m_PacketSize; + + if (Position <= m_SinglePacketPosition) + { + // Handle the case of wraparound by copying from the last position to the end of the buffer + m_SaveData.WriteData(packetBuffer + m_SinglePacketPosition, m_PacketSize - m_SinglePacketPosition); + m_SinglePacketPosition = 0; + } + // Write from the last position (0 in the case of wraparound) to the new Position + m_SaveData.WriteData(packetBuffer + m_SinglePacketPosition, Position); + m_SinglePacketPosition = Position; + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::SetLastBufferPosition( + ULONG Position +) +{ + PAGED_CODE(); + + ULONG bytesPerSec = GetBytesPerSecond(); + + // Determine buffer + ULONG packetIndex = Position < m_PacketSize ? 0 : 1; + ULONG lastBufferSize = Position <= m_PacketSize ? Position : Position - m_PacketSize; + + // time for rendering last buffer + ULONGLONG lastBufferTime = (ULONGLONG)lastBufferSize * HNS_PER_SEC / (ULONGLONG)bytesPerSec; + + ULONGLONG totalStreamTime = (ULONGLONG)m_PacketsWritten* (ULONGLONG)m_PacketSize* HNS_PER_SEC / (ULONGLONG)bytesPerSec; + totalStreamTime += (ULONGLONG)lastBufferTime; + + // Simulate Presentation position lag by 20 ms + ULONGLONG presentationTime = m_LinearBufferClock.GetElapsedTime(NULL) - (OFFLOAD_PRESENTATION_POSITION_LAG_IN_MS * HNSTIME_PER_MILLISECOND); + + lastBufferTime = totalStreamTime - presentationTime; + + // + // Start last buffer timer + // + RETURN_NTSTATUS_IF_TRUE_MSG(NULL == m_LastBufferTimer, STATUS_INVALID_PARAMETER, L"Set Last Buffer Position called out of sequence - without calling prepare hardware"); + WdfTimerStart(m_LastBufferTimer, WDF_REL_TIMEOUT_IN_MS(lastBufferTime / HNSTIME_PER_MILLISECOND)); + + PBYTE packetBuffer = NULL; + 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, lastBufferSize); + + return STATUS_SUCCESS; +} + +_Use_decl_annotations_ +PAGED_CODE_SEG +NTSTATUS +COffloadStreamEngine::AssignDrmContentId( + ULONG, + PACXDRMRIGHTS DrmRights +) +{ + PAGED_CODE(); + + // + // 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_ +#pragma code_seg() +VOID +COffloadStreamEngine::s_EvtBufferReadTimerCallback( + WDFTIMER Timer +) +{ + COffloadStreamEngine * This; + PSTREAM_TIMER_CONTEXT timerCtx; + + // Get our stream engine pointer from the timer context + timerCtx = GetStreamTimerContext(Timer); + This = (COffloadStreamEngine *)timerCtx->StreamEngine; + + // Call the BufferReadCallback for the engine + This->BufferReadCallback(); +} + +// Callback indicating buffer read complete +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::BufferReadCallback() +{ + // Save the time at which we moved to the next packet + ULONGLONG qpcCompleted; + qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; + + ULONG packetsWritten = (ULONG)InterlockedCompareExchange((LONG*)&m_PacketsWritten, -1, -1); + + // We've completed a packet! Increment our currently active packet + ULONG packetsRead = (ULONG)InterlockedIncrement((LONG *)&m_PacketsRead) - 1; + + // + // \TODO + // Detect if hardware has cycled more than the OS. + // Can happen if application doesn't write data on time + // + if(packetsRead > packetsWritten) + { + DrvLogError(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine starved PacketsWritten: %08d, Packets Read: %08d", packetsWritten, packetsRead); + } + + // Tell ACX we've completed the packet. + // 0 based packet count + (void)AcxRtStreamNotifyPacketComplete(m_Stream, (ULONGLONG)packetsRead, qpcCompleted); + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::BufferReadCallback packet complete - %d", packetsRead); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::s_EvtLastBufferTimerCallback( + WDFTIMER Timer +) +{ + COffloadStreamEngine * This; + PSTREAM_TIMER_CONTEXT timerCtx; + + // Get our stream engine pointer from the timer context + timerCtx = GetStreamTimerContext(Timer); + This = (COffloadStreamEngine *)timerCtx->StreamEngine; + + // Call the LastBufferRenderComplete for the engine + This->LastBufferRenderComplete(); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::LastBufferRenderComplete() +{ + // Save the time at which we moved to the next packet + ULONGLONG qpcCompleted; + qpcCompleted = (ULONGLONG)KeQueryPerformanceCounter(NULL).QuadPart; + + ULONGLONG completedPacket; + completedPacket = (ULONG)InterlockedIncrement((LONG*)&m_PacketsRead) - 1; + + // Tell ACX we've completed the packet. + (void)AcxRtStreamNotifyPacketComplete(m_Stream, completedPacket, qpcCompleted); + DrvLogVerbose(g_SDCAVDspLog, FLAG_STREAM, L"COffloadStreamEngine::LastBufferRenderComplete packet complete - %d", (ULONG)completedPacket); +} + +_Use_decl_annotations_ +#pragma code_seg() +VOID +COffloadStreamEngine::ProcessPacket() +{ +} + |
