By Michael Portuesi.
Because the VL supports so many different pieces of video hardware, you might be tempted to think that for a given task, such as displaying incoming video on the workstation screen, that you may write one piece of code for all the devices VL supports. Unfortnately you cannot, and nowhere is this more apparent than when you are programming video display using Galileo (ev1) and its relatives.
With most video devices that support a full rate video path to memory, you can create a video to memory path and display the video in your application using OpenGL. Your application wakes up whenever the transfer buffer is full, retrieves a frame from the buffer, and draws the frame to the display.
Galileo does not offer you this option; its video to memory path
does not support a full-rate stream into memory. Instead, it offers a
VL screen node. You create a video to screen path using the
VL, set some controls on the path to tell the screen node the X window
it should use and where the video should be located, then call
vlBeginTransfer()
to display the video to the user.
The code to display video using ev1 differs from other devices not only in the path setup, but in the maintenance work your application must perform. Using ev1, the display work is handled automatically; your application need never service a transfer buffer. But your application must keep the video positioned and sized appropriately, and update its position if the user moves the window. This task is the central topic of this document.
The ev1 video device (in all its forms) provides the programmer with three screen drain nodes. Here's how they are described by vlinfo:
lipstick.esd.sgi.com % vlinfo name of server: number of devices on server: 2 device: ev1 1 nodes = 14
[entries omitted]
Screen Drain A, type = Drain, kind = Screen, number = 0 Screen Drain B, type = Drain, kind = Screen, number = 1 Screen Drain C, type = Drain, kind = Screen, number = 2
[entries omitted]
However, in practical use, you will likely use only the first two nodes - numbers 0 and 1. Node 1 can be divided into two half-sized 12-bit screen displays. When you divide screen drain node 1, the second of the two half-displays is denoted by node number 2. This special "half size" mode is not discussed in this document, and judging from the restrictions on its use over and above those discussed here, it seems inappropriate at best for general-purpose application software.
By far, the biggest headache facing the ev1 programmer is simply positioning the video at the right place on the screen. The video drain doesn't render into the framebuffer, as does all other visual display on the system. Instead, the ev1 device provides a hardware-generated "overlay" which is merged with the contents of the frame buffer. As such, all attributes of the video display including screen position and size are controlled through the VL, rather than through X or OpenGL.
To make matters worse, the ev1 hardware imposes a raft of restrictions on where and and under what circumstances the video overlay can be positioned. None of them are particularly friendly to the application developer. Here is a list of the problems:
VL_ORIGIN
control immediately after it sets
it, and then moves its X window to where the video landed! See below
for advice on how to handle this situation in a better, if not
totally, acceptable way.VL_ORIGIN
control
after setting it and check to see if it is in the same spot you asked
for.
A civilized application program must allow its windows to go where
the user places them. We can forgive videoin due to its
simplicity. But a better solution for most applications is to put the
window where the user wants it, but to post a notifier if the ev1
video display cannot be placed to match the window location. This
might disappoint the user, but nevertheless it still leaves them
feeling like they have control over the windows on their screen.
void installStructureNotifyHandler() { if (handlerActive) return; Widget shell = _WidgetDisplayingVideo; while (!XtIsShell(shell)) { XtAddEventHandler(shell, StructureNotifyMask, FALSE, &structureNotifyHandler, (XtPointer) this); shell = XtParent(shell); } XtAddEventHandler(shell, StructureNotifyMask, FALSE, &structureNotifyHandler, (XtPointer) this); handlerActive = True; }
void removeStructureNotifyHandler() { if (!handlerActive) return; Widget shell = _WidgetDisplayingVideo; while (!XtIsShell(shell)) { XtRemoveEventHandler(shell, StructureNotifyMask, FALSE, &structureNotifyHandler, (XtPointer) this); shell = XtParent(shell); } XtRemoveEventHandler(shell, StructureNotifyMask, FALSE, &structureNotifyHandler, (XtPointer) this); handlerActive = False; }
void structureNotifyHandler(Widget w, XtPointer client_data, XEvent* event, Boolean*) { switch (event->type) { case ConfigureNotify: // we've been moved around onscreen. update our video display if (XtIsRealized(w)) { // // figure out dimensions and screen coordinates of the // video widget // Position x, y; Position rootx, rooty; Dimension widgetW, widgetH; XtVaGetValues(w, XmNx, &x, XmNy, &y, XmNwidth, &widgetW, XmNheight, &widgetH, NULL); XtTranslateCoords(XtParent(w), x, y, &rootx, &rooty); // // armed with root x and root y, we now calculate the // placement of the video overlay. // if (!displayPath) return; // determine size of video display. VLControlValue val; vlGetControl( svr, displayPath, src, VL_SIZE, &val ); int sizeW = val.xyVal.x; int sizeH = val.xyVal.y; // divide by current ZOOM setting. assume that zoom // control is 1/n. vlGetControl( svr, displayPath, drn, VL_ZOOM, &val ); sizeW /= val.fractVal.denominator; sizeH /= val.fractVal.denominator; // // find the origin coordinates of the video overlay, // taking into account that the video overlay cannot // go even partially off the screen. // int originX = rootx + ((Position) (widgetW / 2) - (Position) (sizeW / 2)); int originY = rooty + ((Position) (widgetH / 2) - (Position) (sizeH / 2)); // did we go off the screen? if so, make adjustments. Dimension screenWidth = WidthOfScreen(XtScreen(w)); Dimension screenHeight = HeightOfScreen(XtScreen(w)); if (originX < 0) { // left edge originX = 0; } if (originY < 0) { // top edge originY = 0; } if (originX + sizeW > screenWidth) { // right edge originX = screenWidth - sizeW - 1; } if (originY + sizeH > screenHeight) { // bottom edge originY = screenHeight - sizeH - 1; } // place the video overlay. val.xyVal.x = originX; val.xyVal.y = originY; vlSetControl(svr, displayPath, drn, VL_ORIGIN, &val); // verify the video was placed where we want it. checkVideoOverlayLocation(originX, originY); } break; default: break; } }
VL_ORIGIN
shouldn't
just check to see that the origin is different than you asked - it
should try to determine whether the problem is due to overlapping
video, or bumping against the edge of the display. You may not want to
complain to the user or take drastic action if the video is merely
running against the edges of the screen.void checkVideoOverlayLocation(Position originX, Position originY) { VLControlValue actualVal; vlGetControl( svr, displayPath, drn, VL_ORIGIN, &actualVal); // // Collisions between video overlays occur only when they are // on the same raster lines (horizontal), so comparing just the // y-value is sufficient to detect these cases. // // We do not worry about offscreen placement, // because that is taken into account elsewhere. // if (actualVal.xyVal.y != originY) { // // video overlay didn't position properly. Your app should // take some sort of action here. Keep in mind that if you // post an error dialog, you should also set a state flag so // that you do not post the dialog more than once. This // code will (unfortunately) get called multiple times, // because the event handler for ConfigureNotify events is // installed on several widgets in the hierarchy. // } // // Compare the x values to determine // if the right edge of the display // cannot go past pixel 1271 // if (actualVal.xyVal.x != originX) { // your app might want to do something here } }
VL_ZOOM
control, you also need to
set the VL_SIZE
control at the same time to guarantee the
video displays properly.
Yet another unpleasant side effect of the ev1 video overlay are the "jailbars" which appear when the window is larger than the video, or when the user moves around on the screen a window containing ev1 video.
The "jailbars" happen when you assign an X window to the
video node by setting the VL_WINDOW
control on the
path. The graphics hardware ignores the framebuffer contents for that
region of the display, replicating the last value that was in its
hardware registers. That last value is usually the pattern for the
window manager border, which when replicated looks like a row of brass
bars on the display.
If the window is larger than the video overlay, the regions of the window above and below the video overlay which don't contain video information will display jailbars. There is no way around this. There are a few ways to deal with the problem:
void createJailBarWidgets() { // // top widget // topArea = XtVaCreateManagedWidget ( "topArea", xmDrawingAreaWidgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNrightPosition, 0, XmNtopOffset, 0, XmNbottomOffset, 0, XmNleftOffset, 0, XmNrightOffset, 0, XmNwidth, topAreaWidth, XmNheight, topAreaHeight, XmNbackground, BlackPixelOfScreen(XtScreen(form)), (XtPointer) NULL ); // // bottom widget // bottomArea = XtVaCreateManagedWidget ( "bottomArea", xmDrawingAreaWidgetClass, form, XmNtopAttachment, XmATTACH_NONE, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNtopPosition, 0, XmNbottomPosition, 0, XmNrightPosition, 0, XmNtopOffset, 0, XmNbottomOffset, 0, XmNleftOffset, 0, XmNrightOffset, 0, XmNwidth, bottomAreaWidth, XmNheight, bottomAreaHeight, XmNbackground, BlackPixelOfScreen(XtScreen(form)), (XtPointer) NULL ); } void placeJailBarWidgets(Dimension widgetW, Dimension widgetH, Position originX, Position originY, Position rootY, Dimension videoH) { Dimension bottomAreaWidth, bottomAreaHeight; Dimension topAreaWidth, topAreaHeight; bottomAreaWidth = topAreaWidth = widgetW; int newTopHeight = ((int) originY) - ((int) rooty); if (newTopHeight < 0) { topAreaHeight = 0; } else { topAreaHeight = newTopHeight; } int newBottomHeight = ((int) widgetH) - (((int) originY) - ((int) rooty) + ((int) videoH)); if (newBottomHeight < 0) { bottomAreaHeight = 0; } else { bottomAreaHeight = newBottomHeight; } XtVaSetValues(topArea, XmNheight, topAreaHeight, (XtPointer) NULL); XtVaSetValues(bottomArea, XmNheight, bottomAreaHeight, (XtPointer) NULL); }
IMPACT Graphics do not display jailbars when used with ev1. But unless you know your application will run only on IMPACT, you need to worry about jailbars.
Finally, once a window is used with the ev1 video display node, it cannot be used for anything else. You cannot render into the window using X or GL drawing operations, even if you destroy the ev1 path which was displaying into the window. Once a window is in jail, it's a life sentence. The only surefire way to "get out of jail" is to destroy the window and create one anew.
There is a very common problem encountered by people who try to
display ev1 video within an X or Motif program. If you call
vlBeginTransfer()
before the window intended to
display it is fully created and mapped (shown) to the screen, the
video will not display.
This problem occurs because the X window system is
asynchronous. The X server does not immediately create and display
windows when the application invokes the appropriate calls. Instead,
the requests are queued up for the server, which performs them at
some arbitrary point in the future. If you start the transfer
immediately after calling a function like XtManageChild()
or XMapWindow()
, the video display won't work.
There are two ways to deal with the problem.
StructureNotifyMask
. When you receive a
MappingNotify
event, the X server has mapped the window
to the display, and it is safe to start the video transfer.switch
statement in the
StructureNotifyHandler
sample function shown above. This
is the preferred, though the more complicated, method of dealing with
the problem.XSync()
after you
make the call to display the window. This will force all outstanding
requests to be sent to the X server, and XSync()
will
wait until the server has processed them.When your code receives a VLStreamAvailable
event for
the display path, your code should do the following:
vlSetupPaths()
to set the path up once
again:vlSetupPaths(svr, &displayPath, 1, VL_SHARE,
VL_SHARE);
VL_ORIGIN
, VL_SIZE
and
VL_ZOOM
controls for the path, since the previous app
using this path have left it in an unknown state.vlBeginTransfer()
to restart the video
transfer:vlBeginTransfer(svr, displayPath, 0, NULL);