#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 × %.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>