用c++生成了个通用的标准USB摄像头dll,包含上下左右镜像+亮度对比度调节功能
By
admin
at 2 小时前 • 0人收藏 • 6人看过

// pch.cpp: 与预编译标头对应的源文件
#include "pch.h"
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mferror.h>
#include <unordered_map>
#include <thread>
#include <atomic>
#include <mutex>
#include <strmif.h> // 包含 IAMVideoProcAmp 硬件控制接口
#pragma comment(lib, "strmiids.lib") // 链接对应的静态库
// 链接 Media Foundation 和 COM 必要的系统静态库
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "mf.lib") // <--- 解决 MFEnumDeviceSources 报错的关键
#pragma comment(lib, "ole32.lib") // <--- 解决 CoInitializeEx 等 COM 内存管理函数
// --- 内部数据结构,用于管理每个摄像头的状态 ---
struct CameraContext {
IMFSourceReader* pReader = nullptr;
HWND hwnd = nullptr;
int mirrorType = 0;
std::thread captureThread;
std::atomic<bool> isRunning{ false };
LONG width = 0;
LONG height = 0;
// 【新增】:硬件属性控制器
IAMVideoProcAmp* pProcAmp = nullptr;
};
// 全局变量:管理所有激活的摄像头
std::unordered_map<int, CameraContext*> g_Cameras;
std::mutex g_Mutex;
bool g_MfInitialized = false;
// --- 内部辅助函数:寻找并设置最高分辨率 ---
void SetMaxResolution(IMFSourceReader* pReader, LONG& outWidth, LONG& outHeight) {
DWORD bestIndex = 0;
LONG maxPixels = 0;
double maxFps = 0.0; // 新增:用于记录当前最高帧率
outWidth = 640; outHeight = 480; // 默认兜底
IMFMediaType* pType = nullptr;
for (DWORD i = 0; ; i++) {
HRESULT hr = pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, i, &pType);
if (FAILED(hr)) break; // 遍历结束
UINT32 w = 0, h = 0;
UINT32 fpsNumerator = 0, fpsDenominator = 1;
// 1. 获取分辨率
MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &w, &h);
// 2. 获取帧率 (分母不能为0的保护)
if (SUCCEEDED(MFGetAttributeRatio(pType, MF_MT_FRAME_RATE, &fpsNumerator, &fpsDenominator)) && fpsDenominator > 0) {
// 计算实际的浮点帧率
double currentFps = (double)fpsNumerator / fpsDenominator;
LONG currentPixels = w * h;
// 3. 核心筛选逻辑:
// 条件A: 如果像素更高,直接替换
// 条件B: 如果像素一样高,但帧率更高,也替换
if (currentPixels > maxPixels || (currentPixels == maxPixels && currentFps > maxFps)) {
maxPixels = currentPixels;
maxFps = currentFps;
bestIndex = i;
outWidth = w;
outHeight = h;
}
}
pType->Release();
}
// 应用筛选出的最高分辨率和帧率
if (maxPixels > 0) {
pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, bestIndex, &pType);
pReader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pType);
// 如果你在 aardio 开了控制台,可以用这句打印出来确认一下:
// printf("已锁定最高画质: %d x %d @ %.2f FPS\n", outWidth, outHeight, maxFps);
pType->Release();
}
}
// --- 内部后台线程:负责不断抓帧并渲染 ---
void CaptureThreadFunc(int deviceIndex) {
CoInitializeEx(NULL, COINIT_MULTITHREADED); // 线程初始化 COM
CameraContext* ctx = nullptr;
{
std::lock_guard<std::mutex> lock(g_Mutex);
if (g_Cameras.find(deviceIndex) != g_Cameras.end()) {
ctx = g_Cameras[deviceIndex];
}
}
if (!ctx) return;
// 准备 GDI 绘制所需的 BITMAP 信息 (RGB32 格式)
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = ctx->width;
bmi.bmiHeader.biHeight = -ctx->height; // 负数表示自顶向下的 DIB
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
while (ctx->isRunning) {
IMFSample* pSample = nullptr;
DWORD streamIndex, flags;
LONGLONG llTimeStamp;
// 读取一帧 (阻塞调用)
HRESULT hr = ctx->pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0, &streamIndex, &flags, &llTimeStamp, &pSample);
if (FAILED(hr) || !pSample) {
Sleep(10); // 避免 CPU 空转
continue;
}
// 提取图像数据
IMFMediaBuffer* pBuffer = nullptr;
if (SUCCEEDED(pSample->ConvertToContiguousBuffer(&pBuffer))) {
BYTE* pData = nullptr;
DWORD cbData = 0;
if (SUCCEEDED(pBuffer->Lock(&pData, nullptr, &cbData))) {
// 开始向目标句柄绘制
HDC hdc = GetDC(ctx->hwnd);
if (hdc) {
// 获取容器大小
RECT rect;
GetClientRect(ctx->hwnd, &rect);
int targetW = rect.right - rect.left;
int targetH = rect.bottom - rect.top;
// ==========================================================
// 【终极修复:1像素边缘溢出补偿 (Overscan)】
// 专门解决 Windows GDI 拉伸算法导致的 1 像素舍入黑边问题
// 故意让坐标向外扩张 1 像素,强制覆盖所有底色缝隙
// ==========================================================
int destX = 0, destY = 0;
int destW = 0, destH = 0;
switch (ctx->mirrorType) {
case 0: // 正常 (左上起点 -1, -1,宽高各加 2)
destX = -1; destY = -1;
destW = targetW + 2; destH = targetH + 2;
break;
case 1: // 上下翻转
case -1:
destX = -1; destY = targetH + 1;
destW = targetW + 2; destH = -(targetH + 2);
break;
case 2: // 左右翻转
case -2:
destX = targetW + 1; destY = -1;
destW = -(targetW + 2); destH = targetH + 2;
break;
case 3: // 全翻转
case -3:
destX = targetW + 1; destY = targetH + 1;
destW = -(targetW + 2); destH = -(targetH + 2);
break;
}
// 开启高质量平滑渲染,并重置画刷原点
SetStretchBltMode(hdc, HALFTONE);
SetBrushOrgEx(hdc, 0, 0, NULL);
// 执行绘制
StretchDIBits(hdc,
destX, destY, destW, destH, // 带有补偿的目标坐标
0, 0, ctx->width, ctx->height, // 源图像坐标
pData, &bmi, DIB_RGB_COLORS, SRCCOPY);
ReleaseDC(ctx->hwnd, hdc);
}
pBuffer->Unlock();
}
pBuffer->Release();
}
pSample->Release();
}
CoUninitialize();
}
// ==========================================================
// 向外导出的标准 API
// ==========================================================
// 注意开头的 void 改成了 bool
BOOL InitCamera(int deviceIndex, int mirrorType, int placeholderHWnd) {
std::lock_guard<std::mutex> lock(g_Mutex);
if (mirrorType < -3 || mirrorType > 3)
{
mirrorType = 0;
}
if (!g_MfInitialized) {
if (FAILED(MFStartup(MF_VERSION))) return FALSE; // 初始化底层失败
g_MfInitialized = true;
}
if (g_Cameras.find(deviceIndex) != g_Cameras.end()) {
g_Cameras[deviceIndex]->isRunning = false;
if (g_Cameras[deviceIndex]->captureThread.joinable()) {
g_Cameras[deviceIndex]->captureThread.join();
}
g_Cameras[deviceIndex]->pReader->Release();
delete g_Cameras[deviceIndex];
g_Cameras.erase(deviceIndex);
}
IMFAttributes* pConfig = nullptr;
MFCreateAttributes(&pConfig, 1);
pConfig->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
IMFActivate** ppDevices = nullptr;
UINT32 count = 0;
HRESULT hrEnum = MFEnumDeviceSources(pConfig, &ppDevices, &count);
pConfig->Release();
// 检查:枚举失败、设备数为0、或者请求的索引越界,统统返回 false
if (FAILED(hrEnum) || count == 0 || deviceIndex < 0 || deviceIndex >= (int)count) {
if (ppDevices) {
for (UINT32 i = 0; i < count; i++) ppDevices[i]->Release();
CoTaskMemFree(ppDevices);
}
return FALSE;
}
IMFMediaSource* pSource = nullptr;
HRESULT hrAct = ppDevices[deviceIndex]->ActivateObject(IID_PPV_ARGS(&pSource));
for (UINT32 i = 0; i < count; i++) ppDevices[i]->Release();
CoTaskMemFree(ppDevices);
if (FAILED(hrAct) || !pSource) return FALSE;
// ==========================================================
// 【新增】:向摄像头索要硬件控制权限 (IAMVideoProcAmp)
// ==========================================================
IAMVideoProcAmp* pProcAmp = nullptr;
pSource->QueryInterface(IID_PPV_ARGS(&pProcAmp));
// 注意:哪怕拿不到(有些极其便宜的山寨摄像头不支持),pProcAmp 也是 null,不影响主流程
IMFAttributes* pReaderConfig = nullptr;
MFCreateAttributes(&pReaderConfig, 1);
pReaderConfig->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE);
IMFSourceReader* pReader = nullptr;
HRESULT hrRead = MFCreateSourceReaderFromMediaSource(pSource, pReaderConfig, &pReader);
pReaderConfig->Release();
pSource->Release();
if (FAILED(hrRead) || !pReader) return FALSE; // 视频流读取器创建失败
LONG width = 0, height = 0;
SetMaxResolution(pReader, width, height);
IMFMediaType* pOutputFormat = nullptr;
MFCreateMediaType(&pOutputFormat);
pOutputFormat->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
pOutputFormat->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
pReader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pOutputFormat);
pOutputFormat->Release();
CameraContext* ctx = new CameraContext();
ctx->pReader = pReader;
ctx->hwnd = (HWND)(LONG_PTR)placeholderHWnd;
ctx->mirrorType = mirrorType;
ctx->width = width;
ctx->height = height;
ctx->isRunning = true;
ctx->pProcAmp = pProcAmp; // 存入上下文
g_Cameras[deviceIndex] = ctx;
ctx->captureThread = std::thread(CaptureThreadFunc, deviceIndex);
// 一切顺利,成功启动!
return TRUE;
}
// ==========================================================
// 动态设置摄像头镜像模式 (运行时即时生效)
// deviceIndex: 摄像头索引
// mirrorType: 0=正常, 1=上下, 2=左右, 3=全翻转
// 返回值: 成功返回 TRUE,失败返回 FALSE (例如该摄像头未启动)
// ==========================================================
BOOL CameraMirror(int deviceIndex, int mirrorType) {
// 加锁以保证线程安全
std::lock_guard<std::mutex> lock(g_Mutex);
if (mirrorType < -3 || mirrorType > 3)
{
mirrorType = 0;
}
// 查找指定索引的摄像头是否存在
auto it = g_Cameras.find(deviceIndex);
if (it != g_Cameras.end()) {
// 直接修改上下文中的镜像标记
// 后台抓图线程在绘制下一帧时,会自动应用新的翻转坐标
it->second->mirrorType = mirrorType;
return TRUE;
}
return FALSE;
}
// ==========================================================
// 硬件调节接口:亮度(0)、对比度(1)、色调(2)、饱和度(3)、清晰度(4)、白平衡(7)
// ==========================================================
// 1. 获取某个属性的支持范围(如果返回 FALSE,说明该摄像头不支持此功能)
BOOL CameraPropRangeGet(int deviceIndex, int propId, int* pMin, int* pMax, int* pStep, int* pDefault, int* pFlags) {
std::lock_guard<std::mutex> lock(g_Mutex);
auto it = g_Cameras.find(deviceIndex);
if (it != g_Cameras.end() && it->second->pProcAmp) {
long lMin, lMax, lStep, lDefault, lCapsFlags;
HRESULT hr = it->second->pProcAmp->GetRange(propId, &lMin, &lMax, &lStep, &lDefault, &lCapsFlags);
if (SUCCEEDED(hr)) {
*pMin = lMin; *pMax = lMax; *pStep = lStep; *pDefault = lDefault; *pFlags = lCapsFlags;
return TRUE;
}
}
return FALSE;
}
// 2. 获取当前属性的数值
BOOL CameraPropGet(int deviceIndex, int propId, int* pValue, int* pFlags) {
std::lock_guard<std::mutex> lock(g_Mutex);
auto it = g_Cameras.find(deviceIndex);
if (it != g_Cameras.end() && it->second->pProcAmp) {
long lValue, lFlags;
HRESULT hr = it->second->pProcAmp->Get(propId, &lValue, &lFlags);
if (SUCCEEDED(hr)) {
*pValue = lValue; *pFlags = lFlags;
return TRUE;
}
}
return FALSE;
}
// 3. 设置当前属性的数值 (flags: 1=自动, 2=手动)
BOOL CameraPropSet(int deviceIndex, int propId, int value, int flags) {
std::lock_guard<std::mutex> lock(g_Mutex);
auto it = g_Cameras.find(deviceIndex);
if (it != g_Cameras.end() && it->second->pProcAmp) {
HRESULT hr = it->second->pProcAmp->Set(propId, value, flags);
return SUCCEEDED(hr) ? TRUE : FALSE;
}
return FALSE;
}
void CloseCamera(int deviceIndex) {
std::lock_guard<std::mutex> lock(g_Mutex);
auto it = g_Cameras.find(deviceIndex);
if (it != g_Cameras.end()) {
CameraContext* ctx = it->second;
// 1. 挂出免战牌,通知循环结束
ctx->isRunning = false;
// ==========================================================
// 【核心防卡死修复】:强行踢醒底层卡死的 ReadSample 线程
// ==========================================================
if (ctx->pReader) {
// Flush 指令会强行清空底层的硬件缓冲区。
// 最神奇的是,它会让一直卡着不动的 ReadSample 瞬间苏醒,
// 并强行返回一个错误代码(跳出阻塞状态)。
ctx->pReader->Flush((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM);
}
// 2. 此时再等线程下班,就能瞬间等到了,绝对不会再卡死!
if (ctx->captureThread.joinable()) {
ctx->captureThread.join();
}
if (ctx->pReader) {
ctx->pReader->Release();
}
// 清理残影
InvalidateRect(ctx->hwnd, NULL, TRUE);
UpdateWindow(ctx->hwnd);
if (ctx->pProcAmp) {
ctx->pProcAmp->Release();
ctx->pProcAmp = nullptr;
}
delete ctx;
g_Cameras.erase(it);
}
}
void CloseAllCameras() {
// 拷贝一份 keys 以免遍历时删除导致迭代器失效
std::vector<int> keys;
{
std::lock_guard<std::mutex> lock(g_Mutex);
for (auto& pair : g_Cameras) keys.push_back(pair.first);
}
for (int k : keys) {
CloseCamera(k);
}
if (g_MfInitialized) {
MFShutdown();
g_MfInitialized = false;
}
}aardio调用代码:
import win.ui;
import console;
/*DSG{{*/
var mainForm = win.form(text="工业级视觉监控面板 (纯原生 C++ 核心)";right=880;bottom=560)
mainForm.add(
btnStart={cls="button";text="启动设备";left=650;top=30;right=850;bottom=70;z=2};
btnStop={cls="button";text="安全关闭";left=650;top=90;right=850;bottom=130;disabled=1;z=3};
button={cls="button";text="恢复默认";left=650;top=351;right=850;bottom=391;z=11};
comboMirror={cls="combobox";left=730;top=150;right=850;bottom=176;edge=1;items={"正常显示","上下翻转","左右翻转","全翻转"};mode="dropdownlist";z=5};
groupProp={cls="groupbox";text="硬件图像调节 (芯片级)";left=640;top=210;right=860;bottom=411;edge=1;z=6};
lbBright={cls="static";text="亮度:";left=650;top=240;right=690;bottom=260;transparent=1;z=7};
lbContrast={cls="static";text="对比度:";left=650;top=300;right=700;bottom=320;transparent=1;z=9};
lbMirror={cls="static";text="镜像模式:";left=650;top=154;right=730;bottom=178;transparent=1;z=4};
trackBright={cls="trackbar";left=690;top=235;right=850;bottom=265;disabled=1;max=100;min=0;z=8};
trackContrast={cls="trackbar";left=690;top=295;right=850;bottom=325;disabled=1;max=100;min=0;z=10};
videoPanel={cls="custom";text="监控画面就绪";left=20;top=20;right=620;bottom=530;bgcolor=0x0000FF;color=0xFFFFFF;z=1}
)
/*}}*/
// 打开控制台方便查阅底层日志
console.open();
console.log("初始化系统...");
// ==========================================
// 1. 加载动态链接库与 API 声明
// ==========================================
// 请确保 VideoXNative.dll 和编译出的 exe 在同一目录下
var camDll = raw.loadDll("\res\VideoX.dll");
var apiInitCamera = camDll.api("InitCamera", "bool(int deviceIndex, int mirrorType, int hwnd)");
var apiCloseCamera = camDll.api("CloseCamera", "void(int deviceIndex)");
var apiCloseAll = camDll.api("CloseAllCameras", "void()");
var apiSetMirror = camDll.api("CameraMirror", "bool(int deviceIndex, int mirrorType)");
var apiGetPropRange = camDll.api("CameraPropRangeGet", "bool(int deviceIndex, int propId, int &pMin, int &pMax, int &pStep, int &pDefault, int &pFlags)");
var apiSetProp = camDll.api("CameraPropSet", "bool(int deviceIndex, int propId, int value, int flags)");
// 全局状态变量
var currentDeviceIndex = 0; // 默认挂载 0 号摄像头
var isRunning = false;
// DirectShow 硬件属性标准 ID
var PROP_BRIGHTNESS = 0;
var PROP_CONTRAST = 1;
// 初始化镜像下拉框的默认选择
mainForm.comboMirror.selIndex = 1;
// ==========================================
// 2. 硬件参数调节初始化模块
// ==========================================
var initHardwareControls = function(){
var min=0; var max=0; var step=0; var def=0; var flags=0;
var success, min, max, step, def, flags = apiGetPropRange(currentDeviceIndex, PROP_BRIGHTNESS, 0, 0, 0, 0, 0);
if(success == 1){
mainForm.trackBright.setRange(min, max);
mainForm.trackBright.pos = def;
mainForm.trackBright.disabled = false;
console.log("✅ 亮度控制激活,范围:", min, "~", max, "默认值:", def);
} else {
mainForm.trackBright.disabled = true;
console.log("❌ 硬件不支持亮度调节");
}
// 对比度也是同样的接法
var success2, min2, max2, step2, def2, flags2 = apiGetPropRange(currentDeviceIndex, PROP_CONTRAST, 0, 0, 0, 0, 0);
if(success2 == 1){
mainForm.trackContrast.setRange(min2, max2);
mainForm.trackContrast.pos = def2;
mainForm.trackContrast.disabled = false;
console.log("✅ 对比度控制激活,范围:", min2, "~", max2, "默认值:", def2);
} else {
mainForm.trackContrast.disabled = true;
console.log("❌ 硬件不支持对比度调节");
}
}
// ==========================================
// 3. UI 交互事件绑定
// ==========================================
// 启动摄像头按钮
mainForm.btnStart.oncommand = function(id,event){
if(isRunning) return;
mainForm.btnStart.disabled = true;
mainForm.btnStart.text = "正在唤醒硬件...";
// 获取当前用户选择的镜像模式 (combo 索引从 1 开始,需减 1)
var mirrorMode = mainForm.comboMirror.selIndex - 1;
// 调用 C++ 底层进行硬件启动与句柄嵌套
var success = apiInitCamera(currentDeviceIndex, mirrorMode, mainForm.videoPanel.hwnd);
if(success == 1){
isRunning = true;
console.log("✅ 摄像头启动成功,底层渲染管线已建立。");
mainForm.btnStart.text = "运行中...";
mainForm.btnStop.disabled = false;
// 尝试探测并开放芯片参数调节
initHardwareControls();
} else {
console.log("❌ 硬件唤醒失败。可能被独占或权限遭拒。");
mainForm.btnStart.disabled = false;
mainForm.btnStart.text = "启动设备";
mainForm.msgboxErr("底层接口返回失败:未能成功开启硬件数据流。");
}
}
// 关闭摄像头按钮
mainForm.btnStop.oncommand = function(id,event){
if(!isRunning) return;
console.log("正在向 C++ 发送安全中断信号...");
apiCloseCamera(currentDeviceIndex);
isRunning = false;
mainForm.btnStart.disabled = false;
mainForm.btnStart.text = "启动设备";
mainForm.btnStop.disabled = true;
// 禁用硬件滑块并重置黑底
mainForm.trackBright.disabled = true;
mainForm.trackContrast.disabled = true;
mainForm.videoPanel.redraw();
console.log("设备已安全下线。");
}
// 动态镜像下拉框切换事件 (无缝热切换)
mainForm.comboMirror.onListChange = function(){
if(!isRunning) return; // 没启动时不发底层指令
var mirrorMode = mainForm.comboMirror.selIndex - 1;
var result = apiSetMirror(currentDeviceIndex, mirrorMode);
if(result == 1){
console.log("内存热写入:镜像模式已切换至 " ++ mirrorMode);
}
}
mainForm.trackBright.oncommand = function(id,event,pos){
if(!isRunning) return;
// 参数 2 代表设为手动调节 (Manual)
var ret = apiSetProp(currentDeviceIndex, PROP_BRIGHTNESS, owner.pos, 2);
console.log(ret,owner.pos);
}
mainForm.trackContrast.oncommand = function(id,event,pos){
if(!isRunning) return;
var ret = apiSetProp(currentDeviceIndex, PROP_CONTRAST, owner.pos, 2);
console.log(ret,owner.pos);
}
// 窗口销毁,安全阻断所有硬件释放内存
mainForm.onDestroy = function(){
console.log("系统退出,正在释放全部系统内存与硬件锁...");
apiCloseAll();
}
mainForm.button.oncommand = function(id,event){
if(!isRunning) return;
// 1. 恢复亮度
// 再次调用 GetPropRange 接口,只为了拿到其中的 def (默认值)
var successB, minB, maxB, stepB, defB, flagsB = apiGetPropRange(currentDeviceIndex, PROP_BRIGHTNESS, 0, 0, 0, 0, 0);
if(successB == 1){
// 向硬件下发默认值 (参数 2 代表手动模式)
apiSetProp(currentDeviceIndex, PROP_BRIGHTNESS, defB, 2);
// 同步更新滑动条的 UI 位置
mainForm.trackBright.pos = defB;
console.log("亮度已恢复出厂默认值:", defB);
}
// 2. 恢复对比度
var successC, minC, maxC, stepC, defC, flagsC = apiGetPropRange(currentDeviceIndex, PROP_CONTRAST, 0, 0, 0, 0, 0);
if(successC == 1){
apiSetProp(currentDeviceIndex, PROP_CONTRAST, defC, 2);
mainForm.trackContrast.pos = defC;
console.log("对比度已恢复出厂默认值:", defC);
}
}
mainForm.show();
return win.loopMessage();编译生成的dll 和工程代码:
登录后方可回帖