@@ -55,13 +55,18 @@ extern std::string wstring_to_utf8(const std::wstring& wstr);
5555 Class definition of CVideoDevice
5656**************************************************************************************************************************/
5757
58- CVideoDeviceInstance_Win32::CVideoDeviceInstance_Win32 (const std::string& sIdentifier , const std::string& sOSName , const std::string& sFriendlyName )
58+ CVideoDeviceInstance_Win32::CVideoDeviceInstance_Win32 (const std::string& sIdentifier , const std::string& sOSName , const std::string& sFriendlyName , LibMCEnv::PDriverEnvironment pDriverEnvironment )
5959 : m_sIdentifier (sIdentifier ),
6060 m_nCurrentResolutionX (0 ),
6161 m_nCurrentResolutionY (0 ),
6262 m_nCurrentFrameRate (0 ),
63- m_SourceFormat (eVideoSourceFormat::Invalid)
63+ m_SourceFormat (eVideoSourceFormat::Invalid),
64+ m_pDriverEnvironment (pDriverEnvironment),
65+ m_bCapturing (false ),
66+ m_dDesiredFramerateInFPS (0.0 )
6467{
68+ if (pDriverEnvironment.get () == nullptr )
69+ throw ELibMCDriver_CameraInterfaceException (LIBMCDRIVER_CAMERA_ERROR_INVALIDPARAM );
6570
6671#ifdef _WIN32
6772
@@ -184,12 +189,19 @@ CVideoDeviceInstance_Win32::CVideoDeviceInstance_Win32(const std::string& sIdent
184189
185190std::string GUIDToString (const GUID & guid) {
186191 wchar_t guidString[39 ] = { 0 }; // GUID string format is 38 characters long plus null terminator
187- StringFromGUID2 (guid, guidString, 39 );
192+ int nCharsWritten = StringFromGUID2 (guid, guidString, 39 );
193+ if (nCharsWritten == 0 )
194+ throw ELibMCDriver_CameraInterfaceException (LIBMCDRIVER_CAMERA_ERROR_INVALIDPARAM , " could not convert GUID to string" );
188195 guidString[38 ] = 0 ;
189- std::wstring sResultStringW (guidString);
190- std::string sResultString (sResultStringW .begin (), sResultStringW .end ());
191196
192- return sResultString ;
197+ // GUID strings are pure ASCII, so a simple narrowing loop is safe
198+ std::string sResult ;
199+ sResult .reserve (38 );
200+ for (int i = 0 ; i < 38 && guidString[i] != 0 ; i++) {
201+ sResult .push_back (static_cast <char >(guidString[i]));
202+ }
203+
204+ return sResult ;
193205}
194206#endif
195207
@@ -256,7 +268,7 @@ void CVideoDeviceInstance_Win32::refreshSupportedResolutions()
256268
257269CVideoDeviceInstance_Win32::~CVideoDeviceInstance_Win32 ()
258270{
259-
271+ stopStreamCapture ();
260272}
261273
262274std::string CVideoDeviceInstance_Win32::getFriendlyName ()
@@ -557,21 +569,140 @@ void CVideoDeviceInstance_Win32::setResolution(uint32_t nWidth, uint32_t nHeight
557569#endif // _WIN32
558570}
559571
572+ void CVideoDeviceInstance_Win32::captureThreadFunction ()
573+ {
574+ while (m_bCapturing.load ()) {
575+ try {
576+ auto pImage = m_pDriverEnvironment->CreateEmptyImage (
577+ m_nCurrentResolutionX, m_nCurrentResolutionY,
578+ 72.0 , 72.0 , LibMCEnv::eImagePixelFormat::RGB24bit);
579+
580+ auto startTime = std::chrono::high_resolution_clock::now ();
581+
582+ if (captureRawImage (pImage)) {
583+ m_pActiveStream->PushFrame (pImage.get ());
584+ }
585+
586+ auto endTime = std::chrono::high_resolution_clock::now ();
587+ double dFrameDurationInSeconds = std::chrono::duration<double >(endTime - startTime).count ();
588+
589+ {
590+ std::lock_guard<std::mutex> lock (m_StatisticsMutex);
591+ m_FrameDurationsInSeconds.push_back (dFrameDurationInSeconds);
592+ }
593+
594+ // Sleep for the remaining frame time if capture was faster than desired
595+ if (m_nCurrentFrameRate > 0 ) {
596+ double dDesiredDurationInSeconds = 1.0 / (double )m_nCurrentFrameRate;
597+ double dRemainingInSeconds = dDesiredDurationInSeconds - dFrameDurationInSeconds;
598+ if (dRemainingInSeconds > 0.0 ) {
599+ auto nSleepMicroseconds = (uint64_t )(dRemainingInSeconds * STREAMCAPTURE_MICROSECONDS_PER_SECOND );
600+ std::this_thread::sleep_for (std::chrono::microseconds (nSleepMicroseconds));
601+ }
602+ }
603+ }
604+ catch (...) {
605+ // If a single frame fails, continue capturing rather than killing the thread.
606+ // A brief sleep avoids busy-looping on persistent errors.
607+ std::this_thread::sleep_for (std::chrono::milliseconds (10 ));
608+ }
609+ }
610+ }
611+
560612void CVideoDeviceInstance_Win32::startStreamCapture (LibMCEnv::PVideoStream pStreamInstance)
561613{
614+ if (pStreamInstance.get () == nullptr )
615+ throw ELibMCDriver_CameraInterfaceException (LIBMCDRIVER_CAMERA_ERROR_INVALIDPARAM );
616+
617+ if ((m_nCurrentResolutionX == 0 ) || (m_nCurrentResolutionY == 0 ) || (m_nCurrentFrameRate == 0 ))
618+ throw ELibMCDriver_CameraInterfaceException (LIBMCDRIVER_CAMERA_ERROR_CAMERARESOLUTIONNOTSET );
619+
620+ // Validate that camera resolution matches the video stream dimensions
621+ uint32_t nStreamWidth = pStreamInstance->GetWidth ();
622+ uint32_t nStreamHeight = pStreamInstance->GetHeight ();
623+ if (nStreamWidth != m_nCurrentResolutionX || nStreamHeight != m_nCurrentResolutionY)
624+ throw ELibMCDriver_CameraInterfaceException (LIBMCDRIVER_CAMERA_ERROR_INVALIDPARAM ,
625+ " video stream dimensions (" + std::to_string (nStreamWidth) + " x" + std::to_string (nStreamHeight) +
626+ " ) do not match camera resolution (" + std::to_string (m_nCurrentResolutionX) + " x" + std::to_string (m_nCurrentResolutionY) + " )" );
627+
628+ // Stop any existing capture before starting a new one
629+ stopStreamCapture ();
630+
631+ m_pActiveStream = pStreamInstance;
632+ m_dDesiredFramerateInFPS = (double )m_nCurrentFrameRate;
633+
634+ {
635+ std::lock_guard<std::mutex> lock (m_StatisticsMutex);
636+ m_FrameDurationsInSeconds.clear ();
637+ }
638+
639+ m_bCapturing.store (true );
640+ m_pCaptureThread = std::make_unique<std::thread>(&CVideoDeviceInstance_Win32::captureThreadFunction, this );
562641}
563642
564643void CVideoDeviceInstance_Win32::stopStreamCapture ()
565644{
645+ m_bCapturing.store (false );
646+
647+ if (m_pCaptureThread && m_pCaptureThread->joinable ()) {
648+ m_pCaptureThread->join ();
649+ }
650+
651+ m_pCaptureThread.reset ();
652+ m_pActiveStream = nullptr ;
566653}
567654
568655bool CVideoDeviceInstance_Win32::streamCaptureIsActive ()
569656{
570- return false ;
657+ return m_bCapturing. load ();
571658}
572659
573660void CVideoDeviceInstance_Win32::getStreamCaptureStatistics (LibMCDriver_Camera_double & dDesiredFramerate, LibMCDriver_Camera_double & dMinFramerate, LibMCDriver_Camera_double & dMaxFramerate, LibMCDriver_Camera_double & dMeanFramerate, LibMCDriver_Camera_double & dStdDevFramerate)
574661{
662+ std::lock_guard<std::mutex> lock (m_StatisticsMutex);
663+
664+ dDesiredFramerate = m_dDesiredFramerateInFPS;
665+ dMinFramerate = 0.0 ;
666+ dMaxFramerate = 0.0 ;
667+ dMeanFramerate = 0.0 ;
668+ dStdDevFramerate = 0.0 ;
669+
670+ if (m_FrameDurationsInSeconds.size () < STREAMCAPTURE_STATISTICS_MIN_FRAME_COUNT )
671+ return ;
672+
673+ // Compute min/max/mean of actual frame durations, then convert to FPS
674+ double dMinDuration = m_FrameDurationsInSeconds[0 ];
675+ double dMaxDuration = m_FrameDurationsInSeconds[0 ];
676+ double dSumDuration = 0.0 ;
677+
678+ for (const auto & dDuration : m_FrameDurationsInSeconds) {
679+ if (dDuration < dMinDuration)
680+ dMinDuration = dDuration;
681+ if (dDuration > dMaxDuration)
682+ dMaxDuration = dDuration;
683+ dSumDuration += dDuration;
684+ }
685+
686+ double dMeanDuration = dSumDuration / (double )m_FrameDurationsInSeconds.size ();
687+
688+ // Standard deviation of durations
689+ double dVarianceSum = 0.0 ;
690+ for (const auto & dDuration : m_FrameDurationsInSeconds) {
691+ double dDiff = dDuration - dMeanDuration;
692+ dVarianceSum += dDiff * dDiff;
693+ }
694+ double dStdDevDuration = std::sqrt (dVarianceSum / (double )m_FrameDurationsInSeconds.size ());
695+
696+ // Convert durations to framerates (shorter duration = higher framerate)
697+ if (dMinDuration > 0.0 )
698+ dMaxFramerate = 1.0 / dMinDuration;
699+ if (dMaxDuration > 0.0 )
700+ dMinFramerate = 1.0 / dMaxDuration;
701+ if (dMeanDuration > 0.0 ) {
702+ dMeanFramerate = 1.0 / dMeanDuration;
703+ // Propagate std dev from duration to framerate via |df/dt| = 1/t^2
704+ dStdDevFramerate = dStdDevDuration / (dMeanDuration * dMeanDuration);
705+ }
575706}
576707
577708
0 commit comments