#include <stdio.h>
#include <Windows.h>
#include <process.h>
#include <conio.h>
#include "MvCameraControl.h"
#ifndef MV_E_TIMEOUT
#define MV_E_TIMEOUT -7
#endif
// 等待按键输入
void WaitForKeyPress(void)
{
while (!_kbhit()) Sleep(10);
_getch();
}
// 全局变量
bool g_bExit = false;
bool g_bStartRecord = false; // 用户触发录制
long g_nTargetFrameCount = 0; // 目标帧数
long g_nRecordedFrames = 0; // 已录帧数
void* g_handle = nullptr;
float g_fFrameRate = 30.0f;
int g_nWidth = 640, g_nHeight = 480;
HWND g_hWnd = nullptr;
unsigned char* g_pDisplayBuffer = nullptr;
CRITICAL_SECTION g_csDisplay; // 保护显示缓冲区
const char* WINDOW_CLASS_NAME = "CameraLiveView";
const char* WINDOW_TITLE = "Camera Live View - Press 'R' to Record 10s";
// 函数声明
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
unsigned int __stdcall DisplayThread(void* lpParam);
// 创建显示窗口线程
// 显示窗口线程函数 - 仅运行窗口过程
unsigned int __stdcall DisplayThread(void* lpParam)
{
HINSTANCE hInstance = (HINSTANCE)lpParam;
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszClassName = WINDOW_CLASS_NAME;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
RegisterClassEx(&wcex);
g_hWnd = CreateWindow(
WINDOW_CLASS_NAME,
WINDOW_TITLE,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
g_nWidth + 20, g_nHeight + 60,
NULL, NULL, hInstance, NULL);
if (!g_hWnd) {
printf("Failed to create window!\n");
return 1;
}
ShowWindow(g_hWnd, SW_SHOW);
UpdateWindow(g_hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// 判断是否为彩色像素格式
bool IsColor(MvGvspPixelType enType)
{
switch (enType) {
case PixelType_Gvsp_BGR8_Packed:
case PixelType_Gvsp_YUV422_Packed:
case PixelType_Gvsp_YUV422_YUYV_Packed:
case PixelType_Gvsp_BayerGR8:
case PixelType_Gvsp_BayerRG8:
case PixelType_Gvsp_BayerGB8:
case PixelType_Gvsp_BayerBG8:
// 支持更多 Bayer 和 YUV 格式...
case PixelType_Gvsp_BayerGB10:
case PixelType_Gvsp_BayerBG10:
case PixelType_Gvsp_BayerRG10:
case PixelType_Gvsp_BayerGR10:
return true;
default:
return false;
}
}
// 窗口过程函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EnterCriticalSection(&g_csDisplay);
if (g_pDisplayBuffer && g_nWidth > 0 && g_nHeight > 0) {
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = g_nWidth;
bmi.bmiHeader.biHeight = -g_nHeight; // Top-down DIB
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;
SetDIBitsToDevice(hdc, 0, 0, g_nWidth, g_nHeight,
0, 0, 0, g_nHeight, g_pDisplayBuffer, &bmi, DIB_RGB_COLORS);
}
else {
TextOut(hdc, 10, 10, "No image data", 13);
}
LeaveCriticalSection(&g_csDisplay);
EndPaint(hWnd, &ps);
break;
}
case WM_KEYDOWN:
if (wParam == 'R' || wParam == 'r') {
if (!g_bStartRecord) {
g_bStartRecord = true;
g_nRecordedFrames = 0;
g_nTargetFrameCount = (long)(g_fFrameRate * 10.0f);
printf(">> Recording started: Target %ld frames (@ %.1ffps)\n", g_nTargetFrameCount, g_fFrameRate);
}
}
else if (wParam == VK_ESCAPE || wParam == 'Q' || wParam == 'q') {
g_bExit = true;
PostQuitMessage(0);
}
break;
case WM_DESTROY:
g_bExit = true;
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 打印设备信息
bool PrintDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo)
{
if (!pstMVDevInfo) return false;
if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE) {
DWORD ip = pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp;
printf("IP: %d.%d.%d.%d\n", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
printf("Name: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);
}
else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE) {
printf("Name: %s\nSN: %s\nDevNum: %d\n\n",
pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName,
pstMVDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber,
pstMVDevInfo->SpecialInfo.stUsb3VInfo.nDeviceNumber);
}
// 其他类型略...
return true;
}
// 主函数
int main()
{
int nRet = MV_OK;
void* handle = nullptr;
unsigned char* pConvertData = nullptr;
unsigned int nConvertDataSize = 0;
do {
InitializeCriticalSection(&g_csDisplay);
// 初始化 SDK
nRet = MV_CC_Initialize();
if (MV_OK != nRet) {
printf("Initialize SDK failed! nRet [0x%x]\n", nRet);
break;
}
// 枚举设备
MV_CC_DEVICE_INFO_LIST stDeviceList = { 0 };
nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
if (MV_OK != nRet || stDeviceList.nDeviceNum == 0) {
printf("No devices found or enum failed! nRet [0x%x]\n", nRet);
break;
}
for (unsigned int i = 0; i < stDeviceList.nDeviceNum; ++i) {
printf("[Device %u]:\n", i);
PrintDeviceInfo(stDeviceList.pDeviceInfo[i]);
}
unsigned int nIndex;
printf("Select camera index (0-%d): ", stDeviceList.nDeviceNum - 1);
scanf_s("%u", &nIndex);
if (nIndex >= stDeviceList.nDeviceNum) {
printf("Invalid index!\n");
break;
}
// 创建句柄
nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]);
if (MV_OK != nRet) {
printf("Create handle failed! nRet [0x%x]\n", nRet);
break;
}
g_handle = handle;
// 打开设备
nRet = MV_CC_OpenDevice(handle);
if (MV_OK != nRet) {
printf("Open device failed! nRet [0x%x]\n", nRet);
break;
}
// GigE 包大小优化
if (stDeviceList.pDeviceInfo[nIndex]->nTLayerType == MV_GIGE_DEVICE) {
int nPacketSize = MV_CC_GetOptimalPacketSize(handle);
if (nPacketSize > 0) {
MV_CC_SetIntValueEx(handle, "GevSCPSPacketSize", nPacketSize);
}
}
// 关闭触发模式
MV_CC_SetEnumValue(handle, "TriggerMode", 0);
// 获取图像参数
MVCC_INTVALUE stParam = { 0 };
MV_CC_GetIntValue(handle, "Width", &stParam); g_nWidth = stParam.nCurValue;
MV_CC_GetIntValue(handle, "Height", &stParam); g_nHeight = stParam.nCurValue;
MVCC_FLOATVALUE stFloat = { 0 };
MV_CC_GetFloatValue(handle, "ResultingFrameRate", &stFloat);
g_fFrameRate = (stFloat.fCurValue > 0.0f) ? stFloat.fCurValue : 30.0f;
// 初始化显示缓冲区与临界区
int imageSize = g_nWidth * g_nHeight * 3;
g_pDisplayBuffer = new unsigned char[imageSize];
memset(g_pDisplayBuffer, 0, imageSize);
InitializeCriticalSection(&g_csDisplay); // ✅ 必须初始化!
// 配置录像参数
MV_CC_RECORD_PARAM stRecordPar = {0};
stRecordPar.enPixelType = PixelType_Gvsp_BGR8_Packed; // 编码器期望 RGB/BGR 输入
stRecordPar.nWidth = g_nWidth;
stRecordPar.nHeight = g_nHeight;
stRecordPar.fFrameRate = g_fFrameRate;
stRecordPar.nBitRate = 5000;
stRecordPar.enRecordFmtType = MV_FormatType_AVI;
static char szFilePath[] = "./Recording.avi";
stRecordPar.strFilePath = szFilePath;
nRet = MV_CC_StartRecord(handle, &stRecordPar);
if (MV_OK != nRet) {
printf("Start record failed! nRet [0x%x]\n", nRet);
break;
}
// 启动取流
nRet = MV_CC_StartGrabbing(handle);
if (MV_OK != nRet) {
printf("Start grabbing failed! nRet [0x%x]\n", nRet);
break;
}
HANDLE hThread = (HANDLE)_beginthreadex(nullptr, 0, DisplayThread, GetModuleHandle(nullptr), 0, nullptr);
if (!hThread) {
printf("Create display thread failed!\n");
break;
}
// 主循环
MV_FRAME_OUT frame = { 0 };
while (!g_bExit) {
nRet = MV_CC_GetImageBuffer(handle, &frame, 1000);
if (nRet == MV_OK) {
MV_FRAME_OUT_INFO_EX* pInfo = &frame.stFrameInfo;
const unsigned char* pData = frame.pBufAddr;
// 转换为 BGR8 用于显示和录像
if (IsColor(pInfo->enPixelType)) {
nConvertDataSize = g_nWidth * g_nHeight * 3;
pConvertData = (unsigned char*)realloc(pConvertData, nConvertDataSize);
if (!pConvertData) {
printf("Memory allocation failed!\n");
break;
}
MV_CC_PIXEL_CONVERT_PARAM_EX convertParam = { 0 };
convertParam.nWidth = pInfo->nWidth;
convertParam.nHeight = pInfo->nHeight;
convertParam.pSrcData = const_cast<unsigned char*>(pData);
convertParam.nSrcDataLen = pInfo->nFrameLenEx;
convertParam.enSrcPixelType = pInfo->enPixelType;
convertParam.enDstPixelType = PixelType_Gvsp_BGR8_Packed;
convertParam.pDstBuffer = pConvertData;
convertParam.nDstBufferSize = nConvertDataSize;
MV_CC_SetBayerCvtQuality(handle, 1); // 插值质量
nRet = MV_CC_ConvertPixelTypeEx(handle, &convertParam);
if (nRet != MV_OK) {
printf("Convert failed! nRet [0x%x]\n", nRet);
}
else {
// 更新显示缓冲区(加锁)
EnterCriticalSection(&g_csDisplay);
memcpy(g_pDisplayBuffer, pConvertData, nConvertDataSize);
LeaveCriticalSection(&g_csDisplay);
InvalidateRect(g_hWnd, NULL, FALSE); // 触发重绘
}
}
// 输入帧到录像编码器
if (g_bStartRecord && g_nRecordedFrames < g_nTargetFrameCount) {
MV_CC_INPUT_FRAME_INFO inputInfo = { 0 };
inputInfo.pData = (unsigned char*)pData;
inputInfo.nDataLen = pInfo->nFrameLenEx;
nRet = MV_CC_InputOneFrame(handle, &inputInfo);
if (nRet == MV_OK) {
InterlockedIncrement(&g_nRecordedFrames);
}
else {
printf("Input frame failed! nRet [0x%x]\n", nRet);
}
}
else if (g_bStartRecord && g_nRecordedFrames >= g_nTargetFrameCount) {
MV_CC_StopRecord(handle);
g_bStartRecord = false;
printf("Recording finished. Saved %ld frames.\n", g_nRecordedFrames);
}
MV_CC_FreeImageBuffer(handle, &frame);
}
else if (nRet != MV_E_TIMEOUT) {
printf("Get image buffer failed! nRet [0x%x]\n", nRet);
}
Sleep(1);
}
// 清理
g_bStartRecord = false;
if (g_hWnd) PostMessage(g_hWnd, WM_CLOSE, 0, 0);
WaitForSingleObject(hThread, 3000);
CloseHandle(hThread);
MV_CC_StopGrabbing(handle);
if (g_bStartRecord || g_nRecordedFrames > 0) {
MV_CC_StopRecord(handle);
}
MV_CC_CloseDevice(handle);
MV_CC_DestroyHandle(handle);
DeleteCriticalSection(&g_csDisplay); // ✅ 销毁临界区
delete[] g_pDisplayBuffer;
free(pConvertData);
} while (0);
MV_CC_Finalize();
printf("Press any key to exit...\n");
WaitForKeyPress();
return 0;
}
分析代码、解读并优化
最新发布