zoukankan      html  css  js  c++  java
  • 基于Directshow的USB视频捕获Delphi篇(一)

    参考:https://blog.csdn.net/dbyoung/article/details/78256226

    工作中用到了USB Camera 来获取图像。用dspack控件,发现有BUG,使用不了。只好自己写了。参考了dspack源码,但实现方法不同。

    也在网上查看了很多资料,终于能顺利的运行。记录一下。也给需要的人有点帮助。

    分下面六个部分:

      第一步:枚举所有视频输入设备;

      第二步:枚举视频支持格式;

      第三步:视频预览;

      第四步:截图

      第五步:相机参数调整;

      第六步:视频录像;

    第一步:枚举所有视频输入设备;

      枚举所有视频输入设备,保存到 TStrings 中。注意保存用的是 AddObject,保存了相机的名称、序列、GUID。因为相机有可能有多个,名称、GUID都可能重复。

      上代码。使用函数方式,不使用面向对象的方式。面向对象掩盖了很多细节,不容易了解到重点。

    { 枚举所有视频输入设备 }
    procedure EnumAllUSBCamera(strsList: TStrings);
    var
      SysDevEnum: ICreateDevEnum;
      EnumCat   : IEnumMoniker;
      hr        : Integer;
      Moniker   : IMoniker;
      Fetched   : ULONG;
      PropBag   : IPropertyBag;
      strName   : OleVariant;
      strGuid   : OleVariant;
      III       : Integer;
      puInfo    : PVideoInputInfo;
      intIndex  : Integer;
    begin
      { 创建系统枚举器对象 }
      hr := CocreateInstance(CLSID_SystemDeviceEnum, nil, CLSCTX_INPROC, IID_ICreateDevEnum, SysDevEnum);
      if hr <> S_OK then
        Exit;
     
      { 用指定的 Filter 目录类型创建一个枚举器,并获得 IEnumMoniker 接口; }
      hr := SysDevEnum.CreateClassEnumerator(CLSID_VideoInputDeviceCategory, EnumCat, 0);
      if hr <> S_OK then
        Exit;
     
      try
        { 释放内存 }
        if strsList.Count > 0 then
        begin
          for III := 0 to strsList.Count - 1 do
          begin
            FreeMem(PVideoFormatInfo(strsList.Objects[III]));
          end;
        end;
        strsList.Clear;
     
        { 获取指定类型目录下所有设备标识 }
        while (EnumCat.Next(1, Moniker, @Fetched) = S_OK) do
        begin
          Moniker.BindToStorage(nil, nil, IID_IPropertyBag, PropBag);
          PropBag.Read('CLSID', strGuid, nil);
          PropBag.Read('FriendlyName', strName, nil);
          New(puInfo);
          puInfo^.id      := TGUID(strGuid);
          puInfo^.strName := ShortString(strName);
          puInfo^.index   := 0;
          if strsList.IndexOf(strName) = -1 then
          begin
            strsList.AddObject(strName, TObject(puInfo));
          end
          else
          begin
            { 相同名称的 USBCamera 相机,<有可能有多个名称重复的相机> }
            intIndex      := GetMaxIndex(strsList, strName);
            puInfo^.index := intIndex + 1;
            strsList.AddObject(strName + format('(%d)', [puInfo^.index]), TObject(puInfo));
          end;
          PropBag := nil;
          Moniker := nil;
        end;
     
      finally
        EnumCat    := nil;
        SysDevEnum := nil;
      end;
    end;

    第二步:枚举视频支持格式;

       选择了某个相机之后,我希望知道这个相机支持的所有视频格式,并可以选择用不同的格式来进行视频预览和视频录像。

      上代码。

    { 枚举视频支持格式 }
    function EnumVideoFormat(const strFriendlyName: String; const intIndex: Integer; strsList: TStrings): Boolean;
    var
      SysDevEnum          : IBaseFilter;
      CaptureGraphBuilder2: ICaptureGraphBuilder2;
      iunk                : IUnknown;
      fStreamConfig       : IAMStreamConfig;
      piCount, piSize     : Integer;
      III                 : Integer;
      pmt                 : PAMMediaType;
      pSCC                : PVideoStreamConfigCaps;
      pvInfo              : PVideoFormatInfo;
    begin
      Result := False;
     
      { 获取指定USB摄像头的 Filter }
      SysDevEnum := CreateFilter(CLSID_VideoInputDeviceCategory, AnsiString(strFriendlyName), intIndex);
      if SysDevEnum = nil then
        Exit;
     
      { 释放内存 }
      if strsList.Count > 0 then
      begin
        for III := 0 to strsList.Count - 1 do
        begin
          FreeMem(PVideoFormatInfo(strsList.Objects[III]));
        end;
      end;
      strsList.Clear;
     
      { 创建 ICaptureGraphBuilder2 接口 }
      if Failed(CocreateInstance(CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, CaptureGraphBuilder2)) then
        Exit;
     
      { 获取 IID_IAMStreamConfig 接口 }
      if Failed(CaptureGraphBuilder2.FindInterface(nil, nil, SysDevEnum, IID_IAMStreamConfig, iunk)) then
        Exit;
     
      { 获取 IAMStreamConfig 媒体类型接口 }
      if Failed(iunk.QueryInterface(IID_IAMStreamConfig, fStreamConfig)) then
        Exit;
     
      if Failed(fStreamConfig.GetNumberOfCapabilities(piCount, piSize)) then
        Exit;
     
      if piCount <= 0 then
        Exit;
     
      { 枚举支持的视频格式 }
      pSCC := AllocMem(piSize);
      try
        for III := 0 to piCount - 1 do
        begin
          if fStreamConfig.GetStreamCaps(III, pmt, pSCC) = S_OK then
          begin
            try
              New(pvInfo); { 注意释放内存 }
              pvInfo^.Frame   := PVIDEOINFOHEADER(pmt^.pbFormat)^.AvgTimePerFrame;
              pvInfo^.id      := pmt^.formattype;
              pvInfo^.iWidth  := pSCC^.MaxOutputSize.cx;
              pvInfo^.iHeight := pSCC^.MaxOutputSize.cy;
              pvInfo^.iMod    := pmt^.subtype;
              pvInfo^.format  := VideoMediaSubTypeToStr(pmt^.subtype);
              strsList.AddObject(format('类型:%s  分辨率:%4d×%4d', [pvInfo^.format, pvInfo^.iWidth, pvInfo^.iHeight]), TObject(pvInfo));
            finally
              DeleteMediaType(pmt);
            end;
          end;
        end;
      finally
        FreeMem(pSCC);
      end;
     
      SysDevEnum           := nil;
      CaptureGraphBuilder2 := nil;
      fStreamConfig        := nil;
     
      Result := True;
    end;
     

    第三步:视频预览;

      选择了视频设备,和视频格式后,启动视频预览。

    function USBCameraPreview(var FIGraphBuilder: IGraphBuilder;          //
      var FICaptureGraphBuilder2: ICaptureGraphBuilder2;                  //
      var FSysDevEnum: IBaseFilter;                                       //
      var FIVideoWindow: IVideoWindow;                                    //
      var FIMediaControl: IMediaControl;                                  //
      var FISampleGrabber: ISampleGrabber;                                //
      pv: PVideoInputInfo; pf: PVideoFormatInfo;                          //
      pnl: TPanel                                                         //
       ): Boolean;
    var
      SampleGrabberFilter: IBaseFilter;
      mt                 : TAMMediaType;
      multiplexer        : IBaseFilter;
      Writer             : IFileSinkFilter;
    begin
      Result := False;
     
      { 创建 IGraphBuilder 接口 }
      if Failed(CocreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, IID_IGraphBuilder, FIGraphBuilder)) then
        Exit;
     
      { 创建 ICaptureGraphBuilder2 接口 }
      if Failed(CocreateInstance(CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, FICaptureGraphBuilder2)) then
        Exit;
     
      { 调用 ICaptureGraphBuilder2 的 SetFilterGraph 方法将 FilterGraph 加入到Builder中 }
      if Failed(FICaptureGraphBuilder2.SetFiltergraph(FIGraphBuilder)) then
        Exit;
     
      { 获取指定USB摄像头的 Filter }
      FSysDevEnum := CreateFilter(CLSID_VideoInputDeviceCategory, AnsiString(pv^.strName), pv^.index);
      if FSysDevEnum = nil then
        Exit;
     
      { 设置指定 Filter 的媒体格式类型 }
      if not SetMediaType(FSysDevEnum, pf^.iWidth, pf^.iHeight, pf^.format) then
        Exit;
     
      { 将视频捕捉 Filter 添加到 Filter 图中 }
      if Failed(FIGraphBuilder.AddFilter(FSysDevEnum, 'VideoCapture')) then
        Exit;
     
      { 渲染预览视频PIN }
      if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, FSysDevEnum, nil, nil)) then
        Exit;
     
      { 设置视频预览窗口 }
      if Failed(FIGraphBuilder.QueryInterface(IID_IVideoWindow, FIVideoWindow)) then
        Exit;
     
      { 设置视频播放的WINDOWS窗口 }
      if Failed(FIVideoWindow.put_Owner(pnl.Handle)) then
        Exit;
     
      if Failed(FIVideoWindow.put_windowstyle(WS_CHILD or WS_Clipsiblings)) then
        Exit;
     
      { 设置视频尺寸 }
      if Failed(FIVideoWindow.SetWindowposition(0, 0, pnl.Width, pnl.Height)) then
        Exit;
     
      { 得到IMediaControl接口,用于控制流播放 }
      if Failed(FIGraphBuilder.QueryInterface(IID_IMediaControl, FIMediaControl)) then
        Exit;
     
      Result := True;
    end;

    第四步:截图

      截图方式有两种,一种是从缓冲区中获取图像,一种是用回调的方式获取图像。

    现在使用第一张方法,从缓冲区中获取图像。回调方式下一篇文件介绍。

    要从缓冲区中获取图像,需要先设置,允许从缓冲区获取图像。

    修改上面的视频预览函数,USBCameraPreview。

    这一句:

    { 渲染预览视频PIN }
      if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, FSysDevEnum, nil, nil)) then
        Exit;

    修改为:

    { 如果需要截图功能 }
      if bSnapBmp then
      begin
        CocreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC, IID_IBaseFilter, SampleGrabberFilter);
        FIGraphBuilder.AddFilter(SampleGrabberFilter, 'SampleGrabber');
        SampleGrabberFilter.QueryInterface(IID_ISampleGrabber, FISampleGrabber);
        zeromemory(@mt, sizeof(AM_MEDIA_TYPE));
        mt.majortype := MEDIATYPE_Video;
        mt.subtype   := MEDIASUBTYPE_RGB24;     // 24位,位图格式输出
        FISampleGrabber.SetMediaType(@mt);      //
        FISampleGrabber.SetBufferSamples(True); // 允许从 Buffer 中获取数据
        { 渲染预览视频PIN }
        if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, FSysDevEnum, SampleGrabberFilter, nil)) then
          Exit;
      end
      else
      begin
        { 渲染预览视频PIN }
        if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, FSysDevEnum, nil, nil)) then
          Exit;
      end;

    函数声明中添加一个布尔类型 bSnapBmp。

    能看出来,只是修改FICaptureGraphBuilder2.RenderStream 了第四个参数。由原来的 nil 变成了 SampleGrabberFilter,是不是很简单。

    (以后我们还会对 USBCameraPreview 这个函数进行修改 。这是个基本函数。)

    开启了允许截图功能,我们就可以截图了。

    { 截图 }
    procedure TForm1.btnSnapBmpClick(Sender: TObject);
    var
      pfs        : TFilterState;
      mt         : TAMMediaType;
      hr         : HResult;
      pBufferSize: Integer;
      pBuffer    : PByte;
      bmp        : TBitmap;
      vi         : PVideoInfoHeader;
    begin
      if FIMediaControl = nil then
        Exit;
     
      FIMediaControl.GetState(1000, pfs);
      if pfs = State_Stopped then
        Exit;
     
      { 获取媒体类型 }
      hr := FISampleGrabber.GetConnectedMediaType(mt);
      if hr <> S_OK then
        Exit;
     
      if mt.pbFormat = nil then
        Exit;
     
      vi := PVideoInfoHeader(mt.pbFormat);
     
      { 获取当前帧数据大小 }
      hr := FISampleGrabber.GetCurrentBuffer(pBufferSize, nil);
      if hr <> S_OK then
        Exit;
     
      { 分配内存大小 }
      pBuffer := AllocMem(pBufferSize);
      try
        { 再一次获取当前帧,获取图像数据 }
        hr := FISampleGrabber.GetCurrentBuffer(pBufferSize, pBuffer);
        if hr <> S_OK then
          Exit;
     
        { 创建位图 }
        bmp := TBitmap.Create;
        try
          bmp.PixelFormat := pf24bit;
          bmp.width       := vi^.bmiHeader.biWidth;
          bmp.height      := vi^.bmiHeader.biHeight;
          SetBitmapBits(bmp.Handle, vi^.bmiHeader.biSizeImage, pBuffer);
          bmp.Canvas.CopyRect(bmp.Canvas.ClipRect, bmp.Canvas, Rect(0, bmp.height, bmp.width, 0));
          img1.Picture.Bitmap.Assign(bmp);
        finally
          bmp.Free;
        end;
      finally
        FreeMem(pBuffer);
      end;
    end;

    第五步:相机参数调整;

      这一步比较简单,USB CAMERA 有两种参数可以调节,一个是视频参数,一个是格式参数。格式参数我们已经枚举出来了,调不调节无所谓了。

    下面给出这两个参数调节的代码。两个函数。

    { 视频参数调节 }
    function ShowFilterPropertyPages(filter: IBaseFilter; hFormHandle: THandle): Boolean;
    var
      pSpecify: ISpecifyPropertyPages;
      caGUID  : TCAGUID;
    begin
      Result   := False;
      pSpecify := nil;
      filter.QueryInterface(ISpecifyPropertyPages, pSpecify);
      if pSpecify <> nil then
      begin
        pSpecify.GetPages(caGUID);
        pSpecify := nil;
        Result   := OleCreatePropertyFrame(hFormHandle, 0, 0, '', 1, Pointer(@filter), caGUID.cElems, PGUID(caGUID.pElems), 0, 0, nil) = S_OK;
        CoTaskMemFree(caGUID.pElems);
      end;
    end;
     
    { 格式参数调节 }
    function ShowPinPropertyPages(pin: IPin; hFormHandle: THandle): Boolean;
    var
      pSpecify: ISpecifyPropertyPages;
      caGUID  : TCAGUID;
    begin
      Result   := False;
      pSpecify := nil;
      pin.QueryInterface(ISpecifyPropertyPages, pSpecify);
      if pSpecify <> nil then
      begin
        pSpecify.GetPages(caGUID);
        pSpecify := nil;
        Result   := OleCreatePropertyFrame(hFormHandle, 0, 0, '', 1, Pointer(@pin), caGUID.cElems, PGUID(caGUID.pElems), 0, 0, nil) = S_OK;
        CoTaskMemFree(caGUID.pElems);
      end;
    end;

    调用:

     { 视频参数调节}
      ShowFilterPropertyPages(FSysDevEnum, Handle);  

     {格式参数调节}

     var
      pin: IPin;
     begin
      FICaptureGraphBuilder2.FindPin(FSysDevEnum, PINDIR_OUTPUT, nil, nil, False, 0, pin);
      ShowPinPropertyPages(pin, Handle);

     end;

     第六步:视频录像;

      视频录像就是将视频保存到磁盘上。这就要使用视频编码器,将视频以何种格式保存到磁盘上。avi,mov,mp4,等等。

    不同的格式,需要你的机器上安装对应的编码器。我们以所有的机器上都支持的avi格式来保存文件。

    (缺点:文件比较大。保存是原始的YUV图像。其实也可以将YUV转化为BMP,下一篇文章中介绍)

    修改上面的视频预览函数,让它可以进行视频录制。

    在截图功能代码之后,添加代码:

      { 如果是视频录制 }
      if bRecord then
      begin
        { 视频录制文件保持路径 }
        if Failed(FICaptureGraphBuilder2.SetOutputFileName(MEDIASUBTYPE_Avi, PWideChar(strSaveFileName), multiplexer, Writer)) then
          Exit;
     
        if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video, FSysDevEnum, nil, multiplexer)) then
          Exit;
      end;

    这样就多了两个参数,bRecord,strSaveFileName。意思很明白。

    这样这个视频预览函数,既可以预览,又支持截图、又支持视频录像。

    完整代码如下:

    function CommonUSBCamera(var FIGraphBuilder: IGraphBuilder;               //
      var FICaptureGraphBuilder2: ICaptureGraphBuilder2;                  //
      var FSysDevEnum: IBaseFilter;                                       //
      var FIVideoWindow: IVideoWindow;                                    //
      var FIMediaControl: IMediaControl;                                  //
      var FISampleGrabber: ISampleGrabber;                                //
      pv: PVideoInputInfo; pf: PVideoFormatInfo;                          //
      pnl: TPanel;                                                        //
      const strSaveFileName: string = ''; const bRecord: Boolean = False; // 录像
      const bSnapBmp: Boolean = False                                     // 截图
      ): Boolean;
    var
      SampleGrabberFilter: IBaseFilter;
      mt                 : TAMMediaType;
      multiplexer        : IBaseFilter;
      Writer             : IFileSinkFilter;
    begin
      Result := False;
     
      { 创建 IGraphBuilder 接口 }
      if Failed(CocreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, IID_IGraphBuilder, FIGraphBuilder)) then
        Exit;
     
      { 创建 ICaptureGraphBuilder2 接口 }
      if Failed(CocreateInstance(CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC, IID_ICaptureGraphBuilder2, FICaptureGraphBuilder2)) then
        Exit;
     
      { 调用 ICaptureGraphBuilder2 的 SetFilterGraph 方法将 FilterGraph 加入到Builder中 }
      if Failed(FICaptureGraphBuilder2.SetFiltergraph(FIGraphBuilder)) then
        Exit;
     
      { 获取指定USB摄像头的 Filter }
      FSysDevEnum := CreateFilter(CLSID_VideoInputDeviceCategory, AnsiString(pv^.strName), pv^.index);
      if FSysDevEnum = nil then
        Exit;
     
      { 设置指定 Filter 的媒体格式类型 }
      if not SetMediaType(FSysDevEnum, pf^.iWidth, pf^.iHeight, pf^.format) then
        Exit;
     
      { 将视频捕捉 Filter 添加到 Filter 图中 }
      if Failed(FIGraphBuilder.AddFilter(FSysDevEnum, 'VideoCapture')) then
        Exit;
     
      { 如果需要截图功能 }
      if bSnapBmp then
      begin
        CocreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC, IID_IBaseFilter, SampleGrabberFilter);
        FIGraphBuilder.AddFilter(SampleGrabberFilter, 'SampleGrabber');
        SampleGrabberFilter.QueryInterface(IID_ISampleGrabber, FISampleGrabber);
        zeromemory(@mt, sizeof(AM_MEDIA_TYPE));
        mt.majortype := MEDIATYPE_Video;
        mt.subtype   := MEDIASUBTYPE_RGB24;     // 24位,位图格式输出
        FISampleGrabber.SetMediaType(@mt);      //
        FISampleGrabber.SetBufferSamples(True); // 允许从 Buffer 中获取数据
        { 渲染预览视频PIN }
        if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, FSysDevEnum, SampleGrabberFilter, nil)) then
          Exit;
      end
      else
      begin
        { 渲染预览视频PIN }
        if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, FSysDevEnum, nil, nil)) then
          Exit;
      end;
     
      { 如果是视频录制 }
      if bRecord then
      begin
        { 视频录制文件保持路径 }
        if Failed(FICaptureGraphBuilder2.SetOutputFileName(MEDIASUBTYPE_Avi, PWideChar(strSaveFileName), multiplexer, Writer)) then
          Exit;
     
        if Failed(FICaptureGraphBuilder2.RenderStream(@PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video, FSysDevEnum, nil, multiplexer)) then
          Exit;
      end;
     
      { 设置视频预览窗口 }
      if Failed(FIGraphBuilder.QueryInterface(IID_IVideoWindow, FIVideoWindow)) then
        Exit;
     
      { 设置视频播放的WINDOWS窗口 }
      if Failed(FIVideoWindow.put_Owner(pnl.Handle)) then
        Exit;
     
      if Failed(FIVideoWindow.put_windowstyle(WS_CHILD or WS_Clipsiblings)) then
        Exit;
     
      { 设置视频尺寸 }
      if Failed(FIVideoWindow.SetWindowposition(0, 0, pnl.Width, pnl.Height)) then
        Exit;
     
      { 得到IMediaControl接口,用于控制流播放 }
      if Failed(FIGraphBuilder.QueryInterface(IID_IMediaControl, FIMediaControl)) then
        Exit;
     
      Result := True;
    end;
     
    { 视频预览 }
    function USBVideoPreview(var FIGraphBuilder: IGraphBuilder; var FICaptureGraphBuilder2: ICaptureGraphBuilder2; var FSysDevEnum: IBaseFilter; var FIVideoWindow: IVideoWindow; var FIMediaControl: IMediaControl; var FISampleGrabber: ISampleGrabber; pv: PVideoInputInfo; pf: PVideoFormatInfo; pnl: TPanel; const bSnapBmp: Boolean = False): Boolean;
    begin
      Result := CommonUSBCamera(FIGraphBuilder, FICaptureGraphBuilder2, FSysDevEnum, FIVideoWindow, FIMediaControl, FISampleGrabber, pv, pf, pnl, '', False, True);
    end;
     
    { 视频录制 }
    function USBVideoRecord(var FIGraphBuilder: IGraphBuilder; var FICaptureGraphBuilder2: ICaptureGraphBuilder2; var FSysDevEnum: IBaseFilter; var FIVideoWindow: IVideoWindow; var FIMediaControl: IMediaControl; var FISampleGrabber: ISampleGrabber; pv: PVideoInputInfo; pf: PVideoFormatInfo; pnl: TPanel; const strSaveFileName: String): Boolean;
    begin
      Result := CommonUSBCamera(FIGraphBuilder, FICaptureGraphBuilder2, FSysDevEnum, FIVideoWindow, FIMediaControl, FISampleGrabber, pv, pf, pnl, strSaveFileName, True, True);
    end;
     


    完整工程代码:

    http://download.csdn.net/download/dbyoung/10025100

  • 相关阅读:
    复习总结
    python 之Tornado
    MySQL 同一Windows系统上安装多个数据库
    CSS 轻松搞定元素(标签)居中问题
    Linux 解决Deepin深度系统无法在root用户启动Google Chrome浏览器的问题
    Django Windows+IIS+wfastcgi 环境下部署
    Django RestFramework(DRF)类视图
    php+ajax实现拖动滚动条分批加载请求加载数据
    Jquery+php鼠标滚动到页面底部自动加载更多内容,使用分页
    jQuery+ajax实现滚动到页面底部自动加载图文列表效果
  • 原文地址:https://www.cnblogs.com/jijm123/p/14270131.html
Copyright © 2011-2022 走看看