/************************************************************************** A/V Stream Camera Sample Copyright (c) 2001, Microsoft Corporation. File: hwsim.cpp Abstract: This file contains the implementation of CHardwareSimulation used to simulate a camera's capture stream. Our simulation cannot mimic a real camera with any precision. A real camera would likely have dedicated hardware to provide interrupts, do DMA transfers of frames from an internal buffer as well as do scaling and on-the-fly colorspace and planar format conversions so as to supply frames for preview, video and photo in varying resolutions and colorspaces. This simulation does not have such hardware, so instead it synthesizes a frame on the fly for each local pin as required. The frame is synthesized from common state information, so the images produced should be comparable from pin to pin. CHardwareSimulation is used as a base class for each simulated pin / hardware. History: created 3/9/2001 **************************************************************************/ #include "Common.h" /************************************************************************** PAGEABLE CODE **************************************************************************/ #ifdef ALLOC_PRAGMA #pragma code_seg("PAGE") #endif // ALLOC_PRAGMA inline CHwSimTimer:: CHwSimTimer( _In_ CHardwareSimulation *Parent ) : KPassiveTimer( Parent->m_Sensor->GetDeviceObject() ) , m_Parent( Parent ) /*++ Routine Description: CHwSimTimer object construction. Arguments: Parent - The parent CHardwareSimulation. Return Value: None --*/ { PAGED_CODE(); } void CHwSimTimer::Handler( _In_opt_ PVOID Context ) /*++ Routine Description: Punts to the HardwareSim... Arguments: Context - Our parent CHardwareSimulation. Return Value: None --*/ { PAGED_CODE(); ((CHardwareSimulation *) Context)->FakeHardware(); } /*************************************************/ CHardwareSimulation:: CHardwareSimulation ( _Inout_ CSensor *Sensor, _In_ LONG PinID ) : m_Sensor (Sensor) , m_IsrTimer(this) , m_ScatterGatherMappingsMax (SCATTER_GATHER_MAPPINGS_MAX) , m_TimePerFrame(ONESECOND/30) , m_PinID(PinID) , m_Synthesizer(nullptr) , m_Width(0) , m_Height(0) , m_ImageSize(0) , m_PinState(PinStopped) , m_NumMappingsCompleted(0) , m_ScatterGatherMappingsQueued(0) , m_ScatterGatherBytesQueued(0) , m_NumFramesSkipped(0) , m_InterruptTime(0) , m_LastReportedExposureTime(DEF_EXPOSURE_TIME) // Assume the default exposure time for now. , m_LastReportedWhiteBalance(0) , m_FaceDetectionDelay(1) // Start out reporting immediately. , m_LastFaceDetect() /*++ Routine Description: CHardwareSimulation Construction. Arguments: Sensor - The parent CSensor object that contains state for the controls. PinID - The pin index. Return Value: None --*/ { PAGED_CODE(); // // Initialize the timer's, and locks necessary to simulate this hardware. // InitializeListHead (&m_ScatterGatherMappings); // // Initialize the entry lookaside. // ExInitializeNPagedLookasideList ( &m_ScatterGatherLookaside, NULL, NULL, POOL_NX_ALLOCATION, sizeof (SCATTER_GATHER_ENTRY), 'nEGS', 0 ); m_StartTime.QuadPart = 0; } CHardwareSimulation:: ~CHardwareSimulation() { PAGED_CODE(); // Cancel the timer, just in case its running. m_IsrTimer.Cancel(); // // Delete the scatter / gather lookaside list. // ExDeleteNPagedLookasideList (&m_ScatterGatherLookaside); } /*************************************************/ BOOLEAN CHardwareSimulation:: Initialize() /*++ Routine Description: Initialize the hardware simulation Arguments: [none] Return Value: TRUE - CHardwareSimulation is fully initialized. --*/ { PAGED_CODE(); return m_IsrTimer.IsValid() ; } /*************************************************/ NTSTATUS CHardwareSimulation:: Start ( IN CSynthesizer *ImageSynth, IN LONGLONG TimePerFrame, IN ULONG Width, IN ULONG Height, IN ULONG ImageSize ) /*++ Routine Description: Start the hardware simulation. This will kick the interrupts on, begin issuing DPC's, filling in capture information, etc... We keep track of starvation starting at this point. Arguments: ImageSynth - The image synthesizer to use to generate pictures to display on the capture buffer. TimePerFrame - The time per frame... we issue interrupts this often. Width - The image width Height - The image height ImageSize - The size of the image. We allocate a temporary scratch buffer based on this size to fake hardware. Return Value: Success / Failure (typical failure will be out of memory on the scratch buffer, etc...) --*/ { PAGED_CODE(); // Prevent state-changes during this call. KScopedMutex Lock(m_ListLock); NTSTATUS Status = STATUS_SUCCESS; m_Synthesizer = ImageSynth; m_TimePerFrame = TimePerFrame; m_ImageSize = ImageSize; m_Height = Height; m_Width = Width; m_NumMappingsCompleted = 0; m_ScatterGatherMappingsQueued = 0; m_NumFramesSkipped = 0; m_InterruptTime = 0; KeQuerySystemTime (&m_StartTime); if( !m_Synthesizer->Initialize() ) { Status = STATUS_INSUFFICIENT_RESOURCES; } // // If everything is ok, start issuing interrupts. // if (NT_SUCCESS (Status)) { LARGE_INTEGER NextTime; NextTime.QuadPart = m_StartTime.QuadPart + m_TimePerFrame; m_PinState = PinRunning; m_IsrTimer.Set( NextTime ); } return Status; } /*************************************************/ NTSTATUS CHardwareSimulation:: Pause ( BOOLEAN Pausing ) /*++ Routine Description: Pause the hardware simulation... When the hardware simulation is told to pause, it stops stops the timer, but it does not reset the counters. Arguments: Pausing - Indicates whether the hardware is pausing or not. TRUE - Pause the hardware FALSE - Unpause the hardware from a previous pause Return Value: Success / Failure --*/ { PAGED_CODE(); { // Prevent state-changes during this call. KScopedMutex Lock(m_ListLock); if (Pausing && m_PinState == PinRunning) { // // If we were running, stop completing mappings, etc... // m_PinState = PinPaused; } else if (!Pausing && m_PinState == PinPaused) { // // For unpausing the hardware, we need to compute the relative time // and restart interrupts. // LARGE_INTEGER UnpauseTime; KeQuerySystemTime (&UnpauseTime); m_InterruptTime = (LONGLONG) ( (UnpauseTime.QuadPart - m_StartTime.QuadPart) / m_TimePerFrame ); UnpauseTime.QuadPart = m_StartTime.QuadPart + (m_InterruptTime + 1) * m_TimePerFrame; m_PinState = PinRunning; m_IsrTimer.Set( UnpauseTime ); } } // If we are pausing, it's best to stop the timer and not wait for another frame. if( Pausing ) { m_IsrTimer.Cancel(); } return STATUS_SUCCESS; } NTSTATUS CHardwareSimulation:: Stop() /*++ Routine Description: Stop the hardware simulation... For us, stop the timer, free the synthesizer and flush the queue. Arguments: None Return Value: Success / Failure --*/ { PAGED_CODE(); // // If the hardware is told to stop while it's running, we need to // halt the interrupts first. If we're already paused, this has // already been done. // DBG_ENTER("(): m_PinID=%d", m_PinID); BOOLEAN bCancel = FALSE; { // Prevent state-changes during this call. KScopedMutex Lock(m_ListLock); bCancel = (m_PinState == PinRunning); m_PinState = PinStopped; // Free the synthesis buffer. m_Synthesizer->Destroy(); // // Free S/G buffer // FreeSGList( &m_ScatterGatherMappings, L"StreamPointer Stop SG List" ); m_ScatterGatherMappingsQueued=0; m_NumMappingsCompleted = 0; m_ScatterGatherBytesQueued = 0; } // If running, stop the timer to make sure we don't try to deliver one last frame. if( bCancel ) { m_IsrTimer.Cancel(); } return STATUS_SUCCESS; } NTSTATUS CHardwareSimulation:: Reset() { PAGED_CODE(); KScopedMutex Lock( m_ListLock ); DBG_ENTER("(): m_PinID=%d", m_PinID); FreeSGList( &m_ScatterGatherMappings, L"StreamPointer Reset SG List" ); m_ScatterGatherMappingsQueued=0; m_NumMappingsCompleted = 0; m_ScatterGatherBytesQueued = 0; DBG_LEAVE("(): m_PinID=%d", m_PinID); return STATUS_SUCCESS; } NTSTATUS CHardwareSimulation:: Reset (IN PKSPIN Pin) { PAGED_CODE(); NTSTATUS status = STATUS_SUCCESS; if(Pin->ResetState == KSRESET_END) { KsPinAttemptProcessing(Pin, TRUE); } else { status = Reset(); } return status; } ULONG CHardwareSimulation:: ReadNumberOfMappingsCompleted() /*++ Routine Description: Read the number of scatter / gather mappings which have been completed since the last reset of the simulated hardware Arguments: None Return Value: Total number of completed mappings. --*/ { PAGED_CODE(); return m_NumMappingsCompleted; } /*************************************************/ ULONG CHardwareSimulation:: ProgramScatterGatherMappings ( IN PKSSTREAM_POINTER *Clone, IN PUCHAR *Buffer, IN PKSMAPPING Mappings, IN ULONG MappingsCount, IN ULONG MappingStride ) /*++ Routine Description: Add a single entry to our queue. In practice our simulation can only accept one frame at a time, but we still base this code off of the avshws sample. Arguments: Buffer - The virtual address of the buffer mapped by the mapping list ClonePointer The KSStreamClone Pointer to be associated with the entry item Mappings - The KSMAPPINGS array corresponding to the buffer MappingsCount - The number of mappings in the mappings array MappingStride - The mapping stride used in initialization of AVStream DMA Return Value: Number of mappings actually inserted. --*/ { PAGED_CODE(); ULONG MappingsInserted = 0; DBG_ENTER("(Clone=%p, Buffer=%p, MappingsCount=%d)", Clone, Buffer, MappingsCount); // // Protect our S/G list with a spinlock. // KScopedMutex Lock( m_ListLock ); // // Loop through the scatter / gather list and break the buffer up into // chunks equal to the scatter / gather mappings. Stuff the virtual // addresses of these chunks on a list somewhere. We update the buffer // pointer the caller passes as a more convenient way of doing this. // // If I could just remap physical in the list to virtual easily here, // I wouldn't need to do it. // do { PSCATTER_GATHER_ENTRY Entry = reinterpret_cast ( ExAllocateFromNPagedLookasideList ( &m_ScatterGatherLookaside ) ); if (!Entry) { break; } Entry ->CloneEntry = nullptr; Entry -> Virtual = nullptr; Entry -> ByteCount = 0; Entry ->CloneEntry = *Clone; Entry -> CloneEntry -> StreamHeader -> PresentationTime.Time = 0; Entry -> CloneEntry -> StreamHeader -> OptionsFlags &= ~KSSTREAM_HEADER_OPTIONSF_TIMEVALID; Entry -> Virtual = *Buffer; Entry -> ByteCount = MappingsCount; Entry->PhotoConfirmationInfo = PHOTOCONFIRMATION_INFO(); // // Move forward a specific number of bytes in chunking this into // mapping sized va buffers. // *Buffer += MappingsCount; Mappings = reinterpret_cast ( (reinterpret_cast (Mappings) + MappingStride) ); InsertTailList (&m_ScatterGatherMappings, &(Entry -> ListEntry)); m_ScatterGatherMappingsQueued++; MappingsInserted = MappingsCount; DBG_TRACE("m_ScatterGatherMappingsQueued=%d", m_ScatterGatherMappingsQueued); m_ScatterGatherBytesQueued += MappingsCount; } while(FALSE); DBG_LEAVE(" MappingsInserted=%d", MappingsInserted ); return MappingsInserted; } /************************************************************************** The following are helper functions used in the production of metadata. Since this is just a simulation, most "auto" modes get converted into a random value that is in range. While simple, this still requires writing a helper function for each piece of data. **************************************************************************/ // // Translate Exposure settings into a "current value". // LONGLONG CHardwareSimulation:: GetCurrentExposureTime() { PAGED_CODE(); ISP_FRAME_SETTINGS *pSettings = GetIspSettings(); LONGLONG Value = 0; LPCSTR Mode = "[UNKNOWN]"; if( pSettings->ExposureMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_AUTO ) { // Get random value in global setting's bound (LONG) // I'm abandoning the reported min/max and using something more reasonable. Value = GetRandom( MIN_EXPOSURE_TIME*5, DEF_EXPOSURE_TIME*5 ); Mode = "KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_AUTO"; } if( pSettings->ExposureMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_MANUAL ) { Value = pSettings->ExposureSetting.VideoProc.Value.ll; Mode = "KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_MANUAL"; } // Locked just reports the last value set... if( pSettings->ExposureMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_LOCK ) { Value = m_LastReportedExposureTime; Mode = "KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_LOCK"; } DBG_TRACE("ExposureMode=0x%016llX (%s), Time=%llu00ns, LastReported=%llu00ns", pSettings->ExposureMode, Mode, Value, m_LastReportedExposureTime ); m_LastReportedExposureTime = Value; return Value; } // // Translate White Balance into a "current value". // ULONG CHardwareSimulation:: GetCurrentWhiteBalance() { PAGED_CODE(); ISP_FRAME_SETTINGS *pSettings = GetIspSettings(); ULONG Value = 0; if( pSettings->WhiteBalanceMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_AUTO ) { Value = (ULONG) GetRandom( pSettings->WhiteBalanceSetting.Min, pSettings->WhiteBalanceSetting.Max ); } if( pSettings->WhiteBalanceMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_MANUAL ) { Value = pSettings->WhiteBalanceSetting.VideoProc.Value.ul; } // Locked just reports the last value set... if( pSettings->WhiteBalanceMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_LOCK ) { Value = m_LastReportedWhiteBalance; } m_LastReportedWhiteBalance= Value; return Value; } // // Translate ISO settings into a "current value". // ULONG CHardwareSimulation:: GetCurrentISOSpeed() { PAGED_CODE(); ISP_FRAME_SETTINGS *pSettings = GetIspSettings(); ULONG Value = 0; if( pSettings->ISOMode & KSCAMERA_EXTENDEDPROP_ISO_AUTO ) { Value = GetRandom( (ULONG) 50, (ULONG) 3200 ); } else if( pSettings->ISOMode & KSCAMERA_EXTENDEDPROP_ISO_MANUAL ) { Value = pSettings->ISOValue; } else { Value = // Try converting any legacy presets to a manual value. IsoPreset2Value( pSettings->ISOMode ); } return Value; } // // Translate Exposure Mode to an Exif Exposure Program value // USHORT CHardwareSimulation:: GetExposureProgram() { PAGED_CODE(); ISP_FRAME_SETTINGS *pSettings = GetIspSettings(); USHORT Value = 0; // Undefined. if( pSettings->ExposureMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_MANUAL ) { Value = 1; // Manual } if( pSettings->ExposureMode & KSCAMERA_EXTENDEDPROP_VIDEOPROCFLAG_AUTO ) { Value = 2; // Normal } // TODO: Extend SceneMode to ISP settings so I can use it here to capture Portrait mode, etc. return Value; } /*************************************************/ // // Emit metadata here that is common to all pins. // // All derived classes' EmitMetadata() function must // call this function first. // void CHardwareSimulation:: EmitMetadata( _Inout_ PKSSTREAM_HEADER pStreamHeader ) { PAGED_CODE(); NT_ASSERT(pStreamHeader); if (0 != (pStreamHeader->OptionsFlags & KSSTREAM_HEADER_OPTIONSF_METADATA)) { PKS_FRAME_INFO pFrameInfo = reinterpret_cast(reinterpret_cast(pStreamHeader) + sizeof(KSSTREAM_HEADER)); PKSSTREAM_METADATA_INFO pMetadata = reinterpret_cast(reinterpret_cast(pFrameInfo) + sizeof(KS_FRAME_INFO)); //PBYTE pData = (PBYTE) pMetadata->SystemVa; //ULONG BytesLeft = pMetadata->BufferSize; // A real driver might write focus state or other info here. We've got nothing. pMetadata->UsedSize = 0; } else { DBG_TRACE("Metadata not present..."); } } bool CHardwareSimulation:: CheckForAvailableBuffer() { PAGED_CODE(); return true; } NTSTATUS CHardwareSimulation:: CommitImageData(PSCATTER_GATHER_ENTRY sGEntry, ULONG stride) { PAGED_CODE(); // Have the synthesizer output a frame to the buffer. ULONG CommitBufferSize = sGEntry->ByteCount; PUCHAR CommitBufferAddress = sGEntry->Virtual; ULONG BytesCopied = m_Synthesizer->DoCommit(CommitBufferAddress, CommitBufferSize, stride); NT_ASSERT(BytesCopied); DBG_TRACE("BytesCopied = %d", BytesCopied); return STATUS_SUCCESS; } NTSTATUS CHardwareSimulation:: ValidateBuffer(PSCATTER_GATHER_ENTRY sGEntry) { PAGED_CODE(); return STATUS_SUCCESS; } void CHardwareSimulation:: EmitFaceMetadata( _Inout_ PKSSTREAM_HEADER pStreamHeader, _In_ ULONG Count, _In_ ULONGLONG Flags, _In_ ULONG DelayLimit ) /*++ Routine Description: Helper function that emits FaceDetection metadata. Note: This function assumes a metadata buffer is present. Arguments: pStreamHeader - The frame's stream header. Count - The maximum number of faces to detect. Flags - A combination of the KSCAMERA_EXTENDEDPROP_FACEDETECTION_BLINK and KSCAMERA_EXTENDEDPROP_FACEDETECTION_BLINK flags passed to the face detection control. Used to decide what characterization data to present in the metadata. DelayLimit - The maximum number of frames to delay before generating the next face detection. Return Value: None. --*/ { PAGED_CODE(); NT_ASSERT(pStreamHeader); NT_ASSERT(Count <= SIZEOF_ARRAY(m_LastFaceDetect.Data)); // Do nothing if we're turned on, but they want no faces. if( Count==0 ) { return; } if( --m_FaceDetectionDelay != 0 || // Count down our random delay for new face data. Count > m_LastFaceDetect.Count ) // Make sure we never emit more faces than requested. { ULONG CountReported = GetRandom( 0, Count ); DBG_TRACE("Generating new faces (Count=%d, Flags=0x%016llX, Delay=%d)...", Count, Flags, DelayLimit); // Synthesize a new set of "detected" faces... m_LastFaceDetect.Header.MetadataId = (ULONG) MetadataId_Custom_FaceDetection; m_LastFaceDetect.Header.Size = sizeof(CAMERA_METADATA_FACEHEADER) + (sizeof(METADATA_FACEDATA) * CountReported); m_LastFaceDetect.Count = CountReported; m_LastFaceDetect.Flags = Flags; m_LastFaceDetect.Timestamp = pStreamHeader->PresentationTime.Time ; DBG_TRACE("Count=%d, Flags=0x%016llX, Time=0x%016llX", m_LastFaceDetect.Count, m_LastFaceDetect.Flags, m_LastFaceDetect.Timestamp ); // Report face data... for( ULONG i=0; i(reinterpret_cast(pFrameInfo) + sizeof(KS_FRAME_INFO)); ULONG BytesLeft = pMetadata->BufferSize - pMetadata->UsedSize; // Write Face Detection Info here // Note: If we derived Video and Preview from a common base class, we // might use that to emit this information for both types. if( BytesLeft >= m_LastFaceDetect.Header.Size ) { // Always write the most recently generated face detection info. RtlCopyMemory( ((PBYTE)pMetadata->SystemVa) + pMetadata->UsedSize, &m_LastFaceDetect, m_LastFaceDetect.Header.Size ); DBG_TRACE("Emitting Face metadata..."); BytesLeft -= m_LastFaceDetect.Header.Size; pMetadata->UsedSize += m_LastFaceDetect.Header.Size; } else { DBG_TRACE("WARNING: Out of metadata buffer..."); } } // // PIN_STATE text for debug output. // static const char * GetPinStateTxt( PIN_STATE state ) { PAGED_CODE(); switch( state ) { case PinStopped: return "STOPPED"; case PinPaused: return "PAUSED"; case PinRunning: return "RUNNING"; } return "[UNKNOWN]"; } NTSTATUS CHardwareSimulation:: FillScatterGatherBuffers() /*++ Routine Description: The hardware has synthesized a buffer and we're to fill a frame buffer. Arguments: None Return Value: Success / Failure --*/ { PAGED_CODE(); DBG_ENTER("() m_PinState=%s, m_PinID=0x%08X, m_ImageSize=0x%08X, m_ScatterGatherMappingsQueued=%u, " "m_ScatterGatherBytesQueue=0x%08X", GetPinStateTxt(m_PinState), m_PinID, m_ImageSize, m_ScatterGatherMappingsQueued, m_ScatterGatherBytesQueued); NTSTATUS ntStatus = STATUS_SUCCESS; ULONG BufferRemaining = m_ImageSize; // // If there isn't a frame buffer queued, we just skip the frame and consider // it starvation. // while (BufferRemaining && !IsListEmpty(&m_ScatterGatherMappings) && (!CheckForAvailableBuffer() || m_ScatterGatherBytesQueued >= BufferRemaining)) { LIST_ENTRY *listEntry = RemoveHeadList(&m_ScatterGatherMappings); m_ScatterGatherMappingsQueued--; PSCATTER_GATHER_ENTRY SGEntry = reinterpret_cast ( CONTAINING_RECORD ( listEntry, SCATTER_GATHER_ENTRY, ListEntry ) ); // Deal with cancellation. PIRP pIrp = KsStreamPointerGetIrp(SGEntry->CloneEntry, FALSE, FALSE); if (pIrp) { if (pIrp->Cancel) { DBG_TRACE( "Cancelling..." ); FreeSGEntry( listEntry, L"StreamPointer Cancel SG List" ); continue; } } // // Since we're software, we'll be accessing this by virtual address... // ULONG Stride = 0; if ( SGEntry->CloneEntry -> StreamHeader -> Size >= sizeof (KSSTREAM_HEADER) + sizeof (KS_FRAME_INFO)) { PKS_FRAME_INFO FrameInfo = reinterpret_cast (SGEntry->CloneEntry->StreamHeader+1); Stride = (ULONG) ABS(FrameInfo->lSurfacePitch); } ntStatus = ValidateBuffer(SGEntry); if (!NT_SUCCESS(ntStatus)) { break; } ntStatus = CommitImageData(SGEntry, Stride); //Adding time stamp if(m_PhotoConfirmationEntry.isRequired()) { SGEntry -> CloneEntry -> StreamHeader -> PresentationTime.Time = m_PhotoConfirmationEntry.getTime(); DBG_TRACE("Using Photo Confirmation time = 0x%016llX", m_PhotoConfirmationEntry.getTime()); } else { SGEntry -> CloneEntry -> StreamHeader -> PresentationTime.Time = ConvertQPCtoTimeStamp(NULL); DBG_TRACE("PresentationTime = 0x%016llX", SGEntry->CloneEntry->StreamHeader->PresentationTime.Time ); } SGEntry -> CloneEntry -> StreamHeader -> PresentationTime.Numerator = SGEntry -> CloneEntry -> StreamHeader -> PresentationTime.Denominator = 1; SGEntry -> CloneEntry -> StreamHeader -> OptionsFlags |= KSSTREAM_HEADER_OPTIONSF_TIMEVALID ; // Add metadata to the sample. EmitMetadata( SGEntry -> CloneEntry -> StreamHeader ); BufferRemaining = 0; //-= BytesCopied; m_NumMappingsCompleted++; m_ScatterGatherBytesQueued -= SGEntry -> ByteCount; // // Release the scatter / gather entry back to our lookaside. // ExFreeToNPagedLookasideList ( &m_ScatterGatherLookaside, reinterpret_cast (SGEntry) ); } // Report an error if we used the last buffer. if (NT_SUCCESS(ntStatus) && BufferRemaining) { DBG_TRACE("BufferRemaining=%u", BufferRemaining); ntStatus = STATUS_INSUFFICIENT_RESOURCES; } DBG_LEAVE("() = 0x%08X", ntStatus); return ntStatus; } /************************************************************************** Debug helpers **************************************************************************/ const CHAR * DVS_Text( ULONGLONG Flags ) { PAGED_CODE(); switch( Flags ) { case KSCAMERA_EXTENDEDPROP_VIDEOSTABILIZATION_OFF: return "Off"; case KSCAMERA_EXTENDEDPROP_VIDEOSTABILIZATION_ON: return "On"; case KSCAMERA_EXTENDEDPROP_VIDEOSTABILIZATION_AUTO: return "Auto"; default: { static CHAR buffer[32]; RtlStringCbPrintfA(buffer, sizeof(buffer), "Unknown [0x%016llX]", Flags); return (const CHAR *) buffer; } } } const CHAR * OIS_Text( ULONGLONG Flags ) { PAGED_CODE(); switch( Flags ) { case KSCAMERA_EXTENDEDPROP_OIS_OFF: return "Off"; case KSCAMERA_EXTENDEDPROP_OIS_ON: return "On"; case KSCAMERA_EXTENDEDPROP_OIS_AUTO: return "Auto"; default: { static CHAR buffer[32]; RtlStringCbPrintfA(buffer, sizeof(buffer), "Unknown [0x%016llX]", Flags); return (const CHAR *) buffer; } } } /**************************************************************************/ void CHardwareSimulation:: FakeHardware ( ) /*++ Routine Description: Simulate an interrupt and what the hardware would have done in the time since the previous interrupt. Arguments: None Return Value: None --*/ { PAGED_CODE(); KScopedMutex Lock(m_ListLock); m_InterruptTime++; // // The hardware can be in a pause state in which case, it issues interrupts // but does not complete mappings. In this case, don't bother synthesizing // a frame and doing the work of looking through the mappings table. // if (m_PinState == PinRunning) { LONGLONG Qpc = (LONGLONG)ConvertQPCtoTimeStamp(nullptr); LARGE_INTEGER Now; KeQuerySystemTimePrecise(&Now); // // Generate a "time stamp" just to overlay it onto the capture image. // It makes it more exciting than bars that do nothing. // // Note: Streaming pins have their own indexes; but still pins track // the most recent streaming pin's index. if (!m_Sensor->IsStillIndex(m_PinID)) { CSensor::SynthesizerAttributeEntry AttributeList[] = { { CSynthesizer::FrameNumber, m_InterruptTime, m_PinID }, { CSynthesizer::RelativePts, (m_InterruptTime + 1) * m_TimePerFrame, m_PinID }, { CSynthesizer::QpcTime, Qpc, m_PinID } }; DBG_TRACE("QPC=0x%016llX", Qpc); // Broadcast the preview pin's info to all pin simulations. m_Sensor->SetSynthesizerAttributeList(SIZEOF_ARRAY(AttributeList), AttributeList); } m_Synthesizer->DoSynthesize(); CHAR Text[64]; CExtendedProperty Control; m_Sensor->GetVideoStabilization(&Control); RtlStringCbPrintfA(Text, sizeof(Text), "DVS: %s", DVS_Text(Control.Flags)); m_Synthesizer->OverlayText(0, m_Height - 38, 1, Text, TRANSPARENT, TEXT_COLOR); m_Sensor->GetOpticalImageStabilization(&Control); RtlStringCbPrintfA(Text, sizeof(Text), "OIS: %s", OIS_Text(Control.Flags)); m_Synthesizer->OverlayText(0, m_Height - 48, 1, Text, TRANSPARENT, TEXT_COLOR); // // Add the Missed frame count RtlStringCbPrintfA(Text, sizeof(Text), "%lld Missed", m_NumFramesSkipped); size_t len = 0; RtlStringCchLengthA(Text, sizeof(Text), &len); m_Synthesizer->OverlayText( (m_Width - (((ULONG)len * 8))), // right-adjust text. (m_Height - 48), 1, Text, TRANSPARENT, TEXT_COLOR ); // // Add the estimated FPS LONGLONG Target = NANOSECONDS / m_TimePerFrame; LONGLONG FPS = ((LONGLONG)m_InterruptTime * NANOSECONDS) / ((Now.QuadPart - m_StartTime.QuadPart) + (NANOSECONDS / 2)); RtlStringCbPrintfA(Text, sizeof(Text), "%lld/%lld FPS", FPS, Target); len = 0; RtlStringCchLengthA(Text, sizeof(Text), &len); m_Synthesizer->OverlayText( (m_Width - (((ULONG)len * 8))), // right-adjust text. (m_Height - 38), 1, Text, TRANSPARENT, TEXT_COLOR ); // // Fill scatter gather buffers // if (!NT_SUCCESS(FillScatterGatherBuffers())) { m_NumFramesSkipped++; } } // // Issue an interrupt to our hardware sink. This is a "fake" interrupt. // It will occur at DISPATCH_LEVEL. // m_Sensor->Interrupt(m_PinID); // // Schedule the timer for the next interrupt time, if the pin is still running. // if (m_PinState == PinRunning) { LARGE_INTEGER NextTime; NextTime.QuadPart = m_StartTime.QuadPart + (m_TimePerFrame * (m_InterruptTime + 1)); #ifdef ENABLE_TRACING // To keep us from a tight spin when trying to debug this code... LARGE_INTEGER Now; KeQuerySystemTime(&Now); if (Now.QuadPart >= NextTime.QuadPart) { NextTime.QuadPart = 0LL - m_TimePerFrame; } #endif m_IsrTimer.Set(NextTime); } } ULONGLONG CHardwareSimulation:: ConvertQPCtoTimeStamp( _In_opt_ PLARGE_INTEGER pQpcTime ) /*++ Routine Description: Get the QPC measured in 100ns units. Arguments: pQpcTime - An optional time value to convert. If NULL, use the current QPC. Return Value: ULONLONG - The time in 100ns increments. --*/ { PAGED_CODE(); LARGE_INTEGER qpcTime = {0}; LARGE_INTEGER qpcFrequency; qpcTime = KeQueryPerformanceCounter(&qpcFrequency); if( pQpcTime ) { qpcTime = *pQpcTime; } return KSCONVERT_PERFORMANCE_TIME( qpcFrequency.QuadPart, qpcTime ); } NTSTATUS CHardwareSimulation:: GeneratePhotoConfirmation( _In_ ULONG PfsFrameNumber, _In_ LONGLONG time ) /*++ Routine Description: So a photo sim can ask a preview sim for a confirmation frame. Arguments: PfsFrameNumber - The VPS shot number. time - The precise time of the associated photo. Return Value: None --*/ { PAGED_CODE(); KScopedMutex Lock( m_ListLock ); if( PinRunning != m_PinState ) { return STATUS_DEVICE_NOT_READY; } DBG_ENTER("( Index=%d, Time=0x%016llX )", PfsFrameNumber, time ); m_PhotoConfirmationEntry = PHOTOCONFIRMATION_INFO( PfsFrameNumber, time ); // We basically used the previously synthesized preview image NTSTATUS status = FillScatterGatherBuffers(); //Clean up the photo confiramtion flag m_PhotoConfirmationEntry = PHOTOCONFIRMATION_INFO(); if(NT_SUCCESS(status)) { m_Sensor->Interrupt (m_PinID); } DBG_LEAVE("() = 0x%08X", status); return status; } void CHardwareSimulation:: FreeSGEntry( _In_ PLIST_ENTRY Entry, _In_ PCWSTR EventName, _In_opt_ LPCGUID Activity, _In_ ULONG DataUsed ) { PAGED_CODE(); PSCATTER_GATHER_ENTRY SGEntry = reinterpret_cast ( CONTAINING_RECORD ( Entry, SCATTER_GATHER_ENTRY, ListEntry ) ); NT_ASSERT(SGEntry->CloneEntry); NT_ASSERT(SGEntry->CloneEntry->StreamHeader); SGEntry->CloneEntry->StreamHeader->DataUsed = DataUsed; KsStreamPointerDelete(SGEntry->CloneEntry); // // Release the scatter / gather entry back to our lookaside. // ExFreeToNPagedLookasideList ( &m_ScatterGatherLookaside, SGEntry ); } void CHardwareSimulation:: FreeSGList( _In_ PLIST_ENTRY List, _In_ PCWSTR EventName, _In_opt_ LPCGUID Activity ) { PAGED_CODE(); while(!IsListEmpty(List)) { PLIST_ENTRY head = RemoveHeadList(List); m_ScatterGatherMappingsQueued--; FreeSGEntry( head, EventName, Activity ); } } // // Get the current frame settings. // ISP_FRAME_SETTINGS * CHardwareSimulation:: GetIspSettings(void) { PAGED_CODE(); return m_Sensor->GetGlobalIspSettings(); }