Notes on Video Input for O2 Systems (mvp)

By Michael Portuesi. Thanks to Mike Travis for a great deal of the information herein.

Introduction

The built-in video for the O2 system (known to VL programmers as "mvp", for "MACE Video Ports") provides a new set of exciting challenges for the VL programmer.

Though most code written for VINO and previous video-to-memory based machines should work with little or no porting effort, there are some subtle but important differences you must know in order to write an effective MVP program.

A few of the most important enhancements (compared with VINO) are:

MVP Input Nodes

MVP provides several video inputs, only some of which are accessible depending on how the hardware is configured. There are two versions of the A/V board available. The regular A/V board provides video input and output jacks, a digital camera connector, and analog audio I/O. The audio-only board, or "A" board, provides the audio connectors, but none of the video I/O connectors.

Here's an excerpt from vlinfo listing all of the possible input nodes. This exhaustive list was produced by setting the environment variable DEBUG_MVP before invoking vlinfo:

SVideo Input, type = Source, kind = Video, number = 3
Composite Video Input, type = Source, kind = Video, number = 2
Camera Video Input, type = Source, kind = Video, number = 1
Digital Video Input, type = Source, kind = Video, number = 4
Primary D1 Input, type = Source, kind = Video, number = 5
Secondary D1 Input, type = Source, kind = Video, number = 6
Loopback E Video Input, type = Source, kind = Video, number = 7
Loopback F Video Input, type = Source, kind = Video, number = 8
Screen Capture Input, type = Source, kind = Screen, number = 0

Programming the MVP input jacks

The biggest improvement regarding the input jacks on MVP, compared with other SGI video boards, is that you do not have to set any controls, such as VL_MUXSWITCH, in order to unambiguously specify an input jack. Every node number in the list returned by vlGetDeviceList() uniquely identifies a jack. All you have to do is provide the desired node number to vlGetNode(), and pass the returned node to vlCreatePath().

However, (and you knew there was a however), the MVP driver dynamically builds the list of active nodes at boot time. The driver leaves out nodes for inputs which are not present, such as an unplugged camera.

In order to fully support video jacks, your application must check the node list at runtime. Furthermore, the driver does not guarantee that node numbers are constant from one invocation of your program to the next (though this does seem to be the case, looking at vlinfo's output).

Three node numbers - Camera, Composite Input, and SVideo - are predefined in the include file <dmedia/vl_mvp.h> as follows:

#define VL_MVP_VIDEO_SOURCE_CAMERA 1
#define VL_MVP_VIDEO_SOURCE_COMPOSITE 2
#define VL_MVP_VIDEO_SOURCE_SVIDEO 3

Furthermore, these node numbers will not change from one invocation of your program to the next. However, you still must query the device list to be certain the jacks actually exist. If the user unplugs the camera, or if the system contains an audio-only board, these jacks will not be accessible.

Here is some example code to check the node list for the presence of the above jacks:

static int validnodes[8];
static int validnodecount = 0;
static VLDev device = -1;

void checkMVPNodes()
{
    //
    // Open a connection to VL.
    //
    VLServer svr = vlOpenVideo("");
    if (svr == NULL) goto cleanup;

    //
    // get the device list, and search for number of the MVP device.
    //
    // Note that the number we find is *not* the one you hand to
    // vlCreatePath().  It is merely an array index into the device
    // list returned by vlGetDeviceList().
    //
    VLDevList devlist;
    if ( vlGetDeviceList(svr, &devlist) < 0)  goto cleanup;

    int deviceID = -1;
    for (i = 0; i < devlist.numDevices; i++) {
       if (strcmp(devlist.devices[i].name, "mvp") == 0) {
           deviceID = i;
           break;
       }
    }
    if (deviceID == -1) goto cleanup;  // MVP does not exist on this system

    //
    // The number you need to unambiguously specify the MVP device
    // for vlCreatePath() may be accessed thus:
    //
    device = devlist.devices[deviceID].dev;
    //

    //
    // Now, look through the nodelist for the MVP device and see
    // if the node number for the camera is there.  If it is, make note
    // of it.
    //
    // We need to know if the camera is present in advance, so that we
    // can ignore the node number for the digital node if it (mistakenly)
    // appears in the list.
    //
    for (i = 0; i < devlist.devices[deviceID].numNodes; i++) {
       if ((devlist.devices[deviceID].nodes[i].type == VL_SRC) &&
           (devlist.devices[deviceID].nodes[i].kind == VL_VIDEO) &&
           (devlist.devices[deviceID].nodes[i].number ==
                   VL_MVP_VIDEO_SOURCE_CAMERA) ) {
           cameraPresent = True;
           // save the node number
           validnodes[validnodecount++] =
               devlist.devices[deviceID].nodes[i].number;
           
           // ... do whatever you might want to do here
           
           break;
       }
    }

    //
    // Now, proceed through the MVP node list a second time
    // to handle the non-camera nodes.
    //
    for (i = 0; i < devlist.devices[deviceID].numNodes; i++) {
       if ((devlist.devices[deviceID].nodes[i].type == VL_SRC) &&
           (devlist.devices[deviceID].nodes[i].kind == VL_VIDEO)) {

           char* nodename = devlist.devices[deviceID].nodes[i].name;
           int nodenumber = devlist.devices[deviceID].nodes[i].number;

           if (nodenumber == VL_MVP_VIDEO_SOURCE_SVIDEO) {
               //
               // the SVideo jack exists on the machine.  Do something
               // interesting with this info.  Here, we store the node
               // number into an array.
               //
               validnodes[validnodecount++] = nodenumber;

               // ...
               //
           } else
           if (nodenumber == VL_MVP_VIDEO_SOURCE_COMPOSITE) {
               validnodes[validnodecount++] = nodenumber;
               // ...
           } else
           //
           // For the paths which do not have #defines to describe them,
           // we must look at the name of each jack.
           //
           if (strstr(nodename, "Digital") != NULL) {
               //
               // the digital node is meaningful only if the
               // camera is not present.  They use the same jack
               // on the A/V board.
               //
               if (!cameraPresent) {
                   validnodes[validnodecount++] = nodenumber;
                   // ...
               }
           } else
           if (strstr(nodename, "Primary D1") != NULL) {
               validnodes[validnodecount++] = nodenumber;
               // ...
           } else
           if (strstr(nodename, "Secondary D1") != NULL) {
               validnodes[validnodecount++] = nodenumber;
               // ...
           } else
           if (strstr(nodename, "Loopback E") != NULL) {
               // The loopback E node will always be present.
               validnodes[validnodecount++] = nodenumber;
               // ...
           }
           if (strstr(nodename, "Loopback F") != NULL) {
               // The loopback F node will always be present.
               validnodes[validnodecount++] = nodenumber;
               // ...
           }
       }
    }

    //
    // Now, the validnodes array contains a list of all the valid
    // video input nodes, and validnodecount contains the number
    // of entries on the list.
    //

    vlCloseVideo(svr);
}

The loopback nodes are always present, even when system contains an audio-only board. Even on an audio-only system, it is possible to set up a loopback transfer from the video out. Though there is no physical video out jack, the loopback transfer will work.

You can query the default input jack much the same way as on previous boards, by setting up a path on the device and querying the appropriate control. However, vcp can store a default input node which may not actually exist. For example, if you set the default input to the camera, unplug the camera, and reboot the system, a query for the default input jack will still return VL_MVP_VIDEO_SOURCE_CAMERA even though it is not in the node list.

Unfortunately, you must deal with this fact if you query the default input node. Here's some code to do just that:

int getDefaultInputSource()
{
    VLNode devNode;
    VLPath controlPath;
    VLControlValue val;
    int errorVL = 0;
    int defaultSrcNumber = 0;
    
    //
    // we assume the a connection to the server has been opened,
    // and the MVP device is known.
    //
    assert(svr != NULL);
    assert(device >= 0);
    
    //
    // also, we assume that we have previously queried the list
    // of valid input jacks.
    //
    assert(validnodescount > 0);
    
    // note that these calls lack error checking.
    
    devNode = vlGetNode(svr, VL_DEVICE, 0, VL_ANY);
    controlPath = vlCreatePath( svr, device, devNode, devNode );
    vlSetupPaths(svr, &controlPath, 1, VL_SHARE, VL_READ_ONLY);
    val.intVal = 0;
    vlGetControl(svr, controlPath, devNode, VL_DEFAULT_SOURCE, &val );

    defaultSrcNumber = val.intVal;

    vlDestroyPath(svr, controlPath);
       
    //
    // Match the default source node number against our table of
    // valid nodes.  If it's not on the list, then choose the
    // first entry on the list as the default.
    //
    // This works around a deficiency in the device driver.  The
    // default node can be the camera, even if the camera is not
    // present.  This is because the device driver dynamically
    // updates the node table when it starts, but it neglects to
    // update the other system defaults.
    //
    Boolean found = False;
    for (int i = 0; i < validnodescount; i++) {
       if (defaultSrcNumber == validnodes[i]) {
           found = True;
       }
    }
    if (!found) {
       defaultSrcNumber = validnodes[0];
    }

    return defaultSrcNumber;
}

Programming Video To Memory Transfer with MVP

The native RGB color space supported by the MVP device is VL_PACKING_ABGR_8 (which is actually packed in memory as RGBA). This is the same packing as that expected by OpenGL. This is all fine for screen display, but if you are porting older, IRIS GL-based VL code written for VINO, you might wish to take this into account.

Also, if you are performing image file writes with the ImageVision Library or the Image File Library, you will need to swap the pixels into ABGR before writing them to the file. This is easily accomplished using the dmColor routines in libdmedia (see the man page for dmColor(3dm).

After your video signal has been pre-empted, you will receive a VLStreamAvailable event when a transfer path becomes available. You should simply be able to call vlSetupPaths() followed by vlBeginTransfer() to pick up where you left off.

Responding to Video Timing Changes

Unlike VINO, the MVP device will send you a VLControlChanged event of type VL_TIMING if the video timing changes on an input node which you have open for transfer. For example, if the user changes the timing with vcp, you will receive a VLControlChanged event.

Unfortunately, it is difficult to simply end the transfer and restart it, because the transfer buffers need to be reconfigured to accept a possibly larger buffer size. In this case, it is probably best to end the transfer, create a new set of buffers, register the new buffers, and restart the transfer. Or do what I do, and simply tear down the entire path and the buffers, and create a new path and new buffer pool anew.

Dealing With the O2 Camera

The O2 camera plugs into the connector on the back of the A/V board. It has a built-in microphone. To access the camera microphone, you must use the Audio Library.

The camera supports only NTSC sized video, at NTSC rates, with square pixels. Conversion to Rec. 601 pixel format is not available. The height of a full frame (field pair) captured from the camera is 480 pixels, as opposed to 486 pixels from the other inputs. This has implications if you are writing code to manipulate or compress the data. In particular, you may wish to adjust the VL_SIZE and/or the VL_OFFSET controls to get data with the size and blanking area you desire.

When you query the timing on a path from the camera, VL will return VL_TIMING_525_4FSC. It is unknown why the camera behaves this way. The 4FSC timings are intended for a D2 composite signal, which is not supported by MVP and has a signal, sample rate, and pixel dimensions much different from what the camera provides. It is interesting to note, however, that the IndyCam on VINO-equipped systems behaves the same way, so this is apparently some nod to backwards compatibility on the part of MVP.

The O2 Camera Shutter Button

The camera also has a shutter button, which is accessed from VL as a GPI trigger. You get a VLDeviceEvent whenever the user presses it. You can determine only that the button was down-clicked; no event is sent on an up-click. Therefore, you cannot determine how long the button is held down.

Here's some example code to make it work:

void setupMyPath()
{
    // ... other path setup

    vlSelectEvents(svr, displayPath,

                  // ... the events you might be interested in will
                  // undoubtedly vary from this example.

                  VLDefaultSourceMask |
                  VLStreamPreemptedMask |
                  VLStreamAvailableMask |
                  VLControlChangedMask |
                  VLControlPreemptedMask |
                  VLControlAvailableMask |
                  VLDeviceEventMask |          // camera shutter button
                  VLTransferFailedMask
                  ) );
}

void dispatchVLEvent()
{
    VLEvent ev;

    while (vlPending(obj->svr)) {
       vlNextEvent(obj->svr, &ev);

       switch (ev.reason) {

       // ...  process other VL events here
       
       case VLDeviceEvent:
           // ... code to handle shutter button here
           break;
           
       // ...
       
       }
    }
}

Because the microphone is an integral part of the camera, you may want to use the shutter button as a trigger in audio-only applications. Unfortunately, you still need to program VL to access the button. Further, you must have a video path open in order to receive the VLDeviceEvent.