(5)c++ __msvc_chrono.hpp 时间类:is_clock_v<T>何时为真,system_clock 码,steady_clock 码,duration 码,time_point 码

C++20 STL库时钟与时间相关源码解析

(11) is_clock_v , 为真时对 模板参数 T 的要求是什么:

在这里插入图片描述

++ 可见,系统定义的时钟类型,应该具有上图中的结构,下面是 is_clock_v 的定义:

在这里插入图片描述

++ 猜测,只要 void_t<T…> 的参数包中,有一个类型参数推断不出来,结果就不应该为 true 。 编写例子测试一下 vs2019 的 c++ 编译器的工作逻辑:

在这里插入图片描述

++ 补充 void_t<T…> 的工作原理:

在这里插入图片描述

(12) system_clock 源代码,因为这些常用的重要的类型的定义,所以单独列出,以便查阅:

    // 系统时钟可以修改,甚至可以网络对时,因此使用系统时间计算时间差可能不准。
    struct system_clock   // 返回的是距离 1970 年的时间值
    { 
        using rep = long long; // sizeof( long long ) = 8 字节
        using period = ratio<1, 10'000'000>; // 100 nanoseconds = 100 纳秒
        using duration = _CHRONO duration<rep, period>; // _CHRONO  =  ::std::chrono::
        using time_point = _CHRONO time_point<system_clock>;

        static constexpr bool is_steady = false;

        // _CRTIMP2_PURE long long __cdecl _Xtime_get_ticks(); 
        // 返回的是距离 1970 年的时间值, 以 100 纳秒  为单位
        _NODISCARD static time_point now() noexcept  // get current time
        { 
            return time_point(duration(_Xtime_get_ticks()));
        }

        _NODISCARD static __time64_t to_time_t(const time_point& _Time) noexcept //拿到时间里的数值
        {   // convert to __time64_t ;   __time64_t = long long 
            return duration_cast<seconds>(_Time.time_since_epoch()).count();
        }

        // using seconds  = duration<long long>;  // 以  秒为计量单位
        _NODISCARD static time_point from_time_t(__time64_t _Tm) noexcept // 构造一个时间值
        {   // convert from __time64_t
            return time_point{ seconds{_Tm} };
        }
    };

(13) steady_clock 的源代码

//固定时钟,相当于秒表。开始计时后,时间只会增长并且不能修改,适合用于记录程序耗时
    struct steady_clock   // wraps QueryPerformanceCounter 返回的是本主机已经开机的时长,以 1 纳秒 为单位
    {
        using rep = long long;
        using period = nano;     // using nano  = ratio<1, 1000000000>; 可见精度更高,纳秒
        using duration = nanoseconds; // using nanoseconds  = duration<long long, nano>;
        using time_point = _CHRONO time_point<steady_clock>;
        static constexpr bool is_steady = true;

        // 本类只有这一个成员函数,返回的是本主机已经开机的时长,以 1 纳秒 为单位
        _NODISCARD static time_point now() noexcept // get current time
        { 
            const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot
            const long long _Ctr  = _Query_perf_counter  ();
            static_assert(period::num == 1, "This assumes period::num == 1.");

            // 10 MHz是现代PC上非常常见的 QPC 频率。
            // 优化该特定频率可以避免昂贵的频率转换路径,从而使该功能的性能翻倍。
            constexpr long long _TenMHz = 10'000'000;  // 定义了一个数, 10兆

            if (_Freq == _TenMHz) {
                static_assert(period::den % _TenMHz == 0, "It should never fail.");

                constexpr long long _Multiplier = period::den / _TenMHz; // 等于 100

                return time_point(duration(_Ctr * _Multiplier)); // 从 10^7 兆放大到纳秒,乘以 100
            } else {
                const long long _Whole = (_Ctr / _Freq) * period::den;
                const long long _Part = (_Ctr % _Freq) * period::den / _Freq;
                return time_point( duration(_Whole + _Part) );
            }
        }
    };
   
    using high_resolution_clock = steady_clock; // 和时钟类 steady_clock是等价的(是它的别名)。

(14) duration 时间值的 常见简化定义

	template <intmax_t _Nx, intmax_t _Dx = 1>  // 默认分母为一
	struct ratio {};

	template <  class _Rep, class _Period = ratio<1>  >
	class duration;

	template <class _Rep, class _Period> //  代表一个时间持续时间
	class duration
	{
    	_Rep _MyRep; // 存储的时间值

    	template < class _Rep2 >    // 有参构造函数
    	duration(const _Rep2& _Val) : _MyRep( static_cast<_Rep>(_Val) ) {}
	}    
    
	using nanoseconds  = duration<long long, nano>
    using microseconds = duration<long long, micro>;
    using milliseconds = duration<long long, milli>;  // 以毫秒为计量单位
    using seconds      = duration<long long>;         // 以  秒为计量单位
    using minutes      = duration<int, ratio<60>>;    // 以  分为计量单位
    using hours        = duration<int, ratio<3600>>;  // 以  时为计量单位
#if _HAS_CXX20
    using days   = duration<int, ratio_multiply<ratio<24>, hours::period>>; // 以 天 为计量单位 24*3600
    using weeks  = duration<int, ratio_multiply<ratio<7>, days::period>>;   // 以 周 为计量单位
    using years  = duration<int, ratio_multiply<ratio<146097, 400>, days::period>>;
    using months = duration<int, ratio_divide<years::period, ratio<12>>>;   // 以 月 为计量单位
#endif // _HAS_CXX20

++ 以下给出 duration 的完整定义

    template <class _Rep, class _Period> // represents a time duration 代表一个时间持续时间
    class duration 
    { 
    private:
        _Rep _MyRep; // the stored rep
    public:
        using rep = _Rep;  // 去掉了前置的下划线
        using period = typename _Period::type; //= ratio<num, den>
		// template <intmax_t _Nx, intmax_t _Dx = 1>  // numerator 分子,被除数 ; denominator 分母
		// struct ratio {  using type = ratio<num, den>; }; // 这个 type 保存了简化版的比率

        static_assert(!_Is_duration_v<_Rep>, "duration can't have duration as first template argument");
        static_assert(_Is_ratio_v<_Period>, "period not an instance of std::ratio");
        static_assert(0 < _Period::num, "period negative or zero"); // 时间只能往将来

        // 默认构造函数
        constexpr duration() = default; 

        template <class _Rep2, enable_if_t<  // 把 _Rep2 类型的变量转换为 _Rep 类型存入类的数据成员
               is_convertible_v<const _Rep2& , _Rep> && 
            (treat_as_floating_point_v<_Rep> || !treat_as_floating_point_v<_Rep2>)
                                             , int > = 0 >
        constexpr explicit duration(const _Rep2& _Val)  // 有参构造函数
            noexcept( is_arithmetic_v<_Rep> && is_arithmetic_v<_Rep2>) // strengthened
            : _MyRep(static_cast<_Rep>(_Val)) {}

        // #define _CHRONO    ::std::chrono::
        // 此处 enable_if_t 的意思是,希望本类的数值是浮点类型;
        // 若都是整数类型,则希望形参的 _Period2 可以更大,可以被本 duration 的计量周期整除。 
        template <class _Rep2, class _Period2, enable_if_t<
                treat_as_floating_point_v< _Rep > || // 或,看来更喜欢本类的模板参数是浮点类型,数值范围更大
            (_Ratio_divide_sfinae<_Period2, _Period>::den == 1 && !treat_as_floating_point_v<_Rep2> )
                                                            , int > = 0 >
        constexpr duration(const duration<_Rep2, _Period2>& _Dur)      // copy 构造函数
            noexcept( is_arithmetic_v<_Rep> && is_arithmetic_v<_Rep2>)
            : _MyRep(_CHRONO duration_cast<duration>(_Dur).count()) {}

        // 返回存储的本类的数据成员 
        constexpr _Rep count() const noexcept(is_arithmetic_v<_Rep>) {  return _MyRep;  }

        // 以下的成员函数,省略 constexpr 与 is_arithmetic_v<_Rep> 的判断,突出重点。

        // 经测试,复杂的泛型推导,简单的结果:
        // common_type_t<  duration<int>  > = duration<int>
        // 取正与取负运算符:  duration<int> a ; b = +a ; c = -a ; 就是此运算符函数
        common_type_t<duration> operator+() {  return common_type_t<duration>(*this); }

        common_type_t<duration> operator-() {  return common_type_t<duration>(-_MyRep);    }

        duration&  operator++() {   ++_MyRep ;   return *this ;  }  // ++a
        duration&  operator--() {   --_MyRep;    return *this;   }  // --a
        duration   operator++(int) {   return  duration(_MyRep++);  }   // a++
        duration   operator--(int) {   return  duration(_MyRep--);  }   // a-- 

        duration&  operator+= (const duration& _Right) { _MyRep += _Right._MyRep; return *this; }
        duration&  operator-= (const duration& _Right) { _MyRep -= _Right._MyRep; return *this; }
        duration&  operator*= (const     _Rep& _Right) { _MyRep *= _Right;        return *this; }
        duration&  operator/= (const     _Rep& _Right) { _MyRep /= _Right;        return *this; }
        duration&  operator%= (const     _Rep& _Right) { _MyRep %= _Right;        return *this; }
        duration&  operator%= (const duration& _Right) { _MyRep %= _Right.count();return *this; }

        static duration zero() { return duration( duration_values<_Rep>::zero() ); } // get zero value
        static duration min () { return duration( duration_values<_Rep>::min() ) ; } // get minimum value
        static duration max () { return duration( duration_values<_Rep>::max() ) ; } // get maximum value
    };

(15) time_point ,表示某种时钟计时下的时间值

	// 第一个模板参数Clock用来指定所要使用的时钟,
    // 标准库中有三种时钟,system_clock,steady_clock 和 high_resolution_clock 。
    // 看定义可知,只需要指定模板类的模板参数一,第二个参数不指定的话就默认采用形参一里的计时单位
    template <class _Clock, class _Duration = typename _Clock::duration>
    class time_point   // represents a point in time
    { 
    private:   // 先对数据成员初始化为 0
        _Duration _MyDur{ duration::zero() }; // duration since the epoch 纪元以来持续的时间
    public:
        using clock = _Clock;        // 类型定义,去掉了模板形参类名的前置下划线 _
        using duration = _Duration;  // 类型定义,去掉了模板形参类名的前置下划线 _
        using rep = typename _Duration::rep;
        using period = typename _Duration::period;

        //   template <class _Ty> // 测试 _Ty 是否是 duration 模板的子类
        //   constexpr bool _Is_duration_v = _Is_specialization_v<_Ty, duration>;
        static_assert(_Is_duration_v<_Duration>, // N4885 ...l]/1授权 Duration为chrono::duration 的具体化。
            "N4885 [time.point.general]/1 mandates Duration to be a specialization of chrono::duration.");

        constexpr time_point() = default;  // 默认的空的构造函数
        // 以下开始省略多余的函数修饰符,突出重点

        explicit time_point(const _Duration& _Other) : _MyDur(_Other) {}  // 带参的构造函数

        template <class _Duration2, enable_if_t<is_convertible_v<_Duration2, _Duration>, int> = 0>
        time_point(const time_point<_Clock, _Duration2>& _Tp)             // copy 构造函数
            noexcept( is_arithmetic_v<rep> && is_arithmetic_v<typename _Duration2::rep>) // strengthened
            : _MyDur(_Tp.time_since_epoch()) {}

        _Duration time_since_epoch() {    return _MyDur;    }  // 纪元,返回自己的数据成员的值

#if _HAS_CXX20
        time_point&  operator++()    {   ++_MyDur;   return *this;    } // ++a
        time_point&  operator--()    {   --_MyDur;   return *this;    } // --a
        time_point   operator++(int) {   return time_point{ _MyDur++ };    } // a++
        time_point   operator--(int) {   return time_point{ _MyDur-- };    } // a--
#endif // _HAS_CXX20

        time_point& operator+=(const _Duration& _Dur) {  _MyDur += _Dur;   return *this;  }
        time_point& operator-=(const _Duration& _Dur) {  _MyDur -= _Dur;   return *this;  }

        static  time_point  min()  {   return time_point(  _Duration::min() );   }
        static  time_point  max()  {   return time_point(  _Duration::max() );   }
    };

(16)

谢谢

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <Windows.h> #include <process.h> #include <conio.h> #include <thread> #include <atomic> #include <mutex> #include <string> #include <chrono> #include <vector> #include <queue> #include <condition_variable> // OpenCV #include <opencv2/opencv.hpp> // 海康 SDK #include "MvCameraControl.h" #pragma comment(lib, "MvCameraControl.lib") #pragma comment(lib, "opencv_world480.lib") // 修改为你的版本 // ==================== 配置参数 ==================== const char* const DESKTOP_TEST_DIR = "C:\\Users\\guolin12\\Desktop\\test"; const double RECORD_DURATION_SECONDS = 10.0; // 想录几秒? const size_t MAX_QUEUE_SIZE = 30; // 最大队列长度(防内存溢出) // ======================================== // 图像数据结构 // ======================================== struct ImageFrame { std::vector<uint8_t> data; MV_FRAME_OUT_INFO_EX info; std::chrono::steady_clock::time_point timestamp; // 用于调试时间间隔 }; // ======================================== // 全局变量 // ======================================== std::atomic<bool> g_bExit(false); std::atomic<bool> g_bIsRecording(false); std::mutex g_queueMutex; std::queue<ImageFrame> g_frameQueue; std::condition_variable g_cvQueue; int g_width = 0, g_height = 0; float g_fps = 25.0f; // 将从设备读取实际值 // 获取临时路径 std::string GetTempPathStr() { char path[MAX_PATH] = { 0 }; GetTempPathA(MAX_PATH, path); return std::string(path); } bool CreateDirectoryRecursive(const std::string& dir) { if (CreateDirectoryA(dir.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS) return true; size_t pos = dir.find_last_of("\\/"); if (pos != std::string::npos) { std::string parent = dir.substr(0, pos); if (CreateDirectoryRecursive(parent)) { return CreateDirectoryA(dir.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS; } } return false; } bool IsDirectoryWritable(const std::string& dir) { if (!CreateDirectoryRecursive(dir)) return false; std::string testFile = dir + "\\__test_write__.tmp"; FILE* f = nullptr; errno_t err = fopen_s(&f, testFile.c_str(), "w"); if (err == 0 && f) { fclose(f); DeleteFileA(testFile.c_str()); return true; } return false; } std::string GetOutputDirectory() { std::string target(DESKTOP_TEST_DIR); if (IsDirectoryWritable(target)) { printf("✅ Using output directory: %s\n", target.c_str()); return target; } else { printf("❌ Cannot write to %s\n", target.c_str()); std::string temp = GetTempPathStr(); if (IsDirectoryWritable(temp)) { printf("💡 Falling back to: %s\n", temp.c_str()); return temp; } printf("❌ All paths unwritable!\n"); return ""; } } void PrintDeviceInfo(MV_CC_DEVICE_INFO* pstDevInfo) { if (pstDevInfo->nTLayerType == MV_GIGE_DEVICE) { unsigned int ip = pstDevInfo->SpecialInfo.stGigEInfo.nCurrentIp; printf("IP: %d.%d.%d.%d\n", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); printf("Model: %s\n", pstDevInfo->SpecialInfo.stGigEInfo.chModelName); } else if (pstDevInfo->nTLayerType == MV_USB_DEVICE) { printf("Model: %s\n", pstDevInfo->SpecialInfo.stUsb3VInfo.chModelName); printf("Serial: %s\n", pstDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber); } } int SetMono8Format(void* handle) { int nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_Mono8); if (nRet != MV_OK) { printf("❌ Failed to set PixelFormat to Mono8! [0x%x]\n", nRet); return nRet; } printf("✅ Pixel format set to Mono8.\n"); return MV_OK; } bool AlignImageSize(void* handle, int& width, int& height) { MVCC_INTVALUE stValue = { 0 }; if (MV_CC_GetIntValue(handle, "Width", &stValue) != MV_OK) { printf("❌ Failed to get Width\n"); return false; } width = (stValue.nCurValue / 32) * 32; MV_CC_SetIntValue(handle, "Width", width); printf("🔧 Set Width to %d\n", width); if (MV_CC_GetIntValue(handle, "Height", &stValue) != MV_OK) { printf("❌ Failed to get Height\n"); return false; } height = (stValue.nCurValue / 2) * 2; MV_CC_SetIntValue(handle, "Height", height); printf("🔧 Set Height to %d\n", height); return true; } // ✅ 修改:传入实帧率作为输出 fps void RecordFromQueue(const std::string& filePath, int width, int height, int targetFrameCount, float outputFps) { printf("🎥 Recording started: %s\n", filePath.c_str()); printf("🎯 Target frames: %d, Output FPS: %.3f → Duration: %.2f sec\n", targetFrameCount, outputFps, targetFrameCount / outputFps); // ✅ 关键修改:使用实帧率! cv::VideoWriter writer(filePath, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), outputFps, cv::Size(width, height), true); if (!writer.isOpened()) { printf("❌ Failed to open VideoWriter: %s\n", filePath.c_str()); g_bIsRecording = false; return; } int recorded = 0; auto start = std::chrono::steady_clock::now(); try { while (g_bIsRecording && !g_bExit && recorded < targetFrameCount) { std::unique_lock<std::mutex> lock(g_queueMutex); auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(3); // 更长等待时间 if (g_frameQueue.empty()) { if (g_cvQueue.wait_until(lock, timeout) == std::cv_status::timeout) { printf("📹 Timeout waiting for frame during recording.\n"); break; } } if (!g_frameQueue.empty()) { ImageFrame frame = std::move(g_frameQueue.front()); g_frameQueue.pop(); lock.unlock(); // 尽早释放锁 if (frame.info.enPixelType == PixelType_Gvsp_Mono8) { cv::Mat gray(frame.info.nHeight, frame.info.nWidth, CV_8UC1, frame.data.data()); cv::Mat bgr; cv::cvtColor(gray, bgr, cv::COLOR_GRAY2BGR); writer.write(bgr); recorded++; } } } } catch (const std::exception& e) { printf("❌ Exception during recording: %s\n", e.what()); } catch (...) { printf("❌ Unknown error during recording.\n"); } writer.release(); auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - start).count(); printf("⏹️ Saved video: %s\n", filePath.c_str()); printf("📊 Wrote %d frames at %.3f FPS → Play time: %.2f sec\n", recorded, outputFps, recorded / outputFps); printf("⏱️ Actual process time: %.3f sec\n", durationMs / 1000.0); g_bIsRecording = false; } // 主工作线程:唯一调用 GetImageBuffer 的地方 unsigned int __stdcall WorkThread(void* pUser) { void* handle = pUser; cv::namedWindow("Live View", cv::WINDOW_AUTOSIZE); MV_FRAME_OUT stImageInfo = { 0 }; int nRet = MV_OK; // 初始化分辨率和帧率 AlignImageSize(handle, g_width, g_height); MVCC_FLOATVALUE stFloat = { 0 }; if (MV_CC_GetFloatValue(handle, "ResultingFrameRate", &stFloat) == MV_OK) { g_fps = stFloat.fCurValue; printf("🎯 Actual Camera FPS: %.3f\n", g_fps); } else { printf("⚠️ Could not get FPS, using default %.2f\n", g_fps); } std::string outputDir = GetOutputDirectory(); while (!g_bExit) { nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 1000); if (nRet == MV_OK) { MV_FRAME_OUT_INFO_EX* pInfo = &stImageInfo.stFrameInfo; // 👉 统一拷贝图像数据 std::vector<uint8_t> imgData(stImageInfo.pBufAddr, stImageInfo.pBufAddr + pInfo->nFrameLen); // 显示处理 cv::Mat gray(pInfo->nHeight, pInfo->nWidth, CV_8UC1, imgData.data()); cv::Mat bgr; cv::cvtColor(gray, bgr, cv::COLOR_GRAY2BGR); std::string status = g_bIsRecording ? "🔴 RECORDING..." : "✅ LIVE STREAM"; cv::putText(bgr, status, cv::Point(20, 40), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2); cv::imshow("Live View", bgr); int key = cv::waitKey(1); if (key == 27 || key == 'q' || key == 'Q') { g_bExit = true; } else if ((key == 'r' || key == 'R') && !g_bIsRecording) { if (outputDir.empty()) { printf("❌ No valid output directory!\n"); } else { g_bIsRecording = true; // ✅ 使用实帧率计算目标帧数 int targetFrameCount = static_cast<int>(RECORD_DURATION_SECONDS * g_fps); printf("🎯 Start recording: %.3f FPS &times; %.1f sec = %d frames\n", g_fps, RECORD_DURATION_SECONDS, targetFrameCount); // 生成文件名(含毫秒) auto now = std::chrono::system_clock::now(); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000; std::time_t t = std::chrono::system_clock::to_time_t(now); struct tm tm; localtime_s(&tm, &t); char filePath[512]; sprintf_s(filePath, "%s\\rec_%04d%02d%02d_%02d%02d%02d_%03dms.avi", outputDir.c_str(), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)ms.count()); // 启动录像线程,传入实帧率 try { std::thread t(RecordFromQueue, std::string(filePath), g_width, g_height, targetFrameCount, g_fps); // ✅ 把实帧率传进去 t.detach(); } catch (const std::exception& e) { printf("❌ Failed to start thread: %s\n", e.what()); g_bIsRecording = false; } } } // 👉 如果正在录像,则入队 if (g_bIsRecording) { std::lock_guard<std::mutex> lock(g_queueMutex); // 控制队列大小,防止内存暴涨 while (g_frameQueue.size() >= MAX_QUEUE_SIZE) { g_frameQueue.pop(); } ImageFrame frame; frame.data = std::move(imgData); frame.info = *pInfo; frame.timestamp = std::chrono::steady_clock::now(); g_frameQueue.push(std::move(frame)); g_cvQueue.notify_one(); // 唤醒录像线程 } // ✅ 必须释放缓冲区 MV_CC_FreeImageBuffer(handle, &stImageInfo); } else { printf("⚠️ Timeout getting frame from camera.\n"); } } // 结束前通知所有等待线程退出 { std::lock_guard<std::mutex> lock(g_queueMutex); g_bIsRecording = false; } g_cvQueue.notify_all(); cv::destroyAllWindows(); return 0; } // 主函数 int main() { int nRet = MV_OK; void* handle = nullptr; HANDLE hThread = nullptr; do { nRet = MV_CC_Initialize(); if (nRet != MV_OK) { printf("❌ Initialize SDK failed! [0x%x]\n", nRet); break; } MV_CC_DEVICE_INFO_LIST stDevList = { 0 }; nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDevList); if (nRet != MV_OK || stDevList.nDeviceNum == 0) { printf("❌ No devices found! [0x%x]\n", nRet); break; } printf("✅ Found %d device(s):\n", stDevList.nDeviceNum); for (unsigned int i = 0; i < stDevList.nDeviceNum; ++i) { printf(" [%d]: ", i); PrintDeviceInfo(stDevList.pDeviceInfo[i]); } unsigned int index = 0; printf("👉 Input camera index (0-%d): ", stDevList.nDeviceNum - 1); scanf_s("%d", &index); if (index >= stDevList.nDeviceNum) { printf("❌ Invalid index!\n"); break; } nRet = MV_CC_CreateHandle(&handle, stDevList.pDeviceInfo[index]); if (nRet != MV_OK) { printf("❌ CreateHandle failed! [0x%x]\n", nRet); break; } nRet = MV_CC_OpenDevice(handle); if (nRet != MV_OK) { printf("❌ OpenDevice failed! [0x%x]\n", nRet); break; } if (stDevList.pDeviceInfo[index]->nTLayerType == MV_GIGE_DEVICE) { int packetSize = MV_CC_GetOptimalPacketSize(handle); if (packetSize > 0) { MV_CC_SetIntValueEx(handle, "GevSCPSPacketSize", packetSize); } } MV_CC_SetEnumValue(handle, "TriggerMode", 0); if (SetMono8Format(handle) != MV_OK) break; nRet = MV_CC_StartGrabbing(handle); if (nRet != MV_OK) { printf("❌ StartGrabbing failed! [0x%x]\n", nRet); break; } unsigned int threadId = 0; hThread = (HANDLE)_beginthreadex(nullptr, 0, WorkThread, handle, 0, &threadId); if (!hThread) { printf("❌ Failed to create work thread!\n"); break; } printf("\n📌 Instructions:\n"); printf(" ➡ Press 'R' to record %.1f seconds video\n", RECORD_DURATION_SECONDS); printf(" ➡ Live preview will NOT freeze!\n"); printf(" ➡ Output FPS matches actual camera FPS\n"); printf(" ➡ File name includes milliseconds to avoid overwrite\n"); printf(" ➡ Press 'Q' or ESC to exit\n\n"); while (!g_bExit) { Sleep(100); } } while (false); // 清理资源 g_bExit = true; if (hThread) { WaitForSingleObject(hThread, 5000); CloseHandle(hThread); } if (handle) { MV_CC_StopGrabbing(handle); MV_CC_CloseDevice(handle); MV_CC_DestroyHandle(handle); } MV_CC_Finalize(); printf("👋 Program exited. Press any key to close.\n"); _getch(); return 0; } 无法打开#include <opencv2/opencv.hpp>
最新发布
11-19
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangzhangkeji

谢谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值