Delphi中DirectShow摄像头开发

AI助手已提取文章相关产品:

Delphi 中基于 DirectShow 的 USB 摄像头视频预览与录像实现

在工业控制、医疗设备和智能监控系统中,图像采集的稳定性和实时性至关重要。尽管现代开发平台不断演进,但在许多长期维护的专业软件项目中, Delphi 依然是构建高性能 Windows 桌面应用的重要工具。它结合 VCL 界面库的强大表现力与对 Windows API 的深度封装能力,特别适合用于快速搭建可靠的数据采集前端。

而当面对“如何让一个普通的 USB 摄像头在 Delphi 程序里流畅出图并录像”这个问题时,答案往往指向微软的经典多媒体框架 —— DirectShow 。虽然官方已将其归为“遗留技术”,推荐新项目使用 Media Foundation,但对于 Delphi 开发者而言,DirectShow 仍是目前文档最全、社区支持最好、兼容性最强的选择。


为什么选择 DirectShow?

我们先抛开术语堆砌,从实际需求出发:你需要的是一个能即插即用、低延迟、可编程控制分辨率帧率,并能把画面保存成标准格式文件的方案。DirectShow 正好满足这些要求。

它的核心思想是“流水线式处理”——把音视频流看作一条从源头到终点的数据管道,每个处理环节都由一个独立模块(Filter)完成。比如:

  • 摄像头本身是一个 Source Filter
  • 编码压缩是一个 Transform Filter
  • 显示画面或写入文件则是 Renderer Filter

所有这些 Filter 被连接在一起形成一个 Filter Graph ,由 IGraphBuilder 统一管理。你可以想象这就像搭积木:选好摄像头、加上渲染器、再接上文件写入器,然后按下“运行”,数据就会自动流动起来。

更重要的是,大多数现代 USB 摄像头遵循 UVC(USB Video Class)标准 ,Windows 内置驱动即可识别,无需额外安装厂商驱动。这意味着只要你的程序能枚举到设备,几乎就能直接使用。


如何发现可用的摄像头?

第一步永远是:“我电脑上到底有几个摄像头?叫什么名字?”
这需要通过 COM 接口遍历系统设备列表。

function EnumerateCameras(lst: TStrings): Boolean;
var
  MonikerEnum: IEnumMoniker;
  Moniker: IMoniker;
  PropBag: IPropertyBag;
  cFetched: ULONG;
  Variant: OleVariant;
begin
  Result := False;
  lst.Clear;

  // 创建设备枚举器
  var ClassFactory: IClassFactory;
  if Failed(CoCreateInstance(CLSID_SystemDeviceEnum, nil,
    CLSCTX_INPROC_SERVER, IID_IClassFactory, Pointer(ClassFactory))) then Exit;

  if Failed(ClassFactory.CreateInstance(nil, IID_IEnumMoniker, Pointer(MonikerEnum))) then Exit;

  // 枚举视频输入设备类别
  MonikerEnum.Reset;
  while (MonikerEnum.Next(1, Moniker, @cFetched) = S_OK) do
  begin
    if Succeeded(Moniker.BindToStorage(nil, nil, IID_IPropertyBag, PropBag)) then
    begin
      Variant := 'FriendlyName';
      if Succeeded(PropBag.Read('FriendlyName', Variant, nil)) then
        lst.Add(Variant);
      PropBag := nil;
    end;
    Moniker := nil;
  end;

  Result := True;
end;

这个函数会将所有可用视频输入设备的友好名称填入传入的 TStrings 列表中,通常你会看到类似 “USB Camera”、“Integrated Camera” 或具体品牌型号的名字。用户可以在下拉框中选择目标设备,后续操作便基于此名称查找对应的源 Filter。

小技巧:如果多个摄像头名称相同,建议同时提取 DevicePath 属性来做唯一标识,避免混淆。


实现视频预览:让画面动起来

有了设备,下一步就是把画面显示出来。这里的关键是使用 VMR-9(Video Mixing Renderer 9) ,它是 DirectShow 中较新的渲染器,支持 Alpha 混合、窗口缩放和 Z-order 控制,非常适合嵌入到 Delphi 的 TPanel 或其他容器控件中。

流程如下:

  1. 初始化 COM 库
  2. 创建 IGraphBuilder ICaptureGraphBuilder2
  3. 查找并添加摄像头 Source Filter
  4. 设置 VMR-9 并绑定到窗体控件句柄
  5. 使用 RenderStream 自动连接预览分支
  6. 启动播放
procedure StartPreview(CameraName: string; Panel: TPanel);
var
  GraphBuilder: IGraphBuilder;
  CaptureGraph: ICaptureGraphBuilder2;
  SourceFilter: IBaseFilter;
begin
  CoInitialize(nil);

  CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder, Pointer(GraphBuilder));
  CoCreateInstance(CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC_SERVER,
    IID_ICaptureGraphBuilder2, Pointer(CaptureGraph));

  CaptureGraph.SetFiltergraph(GraphBuilder);

  SourceFilter := FindCameraFilterByName(CameraName);
  if not Assigned(SourceFilter) then raise Exception.Create('未找到指定摄像头');

  GraphBuilder.AddFilter(SourceFilter, 'Video Capture');

  // 配置 VMR-9 渲染器
  SetupVMR9Renderer(GraphBuilder, Panel.Handle);

  // 连接预览流
  CaptureGraph.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video,
    SourceFilter, nil, nil);

  // 开始运行
  (GraphBuilder as IMediaControl).Run;
end;

其中 SetupVMR9Renderer 是一个辅助函数,负责创建 VMR-9 Filter 并设置其窗口模式为 Windowed ,并将宿主窗口设为 Panel 的 Handle。你还可以在此阶段设置背景色、是否拉伸填充等视觉参数。

工程经验:若出现黑屏或卡顿,优先检查是否正确设置了 VMR 模式;其次尝试强制摄像头输出 YUY2 格式而非 MJPEG,某些低端摄像头在压缩模式下容易丢帧。


开始录像:不只是截图那么简单

很多人误以为“录像”就是定时抓图然后打包成视频,但那样效率极低且无法保证时间连续性。真正的视频录制应该是在 Filter Graph 中开辟一条独立的数据通路,将原始视频流导向文件写入器。

典型结构是:

[Camera] 
   ↓ (Capture Pin)
[AVI Mux] → [File Writer] → output.avi

AVI Mux 负责将视频帧打包成 AVI 容器格式,File Writer 则负责落地写盘。整个过程与预览并行不悖 —— 你可以一边看画面,一边后台安静地录着高清视频。

以下是启动录制的核心代码:

procedure StartRecording(GraphBuilder: IGraphBuilder;
  CaptureGraph: ICaptureGraphBuilder2;
  SourceFilter: IBaseFilter;
  const FileName: string);
var
  AviMux: IBaseFilter;
  FileWriter: IBaseFilter;
begin
  // 创建 AVI 多路复用器
  CoCreateInstance(CLSID_AviDest, nil, CLSCTX_INPROC_SERVER,
    IID_IBaseFilter, Pointer(AviMux));
  GraphBuilder.AddFilter(AviMux, 'AVI Mux');

  // 创建文件写入器
  CoCreateInstance(CLSID_FileWriter, nil, CLSCTX_INPROC_SERVER,
    IID_IBaseFilter, Pointer(FileWriter));
  GraphBuilder.AddFilter(FileWriter, 'File Writer');

  // 设置输出路径
  (FileWriter as IFileSinkFilter).SetFileName(PWideChar(WideString(FileName)), nil);

  // 连接 Capture Pin 到 AVI Mux(关键!不要走 Preview 分支)
  CaptureGraph.RenderStream(@PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video,
    SourceFilter, nil, AviMux);

  // 手动连接 AVI Mux 输出到 FileWriter 输入
  ConnectFilters(GraphBuilder, AviMux, FileWriter);

  // 开始录制
  (GraphBuilder as IMediaControl).Run;
end;

注意这里使用的是 PIN_CATEGORY_CAPTURE 而非 PREVIEW 。这是因为部分摄像头的 Preview 流可能被降质(如降低分辨率以提升流畅度),而 Capture 流才是原始高质量输出,确保录像画质不受影响。

实践建议:录制结束后应先调用 .Stop 停止 Graph,再移除新增的 Filter,否则生成的 AVI 文件可能缺少索引头导致无法拖动进度条播放。


实际开发中的常见问题与对策

问题现象 可能原因 解决方法
枚举不到摄像头 UVC 驱动未加载 检查设备管理器中是否有黄色感叹号;尝试更换 USB 接口
预览正常但无法录像 Capture Pin 不可用 某些老旧摄像头仅支持单路输出,需停止预览后再开启录像
录像文件打不开 缺少索引或编码异常 使用标准 AVI Mux;避免中途强行终止程序
图像模糊/颜色偏绿 像素格式不匹配 强制设置 IAMStreamConfig 输出 RGB24 或 YUY2 格式
多次启停后崩溃 COM 对象未释放 每次关闭时显式 Release 所有接口,最后调用 CoUninitialize

此外,在设计 UI 时也应注意用户体验细节:

  • 提供“刷新设备”按钮,支持热插拔后重新扫描
  • 显示当前流的分辨率和帧率(可通过 IAMStreamConfig.GetFormat 获取)
  • 添加“测试预览”功能,一键验证摄像头状态
  • 录像按钮禁用期间给出明确提示(如“正在录制中…”)

性能与稳定性优化建议

  1. 内存管理严格化
    所有 IInterface 派生的对象(如 IBaseFilter , IPin )必须手动 Release() ,尤其是在频繁开关摄像头的场景下,否则极易引发内存泄漏。

  2. 线程隔离
    IMediaControl.Run Stop 必须在主线程调用,跨线程操作可能导致 Access Violation。如有异步需求,可通过消息机制通知主窗体执行控制命令。

  3. 错误处理全面化
    每个 COM 调用都要判断返回值是否 SUCCEEDED(hr) ,失败时记录日志并提示用户,而不是直接抛异常中断流程。

  4. 资源复用策略
    若需频繁切换摄像头,可缓存 Filter Graph 实例,仅替换 Source Filter,减少重建开销。


这套方案适合谁?

如果你正在开发以下类型的系统,那么 Delphi + DirectShow 的组合依然极具价值:

  • 医疗辅助设备 :如耳鼻喉镜、皮肤检测仪的图像采集前端
  • 教学实验平台 :学生实验过程视频记录与回放
  • 工业视觉初筛 :低成本产线质检相机控制台
  • 生物特征采集 :指纹、虹膜、面部识别前的图像抓取模块
  • 临时取证工具 :现场快速拍摄并封存证据视频

这类应用通常不需要 H.265 编码、HDR 支持或网络推流等高级特性,反而更看重稳定性、易维护性和部署便捷性 —— 这正是 DirectShow 的强项。


未来的延伸方向

尽管立足于传统技术,但这并不意味着止步不前。你可以在这个基础上轻松扩展更多功能:

  • 帧级图像处理 :插入 Sample Grabber Filter,获取每一帧位图数据,送入 OpenCV 进行边缘检测、人脸识别等分析
  • 音频同步录制 :添加麦克风 Source Filter,与视频流一同 mux 成 AVI
  • RTSP 推流 :结合 FFmpeg 或自定义 Transform Filter,将本地流转发至网络
  • 参数动态调节 :通过 IAMCameraControl IAMVideoProcAmp 接口调整焦距、曝光、亮度等属性

甚至可以考虑未来逐步迁移到 Media Foundation,利用 MFCreateSourceReaderFromMediaSource 实现更现代化的流处理,只是目前 Delphi 社区对此的支持仍显薄弱。


这种高度集成的设计思路,正引领着专业采集设备向更可靠、更高效的方向演进。掌握 Delphi 下 DirectShow 的摄像头开发技术,仍然是当前许多行业软件工程师不可或缺的一项实用技能。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

您可能感兴趣的与本文相关内容

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值