home *** CD-ROM | disk | FTP | other *** search
/ Chip 2001 December / Chip_2001-12_cd1.bin / zkuste / delphi / kompon / d56 / CMDXCAP.ZIP / DXCapture.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2001-09-17  |  78.9 KB  |  2,358 lines

  1. //******************************************************************************
  2. //  TCapture -  Capturing from VFW, WDM or DV compatible devices
  3. //
  4. //              Compiled with Delphi6 Personal Edition
  5. //              You need DirectX 8 or above installed!
  6. //
  7. //  based on:
  8. //  - Microsoft's AMCap & StillCap
  9. //  - DirectX Jedi Compilation
  10. //  - DScapture by orthkon * www.mp3.com/orthkon * orthkon@mail.com
  11. //  - TVideoCapture by E. Averchekov  e_g_o_r@mail.ru
  12. //
  13. //  new attempt by orthkon / orthkon@mail.com
  14. //
  15. //  history log:
  16. //  01-09-11 - v1.00
  17. //    America under Attack!!! i'm so sorry 8(
  18. //  01-09-16 - v1.01
  19. //    - i'm happy about Averchekov's nice work
  20. //    - i removed the bitmap routines, sorry :)
  21. //    - optimized capture callback class
  22. //    - added audio capturing
  23. //  01-09-17 - v1.02
  24. //    - added device owned option dialogs ( TDeviceOptions )
  25. //    - added audio + video format settings
  26. //  01-09-18 - v1.03
  27. //    - the image routines are back :)
  28. //    - to save an image call SaveAsBitmap or SaveAsJpeg
  29. //    - removed some bugs
  30. //
  31. //  you can use this code under GNU license, found at www.gnu.org
  32. //  if you make changed source or components public, please inform us
  33. //******************************************************************************
  34. unit DXCapture;
  35.  
  36. interface
  37.  
  38. uses
  39.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  40.   extctrls, DirectShow, ActiveX, DirectSound, Menus, MMSystem, jpeg;
  41.  
  42. const
  43.   WM_FGNOTIFY = WM_USER + 1;
  44.   WM_CAPTURE_BITMAP = WM_USER + 2;
  45.     SAMPLE_RATE : array[0..3] of Cardinal = ( 8000, 11025, 22050, 44100 );
  46.  
  47. type
  48.   EVideoCaptureError = class(Exception);
  49.  
  50. type
  51.     TFProcessBuffer = procedure( Frame : Cardinal; Buffer : Pointer; Size : Integer ) of object;
  52.  
  53.   TSampleGrabberCB = class(TObject, ISampleGrabberCB)
  54.   protected
  55.     FOwner : HWND;
  56.     FEnabled : boolean;
  57.     FProcessBuffer : TFProcessBuffer;
  58.     FFrame : Cardinal;
  59.   public
  60.     { IUnknown }
  61.     function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
  62.     function _AddRef: Integer; stdcall;
  63.     function _Release: Integer; stdcall;
  64.     { ISampleGrabberCB }
  65.     function SampleCB(SampleTime: Double; pSample: IMediaSample): HResult; stdcall;
  66.     function BufferCB(SampleTime: Double; pBuffer: PByte; BufferLen: longint): HResult; stdcall;
  67.   public
  68.     property Owner: HWND read FOwner write FOwner;
  69.     property Enabled: Boolean read FEnabled write FEnabled;
  70.     property ProcessBuffer : TFProcessBuffer read FProcessBuffer write FProcessBuffer;
  71.     constructor Create;
  72.     destructor Destroy; override;
  73.   end;
  74.  
  75. type
  76.   TBitmapCapturedEvent = procedure(CapturedImage: TBitmap) of object;
  77.  
  78. type
  79.   TDVSize = (dvsDontWorry, dvsFull, dvsHalf, dvsQuater, dvsDC);
  80.  
  81. const
  82.   DVSizeName: array [TDVSize] of string =
  83.   ('═σ Φ∞σσ≥ τφα≈σφΦ ', '╧εδφ√Θ ≡ατ∞σ≡', '╧εδεΓΦφα', '╫σ≥Γσ≡≥ⁿ', '1/8 Φ±⌡εΣφεπε');
  84.  
  85.  
  86. // device option dialog identifier constants
  87. const
  88.     DEVOPT_VFORMAT          = 1;
  89.     DEVOPT_VSOURCE          = 2;
  90.     DEVOPT_VDISPLAY         = 3;
  91.     DEVOPT_VCAPTURE         = 4;
  92.     DEVOPT_VCROSSBAR        = 5;
  93.     DEVOPT_TVTUNER          = 6;
  94.     DEVOPT_ACAPTURE         = 7;
  95.     DEVOPT_ACROSSBAR        = 8;
  96.     DEVOPT_TVAUDIO          = 9;
  97.     DEVOPT_VCAPTURE_PIN     = 10;
  98.     DEVOPT_VPREVIEW_PIN     = 11;
  99.     DEVOPT_ACAPTURE_PIN     = 12;
  100.  
  101. type
  102.   TCapture = class;
  103.  
  104.   TDeviceOption = record
  105.     szCaption : String;
  106.     iType : Integer;
  107.     bVideo : Boolean; // true = video, false = audio
  108.   end;
  109.  
  110.   TDeviceOptions = class
  111.   private
  112.     FOptions : array of TDeviceOption;
  113.     FCount : Integer;
  114.     FOwner : TCapture;
  115.     function GetCaption( Index : Integer ) : String;
  116.     procedure SetCaption( Index : Integer; const szCaption : String );
  117.     function Call( Index : Integer ) : HRESULT;
  118.     function GetVideo( Index : Integer ) : Boolean;
  119.   public
  120.     procedure Add( iType : Integer; const szCaption : String; bVideo : Boolean );
  121.     procedure Clear;
  122.     constructor Create( Owner : TCapture ); overload;
  123.     function Dialog( const szCaption : String ) : HRESULT;
  124.   public
  125.     property Count : Integer read FCount;
  126.     property Captions[Index: Integer] : String read GetCaption write SetCaption;
  127.     property Dialogs[Index: Integer] : HRESULT read Call;
  128.     property IsVideo[Index: Integer] : Boolean read GetVideo;
  129.   end;
  130.  
  131.   TCapture = class(TCustomControl)
  132.   private
  133.     Graph: IGraphBuilder;
  134.     Builder: ICaptureGraphBuilder2;
  135.     VideoWindow: IVideoWindow;
  136.     MediaEvent: IMediaEventEx;
  137.     DroppedFrames: IAMDroppedFrames;
  138.     VideoCompression: IAMVideoCompression;
  139.     CaptureDialogs: IAMVfwCaptureDialogs;
  140.     AStreamConf: IAMStreamConfig;      // for audio capture
  141.     VStreamConf: IAMStreamConfig;      // for video capture
  142.     Render: IBaseFilter;
  143.     VCap: IBaseFilter;
  144.     ACap: IBaseFilter;
  145.     Sink: IFileSinkFilter;
  146.     ConfigAviMux: IConfigAviMux;
  147.     Grabber: ISampleGrabber;
  148.  
  149.     VGrabberCB: TSampleGrabberCB;
  150.     AGrabberCB: TSampleGrabberCB;
  151.     FDeviceOptions : TDeviceOptions;
  152.         FAudioFormat : TWaveFormatEx;
  153.         FVideoFormat : TVIDEOINFOHEADER;
  154.     mVideo, mAudio: IMoniker;
  155.         p_mtVideo, p_mtAudio : PAM_Media_Type;
  156.  
  157.     fCapAudioIsRelevant: boolean;
  158.     fCapAudio: boolean;
  159.     fCCAvail: boolean;
  160.     fCapCC: boolean;
  161.     fCaptureGraphBuilt: boolean;
  162.     fPreviewGraphBuilt: boolean;
  163.     fPreviewFaked: boolean;
  164.     FVCapFriendlyName: string;
  165.  
  166.     FCapturing: boolean;
  167.     FPreviewing: boolean;
  168.     FUseFrameRate: boolean;
  169.     FUseTimeLimit: boolean;
  170.     FWantPreview: boolean;
  171.     FCapStartTime: DWORD;
  172.     FCapStopTime: DWORD;
  173.     FMasterStream: integer;
  174.  
  175.     FVideoWidth: integer;
  176.     FVideoHeight: integer;
  177.     FVideoFrameRate: double;
  178.     FVideoBitCount: integer;
  179.  
  180.     FAudioSampleRate : Cardinal;
  181.     FAudioBitRate : Integer;
  182.     FAudioChannels : Integer;
  183.  
  184.     FNotDropped: integer;
  185.     FDroppedFrames: integer;
  186.     FNotDroppedBase: integer;
  187.     FDroppedBase: integer;
  188.  
  189.     FCapTime: DWORD;
  190.     FCaptureTimer: TTimer;
  191.     FTempCaptureFileName: string;
  192.     FCaptureFileName: string;
  193.  
  194.     // to save the old function
  195.     FProcessBuffer : TFProcessBuffer;
  196.     FStillImage : Boolean;
  197.     FImageFile : String;
  198.     FImageType : Integer;
  199.     FImageQuality : Integer;
  200.  
  201.     FTimeLimit: integer;
  202.     FUseTempFile: boolean;
  203.     FPreallocFileSize: Cardinal;
  204.     FDVSize: TDVSize;
  205.     FCaptureFile: WideString;
  206.  
  207.     FOnStopPreview: TNotifyEvent;
  208.     FOnStartPreview: TNotifyEvent;
  209.     FOnStopCapture: TNotifyEvent;
  210.     FOnStartCapture: TNotifyEvent;
  211.     FOnChangeDevice: TNotifyEvent;
  212.     FOnCaptureProgress: TNotifyEvent;
  213.     FOnVideoFormatChange: TNotifyEvent;
  214.     FOnAudioFormatChange: TNotifyEvent;
  215.  
  216.     procedure SetMasterStream(const Value: integer);
  217.     property MasterStream: integer read FMasterStream write SetMasterStream;
  218.     procedure SetTempCaptureFileName(const Value: string);
  219.     procedure SetCaptureFileName(const Value: string);
  220.     function AllocCaptureFile(const SizeMb: integer): boolean;
  221.     function SaveCaptureFile(const FileName: string): boolean;
  222.  
  223.   private
  224.     procedure SetSize(var msg: TMessage); message WM_SIZE;  // Changing size of cap window
  225.     procedure GraphEvent(var msg: TMessage); message WM_FGNOTIFY;
  226.     procedure CaptureProgress(Sender: TObject);
  227.     procedure ResizeWindow;
  228.     procedure ChooseDevices(nmVideo, nmAudio: IMoniker); overload;
  229.     procedure UpdateStatus;
  230.  
  231.     function InitCapFilters: boolean;
  232.     procedure CleanUp;
  233.     procedure FreeCapFilters;
  234.     function MakeBuilder: boolean;
  235.     function MakeGraph: boolean;
  236.     procedure NukeDownstream(pf: IBaseFilter);
  237.     procedure TearDownGraph;
  238.     function RenderPreviewPin: boolean;
  239.     function FindVideoWindow: boolean;
  240.     function AdjustVideoGrabber: boolean;
  241.     function AdjustAudioGrabber: boolean;
  242.     function BuildPreviewGraph: boolean;
  243.     function BuildCaptureGraph: boolean;
  244.  
  245.     procedure GetDeviceOptions;
  246.     function OptionDialog( iType : Integer ) : HRESULT;
  247.  
  248.     function CreateBitmap( Buffer : Pointer; Size : Integer ) : TBitmap;
  249.     procedure OnVideoFrame( Frame : Cardinal; Buffer : Pointer; Size : Integer );
  250.  
  251.   public
  252.     // state properties
  253.     property Capturing: boolean read FCapturing;
  254.     property Previewing: boolean read FPreviewing;
  255.  
  256.     property VCapFriendlyName: string read FVCapFriendlyName;
  257.  
  258.     property VideoWidth: Integer read FVideoWidth;
  259.     property VideoHeight: Integer read FVideoHeight;
  260.     property VideoFrameRate: Double read FVideoFrameRate;
  261.     property VideoBitCount: Integer read FVideoBitCount;
  262.  
  263.     property AudioSampleRate : Cardinal read FAudioSampleRate;
  264.     property AudioBitRate : Integer read FAudioBitRate;
  265.     property AudioChannels : Integer read FAudioChannels;
  266.  
  267.     property ImageFile : String read FImageFile write FImageFile;
  268.  
  269.     property FramesDropped: integer read FDroppedFrames;
  270.     property NotDropped: integer read FNotDropped;
  271.     property CapStartTime: DWORD read FCapStartTime;
  272.     property CapStopTime: DWORD read FCapStopTime;
  273.     property CapTime: DWORD read FCapTime;
  274.  
  275.     property DeviceOptions : TDeviceOptions read FDeviceOptions; 
  276.  
  277.     procedure ChooseDevices(szVideo, szAudio: string); overload;
  278.     function Init: boolean;
  279.     function StartPreview: boolean;
  280.     function StopPreview: boolean;
  281.     function StartCapture(const Dialog: boolean = false): boolean;
  282.     function StopCapture: boolean;
  283.  
  284.         procedure SetAudioFormat( SamplesPerSec : Cardinal; Channels, BitsPerSec : Word );
  285.         procedure SetVideoFormat( Width, Height : Integer; BitCount : Word; FrameRate : Double );
  286.  
  287.     procedure SaveAsBitmap;
  288.     procedure SaveAsJpeg( Quality : TJPEGQualityRange ); // 0 - 100 %
  289.  
  290.     constructor Create(AOwner: TComponent); override;
  291.     destructor Destroy; override;
  292.   published
  293.     property DVPreviewSize: TDVSize read FDVSize write FDVSize;
  294.  
  295.     // set to true if you want to capture single frames during preview
  296.     property WantPreview: boolean read FWantPreview write FWantPreview;
  297.     property UseFrameRate: boolean read FUseFrameRate write FUseFrameRate;
  298.     property CaptureFileName: string read FCaptureFileName write SetCaptureFileName;
  299.     property UseTimeLimit: boolean read FUseTimeLimit write FUseTimeLimit;
  300.     property TimeLimit: integer read FTimeLimit write FTimeLimit;
  301.     property UseTempFile: boolean read FUseTempFile write FUseTempFile;
  302.     property PreallocFileSize: Cardinal read FPreallocFileSize write FPreallocFileSize default 10;
  303.     property TempCaptureFileName: string read FTempCaptureFileName write SetTempCaptureFileName;
  304.  
  305.     property OnChangeDevice: TNotifyEvent read FOnChangeDevice write FOnChangeDevice;
  306.     property OnCaptureProgress: TNotifyEvent read FOnCaptureProgress write FOnCaptureProgress;
  307.     property OnStopCapture: TNotifyEvent read FOnStopCapture write FOnStopCapture;
  308.     property OnStopPreview: TNotifyEvent read FOnStopPreview write FOnStopPreview;
  309.     property OnStartCapture: TNotifyEvent read FOnStartCapture write FOnStartCapture;
  310.     property OnStartPreview: TNotifyEvent read FOnStartPreview write FOnStartPreview;
  311.     property OnVideoFormatChange: TNotifyEvent read FOnVideoFormatChange write FOnVideoFormatChange;
  312.     property OnAudioFormatChange: TNotifyEvent read FOnAudioFormatChange write FOnAudioFormatChange;
  313.  
  314.     // grabber classes
  315.     property VideoGrabber: TSampleGrabberCB read VGrabberCB;
  316.     property AudioGrabber: TSampleGrabberCB read AGrabberCB;
  317.  
  318.   published
  319.     property Align;
  320.     property Color;
  321.     property Visible;
  322.     property OnMouseMove;
  323.     property OnMouseUp;
  324.     property OnMouseDown;
  325.     property OnClick;
  326.     property OnDblClick;
  327.  end;
  328.  
  329.  
  330. // device enum functions
  331. // caller have to free aquired list!!!
  332. function GetVideoDevicesList(const Refresh: boolean = false): TStringList;
  333. function GetAudioDevicesList(const Refresh: boolean = false): TStringList;
  334.  
  335. procedure Register;
  336.  
  337. implementation
  338.  
  339. uses contnrs;
  340.  
  341. //-----------------------------------------------------------------
  342. resourcestring
  343.   rsDShowCapture = 'DirectShow - Capture';
  344.   rsGraphCantPreview = 'This graph can''t preview!';
  345.   rsCantRenderCC = 'Cannot render closed captioning!';
  346.   rsGraphCantBePreviewedProperly = 'This graph cannot be previewwd properly!';
  347.   rsCantSetPreviewFrameRate = '%x: can''t set preview frame rate!';
  348.   rsSetMasterStreamFailed = 'SetMasterStream failed!';
  349.   rsCantSetCaptureFile = 'Can''t set capture file!';
  350.   rsCantRenderVCaptureStream = 'Can''t render video capture stream!';
  351.   rsCantRenderPreviewStream = 'Can''t render preview stream!';
  352.   rsCantRenderACaptureStream = 'Can''t render audio preview stream!';
  353.   rsThisGraphCantPreview = 'This graph cannot preview!';
  354.   rsCantSetCaptureFrameRate = 'Cannot set frame rate for capture!';
  355.   rsCantMakeGraphBuilder = 'Can''t init graph builder. Probably DirectShow is not installed!';
  356.   rsCantCreateVCaptureFilter = 'Error %x.'#13#10'Can''t create video capture filter - propably you havn''t video capture device!';
  357.   rsCantMakeGraph = 'Can''t init graph. Probably DirectShow is not installed!';
  358.   rsCantSetFilterGraph = 'Can''t set filter graph!';
  359.   rsCantAddVFilterToGraph = 'Error %x: Can''t add video capture filter into graph!';
  360.   rsCantFindVStreamConfig = 'Error %x: Can''t find VCapture:IAMStreamConfig!';
  361.   rsCantMakeACapFilter = 'Can''t create audio capture filter!';
  362.   rsCantAddAFilterToGraph = 'Error %x: Can''t add audio capture filter into graph!';
  363.   rsCantFindAStreamConfig = 'Can''t find ACapture:IAMStreamConfig!';
  364.   rsCantRunPreviewGraph = 'Error %x: Cannot run preview graph!';
  365.   rsCantStopPreviewGraph = 'Error %x: Cannot stop preview graph!';
  366.   rsCantGetMediaControl = 'Error %x: Can''t get IMediaControl!';
  367.   rsCantRunGraph = 'Error %x: Cannot run graph!';
  368.   rsStartCapture = 'Starting capture!';
  369.   rsCantStopGraph = 'Error %x: Cannot stop graph!';
  370.   rsEmptyFileName = 'Capture filename required!';
  371.   rsFailedToAllocFileSize = 'Can''t allocate space for capture file! Disk full?';
  372.  
  373. //-----------------------------------------------------------------
  374.  
  375. const
  376.   IID_IPropertyBag: TGUID = '{55272A00-42CB-11CE-8135-00AA004BB851}';
  377.     IID_ISpecifyPropertyPages : TGUID = '{B196B28B-BAB4-101A-B69C-00AA00341D07}';
  378.  
  379. type
  380.   PVIDEOINFOHEADER = ^TVIDEOINFOHEADER;
  381.   TVIDEOINFOHEADER = record
  382.     rcSource: TRECT;          // The bit we really want to use
  383.     rcTarget: TRECT;          // Where the video should go
  384.     dwBitRate: Cardinal;      // Approximate bit data rate
  385.     dwBitErrorRate: Cardinal; // Bit error rate for this stream
  386.     AvgTimePerFrame: Int64;   // Average time per frame (100ns units)
  387.     bmiHeader: BITMAPINFOHEADER;
  388.   end;
  389.  
  390. const
  391.   DVSizes: array [TDVSize] of integer = ( 0,
  392.                                           DVRESOLUTION_FULL,
  393.                                           DVRESOLUTION_HALF,
  394.                                           DVRESOLUTION_QUARTER,
  395.                                           DVRESOLUTION_DC);
  396.  
  397.  
  398. //-----------------------------------------------------------------
  399. function MyMsg(szMsg: string; hr: HRESULT): string;
  400. begin
  401.   Result:= Format(szMsg, [hr]);
  402.   MessageBox(GetForegroundWindow, PChar(Result), PChar(rsDShowCapture), MB_OK or MB_ICONSTOP);
  403. end;
  404.  
  405. procedure ErrMsg(szMsg: string; hr: HRESULT = 0);
  406. begin
  407.   MyMsg(szMsg, hr);
  408. end;
  409.  
  410. procedure ErrMsgException(szMsg: string; hr: HRESULT = 0);
  411. begin
  412.   raise EVideoCaptureError.Create(MyMsg(szMsg, hr));
  413. end;
  414.  
  415. //-----------------------------------------------------------------
  416. function CheckGUID(p1, p2: TGUID): boolean;
  417. var
  418.   i: Integer;
  419. begin
  420.   for i:= 0 to 7 do if p1.D4[i] <> p2.D4[i] then begin
  421.     Result:= false;
  422.     Exit;
  423.   end;
  424.   Result:= (p1.D1 = p2.D1) and (p1.D2 = p2.D2) and (p1.D3 = p2.D3);
  425. end;
  426.  
  427. // Free an existing media type (ie free resources it holds)
  428. procedure FreeMediaType(mt: TAM_MEDIA_TYPE);
  429. begin
  430.   if mt.cbFormat <> 0 then begin
  431.     CoTaskMemFree(mt.pbFormat);
  432.     // Strictly unnecessary but tidier
  433.     mt.cbFormat:= 0;
  434.     mt.pbFormat:= nil;
  435.   end;
  436.   mt.pUnk:= nil;
  437. end;
  438.  
  439. procedure DeleteMediaType(pmt: PAM_MEDIA_TYPE);
  440. begin
  441.   // allow NULL pointers for coding simplicity
  442.   if pmt <> nil then begin
  443.     FreeMediaType(pmt^);
  444.     CoTaskMemFree(pmt);
  445.   end;
  446. end;
  447.  
  448. //-----------------------------------------------------------------
  449. { TCapDeviceInfo }
  450.  
  451. type
  452.   TCapDeviceInfo = class
  453.     DeviceName: string;
  454.     Moniker: IMoniker;
  455.     constructor Create(const aDeviceName: string; const aMoniker: IMoniker);
  456.   end;
  457.  
  458. constructor TCapDeviceInfo.Create(const aDeviceName: string; const aMoniker: IMoniker);
  459. begin
  460.   DeviceName:= aDeviceName;
  461.   Moniker:= aMoniker;
  462. end;
  463. //-----------------------------------------------------------------
  464.  
  465. var
  466.   VideoDevicesList,
  467.   AudioDevicesList: TObjectList;
  468.  
  469. procedure BuildDeviceList;
  470. var
  471.   SysDevEnum: ICreateDevEnum;
  472.   EnumCat: IEnumMoniker;
  473.   Moniker: IMoniker;
  474.   cFetched: Longint;
  475.   PropBag: IPropertyBag;
  476.   varName: OleVariant;
  477.   hr: HRESULT;
  478. begin
  479.   VideoDevicesList.Clear;
  480.   AudioDevicesList.Clear;
  481.   SysDevEnum:= nil;
  482.   hr:= CoCreateInstance(CLSID_SystemDeviceEnum, nil, CLSCTX_INPROC, IID_ICreateDevEnum, SysDevEnum);
  483.   if (hr = S_OK) then begin
  484.     // enum available video capture devices
  485.     EnumCat:= nil;
  486.     if (SysDevEnum.CreateClassEnumerator(CLSID_VideoInputDeviceCategory, EnumCat, 0)= S_OK) then
  487.       while EnumCat.Next(1, Moniker, @cFetched) = S_OK do begin
  488.         Moniker.BindToStorage(nil, nil, IID_IPropertyBag, PropBag);
  489.         PropBag.Read('FriendlyName', varName, nil);
  490.         VideoDevicesList.Add(TCapDeviceInfo.Create(varName, Moniker) );
  491.         PropBag:= nil;
  492.         Moniker:= nil;
  493.       end;
  494.     EnumCat:= nil;
  495.  
  496.     // enum available audio capture devices
  497.     if (SysDevEnum.CreateClassEnumerator(CLSID_AudioInputDeviceCategory, EnumCat, 0)= S_OK) then
  498.       while EnumCat.Next(1, Moniker, @cFetched) = S_OK do begin
  499.         Moniker.BindToStorage(nil, nil, IID_IPropertyBag, PropBag);
  500.         PropBag.Read('FriendlyName', varName, nil);
  501.         AudioDevicesList.Add(TCapDeviceInfo.Create(varName, Moniker));
  502.         PropBag:= nil;
  503.         Moniker:= nil;
  504.       end;
  505.     EnumCat:= nil;
  506.     SysDevEnum:= nil;
  507.   end;
  508. end;
  509.  
  510. function GetVideoDevicesList(const Refresh: boolean): TStringList;
  511. var i: integer;
  512. begin
  513.   Result:= TStringList.Create;
  514.   if Refresh or (VideoDevicesList.Count=0) then BuildDeviceList;
  515.   for i:= 0 to VideoDevicesList.Count-1 do
  516.     Result.Add(TCapDeviceInfo(VideoDevicesList[i]).DeviceName);
  517. end;
  518.  
  519. function GetAudioDevicesList(const Refresh: boolean): TStringList;
  520. var i: integer;
  521. begin
  522.   Result:= TStringList.Create;
  523.   if Refresh or (AudioDevicesList.Count=0) then BuildDeviceList;
  524.   for i:= 0 to AudioDevicesList.Count-1 do
  525.     Result.Add(TCapDeviceInfo(AudioDevicesList[i]).DeviceName);
  526. end;
  527.  
  528. //-----------------------------------------------------------------
  529.  
  530. { IUnknown }
  531. function TSampleGrabberCB._AddRef: Integer;
  532. begin
  533.   Result:= 2;
  534. end;
  535.  
  536. function TSampleGrabberCB._Release: Integer;
  537. begin
  538.   Result:= 1;
  539. end;
  540.  
  541. function TSampleGrabberCB.QueryInterface(const IID: TGUID; out Obj): HRESULT;
  542. begin
  543.   // We need to return the two event interfaces when they're asked for
  544.   Result:= E_NOINTERFACE;
  545.   if CheckGUID(IID, ISampleGrabberCB) or CheckGUID(IID, IUnknown) then begin
  546.     if GetInterface(IID,Obj) then Result := S_OK;
  547.   end;
  548. end;
  549.  
  550. { TSampleGrabberCB }
  551. constructor TSampleGrabberCB.Create;
  552. begin
  553.   FEnabled := True;
  554.   FFrame := 0;
  555. end;
  556.  
  557. destructor TSampleGrabberCB.Destroy;
  558. begin
  559.   inherited;
  560. end;
  561.  
  562. { TSampleGrabberCB - ISampleGrabberCB }
  563. function TSampleGrabberCB.BufferCB(SampleTime: Double; pBuffer: PByte; BufferLen: Integer): HResult;
  564. begin
  565.   if FEnabled then begin
  566.     Inc( FFrame );
  567.     if Assigned( FProcessBuffer ) then FProcessBuffer( FFrame, pBuffer, BufferLen );
  568.   end;
  569.   Result:= S_OK;
  570. end;
  571.  
  572. function TSampleGrabberCB.SampleCB(SampleTime: Double; pSample: IMediaSample): HResult;
  573. begin // not implemented
  574.   Result:= S_OK;
  575. end;
  576.  
  577. //-----------------------------------------------------------------
  578.  
  579. { TDeviceOptions }
  580.  
  581. function TDeviceOptions.Call( Index : Integer ) : HRESULT;
  582. begin
  583.   Result := E_FAIL;
  584.   if ( Index < 0 ) or ( Index >= FCount ) then Exit;
  585.   if FOwner = nil then Exit;
  586.   Result := FOwner.OptionDialog( FOptions[Index].iType );
  587. end;
  588.  
  589. function TDeviceOptions.Dialog( const szCaption : String ) : HRESULT;
  590. var
  591.   i : Integer;
  592. begin
  593.   Result := E_FAIL;
  594.   if FOwner = nil then Exit;
  595.   i := 0;
  596.   while i < FCount do begin
  597.     if FOptions[i].szCaption = szCaption then begin
  598.       Result := FOwner.OptionDialog( FOptions[i].iType );
  599.       Break;
  600.     end;
  601.     Inc( i );
  602.   end;
  603. end;
  604.  
  605. function TDeviceOptions.GetVideo( Index : Integer ) : Boolean;
  606. begin
  607.   if ( Index >= 0 ) and ( Index < FCount ) then Result := FOptions[Index].bVideo else Result := False;
  608. end;
  609.  
  610. function TDeviceOptions.GetCaption( Index : Integer ) : String;
  611. begin
  612.   Result := '';
  613.   if ( Index < 0 ) or ( Index >= FCount ) then Exit;
  614.   Result := FOptions[Index].szCaption;
  615. end;
  616.  
  617. procedure TDeviceOptions.SetCaption( Index : Integer; const szCaption : String );
  618. begin
  619.   if ( Index < 0 ) or ( Index >= FCount ) then Exit;
  620.   FOptions[Index].szCaption := szCaption;
  621. end;
  622.  
  623. procedure TDeviceOptions.Add( iType : Integer; const szCaption : String; bVideo : Boolean );
  624. begin
  625.   SetLength( FOptions, FCount + 1 );
  626.   FOptions[FCount].szCaption := szCaption;
  627.   FOptions[FCount].iType := iType;
  628.   FOptions[FCount].bVideo := bVideo;
  629.   Inc( FCount );
  630. end;
  631.  
  632. procedure TDeviceOptions.Clear;
  633. begin
  634.   FCount := 0;
  635. end;
  636.  
  637. constructor TDeviceOptions.Create( Owner : TCapture );
  638. begin
  639.   FOwner := Owner;
  640.   FCount := 0;
  641. end;
  642.  
  643. //-----------------------------------------------------------------
  644.  
  645. { TCapture }
  646.  
  647. constructor TCapture.Create(AOwner: TComponent);
  648. begin
  649.   inherited;
  650.   fUseFrameRate:= false;
  651.   fWantPreview:= true;
  652.   Color:= clBlue;
  653.   Width:= 320;
  654.   Height:= 240;
  655.   FPreallocFileSize:= 100;
  656.  
  657.   VGrabberCB:= TSampleGrabberCB.Create;
  658.   AGrabberCB:= TSampleGrabberCB.Create;
  659.   FDeviceOptions := TDeviceOptions.Create( Self );
  660.  
  661.   SetVideoFormat( 320, 240, 24, 15 );
  662.   SetAudioFormat( 22000, 1, 16 ); 
  663.  
  664.   CleanUp;
  665.  
  666.   FCaptureTimer:= TTimer.Create(Self);
  667.   FCaptureTimer.Interval:= 100;
  668.   FCaptureTimer.OnTimer:= CaptureProgress;
  669.  
  670. end;
  671.  
  672. destructor TCapture.Destroy;
  673. begin
  674.   VGrabberCB.Enabled := False;
  675.   AGrabberCB.Enabled := False;
  676.  
  677.   StopPreview;
  678.   StopCapture;
  679.   TearDownGraph;
  680.   CleanUp;
  681.  
  682.   FCaptureTimer.Enabled:= false;
  683.   FCaptureTimer.Free;
  684.  
  685.   VGrabberCB.Free;
  686.   AGrabberCB.Free;
  687.  
  688.   FDeviceOptions.Free;
  689.  
  690.   inherited;
  691. end;
  692.  
  693. procedure TCapture.ChooseDevices(nmVideo, nmAudio: IMoniker);
  694. begin
  695.   VGrabberCB.Enabled := False;
  696.   AGrabberCB.Enabled := False;
  697.  
  698.   mVideo:= nmVideo;
  699.   mAudio:= nmAudio;
  700.  
  701.   StopCapture;
  702.   StopPreview;
  703.   if fCaptureGraphBuilt or fPreviewGraphBuilt then TearDownGraph;
  704.   FreeCapFilters;
  705.   InitCapFilters;
  706.  
  707.   if FWantPreview then begin
  708.     BuildPreviewGraph;
  709.     StartPreview;
  710.   end;
  711.  
  712.   GetDeviceOptions;
  713.   if Assigned(FOnChangeDevice) then FOnChangeDevice(Self);
  714.  
  715.   VGrabberCB.Enabled := True;
  716.   AGrabberCB.Enabled := True;
  717. end;
  718.  
  719. procedure TCapture.ChooseDevices(szVideo, szAudio: string);
  720. var
  721.   nmVideo, nmAudio: IMoniker;
  722.   i: integer;
  723. begin
  724.   nmVideo:= nil;
  725.   nmAudio:= nil;
  726.  
  727.   for i:= 0 to VideoDevicesList.Count-1 do
  728.     with VideoDevicesList[i] as TCapDeviceInfo do
  729.       if DeviceName = szVideo then begin
  730.         nmVideo:= Moniker;
  731.         break;
  732.       end;
  733.  
  734.   for i:= 0 to AudioDevicesList.Count-1 do
  735.     with AudioDevicesList[i] as TCapDeviceInfo do
  736.       if DeviceName = szAudio then begin
  737.         nmAudio:= Moniker;
  738.         break;
  739.       end;
  740.  
  741.   ChooseDevices(nmVideo, nmAudio);
  742.   nmVideo:= nil;
  743.   nmAudio:= nil;
  744. end;
  745.  
  746. procedure TCapture.CleanUp;
  747. begin
  748.   FreeCapFilters;
  749.   
  750.   VideoWindow:= nil;
  751.   MediaEvent:= nil;
  752.   DroppedFrames:= nil;
  753.  
  754.   Render:= nil;
  755.   Sink:= nil;
  756.   ConfigAviMux:= nil;
  757.  
  758.   p_mtVideo := nil;
  759.   p_mtAudio := nil;
  760.  
  761.   fCapAudioIsRelevant:= true;
  762.   fCapAudio:= true;
  763.   fCCAvail:= false;
  764.   fCapCC:= false;
  765.   fCaptureGraphBuilt:= false;
  766.   fPreviewGraphBuilt:= false;
  767.   fPreviewFaked:= false;
  768.   fCapturing:= false;
  769.   fPreviewing:= false;
  770.   FMasterStream:= -1;
  771. end;
  772.  
  773. procedure TCapture.FreeCapFilters;
  774. begin
  775.   Graph:= nil;
  776.   Builder:= nil;
  777.   VCap:= nil;
  778.   ACap:= nil;
  779.   AStreamConf:= nil;
  780.   VStreamConf:= nil;
  781.   VideoCompression:= nil;
  782.   CaptureDialogs:= nil;
  783.   Grabber:= nil;
  784. end;
  785.  
  786. function TCapture.MakeBuilder: boolean;
  787. begin
  788.   Result:= (Builder <> nil) or
  789.            (CoCreateInstance( CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC,
  790.                               IID_ICaptureGraphBuilder2, Builder) = NOERROR);
  791. end;
  792.  
  793. function TCapture.MakeGraph: boolean;
  794. begin
  795.   Result:= (Graph <> nil) or
  796.            (CoCreateInstance( CLSID_FilterGraph, nil, CLSCTX_INPROC,
  797.                   IID_IGraphBuilder, Graph) = NOERROR);
  798. end;
  799.  
  800. function TCapture.Init: boolean;
  801. begin
  802.   // Create the filter graph and create the capture graph builder.
  803.   Result:= MakeGraph and MakeBuilder;
  804.  
  805.   if not Result then Exit;
  806.  
  807.   Builder.SetFiltergraph(Graph);
  808.   BuildDeviceList;
  809.   Result:= (VideoDevicesList.Count>0) or (AudioDevicesList.Count>0);
  810. end;
  811.  
  812. function TCapture.InitCapFilters: boolean;
  813. var
  814.   PropBag: IPropertyBag;
  815.   hr: HRESULT;
  816.   varOle: OleVariant;
  817.   pmt: PAM_MEDIA_TYPE;
  818.   Pin: IPin;
  819.   pins: IEnumPins;
  820.   n: Cardinal;
  821.   pinInfo: TPIN_INFO;
  822.   Found: boolean;
  823.   Ks: IKsPropertySet;
  824.   guid: TGUID;
  825.   dw: DWORD;
  826. begin
  827.   hr:= 0;
  828.  
  829.   Result:= MakeBuilder;
  830.   if not Result then begin
  831.     ErrMsg(rsCantMakeGraphBuilder);
  832.     Exit;
  833.   end;
  834.  
  835.   try
  836.     VCap:= nil;
  837.     if mVideo <> nil then begin
  838.       hr:= mVideo.BindToStorage(nil, nil, IID_IPropertyBag, PropBag);
  839.       if Succeeded(hr) then begin
  840.         hr:= PropBag.Read('FriendlyName', varOle, nil);
  841.         if hr = NOERROR then FVCapFriendlyName:= varOle;
  842.         PropBag:= nil;
  843.       end;
  844.       hr:= mVideo.BindToObject(nil, nil, IID_IBaseFilter, VCap);
  845.     end;
  846.     if VCap = nil then
  847.       ErrMsgException(rsCantCreateVCaptureFilter, hr);
  848.  
  849.     // make a filtergraph, give it to the graph builder and put the video
  850.     // capture filter in the graph
  851.     if not MakeGraph then ErrMsgException(rsCantMakeGraph);
  852.  
  853.     if Builder.SetFiltergraph(Graph) <> NOERROR then
  854.       ErrMsgException(rsCantSetFilterGraph);
  855.  
  856.     if Graph.AddFilter(VCap, nil) <> NOERROR then
  857.       ErrMsgException(rsCantAddVFilterToGraph, hr);
  858.  
  859.     // Calling FindInterface below will result in building the upstream
  860.     // section of the capture graph (any WDM TVTuners or Crossbars we might need).
  861.     // we use this interface to get the name of the driver
  862.     // Don't worry if it doesn't work:  This interface may not be available
  863.     // until the pin is connected, or it may not be available at all.
  864.     // (eg: interface may not be available for some DV capture)
  865.     hr:= Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved,
  866.                                 VCap, IID_IAMVideoCompression, VideoCompression);
  867.     if hr <> S_OK then
  868.       Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video,
  869.                              VCap, IID_IAMVideoCompression, VideoCompression);
  870.  
  871.     // !!! What if this interface isn't supported?
  872.     // we use this interface to set the frame rate and get the capture size
  873.     hr:= Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved,
  874.                                 VCap, IID_IAMStreamConfig, VStreamConf);
  875.     if hr <> NOERROR then begin
  876.       hr:= Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video,
  877.                                   VCap, IID_IAMStreamConfig, VStreamConf);
  878.       if hr <> NOERROR then // this means we can't set frame rate (non-DV only)
  879.         ErrMsg(rsCantFindVStreamConfig, hr);
  880.     end;
  881.  
  882.     fCapAudioIsRelevant:= true;
  883.  
  884.     // default capture format
  885.     if (VStreamConf <> nil) and (VStreamConf.GetFormat(pmt)=S_OK) then
  886.       try
  887.         // DV capture does not use a VIDEOINFOHEADER
  888.         if CheckGUID(pmt^.formattype, FORMAT_VideoInfo) then begin
  889.           p_mtVideo := pmt;
  890.           if Assigned( FOnVideoFormatChange ) then begin
  891.             FOnVideoFormatChange( Self );
  892.             pmt^.pbFormat := @FVideoFormat;
  893.             VStreamConf.SetFormat( pmt^ );
  894.           end;
  895.           p_mtVideo := nil;
  896.           // resize our window to the default capture size
  897.           FVideoWidth := PVIDEOINFOHEADER(pmt^.pbFormat)^.bmiHeader.biWidth;
  898.           FVideoHeight := ABS(PVIDEOINFOHEADER(pmt^.pbFormat)^.bmiHeader.biHeight);
  899.           ResizeWindow;
  900.         end;
  901.         if not CheckGUID(pmt^.majortype, MEDIATYPE_Video) then begin
  902.           // This capture filter captures something other that pure video.
  903.           // Maybe it's DV or something?  Anyway, chances are we shouldn't
  904.           // allow capturing audio separately, since our video capture
  905.           // filter may have audio combined in it already!
  906.           fCapAudioIsRelevant:= false;
  907.           fCapAudio:= false;
  908.         end;
  909.       finally
  910.         DeleteMediaType( pmt );
  911.       end;
  912.  
  913.     // we use this interface to bring up the 3 dialogs
  914.     // NOTE:  Only the VfW capture filter supports this.  This app only brings
  915.     // up dialogs for legacy VfW capture drivers, since only those have dialogs
  916.     Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video,
  917.                            VCap, IID_IAMVfwCaptureDialogs, CaptureDialogs);
  918.  
  919.     Found:= false;
  920.     Pin:= nil;
  921.  
  922.     if Succeeded(VCap.EnumPins(pins)) then begin
  923.       while (not Found) and (pins.Next(1, pin, n) = S_OK) do begin
  924.         if S_OK = pin.QueryPinInfo(pinInfo) then begin
  925.           if pinInfo.dir = PINDIR_INPUT then begin
  926.             // is this pin an ANALOGVIDEOIN input pin?
  927.             if pin.QueryInterface(IID_IKsPropertySet, Ks) = S_OK then begin
  928.               Found:= (Ks.Get(AMPROPSETID_Pin, 0, nil, 0, @guid, sizeof(guid), dw) = S_OK) and
  929.                        CheckGuid(guid, PIN_CATEGORY_ANALOGVIDEOIN);
  930.               Ks:= nil;
  931.             end;
  932.           end;
  933.           pinInfo.pFilter:= nil;
  934.         end;
  935.         pin:= nil;
  936.       end;
  937.       pins:= nil;
  938.     end;
  939.  
  940.     // there's no point making an audio capture filter
  941.     if (fCapAudioIsRelevant) then begin
  942.       // create the audio capture filter, even if we are not capturing audio right
  943.       // now, so we have all the filters around all the time.
  944.       //
  945.       // We want an audio capture filter and some interfaces
  946.       if mAudio = nil then begin
  947.         // there are no audio capture devices. We'll only allow video capture
  948.         fCapAudio:= false;
  949.       end
  950.       else begin
  951.         ACap:= nil;
  952.  
  953.         mAudio.BindToObject(nil, nil, IID_IBaseFilter, ACap);
  954.         if ACap = nil then begin
  955.           // there are no audio capture devices. We'll only allow video capture
  956.           fCapAudio:= false;
  957.           ErrMsg(rsCantMakeACapFilter);
  958.         end
  959.         else begin
  960.           // put the audio capture filter in the graph
  961.  
  962.           // We'll need this in the graph to get audio property pages
  963.           if Graph.AddFilter(ACap, nil) <> NOERROR then
  964.             ErrMsgException(rsCantAddAFilterToGraph, hr);
  965.  
  966.           // Calling FindInterface below will result in building the upstream
  967.           // section of the capture graph (any WDM TVAudio's or Crossbars we might need).
  968.  
  969.           // !!! What if this interface isn't supported?
  970.           // we use this interface to set the captured wave format
  971.           hr:= Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Audio,
  972.                                       ACap, IID_IAMStreamConfig, AStreamConf );
  973.           if hr <> NOERROR then ErrMsg(rsCantFindAStreamConfig);
  974.         end;
  975.       end;
  976.  
  977.       // default capture format
  978.       if (AStreamConf <> nil) and (AStreamConf.GetFormat(pmt)=S_OK) then
  979.         try
  980.           // DV capture does not use a VIDEOINFOHEADER
  981.           if CheckGUID(pmt^.formattype, FORMAT_WaveFormatEx) then begin
  982.             p_mtAudio := pmt;
  983.             p_mtAudio.pbFormat := @FAudioFormat;
  984.             if Assigned( FOnAudioFormatChange ) then begin
  985.               FOnAudioFormatChange( Self );
  986.               AStreamConf.SetFormat( pmt^ );
  987.             end;
  988.             p_mtAudio := nil;
  989.           end;
  990.         finally
  991.           DeleteMediaType(pmt);
  992.       end;
  993.  
  994.     end;
  995.  
  996.     // Can this filter do closed captioning?
  997.     hr:= Builder.FindPin( VCap, PINDIR_OUTPUT, PIN_CATEGORY_VBI, TGUID(nil^),
  998.                           false, 0, Pin);
  999.     if hr <> S_OK then
  1000.       hr:= Builder.FindPin( VCap, PINDIR_OUTPUT, PIN_CATEGORY_CC, TGUID(nil^),
  1001.                             false, 0, Pin);
  1002.  
  1003.     fCCAvail:= (hr = S_OK); // can't capture it, then
  1004.     if fCapCC then Pin:= nil;
  1005.  
  1006.     Result:= true;
  1007.   except
  1008.     FreeCapFilters;
  1009.     Result:= false;
  1010.   end;
  1011. end;
  1012.  
  1013. // Tear down everything downstream of a given filter
  1014. procedure TCapture.NukeDownstream(pf: IBaseFilter);
  1015. var
  1016.   pP, pTo: IPin;
  1017.   u: Cardinal;
  1018.   pins: IEnumPins;
  1019.   pininfo: TPIN_INFO;
  1020.   hr: HRESULT;
  1021. begin
  1022.   pins:= nil;
  1023.   hr:= pf.EnumPins(pins);
  1024.   pins.Reset;
  1025.   while hr = NOERROR do begin
  1026.     hr:= pins.Next(1, pP, u);
  1027.     if (hr = S_OK) and (pP <> nil) then begin
  1028.       pP.ConnectedTo(pTo);
  1029.       if pTo <> nil then begin
  1030.         hr:= pTo.QueryPinInfo(pininfo);
  1031.         if hr = NOERROR then begin
  1032.         if pininfo.dir = PINDIR_INPUT then begin
  1033.         NukeDownstream(pininfo.pFilter);
  1034.         Graph.Disconnect(pTo);
  1035.         Graph.Disconnect(pP);
  1036.             Graph.RemoveFilter(pininfo.pFilter);
  1037.       end;
  1038.           pininfo.pFilter:= nil;
  1039.         end;
  1040.         pTo:= nil;
  1041.       end;
  1042.       pP:= nil;
  1043.     end;
  1044.   end;
  1045.   pins:= nil;
  1046. end;
  1047.  
  1048. // make sure the preview window inside our window is as big as the
  1049. // dimensions of captured video, or some capture cards won't show a preview.
  1050. // (Also, it helps people tell what size video they're capturing)
  1051. // We will resize our app's window big enough so that once the status bar
  1052. // is positioned at the bottom there will be enough room for the preview
  1053. // window to be w x h
  1054. //
  1055. procedure TCapture.ResizeWindow;
  1056. begin
  1057.   if Assigned(VideoWindow) then
  1058.     VideoWindow.SetWindowPosition(0, 0, ClientWidth, ClientHeight);
  1059. end;
  1060.  
  1061. procedure TCapture.SetSize(var msg: TMessage);
  1062. begin
  1063.   inherited;
  1064.   ResizeWindow;
  1065. end;
  1066.  
  1067. // graph event occured
  1068. // get events
  1069. procedure TCapture.GraphEvent(var msg: TMessage);
  1070. var
  1071.   Event, l1, l2: integer;
  1072.   wasCapturing, wasPreviewing: boolean;
  1073. begin
  1074.   wasCapturing:= Capturing;
  1075.   wasPreviewing:= Previewing;
  1076.   if (MediaEvent <> nil) then begin
  1077.     while MediaEvent.GetEvent(Event, l1, l2, 0) = S_OK do
  1078.       try
  1079.         case Event of
  1080.           EC_ERRORABORT: StopCapture;
  1081.           EC_DEVICE_LOST: begin
  1082.                             StopCapture;
  1083.                             StopPreview;
  1084.                           end;
  1085.           EC_REPAINT : begin
  1086.           end;
  1087.         end;
  1088.       finally
  1089.         MediaEvent.FreeEventParams(Event, l1, l2);
  1090.       end;
  1091.     // we have stopped capture need to restore preview
  1092.     if ((Capturing<>wasCapturing) or (wasPreviewing<>Previewing)) and (FWantPreview) then begin
  1093.       BuildPreviewGraph;
  1094.       StartPreview;
  1095.     end;
  1096.   end;
  1097. end;
  1098.  
  1099. function TCapture.RenderPreviewPin: boolean;
  1100. var
  1101.   hr: HRESULT;
  1102.   DVDec: IBaseFilter;
  1103.   DVDecProp: IIPDVDec;
  1104. begin
  1105.   Result:= true;
  1106.   fPreviewFaked:= false;
  1107.   // Render the preview pin - even if there is not preview pin, the capture
  1108.   // graph builder will use a smart tee filter and provide a preview.
  1109.   // !!! what about latency/buffer issues?
  1110.   // NOTE that we try to render the interleaved pin before the video pin, because
  1111.   // if BOTH exist, it's a DV filter and the only way to get the audio is to use
  1112.   // the interleaved pin.  Using the Video pin on a DV filter is only useful if
  1113.   // you don't want the audio.
  1114.   hr:= Builder.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Interleaved, VCap, nil, nil);
  1115.   if hr = VFW_S_NOPREVIEWPIN then begin
  1116.     // preview was faked up for us using the (only) capture pin
  1117.     fPreviewFaked:= true;
  1118.   end
  1119.   else if hr <> S_OK then begin
  1120.     // maybe it's DV?
  1121.     hr:= Builder.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, VCap, nil, nil);
  1122.     if hr = VFW_S_NOPREVIEWPIN then begin
  1123.       // preview was faked up for us using the (only) capture pin
  1124.       fPreviewFaked:= true;
  1125.     end
  1126.     else
  1127.       Result:= hr = S_OK;
  1128.   end;
  1129.   if Result then begin
  1130.     if (FDVSize<>dvsDontWorry) and
  1131.        (Graph.FindFilterByName('DV Video Decoder', DVDec) =S_OK) and
  1132.        (DVDec.QueryInterface(IIPDVDec, DVDecProp)=S_OK) then
  1133.     begin
  1134.       DVDecProp.put_IPDisplay(DVSizes[FDVSize]);
  1135.     end;
  1136.   end;
  1137. end;
  1138.  
  1139. function FindPin(pins: IEnumPins; PinDir: TPin_Direction): IPin;
  1140. var
  1141.   pin: IPin;
  1142.   pininfo: TPin_Direction;
  1143.   hr: HRESULT;
  1144.   u: Cardinal;
  1145. begin
  1146.   Result:= nil;
  1147.   if pins=nil then Exit;
  1148.   pins.Reset;
  1149.   repeat
  1150.     hr:= pins.Next(1, pin, u);
  1151.     if (hr = S_OK) then begin
  1152.       hr:= pin.QueryDirection(pininfo);
  1153.       if (hr = S_OK) and (pininfo = PinDir) then begin
  1154.         Result:= pin;
  1155.         break;
  1156.       end;
  1157.     end;
  1158.   until hr<>NOERROR;
  1159. end;
  1160.  
  1161. function TCapture.AdjustVideoGrabber: boolean;
  1162. var
  1163.   FGrabber, FRenderer: IBaseFilter;
  1164.   GrabIn, GrabOut, RenderIn, DecoderOut: IPin;
  1165.   pins: IEnumPins;
  1166.   mt: TAM_Media_Type;
  1167. begin
  1168.   // find video renderer to put grabber just before it
  1169.   // I dont know how to find it another way except by Name(((((((
  1170.   Result:= Graph.FindFilterByName('Video Renderer', FRenderer) = S_OK;
  1171.   if Result then begin
  1172.     Result:= (CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC, IID_IBaseFilter, FGrabber) = NOERROR) and
  1173.              (FGrabber.QueryInterface(IID_ISampleGrabber, Grabber) = S_OK) and
  1174.              (Graph.AddFilter(FGrabber, 'Video Grabber') = S_OK) and
  1175.              (FGrabber.EnumPins(pins)=S_OK);
  1176.     if (Result) then begin
  1177.       GrabIn:= FindPin(pins, PINDIR_INPUT);
  1178.       GrabOut:= FindPin(pins, PINDIR_OUTPUT);
  1179.       pins:= nil;
  1180.       if (FRenderer.EnumPins(pins)=S_OK) then begin
  1181.         RenderIn:= FindPin(pins, PINDIR_INPUT);
  1182.         RenderIn.ConnectedTo(DecoderOut);
  1183.         pins:= nil;
  1184.         Result:= (Graph.RemoveFilter(FRenderer) = S_OK);
  1185.         FillChar(mt, sizeof(mt), 0);
  1186.         mt.majortype:= MEDIATYPE_Video;
  1187.         mt.formattype:= FORMAT_VideoInfo;
  1188.         case FVideoFormat.bmiHeader.biBitCount of
  1189.           1 : mt.subtype := MEDIASUBTYPE_RGB1;
  1190.           4 : mt.subtype := MEDIASUBTYPE_RGB4;
  1191.           8 : mt.subtype := MEDIASUBTYPE_RGB8;
  1192.          16 : mt.subtype := MEDIASUBTYPE_RGB555;
  1193.          32 : mt.subtype := MEDIASUBTYPE_RGB32;
  1194.          else mt.subtype:= MEDIASUBTYPE_RGB24;
  1195.         end;
  1196.         mt.bFixedSizeSamples := True;
  1197.         mt.pbFormat := @FVideoFormat;
  1198.         mt.cbFormat := sizeof( TVIDEOINFOHEADER );
  1199.         Grabber.SetMediaType( mt );
  1200.         Result:= Result and (DecoderOut<>nil) and (GrabIn<>nil) and
  1201.                  (DecoderOut.Connect(GrabIn, nil)=S_OK);
  1202.  
  1203.         if Result then begin
  1204.           Grabber.SetBufferSamples(false);
  1205.           Grabber.SetOneShot(false);
  1206.           VGrabberCB.Owner:= Handle;
  1207.           Grabber.SetCallback( VGrabberCB, 1 );
  1208.           Result:= ( Graph.Render( GrabOut ) = S_OK );
  1209.         end;
  1210.  
  1211.       end;
  1212.     end;
  1213.   end;
  1214. end;
  1215.  
  1216. function TCapture.AdjustAudioGrabber: boolean;
  1217. var
  1218.   FGrabber, FRenderer: IBaseFilter;
  1219.   GrabIn, GrabOut, RenderIn, DecoderOut: IPin;
  1220.   pins: IEnumPins;
  1221.   mt: TAM_Media_Type;
  1222. begin
  1223.   // find audio renderer to put grabber just before it
  1224.   // I dont know how to find it another way except by Name(((((((
  1225.   Result:= Graph.FindFilterByName('Audio Renderer', FRenderer) = S_OK;
  1226.   if Result then begin
  1227.     Result:= (CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC, IID_IBaseFilter, FGrabber) = NOERROR) and
  1228.              (FGrabber.QueryInterface(IID_ISampleGrabber, Grabber) = S_OK) and
  1229.              (Graph.AddFilter(FGrabber, 'Audio Grabber') = S_OK) and
  1230.              (FGrabber.EnumPins(pins)=S_OK);
  1231.     if (Result) then begin
  1232.       GrabIn:= FindPin(pins, PINDIR_INPUT);
  1233.       GrabOut:= FindPin(pins, PINDIR_OUTPUT);
  1234.       pins:= nil;
  1235.       if (FRenderer.EnumPins(pins)=S_OK) then begin
  1236.         RenderIn:= FindPin(pins, PINDIR_INPUT);
  1237.         RenderIn.ConnectedTo(DecoderOut);
  1238.         pins:= nil;
  1239.         Result:= (Graph.RemoveFilter(FRenderer) = S_OK);
  1240.  
  1241.         FillChar(mt, sizeof(mt), 0);
  1242.         mt.majortype:= MEDIATYPE_Audio;
  1243.         mt.formattype:= FORMAT_WaveFormatEx;
  1244.         mt.subtype:= MEDIASUBTYPE_PCM;
  1245.         mt.bFixedSizeSamples := True;
  1246.         mt.pbFormat := @FAudioFormat;
  1247.         mt.cbFormat := sizeof( TVIDEOINFOHEADER );
  1248.         Grabber.SetMediaType(mt);
  1249.  
  1250.         Result:= Result and (DecoderOut<>nil) and (GrabIn<>nil) and
  1251.                  (DecoderOut.Connect(GrabIn, nil)=S_OK);
  1252.  
  1253.         if Result then begin
  1254.           Grabber.SetBufferSamples(false);
  1255.           Grabber.SetOneShot(false);
  1256.           AGrabberCB.Owner:= Handle;
  1257.           Grabber.SetCallback( AGrabberCB, 1 );
  1258.           Result:= ( Graph.Render( GrabOut ) = S_OK );
  1259.         end;
  1260.  
  1261.       end;
  1262.     end;
  1263.   end;
  1264. end;
  1265.  
  1266. function TCapture.FindVideoWindow: boolean;
  1267. begin
  1268.   // Get the preview window to be a child of our app's window
  1269.   // This will find the IVideoWindow interface on the renderer.  It is
  1270.   // important to ask the filtergraph for this interface... do NOT use
  1271.   // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to
  1272.   // know we own the window so it can give us display changed messages, etc.
  1273.   Result:= (Graph.QueryInterface(IID_IVideoWindow, VideoWindow)=S_OK);
  1274.   if Result then begin
  1275.     VideoWindow.put_Owner(Handle);    // We own the window now
  1276.     VideoWindow.put_WindowStyle(WS_CHILD or WS_CLIPSIBLINGS);    // you are now a child
  1277.     // give the preview window all our space
  1278.     ResizeWindow;
  1279.     VideoWindow.put_Visible(true);
  1280.   end;
  1281. end;
  1282.  
  1283. // build the preview graph!
  1284. // !!! PLEASE NOTE !!!  Some new WDM devices have totally separate capture
  1285. // and preview settings.  An application that wishes to preview and then
  1286. // capture may have to set the preview pin format using IAMStreamConfig on the
  1287. // preview pin, and then again on the capture pin to capture with that format.
  1288. // In this sample app, there is a separate page to set the settings on the
  1289. // capture pin and one for the preview pin.  To avoid the user
  1290. // having to enter the same settings in 2 dialog boxes, an app can have its own
  1291. // UI for choosing a format (the possible formats can be enumerated using
  1292. // IAMStreamConfig) and then the app can programmatically call IAMStreamConfig
  1293. // to set the format on both pins.
  1294. function TCapture.BuildPreviewGraph: boolean;
  1295. var
  1296.   hr: HRESULT;
  1297.   pmt: PAM_MEDIA_TYPE;
  1298. begin
  1299.   // we have one already
  1300.   Result:= fPreviewGraphBuilt;
  1301.   if Result then Exit;
  1302.  
  1303.   // No rebuilding while we're running
  1304.   if fCapturing or fPreviewing then Exit;
  1305.  
  1306.   // We don't have the necessary capture filters
  1307.   if VCap = nil then Exit;
  1308.   if (ACap = nil) and fCapAudio then Exit;
  1309.  
  1310.   // we already have another graph built... tear down the old one
  1311.   if fCaptureGraphBuilt then TearDownGraph;
  1312.  
  1313.   if RenderPreviewPin then
  1314.     AdjustVideoGrabber
  1315.   else
  1316.     ErrMsg(rsGraphCantPreview);
  1317.  
  1318.   if fCapAudio then fCapAudio := Builder.RenderStream( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Audio, ACap, nil, nil ) = S_OK;
  1319.   if fCapAudio then AdjustAudioGrabber;
  1320.  
  1321.   // Render the closed captioning pin? It could be a CC or a VBI category pin,
  1322.   // depending on the capture driver
  1323.   if fCapCC then begin
  1324.     hr:= Builder.RenderStream(@PIN_CATEGORY_CC, nil, VCap, nil, nil);
  1325.     if hr <> NOERROR then begin
  1326.       hr:= Builder.RenderStream(@PIN_CATEGORY_VBI, nil, VCap, nil, nil);
  1327.       if hr <> NOERROR then ErrMsg(rsCantRenderCC);
  1328.     end;
  1329.   end;
  1330.  
  1331.   if not FindVideoWindow then ErrMsg(rsGraphCantBePreviewedProperly);
  1332.  
  1333.   // now tell it what frame rate to capture at.  Just find the format it
  1334.   // is capturing with, and leave everything alone but change the frame rate
  1335.   // No big deal if it fails.  It's just for preview
  1336.   // !!! Should we then talk to the preview pin?
  1337.   if VStreamConf <> nil then begin
  1338.     hr:= VStreamConf.GetFormat(pmt);
  1339.     // DV capture does not use a VIDEOINFOHEADER
  1340.     if hr = NOERROR then begin
  1341.       DeleteMediaType(pmt);
  1342.     end;
  1343.   end;
  1344.  
  1345.   // make sure we process events while we're previewing!
  1346.   if (Graph.QueryInterface(IID_IMediaEventEx, MediaEvent) = NOERROR) then
  1347.     MediaEvent.SetNotifyWindow(Handle, WM_FGNOTIFY, 0);
  1348.  
  1349.   // All done.
  1350.   fPreviewGraphBuilt:= true;
  1351.   Result:= true;
  1352. end;
  1353.  
  1354. // build the capture graph!
  1355. function TCapture.BuildCaptureGraph: boolean;
  1356. var
  1357.    hr: HRESULT;
  1358.    pmt: PAM_MEDIA_TYPE;
  1359. begin
  1360.   // we have one already
  1361.   Result:= fCaptureGraphBuilt;
  1362.   if Result then Exit;
  1363.  
  1364.   // No rebuilding while we're running
  1365.   Result:= false;
  1366.   if (fCapturing or fPreviewing) then Exit;
  1367.  
  1368.   // We don't have the necessary capture filters
  1369.   if (VCap = nil) then Exit;
  1370.   if (ACap = nil) and (fCapAudio) then Exit;
  1371.  
  1372.   // no capture file name yet... we need one first
  1373.   if UseTempFile then
  1374.     FCaptureFile:= TempCaptureFileName
  1375.   else
  1376.     FCaptureFile:= CaptureFileName;
  1377.  
  1378.   if (FCaptureFile = '') then begin
  1379.     ErrMsg(rsEmptyFileName);
  1380.     Result:= false;
  1381.     Exit;
  1382.   end;
  1383.  
  1384.   if not AllocCaptureFile(PreallocFileSize) then begin
  1385.     ErrMsg(rsFailedToAllocFileSize);
  1386.     Result:= false;
  1387.     Exit;
  1388.   end;
  1389.  
  1390.   // we already have another graph built... tear down the old one
  1391.   try
  1392.     if (fPreviewGraphBuilt) then TearDownGraph();
  1393.  
  1394. // We need a rendering section that will write the capture file out in AVI file format
  1395.     hr:= Builder.SetOutputFileName(MEDIASUBTYPE_Avi, PWCHAR(FCaptureFile), Render, Sink);
  1396.     if (hr <> NOERROR) then ErrMsgException(rsCantSetCaptureFile);
  1397.  
  1398. // Now tell the AVIMUX to write out AVI files that old apps can read properly.
  1399. // If we don't, most apps won't be able to tell where the keyframes are, slowing down editing considerably
  1400. // Doing this will cause one seek (over the area the index will go) when
  1401. // you capture past 1 Gig, but that's no big deal.
  1402. // NOTE: This is on by default, so it's not necessary to turn it on
  1403.     hr:= Render.QueryInterface(IID_IConfigAviMux, ConfigAviMux);
  1404.     if (hr = NOERROR) and (ConfigAviMux<>nil) then begin
  1405.       ConfigAviMux.SetOutputCompatibilityIndex(true);
  1406.       if (fCapAudio) then // Also, set the proper MASTER STREAM
  1407.         MasterStream:= FMasterStream;
  1408.     end;
  1409.  
  1410. // Render the video capture and preview pins - even if the capture filter only
  1411. // has a capture pin (and no preview pin) this should work... because the
  1412. // capture graph builder will use a smart tee filter to provide both capture
  1413. // and preview.  We don't have to worry.  It will just work.
  1414.  
  1415. // NOTE that we try to render the interleaved pin before the video pin, because
  1416. // if BOTH exist, it's a DV filter and the only way to get the audio is to use
  1417. // the interleaved pin.  Using the Video pin on a DV filter is only useful if
  1418. // you don't want the audio.
  1419.     hr:= Builder.RenderStream( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved,
  1420.                    VCap, nil, Render);
  1421.     if (hr <> NOERROR) then begin
  1422.         hr:= Builder.RenderStream( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video,
  1423.                    VCap, nil, Render);
  1424.         if (hr <> NOERROR) then ErrMsgException(rsCantRenderVCaptureStream);
  1425.     end;
  1426.  
  1427.     // Rendering preview pin
  1428.     if (fWantPreview) and not RenderPreviewPin then
  1429.       ErrMsgException(rsCantRenderPreviewStream);
  1430.  
  1431.     if (fCapAudio) then begin // Render the audio capture pin?
  1432.     hr:= Builder.RenderStream( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Audio,
  1433.                                    ACap, nil, Render);
  1434.     if (hr <> NOERROR) then
  1435.           ErrMsgException(rsCantRenderACaptureStream);
  1436.     end;
  1437.  
  1438.     // Render the closed captioning pin? It could be a CC or a VBI category pin,
  1439.     // depending on the capture driver
  1440.     if (fCapCC) then begin
  1441.       hr:= Builder.RenderStream(@PIN_CATEGORY_CC, nil, VCap, nil, Render);
  1442.       if (hr <> NOERROR) then begin
  1443.         hr:= Builder.RenderStream(@PIN_CATEGORY_VBI, nil, VCap, nil, Render);
  1444.         if (hr <> NOERROR) then ErrMsg(rsCantRenderCC);
  1445.       end;
  1446.       // To preview and capture VBI at the same time, we can call this twice
  1447.       if (fWantPreview) then
  1448.         Builder.RenderStream(@PIN_CATEGORY_VBI, nil, VCap, nil, nil);
  1449.     end;
  1450.  
  1451.     // NOTE: We do this even if we didn't ask for a preview, because rendering
  1452.     // the capture pin may have rendered the preview pin too (WDM overlay
  1453.     // devices) because they must have a preview going.  So we better always
  1454.     // put the preview window in our app, or we may get a top level window
  1455.     // appearing out of nowhere!
  1456.     if not FindVideoWindow and (fWantPreview) then ErrMsg(rsThisGraphCantPreview);
  1457.  
  1458.     // now tell it what frame rate to capture at.  Just find the format it
  1459.     // is capturing with, and leave everything alone but change the frame rate
  1460.     if fUseFrameRate then
  1461.       hr:= E_FAIL
  1462.     else
  1463.       hr:= NOERROR;
  1464.  
  1465.     if VStreamConf <> nil then begin
  1466.         hr:= VStreamConf.GetFormat(pmt);
  1467.         // DV capture does not use a VIDEOINFOHEADER
  1468.       if hr = NOERROR then begin
  1469.         if CheckGuid(pmt^.formattype, FORMAT_VideoInfo) then begin
  1470.           p_mtVideo := pmt;
  1471.           if Assigned( FOnVideoFormatChange ) then begin
  1472.             FOnVideoFormatChange( Self );
  1473.             pmt^.pbFormat := @FVideoFormat;
  1474.             hr:= VStreamConf.SetFormat( pmt^ );
  1475.             if hr <> NOERROR then ErrMsg(rsCantSetPreviewFrameRate, hr);
  1476.           end;
  1477.           p_mtVideo := nil;
  1478.           // resize our window to the default capture size
  1479.           FVideoWidth := PVIDEOINFOHEADER(pmt^.pbFormat)^.bmiHeader.biWidth;
  1480.           FVideoHeight := ABS(PVIDEOINFOHEADER(pmt^.pbFormat)^.bmiHeader.biHeight);
  1481.           ResizeWindow;
  1482.         end;
  1483.         DeleteMediaType(pmt);
  1484.       end;
  1485.     end;
  1486.     if (hr <> NOERROR) then ErrMsg(rsCantSetCaptureFrameRate);
  1487.  
  1488.     // now ask the filtergraph to tell us when something is completed or aborted
  1489.     // (EC_COMPLETE, EC_USERABORT, EC_ERRORABORT).  This is how we will find out
  1490.     // if the disk gets full while capturing
  1491.     if (Graph.QueryInterface(IID_IMediaEventEx, MediaEvent) = NOERROR) then
  1492.       MediaEvent.SetNotifyWindow(Handle, WM_FGNOTIFY, 0);
  1493.  
  1494. // All done.
  1495.     fCaptureGraphBuilt:= true;
  1496.     Result:= true;
  1497.   except
  1498.     TearDownGraph;
  1499.     Result:= false;
  1500.   end;
  1501.  
  1502. end;
  1503.  
  1504. procedure TCapture.GetDeviceOptions;
  1505. var
  1506.     Spec : ISpecifyPropertyPages;
  1507.     StreamConf : IAMStreamConfig;
  1508.     auuid : CAUUID;
  1509.     hr : HRESULT;
  1510.     TVTuner : IAMTVTuner;
  1511.     TVAudio : IAMTVAudio;
  1512.   bVFormat : Boolean;
  1513. begin
  1514.  
  1515.   FDeviceOptions.Clear;
  1516.   bVFormat := False;
  1517.  
  1518.     if CaptureDialogs <> nil then begin
  1519.     // If this device supports the old legacy UI dialogs, offer them
  1520.         if CaptureDialogs.HasDialog( VfwCaptureDialog_Format ) = S_OK then begin
  1521.       FDeviceOptions.Add( DEVOPT_VFORMAT, 'Video Format', True );
  1522.       bVFormat := True;
  1523.         end;
  1524.  
  1525.         if CaptureDialogs.HasDialog( VfwCaptureDialog_Source ) = S_OK then begin
  1526.       FDeviceOptions.Add( DEVOPT_VSOURCE, 'Video Source', True );
  1527.         end;
  1528.  
  1529.         if CaptureDialogs.HasDialog( VfwCaptureDialog_Display ) = S_OK then begin
  1530.       FDeviceOptions.Add( DEVOPT_VDISPLAY, 'Video Display', True );
  1531.         end;
  1532.     end;
  1533.  
  1534.     // Also check the audio capture filter at this point, since even non wdm devices
  1535.     // may support an IAMAudioInputMixer property page (we'll also get any wdm filter
  1536.     // properties here as well). We'll get any audio capture pin property pages just
  1537.     // a bit later.
  1538.     if ACap <> nil then begin
  1539.         hr := ACap.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1540.         if hr = S_OK then begin
  1541.             hr := Spec.GetPages( auuid );
  1542.             if ( hr = S_OK ) and ( auuid.cElems > 0 ) then begin
  1543.         FDeviceOptions.Add( DEVOPT_ACAPTURE, 'Audio Capture Filter', False );
  1544.                 CoTaskMemFree( auuid.pElems );
  1545.             end;
  1546.             Spec := nil;
  1547.         end;
  1548.     end;
  1549.  
  1550.     // don't bother looking for new property pages if the old ones are supported
  1551.     // or if we don't have a capture filter
  1552.     if ( VCap = nil ) or ( bVFormat ) then Exit;
  1553.  
  1554.     // New WDM devices support new UI and new interfaces.
  1555.     // Your app can use some default property
  1556.     // pages for UI if you'd like (like we do here) or if you don't like our
  1557.     // dialog boxes, feel free to make your own and programmatically set
  1558.     // the capture options through interfaces like IAMCrossbar, IAMCameraControl
  1559.     // etc.
  1560.  
  1561.     // There are 9 objects that might support property pages.  Let's go through
  1562.     // them.
  1563.  
  1564.     // 1. the video capture filter itself
  1565.  
  1566.     hr := VCap.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1567.     if hr = S_OK then begin
  1568.         hr := Spec.GetPages( auuid );
  1569.         if ( hr = S_OK ) and ( auuid.cElems > 0 ) then begin
  1570.       FDeviceOptions.Add( DEVOPT_VCAPTURE, 'Video Capture Filter', True );
  1571.             CoTaskMemFree( auuid.pElems );
  1572.         end;
  1573.         Spec := nil;
  1574.     end;
  1575.  
  1576.     // 2.  The video capture capture pin
  1577.  
  1578.     hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved,    VCap, IID_IAMStreamConfig, StreamConf );
  1579.     if hr <> S_OK then hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video, VCap, IID_IAMStreamConfig, StreamConf );
  1580.     if hr = S_OK then begin
  1581.         hr := StreamConf.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1582.         if hr = S_OK then begin
  1583.             hr := Spec.GetPages( auuid );
  1584.             if ( hr = S_OK ) and ( auuid.cElems > 0 ) then begin
  1585.         FDeviceOptions.Add( DEVOPT_VCAPTURE_PIN, 'Video Capture Pin', True );
  1586.                 CoTaskMemFree( auuid.pElems );
  1587.             end;
  1588.             Spec := nil;
  1589.         end;
  1590.         StreamConf := nil;
  1591.     end;
  1592.  
  1593.     // 3.  The video capture preview pin.
  1594.  
  1595.     // This basically sets the format being previewed.  Typically, you
  1596.     // want to capture and preview using the SAME format, instead of having to
  1597.     // enter the same value in 2 dialog boxes.  For a discussion on this, see
  1598.     // the comment above the MakePreviewGraph function.
  1599.  
  1600.     hr := Builder.FindInterface( @PIN_CATEGORY_PREVIEW, @MEDIATYPE_Interleaved, VCap, IID_IAMStreamConfig, StreamConf );
  1601.     if hr <> NOERROR then hr := Builder.FindInterface( @PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, VCap,    IID_IAMStreamConfig, StreamConf );
  1602.     if hr = S_OK then begin
  1603.         hr := StreamConf.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1604.         if hr = S_OK then begin
  1605.             hr := Spec.GetPages( auuid );
  1606.             if ( hr = S_OK ) and ( auuid.cElems > 0 ) then begin
  1607.         FDeviceOptions.Add( DEVOPT_VPREVIEW_PIN, 'Video Preview Pin', True );
  1608.                 CoTaskMemFree( auuid.pElems );
  1609.             end;
  1610.             Spec := nil;
  1611.         end;
  1612.         StreamConf := nil;
  1613.     end;
  1614.  
  1615.     // 6.  The TVTuner
  1616.  
  1617.     hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved, VCap, IID_IAMTVTuner, TVTuner );
  1618.     if hr <> S_OK then hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video, VCap, IID_IAMTVTuner, TVTuner );
  1619.     if hr = S_OK then begin
  1620.         hr := TVTuner.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1621.         if hr = S_OK then begin
  1622.             hr := Spec.GetPages( auuid );
  1623.             if ( hr = S_OK ) and ( auuid.cElems > 0 ) then begin
  1624.         FDeviceOptions.Add( DEVOPT_TVTUNER, 'TV Tuner', True );
  1625.                 CoTaskMemFree( auuid.pElems );
  1626.             end;
  1627.             Spec := nil;
  1628.         end;
  1629.          TVTuner := nil;
  1630.     end;
  1631.  
  1632.     // no audio capture, we're done
  1633.     if ACap = nil then Exit;
  1634.  
  1635.     // 7.  The Audio capture filter itself... Thanks anyway, but we got these already
  1636.  
  1637.     // 8.  The Audio capture pin
  1638.  
  1639.     hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Audio, ACap, IID_IAMStreamConfig, StreamConf );
  1640.     if hr = S_OK then begin
  1641.         hr := StreamConf.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1642.         if hr = S_OK then begin
  1643.             hr := Spec.GetPages( auuid );
  1644.             if ( hr = S_OK ) and ( auuid.cElems > 0 ) then begin
  1645.         FDeviceOptions.Add( DEVOPT_ACAPTURE_PIN, 'Audio Capture Pin', False );
  1646.                 CoTaskMemFree( auuid.pElems );
  1647.             end;
  1648.             Spec := nil;
  1649.         end;
  1650.         StreamConf := nil;
  1651.     end;
  1652.  
  1653.     // 9.  The TV Audio filter
  1654.  
  1655.     hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Audio, ACap, IID_IAMTVAudio, TVAudio );
  1656.     if hr = S_OK then begin
  1657.         hr := TVAudio.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1658.         if hr = S_OK then begin
  1659.             hr := Spec.GetPages( auuid );
  1660.             if ( hr = S_OK ) and ( auuid.cElems > 0 ) then begin
  1661.         FDeviceOptions.Add( DEVOPT_TVAUDIO, 'TV Audio', False );
  1662.                 CoTaskMemFree( auuid.pElems );
  1663.             end;
  1664.             Spec := nil;
  1665.         end;
  1666.         TVAudio := nil;
  1667.     end;
  1668. end;
  1669.  
  1670. function TCapture.OptionDialog( iType : Integer ) : HRESULT;
  1671. var
  1672.     Spec : ISpecifyPropertyPages;
  1673.     auuid : CAUUID;
  1674.     hr : HRESULT;
  1675.     hrD : Cardinal;
  1676.     pmt : PAM_MEDIA_TYPE;
  1677.     StreamConf : IAMStreamConfig;
  1678.     TVTuner : IAMTVTuner;
  1679. begin
  1680.  
  1681.   // they want the VfW format dialog
  1682.  
  1683.     if iType = DEVOPT_VFORMAT then begin
  1684.         // this dialog will not work while previewing
  1685.     if fWantPreview then StopPreview;
  1686.         hrD := CaptureDialogs.ShowDialog( VfwCaptureDialog_Format, Handle );
  1687.         // Oh uh!  Sometimes bringing up the FORMAT dialog can result
  1688.         // in changing to a capture format that the current graph
  1689.         // can't handle.  It looks like that has happened and we'll
  1690.         // have to rebuild the graph.
  1691.         if hrD = VFW_E_CANNOT_CONNECT then begin
  1692.             //DbgLog((LOG_TRACE,1,TEXT("DIALOG CORRUPTED GRAPH!")));
  1693.             TearDownGraph;    // now we need to rebuild
  1694.             // !!! This won't work if we've left a stranded h/w codec
  1695.         end;
  1696.  
  1697.         // Resize our window to be the same size that we're capturing
  1698.         if VStreamConf <> nil then begin
  1699.             // get format being used NOW
  1700.             hr := VStreamConf.GetFormat( pmt );
  1701.             // DV capture does not use a VIDEOINFOHEADER
  1702.             if hr = NOERROR then begin
  1703.                 if CheckGuid( pmt^.formattype, FORMAT_VideoInfo ) then begin
  1704.                     // resize our window to the new capture size
  1705.           //p_mtVideo := pmt;
  1706.                     //pVideoFormat := pmt^.pbFormat;
  1707.                     //FProcessMessage( CAPM_VIDEOINFOHEADER, Integer( pVideoFormat ) );
  1708.                     //ResizeWindow( pVideoFormat^.bmiHeader.biWidth, Abs( pVideoFormat^.bmiHeader.biHeight ) );
  1709.                 end;
  1710.                 DeleteMediaType( pmt );
  1711.             end;
  1712.         end;
  1713.  
  1714.         if fWantPreview then begin
  1715.             BuildPreviewGraph;
  1716.             StartPreview;
  1717.         end;
  1718.     end
  1719.   else if iType = DEVOPT_VSOURCE then begin
  1720.         // this dialog will not work while previewing
  1721.         if fWantPreview then StopPreview;
  1722.         CaptureDialogs.ShowDialog( VfwCaptureDialog_Source, Handle );
  1723.         if fWantPreview then StartPreview;
  1724.     end
  1725.   else if iType = DEVOPT_VDISPLAY then begin
  1726.         // this dialog will not work while previewing
  1727.         if fWantPreview then StopPreview;
  1728.         CaptureDialogs.ShowDialog( VfwCaptureDialog_Display, Handle );
  1729.         if fWantPreview then StartPreview;
  1730.  
  1731.   // now the code for the new dialogs
  1732.  
  1733.     end
  1734.   else if iType = DEVOPT_VCAPTURE then begin
  1735.         hr := VCap.QueryInterface( IID_ISpecifyPropertyPages,    Spec );
  1736.         if hr = S_OK then begin
  1737.             Spec.GetPages( auuid );
  1738.             OleCreatePropertyFrame( Handle, 30, 30, nil, 1, @VCap, auuid.cElems, auuid.pElems, 0, 0, nil );
  1739.             CoTaskMemFree( auuid.pElems );
  1740.             Spec := nil;
  1741.         end;
  1742.     end
  1743.   else if iType = DEVOPT_VCAPTURE_PIN then begin
  1744.         // You can change this pin's output format in these dialogs.
  1745.         // If the capture pin is already connected to somebody who's
  1746.         // fussy about the connection type, that may prevent using
  1747.         // this dialog(!) because the filter it's connected to might not
  1748.         // allow reconnecting to a new format. (EG: you switch from RGB
  1749.         // to some compressed type, and need to pull in a decoder)
  1750.         // I need to tear down the graph downstream of the
  1751.         // capture filter before bringing up these dialogs.
  1752.         // In any case, the graph must be STOPPED when calling them.
  1753.         if fWantPreview then StopPreview;  // make sure graph is stopped
  1754.         // The capture pin that we are trying to set the format on is connected if
  1755.         // one of these variable is set to TRUE. The pin should be disconnected for
  1756.         // the dialog to work properly.
  1757.         if fPreviewGraphBuilt then begin
  1758.             //DbgLog((LOG_TRACE,1,TEXT("Tear down graph for dialog")));
  1759.             TearDownGraph;    // graph could prevent dialog working
  1760.         end;
  1761.         hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved, VCap, IID_IAMStreamConfig, StreamConf );
  1762.         if hr <> NOERROR then Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video, VCap, IID_IAMStreamConfig, StreamConf );
  1763.         hr := StreamConf.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1764.         if hr = S_OK then begin
  1765.             Spec.GetPages( auuid );
  1766.             OleCreatePropertyFrame( Handle, 30, 30, nil, 1, @StreamConf, auuid.cElems, auuid.pElems, 0, 0, nil );
  1767.  
  1768.             // !!! What if changing output formats couldn't reconnect
  1769.             // and the graph is broken?  Shouldn't be possible...
  1770.  
  1771.             if VStreamConf <> nil then begin
  1772.                 // get format being used NOW
  1773.                 hr := VStreamConf.GetFormat( pmt );
  1774.                 // DV capture does not use a VIDEOINFOHEADER
  1775.                 if hr = NOERROR then begin
  1776.                     if CheckGuid( pmt^.formattype, FORMAT_VideoInfo ) then begin
  1777.                         // resize our window to the new capture size
  1778.                         //pVideoFormat := pmt^.pbFormat;
  1779.                         //FProcessMessage( CAPM_VIDEOINFOHEADER, Integer( pVideoFormat ) );
  1780.                         //ResizeWindow( pVideoFormat^.bmiHeader.biWidth, Abs( pVideoFormat^.bmiHeader.biHeight ) );
  1781.                     end;
  1782.                     DeleteMediaType( pmt );
  1783.                 end;
  1784.             end;
  1785.  
  1786.             CoTaskMemFree( auuid.pElems );
  1787.             Spec := nil;
  1788.         end;
  1789.         StreamConf := nil;
  1790.         if fWantPreview then begin
  1791.             BuildPreviewGraph;
  1792.             StartPreview;
  1793.         end;
  1794.     end
  1795.   else if iType = DEVOPT_VPREVIEW_PIN then begin
  1796.         // this dialog may not work if the preview pin is connected
  1797.         // already, because the downstream filter may reject a format
  1798.         // change, so we better kill the graph. (EG: We switch from
  1799.         // capturing RGB to some compressed fmt, and need to pull in
  1800.         // a decompressor)
  1801.         if fWantPreview then begin
  1802.             StopPreview;
  1803.             TearDownGraph;
  1804.         end;
  1805.         // This dialog changes the preview format, so it might affect
  1806.         // the format being drawn.  Our app's window size is taken
  1807.         // from the size of the capture pin's video, not the preview
  1808.         // pin, so changing that here won't have any effect. All in all,
  1809.         // this probably won't be a terribly useful dialog in this app.
  1810.         hr := Builder.FindInterface( @PIN_CATEGORY_PREVIEW, @MEDIATYPE_Interleaved, VCap, IID_IAMStreamConfig, StreamConf );
  1811.         if hr <> NOERROR then  Builder.FindInterface( @PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, VCap, IID_IAMStreamConfig, StreamConf );
  1812.         hr := StreamConf.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1813.         if hr = S_OK then begin
  1814.             Spec.GetPages( auuid );
  1815.             OleCreatePropertyFrame( Handle, 30, 30, nil, 1, @StreamConf, auuid.cElems, auuid.pElems, 0, 0, nil );
  1816.             CoTaskMemFree( auuid.pElems );
  1817.             Spec := nil;
  1818.         end;
  1819.         StreamConf := nil;
  1820.         if fWantPreview then begin
  1821.             BuildPreviewGraph;
  1822.             StartPreview;
  1823.         end;
  1824.     end
  1825.   else if iType = DEVOPT_TVTUNER then begin
  1826.         hr := Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved, VCap, IID_IAMTVTuner, TVTuner );
  1827.         if hr <> NOERROR then Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video, VCap, IID_IAMTVTuner, TVTuner );
  1828.         hr := TVTuner.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1829.         if hr = S_OK then begin
  1830.             Spec.GetPages( auuid );
  1831.             OleCreatePropertyFrame( Handle, 30, 30, nil, 1, @TVTuner, auuid.cElems, auuid.pElems, 0, 0, nil );
  1832.             CoTaskMemFree( auuid.pElems );
  1833.             Spec := nil;
  1834.         end;
  1835.         TVTuner := nil;
  1836.     end
  1837.   else if iType = DEVOPT_ACAPTURE then begin
  1838.         hr := ACap.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1839.         if hr = S_OK then begin
  1840.             Spec.GetPages( auuid );
  1841.             OleCreatePropertyFrame( Handle, 30, 30, nil, 1, @ACap, auuid.cElems, auuid.pElems, 0, 0, nil );
  1842.             CoTaskMemFree( auuid.pElems );
  1843.             Spec := nil;
  1844.         end;
  1845.     end
  1846.   else if iType = DEVOPT_ACAPTURE_PIN then begin
  1847.         // this dialog will not work while previewing - it might change
  1848.         // the output format!
  1849.         if fWantPreview then StopPreview;
  1850.         Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Audio, ACap, IID_IAMStreamConfig, StreamConf );
  1851.         hr := StreamConf.QueryInterface( IID_ISpecifyPropertyPages, Spec );
  1852.         if hr = S_OK then begin
  1853.             Spec.GetPages( auuid );
  1854.             OleCreatePropertyFrame( Handle, 30, 30, nil, 1, @StreamConf, auuid.cElems, auuid.pElems, 0, 0, nil );
  1855.             CoTaskMemFree( auuid.pElems );
  1856.             Spec := nil;
  1857.         end;
  1858.         StreamConf := nil;
  1859.         if fWantPreview then StartPreview;
  1860.     end;
  1861.   Result := S_OK;
  1862. end;
  1863.  
  1864. procedure TCapture.SetMasterStream(const Value: integer);
  1865. begin
  1866.   if (ConfigAviMux<>nil) and (ConfigAviMux.SetMasterStream(Value) = NOERROR) then
  1867.     FMasterStream:= Value
  1868.   else
  1869.     ErrMsg(rsSetMasterStreamFailed);
  1870. end;
  1871.  
  1872. function TCapture.StartPreview: boolean;
  1873. var
  1874.   MC: IMediaControl;
  1875.   hr: HRESULT;
  1876. begin
  1877.   // way ahead of you
  1878.   Result:= fPreviewing or not fPreviewGraphBuilt;
  1879.   if Result then Exit;
  1880.   // enable grabber classes
  1881.   VGrabberCB.Enabled := True;
  1882.   AGrabberCB.Enabled := True;
  1883.   // run the graph
  1884.   hr:= Graph.QueryInterface(IID_IMediaControl, MC);
  1885.   if Succeeded(hr) then begin
  1886.     hr:= MC.Run;
  1887.     if Failed(hr) then MC.Stop;  // stop parts that ran
  1888.     MC:= nil;
  1889.   end;
  1890.   if Failed(hr) then begin
  1891.     ErrMsg(rsCantRunPreviewGraph, hr);
  1892.     Exit;
  1893.   end;
  1894.   if Assigned(FOnStartPreview) then FOnStartPreview(Self);
  1895.   fPreviewing:= true;
  1896.   Result:= true;
  1897. end;
  1898.  
  1899. function TCapture.StopPreview: boolean;
  1900. var
  1901.   MC: IMediaControl;
  1902.   hr: HRESULT;
  1903. begin
  1904.   Result:= false;
  1905.   // way ahead of you
  1906.   if not fPreviewing then Exit;
  1907.   // disable grabber classes
  1908.   VGrabberCB.Enabled := False;
  1909.   AGrabberCB.Enabled := False;
  1910.   // stop the graph
  1911.   MC:= nil;
  1912.   if Graph <> nil then begin
  1913.     hr:= Graph.QueryInterface(IID_IMediaControl, MC);
  1914.     if Succeeded(hr) then begin
  1915.       hr:= MC.Stop;
  1916.       MC:= nil;
  1917.     end;
  1918.     if Failed(hr) then begin
  1919.       ErrMsg(rsCantStopPreviewGraph, hr);
  1920.       Exit;
  1921.     end;
  1922.   end;
  1923.   Invalidate; // !!! get rid of garbage
  1924.   if Assigned(FOnStopPreview) then FOnStopPreview(Self);
  1925.   fPreviewing:= false;
  1926.   Result:= true;
  1927. end;
  1928.  
  1929. // Tear down everything downstream of the capture filters, so we can build
  1930. // a different capture graph.  Notice that we never destroy the capture filters
  1931. // and WDM filters upstream of them, because then all the capture settings
  1932. // we've set would be lost.
  1933. procedure TCapture.TearDownGraph;
  1934. begin
  1935.   Sink:= nil;
  1936.   ConfigAviMux:= nil;
  1937.   Render:= nil;
  1938.   if VideoWindow <> nil then begin
  1939.     // stop drawing in our window, or we may get wierd repaint effects
  1940.     VideoWindow.put_Visible(false);
  1941.     VideoWindow.put_Owner(0);
  1942.   end;
  1943.   VideoWindow:= nil;
  1944.   MediaEvent:= nil;
  1945.   DroppedFrames:= nil;
  1946.  
  1947.   // destroy the graph downstream of our capture filters
  1948.   if VCap <> nil then NukeDownstream(VCap);
  1949.   if ACap <> nil then NukeDownstream(ACap);
  1950.  
  1951.   fCaptureGraphBuilt:= false;
  1952.   fPreviewGraphBuilt:= false;
  1953.   fPreviewFaked:= false;
  1954. end;
  1955.  
  1956. // capture AVI
  1957. function TCapture.StartCapture(const Dialog: boolean): boolean;
  1958. const
  1959.   MAX_TIME = $7FFFFFFFFFFFFFFF;
  1960. var
  1961.   fHasStreamControl: boolean;
  1962.   hr: HRESULT;
  1963.   start, stop: TREFERENCE_TIME;
  1964.   MC: IMediaControl;
  1965. begin
  1966.   // way ahead of you
  1967.   Result:= fCapturing;
  1968.   if Result then Exit;
  1969.   FCaptureTimer.Enabled:= false;
  1970.  
  1971.   if (fPreviewing) then StopPreview();     // or we'll get confused
  1972.   if (fPreviewGraphBuilt) then TearDownGraph();
  1973.  
  1974.   // or we'll crash
  1975.   Result:= false;
  1976.   if not BuildCaptureGraph and fCaptureGraphBuilt then Exit;
  1977.  
  1978.   // This amount will be subtracted from the number of dropped and not
  1979.   // dropped frames reported by the filter.  Since we might be having the
  1980.   // filter running while the pin is turned off, we don't want any of the
  1981.   // frame statistics from the time the pin is off interfering with the
  1982.   // statistics we gather while the pin is on
  1983.   FDroppedFrames:= 0; FNotDropped:= 0;
  1984.  
  1985.   start:= MAX_TIME; stop:= MAX_TIME;
  1986.  
  1987.   // don't capture quite yet...
  1988.   hr:= Builder.ControlStream(@PIN_CATEGORY_CAPTURE, nil, nil, @start, nil, 0, 0);
  1989.   // Do we have the ability to control capture and preview separately?
  1990.   fHasStreamControl:= SUCCEEDED(hr);
  1991.  
  1992.   // prepare to run the graph
  1993.   MC:= nil;
  1994.   hr:= Graph.QueryInterface(IID_IMediaControl, MC);
  1995.   if (FAILED(hr)) then begin
  1996.     ErrMsg(rsCantGetMediaControl, hr);
  1997.     Result:= false;
  1998.     Exit;
  1999.   end;
  2000.  
  2001.   try
  2002.     try
  2003.       // If we were able to keep capture off, then we can
  2004.       // run the graph now for frame accurate start later yet still showing a
  2005.       // preview.   Otherwise, we can't run the graph yet without capture
  2006.       // starting too, so we'll pause it so the latency between when they
  2007.       // press a key and when capture begins is still small (but they won't have
  2008.       // a preview while they wait to press a key)
  2009.       if (fHasStreamControl) then hr:= MC.Run()
  2010.                              else hr:= MC.Pause();
  2011.  
  2012.       if (FAILED(hr)) then // stop parts that started
  2013.         ErrMsgException(rsCantRunGraph, hr);
  2014.  
  2015.       // press a key to start capture
  2016.       if ( Dialog and ( MessageDlg(rsStartCapture, mtConfirmation, [mbYes, mbCancel], 0) <> mrYes)) then begin
  2017.         // kill all fucking stuff :)
  2018.         ChooseDevices( mVideo, mAudio );
  2019.         Result := False;
  2020.         Exit;
  2021.       end;
  2022.  
  2023.       // Start capture NOW!
  2024.       if (fHasStreamControl) then begin
  2025.           // we may not have this yet
  2026.           if (DroppedFrames=nil) then begin
  2027.             hr:= Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Interleaved,
  2028.                                         VCap, IID_IAMDroppedFrames, DroppedFrames);
  2029.             if (hr <> NOERROR) then
  2030.               Builder.FindInterface( @PIN_CATEGORY_CAPTURE, @MEDIATYPE_Video,
  2031.                                      VCap, IID_IAMDroppedFrames, DroppedFrames);
  2032.           end;
  2033.           // turn the capture pin on now!
  2034.           Builder.ControlStream(@PIN_CATEGORY_CAPTURE, nil, nil, nil, @stop, 0, 0);
  2035.           // make note of the current dropped frame counts
  2036.           if (DroppedFrames<>nil) then begin
  2037.             DroppedFrames.GetNumDropped(FDroppedBase);
  2038.             DroppedFrames.GetNumNotDropped(FNotDroppedBase);
  2039.           end;
  2040.       end
  2041.       else begin
  2042.         hr:= MC.Run();
  2043.         if (FAILED(hr)) then // stop parts that started
  2044.           ErrMsgException(rsCantRunGraph, hr);
  2045.       end;
  2046.  
  2047.   // when did we start capture?
  2048.       FCapStartTime:= timeGetTime();
  2049.       FCaptureTimer.Enabled:= true;
  2050.       fCapturing:= true;
  2051.       Result:= true;
  2052.       if Assigned(FOnStartCapture) then FOnStartCapture(Self);
  2053.     except
  2054.       MC.Stop();
  2055.       Result:= false;
  2056.     end
  2057.   finally
  2058.     MC:= nil;
  2059.   end;
  2060.  
  2061. end;
  2062.  
  2063. function TCapture.StopCapture: boolean;
  2064. var
  2065.   MC: IMediaControl;
  2066.   hr: HRESULT;
  2067. begin
  2068.   // way ahead of you
  2069.   Result:= false;
  2070.   if not fCaptureGraphBuilt then Exit;
  2071.  
  2072.   FCaptureTimer.Enabled:= false;
  2073.  
  2074.   // stop the graph
  2075.   MC:= nil;
  2076.   hr:= Graph.QueryInterface(IID_IMediaControl, MC);
  2077.   if (SUCCEEDED(hr)) then begin
  2078.     hr:= MC.Stop();
  2079.     MC:= nil;
  2080.   end;
  2081.   if (FAILED(hr)) then begin
  2082.     ErrMsg(rsCantStopGraph, hr);
  2083.     Exit;
  2084.   end;
  2085.  
  2086.   if not fCapturing then Exit;
  2087.  
  2088.   // when the graph was stopped
  2089.   FCapStopTime:= timeGetTime();
  2090.   FCapturing:= false;
  2091.  
  2092.   if UseTempFile and SaveCaptureFile(CaptureFileName) then begin
  2093.     DeleteFile(TempCaptureFileName);
  2094.   end;
  2095.  
  2096.   // one last time for the final count and all the stats
  2097.   try
  2098.     if Assigned(FOnCaptureProgress) then FOnCaptureProgress(Self);
  2099.   except
  2100.   end;
  2101.   // !!! get rid of garbage
  2102.   Invalidate;
  2103.   if Assigned(FOnStopCapture) then FOnStopCapture(Self);
  2104.   Result:= true;
  2105. end;
  2106.  
  2107. procedure TCapture.CaptureProgress(Sender: TObject);
  2108. begin
  2109.   FCapTime:= timeGetTime() - FCapStartTime;
  2110.   UpdateStatus;
  2111.   if not Capturing then Exit;
  2112.   if (UseTimeLimit) and (FCapTime div 1000 >= DWORD(TimeLimit)) then begin
  2113.     StopCapture();
  2114.     if (FWantPreview) then begin
  2115.       BuildPreviewGraph();
  2116.       StartPreview();
  2117.     end;
  2118.   end
  2119.   else if Assigned(FOnCaptureProgress) then FOnCaptureProgress(Self);
  2120. end;
  2121.  
  2122. procedure TCapture.UpdateStatus;
  2123. begin
  2124.   // this filter can't tell us dropped frame info.
  2125.   if (DroppedFrames<>nil) and FCapturing then begin
  2126.     if (DroppedFrames.GetNumDropped(FDroppedFrames)=S_OK) and
  2127.        (DroppedFrames.GetNumNotDropped(FNotDropped)=S_OK) then
  2128.     begin
  2129.       FDroppedFrames:= FDroppedFrames - FDroppedBase;
  2130.       FNotDropped:= FNotDropped - FNotDroppedBase;
  2131.     end;
  2132.  
  2133. {
  2134.     // we want all possible stats, including capture time and actual acheived
  2135.     // frame rate and data rate (as opposed to what we tried to get).  These
  2136.     // numbers are an indication that though we dropped frames just now, if we
  2137.     // chose a data rate and frame rate equal to the numbers I'm about to
  2138.     // print, we probably wouldn't drop any frames.
  2139.     // average size of frame captured
  2140.     if (DroppedFrames.GetAverageFrameSize(&lAvgFrameSize) = S_OK) then begin
  2141.     end;
  2142. }
  2143.   end;
  2144. end;
  2145.  
  2146. procedure TCapture.SetTempCaptureFileName(const Value: string);
  2147. begin
  2148.   FTempCaptureFileName:= Value;
  2149.   if (Sink<>nil) then begin
  2150.     Sink.SetFileName(PWCHAR(WideString(FTempCaptureFileName)), TAM_Media_Type(nil^));
  2151.   end;
  2152. end;
  2153.  
  2154. function TCapture.AllocCaptureFile(const SizeMb: integer): boolean;
  2155. begin
  2156.   DeleteFile(FCaptureFile);
  2157.   Result:= MakeBuilder and (Builder.AllocCapFile(PWCHAR(FCaptureFile),
  2158.                             SizeMb*1024*1024) = NOERROR);
  2159. end;
  2160.  
  2161. function TCapture.SaveCaptureFile(const FileName: string): boolean;
  2162. var
  2163.   tempBuilder: ICaptureGraphBuilder2;
  2164. begin
  2165.   Result:= FCaptureFile <> '';
  2166.   if Result then
  2167.     try
  2168.       // we need our own graph builder because the main one might not exist
  2169.       Result:= (CoCreateInstance( CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC,
  2170.                                   IID_ICaptureGraphBuilder2,  tempBuilder ) = NOERROR);
  2171.       if (Result) then begin
  2172.         // allow the user to press ESC to abort... don't ask for progress
  2173.         Result:= tempBuilder.CopyCaptureFile( PWCHAR(FCaptureFile),
  2174.                                               PWCHAR(WideString(FileName)),
  2175.                                               -1, nil) = S_OK;
  2176.         tempBuilder:= nil;
  2177.       end;
  2178.     except
  2179.       Result:= false;
  2180.     end;
  2181. end;
  2182.  
  2183. procedure TCapture.SetCaptureFileName(const Value: string);
  2184. begin
  2185.   FCaptureFileName:= Value;
  2186. end;
  2187.  
  2188. procedure TCapture.SetAudioFormat( SamplesPerSec : Cardinal; Channels, BitsPerSec : Word );
  2189. var
  2190.     c1 : Integer;
  2191. begin
  2192.     ZeroMemory( @FAudioFormat, sizeof( TWaveFormatEx ) );
  2193.     FAudioFormat.wFormatTag := WAVE_FORMAT_PCM;
  2194.     if Channels in [1..2] then FAudioFormat.nChannels := Channels else FAudioFormat.nChannels := 1;
  2195.     FAudioFormat.nSamplesPerSec := SAMPLE_RATE[0];
  2196.     for c1 := 0 to 3 do if SamplesPerSec = SAMPLE_RATE[c1] then FAudioFormat.nSamplesPerSec := SAMPLE_RATE[c1];
  2197.     if ( BitsPerSec = 8 ) or ( BitsPerSec = 16 ) then    FAudioFormat.wBitsPerSample := BitsPerSec else FAudioFormat.wBitsPerSample := 8;
  2198.     FAudioFormat.nBlockAlign := FAudioFormat.nChannels * FAudioFormat.wBitsPerSample div 8;
  2199.     FAudioFormat.nAvgBytesPerSec := FAudioFormat.nSamplesPerSec * FAudioFormat.nBlockAlign;
  2200.   FAudioBitRate := FAudioFormat.wBitsPerSample;
  2201.   FAudioSampleRate := FAudioFormat.nSamplesPerSec;
  2202.   FAudioChannels := FAudioFormat.nChannels;
  2203.   if p_mtAudio = nil then Exit;
  2204.     p_mtAudio^.majortype := MEDIATYPE_Audio;
  2205.     p_mtAudio^.subtype := MEDIASUBTYPE_PCM;
  2206.     p_mtAudio^.formattype := FORMAT_WaveFormatEx;
  2207.     p_mtAudio^.pbFormat := @FAudioFormat;
  2208. end;
  2209.  
  2210. procedure TCapture.SetVideoFormat( Width, Height : Integer; BitCount : Word; FrameRate : Double );
  2211. begin
  2212.     if Width > 0 then FVideoFormat.bmiHeader.biWidth := Width;
  2213.     if Height > 0 then FVideoFormat.bmiHeader.biHeight := Height;
  2214.   FVideoWidth := FVideoFormat.bmiHeader.biWidth;
  2215.   FVideoHeight := FVideoFormat.bmiHeader.biHeight;
  2216.   case BitCount of
  2217.     1  : FVideoFormat.bmiHeader.biBitCount := 1;
  2218.     4  : FVideoFormat.bmiHeader.biBitCount := 4;
  2219.     8  : FVideoFormat.bmiHeader.biBitCount := 8;
  2220.     16 : FVideoFormat.bmiHeader.biBitCount := 16;
  2221.     32 : FVideoFormat.bmiHeader.biBitCount := 32;
  2222.     else FVideoFormat.bmiHeader.biBitCount := 24;
  2223.   end;
  2224.     FVideoFormat.bmiHeader.biSize := Width * Height * ( FVideoFormat.bmiHeader.biBitCount div 8 );
  2225.   FVideoFormat.bmiHeader.biPlanes := 1;
  2226.   FVideoFormat.bmiHeader.biCompression := BI_RGB;
  2227.   if FrameRate > 0 then FVideoFrameRate := FrameRate else FVideoFrameRate := 15;
  2228.   FVideoFormat.AvgTimePerFrame := Round( 1 / FrameRate * 10000000 );
  2229.   if p_mtVideo = nil then Exit;
  2230.   p_mtVideo^.pbFormat := @FVideoFormat;
  2231.   p_mtVideo^.cbFormat := sizeof( TVIDEOINFOHEADER );
  2232.   p_mtVideo^.majortype:= MEDIATYPE_Video;
  2233.   p_mtVideo^.formattype:= FORMAT_VideoInfo;
  2234.   //p_mtVideo^.lSampleSize := FVideoFormat.bmiHeader.biSize * 2;
  2235.   p_mtVideo^.bFixedSizeSamples := True;
  2236. end;
  2237.  
  2238. // this function is not optimized. but readable :)
  2239. function TCapture.CreateBitmap( Buffer : Pointer; Size : Integer ) : TBitmap;
  2240. var
  2241.   iByteCount : Integer;
  2242.   SrcScanLine, DstScanLine : PByteArray;
  2243.   SrcX, SrcY, DstY : Integer;
  2244. begin
  2245.   Result := nil;
  2246.  
  2247.   // oh no, go out
  2248.   if ( Buffer = nil ) or ( Size <= 0 ) then Exit;
  2249.  
  2250.   // read the bytecount
  2251.   iByteCount := Size div ( FVideoWidth * FVideoHeight );
  2252.  
  2253.   // wrong frame size??
  2254.   if not iByteCount in [1..4] then Exit;
  2255.  
  2256.   Result := TBitmap.Create;
  2257.  
  2258.   // set bitmap dimensions
  2259.   Result.Width := FVideoWidth;
  2260.   Result.Height := FVideoHeight;
  2261.  
  2262.   // set right pixelformat
  2263.   case iByteCount of
  2264.     1 : Result.PixelFormat := pf8Bit;
  2265.     2 : Result.PixelFormat := pf16Bit;
  2266.     3 : Result.PixelFormat := pf24Bit;
  2267.     4 : Result.PixelFormat := pf32Bit;
  2268.   end;
  2269.  
  2270.   // copy the bytes
  2271.   SrcY := 0;
  2272.   DstY := FVideoHeight - 1; // flip destination vertical
  2273.   while SrcY < FVideoHeight do begin
  2274.     DstScanLine := Result.ScanLine[DstY];
  2275.     SrcScanLine := Pointer( Integer( Buffer ) + ( SrcY * FVideoWidth * iByteCount ) );
  2276.     SrcX := 0;
  2277.     while SrcX < FVideoWidth * iByteCount do begin
  2278.       DstScanLine[SrcX] := SrcScanLine[SrcX];
  2279.       Inc( SrcX );
  2280.     end;
  2281.     Inc( SrcY );
  2282.     Dec( DstY );
  2283.   end;
  2284. end;
  2285.  
  2286. procedure TCapture.OnVideoFrame( Frame : Cardinal; Buffer : Pointer; Size : Integer );
  2287. var
  2288.   Bitmap : TBitmap;
  2289.   imgJPEG : TJpegImage;
  2290. begin
  2291.   if FStillImage then begin
  2292.     Bitmap := CreateBitmap( Buffer, Size );
  2293.     if not Bitmap.Empty then begin
  2294.       if FImageType = 0 then begin
  2295.         if FImageFile = '' then FImageFile := 'capture.bmp';
  2296.         Bitmap.SaveToFile( FImageFile );
  2297.       end
  2298.       else if FImageType = 1 then begin
  2299.         if FImageFile = '' then FImageFile := 'capture.jpg';
  2300.         if ( FImageQuality < 0 ) or ( FImageQuality > 100 ) then FImageQuality := 100;
  2301.         imgJPEG := TJpegImage.Create;
  2302.         imgJPEG.Assign( Bitmap );
  2303.         imgJPEG.CompressionQuality := FImageQuality;
  2304.         imgJPEG.SaveToFile( FImageFile );
  2305.         imgJPEG.Free;
  2306.       end;
  2307.     end;
  2308.     Bitmap.Free;
  2309.     // kill us!
  2310.     VGrabberCB.FProcessBuffer := FProcessBuffer;
  2311.     FStillImage := False;
  2312.   end;
  2313.   // call another handler
  2314.   if Assigned( FProcessBuffer ) then FProcessBuffer( Frame, Buffer, Size );
  2315. end;
  2316.  
  2317. procedure TCapture.SaveAsBitmap;
  2318. begin
  2319.   if not VGrabberCB.Enabled then Exit;
  2320.   FStillImage := True;
  2321.   FImageType := 0;
  2322.   // save the current handler
  2323.   FProcessBuffer := VGrabberCB.FProcessBuffer;
  2324.   // set our handler
  2325.   VGrabberCB.FProcessBuffer := OnVideoFrame;
  2326. end;
  2327.  
  2328. procedure TCapture.SaveAsJpeg( Quality : TJPEGQualityRange );
  2329. begin
  2330.   if not VGrabberCB.Enabled then Exit;
  2331.   FStillImage := True;
  2332.   FImageType := 1;
  2333.   if ( Quality >= 0 ) and ( Quality <= 100 ) then FImageQuality := Quality;
  2334.   // save the current handler
  2335.   FProcessBuffer := VGrabberCB.FProcessBuffer;
  2336.   // set our handler
  2337.   VGrabberCB.FProcessBuffer := OnVideoFrame;
  2338. end;
  2339.  
  2340. //-----------------------------------------------------------------------
  2341.  
  2342. procedure Register;
  2343. begin
  2344.   RegisterComponents( 'Samples', [TCapture] );
  2345. end;
  2346.  
  2347. initialization
  2348.  
  2349.   VideoDevicesList := TObjectList.Create;
  2350.   AudioDevicesList := TObjectList.Create;
  2351.  
  2352. finalization
  2353.  
  2354.   VideoDevicesList.Free;
  2355.   AudioDevicesList.Free;
  2356.  
  2357. end.
  2358.