#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>
// 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";
// 全局变量
std::atomic<bool> g_bExit(false);
std::atomic<bool> g_bIsRecording(false);
std::mutex g_capMutex; // 控制取流与录像共享资源
cv::VideoWriter g_videoWriter; // OpenCV 视频写入器
std::thread g_recordThread; // 单独的录像线程(管理生命周期)
// 获取临时目录作为 fallback
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);
}
}
// 设置像素格式为 Mono8
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;
}
// 独立的录像线程函数(使用 OpenCV 写视频)
void RecordWithOpenCV(void* handle, int width, int height, float fps, const std::string& filePath)
{
printf("🎥 Recording with OpenCV: %s\n", filePath.c_str());
// 使用 MJPG 四字符编码(MJPEG)
g_videoWriter.open(filePath, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), fps, cv::Size(width, height), true);
if (!g_videoWriter.isOpened())
{
printf("❌ Failed to open VideoWriter for %s\n", filePath.c_str());
g_bIsRecording = false;
return;
}
const int totalFrames = static_cast<int>(fps * 10); // 录 10 秒
MV_FRAME_OUT stImageInfo = { 0 };
int recordedCount = 0;
for (int i = 0; i < totalFrames && !g_bExit && g_bIsRecording; ++i)
{
int nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 1000);
if (nRet == MV_OK)
{
MV_FRAME_OUT_INFO_EX* pInfo = &stImageInfo.stFrameInfo;
if (pInfo->enPixelType == PixelType_Gvsp_Mono8)
{
cv::Mat gray(pInfo->nHeight, pInfo->nWidth, CV_8UC1, stImageInfo.pBufAddr);
cv::Mat bgr;
cv::cvtColor(gray, bgr, cv::COLOR_GRAY2BGR);
g_videoWriter.write(bgr);
recordedCount++;
}
MV_CC_FreeImageBuffer(handle, &stImageInfo);
}
else
{
printf("⚠️ Timeout getting frame during recording.\n");
}
}
g_videoWriter.release();
printf("⏹️ Recording saved: %s (%d frames)\n", filePath.c_str(), recordedCount);
g_bIsRecording = false;
}
// 主工作线程:显示 + 触发录像
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;
while (!g_bExit)
{
if (g_bIsRecording)
{
Sleep(10);
continue;
}
nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 1000);
if (nRet == MV_OK)
{
MV_FRAME_OUT_INFO_EX* pInfo = &stImageInfo.stFrameInfo;
cv::Mat gray(pInfo->nHeight, pInfo->nWidth, CV_8UC1, stImageInfo.pBufAddr);
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)
{
// 启动 OpenCV 录像
g_bIsRecording = true;
// 获取参数
int width = 0, height = 0;
AlignImageSize(handle, width, height);
float fps = 25.0f;
MVCC_FLOATVALUE stFloat = { 0 };
if (MV_CC_GetFloatValue(handle, "ResultingFrameRate", &stFloat) == MV_OK)
fps = stFloat.fCurValue;
std::string outputDir = GetOutputDirectory();
if (outputDir.empty())
{
printf("❌ No valid output directory!\n");
g_bIsRecording = false;
}
else
{
char filePath[512];
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
struct tm tm;
localtime_s(&tm, &t);
sprintf_s(filePath, "%s\\rec_%04d%02d%02d_%02d%02d%02d.avi",
outputDir.c_str(),
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
printf("🔔 Starting recording via OpenCV...\n");
try {
g_recordThread = std::thread(RecordWithOpenCV, handle, width, height, fps, std::string(filePath));
g_recordThread.detach(); // 生产环境建议用 thread 容器管理
}
catch (...)
{
printf("❌ Failed to start record thread!\n");
g_bIsRecording = false;
}
}
}
MV_CC_FreeImageBuffer(handle, &stImageInfo);
}
}
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 10 seconds video using OpenCV\n");
printf(" ➡ Saved to: %s\n", DESKTOP_TEST_DIR);
printf(" ➡ Uses MJPEG inside AVI container\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;
}
代码解读