Hints for Vid-to-Mem Applications
By Nelson Bolyard.
Updated for mvp by Chris Pirazzi.
This document supplements the material in the "IRIS Media Libraries Programming Guide" (hereinafter, "the Guide"). It adds information about the VL that I think is missing or wrong in the Guide. It doesn't attempt to answer all remaining questions about the VL, or to be a complete reference. This is merely the information that I figured out while making InPerson work on vino, ev1, ev3, and sirius video for IRIX 5.3 and 6.2. Chris updated this document for mvp and IRIX 6.3. This information applies only to those VL video devices, and not to any other present or future VL video devices (it does not yet completely reflect cosmo2 and divo, for example).
This document does not represent SGI, and is not an official SGI publication. The entire contents of this document are my personal observations, and/or opinions, and all errors and opinions in it are mine alone. SGI makes no guarantees about anything in this document. Neither do I. You use it at your own risk. Additional disclaimers below.
Some topics NOT discussed in this page include:
- Selection of input jack using VL_FORMAT or VL_MUXSWITCH.
Follow this link to a page on that topic: Choosing an Input Jack
- 1 Video source node controls
- 2 Memory drain node controls
- 3 Hints about VL Events
- 4 Other Miscellany
Video source node controls
The VL_FORMAT control on the video source node is usually set using the Video Control Panel. It is often of no concern to a vid-to-mem application, except with sirius video, where it is used to determine color space conversion. See the discussion of the VL_PACKING and VL_FORMAT control on the memory drain node (below) for more details.
The VL_TIMING control on the video source node is usually set using the Video Control Panel. It is often read by vid-to-mem apps to determine if the input source timing is PAL or NTSC, and if the pixels will be square or not. It also affects the value returned by the VL_SIZE control on the video source node. The knowledge of PAL vs. NTSC timing is useful to the vid-to-mem app for setting the VL_RATE control on the memory drain node. For sirius Video, it is also used to determine the value for the VL_TIMING control on the memory drain node. See the discussion of the VL_TIMING control on the memory drain node (below) for more details.
There are two separate VL_SIZE controls that a vid-to-mem app must deal with. One is a control on the video node, the other is on the memory node. They have different meanings, different units, and do different things. The most common problem in discussing the VL_SIZE control is people forget to specify which one they're talking about, or are confused about that. This section discusses the VL_SIZE control on the video source
The VL_SIZE control on the video source node is a read-only control. The x and y values returned by this control are affected by the setting of the VL_TIMING control on the video source node. The x and y values of this control are not, in general, affected by the settings of any controls in the memory drain node, including VL_ZOOM, VL_SIZE, and VL_CAP_TYPE.
The x component value of this control reveals the width, in pixels, of
the unzoomed, unclipped video input images (fields or frames). The
meaning of the y component value of the video source node's VL_SIZE
control depends on the video device.
- On sirius, the y value is the number of pixel rows in each field, and includes all 243 (525-line) or 288 (625-line) active lines per field found in Definitions: F1/F2, Interleave, Field Dominance, and More.
- On ev1, ev3, mvp, and vino, the y value is the number of pixel rows in each frame (pair of fields). The y value is 480 or 486 for 525-line video (depends on device and input jack!) and 576 for 625-line video.
Memory drain node controls
VL_PACKING (plus VL_FORMAT or VL_COLORSPACE)
A vid-to-mem app chooses:
- a colorspace, which determines which components (R, G, B, A, Y, V/Cr, U/Cb, etc.) make up each pixel, and the canonical maximum and minimum value of each of those components.
- a packing, which determines how each of those components is laid out in memory.
On all devices except sirius and divo, an application specifies both
of these with VL_PACKING on the memory node.
On sirius, an application uses VL_PACKING on the memory node to specify the packing, and VL_FORMAT on the memory node to specify colorspace. If necessary, sirius will do colorspace conversion. Sirius is the only device where VL_FORMAT even applies to memory node.
On divo, an application uses VL_PACKING on the memory node to specify the packing, and VL_COLORSPACE on the memory node to specify colorspace. If necessary, divo will do colorspace conversion. This is a new API which should be inherited by all new VL devices from divo and beyond.
For complete information on pixel packings and colorspaces, check out The Pixel Rosetta Stone: Packings and Colorspaces.
The VL_PACKING_YVYU_422_8 packing is the only packing that is natively supported in hardware (requiring no software conversion) on all VL video devices. The packing always uses 4:2:2 sampling, and the default colorspace for this packing is always headroom-range. See The Pixel Rosetta Stone: Packings and Colorspaces for definitions of these terms.
On all VL video devices except sirius, the VL_TIMING control applies only to the video (not memory) nodes. The timing on the video source node is usually set through the Video Control Panel. With sirius video, the VL_TIMING control must be set properly on the memory drain node. An easy way to set the VL_TIMING value for the memory node is to read the value of the VL_TIMING control from the video source node, and then set that value into the VL_TIMING control for the memory
On all VL video devices except sirius, this control is settable by the application. Its setting determines whether the images in the buffers returned by the VL are individual fields, or interleaved frames, or
pairs of non-interleaved fields.
On sirius, this control is read-only, and is permanently set to VL_CAPTURE_NONINTERLEAVED. Each captured buffer contains exactly one field, unclipped, unzoomed.
The possible values are:
On all devices except ev1, this setting of VL_CAP_TYPE will give you one field in each entry of a VLBuffer. The data will always begin with a dominant field (F1 unless otherwise specified by the video device) and it will always alternate between F1 and F2 (see Definitions: F1/F2, Interleave, Field Dominance, and More for definitions of dominance, F1, and F2). If the device has to drop some data, it will drop both an F1 and an F2 (in some order), so that the .../F1/F2/F1/F2/... ordering is maintained. VL_RATE is in fields per second.
On ev1, each VLBuffer entry contains two fields butted up against one another in memory, with the F1 field first in memory and then the F2 field. VL_RATE is in frames per second.
On devices which support it, this setting of VL_CAP_TYPE gives you pairs of fields which have been weaved together in memory to form a complete frame (television picture) from top to bottom. The F1 and F2 fields are placed according to the rules for the video standard being used, as described in <a href="dominance.html">Definitions: F1/F2, Interleave, Field Dominance, and More.</a> VL_RATE is in frames per second.
This setting is like VL_CAPTURE_NONINTERLEAVED except you only get F1 fields. VL_RATE is in frames per second.
This setting is like VL_CAPTURE_NONINTERLEAVED except you only get F2 fields. VL_RATE is in frames per second.
Vino has a bug that affects capturing with VL_CAPTURE_INTERLEAVED. The first buffer returned after the call to vlBeginTransfer() may have only one field in it. The other field may contain all zero values. For continuous transfers, this may go unnoticed, but for VL_TRANSFER_MODE_DISCRETE captures (e.g. of a single image), this will often result in an image in which every other line is black, or green, depending on the color space in use. A missing field can be detected by examining the first pixel or two in several successive pixel rows. The workaround for this problem is to examine the image for the presence of one black field, and one non-black field, and recapturing another buffer if one field is black. Another solution worth exploring is to capture two buffers, instead of one, and then throw
the first one away. This might suffice.
There is no single VL_CAP_TYPE that is available, and implemented in the same way, on all VL video devices. VL_CAPTURE_NONINTERLEAVED is available on all devices, but has different meanings on different platforms. VL_CAPTURE_INTERLEAVED, VL_CAPTURE_EVEN_FIELDS, and VL_CAPTURE_ODD_FIELDS are available and common to all VL video devices except sirius.
In the section entitled "Setting Drain Node Controls for Data Transfer", the Guide states, incorrectly, that the VL_RATE control is "not applicable" to memory drain nodes. For vid-to-mem applications,
the memory drain node is precisely where video rate is controlled.
On all VL video devices except sirius, this control sets the target rate (upper bound) of image buffers per second that will be captured and returned to the application. That is, the VL will not deliver more buffers per second than the rate you specify, but it might deliver less than you specify, especially on ev1.
VL_RATE is in units of buffers per second. That may be frames or fields per second depending on whether each buffer entry has a frame or field in it, which is determined by VL_CAP_TYPE. See VL_CAP_TYPE above for more info.
VL_RATE is a fraction. Both the numerator and denominator must be specified. The usual value for the denominator is 1. Some devices, such as ev1, internally convert the fraction to a whole integer (truncating, not rounding) of images per second, so using values that are equivalent to integer values is the safest thing to do.
Because VL_RATE is a fraction, vlGetControlInfo() cannot be used to obtain the minimum or maximum values for VL_RATE.
Acceptable values are determined from the following table for non-sirius devices:
(all devices except ev1 and sirius)
|525||All multiples of 10 and 12 between 10 and 60|
|625||All multiples of 10 between 10 and 50|
|525||All multiples of 5 and 6 between 5 and 30|
|625||All multiples of 5 between 5 and 25|
VL_CAPTURE_INTERLEAVED, VL_CAPTURE_EVEN_FIELDS, and VL_CAPTURE_ODD_FIELDS
|525||All multiples of 5 and 6 between 5 and 30|
|625||All multiples of 5 between 5 and 25|
With sirius video, this control is read-only. Its value is determined by
the setting of the VL_TIMING control on the memory node:
|VL_CAPTURE_NONINTERLEAVED||525||60 fields per second|
|625||50 fields per second|
Vino's VL_RATE cannot be set to a value less than 5/1.
The table of acceptable values for VL_ZOOM in the Guide is incorrect. Below is a corrected table of acceptable values for vid-to-mem applications. The only value of VL_ZOOM that works on all VL devices
Vino has hardware bugs that affect decimation of images at these decimation factors: 1/4, 1/5, 1/6, 1/7, and 1/8. These bugs include:
- Y values that are not adjacent horizontally are averaged together.
- the decimated images appear extremely green.
To work around these bugs, the vino driver implements decimation by 1/4 and 1/6 by decimating in hardware by 1/2 or 1/3, and then decimates by an additional factor of 1/2 in software. This produces acceptable looking images, but at significant cost in CPU time. The three other VL_ZOOM factors, 1/5, 1/7, and 1/8 all exhibit the green
image problem described above.
|1/1, 1/2, 1/3||Implemented in hardware, looks OK.|
|1/4, 1/6||Implemented partially in hardware, partially in software. Looks OK, but slower and uses roughly 10% of an R4600 CPU.|
|1/5, 1/7, 1/8||implemented in hardware. Looks bad. Green shift.|
|1/1, 1/2, 1/4, 1/8||Works OK for vid-to-mem|
|1/3, 1/5, 1/7||Works only for vid-to-screen, not vid-to-mem, and only with VL_CAPTURE_INTERLEAVED.|
|2/1, 4/1||Works only for vid-to-screen, not vid-to-mem.|
sirius and ev3:
|1/1||sirius and ev3 don't zoom.|
VL_SIZE and VL_OFFSET
There are two separate VL_SIZE controls that a vid-to-mem app must deal with. One is a control on the video node, the other is on the memory node. They have different meanings, different units, and do different things. The most common problem in discussing the VL_SIZE control is people forget to specify which one they're talking about, or are confused about that. This section discusses the VL_SIZE
control and the VL_OFFSET control on the memory drain node.
The VL_SIZE control on the memory drain node determines the number of rows of pixels, and the number of pixels in each row, in each image buffer (field or frame) that the VL returns to the application. If zooming (decimation) is being done, the VL_SIZE control on the memory drain node specifies the size of the image after it has been decimated.
The VL_SIZE control on the memory drain node can be used to "clip" a region out of an image by setting the X and/or Y components to values that are smaller than the size of the captured (and decimated, if applicable) image.
When the (possibly decimated) image is being clipped, the clipped region does not have to come from the upper left hand corner of the (possibly decimated) source image. The VL_OFFSET control on the memory drain node determines the number of top pixel rows to skip and the number of leading pixels to skip in each row to find the first pixel in the (possibly decimated) image to place in the image buffer, the first pixel of the clipping region.
The Guide says, incorrectly, that "VL_OFFSET operates on the unzoomed image; it does not change if the zoom factor is changed.". The truth is that when zooming (decimation) is being used, VL_OFFSET is always in coordinates of the zoomed image. It is as if the entire source image is decimated down, and then the clipping function is applied to the decimated image. The hardware rarely works that way, and usually clips before decimating, but the VL API always specifies the VL_OFFSET in the coordinates of the decimated (virtual) image.
On all VL devices except sirius, the vertical (Y) component of VL_OFFSET may be specified with a negative value. This causes the clipping region to include row of samples taken before the top of the image, e.g. rows from the vertical blanking interval. This feature is usually used with VL_ZOOM of 1/1, since the information in the vertical blanking interval is generally destroyed by a zooming operation.
Because VL_OFFSET is useful for parsing VITC and other vertical interval information, it is useful to know which video line you get at the top of your F1 and F2 buffers for a particular Y value of VL_OFFSET, when you have selected VL_CAPTURE_NONINTERLEAVED and VL_ZOOM=1/1. A VL_OFFSET Y value of 0 gives you F1 and F2 buffers which begin on the following video lines:
|Video Standard||vino F1||vino F2||ev1 F1||ev1 F2||mvp F1||mvp F2||ev3 F1||ev3 F2|
This table uses the line numbers and definitions from Definitions: F1/F2, Interleave, Field Dominance, and More. Note that all the cases except for 525-line F2 fields with vino conform to the diagrams "analog 525, practical digital 525,
analog 625, and digital 625" shown in that document.
On vino, ev1, mvp, and ev3, any other VL_OFFSET Y value (positive or negative) slides your buffer up or down the video signal by that many lines. The minimum allowed Y value is device-dependent, but is negative enough to pick up VITC on at least the higher of the recommended lines found in ANSI/SMPTE 12M.
On ev1, when you select VL_CAPTURE_NONINTERLEAVED, each of your buffers contains an F1 and an F2 field butted together. The Y coordinates of VL_OFFSET have strange units which behave like "picture lines" even though this doesn't make sense. Basically, if you would like to shift N lines, set VL_OFFSET's Y coordinate to 2*N.
This document does not cover what ev3's units are because we haven't tested it yet.
The VL imposes these requirements on the values of VL_OFFSET and VL_SIZE:
- The sum of the vertical components of VL_OFFSET and VL_SIZE must not exceed the height of the virtual (zoomed) image, and
- The sum of the horizontal components of VL_OFFSET and VL_SIZE must not exceed the width of the virtual (zoomed) image.
When an attempt to set either one of these controls would violate either of the rules above, the call to vlSetControl() fails with the vlErrno VLValueOutOfRange, and the offending component (horizontal or vertical) is set to the largest non-negative value that does not violate the rule, or to zero if no such non-negative value exists.
VL_OFFSET and VL_SIZE cannot be both set in one atomic operation. A change in either component of either control could violate one of the rules above (or below), especially after VL_ZOOM is set to a smaller fraction. It may be necessary to alternately and repeatedly set VL_OFFSET and VL_SIZE until no VLValueOutOfRange errors are reported. vlSetControlLoop.txt Follow this link to see a code sample that does this.
Every VL video device places additional limitations on the range of acceptable values of VL_SIZE and VL_OFFSET. Each device has different limitations.
sirius doesn't clip at all. VL_SIZE and VL_OFFSET are read-only in sirius.
- ev1 supports clipping only in the vertical (Y) direction. The
entire width of the (possibly decimated) image is always placed in the
image buffer. Application-specified horizontal clipping values are
An interesting bug: After setting VL_SIZE and/or VL_OFFSET on an ev1 memory drain node, the vertical value read back from the VL_SIZE control is often (always?) one less than the value set into the VL_SIZE control, indicating that the image buffers will contain one less row of pixels than were requested. In this case, the actual size of the buffer returned by ev1 will usually contain the requested number of rows, one more than the value returned in the VL_SIZE control.
- vino imposes the following clipping requirements:
- The right side edge of the clipped image must always coincide with the right side edge of the virtual (possibly decimated) image. That is, the clipped image must always come from the right side of the (possibly decimated) source image. Consequently, when vlSetControl is called to set the VL_OFFSET or VL_SIZE control on a memory node, if the sum of the horizontal components of the (new) settings of VL_OFFSET and VL_SIZE is less than the width of the virtual (zoomed) image, the vlSetControl call will succeed, and the horizontal component of the other control will be adjusted so that the sum of the two components exactly equals the width of the virtual (zoomed) image. This is done only in the horizontal direction.
- Each pixel row in the image buffer must be a muiltiple of 8 bytes
in length. This means that the horizontal component of VL_SIZE must
be a multiple of 2, 4, or 8 pixels, depending on the pixel packing
(size of the individual pixels in memory).
- for ev3, each pixel row in the image buffer must be a multiple
of 8 bytes in length, as with vino.
- for mvp, each pixel row must be a multiple of 2 pixels in size
and a multiple of 8 bytes in size (whichever is stricter).
Hints about VL Events
If your application has setup a VL path with "shared" usage of controls and the data stream (e.g. vlSetupPaths(..., VL_SHARE, VL_SHARE), then whenever another application attempts to setup the same path as yours, or a path that shares nodes with your path, your program will be preempted. To handle preemption properly, your program must select VLStreamPreempted and VLStreamAvailable events in
its call to vlSelectEvents().
Sirius doesn't support preemption. ev1 ellows preemption in some "sync modes" but not in others. When preemption is not allowed, the VL behaves as if the path was in mode VL_LOCK instead of VL_SHARE; preemptions simply don't happen.
When preemption occurs, your program will receive a VlStreamPreempted event. This means the following things:
- vlEndTransfer() has been done on your path (the path is indicated in the event to help you determine which path was preempted, if you have more than one path in use).
- vlSetupPaths(..., VL_SHARE, VL_READ_ONLY) has been done on this path.
No immediate action is required on the part of a program that receives a VLStreamPreempted event. The transfer has been ended, so you don't need to call vlEndTransfer(). The ring buffer in use by your path at the time of its preemption is neither deregistered nor destroyed by the preemption. There may still be one or more image buffers available. Calls to vlGetNextValid() or vlGetLatestValid() may continue to return previously unprocessed images. Non-NULL values returned from vlGetNextValid() or vlGetLatestValid() should not be construed to mean that the transfer is still in progress. It is probably a good idea to process and free all image buffers that can be
obtained from calls to vlGetNextValid() or vlGetLatestValid().
When all the nodes of a path that is being held in VL_READ_ONLY mode are released, all programs holding the path in this mode will receive a VLStreamAvailable event. This will happen when the program that was last using the path called vlDestroyPath(), or released the path by setting the stream usage to VL_READ_ONLY or VL_DONE_USING in a call to
The VLStreamAvailable events create a race between the preempted programs. The outcome of the race depends on the actions taken by the programs, and also on the device's implementation. Generally, as long as all the programs are attempting shared use of the path, the last caller of vlSetupPaths() wins.
When a program receives the VLStreamAvailable event, to regain the usage of the path's data stream, it should call vlSetupPaths(..., VL_SHARE, VL_SHARE) again. This call to vlSetupPaths() to regain access to the path's data stream can succeed or it can fail. The VLStreamAvailable event does not guarantee that it will succeed. Let's look at these cases separately.
If the call to vlSetupPaths() succeeds, the application should set all the controls for the path to the values they had before it was preempted. Your application, that is now regaining the use of the stream, should not assume that the controls are as they were before the preemption. The preemption caused the usage of the path's controls to become VL_SHARE, so the preempting application could have changed any or all of those controls.
Since preemption does not deregister or destroy the ring buffer used by your path, you may resume usage of that same buffer, provided that all the path's controls are returned to the same values they had before the preemption (especially VL_SIZE and VL_PACKING). However, you might want to be doubly sure that the ring buffer is empty before restarting the transfer with a call to vlBeginTransfer().
If the call to vlSetupPaths() to regain access to the path's data stream fails (returns a negative value), the path should remain in the same mode in which it was before the failed call to vlSetupPaths(). If your program hasn't changed the path since it was preempted, and if the call to vlSetupPaths() failed with vl error VLPathInUse, the path should still be in the mode in which it was placed by the original preemption, namely as if vlSetupPaths(..., VL_SHARE, VL_READ_ONLY) had been done on this path. The path should continue to behave exactly as when it was preempted, and another VLStreamAvailable should be generated, if and when the path becomes available again.
Each video device may have multiple video source nodes, each of which serves one or more physical input jacks on the video board. The VL supports the concept of a "default input source node" or "default video source node" for each device. There is no system-wide default input source node, nor is there a default input device. An application on a system with multiple video devices must choose one of
the devices according to its own methods.
A VL application that wishes to use the default input source node on a given device calls vlGetNode(vlServer, VL_SRC, VL_VIDEO, VL_ANY).
Each device's default input source node may be changed via a control. The Video Control Panel supports this, and is usually used to make this choice. When the default input source node on any VL video device is changed, (e.g. between a digital input source node and an analog input source node), the VL sends a VLDefaultSource event to all programs that have selected this event, regardless of whether they are using the device whose default input changed, or another VL device.
Each video source node may serve multiple jacks. The choice of active input jack on a given source node is done with some VL controls, VL_FORMAT or VL_MUXSWITCH, as described in Choosing an Input Jack. A change in input jack between jacks connected to the same video source node usually results in a VLControlChanged event, not a VLDefaultSource event.
If you wish your application to track the default input node, you must manually tear down and rebuild your video path each time you receive a VLDefaultSource event, like this:
- Stop any transfer in progress,
- Dismantle and destroy the path and ring buffer it is using,
- call vlGetNode(vlServer, VL_SRC, VL_VIDEO, VL_ANY) to get the node of the new default video source node,
- call vlGetNode(vlServer, VL_DRN, VL_MEM, VL_ANY) to get a new memory drain node.
- Rebuild the path,
- Create and register a new ring buffer,
- Reinitialize the controls, and
- Restart the transfer.
If you wish to track VL_FORMAT and VL_MUXSWITCH controls on a given node you are using, this may or may not happen automatically depending on the device. See Choosing an Input Jack.
More About vlSetupPaths()
If a first attempt to gain access to a path via vlSetupPaths(..., VL_SHARE, VL_SHARE) fails, and vlGetErrno() returns VLPathInUse, then it should be possible to call vlSetupPaths(..., VL_SHARE, VL_READ_ONLY) to put the path in the same state as it would be if it had been preempted. Likewise, a program that wishes to voluntarily release the path, and then later regain it, as if it had been
preempted should be able to put the path info VL_READ_ONLY mode.
While the path's stream usage is in VL_READ_ONLY mode, a VLStreamAvailable event should be generated when the data stream for the selected path becomes available. That is, a program should be able to put itself into a "voluntarily preempted" mode by putting the path's stream usage is in VL_READ_ONLY mode. Whether this actually works or not depends on the device, and on the OS release. It has worked on ev1 at one time. sirius does not support putting a path into VL_READ_ONLY mode. sirius also does not support preemption.
An application should be able to transition the stream usage of a path from any one to any other of the four modes (VL_SHARE, VL_READ_ONLY, VL_LOCK, VL_DONE_USING) as many times as it wishes. VL_DONE_USING is not the "mode of no return". The principal difference between VL_READ_ONLY and VL_DONE_USING seems to be the receipt of VLStreamAvailable events in the VL_READ_ONLY mode.
Since sirius doesn't support VL_READ_ONLY, an application cannot use VL_READ_ONLY to put itself into a mode where it will be notified when another device has finished with the path. A sirius app has no choice but to go to VL_DONE_USING mode when relenquishing the path, and to use some external or manual means to know when to attempt to reaquire the path.
Premption vs. Cooperative Management
Since pre-emption and VL_READ_ONLY modes don't work on all platforms, two or more programs that wish to cooperatively share the use of a VL device might be better off to use their own protocol to pass ownership of the device between them. The programs in the InPerson product used
this approach succesfully.
IndyCam on ev1 Video Devices
IndyCam works on many of the boards in the ev1 family, as explained in ev1 and cosmo1. The images produced by IndyCam on ev1 tend to be very noisy in low light, much more so than the images produced by IndyCam on vino. This noise is seen as vertical and horizontal lines of gray in the image. Since the output of IndyCam is digital, this noise is probably induced via
the power supplied to the IndyCam by the video board.
This product is intended for educational purposes only. Not responsible for direct, indirect, incidental or consequential damages resulting from any defect, error, or failure to perform. NO OTHER WARRANTY EXPRESSED OR IMPLIED. Reproduction strictly prohibited. The trademarks mentioned in this product appear for identification purposes only. Your mileage may vary. Not for resale. Any resemblance to real persons, living or dead, is purely coincidental. Void where prohibited. This is not an offer to sell securities. Some assembly required. Batteries not included. Not to be taken internally. Contents may settle during shipment. Freshest if eaten before date on carton. Use only as directed. Avoid contact with skin. Apply only to affected areas. Use only in well-ventilated area. Keep away from fire or flame. Do not use while operating a motor vehicle or heavy equipment. If condition persists, contact your physician. Contains a substantial amount of non-tobacco ingredients. Not recommended for children under five. May be too intense for some viewers. No passes accepted for this engagement. All models over 18 years of age. Times approximate. Simulated picture. Edited for television. Processed at location stamped in code at top of carton. No user-serviceable parts inside. Subject to change without notice. For recreational use only. For off-road use only. Subject to approval. One size fits all. Colors may fade. See label for washing instructions. For office use only. Post office will not deliver without postage. Penalty for private use. Substantial penalty for early withdrawl. Nor for removal except by the consumer. List was current at time of printing. Lost ticket pays maximum rate. At participating locations only. Your canceled check is your receipt. Slightly higher west of the Mississippi. Employees and their families not eligible. You must be present to win. Contestants have been briefed on some questions before the show. Limited time offer. No purchase necessary. Price does not include sales tax. Slippery when wet. Breaking seal constitutes acceptance of agreement. Some equipment shown is optional. Replace with same type. No anchovies unless otherwise specified. Driver does not carry cash.