{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvAVICapture.PAS, released 2003-07-05.

The Initial Developer of the Original Code is Olivier Sannier <obones att altern dott org>
Portions created by Olivier Sannier are Copyright (C) 2003 Olivier Sannier.
All Rights Reserved.

Contributor(s): none to date

Current Version: 0.4

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org

Description: This unit defines a component that you can drop on any form or
             frame and that will display the video stream captured by a video
             device installed under Windows. You can perform live previews,
             record movies (and save them to avi files) or even capture
             single frames. A direct access is provided to the frames so that
             you can process them if you want. This is an encapsulation of the
             AVICap API from Win32.

Known Issues: none known
-----------------------------------------------------------------------------}
// $Id$

unit JvAVICapture;

{$I jvcl.inc}
{$I windowsonly.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  Windows, Messages, VFW, MMSystem, SysUtils, Classes, Graphics, Controls,
  JvTypes;

type
  TJvScrollPos = class(TPersistent)
  protected
    FLeft: Integer;
    FTop: Integer;
  published
    property Left: Integer read FLeft write FLeft;
    property Top: Integer read FTop write FTop;
  end;

  // The video format used by the video device
  TJvVideoFormat = class(TPersistent)
  protected
    FHWnd: HWND;                // the AVICap window using this format
    FWidth: Cardinal;           // width of the image
    FHeight: Cardinal;          // height of the image
    FBitDepth: Cardinal;        // bits per pixel (8-16-24-32...)
    FPixelFormat: TPixelFormat; // pixel format (RGB, BGR, YUV...)
    FCompression: Integer;      // compression used
  public
    procedure Update;   // Update from the AVICap window
    function Apply: Boolean; // apply the format to the window, returns True if successfull

    property Width: Cardinal read FWidth write FWidth;
    property Height: Cardinal read FHeight write FHeight;
    property BitDepth: Cardinal read FBitDepth write FBitDepth;
    property PixelFormat: TPixelFormat read FPixelFormat;
    property Compression: Integer read FCompression write FCompression;
  end;

  // The audio format used by the device
  TJvAudioFormat = class(TPersistent)
  protected
    FHWnd: HWND;               // the AVICap window using this format
    FFormatTag: Cardinal;      // the format tag (PCM or others...)
    FChannels: Cardinal;       // number of channels (usually 1 or 2)
    FSamplesPerSec: Cardinal;  // number of samples per second in the stream
    FAvgBytesPerSec: Cardinal; // the average number of bytes per second
    FBlockAlign: Cardinal;     // size of the block to align on
    FBitsPerSample: Cardinal;  // number of bits per sample
    FExtraSize: Cardinal;      // size of the extra data
    FExtra: Pointer;           // extra data for formats other than PCM
  public
    // updates from the AVICap window
    procedure Update;
    // apply the format to the window, returns True if successfull
    function Apply: Boolean;
    // fill in a PWaveFormatEx structure to use with API calls
    procedure FillWaveFormatEx(var wfex: PWaveFormatEx);
    // run-time only property, see FSize
    property ExtraSize: Cardinal read FExtraSize write FExtraSize;
    // run-time only property, see FExtra
    property Extra: Pointer read FExtra write FExtra;
  published
    // see the relevant fields for details on the following properties
    property FormatTag: Cardinal read FFormatTag write FFormatTag;
    property Channels: Cardinal read FChannels write FChannels;
    property SamplesPerSec: Cardinal read FSamplesPerSec write FSamplesPerSec;
    property AvgBytesPerSec: Cardinal read FAvgBytesPerSec write FAvgBytesPerSec;
    property BlockAlign: Cardinal read FBlockAlign write FBlockAlign;
    property BitsPerSample: Cardinal read FBitsPerSample write FBitsPerSample;
  end;

  // a percentage
  TJvPercent = 0..100;

  // the number of audio buffers to use (maximum 10)
  TJvNumAudioBuffer = 0..10;

  // the type of a virtual key
  TJvVirtualKey = type Integer;

  // the capture settings to use to save a video stream to an AVI file
  TJvCaptureSettings = class(TPersistent)
  protected
    // the AVICap window that will use these settings and from which
    // we will get the values when we update them
    FHWnd: HWND;
    // if True, the API will popup a confirmation window when starting the
    // capture session allowing the user to choose to continue or not.
    FConfirmCapture: Boolean;
    // the delay in microsecond between two frames. This is a requested
    // value, it may not be fully respected by the driver when capturing
    FFrameDelay: Cardinal;
    // the percentage of frames dropped above which the capture will end
    // in an error state (too many drops having occurred)
    FPercentDropForError: TJvPercent;
    // if True the capture session will be launched in a separate background
    // thread, not disabling the caller. Reentrance issues must then be
    // considered to avoid the user to launch twice the capture, for instance
    FYield: Boolean;
    // the requested number of video buffers. The actual number of allocated
    // buffers may well be smaller because of hardware limitations
    FNumVideoBuffer: Cardinal;
    // the requested number of audio buffers. The actual number of allocated
    // buffers may well be smaller because of hardware limitations
    FNumAudioBuffer: TJvNumAudioBuffer;
    // if True, the audio stream will also be captured
    FCaptureAudio: Boolean;
    // if True, a left mouse click will stop the capture session
    FAbortLeftMouse: Boolean;
    // if True, a right mouse click will stop the capture session
    FAbortRightMouse: Boolean;
    // if different from 0, a press on that virtual key will stop the
    // capture session
    FKeyAbort: TJvVirtualKey;
    // if True, the FTimeLimit parameter will be considered
    FLimitEnabled: Boolean;
    // the time limit for the capture session (in seconds). Will only be
    // considered if FLimitEnabled is True
    FTimeLimit: Cardinal;
    // if True, the capture will occur at twice the size specified in the
    // other parameters of this class.
    FStepCapture2x: Boolean;
    // the number of frames to sample and make the average of when using
    // a step capture
    FStepCaptureAverageFrames: Cardinal;
    // the size of an audio buffer
    FAudioBufferSize: Cardinal;
    // if True, the audio stream is the master one with respect to time
    // alignment. if False, the video stream is the master (recommanded)
    FAudioMaster: Boolean;
    // if True, the capture will controll a MCI device as its source
    FMCIControl: Boolean;
    // if True, the step capture is enabled on the MCI device
    // this is only considered if FMCIControl is True
    FMCIStep: Boolean;
    // time of the MCI device to start capture at
    // this is only considered if FMCIControl is True
    FMCIStartTime: Cardinal;
    // time of the MCI device to stop capture at
    // this is only considered if FMCIControl is True
    FMCIStopTime: Cardinal;
    // sets the FKeyAbort field
    procedure SetKeyAbort(nKeyAbort: TJvVirtualKey);
    // get and set the FPS property
    function GetFPS: Double;
    procedure SetFPS(const Value: Double);
    // set the FrameDelay property, ensuring the value is always
    // greater than 0
    procedure SetFrameDelay(const Value: Cardinal);
  public
    // creates and initializes the class
    constructor Create;
    // updates the class fields from the AVICap window
    procedure Update;
    // applies the class fields to the AVICap window, returns True if successful
    function Apply: Boolean;
  published
    // (rom) default values would be a good idea
    // please refer to the relevant field declarations for detail on the following properties
    property ConfirmCapture: Boolean read FConfirmCapture write FConfirmCapture;
    property FrameDelay: Cardinal read FFrameDelay write SetFrameDelay;
    property FPS: Double read GetFPS write SetFPS;
    property PercentDropForError: TJvPercent read FPercentDropForError write FPercentDropForError;
    property Yield: Boolean read FYield write FYield;
    property NumVideoBuffer: Cardinal read FNumVideoBuffer write FNumVideoBuffer;
    property NumAudioBuffer: TJvNumAudioBuffer read FNumAudioBuffer write FNumAudioBuffer;
    property CaptureAudio: Boolean read FCaptureAudio write FCaptureAudio;
    property AbortLeftMouse: Boolean read FAbortLeftMouse write FAbortLeftMouse;
    property AbortRightMouse: Boolean read FAbortRightMouse write FAbortRightMouse;
    property KeyAbort: TJvVirtualKey read FKeyAbort write SetKeyAbort;
    property LimitEnabled: Boolean read FLimitEnabled write FLimitEnabled;
    property TimeLimit: Cardinal read FTimeLimit write FTimeLimit;
    property StepCapture2x: Boolean read FStepCapture2x write FStepCapture2x;
    property StepCaptureAverageFrames: Cardinal read FStepCaptureAverageFrames write FStepCaptureAverageFrames;
    property AudioBufferSize: Cardinal read FAudioBufferSize write FAudioBufferSize;
    property AudioMaster: Boolean read FAudioMaster write FAudioMaster;
    property MCIControl: Boolean read FMCIControl write FMCIControl;
    property MCIStep: Boolean read FMCIStep write FMCIStep;
    property MCIStartTime: Cardinal read FMCIStartTime write FMCIStartTime;
    property MCIStopTime: Cardinal read FMCIStopTime write FMCIStopTime;
  end;

  // the type for the number of colors a palette can have
  TJvPaletteNbColors = 0..256;

  TJvPalette = class(TPersistent)
  protected
    FHWnd: HWND; // the AVICap window that will use these settings
  public
    // save the palette associated with the driver into the given file
    // and returns True upon success.
    function Save(const FileName: string): Boolean;
    // loads the palette from the given file and returns True upon success
    // FHWnd must not be null
    function Load(const FileName: string): Boolean;
    // paste the palette from the clipboard
    function PasteFromClipboard: Boolean;
    // automatically create the best palette from the first nbFrames frames with
    // a maximum of nbColors colors
    function AutoCreate(nbFrames: Integer; nbColors: TJvPaletteNbColors): Boolean;
    // Use this call from a frame callback and set the Flag to True to indicate that
    // the current frame must be considered when creating the palette. Continue
    // calling this method with Flag set to True as long as you need it.
    // Then call it again with Flag set to False, to finalize the palette and pass
    // it to the capture driver that will now use it.
    function ManuallyCreate(Flag: Boolean; nbColors: TJvPaletteNbColors): Boolean;
  end;

  // the driver index (-1 if not connected, 0-9 if connected as there are at most 10 drivers
  // according to Microsoft documentation. But there can be more than 1 source per driver...
  TJvDriverIndex = -1..9;

  // The exception triggered when an invalid index driver index is given
  EInvalidDriverIndexError = class(EJVCLException)
  public
    constructor Create(Index: TJvDriverIndex; MaxIndex: TJvDriverIndex);
  end;

  // what a driver can do on the system
  TJvDriverCaps = set of
   (dcOverlay,            // overlay rendering
    dcDlgVideoSource,     // display a dialog to choose video source
    dcDlgVideoFormat,     // display a dialog to choose video format
    dcDlgVideoDisplay,    // display a dialog to choose video display
    dcCaptureInitialized, // is the capture initialized
    dcSuppliesPalettes);  // if the driver supplies palettes

  TJvUsedEvents = set of
   (ueCapControl,  // the OnCapControl event will be triggered
    ueError,       // the OnError event will be triggered
    ueFrame,       // the OnFrame event will be triggered
    ueStatus,      // the OnStatus event will be triggered
    ueVideoStream, // the OnVideoStream event will be triggered
    ueWaveStream,  // the OnWaveStream event will be triggered
    ueYield);      // the OnYield event will be triggered

  // the video dialog to display
  TJvVideoDialog =
   (vdSource,       // the source dialog (only if dcDlgVideoSource is in the caps)
    vdFormat,       // the format dialog (only if dcDlgVideoFormat is in the caps)
    vdDisplay,      // the display dialog (only if dcDlgVideoDisplay is in the caps)
    vdCompression); // the compression dialog (with all the installed video codecs)

  // local type for the events
  PJvVideoHdr = PVIDEOHDR;
  PJvWaveHdr = PWaveHdr;

  // forward declaration for the events
  TJvAVICapture = class;

  // the event triggered in case of an error
  // Sender is the TJvAVICapture component triggering the event
  // nErr is the error number
  // Str is the string associated with that error
  TOnError = procedure(Sender: TJvAVICapture; nErr: Integer; Str: string) of object;

  // the event triggered in case of a status change (use it to follow progress)
  // Sender is the TJvAVICapture component triggering the event
  // nId is the id of the status change (see win32 API for more details)
  // Str is the string associated with that status change
  TOnStatus = procedure(Sender: TJvAVICapture; nId: Integer; Str: string) of object;

  // the event triggerred when the driver is yielding. a good place to put a
  // call to Application.ProcessMessages
  // Sender is the TJvAVICapture component triggering the event
  TOnYield = procedure(Sender: TJvAVICapture) of object;

  // the event trigerred when a frame is ready to be written to disk during streaming capture
  // Sender is the TJvAVICapture component triggering the event
  // videoHdr is the video header describing the stream
  TOnVideoStream = procedure(Sender: TJvAVICapture; videoHdr: PJvVideoHdr) of object;

  // the event trigerred when a frame is ready, in a non streaming capture session
  TOnFrame = TOnVideoStream;

  // the event trigerred when an audio buffer is ready to be written do disk during streaming capture
  // Sender is the TJvAVICapture component triggering the event
  // audioHdr is the audio header describing the stream
  TOnWaveStream = procedure(Sender: TJvAVICapture; waveHdr: PJvWaveHdr) of object;

  // the event triggered when you want to use precise capture control
  // Sender is the TJvAVICapture component triggering the event
  // state is the state in which the capture is (refer to API for details)
  // Result is to be set to True if capture must continue, False if it must stop
  TOnCapControl = procedure(Sender: TJvAVICapture; nState: Integer; var Result: Boolean) of object;

  // the main component. Just drop it on a form or a frame, set the driver property, set previewing to
  // True and you should see the video coming through (even in design mode !)
  {$IFDEF RTL230_UP}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64)]
  {$ENDIF RTL230_UP}
  TJvAVICapture = class(TWinControl)
  protected
    FCaptureSettings: TJvCaptureSettings; // the capture settings
    FCapturing: Boolean;                  // True if capture is happening
    FConnected: Boolean;                  // True if connected to a driver
    FDrivers: TStringList;                // the available drivers as a TStringList
    FDriverCaps: TJvDriverCaps;           // the current driver capabilities
    FHWnd: HWND;                          // the handle to the AviCap window
    FNoFile: Boolean;                     // True if not capturing to a file
    FOverlaying: Boolean;                 // True if using overlay display
    FPreviewFrameDelay: Cardinal;         // the time between two preview frames (ms)
    FPreviewing: Boolean;                 // True if previewing
    FSingleFrameCapturing: Boolean;       // True if capturing using single frame capture
    FVideoLeft: Integer;                  // the left coordinate of the displayed video
    FVideoTop: Integer;                   // the top coordinate of the displayed video
    // the user supplied event handlers
    // see respective types for details
    FOnError: TOnError;
    FOnStatus: TOnStatus;
    FOnYield: TOnYield;
    FOnFrame: TOnFrame;
    FOnVideoStream: TOnVideoStream;
    FOnWaveStream: TOnWaveStream;
    FOnCapControl: TOnCapControl;

    FFileName: string;            // the filename for the capture file
    FFileSizeAlloc: Cardinal;     // the size to allocate for the capture file
    FUsedEvents: TJvUsedEvents;   // which events are used
    FCaptureStatus: TCAPSTATUS;   // the state of the current capture
    FVideoFormat: TJvVideoFormat; // the current video format used (or to be used)
    FAudioFormat: TJvAudioFormat; // the current audio format used (or to be used)
    FScrollPos: TJvScrollPos;     // the scrolling position in the window
    FPalette: TJvPalette;         // the palette in use
    FDriverIndex: TJvDriverIndex; // the driver index (-1 if not connected)

    // window creation stuff, where the AviCap window is created:
    // what is done is that the component inherits from TWinControl and as such
    // has its own handle. We then create the AviCap window and set it as a child
    // of the TWinControl. This allows to take advantage of all the VCL handling
    // for design time, parent, ownership... and we can focus on using the
    // AviCap window to do the capture
    procedure CreateWindowHandle(const Params: TCreateParams); override;
    // destroys the AviCap window just before letting the VCL destroy the handle
    // for the TWinControl
    procedure DestroyWindowHandle; override;
    // We enforce the size of the window to be equal to the
    // video frame in this method as it is the place where it
    // should be done, rather than doing it in SetBounds
    function CanAutoSize(var NewWidth, NewHeight: Integer): Boolean; override;
    // gets the title of the AviCap window
    function GetTitle: TCaption;
    // sets the title of the AviCap window
    procedure SetTitle(const nTitle: TCaption);
    // sets the preview frame delay (the time between two frames)
    procedure SetPreviewFrameDelay(nPreviewFrameDelay: Cardinal);
    // sets and gets the preview frame rate in frames per second
    procedure SetPreviewFPS(nPreviewFPS: Double);
    function GetPreviewFPS: Double;
    // sets the previewing property and starts or stop previewing accordingly
    procedure SetPreviewing(nPreviewing: Boolean);
    // sets and gets the filename for capture
    procedure SetFileName(nFileName: TFileName);
    function GetFileName: TFileName;
    // delivers FDrivers as TStrings for property
    function GetDrivers: TStrings;
    // sets the file size to allocate before capture. This might speed up capture as
    // the file won't need to be grown
    procedure SetFileSizeAlloc(nFileSizeAlloc: Cardinal);
    // sets the used events and updates the related values in the AviCap window
    procedure SetUsedEvents(nUsedEvents: TJvUsedEvents);
    // sets the overlaying rendering. May do nothing if driver cannot do overlay rendering
    procedure SetOverlaying(nOverlaying: Boolean);
    // returns the name of the driver or an empty string if FConnected is False
    function GetDriverName: string;
    // returns the version of the driver or an empty string if FConnected is False
    function GetDriverVersion: string;
    // set the scrolling position in the AviCap window. Useful if the frame is larger than
    // the actual size of the control
    procedure SetScrollPos(nScrollPos: TJvScrollPos);
    // sets and gets the MCI device used with this AviCap component (may well be empty)
    procedure SetMCIDevice(nMCIDevice: string);
    function GetMCIDevice: string;
    // sets the driver index to the given value and tries to connect. If connection
    // is not possible, will not change the current value
    procedure SetDriverIndex(nIndex: TJvDriverIndex);
    // tries to starts or stops capture according to the value
    // immediately check the value of FCapturing to see if capture
    // started succesfuly
    procedure SetCapturing(nCapturing: Boolean);
    // tries starts or stops single frame capture according to the value
    // immediately check the value of FSingleFrameCapturing to see
    // if capture started succesfuly
    procedure SetSingleFrameCapturing(const Value: Boolean);
    // sets the FNoFile flag
    procedure SetNoFile(nNoFile: Boolean);
    // sets the FVideoLeft and FVideoTop values and also
    // makes the required capCall
    procedure SetVideoLeft(const Value: Integer);
    procedure SetVideoTop(const Value: Integer);
    // updates the content of the FDriverCaps field
    procedure UpdateCaps;
    // updates the content of the FCaptureStatus field
    procedure UpdateCaptureStatus;
    // stops and start using callbacks. This is required as it appears that the
    // callbacks are still called after a capture session has been stopped.
    procedure StopCallbacks;
    procedure RestartCallbacks;
    // Functions to be called from the callbacks that will trigger the user events
    procedure DoError(ErrId: Integer; Str: string);
    procedure DoStatus(nId: Integer; Str: string);
    procedure DoYield;
    procedure DoFrame(videoHdr: PVIDEOHDR);
    procedure DoVideoStream(videoHdr: PVIDEOHDR);
    procedure DoWaveStream(waveHdr: PWaveHdr);
    procedure DoCapControl(nState: Integer; var AResult: Boolean);
  public
    // creates the component and initializes the different fields
    constructor Create(AOwner: TComponent); override;
    // destroys the component
    destructor Destroy; override;
    // sets the size of the component
    procedure SetBounds(nLeft, nTop, nWidth, nHeight: Integer); override;
    // enumarate the drivers and populates the FDrivers list
    procedure EnumDrivers;
    // tries to connect to the given driver. Returns True if successful, False otherwise
    function Connect(Driver: TJvDriverIndex): Boolean;
    // tries to disconnect from a driver. Returns True if successful, False otherwise
    function Disconnect: Boolean;
    // shows the given dialog and returns True if user pressed ok. If the driver
    // cannot show the given dialog...
    function ShowDialog(Dialog: TJvVideoDialog): Boolean;
    // starts and stop previewing, returning True upon success
    function StartPreview: Boolean;
    function StopPreview: Boolean;
    // start capturing to a file using streaming capture
    function StartCapture: Boolean;
    // start capturing without using a file. You should use the OnVideoStream event in that
    // case to process the frames yourself. This might be useful in a videoconferencing
    // software, where you transfer the frames directly
    function StartCaptureNoFile: Boolean;
    // stops the capture properly
    function StopCapture: Boolean;
    // aborts the capture, leaving the file unusable
    function AbortCapture: Boolean;
    // starts frame by frame capture (non streaming)
    function StartSingleFrameCapture: Boolean;
    // captures one frame in a frame by frame capture session
    function CaptureFrame: Boolean;
    // stops frame by frame capture
    function StopSingleFrameCapture: Boolean;
    // starts and stop overlay rendering, returns True if successful
    function StartOverlay: Boolean;
    function StopOverlay: Boolean;
    // applies the capture settings, returns True if successful
    function ApplyCaptureSettings: Boolean;
    // applies the audio format settings, returns True if successful
    function ApplyAudioFormat: Boolean;
    // saves the stream under the given filename
    function SaveAs(Name: string): Boolean;
    // sets information chunks in the output file
    function SetInfoChunk(const Chunk: TCAPINFOCHUNK): Boolean;
    // saves the latest captured frame to a DIB file
    function SaveDIB(Name: string): Boolean;
    // copies the latest frame to the clipboard
    function CopyToClipboard: Boolean;
    // grabs one frame, not using any capture session
    // if stop is True, previewing and overlaying are stopped
    // if stop is False, previewing and overlaying are left untouched
    function GrabFrame(Stop: Boolean): Boolean;
    // public properties (run-time only), refer to fields and methods descriptions
    // for details on the usage
    property CaptureStatus: TCAPSTATUS read FCaptureStatus;
    property Capturing: Boolean read FCapturing write SetCapturing;
    property Connected: Boolean read FConnected;
    property DriverCaps: TJvDriverCaps read FDriverCaps;
    property DriverName: string read GetDriverName;
    property DriverVersion: string read GetDriverVersion;
    property Drivers: TStrings read GetDrivers;
    property Handle: HWND read FHWnd;
    property Palette: TJvPalette read FPalette;
    property SingleFrameCapturing: Boolean read FSingleFrameCapturing write SetSingleFrameCapturing;
    property VideoFormat: TJvVideoFormat read FVideoFormat;
  published
    // published properties, refer to the field and methods descriptions for details
    property AudioFormat: TJvAudioFormat read FAudioFormat;
    property CaptureSettings: TJvCaptureSettings read FCaptureSettings;
    property DriverIndex: TJvDriverIndex read FDriverIndex write SetDriverIndex default -1;
    property FileName: TFileName read GetFileName write SetFileName;
    property FileSizeAlloc: Cardinal read FFileSizeAlloc write SetFileSizeAlloc default 0;
    property MCIDevice: string read GetMCIDevice write SetMCIDevice;
    property NoFile: Boolean read FNoFile write SetNoFile default False;
    property Overlaying: Boolean read FOverlaying write SetOverlaying default False;
    property PreviewFrameDelay: Cardinal read FPreviewFrameDelay write SetPreviewFrameDelay default 50;
    property PreviewFPS: Double read GetPreviewFPS write SetPreviewFPS;
    property Previewing: Boolean read FPreviewing write SetPreviewing default False;
    property ScrollPos: TJvScrollPos read FScrollPos write SetScrollPos;
    property Title: TCaption read GetTitle write SetTitle;
    property UsedEvents: TJvUsedEvents read FUsedEvents write SetUsedEvents default [];
    property VideoLeft: Integer read FVideoLeft write SetVideoLeft default 0;
    property VideoTop: Integer read FVideoTop write SetVideoTop default 0;
    // inherited properties getting published
    property AutoSize;
    property ParentShowHint;
    property ShowHint;
    property Visible;
    // the events, refer to the fields decriptions for details
    property OnError: TOnError read FOnError write FOnError;
    property OnStatus: TOnStatus read FOnStatus write FOnStatus;
    property OnYield: TOnYield read FOnYield write FOnYield;
    property OnFrame: TOnFrame read FOnFrame write FOnFrame;
    property OnVideoStream: TOnVideoStream read FOnVideoStream write FOnVideoStream;
    property OnWaveStream: TOnWaveStream read FOnWaveStream write FOnWaveStream;
    property OnCapControl: TOnCapControl read FOnCapControl write FOnCapControl;
  end;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL$';
    Revision: '$Revision$';
    Date: '$Date$';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  Math, // for Min and Max
  {$IFNDEF COMPILER12_UP}
  JvJCLUtils, // SetWindowLongPtr
  {$ENDIF ~COMPILER12_UP}
  JvResources;

const
  // minimal height and width of the display window
  cMinHeight = 20;
  cMinWidth = 20;

{ Global functions }

// an helper function that tells if the window is connected to a driver

function capDriverConnected(hWnd: HWND): Boolean;
var
  TmpName: array [0..MAX_PATH] of Char;
begin
  Result := capDriverGetName(hWnd, TmpName, SizeOf(TmpName));
end;

{ Callbacks }

// This is the callback called in case of an error
// will only be called if the user chose so with ueError

function ErrorCallback(hWnd: HWND; ErrId: Integer; Str: LPTSTR): LRESULT; stdcall;
var
  SelfObj: TJvAVICapture;
begin
  // clear previous error if required
  if ErrId = 0 then
  begin
    Result := LRESULT(Ord(True));
    Exit;
  end;

  // get the Pointer to self from the window user data
  SelfObj := TJvAVICapture(GetWindowLongPtr(hWnd, GWLP_USERDATA));
  if SelfObj <> nil then
    SelfObj.DoError(ErrId, Str);

  Result := LRESULT(Ord(True));
end;

// This is the callback called in case of a status change
// will only be called if the user chose so with ueStatus

function StatusCallback(hWnd: HWND; nId: Integer; Str: LPTSTR): LRESULT; stdcall;
var
  SelfObj: TJvAVICapture;
begin
  // get the Pointer to self from the window user data
  SelfObj := TJvAVICapture(GetWindowLongPtr(hWnd, GWLP_USERDATA));
  if SelfObj <> nil then
    SelfObj.DoStatus(nId, Str);

  Result := LRESULT(Ord(True));
end;

// This is the callback called in case of yielding
// will only be called if the user chose so with ueYield

function YieldCallback(hWnd: HWND): LRESULT; stdcall;
var
  SelfObj: TJvAVICapture;
begin
  // get the Pointer to self from the window user data
  SelfObj := TJvAVICapture(GetWindowLongPtr(hWnd, GWLP_USERDATA));
  if SelfObj <> nil then
    SelfObj.DoYield;

  Result := LRESULT(Ord(True));
end;

// This is the callback called in case a new frame is available while a non
// streaming capture is in progress
// will only be called if the user chose so with ueFrame

function FrameCallback(hWnd: HWND; videoHdr: PVIDEOHDR): LRESULT; stdcall;
var
  SelfObj: TJvAVICapture;
begin
  // get the Pointer to self from the window user data
  SelfObj := TJvAVICapture(GetWindowLongPtr(hWnd, GWLP_USERDATA));
  if SelfObj <> nil then
    SelfObj.DoFrame(videoHdr);

  Result := LRESULT(Ord(True));
end;

// This is the callback called when a frame is available, just before being
// written to disk, only if using stream capture
// will only be called if the user chose so with ueVideoStream

function VideoStreamCallback(hWnd: HWND; videoHdr: PVIDEOHDR): LRESULT; stdcall;
var
  SelfObj: TJvAVICapture;
begin
  // get the Pointer to self from the window user data
  SelfObj := TJvAVICapture(GetWindowLongPtr(hWnd, GWLP_USERDATA));
  if SelfObj <> nil then
    SelfObj.DoVideoStream(videoHdr);

  Result := LRESULT(Ord(True));
end;

// this is the callback when an audio buffer is ready to be written to disk
// and only when using streaming capture
// will only be called if user chose so with ueWaveStream

function WaveStreamCallback(hWnd: HWND; waveHdr: PWaveHdr): LRESULT; stdcall;
var
  SelfObj: TJvAVICapture;
begin
  // get the Pointer to self from the window user data
  SelfObj := TJvAVICapture(GetWindowLongPtr(hWnd, GWLP_USERDATA));
  if SelfObj <> nil then
    SelfObj.DoWaveStream(waveHdr);

  Result := LRESULT(Ord(True));
end;

// this is the callback called when a precise capture control event has
// occurred. Only called if user chose so with ueCapControl

function CapControlCallback(hWnd: HWND; nState: Integer): LRESULT; stdcall;
var
  SelfObj: TJvAVICapture;
  res: Boolean;
begin
  res := True;
  // get the Pointer to self from the window user data
  SelfObj := TJvAVICapture(GetWindowLongPtr(hWnd, GWLP_USERDATA));
  if SelfObj <> nil then
    SelfObj.DoCapControl(nState, res);

  Result := LRESULT(Ord(res));
end;

//=== { TJvVideoFormat } =====================================================

function TJvVideoFormat.Apply: Boolean;
var
  BmpInfo: BITMAPINFOHEADER;
begin
  Result := False;
  if FHWnd <> 0 then
  begin
    BmpInfo.biWidth := FWidth;
    BmpInfo.biHeight := FHeight;
    BmpInfo.biBitCount := FBitDepth;
    BmpInfo.biCompression := FCompression;

    Result := capSetVideoFormat(FHWnd, @BmpInfo, SizeOf(BmpInfo));
  end;
end;

procedure TJvVideoFormat.Update;
var
  BmpInfo: BITMAPINFOHEADER;
begin
  if (FHWnd <> 0) and capDriverConnected(FHWnd) then
  begin
    // get format from the AviCap window
    capGetVideoFormat(FHWnd, @BmpInfo, SizeOf(BmpInfo));

    // update the internal values
    FWidth := BmpInfo.biWidth;
    FHeight := BmpInfo.biHeight;
    FBitDepth := BmpInfo.biBitCount;
    FCompression := BmpInfo.biCompression;

    case BitDepth of
      0:
        FPixelFormat := pfDevice;
      1:
        FPixelFormat := pf1bit;
      4:
        FPixelFormat := pf4bit;
      8:
        FPixelFormat := pf8bit;
      16:
        FPixelFormat := pf15bit;
      24:
        FPixelFormat := pf24bit;
      32:
        FPixelFormat := pf32bit;
    else
      FPixelFormat := pfCustom;
    end;
  end;
end;

//=== { TJvAudioFormat } =====================================================

procedure TJvAudioFormat.Update;
var
  Info: tWAVEFORMATEX;
begin
  if (FHWnd <> 0) and capDriverConnected(FHWnd) then
  begin
    // gets the format from the AviCap window
    capGetAudioFormat(FHWnd, @Info, SizeOf(Info));

    // sets the internal values
    FFormatTag := Info.wFormatTag;
    FChannels := Info.nChannels;
    FSamplesPerSec := Info.nSamplesPerSec;
    FAvgBytesPerSec := Info.nAvgBytesPerSec;
    FBlockAlign := Info.nBlockAlign;
    FBitsPerSample := Info.wBitsPerSample;
    FExtraSize := Info.cbSize;

    // if there is extra data, save it too
    if FExtraSize > 0 then
    begin
      // if there was extra data saved before, free it before
      if FExtra <> nil then
        FreeMem(FExtra);
      GetMem(FExtra, ExtraSize);
      CopyMemory(FExtra, PAnsiChar(@Info) + SizeOf(tWAVEFORMATEX), FExtraSize);
    end;
  end;
end;

function TJvAudioFormat.Apply: Boolean;
var
  pwfex: PWaveFormatEx;
begin
  Result := False;
  if FHWnd <> 0 then
  begin
    FillWaveFormatEx(pwfex);
    Result := capSetAudioFormat(FHWnd, pwfex, SizeOf(tWAVEFORMATEX) + pwfex^.cbSize);
  end;
end;

procedure TJvAudioFormat.FillWaveFormatEx(var wfex: PWaveFormatEx);
begin
  case FormatTag of
    WAVE_FORMAT_PCM:
      begin
        GetMem(wfex, SizeOf(tWAVEFORMATEX));
        wfex^.wFormatTag := FFormatTag;
        // ensure maximum 2 channels
        wfex^.nChannels := FChannels mod 3;
        wfex^.nSamplesPerSec := FSamplesPerSec;
        // ensure 8 or 16 bits
        wfex^.wBitsPerSample := ((FBitsPerSample div 8) mod 3) * 8;
        // using rules defined in Documentation
        wfex^.nBlockAlign := wfex.nChannels * wfex.wBitsPerSample div 8;
        wfex^.nAvgBytesPerSec := wfex.nSamplesPerSec * wfex.nBlockAlign;
        wfex^.cbSize := 0;
      end;
  else
    GetMem(wfex, SizeOf(tWAVEFORMATEX) + FExtraSize);
    wfex^.wFormatTag := FFormatTag;
    wfex^.nChannels := FChannels;
    wfex^.nSamplesPerSec := FSamplesPerSec;
    wfex^.nAvgBytesPerSec := FAvgBytesPerSec;
    wfex^.nBlockAlign := FBlockAlign;
    wfex^.wBitsPerSample := FBitsPerSample;
    wfex^.cbSize := FExtraSize;

      // copy Extra to the end of the structure
    CopyMemory(PAnsiChar(@wfex) + SizeOf(tWAVEFORMATEX), FExtra, FExtraSize);
  end;
end;

//=== { TJvCaptureSettings } =================================================

constructor TJvCaptureSettings.Create;
begin
  inherited Create;
  FFrameDelay := 1;
end;

procedure TJvCaptureSettings.SetKeyAbort(nKeyAbort: TJvVirtualKey);
var
  Modifiers: Word;
begin
  // Unregister any previous hotkey
  if FKeyAbort <> 0 then
    UnregisterHotKey(FHWnd, 0);

  // register hotkey, only if needed
  if nKeyAbort <> 0 then
  begin
    Modifiers := 0;
    if (nKeyAbort and $4000) <> 0 then
      Modifiers := Modifiers or MOD_SHIFT;
    if (nKeyAbort and $8000) <> 0 then
      Modifiers := Modifiers or MOD_CONTROL;
    if RegisterHotKey(FHWnd, 0, Modifiers, nKeyAbort and $FF) then
      FKeyAbort := nKeyAbort;
  end
  else
    FKeyAbort := nKeyAbort;
end;

procedure TJvCaptureSettings.Update;
var
  Parms: TCAPTUREPARMS;
begin
  if FHWnd <> 0 then
  begin
    // get capture settings from window
    capCaptureGetSetup(FHWnd, @Parms, SizeOf(Parms));

  // update internal settings
    FFrameDelay := Parms.dwRequestMicroSecPerFrame;
//      FFramesPerSec             := 1/dwRequestMicroSecPerFrame*1E6;
    FConfirmCapture := Parms.fMakeUserHitOKToCapture;
    FPercentDropForError := Parms.wPercentDropForError;
    FYield := Parms.FYield;
    FNumVideoBuffer := Parms.wNumVideoRequested;
    FCaptureAudio := Parms.FCaptureAudio;
    FNumAudioBuffer := Parms.wNumAudioRequested;
    FAbortLeftMouse := Parms.FAbortLeftMouse;
    FAbortRightMouse := Parms.FAbortRightMouse;
    FKeyAbort := Parms.vKeyAbort;
    FLimitEnabled := Parms.FLimitEnabled;
    FTimeLimit := Parms.wTimeLimit;
    FStepCapture2x := Parms.fStepCaptureAt2x;
    FStepCaptureAverageFrames := Parms.wStepCaptureAverageFrames;
    FAudioBufferSize := Parms.dwAudioBufferSize;
    FAudioMaster := (Parms.AVStreamMaster = AVSTREAMMASTER_AUDIO);
    FMCIControl := Parms.FMCIControl;
    FMCIStep := Parms.fStepMCIDevice;
    FMCIStartTime := Parms.dwMCIStartTime;
    FMCIStopTime := Parms.dwMCIStopTime;
  end;
end;

function TJvCaptureSettings.Apply: Boolean;
var
  Parms: TCAPTUREPARMS;
begin
  Result := False;
  if FHWnd <> 0 then
  begin
    // get original values from window
    capCaptureGetSetup(FHWnd, @Parms, SizeOf(Parms));

    // set our own values
    with Parms do
    begin
      dwRequestMicroSecPerFrame := FFrameDelay;
      fMakeUserHitOKToCapture := ConfirmCapture;
      wPercentDropForError := PercentDropForError;
      FYield := Yield;
      wNumVideoRequested := NumVideoBuffer;
      FCaptureAudio := CaptureAudio;
      wNumAudioRequested := NumAudioBuffer;
      FAbortLeftMouse := AbortLeftMouse;
      FAbortRightMouse := AbortRightMouse;
      vKeyAbort := FKeyAbort;
      FLimitEnabled := LimitEnabled;
      wTimeLimit := TimeLimit;
      fStepCaptureAt2x := StepCapture2x;
      wStepCaptureAverageFrames := StepCaptureAverageFrames;
      dwAudioBufferSize := AudioBufferSize;
      if AudioMaster then
        AVStreamMaster := AVSTREAMMASTER_AUDIO
      else
        AVStreamMaster := AVSTREAMMASTER_NONE;
      FMCIControl := Self.FMCIControl;
      fStepMCIDevice := Self.FMCIStep;
      dwMCIStartTime := FMCIStartTime;
      dwMCIStopTime := FMCIStopTime;
    end;

    // apply new settings
    Result := capCaptureSetSetup(FHWnd, @Parms, SizeOf(Parms));
  end;
end;

function TJvCaptureSettings.GetFPS: Double;
begin
  Result := 1 / FFrameDelay * 1.0E6;
end;

procedure TJvCaptureSettings.SetFPS(const Value: Double);
begin
  FFrameDelay := Round(1.0E6 / Value);
end;

procedure TJvCaptureSettings.SetFrameDelay(const Value: Cardinal);
begin
  // to avoid division by 0 and stupid value for a time delay
  // between two frames
  if Value = 0 then
    FFrameDelay := 1
  else
    FFrameDelay := Value;
end;

//=== { TJvPalette } =========================================================

function TJvPalette.Load(const FileName: string): Boolean;
begin
  Result := (FHWnd <> 0) and capPaletteOpen(FHWnd, PChar(FileName));
end;

function TJvPalette.Save(const FileName: string): Boolean;
begin
  Result := (FHWnd <> 0) and capPaletteSave(FHWnd, PChar(FileName));
end;

function TJvPalette.PasteFromClipboard: Boolean;
begin
  Result := (FHWnd <> 0) and capPalettePaste(FHWnd);
end;

function TJvPalette.AutoCreate(nbFrames: Integer; nbColors: TJvPaletteNbColors): Boolean;
begin
  Result := (FHWnd <> 0) and capPaletteAuto(FHWnd, nbFrames, nbColors);
end;

function TJvPalette.ManuallyCreate(Flag: Boolean; nbColors: TJvPaletteNbColors): Boolean;
begin
  Result := (FHWnd <> 0) and capPaletteManual(FHWnd, Flag, nbColors);
end;

//=== { TJvAVICapture } ======================================================

constructor TJvAVICapture.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FScrollPos := TJvScrollPos.Create;
  // Not connected yet
  FDriverIndex := -1;
  FFileSizeAlloc := 0;
  FOverlaying := False;
  FPreviewing := False;
  FUsedEvents := [];
  FVideoLeft := 0;
  FVideoTop := 0;
  FDrivers := TStringList.Create;
  // Preview frame delay = 50ms between frames (20 frames per second)
  FPreviewFrameDelay := 50;
  FVideoFormat := TJvVideoFormat.Create;
  FAudioFormat := TJvAudioFormat.Create;
  // Default to PCM, 11.025khz 8 bit Mono
  with FAudioFormat do
  begin
    FormatTag := WAVE_FORMAT_PCM;
    Channels := 1;
    BitsPerSample := 8;
    SamplesPerSec := 11025;
  end;
  FCaptureSettings := TJvCaptureSettings.Create;
  FPalette := TJvPalette.Create;
  SetBounds(0, 0, 320, 240);
  EnumDrivers;
  // set all events to 'used'
  UsedEvents := [ueError, ueStatus, ueYield, ueFrame, ueVideoStream, ueWaveStream, ueCapControl];
end;

destructor TJvAVICapture.Destroy;
begin
  Disconnect;
  FDrivers.Free;
  FCaptureSettings.Free;
  FAudioFormat.Free;
  FVideoFormat.Free;
  FPalette.Free;
  FScrollPos.Free;
  inherited Destroy;
end;

procedure TJvAVICapture.CreateWindowHandle(const Params: TCreateParams);
begin
  // create the AviCap window
  FHWnd := capCreateCaptureWindow(
    Params.Caption,
    Params.Style,
    Params.X,
    Params.Y,
    Params.Width,
    Params.Height,
    Params.WndParent,
    0 // window identifier
  );

  // place the Pointer to Self in the user data
  SetWindowLongPtr(FHWnd, GWLP_USERDATA, LONG_PTR(Self));
  // replace the WndProc to be ours
  DefWndProc := Pointer(GetWindowLongPtr(FHWnd, GWLP_WNDPROC));
  SetWindowLongPtr(FHWnd, GWLP_WNDPROC, LONG_PTR(@InitWndProc));

  // updates the FHWnd member of audio format, capture settings, palette and video format
  // yes, they are private members, but they can still be accessed by a foreign class
  // because the access is done in the same pas file !
  FAudioFormat.FHWnd := FHWnd;
  FCaptureSettings.FHWnd := FHWnd;
  FPalette.FHWnd := FHWnd;
  FVideoFormat.FHWnd := FHWnd;
  // sets the callbacks
  UsedEvents := FUsedEvents;

  WindowHandle := FHWND;
end;

procedure TJvAVICapture.DestroyWindowHandle;
begin
  // restore the window proc
  SetWindowLongPtr(FHWnd, GWLP_WNDPROC, LONG_PTR(DefWndProc));

  FHWND := 0;
  FAudioFormat.FHWnd := 0;
  FCaptureSettings.FHWnd := 0;
  FPalette.FHWnd := 0;
  FVideoFormat.FHWnd := 0;

  // let the TWinControl window be destroyed
  inherited DestroyWindowHandle;
end;

function TJvAVICapture.GetTitle: TCaption;
begin
  Result := Text;
end;

procedure TJvAVICapture.SetTitle(const nTitle: TCaption);
begin
  Text := nTitle;
end;

procedure TJvAVICapture.SetPreviewFrameDelay(nPreviewFrameDelay: Cardinal);
begin
  FPreviewFrameDelay := nPreviewFrameDelay;
  if Previewing then
  begin
    StopPreview;
    StartPreview;
  end;
end;

procedure TJvAVICapture.SetPreviewFPS(nPreviewFPS: Double);
begin
  SetPreviewFrameDelay(Round(1.0E3 * 1.0 / nPreviewFPS));
end;

function TJvAVICapture.GetPreviewFPS: Double;
begin
  Result := 1.0E3 * 1.0 / FPreviewFrameDelay;
end;

procedure TJvAVICapture.SetPreviewing(nPreviewing: Boolean);
begin
  if (not nPreviewing) and Previewing then
    StopPreview;
  if nPreviewing and (not Previewing) then
    StartPreview;
end;

procedure TJvAVICapture.SetFileName(nFileName: TFileName);
begin
  if FHWnd <> 0 then
  begin
    FFileName := nFileName;
    // change the filename
    capFileSetCaptureFile(FHWnd, PChar(nFileName));
  end;
end;

function TJvAVICapture.GetFileName: TFileName;
var
  Name: array [0..MAX_PATH] of Char;
begin
  if FHWnd <> 0 then
  begin
    // get the filename from the window
    capFileGetCaptureFile(FHWnd, Name, SizeOf(Name));
    FFileName := Name;
  end;
  Result := FFileName;
end;

function TJvAVICapture.GetDrivers: TStrings;
begin
  Result := FDrivers;
end;

procedure TJvAVICapture.SetFileSizeAlloc(nFileSizeAlloc: Cardinal);
begin
  if FHWnd <> 0 then
  begin
    FFileSizeAlloc := nFileSizeAlloc;
    capFileAlloc(FHWnd, FFileSizeAlloc);
  end;
end;

procedure TJvAVICapture.SetUsedEvents(nUsedEvents: TJvUsedEvents);
begin
  FUsedEvents := nUsedEvents;

  if FHWnd <> 0 then
  begin
    if ueError in FUsedEvents then
      capSetCallbackOnError(FHWnd, @ErrorCallback)
    else
      capSetCallbackOnError(FHWnd, nil);

    if ueStatus in FUsedEvents then
      capSetCallbackOnStatus(FHWnd, @StatusCallback)
    else
      capSetCallbackOnStatus(FHWnd, nil);

    if ueYield in FUsedEvents then
      capSetCallbackOnYield(FHWnd, @YieldCallback)
    else
      capSetCallbackOnYield(FHWnd, nil);

    if ueFrame in FUsedEvents then
      capSetCallbackOnFrame(FHWnd, @FrameCallback)
    else
      capSetCallbackOnFrame(FHWnd, nil);

    if ueVideoStream in FUsedEvents then
      capSetCallbackOnVideoStream(FHWnd, @VideoStreamCallback)
    else
      capSetCallbackOnVideoStream(FHWnd, nil);

    if ueWaveStream in FUsedEvents then
      capSetCallbackOnWaveStream(FHWnd, @WaveStreamCallback)
    else
      capSetCallbackOnWaveStream(FHWnd, nil);

    if ueCapControl in FUsedEvents then
      capSetCallbackOnCapControl(FHWnd, @CapControlCallback)
    else
      capSetCallbackOnCapControl(FHWnd, nil);
  end;
end;

procedure TJvAVICapture.SetOverlaying(nOverlaying: Boolean);
begin
  if not nOverlaying then
  begin
    if Overlaying then
      StopOverlay;
  end
  else
  if not Overlaying then
    StartOverlay;
end;

function TJvAVICapture.GetDriverName: string;
var
  Name: array [0..MAX_PATH] of Char;
begin
  if FHWnd <> 0 then
  begin
    capDriverGetName(FHWnd, Name, SizeOf(Name));
    Result := Name;
  end
  else
    Result := RsNotConnected;
end;

function TJvAVICapture.GetDriverVersion: string;
var
  Version: array [0..MAX_PATH] of Char;
begin
  if FHWnd <> 0 then
  begin
    capDriverGetVersion(FHWnd, Version, SizeOf(Version));
    Result := Version;
  end
  else
    Result := RsNotConnected;
end;

procedure TJvAVICapture.SetScrollPos(nScrollPos: TJvScrollPos);
var
  TmpPoint: TPoint;
begin
  if FHWnd <> 0 then
  begin
    FScrollPos := nScrollPos;
    TmpPoint.X := FScrollPos.Left;
    TmpPoint.Y := FScrollPos.Top;
    capSetScrollPos(FHWnd, @TmpPoint);
  end;
end;

procedure TJvAVICapture.SetMCIDevice(nMCIDevice: string);
begin
  if FHWnd <> 0 then
    capSetMCIDeviceName(FHWnd, PChar(nMCIDevice));
end;

function TJvAVICapture.GetMCIDevice: string;
var
  Name: array [0..MAX_PATH] of Char;
begin
  if FHWnd <> 0 then
  begin
    capGetMCIDeviceName(FHWnd, Name, SizeOf(Name));
    Result := Name;
  end
  else
    Result := RsNotConnected;
end;

procedure TJvAVICapture.SetDriverIndex(nIndex: TJvDriverIndex);
begin
  if Connect(nIndex) then
    FDriverIndex := nIndex;
end;

procedure TJvAVICapture.SetCapturing(nCapturing: Boolean);
begin
  if FCapturing then
  begin
    if not nCapturing then
      StopCapture;
  end
  else
  if nCapturing then
    if FNoFile then
      StartCaptureNoFile
    else
      StartCapture;
end;

procedure TJvAVICapture.SetNoFile(nNoFile: Boolean);
begin
  // only allow to change if not capturing
  if not FCapturing then
    FNoFile := nNoFile;
end;

procedure TJvAVICapture.UpdateCaps;
var
  Caps: TCAPDRIVERCAPS;
begin
  if FHWnd <> 0 then
  begin
    FDriverCaps := [];
    // get value from the window
    if capDriverGetCaps(FHWnd, @Caps, SizeOf(Caps)) then
    begin
      // update internal value
      if Caps.fHasOverlay then
        FDriverCaps := FDriverCaps + [dcOverlay];
      if Caps.fHasDlgVideoSource then
        FDriverCaps := FDriverCaps + [dcDlgVideoSource];
      if Caps.fHasDlgVideoFormat then
        FDriverCaps := FDriverCaps + [dcDlgVideoFormat];
      if Caps.fHasDlgVideoDisplay then
        FDriverCaps := FDriverCaps + [dcDlgVideoDisplay];
      if Caps.fCaptureInitialized then
        FDriverCaps := FDriverCaps + [dcCaptureInitialized];
      if Caps.fDriverSuppliesPalettes then
        FDriverCaps := FDriverCaps + [dcSuppliesPalettes];
    end;
  end;
end;

procedure TJvAVICapture.UpdateCaptureStatus;
begin
  if FHWnd <> 0 then
  begin
    capGetStatus(FHWnd, @FCaptureStatus, SizeOf(FCaptureStatus));
    FCapturing := FCaptureStatus.fCapturingNow;
    FPreviewing := FCaptureStatus.fLiveWindow;
    FOverlaying := FCaptureStatus.fOverlayWindow;
  end;
end;

procedure TJvAVICapture.StopCallbacks;
begin
  if FHWnd <> 0 then
  begin
    if not (csDesigning in ComponentState) then
      capSetCallbackOnError(FHWnd, nil);

    capSetCallbackOnStatus(FHWnd, nil);
    capSetCallbackOnYield(FHWnd, nil);
    capSetCallbackOnFrame(FHWnd, nil);
    capSetCallbackOnVideoStream(FHWnd, nil);
    capSetCallbackOnWaveStream(FHWnd, nil);
    capSetCallbackOnCapControl(FHWnd, nil);
  end;
end;

procedure TJvAVICapture.RestartCallbacks;
begin
  UsedEvents := FUsedEvents;
end;

procedure TJvAVICapture.SetBounds(nLeft, nTop, nWidth, nHeight: Integer);
var
  lWidth, lHeight: Integer;
begin
  // reload video size
  FVideoFormat.Update;

  // Force the width and height to stay in a constant interval :
  // not less than cMinHeight and cMinWidth
  // not more than the video size
  // Autosizing will have been enforced in the CanAutoSize procedure
  lHeight := Max(Min(nHeight, FVideoFormat.Height), cMinHeight);
  lWidth := Max(Min(nWidth, FVideoFormat.Width), cMinWidth);

  inherited SetBounds(nLeft, nTop, lWidth, lHeight);
end;

procedure TJvAVICapture.EnumDrivers;
var
  I: Integer;
  DeviceName: array [0..MAX_PATH] of Char;
  DeviceVersion: array [0..MAX_PATH] of Char;
begin
  // no more than 10 drivers in the system (cf Win32 API)
  Drivers.Clear;
  for I := 0 to 9 do
    if capGetDriverDescription(I, DeviceName, SizeOf(DeviceName), DeviceVersion, SizeOf(DeviceVersion)) then
      Drivers.Add(DeviceName);
end;

function TJvAVICapture.Connect(Driver: TJvDriverIndex): Boolean;
begin
  // Request a handle, will create the AviCap internal window
  // will trigger an exception if no parent is set
  HandleNeeded;

  if Driver = -1 then
  begin
    // if Driver is -1, then we disconnect
    Result := Disconnect;
    // force the video format to be 0, 0 and update the size of the control
    FVideoFormat.FHeight := 0;
    FVideoFormat.FWidth := 0;
  end
  else
  begin
    // else we try to connect to that driver
    Result := capDriverConnect(FHWnd, Driver);
    FConnected := Result;

    if FConnected then
    begin
      // if connected successfully, update the property
      FDriverIndex := Driver;
      UpdateCaps;
      FCaptureSettings.Update;
      FAudioFormat.Update;
      UpdateCaptureStatus;
    end
    else
      // if not, trigger an exception
      raise EInvalidDriverIndexError.Create(Driver, Drivers.Count - 1);
  end;
  AdjustSize;
end;

function TJvAVICapture.Disconnect: Boolean;
begin
  Result := capDriverDisconnect(FHWnd);
  UpdateCaptureStatus;
  FConnected := False;
end;

function TJvAVICapture.ShowDialog(Dialog: TJvVideoDialog): Boolean;
begin
  Result := False;
  if FHWnd <> 0 then
  begin
    case Dialog of
      vdSource:
        Result := capDlgVideoSource(FHWnd);
      vdFormat:
        Result := capDlgVideoFormat(FHWnd);
      vdDisplay:
        Result := capDlgVideoDisplay(FHWnd);
      vdCompression:
        Result := capDlgVideoCompression(FHWnd);
    end;
    // update everything to reflect user changes
    UpdateCaps;
    VideoFormat.Update;
    AudioFormat.Update;
    CaptureSettings.Update;
    AdjustSize;
  end;
end;

function TJvAVICapture.StartPreview: Boolean;
begin
  // if we have a valid window that is not already previewing
  if (FHWnd <> 0) and not FPreviewing then
  begin
    capPreviewRate(FHWnd, FPreviewFrameDelay);
    FPreviewing := capPreview(FHWnd, True);
    UpdateCaptureStatus;
    VideoFormat.Update;
    if FPreviewing then
    begin
      FOverlaying := False;
      RestartCallbacks;
    end;
    Result := FPreviewing;
  end
  else
    Result := False;
end;

function TJvAVICapture.StopPreview: Boolean;
begin
  // if we have a valid window doing previewing
  // then the result is the result of capPreview
  Result := (FHWnd <> 0) and FPreviewing and capPreview(FHWnd, False);

  // if succesfully stopped preview, update internal values
  if Result then
  begin
    UpdateCaptureStatus;
    FPreviewing := False;
    StopCallbacks;
  end;
end;

function TJvAVICapture.StartCapture: Boolean;
begin
  if (FHWnd <> 0) and not FCapturing and ApplyCaptureSettings and
    ApplyAudioFormat then
  begin
    UpdateCaptureStatus;
    VideoFormat.Update;
    FCapturing := capCaptureSequence(FHWnd);
    if FCapturing then
      RestartCallbacks;
    Result := FCapturing;
  end
  else
    Result := False;
end;

function TJvAVICapture.StartCaptureNoFile: Boolean;
begin
  if (FHWnd <> 0) and not FCapturing and ApplyCaptureSettings and
    ApplyAudioFormat then
  begin
    UpdateCaptureStatus;
    VideoFormat.Update;
    FCapturing := capCaptureSequenceNoFile(FHWnd);
    FNoFile := True;
    if FCapturing then
      RestartCallbacks;
    Result := FCapturing;
  end
  else
    Result := False;
end;

function TJvAVICapture.StopCapture: Boolean;
begin
  Result := (FHWnd <> 0) and FCapturing and capCaptureStop(FHWnd);
  if Result then
  begin
    FCapturing := False;
    StopCallbacks;
  end;
end;

function TJvAVICapture.AbortCapture: Boolean;
begin
  Result := (FHWnd <> 0) and FCapturing and capCaptureAbort(FHWnd);
  if Result then
  begin
    FCapturing := False;
    StopCallbacks;
  end;
end;

function TJvAVICapture.StartSingleFrameCapture: Boolean;
begin
  Result := (FHWnd <> 0) and not FSingleFrameCapturing and
    capCaptureSingleFrameOpen(FHWnd);
  if Result then
  begin
    UpdateCaptureStatus;
    VideoFormat.Update;
    RestartCallbacks;
    FSingleFrameCapturing := True;
  end;
end;

function TJvAVICapture.CaptureFrame: Boolean;
begin
  Result := (FHWnd <> 0) and FSingleFrameCapturing and
    capCaptureSingleFrame(FHWnd);
  UpdateCaptureStatus;
  VideoFormat.Update;
end;

function TJvAVICapture.StopSingleFrameCapture: Boolean;
begin
  Result := (FHWnd <> 0) and FSingleFrameCapturing and
    capCaptureSingleFrameClose(FHWnd);
  if Result then
  begin
    UpdateCaptureStatus;
    VideoFormat.Update;
    StopCallbacks;
    FSingleFrameCapturing := False;
  end;
end;

function TJvAVICapture.StartOverlay: Boolean;
begin
  if (FHWnd <> 0) and not FOverlaying then
  begin
    capPreviewRate(FHWnd, FPreviewFrameDelay);
    FOverlaying := capOverlay(FHWnd, True);
    UpdateCaptureStatus;
    VideoFormat.Update;
    if FOverlaying then
    begin
      FPreviewing := False;
      RestartCallbacks;
    end;
    Result := FOverlaying;
  end
  else
    Result := False;
end;

function TJvAVICapture.StopOverlay: Boolean;
begin
  Result := (FHWnd <> 0) and FOverlaying and capOverlay(FHWnd, False);
  if Result then
  begin
    UpdateCaptureStatus;
    FOverlaying := False;
    StopCallbacks;
  end;
end;

function TJvAVICapture.ApplyCaptureSettings: Boolean;
begin
  Result := CaptureSettings.Apply;
end;

function TJvAVICapture.ApplyAudioFormat: Boolean;
begin
  Result := AudioFormat.Apply;
end;

function TJvAVICapture.SaveAs(Name: string): Boolean;
begin
  Result := (FHWnd <> 0) and capFileSaveAs(FHWnd, PChar(Name));
end;

function TJvAVICapture.SetInfoChunk(const Chunk: TCAPINFOCHUNK): Boolean;
begin
  Result := (FHWnd <> 0) and capFileSetInfoChunk(FHWnd, @Chunk);
end;

function TJvAVICapture.SaveDIB(Name: string): Boolean;
begin
  Result := (FHWnd <> 0) and capFileSaveDIB(FHWnd, PChar(Name));
end;

function TJvAVICapture.CopyToClipboard: Boolean;
begin
  Result := (FHWnd <> 0) and capEditCopy(FHWnd);
end;

function TJvAVICapture.GrabFrame(Stop: Boolean): Boolean;
begin
  Result := False;
  if FHWnd <> 0 then
    if Stop then
    begin
      FPreviewing := False;
      FOverlaying := False;
      Result := capGrabFrame(FHWnd);
    end
    else
      Result := capGrabFrameNoStop(FHWnd);
end;

procedure TJvAVICapture.DoError(ErrId: Integer; Str: string);
begin
  if csDesigning in ComponentState then
    Windows.MessageBox(WindowHandle, PChar(Str), PChar(RsErrorMessagePrefix + IntToStr(ErrId)), MB_ICONERROR);
  if Assigned(FOnError) then
    FOnError(Self, ErrId, Str);
end;

procedure TJvAVICapture.DoStatus(nId: Integer; Str: string);
begin
  UpdateCaptureStatus;
  if Assigned(FOnStatus) then
    FOnStatus(Self, nId, Str);
end;

procedure TJvAVICapture.DoYield;
begin
  UpdateCaptureStatus;
  if Assigned(FOnYield) then
    FOnYield(Self);
end;

procedure TJvAVICapture.DoFrame(videoHdr: PVIDEOHDR);
begin
  if Assigned(FOnFrame) then
    FOnFrame(Self, videoHdr);
end;

procedure TJvAVICapture.DoVideoStream(videoHdr: PVIDEOHDR);
begin
  if Assigned(FOnVideoStream) then
    FOnVideoStream(Self, videoHdr);
end;

procedure TJvAVICapture.DoWaveStream(waveHdr: PWaveHdr);
begin
  if Assigned(FOnWaveStream) then
    FOnWaveStream(Self, waveHdr);
end;

procedure TJvAVICapture.DoCapControl(nState: Integer; var AResult: Boolean);
begin
  AResult := True;
  if Assigned(FOnCapControl) then
    FOnCapControl(Self, nState, AResult);
end;

procedure TJvAVICapture.SetVideoLeft(const Value: Integer);
var
  P: TPoint;
begin
  P.X := Value;
  P.Y := VideoTop;
  if capSetScrollPos(FHWnd, @P) then
    FVideoLeft := Value;
end;

procedure TJvAVICapture.SetVideoTop(const Value: Integer);
var
  P: TPoint;
begin
  P.X := VideoLeft;
  P.Y := Value;
  if capSetScrollPos(FHWnd, @P) then
    FVideoTop := Value;
end;

function TJvAVICapture.CanAutoSize(var NewWidth, NewHeight: Integer): Boolean;
begin
  // always possible to do autosizing
  Result := True;

  // reload video size
  FVideoFormat.Update;

  // force the width and height to be equal
  // to the one from the video (with a minimum value set
  // in case there is no video yet)
  NewHeight := Max(cMinHeight, FVideoFormat.Height);
  NewWidth := Max(cMinWidth, FVideoFormat.Width);
end;

procedure TJvAVICapture.SetSingleFrameCapturing(const Value: Boolean);
begin
  if Value then
    StartSingleFrameCapture
  else
    StopSingleFrameCapture;
end;

//=== EInvalidDriverIndexError ===============================================

constructor EInvalidDriverIndexError.Create(Index: TJvDriverIndex; MaxIndex: TJvDriverIndex);
begin
  inherited CreateResFmt(@RsEInvalidDriverIndex, [Index, MaxIndex]);
end;

{$IFDEF UNITVERSIONING}
initialization
  RegisterUnitVersion(HInstance, UnitVersioning);

finalization
  UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}

end.
