Source: Dr Dobb's Journal, July 1992 (original HERE)
Author: James L. Green
This article contains the following executables: AVKCAPT.ZIP
Touring the AVK
Capturing Digital Video
Building the Recorder
Controlling the Recorder
Writing Compressed Data to Disk
Conclusion
Data Compression and the AVK
Compressing Data
Data Streams
Listing One Windows AVK Capture Program - AvkCapt.h (?)
Listing Two Windows AVK Capture Program - Create Recorder
Listing Three Windows AVK Capture Program - Recorder Control
Listing Four Windows AVK Capture Program - Write Captured Data to Disk
The DVI multimedia tools, developed by Intel and IBM,
provide application developers and users with a highly
integrated set of multimedia capabilities. The ActionMedia
II delivery board available for ISA and Micro Channel bus
PCs can be used to play digital audio and video data on
desktop PCs running DOS, Windows, or OS/2. ActionMedia II
cards utilize the i750 video processor to perform
real-time encoding and decoding of digital video images
and come configured with two megabytes of video memory
(VRAM). The system software used to enable these
capabilities under Windows and OS/2 is called the "audio
video kernel" (AVK). AVK provides control over digital
multimedia elements such as audio, video, and still
images.
By attaching the optional capture module to the
Action-Media II delivery card, applications can capture
and compress audio and video data. All of the analog
signals (both audio and video) enter the system via an
8-pin mini-DIN connector located on the delivery board. A
variety of video signals (Y-C, RGB, and Composite) are
supported, as well as stereo audio. The capture subsystem
performs analog-to-digital conversion of the source signal
and deposits the data into VRAM. Digitizing, compressing,
and displaying are independent events under the control of
the software. This enables a variety of data-flow
scenarios. For example, the data can be digitized and
displayed without compression (laser-disc emulation), or
the data can be digitized, compressed, and stored (or
transmitted) without displaying (video
mail/teleconferencing).
Touring
the AVK
AVK is a set of OS-independent, dynamically linked
libraries that provide applications with a collection of
components similar to those found in a recording studio.
These objects can be configured in various ways for
manipulating multimedia data. All AVK function calls take
the form AvkObject-Method(Params). The objects defined in
the AVK programming interface are: groups, buffers,
streams, views, images, and connectors.
An AVK group is the unit of control synchronization and is
analogous to the tape-transport functions of a tape deck.
Group calls include starting, pausing, and recording. A
group buffer is the digital representation of a tape; an
area in VRAM used as a temporary repository of compressed
audio and video data. Since the audio and video data is
often interleaved, a group buffer can contain multiple
streams of data as long as they all play at the same rate,
just as all the tracks on an analog tape must pass the
tape heads at the same rate.
A stream is analogous to a track of audio or video data.
While a motion-video sequence is physically delivered as a
series of consecutive frames, it can be viewed as a
logical stream of data. A video stream is implemented as a
circular array of bitmaps. While capturing, the digitizer
on the capture module places each frame into one of the
bitmaps, while the encode task running on the i750 video
processor compresses each frame and places it into the
group buffer. The audio data is handed to an audio DSP for
encoding before it is placed into its own group buffer.
Another AVK object, called a "view," implements the notion
of a video monitor. A view is a special kind of bitmap
that can genlock to the host display system, allowing DVI
video and standard VGA/XGA graphics to be mixed on a
pixel-by-pixel basis. Views also include a collection of
rectangular visual regions called "boxes" which are mapped
into windows by the application. Video streams and still
images are typically the sources of these visual regions.
The concept of the view is analogous to a visual "mix."
Applications can create and maintain multiple views and
select the view to be monitored on the display.
If the group is a tape deck, and the view is a monitoring
system, then there needs to be a way to connect them. This
is handled by an AVK object called a "connector,"
analogous to a channel on an audio/video mixing board. It
has an input ("source"), an output ("destination"), and
parameters for altering the data in real time. Video
streams, images, views, and the digitizer can all be
connected in various configurations depending on the
application's requirements. At its simplest, the connector
is a higher-level abstraction of a bitmap copy operation.
Connectors allow boxes to be defined for the source and
destination bitmaps. The size of the boxes can be modified
in real time to allow resizing and relocating of images to
support windowing. Connectors behave differently,
depending on which objects are used as their source and
destination. For example, if the source is a video stream
and the destination is a view, the connector will copy
each frame automatically based on the frame rate. If the
source is an image and the destination is a view, the
connector will perform a single copy. Connectors also
provide control for scaling, cropping, and adjusting the
tint, contrast, saturation, and brightness of the image.
Capturing
Digital Video
AvkCapt is a Windows program that captures video and audio
from an analog source using Intel's ActionMedia II board
set. AvkCapt allows you to monitor the analog source and
capture the audio/video data to a file. You can enter a
filename, turn monitoring on and off, and turn capturing
on and off by making selections from pull down menus. When
you begin monitoring, AvkCapt digitizes the data, sending
the audio out to the speakers (attached to the delivery
board) and the video to the computer's display screen.
When capture is toggled on, AvkCapt begins compressing the
incoming data and writing it out to a file.
The audio and video data is compressed using different
algorithms (see the sidebar "Data Compression and the
AVK"). The video is compressed using the real-time video
(RTV 2.0) algorithm at a resolution of 128x240 pixels by
30-frames per second (NTSC) or 128x244 pixels by 25-frames
per second (PAL).
The AVK function AvkDeviceVideoIn() allows the application
to determine the type of source video. RTV doubles the
number of horizontal pixels on playback, resulting in a
256x240 (NTSC) or 256x288 (PAL) video. Using NTSC as the
example, if this video is displayed using a 512x480 view,
the result will appear in a quarter-screen video window.
If a 256x240 view is used, the video will appear full
screen. However, since the horizontal resolution of the
capture stream is only 128 pixels, we can't show the
monitored video at full-screen size on the 256x240 view.
This is because the current version of AVK can't scale the
video up in real time using a connector. In AVK, if the
resolution of the destination is larger than the source,
the video will be displayed in the upper-left corner of
the destination box. Therefore, AvkCapt uses a fixed
window of 128x120 pixels to display the monitored video.
The source to the AvkCapt program is longer than can be
reproduced here. I can, however, describe the three main
aspects of the program apart from the GUI interaction:
configuring the AVK objects for recording audio and video
data, controlling the flow of data through the system, and
writing the compressed data to disk.
More Details.
Building
the Recorder
As described above, AVK provides a collection of
components that can be configured in a variety of ways.
For our purposes, we need to build an audio/video
recorder.
Figure 1
illustrates the configuration used by the AvkCapt program.
In any AVK program, the first steps include initiating an
AVK session and opening the ActionMedia II board. The
function InitAvk() in Listing Two,
page 90 begins an AVK session with a call to
AvkBeginMsg(). This function takes the application's
window handle as one of its parameters. AVK will send
messages to this window to notify it of various events.
Next the board is opened for the exclusive use of the
application with a call to AvkDeviceOpen(), and finally, a
request is sent to the device to identify the capture sync
of source video connected to the digitizer. AVK will
respond to this request by sending an AVK_IDENTIFY message
to the application window. The capture sync will be
returned as part of the 32-bit parameter to the message.
The application's main window procedure intercepts this
message, and the capture sync is passed as a parameter to
the CreateAvkResources() function. This function builds
the recorder by creating and formatting the appropriate
AVK objects.
CreateAvkResources() calls a number of other functions
that do the real work. The GetDevCaps() function uses
AvkGet-DevCaps() to retrieve the device capabilities from
the AVK.INI file. One of the attributes retrieved by this
call is the DviMonitorSync, which is used to decide on the
type of the AVK view and the x and y resolutions. Once
this value is known, the attributes of the view-control
structure can be defined. Since more than one monitor
choice can be bitmapped in DviMonitorSync, we default in
whatever order most suits the specific application's
needs. In this case, we let VGA take precedence over XGA
if both are indicated, and either VGA or XGA over either
PAL or NTSC. We then calculate the screen-to-AVK
coordinate-conversion deltas. These deltas will be used to
convert from
the native-screen resolution to the AVK-view resolution.
For example, given a VGA-screen resolution of 640x480 and
an AVK View of 256x240, we convert an x coordinate with
the formula: Xavk = (int) ((double) Xscreen*
(256.0/640.0)). This is necessary because the video pixels
have a 5/4 aspect ratio. (There are five video pixels for
every four VGA pixels.)
Once the view parameters have been defined, the
Create-View() function is used to create and display an
AVK view by calling AvkViewCreate() and AvkViewDisplay(),
respectively. RTV uses a YUV9 bitmap format (YUV color
space with 4-1-1 subsampling). The view is initially
displayed as a black rectangle. Finally, a call to
SetDstBox() sets the destination-box coordinates for the
stream-to-view connector according to the coordinates of
the main window's client rectangle.
The LoadVshFile() function loads data used by RTV during
the compression process. (A discussion of the VSH data is
beyond the scope of this article.) Now that we have
identified and configured the source for the data (the
digitizer) and the destination (the view), we need to
create a capture group. As shown in CreateCaptureGroup(),
two group buffers are created--one for audio and one for
video.
AVK requires that separate buffers be used when capturing
data, although this data is typically interleaved together
when it is written to disk. On playback, the group buffers
can be configured to contain multiple streams of data.
This allows the interleaved files to be played as is,
without the application having to parse the data back into
separate streams. In addition to the group buffers, which
use video RAM on the ActionMedia II board, buffers in host
RAM are also created for holding video and audio frames
while they are being written to disk.
CreateVideoStream() creates and formats a video stream for
the video-capture buffer. The RTV encoding parameters
Rtv20Args and the x and y resolutions for the specific
capture sync are passed along with the VSH data read in by
LoadVshData(). Once the video stream has been formatted,
the area of memory used to store the VSH data can be
discarded, since AvkVidStrmFormat() makes its own copy.
The last step in building the recorder is to create the
connectors from the digitizer to the video stream and from
the video stream to the view. These connectors act like
the channels in a mixing console, allowing us to control
the flow of data from one place to another. When the
connector from the digitizer to the video stream is
enabled, capture data will begin to flow from the board to
the stream. When the connector between the video stream
and the view is enabled, the captured data will begin to
flow from the video stream to the view and will appear on
the screen in the rectangle defined in View.DstBox.
Creating the audio stream is more straightforward: Simply
create the stream and format it with the frame rate,
sample rate, and algorithm name (in this case ADPCM4).
Closing the AVK session is a simple matter of calling
AvkEnd() and freeing up the memory used for the host I/O
buffers. While there are calls in AVK to explicitly
deallocate AVK objects, AvkEnd() will implicitly destroy
all created objects.
Controlling the Recorder
There are two types of control objects in the AVK
library--connectors and groups. Groups control the flow of
data through the compression/decompression process, and
connectors control the flow of visual data through the
monitoring system. These data flows are shown in Figure 2.
AvkCapt defines four states that determine its behavior:
uninitialized, initialized, monitoring, and capturing.
AvkCapt uses the three functions ToState(), IsState(), and
GetState() shown in Listing
Three , page 94, to alter and query the current
state. These states are used by the application to control
the menu options available to the user.
The ToggleMonitor() and ToggleCapture() functions (also
shown in Listing Three)
illustrate how AvkCapt controls the connectors and groups
by changing state. In ToggleMonitor(), if the current
state is initialized and monitoring is off, we turn it on.
If monitoring is on and we are not capturing, we turn it
off. The functions MonitorOn() and MonitorOff() are used
to do the real work.
MonitorOn() uses the AvkConnEnable() function to enable
the connectors from the digitizer to the video stream and
from the video stream to the view, causing video to be
displayed, and it turns the audio on by calling the
AvkDeviceAudioIn() function. Monitor-Off() turns off the
flow of video by hiding the connectors. AvkConnHide()
paints the key color (black) into the connector's
destination and then disables the connector. Another call
to AvkDeviceAudioIn() turns off audio monitoring.
ToggleCapture() toggles the capture state on or off
(assuming a file has been opened to receive the captured
data). If we are monitoring, we turn on capture by
starting the group. If we are already capturing, we turn
it off by pausing the group. For any other state, we
simply return without doing anything.
Writing
Compressed Data to Disk
The most difficult part of capturing the incoming
digitized data is keeping up with it. If AvkCapt does not
read the frames from the VRAM buffers fast enough, the
frames will be lost, and a series of blank frames will
have to be inserted to take their place (in order to keep
the frame rate constant). This will cause skipping effects
on playback. On the other hand, if too much time is spent
retrieving data, the message loop may not respond promptly
and mouse action may be degraded.
AvkCapt illustrates two different approaches to retrieving
data in a timely manner. The first
involves calling a read routine each time AvkVCapt
receives a AVK_CAPTURE_DATA_AVAILABLE message from AVK
informing it that a designated amount of data (called the
"hungry granularity") has been captured into a VRAM group
buffer. The application sets this level when creating the
group buffer.
The read routine then retrieves as much data from the VRAM
buffer as it can, parses it into frames, and writes it out
to the AVSS file. The ~ returns to process the message
loop and awaits the next AVK_CAPTURE_DATA_AVAILABLE
message.
The second method (enabled by selecting the Timer option
from the File pop-up menu) involves setting up a Windows
timer and calling the same read routine on each timer. (We
use a timer tick of 500 milliseconds in AvkCapt.) This
will result in a maximum of two calls per second, so the
capture function has to write about 15 frames per call to
keep up. CaptureAvioData() writes out more than that if
more data is present, so the timer messages may back up.
Since windows discards these if another set of timer ticks
is already waiting in the queue, this is not a problem.
AvkCapt's capturing performance can be tuned by varying
the TIMER_INTERVAL, the HOST_BUF_SIZE, or the value for
CAPTURE_LOOPS (which dictates how many iterations of the
read write loop will be executed in CaptureAvioData()
before it is forced back to the main message loop). These
values are defined in avkcapt.h; see Listing One, page 90.
Listing Four, page 94, shows
the CaptureAvioData() and ReadGrpBuf() functions. These
functions retrieve frames from the group buffers in VRAM
and write them out to an AVSS file on disk. AvkCapt uses
the AVKIO file I/O subsystem to create an AVSS file. Video
frames and one frame's worth of audio samples are
retrieved separately from their respective buffers. The
AVKIO function AvioFileFrmWrite() interleaves the video
and audio into the AVSS file. Each iteration of the main
loop starts by checking to see whether the application's
video or audio host RAM buffer is empty, and, if so, it
reads one buffer's worth of frames from the VRAM group
buffers. ReadGrpBuf() is used to read newly captured
frames from an AVK group buffer into one of the
application's host RAM buffers. The count of bytes read is
put in the CAPT structure's BufDataCnt element. If any
data is
read, the caller's flag, pbDataRead is set to True. Next
we loop through the video and audio host RAM buffers
writing out matched video and audio frames to the file.
When we run out of either video or audio frames, we loop
back to the top to retrieve more frames. This loop
continues until all frames currently captured in VRAM have
been retrieved, or until the loop has executed
CAPTURE_LOOPS times. We use this countdown value to
prevent the loop from executing for too long without
giving the message loop time to run. If frames are being
captured as fast as we are reading them, we might
otherwise never exit this loop.
It is rather unlikely, but possible, that we will have a
re-entrancy problem here. Since the function creates a
message box in the case of an error, it can allow the
message loop to process new messages before we exit it.
This might result in a new timer tick or an AVK message
causing reentry before we have finished displaying the
error message and killing the process. To prevent this
contingency, we use an ownership semaphore. If the
semaphore is set when we enter, something has gone wrong
in the currently executing code. So, instead of just
blocking on the semaphore, the new occurrence exists.
The semaphore is set to indicate that the code is
executing and is cleared as the last operation before a
successful exit. Note that we do not clear the semaphore
before exiting on an error condition, since we will be
terminating the application on any error here and do not
want to begin executing this code again between this exit
and the applications termination.
Conclusion
AVK's function calls are identical between the Windows and
OS/2 versions of the library. So the bulk of the code I
have discussed will remain the same for an OS/2
implementation of AvkCapt. The main difference will be in
the code that writes the data to disk. With OS/2, this can
be accomplished using a couple of threads and sharing a
common-host memory buffer, eliminating the need for the
timer-tick mechanism.
It is also possible to create other configurations using
the AVK objects. For example, applications can build
recorders that capture only video or only audio data,
players that play audio and video data from different
sources, or players that combine motion-video and
still-image data to a common view.
This kind of flexibility has its costs in terms of code
complexity, however. Both the i750 video processor and AVK
were architected to be platform independent. AVK can
support a variety of higher-level APIs which encapsulate
OS-specific file-I/O functions.
As one example, Intel and IBM are working on a
higher-level library that implements the digital-video
media-control interface (DV MCI) command set for
multimedia extensions to Windows and OS/2.
QuickTime will be implemented on top of AVK by New Video
(Venice, California) for its DVI Macintosh products. (See
"The QuickTime/AVK Connection" on page 28.) Both DV MCI
products and QuickTime provide "preconfigured"
player/recorder objects for developers who do not need or
want to roll their own.
Acknowledgments
The author would like to express his appreciation to John
Novack, who developed the AvkCapt program described in
this article.
Data Compression
and the AVK
The Audio Video Kernel (AVK) is a multilayered
architecture that isolates hardware-specific features from
the application programmer while enabling porting of audio
and video data to other platforms.
The AVK is itself sandwiched between an
environment-specific API (in this case, the Windows API)
and the Action Media II hardware. The ActionMedia II board
includes the i750 video processor, an optional capture
module, and typically two megabytes of local video memory
(VRAM). The i750 consists of two processors: the 82750PB
pixel processor and the 82750DB display processor.
Closest to the hardware is the microcode engine, a
collection of routines loaded into instruction memory
aboard the pixel processor. These routines manage
real-time requirements such as task scheduling, data
compression and decompression, and image scaling and
copying. The big win here is that by loading these
routines into instruction memory, there is no hardwriting,
for example, of compression and decompression algorithms.
The microcode routines can be modified to add or change
functionality without updating the hardware.
The next layer in the AVK, the audio/video driver (AVD),
provides a C interface to the ActionMedia II hardware,
thus providing access to each component of the board.
Included are functions to access VRAM, load microcode
functions into instruction memory aboard the pixel
processor, set display formats for the display processor,
and access the audio and capture subsystems. Intel has
also created a conceptual model of a "digital production
studio" which contains individual subsystems that
correspond to real-world systems such as tape decks,
effects processors, mixing boards, and so on. The
audio/video library (AVL) adds a set of multimedia
functions that are independent of the host environment to
implement these concepts.
Compressing Data
The AVK currently supports two forms of compression for
video images. The first is real-time video (RTV), which is
implemented in microcode and processed on the pixel
processor. RTV takes multiple passes over the video data
using several techniques, including frame differencing and
Huffman coding to reduce the bit rate. Therefore, RTV
compression is lossy. RTV 2.0 improves over the original
algorithm with better image quality and adjustable data
rates of up to 300 Kbytes/second, or twice that of CD-ROM
rates. Image quality, which is directly affected by the
amount of data lost, can be specified by the application
as good, better or best. Good is typically used at lower
data rates such as CD-ROM. Better quality is recommended
when playback will occur from the hard drive and best is
used when compressing to a RAM disk.
Production-level video (PLV) uses essentially the same
compression as RTV, but takes advantage of offline
compression services to gain the highest quality. Thus,
PLV data including audio can be decompressed and displayed
at rates similar to best quality RTV, which is closer to
150 Kbytes/second.
AVK uses JPEG for capture and lossy compression of still
images, using 4:1:1 YUV color sampling. Though the
compression technique is different, still images are
treated as a special case of motion video that contains
just a single frame. From the AVK perspective, programmers
can open, play, and close a still image using the same
calls as motion video. Developers can also adjust
frequency for still images.
Audio data is compressed using a 4-bit
adaptive-compression algorithm (ADPCM4). This is a
straightforward technique that predicts the next audio
sample based on the previous sample. As with video
quality, audio quality can be specified as good, better,
or best. But since this algorithm was originally intended
for voice samples, it doesn't achieve the high quality one
might expect. The encoding of audio data is an area that
MPEG greatly improves upon over ADPCM4. Intel promises to
support the MPEG standard when it is finalized, so look
for big gains here.
Data
Streams
A stream is composed of a set of audio or video frames. A
video stream consists of a starting reference frame whose
entire image is encoded. Subsequent frames, called
"dependent" frames, are encoded as changes to previously
decompressed images. (Only pixels that change between
frames are stored.) Occasionally, when the image
significantly changes or image quality begins to
deteriorate, a new reference frame is inserted. A benefit
of reference frames is that they can be decompressed
independent of other frames. Note, however, that you
currently cannot seek to a dependent frame--only to a
reference frame. Note also that audio frames are
independent of video. As previously mentioned, they are
compressed using a 4-bit ADPCM algorithm. Once
decompressed, audio and video streams can be interleaved
on a frame-by-frame basis.
Finally, it's interesting to note that Intel, at the
request of the Interactive Multimedia
Association (IMA), is making available details of RTV's
compressed video bit-stream format.
Documentation is available to developers on a licensing
basis, thus opening the door for software-only
decompression of AVSS files. Fluent Machines (Framingham,
Massachusetts) is expected to be the first to offer a
software-only solution. For more information, contact the
IMA, 3 Church Circle, Suite 800, Annapolis, MD 21401;
410-626-1380.
--Michael Floyd
_CAPTURING DIGITAL VIDEO USING DVI_
by James L. Green
Listing One
//--- AvkCapt.h Copyright Intel Corp. 1991, 1992, All Rights Reserved ---
#include "avkapi.h"
// File name for the RTV 2.0 VSH data file (from avkalg.h)
#define VSHFILE_NAME AVK_RTV_20_ENCODE_DATA_NAME
// A couple of shorthand AVK #defines for convenience
#define OK AVK_ERR_OK
#define NOW AVK_TIME_IMMEDIATE
#define HNULL ((HAVK)0)
// Values for capturing
#define AUD_SAMPLE_RATE (U32)33075
#define FRAME_RATE (U32)33367
// Size of Capture Data Buffers
#define VID_BUF_SIZE (256L * 1024L)
#define VID_BUF_GRAN ( 64L * 1024L)
#define AUD_BUF_SIZE (128L * 1024L)
#define AUD_BUF_GRAN ( 16L * 1024L)
#define HOST_BUF_SIZE 32768U
// Maximum number of iterations of the capture loop
// before we are forced back to the main message loop
#define CAPTURE_LOOPS 10
// ID value for the capture Windows timer
#define TIMER_ID 1
// Number of milliseconds between timer ticks
#define TIMER_INTERVAL 500
// States for the capture engine
#define ST_UNINITIALIZED 0
#define ST_INITIALIZED 1
#define ST_MONITORING 2
#define ST_CAPTURING 3
// Control structure for the current view
typedef struct tagVIEW
{
HAVK hView; // AVK View handle
HAVK hConnDigi2Strm; // Digitizer to Video Stream connector
HAVK hConnStrm2View; // Video Stream to View connector
BOOL bConnEnabled; // TRUE if the connector is enabled
WORD DviMonitorSync; // DviMonitorSync value from AVK.INI
I16 cxView; // View's x resolution
I16 cyView; // View's y resolution
double xDelta; // used to convert screen
double yDelta; // coords to view
I16 cxScreen; // physical screen's x resolution
I16 cyScreen; // physical screen's y resolution
U16 VidType; // View's video type
U16 BmFmt; // View's bitmap format
BOOL bIsKeyed; // TRUE if the View is keyed
BOX SrcBox; // connector's source rectangle
BOX DstBox; // connector's destination rectangle
} VIEW;
// Control structure for capture buffers
typedef struct tagCAPT
{
HAVK hGrpBuf; // group buffer handle
HAVK hStrm; // stream handle
char far *pBufHead; // host RAM I/O buffer
char far *pBufCurr; // current position in host I/O buffer
U32 BufDataCnt; // amount of data in host I/O buffer
} CAPT;
// Structure for storing sync resolutions. The sync table
// will be an array of VIDEO_SYNC structures called Syncs[].
typedef struct tagVIDEO_SYNC
{
WORD xResRTV; // RTV capture x resolution
WORD xResVid; // Video stream premonitor x resolution
WORD yResVid; // Video stream premonitor y resolution
WORD FrameRate;
WORD PixelAspect;
} VIDEO_SYNC;
// These sync values are subscripts into a table of VIDEO_SYNC structures
#define SYNC_NTSC 0
#define SYNC_PAL 1
Listing Two
// ---- Windows AVK Capture Program - Create Recorder ----------------
// ---- Copyright Intel Corp. 1991, 1992, All Rights Reserved ---------
extern HWND hwndMain;
extern VIDEO_SYNC Syncs[];
// Local variables
static WORD State = ST_UNINITIALIZED; // current state of capture engine
WORD CaptureSync = SYNC_NTSC; // default to NTSC
char far *pVshBuf = NULL; // buffer for reading VSH data.
U32 VshSize; // size of the VSH data
VIEW View; // view control structure
AVIO_SUM_HDR Avio; // master control struct for AVSS file I/O
CAPT Vid; // video capture control structure
CAPT Aud; // audio capture control structure
I16 AvkRet; // general AVK return code variable
// RTV 2.0 encoding arguments
AVK_RTV_20_ENCODE_ARGS Rtv20Args =
{
12, // argument count
AVK_RTV_2_0, // algorithm size
0,0, // x,y coords of origin
128, 240, // xLength, yLength
3, // still period
0, 0, // bytes,lines
AVK_RTV_20_PREFILTER | AVK_RTV_20_ASPECT_25, // flags
0, 0 // quantization values
};
// AVK handles
HAVK hAvk = (HAVK)0;
HAVK hDev = (HAVK)0;
HAVK hGrp = (HAVK)0;
// Create AVK session and initialize the device
BOOL InitAvk()
{
if (!IsState(ST_UNINITIALIZED))
return TRUE;
// Start an AVK session with messaging
if ((AvkRet = AvkBeginMsg(hwndMain, &hAvk,
AVK_SESSION_DEFAULT)) != OK)
return DispAvkErr(AvkRet, "AvkBeginMsg");
// Open the ActionMedia(R) device
if ((AvkRet = AvkDeviceOpen(hAvk, 0,
AVK_DEV_OPEN_EXCLUSIVE, &hDev)) != OK)
return DispAvkErr(AvkRet, "AvkDeviceOpen");
// Get the capture sync by calling AvkDeviceVideoIn()
if ((AvkRet = AvkDeviceVideoIn(hDev, AVK_CONN_DIGITIZER)) != OK)
return DispAvkErr(AvkRet, "AvkDeviceVideoIn");
return TRUE;
}
// Check device capabilities and build the recorder
BOOL CreateAvkResources(WORD NewCaptureSync)
{
switch (NewCaptureSync)
{
case AVK_SYNC_NTSC: CaptureSync = SYNC_NTSC; break;
case AVK_SYNC_PAL: CaptureSync = SYNC_PAL; break;
}
// Get the AVK device capabilities from AVK.INI
if (!GetDevCaps(&View))
return FALSE;
if (!CreateView(&View))
return FALSE;
// The Vsh file contains data used in compressing
// the incoming motion video into an RTV 2.0 file.
if (!LoadVshFile())
return FALSE;
if (!CreateCaptureGroup())
return FALSE;
ToState(ST_INITIALIZED);
return TRUE;
}
// Get the device capabilities from AVK
BOOL GetDevCaps(VIEW *pView)
{
DVICAPS DevCaps;
// Get the physical screen resolution from the system
pView->cxScreen = GetSystemMetrics(SM_CXSCREEN);
pView->cyScreen = GetSystemMetrics(SM_CYSCREEN);
// Get the AVK device capabilities which were set in AVK.INI
if ((AvkRet = AvkGetDevCaps(0, sizeof(DevCaps), &DevCaps)) != OK)
return DispAvkErr(AvkRet, "AvkGetDevCaps");
if (DevCaps.DigitizerRevLevel == 0)
return DispErr("GetDevCaps",
"Digitizer needed for capturing - check AVK.INI");
if (DevCaps.DviMonitorSync & 0x10) // VGA
{
pView->cxView = 256;
pView->cyView = 240;
pView->VidType = AVK_VID_VGA_KEYED;
pView->bIsKeyed = TRUE;
}
else if (DevCaps.DviMonitorSync & 0x100) // XGA
{
pView->cxView = 256;
pView->cyView = 192;
pView->VidType = AVK_VID_XGA_KEYED;
pView->bIsKeyed = TRUE;
}
else if (DevCaps.DviMonitorSync & 0x02) // PAL
{
pView->cxView = 306;
pView->cyView = 288;
pView->VidType = AVK_VID_PAL;
}
else if (DevCaps.DviMonitorSync & 0x01) // NTSC
{
pView->cxView = 256;
pView->cyView = 240;
pView->VidType = AVK_VID_NTSC;
}
else
return DispErr("GetDevCaps", "Invalid monitor sync");
// Calculate Screen-To-AVK coordinate conversion deltas.
pView->xDelta = (double)pView->cxView / (double)pView->cxScreen;
pView->yDelta = (double)pView->cyView / (double)pView->cyScreen;
return TRUE;
}
// Create and display an AVK View
static BOOL CreateView(VIEW *pView)
{
if ((AvkRet = AvkViewCreate(hDev, pView->cxView, pView->cyView,
AVK_YUV9, pView->VidType, &pView->hView)) != OK)
return DispAvkErr(AvkRet, "AvkViewCreate");
// Display the View
if ((AvkRet = AvkViewDisplay(hDev, pView->hView, NOW,
AVK_VIEW_DISPLAY_DEFAULT)) != OK)
return DispAvkErr(AvkRet, "AvkViewDisplay");
// Set the destination box for the stream-to-view connector
if (!SetDstBox(hwndMain))
return FALSE;
return TRUE;
}
// Set the destination box for the stream-to-view connector
BOOL SetDstBox(HWND hwndMain)
{
RECT WinRect;
BOX NewDstBox;
GetClientRect(hwndMain, (LPRECT)&WinRect);
ClientToScreen(hwndMain, (LPPOINT)&WinRect);
WinRect.right = WinRect.left + (View.cxScreen >> 1) - 1;
WinRect.bottom = WinRect.top + (View.cyScreen >> 1) -1;
WinRect2AvkBox(&WinRect, &NewDstBox, &View);
if (View.hConnStrm2View)
{
if ((AvkRet = AvkConnHide(View.hConnStrm2View, NOW)) != OK)
return DispAvkErr(AvkRet, "AvkConnHide");
if ((AvkRet = AvkViewCleanRect(View.hView,
&View.DstBox)) != OK)
return DispAvkErr(AvkRet, "AvkViewCleanRect");
// Reset the destination of the connector to our new box
if ((AvkRet = AvkConnModSrcDst(View.hConnStrm2View, NULL,
&NewDstBox, NOW)) != OK)
return DispAvkErr(AvkRet, "AvkConnModSrcDst");
if ((AvkRet = AvkConnEnable(View.hConnStrm2View, NOW)) != OK)
return DispAvkErr(AvkRet, "AvkConnEnable");
}
// Copy new destination coords into the view's destination box
COPYBOX(&View.DstBox, &NewDstBox);
return TRUE;
}
// Get the standard VSH file that comes with AVK
static BOOL LoadVshFile()
{
int fhVsh;
OFSTRUCT Of;
// Open the VSH file
if ((fhVsh = OpenFile(VSHFILE_NAME, &Of, OF_READ)) == -1)
return DispErr("LoadVshFile",
"Unable to find the file KE080200.VSH");
VshSize = filelength(fhVsh);
// Range check - Reject if VshSize == 0 or VshSize > 65535L
if (!VshSize || VshSize & 0xffff0000)
return DispErr("LoadVshFile", "VSH file too large to load");
// Allocate a buffer to stash the VSH file.
if ((pVshBuf = MemAlloc((WORD)VshSize)) == NULL)
return DispErr("LoadVshFile",
"Unable to allocate VSH file buffer");
// Read the VSH data from the file
if (_lread(fhVsh, pVshBuf, (WORD)VshSize) != (WORD)VshSize)
return DispErr("LoadVshFile", "Unable to read VSH file");
return TRUE;
}
// Create Capture Group and resources needed for premonitoring
static BOOL CreateCaptureGroup()
{
if ((AvkRet = AvkGrpCreate(hDev, &hGrp)) != OK)
return DispAvkErr(AvkRet, "AvkGrpCreate");
if ((AvkRet = AvkGrpBufCreate(hGrp, AVK_BUF_CAPTURE, VID_BUF_SIZE,
VID_BUF_GRAN, 1, &Vid.hGrpBuf)) != OK)
return DispAvkErr(AvkRet, "AvkGrpBufCreate");
if ((AvkRet = AvkGrpBufCreate(hGrp, AVK_BUF_CAPTURE, AUD_BUF_SIZE,
AUD_BUF_GRAN, 1, &Aud.hGrpBuf)) != OK)
return DispAvkErr(AvkRet, "AvkGrpBufCreate");
// Create host RAM I/O buffers for retrieving
// video and audio frames and initialize them.
if ((Vid.pBufHead = MemAlloc(HOST_BUF_SIZE)) == NULL
|| (Aud.pBufHead = MemAlloc(HOST_BUF_SIZE)) == NULL)
return DispErr("CreateCaptureGroup",
"Unable to allocate host RAM I/O buffer");
Vid.BufDataCnt = (U32)0;
Aud.BufDataCnt = (U32)0;
if (!CreateVideoStream())
return FALSE;
if (!CreateAudioStream())
return FALSE;
if ((AvkRet = AvkGrpFlush(hGrp)) != OK)
return DispAvkErr(AvkRet, "AvkGrpFlush");
return TRUE;
}
// Create and format a video stream for the video capture buffer
static BOOL CreateVideoStream()
{
if ((AvkRet = AvkVidStrmCreate(Vid.hGrpBuf, 0, &Vid.hStrm)) != OK)
return DispAvkErr(AvkRet, "AvkVidStrmCreate");
// Format the video stream
Rtv20Args.xLen = Syncs[CaptureSync].xResRTV;
Rtv20Args.yLen = Syncs[CaptureSync].yResVid;
if ((AvkRet = AvkVidStrmFormat(Vid.hStrm,
6,
Syncs[CaptureSync].xResVid,
Syncs[CaptureSync].yResVid,
AVK_YUV9,
Syncs[CaptureSync].FrameRate,
AVK_RTV_2_0,
&Rtv20Args, sizeof(Rtv20Args), sizeof(Rtv20Args),
pVshBuf, VshSize, 64L * 1024L)) != OK)
return DispAvkErr(AvkRet, "AvkVidStrmFormat");
// Free the VSH buffer
MemFree(pVshBuf);
// Create a connector from the digitizer to the video stream
if ((AvkRet = AvkConnCreate(AVK_CONN_DIGITIZER, NULL, Vid.hStrm,
NULL, 0, &View.hConnDigi2Strm)) != OK)
return DispAvkErr(AvkRet,
"AvkConnCreate(Digitizer to Stream)");
// Create the connector from the video stream to the view
if ((AvkRet = AvkConnCreate(Vid.hStrm, NULL, View.hView,
&View.DstBox, AVK_PRE_MONITOR, &View.hConnStrm2View)) != OK)
return DispAvkErr(AvkRet, "AvkConnCreate (Stream to View)");
return TRUE;
}
// Create and format a audio stream for the audio capture buffer
static BOOL CreateAudioStream()
{
if ((AvkRet = AvkAudStrmCreate(Aud.hGrpBuf, 0, &Aud.hStrm)) != OK)
return DispAvkErr(AvkRet, "AvkAudStrmCreate");
// Format the audio stream
if ((AvkRet = AvkAudStrmFormat(Aud.hStrm, FRAME_RATE,
AUD_SAMPLE_RATE, AVK_ADPCM4, AVK_AUD_MIX, NULL, 0, 0)) != OK)
return DispAvkErr(AvkRet, "AvkAudStrmFormat");
return TRUE;
}
// Close the AVK session
BOOL EndAvk()
{
BOOL Ret = TRUE;
if (hAvk != HNULL)
{
if ((AvkRet = AvkEnd(hAvk)) != OK)
{
DispAvkErr(AvkRet, "AvkEnd");
Ret = FALSE;
}
}
if (Vid.pBufHead)
{
MemFree(Vid.pBufHead);
Vid.pBufHead = NULL;
}
if (Aud.pBufHead)
{
MemFree(Aud.pBufHead);
Aud.pBufHead = NULL;
}
// Null out all of the AVK handles
hAvk = hDev = HNULL;
hGrp = HNULL;
Vid.hGrpBuf = Vid.hStrm = HNULL;
Aud.hGrpBuf = Aud.hStrm = HNULL;
View.hView = HNULL;
View.hConnDigi2Strm = View.hConnStrm2View = HNULL;
ToState(ST_UNINITIALIZED);
return Ret;
}
Listing Three
//---- Windows AVK Capture Program - Recorder Control ------------
//---- Copyright Intel Corp. 1991, 1992, All Rights Reserved -----
// Sets a new state and enables/disables the applicable menu options
WORD ToState(WORD NewState)
{
WORD OldState;
if (NewState == ST_CAPTURING
|| NewState == ST_MONITORING
|| NewState == ST_INITIALIZED
|| NewState == ST_UNINITIALIZED)
{
if (State != NewState)
{
OldState = State;
State = NewState;
UpdateMenus(State);
return OldState;
}
else
return NewState;
}
return 0xffff;
}
// Checks whether the current state equals the caller's query state
BOOL IsState(WORD QueryState)
{
return State == QueryState;
}
// Returns the current state to the caller
WORD GetState()
{
return State;
}
// Toggle monitoring on and off based on user input
BOOL ToggleMonitor(VOID)
{
BOOL bRet;
switch (GetState())
{
case ST_INITIALIZED: bRet = MonitorOn(); break;
case ST_MONITORING: bRet = MonitorOff(); break;
default: bRet = TRUE; break;
}
return bRet;
}
// Turn on premonitoring
static BOOL MonitorOn()
{
if ((AvkRet = AvkConnEnable(View.hConnDigi2Strm, NOW)) != OK
|| (AvkRet = AvkConnEnable(View.hConnStrm2View, NOW)) != OK)
return DispAvkErr(AvkRet, "AvkConnEnable");
if ((AvkRet = AvkDeviceAudioIn(hDev, AVK_AUD_CAPT_LINE_INPUT,
AVK_MONITOR_ON)) != AVK_ERR_OK)
return DispAvkErr(AvkRet, "AvkDeviceAudioIn");
ToState(ST_MONITORING);
SetClipTimer();
return TRUE;
}
// Turn off premonitoring
static BOOL MonitorOff()
{
KillClipTimer();
if ((AvkRet = AvkConnHide(View.hConnStrm2View, NOW)) != OK
|| (AvkRet = AvkConnHide(View.hConnDigi2Strm, NOW)) != OK)
return DispAvkErr(AvkRet, "AvkConnHide");
if ((AvkRet = AvkDeviceAudioIn(hDev, AVK_AUD_CAPT_LINE_INPUT,
AVK_MONITOR_OFF)) != AVK_ERR_OK)
return DispAvkErr(AvkRet, "AvkDeviceAudioIn");
ToState(ST_INITIALIZED);
return TRUE;
}
// Toggles the capture on or off
BOOL ToggleCapture()
{
// If no file has been opened, return
if (!bAvioFileExists)
{
DispMsg("You must open a file before you can capture");
return TRUE;
}
switch(GetState())
{
case ST_MONITORING:
// If we are monitoring, turn on
// capture by starting the group
if ((AvkRet = AvkGrpStart(hGrp, NOW)) != OK)
return DispAvkErr(AvkRet, "AvkGrpStart");
ToState(ST_CAPTURING);
break;
case ST_CAPTURING:
// If we are already capturing, turn
// it off by pausing the group
if ((AvkRet = AvkGrpPause(hGrp, NOW)) != OK)
return DispAvkErr(AvkRet, "AvkGrpPause");
break;
default:
// Any other state, just do nothing - no error
break;
}
return TRUE;
}
Listing Four
// ---- Windows AVK Capture Program - Write Captured Data to Disk ------
// ---- Copyright Intel Corp. 1991, 1992, All Rights Reserved ----------
extern CAPT Aud;
extern CAPT Vid;
extern I16 AvkRet;
extern HAVK hGrp;
extern WORD CaptureSync;
AVIO_SUM_HDR Avio;
BOOL bAvioFileExists = FALSE;
I16 AvioRet;
static BOOL ReadGrpBuf(CAPT *, BOOL *);
I16 DispAvioErr(char *pMsg);
VIDEO_SYNC Syncs[2] =
{
{ 128, 128, 240, AVK_NTSC_FULL_RATE, AVK_PA_NTSC },
{ 128, 153, 288, AVK_PAL_FULL_RATE, AVK_PA_PAL }
};
// Initialize the AVIO summary header and use it to create an AVSS file.
BOOL OpenAvioFile(char *pFileSpec)
{
AVIO_VID_SUM FAR *pVid;
AVIO_AUD_SUM FAR *pAud;
VIDEO_SYNC *pSync;
if (!*pFileSpec)
return DispErr("OpenAvioFile", "No file spec");
// Clear out the Avio structure.
_fmemset((char FAR *)&Avio, 0, sizeof(Avio));
// Initialize the structure.
Avio.SumHdrSize = sizeof(AVIO_SUM_HDR);
Avio.VidSumSize = sizeof(AVIO_VID_SUM);
Avio.AudSumSize = sizeof(AVIO_AUD_SUM);
Avio.StrmCnt = 2;
Avio.VidCnt = 1;
Avio.AudCnt = 1;
if ((AvioRet = AvioFileAlloc((AVIO_SUM_HDR FAR *)&Avio)) < 0)
return DispAvioErr("AvioFileAlloc");
// Fill out the video stream substructure.
pSync = &Syncs[CaptureSync]; // sync data (NTSC or PAL)
pVid = Avio.VidStrms;
pVid->StrmNum = 0; // video stream number
pVid->Type = AVL_T_CIM; // compressed data
pVid->SubType = AVL_ST_YVU; // packed data
pVid->StillPeriod = AVL_CIM_RANDOM_STILL; // freq of still frames
pVid->xRes = pSync->xResVid << 1; // x resolution
pVid->yRes = pSync->yResVid; // y resolution
pVid->BitmapFormat = AVK_BM_9; // bitmap format
pVid->FrameRate = pSync->FrameRate; // frame rate
pVid->PixelAspect = pSync->PixelAspect; // NTSC aspect ratio
pVid->AlgCnt = 1; // only one algorithm
pVid->AlgName[0] = AVK_RTV_2_0; // RTV 2.0 compression alg
// Fill out the audio stream substructure.
pAud = Avio.AudStrms;
pAud->StrmNum = 1; // audio stream number
pAud->LeftVol = 100; // left channel volume = 100%
pAud->RightVol = 100; // right channel volume = 100%
pAud->FrameRate = pSync->FrameRate; // frame rate
pAud->SamplesPerSecond = AUD_SAMPLE_RATE; // audio samples-per-second
pAud->AudChannel = AVK_AUD_MIX; // both speakers
pAud->AlgCnt = 1; // number of algorithms
pAud->AlgName[0] = AVK_ADPCM4; // audio ADPCM4 algorithm
// Now create the file with all standard AVSS headers.
if ((AvioRet = AvioFileCreate((char far *)pFileSpec,
(AVIO_SUM_HDR FAR *)&Avio, OF_CREATE)) < 0)
return DispAvioErr("AvioFileCreate");
bAvioFileExists = TRUE;
return TRUE;
}
// This function retrieves frames from the Group Buffers
// in VRAM and writes them out to an AVSS file on disk.
BOOL CaptureAvioData()
{
static BOOL bInUse = FALSE;
AVIO_FRM_HDR FAR *pFrmHdr[2]; // frame header pointers
// for video & audio
BOOL bDataRead;
int Ret;
U32 VidFrmSize, AudFrmSize;
WORD Count;
if (bInUse)
return TRUE;
bInUse = TRUE;
// Error if no buffers have been allocated.
if (!Vid.pBufHead || !Aud.pBufHead)
return DispErr("CaptureAvioData",
"NULL host RAM buffer pointer");
Count = CAPTURE_LOOPS;
do {
// Init the data-read flag
bDataRead = FALSE;
if (!Vid.BufDataCnt)
{
if (!ReadGrpBuf(&Vid, &bDataRead))
return FALSE;
}
if (!Aud.BufDataCnt)
{
if (!ReadGrpBuf(&Aud, &bDataRead))
return FALSE;
}
while (Vid.BufDataCnt && Aud.BufDataCnt)
{
pFrmHdr[0] = (AVIO_FRM_HDR FAR *)Vid.pBufCurr;
pFrmHdr[1] = (AVIO_FRM_HDR FAR *)Aud.pBufCurr;
if ((Ret = AvioFileFrmWrite((AVIO_SUM_HDR FAR *)&Avio,
pFrmHdr)) < 0)
return DispAvioErr("AvioFileFrmWrite");
VidFrmSize = (U32)sizeof(AVIO_FRM_HDR)
+ pFrmHdr[0]->StrmSize[0];
Vid.pBufCurr += (WORD)VidFrmSize;
Vid.BufDataCnt -= VidFrmSize;
AudFrmSize = (U32)sizeof(AVIO_FRM_HDR)
+ pFrmHdr[1]->StrmSize[0];
Aud.pBufCurr += (WORD)AudFrmSize;
Aud.BufDataCnt -= AudFrmSize;
}
} while (bDataRead && Count--);
bInUse = FALSE;
return TRUE;
}
// Read newly captured frames from an AVK Group Buffer
// into one of the application's host RAM buffers
static BOOL ReadGrpBuf(CAPT *pCapt, BOOL *pbDataRead)
{
// Only refill the buffer if it is empty
if (!pCapt->BufDataCnt)
{
// Retrieve a buffer of frames.
if ((AvkRet = AvkGrpBufRead(pCapt->hGrpBuf, HOST_BUF_SIZE,
pCapt->pBufHead, &pCapt->BufDataCnt, AVK_ENABLE)) != OK)
return DispAvkErr(AvkRet, "AvkGrpBufRead");
// Set data-read flag if we read any data.
*pbDataRead = pCapt->BufDataCnt == (U32)0 ? FALSE : TRUE;
// Point back to start of buffer.
pCapt->pBufCurr = pCapt->pBufHead;
}
return TRUE;
}
// Update and lose an AVSS file using AVKIO.
BOOL CloseAvioFile()
{
if (bAvioFileExists == TRUE)
{
// Update the file's header with current information that
// AVKIO keeps in the Avio summary header.
if ((AvioRet = AvioFileUpdate((AVIO_SUM_HDR FAR *)&Avio, 0)) < 0)
return DispAvioErr("AvioFileUpdate");
// Close the file.
if ((AvioRet = AvioFileClose((AVIO_SUM_HDR FAR *)&Avio)) < 0)
return DispAvioErr("AvioFileClose");
bAvioFileExists = FALSE;
}
return TRUE;
}
James L. Green - James is a senior software engineer at Intel's multimedia
and supercomputing components group in Princeton, New
Jersey. He is one of the principal architects of the audio
video kernel and is a member of the Interactive Multimedia
Association's technical working group on multimedia
software architectures. You can reach him through the DDJ
offices.
|