A bit of historical perspective:
Bear in mind as you read this that when this project was designed
Delphi was new. Although we knew from (at that time) 13 years of
Turbo Pascal experience that it should produce fast code,
until then the only RAD tool available (VB) was extremely slow and
required external components to do anything useful. Realtime was
-- and still remains -- out of the question for VB, and at the
time VB was in at least its 3rd version. In that era due to the
performance of VB Rapid App Development was considered by many as
a prototyping methodology -- at best -- and not a serious environment.
We needed to develop a project that relied on video capture, which
back then was prone to dropping frames even with compiled C code,
and we needed to capture 30 fps (max possible rate) at 320 x 200
(max capture window size) in 24 bit color. Frame drop potential
was magnified in that Windows95 was only 6 months old, the target
baseline was a 60 MHz Pentium, and back then 16 Megs of RAM was
about as much as one could reasonably expect -- we didn't have
today's ultra fast hard drives or tons of RAM to cache with.
On the other hand, we had a tight deadline, SDK benchmark C code,
and a promising new tool. To our utter relief and amazement,
Delphi 1.0 benchmarked as well as our (non-RAD) C++ compiler,
proving that it was as high performance as Borland had promised.
Although the code presented here is stripped down to show only the
basic use of the Win95 capture DLL, you will be able to see that
Delphi did provide a simple, fast and effective framework to build on.
About a year ago I was strapped into service doing a specialized
video capture application. The tool I chose to use was my (then
new) Delphi 1.0 compiler, mostly because I know that it could be
done in C/C++ and I wanted to find out for myself if Delphi was
as fast as Borland claimed it was. If Delphi could handle real-time
stuff like video capture, the sky was the limit!
Video capture isn't as exotic as you might think. In fact, it's
really pretty simple to do, thanks to AVICAP.DLL. This DLL ships
with Win95 and is a video capture engine. All you need to do to
capture video is create a capture window and then do some message
sending, and AVICAP handles the hard stuff. That's not to say that
you have no control, either; AVICAP provides all sorts of control
options via settings and callback functions. Essentially, your app
talks to AVICAP, which in turn talks to the driver, which in turn
does all of the talking to the hardware. The mechanism for the app
talking to AVICAP is not the prettiest in the world, however: it
uses user-defined message numbers and requires the SendMessage()
API call. I changed the constants in my code to WM_USER + N so
that you could get a better idea of what it looks like.
My primary source of information was Microsoft's Win32 SDK which
assumes C code, so there was a bit of translating to do. The
example code is Delphi 1.0 and is culled from my working app. It
should translate to 32 bits with no problems.
OK, to start, you need to create a structure that will hold the
set of capture control parameters that AVICAP uses. The "capturerec"
record is the Delphi equivalent of what I found in the SDK docs.
I left the names the same as the C names.
AVICAP basically grabs video frames and streams them to a disk file.
You can specify the name of this file. If you don't, it will create
CAPTURE.AVI on the root directory of the drive it's running on.
The first thing that needs to be done is to create a capture
window, which is done by CreateCapWin. First, you need to create a
window and give it a name:
The position coordinates simply outline the location and size of
the preview window, although for the best looking results the window
sizes ought to be the same as the video being captured. More on that
later.
The last parameter specifies which capture device. In my case I knew
that the capture board was an Intel ISVR Pro, and the driver for it
doesn't do screen captures. On the other hand, 0 on a Video Blaster
is used for screen capture and 1 is used for video capture. The
Win32 SDK docs have some examples that will help you figure this
out dynamically if you need to. This same number must also be sent
to the driver via the SendMessage() call that does the driver
connect using the wParam (third) parameter. A Video Blaster
implementation would look like this:
Note that the code also specifies "main.handle", which is the hWnd
that your preview window will show up in. You will need to rename
this to your main form's handle (or any other window handle)
accordingly.
After that, the rest is mostly a matter of telling AVICAP exactly
what to do. To start, you need to retrieve the current capture
parameters AVICAP is using so that you can initialize the capturerec
record. You could just create a record from scratch, but since you
probably are not going to fill in every field, it's safest to let
AVICAP fill in the defaults for you.
It turns out that AVICAP is quite flexible. For instance, note that
in the code we're specifying a preview window that is showing video
at 30 frames per second. The frames per second rate is variable,
with 30 being the maximum. Typical frame rates are 15, 24 and 30.
In the app I wrote, audio wasn't important, so this was disabled.
The app needed to capture 30 fps at (MAX) 320x200, which is really
hard on the hardware, so I elected to not capture audio since this
steals some clock cycles. If you decide to capture audio, you will
probably have to reduce the capture frame rate, the size of the
capture window, or both -- unless you have top notch hardware.
There are a group of parameters that in my case were left to their
default states, mostly because they didn't have any effect on what
I was doing. You can look the descriptions up in the SDK and decide
if these will effect you. They probably won't. After writing the
modified capturerec back to AVICAP, all you have to do is specify
any callbacks and it's pretty much ready to go.
Callbacks:
AVICAP has the ability to let your program know when a video frame
(or an audio frame if you're also capturing audio) has been captured,
and if your system is fast enough, do something to the frame before
it is streamed to the disk. It uses a callback to do this. A callback
function is one in YOUR program that is called by Windows. Essentially
you can think of a callback as a way to extend something that resides
in another program. For instance, if you wanted to have a visible
time and date stamp on the captured video, if your hardware is fast
enough (or the capture speed is slow enough, etc.) you could get into
the video data (it's basically a bitmap), add the stamp, and restore
the video data in the proper format before finishing the function.
As you could guess, it's important to make sure that the callback
can execute cleanly between the times that it is called.
The method for specifying callbacks with AVICAP is a little ugly,
which essentially is the use of the 32 Bit lParam parameter of
SendMessage. What we have to do is get the function address using
the addr() function, typecast this to a longint, and then include
this in the message:
Now for every video frame AVICAP will call the StreamCallback
function by referencing its address. In the example code,
StreamCallback does little more than count up the number of frames
that have been processed, and if you want to play with the code
(and test callbacks in general) to cause an autoabort of the capture
process, all you have to do is set the framestopcount variable to a
number > 1. In the app this code is pulled from, I was looking at
some hardware for a specific condition, which if met, would cause
the process to stop.
The procedures ShowVideoParamsWindow and ShowCompressionWindow aren't
required for capturing but serve to illustrate the relationship of
your code, AVICAP, and the driver for the hardware. These procedures
cause dialogs to appear that modify the capture, such as which Codec
is used, how many colors are captured, etc. Essentially these
functions are little more than wrappers for SendMessage() calls;
they tell AVICAP to tell the driver that we need the dialogs. The
important thing here is that these dialogs are supplied by the driver
and will vary with the hardware.
The procedures ShowCapWin, HideCapWin and KillCapWin are wrappers
for the window management API calls. Lastly, CreateVideo serves as
a small function that acts as a main() for the purposes of showing
what the final app would tend to look like. All you need to do
capture a video is (of course) send a message! In sum, video
capture is a simple process:
Other things to consider when capturing video mainly depend on your
hardware. For instance, the largest video you can currently capture
with consumer grade hardware is 320x200 at 30 frames/sec. And believe
it or not, only the Intel ISVR PRO board can do this reliably. I
tested a lot of different boards, and the frame dropout rate was
not good at all, and this was on a moderate speed Pentium. The
reason the Intel board is so much more reliable is because it is
fine-tuned to run the INDEO Codec and use your main processor. Even
then, it's not perfect. At 30 frames per second, you can expect to
drop frames depending on what is happening in the picture. Too much
movement, and you'll lose frames. We're a long way away from being
perfect, but if you're needing to do multimedia work or games, the
ISVR board is about $250 or so and you can hook it up to a standard
camcorder.
By the way, Delphi 1.0 did everything either at the same speed as
a C app or faster. The Win32 SDK provides benchmarks which I compiled
using Borland's BC++ 4.5, and the Delphi app offered equivalent
performance. Apparently Borland wasn't kidding.
OK, that about sums it all up. I hope I haven't forgotten anything
really important. If I did, or you'd like further information, you
can always ask me.
Note: ISVR PRO is a trademark of Intel Corp. The author is not
affiliated in any way with Intel Corp. or any subsidiaries.
Article
-- G.L. Alston (1997)
hWndC := capCreateCaptureWindow(captit,WS_CHILD or WS_VISIBLE,
{ The next 4 items are coordinates)
0, 0, { upper left }
320, 240, { height / width}
main.handle, 0);
{ Tell AVICAP to connect to the Capture driver. }
smreturn := SendMessage(hWndC, WM_USER + 10, 0, 0);
smreturn := SendMessage(hWndC, WM_USER + 10, 1, 0);
{ tell AVICAP where to find the framecount callback }
SendMessage(hWndC, WM_USER + 6, 0, longint(addr(StreamCallback)));
unit vidcap;
interface
type
capturerec = record
dwRequestMicroSecPerFrame: longint;
fMakeUserHitOKToCapture: wordbool;
wPercentDropForError: word;
fYield: wordbool;
dwIndexSize: longint;
wChunkGranularity: word;
fUsingDOSMemory: wordbool;
wNumVideoRequested: word;
fCaptureAudio: wordbool;
wNumAudioRequested: word;
vKeyAbort: word;
fAbortLeftMouse: wordbool;
fAbortRightMouse: wordbool;
fLimitEnabled: wordbool;
wTimeLimit: word;
fMCIControl: wordbool;
fStepMCIDevice: wordbool;
dwMCIStartTime: longint;
dwMCIStopTime: longint;
fStepCaptureAt2x: wordbool;
wStepCaptureAverageFrames: word;
dwAudioBufferSize: longint;
fDisableWriteCache: wordbool;
AVStreamMaster: word;
end;
procedure InitVariables;
function CreateVideo: integer;
procedure CreateCapWin;
procedure ShowCapWin;
procedure HideCapWin;
procedure KillCapWin;
procedure ShowVideoParamsWindow;
procedure ShowCompressionWindow;
procedure UserMessage(msg: string);
function StreamCallback(wnd: THandle; vh: longint): wordbool; export;
var
framespersec,
framecount,
framestopcount: integer;
videofilename: string;
cparams,
captureparams: capturerec;
implementation
{
this prototype needs to be listed to access the AVICAP function
}
function capCreateCaptureWindow(var lpszWindowName;
dwStyle: longint; x: integer; y: integer;
nWidth: integer; nHeight: integer;
anhwnd: THandle; nID: integer): THandle; far; external 'avicap';
procedure InitVariables;
begin
{ This is used by the callback to auto-stop after the given
number of frames have been captured. }
framestopcount := 0;
{ AVICAP will default to creating the file CAPTURE.AVI in the
root directory of the drive the program is called from. This
variable can store a path\name so that you have some better
control over where it gets captured to. }
videofilename := 'c:\mydir\testcap.avi';
{ The frame capture rate is dependant on many conditions. These
are addressed in the article. For now, we'll set it to MAX. }
framespersec := 30;
end;
procedure CreateVideo;
begin
InitVariables;
CreateCapWin;
{ Delete any capture file if it exists }
if(FileExists(videofilename)) then
DeleteFile(videofilename);
{ There are no frames captured yet }
framecount := 0;
{ Tell AVICAP to begin capturing }
smreturn := SendMessage(hWndC, WM_USER + 62, 0, 0);
KillCapWin;
end;
{
Callback from AVICAP.DLL... every frame that gets captured
generates a function call from AVICAP. In this case we are
using it strictly to count the number of captured frames.
This callback gets initialized by CreateCapWin().
}
function StreamCallback(wnd: THandle; vh: longint): wordbool;
begin
if(framestopcount > 0) then begin
inc(framecount); { note the frame number }
if(framecount > framestopcount ) then
{ Tell AVICAP to abort the operation. }
SendMessage(hWndC, WM_USER + 69, 0, 0);
end;
{ Reassure AVICAP that all is OK. }
result := wordbool(1);
end;
{
This procedure creates the capture window and initializes
all of the capture parameters.
}
procedure CreateCapWin;
var
capavi: array[0..40] of char;
captit: array[0..40] of char;
smreturn: longint;
apntr: pointer;
asize: integer;
begin
{
STEP 1: INIT THE CAPTURE WINDOW AND GET CONNECTED TO
THE DRIVER
}
strpcopy(capavi, videofilename); { captured video file name }
strpcopy(captit, 'capture win'); { capture window }
(*
SEE THE ARTICLE TEXT ABOUT THE FOLLOWING WINDOW CREATION ROUTINE
*)
hWndC := capCreateCaptureWindow ( captit, WS_CHILD or WS_VISIBLE, 0, 0,
320, 240, main.handle, 0);
ShowWindow(hWndC, SW_SHOW);
{ Tell AVICAP to connect to the Capture driver. }
smreturn := SendMessage(hWndC, WM_USER + 10, 0, 0);
if(smreturn <> 0) then begin
usermessage( 'Connected' ); { feedback }
{ tell AVICAP what the name of the file to capture to is }
apntr := addr(capavi);
SendMessage(hWndC, WM_USER + 20, 0, longint(apntr));
{ STEP 2: SET IMAGE PREVIEW UP }
{ Set preview rate at 30 frames per second, in mSec.
1000 mSec/30 frames = 33 mSec. }
SendMessage(hWndC, WM_USER + 52, 33, 0);
{ Now go ahead and preview }
SendMessage(hWndC, WM_USER + 50, 1, 0);
{ STEP 3: INITIALIZE CAPTURE PARAMETERS }
{ First, the capture parameters structure gets initialized
by asking AVICAP to fill it in for us. }
apntr := addr(captureparams);
asize := sizeof(captureparams);
SendMessage(hWndC, WM_USER + 65, asize, longint(apntr));
{ Then start setting up the preferences: }
{ 1 = capture audio, 0 = disable }
captureparams.fCaptureAudio := wordbool(0);
(*
The time limit params are used to force a stop of the
video capture at a specified time, just in case
anything goes wrong. The params here are filled out
to stop capture automatically after 15 seconds just
for the sake of illustration.
*)
{ 1 = enable time limiting, 0 = disable }
captureparams.fLimitEnabled := wordbool(1);
{ In this case, 15 seconds of video, translated to hex }
captureparams.wTimeLimit := word($0E);
{ max error rate = 1%. This is somewhat hardware dependant. }
captureparams.wPercentDropForError := 1;
{ these are the most common frame rates }
if(framespersec = 30) then
captureparams.dwRequestMicroSecPerFrame := 33334 { 30 fps }
else
captureparams.dwRequestMicroSecPerFrame := 41667; { 24 fps }
{ 0 = automatic mode, 1 = put up an OK button to initiate }
captureparams.fMakeUserHitOKToCapture := wordbool(0);
{ 1 = abort capture on Left button click, 1 = disable }
captureparams.fAbortLeftMouse := wordbool(0);
{ 1 = abort capture on Right button click, 1 = disable }
captureparams.fAbortRightMouse := wordbool(0);
{ escape key aborts capturing }
captureparams.vKeyAbort := VK_ESCAPE;
(*
These parameters are listed but aren't required for general
purpose video capture. Refer to the Win32 SDK discussion of
AVICAP to see if your intended application will be affected.
captureparams.wChunkGranularity := 0;
captureparams.dwIndexSize := 0;
captureparams.fUsingDOSMemory := byte(0); { don't use DOS memory }
captureparams.fStepMCIDevice := 0;
captureparams.fMCIControl := 0;
captureparams.fStepCaptureAt2x := 0;
captureparams.fDisableWriteCache := byte(0);
captureparams.wStepCaptureAverageFrames := 3;
*)
{ Now write the capture parameters }
apntr := addr(captureparams);
asize := sizeof(captureparams);
SendMessage(hWndC, WM_USER + 64, asize, longint(apntr));
{ tell AVICAP where to find the framecount callback }
SendMessage(hWndC, WM_USER + 6, 0, longint(addr(StreamCallback)));
end else begin
usermessage( 'Invalid video driver.');
killcapwin; { just to be safe. It *is* windows, after all... }
end;
end;
{ show the capture window (including the live video) }
procedure ShowCapWin;
begin
ShowWindow(hWndC, SW_SHOW);
end;
{ hide the capture window }
procedure HideCapWin;
begin
ShowWindow(hWndC, SW_HIDE);
end;
{ destroy the capture window used by AVICAP }
procedure KillCapWin;
begin
ShowWindow(hWndC, SW_HIDE);
DestroyWindow(hWndC);
end;
{ Show the video format dialog. This is supplied by
the capture driver. All we need to do is tell
AVICAP to make the proper call to the driver. }
procedure ShowVideoParamsWindow;
begin
ShowCapWin;
SendMessage(hWndC, WM_USER + 41, 0, 0);
{ allow this to happen }
application.processmessages;
HideCapWin;
end;
{ Show the video compression options dialog supplied by the
capture driver. This works like the ShowVideoParamsWindow
procedure. }
procedure ShowCompressionWindow;
begin
ShowCapWin;
SendMessage(hWndC, WM_USER + 46, 0, 0);
{ allow this to happen }
application.processmessages;
HideCapWin;
end;
{ display a message to the user }
procedure UserMessage(msg: string);
begin
{
use for troubleshooting or your own messages...
main.messagelabel.caption := msg;
}
end;
end.