Delphi USBCamera DirectShow 预览录像截图

参照:https://www.haolizi.net/example/view_37332.html

实例介绍

【实例简介】用DirectShow 的方式,视频输入设备枚举、视频支持格式枚举、视频预览、视频录像、视频截图。Delphi 源代码

【实例截图】

 

 

 

 

 

 【核心代码】

unit untUSBCamera;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, DirectShow9, ActiveX, Dialogs, StdCtrls, ExtCtrls;

type
  PVideoInputInfo  = ^TVideoInputInfo;
  PVideoFormatInfo = ^TVideoFormatInfo;

  { 视频输入设备 }
  TVideoInputInfo = record
    id: TGUID;
    strName: ShortString;
    index: Integer;
  end;

  { 视频支持格式 }
  TVideoFormatInfo = record
    id: TGUID;
    iWidth, iHeight: Integer;
    iMod: TGUID;
    Frame: Int64;
    format: ShortString;
  end;

type
  IAMStreamConfig = interface(IUnknown)
    ['{C6E13340-30AC-11d0-A18C-00A0C9118956}']
    function SetFormat(const pmt: TAMMediaType): HResult; stdcall;
    function GetFormat(out ppmt: PAMMediaType): HResult; stdcall;
    function GetNumberOfCapabilities(out piCount, piSize: Integer): HResult; stdcall;
    { Delphi 声明有误,修改声明 }
    function GetStreamCaps(iIndex: Integer; var ppmt: PAMMediaType; pSCC: PVideoStreamConfigCaps): HResult; stdcall;
  end;

  ISampleGrabber = interface(IUnknown)
    ['{6B652FFF-11FE-4FCE-92AD-0266B5D7C78F}']
    function SetOneShot(OneShot: BOOL): HResult; stdcall;
    { Delphi 声明有误,修改声明 }
    function SetMediaType(pType: PAMMediaType): HResult; stdcall;
    function GetConnectedMediaType(out pType: TAMMediaType): HResult; stdcall;
    function SetBufferSamples(BufferThem: BOOL): HResult; stdcall;
    function GetCurrentBuffer(var pBufferSize: longint; pBuffer: Pointer): HResult; stdcall;
    function GetCurrentSample(out ppSample: IMediaSample): HResult; stdcall;
    function SetCallback(pCallback: ISampleGrabberCB; WhichMethodToCallback: longint): HResult; stdcall;
  end;

  { 枚举所有视频输入设备 }
procedure EnumAllUSBCamera(strsList: TStrings);

{ 枚举视频支持格式 }
function EnumVideoFormat(const strFriendlyName: String; const intIndex: Integer; strsList: TStrings): Boolean;

{ 视频预览 }
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;

{ 视频录制 }
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;

implementation

const
  IID_IPropertyBag: TGUID = '{55272A00-42CB-11CE-8135-00AA004BB851}';

  c_arrVideoFormatGUID: array [0 .. 35] of PGUID = (                                                                                 //
    @MEDIASUBTYPE_CLPL, @MEDIASUBTYPE_YUYV, @MEDIASUBTYPE_IYUV, @MEDIASUBTYPE_YVU9, @MEDIASUBTYPE_Y411, @MEDIASUBTYPE_Y41P,          //
    @MEDIASUBTYPE_YUY2, @MEDIASUBTYPE_YVYU, @MEDIASUBTYPE_UYVY, @MEDIASUBTYPE_Y211, @MEDIASUBTYPE_YV12, @MEDIASUBTYPE_CLJR,          //
    @MEDIASUBTYPE_IF09, @MEDIASUBTYPE_CPLA, @MEDIASUBTYPE_MJPG, @MEDIASUBTYPE_TVMJ, @MEDIASUBTYPE_WAKE, @MEDIASUBTYPE_CFCC,          //
    @MEDIASUBTYPE_IJPG, @MEDIASUBTYPE_Plum, @MEDIASUBTYPE_DVCS, @MEDIASUBTYPE_DVSD, @MEDIASUBTYPE_MDVF, @MEDIASUBTYPE_RGB1,          //
    @MEDIASUBTYPE_RGB4, @MEDIASUBTYPE_RGB8, @MEDIASUBTYPE_RGB565, @MEDIASUBTYPE_RGB555, @MEDIASUBTYPE_RGB24, @MEDIASUBTYPE_RGB32,    //
    @MEDIASUBTYPE_ARGB1555, @MEDIASUBTYPE_ARGB4444, @MEDIASUBTYPE_ARGB32, @MEDIASUBTYPE_AYUV, @MEDIASUBTYPE_AI44, @MEDIASUBTYPE_IA44 //
    );

  c_arrVideoFormatName: array [0 .. 35] of AnsiString = (    //
    'CLPL', 'YUYV', 'IYUV', 'YVU9', 'Y411', 'Y41P',          //
    'YUY2', 'YVYU', 'UYVY', 'Y211', 'YV12', 'CLJR',          //
    'IF09', 'CPLA', 'MJPG', 'TVMJ', 'WAKE', 'CFCC',          //
    'IJPG', 'Plum', 'DVCS', 'DVSD', 'MDVF', 'RGB1',          //
    'RGB4', 'RGB8', 'RGB565', 'RGB555', 'RGB24', 'RGB32',    //
    'ARGB1555', 'ARGB4444', 'ARGB32', 'AYUV', 'AI44', 'IA44' //
    );

function GetMaxIndex(const strsList: TStrings; const strUSBCameraName: string): Integer;
var
  III, Count: Integer;
begin
  Result  := 0;
  Count   := strsList.Count;
  for III := 0 to Count - 1 do
  begin
    if CompareText(String(PVideoInputInfo(strsList.Objects[III])^.strName), strUSBCameraName) = 0 then
    begin
      Result := Result   1;
    end;
  end;
end;

function CreateFilter(gid: TGUID; FriendlyName: AnsiString; instanceIndex: Integer): IBaseFilter;
var
  pSysDevEnum: ICreateDevEnum;
  pEnumCat   : IEnumMoniker;
  pMoniker   : IMoniker;
  cFetched   : ULONG;
  pPropBag   : IPropertyBag;
  bc         : IBindCtx;
  mo         : IMoniker;
  er         : IErrorLog;
  ov         : OleVariant;
  iIndex     : Integer;
begin
  Result := nil;

  pSysDevEnum := nil;
  pEnumCat    := nil;
  pMoniker    := nil;

  if CocreateInstance(CLSID_SystemDeviceEnum, nil, CLSCTX_INPROC, IID_ICreateDevEnum, pSysDevEnum) = S_OK then
  begin
    if pSysDevEnum.CreateClassEnumerator(gid, pEnumCat, 0) = S_OK then
    begin
      iIndex := 0;
      while (pEnumCat.Next(1, pMoniker, @cFetched) = S_OK) and (cFetched > 0) and (pMoniker <> nil) do
      begin
        bc := nil;
        mo := nil;
        pMoniker.BindToStorage(bc, mo, IID_IPropertyBag, pPropBag);
        er := nil;
        pPropBag.Read('FriendlyName', ov, er);
        if AnsiString(ov) = FriendlyName then
        begin
          if iIndex = instanceIndex then
          begin
            bc := nil;
            mo := nil;
            pMoniker.BindToObject(bc, mo, IID_IBaseFilter, Result);
          end;
          Inc(iIndex);
        end;
        pPropBag := nil;
        pMoniker := nil;
      end;
    end;
  end;
  pEnumCat    := nil;
  pSysDevEnum := nil;
end;

function VideoMediaSubTypeToStr(mst: TGUID): AnsiString;
var
  I: Integer;
begin
  Result := '';
  for I  := 0 to 35 do
    if CompareMem(c_arrVideoFormatGUID[I], @mst, sizeof(TGUID)) then
    begin
      Result := c_arrVideoFormatName[I];
      break;
    end;
end;

procedure FreeMediaType(mt: TAMMediaType);
begin
  if (mt.cbFormat <> 0) then
  begin
    CoTaskMemFree(mt.pbFormat);
    mt.cbFormat := 0;
    mt.pbFormat := nil;
  end;
  if (mt.pUnk <> nil) then
  begin
    mt.pUnk := nil;
  end;
end;

procedure DeleteMediaType(pmt: PAMMediaType);
begin
  if pmt <> nil then
  begin
    FreeMediaType(pmt^);
    CoTaskMemFree(pmt);
  end;
end;

function GetOutputPin(filter: IBaseFilter): IPin;
var
  penum: IEnumPins;
  f    : Integer;
  d    : PIN_DIRECTION;
begin
  Result := nil;
  filter.EnumPins(penum);
  while (penum.Next(1, Result, @f) = S_OK) and (f > 0) do
  begin
    if (Result.QueryDirection(d) = S_OK) and (d = PINDIR_OUTPUT) then
    begin
      { 找到输出接口,返回 }
      Exit;
    end;
  end;
  Result := nil;
end;

function StrToVideoMediaSubType(ss: AnsiString): TGUID;
var
  I: Integer;
begin
  Result := c_arrVideoFormatGUID[0]^;
  for I  := 0 to 35 do
    if ss = c_arrVideoFormatName[I] then
    begin
      Result := c_arrVideoFormatGUID[I]^;
      break;
    end;
end;

function CompareGUID(const g1, g2: TGUID): Boolean;
begin
  Result := CompareMem(@g1, @g2, sizeof(TGUID));
end;

function SetMediaType(filter: IBaseFilter; Width, Height: Integer; format: AnsiString): Boolean;
var
  pmt            : PAMMediaType;
  piCount, piSize: Integer;
  I              : Integer;
  pSCC           : PVideoStreamConfigCaps;
  streamConfig   : IAMStreamConfig;
  outPin         : IPin;
  formatID       : TGUID;
  selectedIndex  : Integer;
  ih             : PVIDEOINFOHEADER;
  bitRate        : dword;
begin
  Result := False;

  if (Width = 0) or (Height = 0) then
    Exit;

  outPin := GetOutputPin(filter);
  outPin.QueryInterface(IID_IAMStreamConfig, streamConfig);
  if assigned(streamConfig) then
  begin
    selectedIndex := -1;
    bitRate       := 0;
    formatID      := StrToVideoMediaSubType(format);
    streamConfig.GetNumberOfCapabilities(piCount, piSize);
    getmem(pSCC, piSize);
    try
      for I := 0 to piCount - 1 do
      begin
        streamConfig.GetStreamCaps(I, pmt, pSCC);
        ih := Pointer(pmt^.pbFormat);
        if (pSCC^.MinOutputSize.cx = Width) and (pSCC^.MinOutputSize.cy = Height) and (ih^.bmiHeader.biWidth = Width) and (ih^.bmiHeader.biheight = Height) and ((format = '') or (CompareGUID(formatID, pmt^.subtype))) and (ih^.dwBitRate > bitRate) // select format with maximum bitrate
        then
        begin
          selectedIndex := I;
          bitRate       := ih^.dwBitRate;
        end;
        DeleteMediaType(pmt);
      end;
      if selectedIndex > -1 then
      begin
        streamConfig.GetStreamCaps(selectedIndex, pmt, pSCC);
        try
          streamConfig.SetFormat(pmt^);
        finally
          DeleteMediaType(pmt);
        end;
      end;
    finally
      FreeMem(pSCC);
    end;
  end;
  Result := True;
end;

{ 枚举所有视频输入设备 }
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 CommonVideo(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 := CommonVideo(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 := CommonVideo(FIGraphBuilder, FICaptureGraphBuilder2, FSysDevEnum, FIVideoWindow, FIMediaControl, FISampleGrabber, pv, pf, pnl, strSaveFileName, True, True);
end;

end.

 

posted on 2021-01-13 01:20  癫狂编程  阅读(876)  评论(0编辑  收藏  举报

导航

好的代码像粥一样,都是用时间熬出来的