Real-time Stereoscopic Video Streaming 
by Karen McMenemy and Stuart Ferguson

Listing One

class CVideoCamera { // video data grabber object class
public:
  .. ..
  HRESULT Render(HWND, long, long); // render video into OpenGL window
  HRESULT InitDShowRenderer(long);  // set up DirectShow to grab video
  HWND    m_hwnd;                // Handle of window for video display 
  long    instance;              // Indicated video for left(=1) or right(=2)
private:
  HRESULT CaptureVideo(IBaseFilter *pRenderer,long id); //start video capture
  HRESULT FindCaptureDevice(IBaseFilter **ppSrcFilter,long *nfilters,long id);
  .. ..
  // Use ATL smart pointer to COM objects for automatic object Release
  CComPtr<ICaptureGraphBuilder2> m_pCapture; //Helps to render capture graphs
  CComPtr<IGraphBuilder> m_pGB;    // GraphBuilder Interface Pointer
  CComPtr<IMediaControl> m_pMC;    // Media Control Interface Pointer
  CComPtr<IMediaEvent> m_pME;      // Media Event Interface pointer
  CComPtr<IAMGraphStreams> m_pGS;  // Graph Streams Interface Pointer
};

// structure to provide thread data
typedef struct tagPROCESSDATA {
   HWND    parent;  // handle of parent window
   long    id;      // identifies if this thread is doing left or right camera
   CVideoCamera *pP; // pointer to object handling left/right camera
}  PROCESSDATA;

// global application data
HINSTANCE     hInstance = NULL; // handle of the application instance
CVideoCamera* g_pCamera1 = NULL;// pointer to object handling first camera
CVideoCamera* g_pCamera2 = NULL;// pointer to object handling second camera
unsigned char *ScreenL=NULL;  // pointer to image buffer (for left image)
unsigned char *ScreenR=NULL;  // pointer to image buffer (for right image)
long          X=0,Y=0;        // horizontal and vertical resolution of image
BOOL bStereo= TRUE,           // try to render into a stereoscopic window
bStereoDraw=  FALSE;          // display device CAN render into stereo window
CCritSec      g_cs;           // Critical section to prevent crash of threads


Listing Two


INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE hInstPrev, 
                                            LPSTR lpCmdLine, INT nCmdShow){
   .. ..
   // start communications thread and initialize the Component Object Model
   comms_open=open_comms();
   CoInitializeEx(NULL,COINIT_MULTITHREADED);
   .. ..
   // create objects to handle the video capture
   g_pCamera1 = new CVideoCamera();
   g_pCamera2 = new CVideoCamera();
   .. ..
   // create a classic window for the application
   WNDCLASSEX wc = {.};
   RegisterClassEx( &wc );
   hWndMain = CreateWindow(..): 
   .. ..
   // create video acquisition threads. Identify which is to handle
   // the left and right cameras by passing pointer to PROCESSDATA structure
   PROCESSDATA p1,p2;
   p1.id=1; p1.parent=hWndMain; p1.pP=g_pCamera1;
   p2.id=2; p2.parent=hWndMain; p2.pP=g_pCamera2;
   _beginthread(GLWindowPr,0,(void *)(&p1));
   _beginthread(GLWindowPr,0,(void *)(&p2));
   MSG msg; // standard Windows message handling loop
   while( msg.message!=WM_QUIT ){
      if(GetMessage( &msg, NULL, 0, 0)){ .. .. }
   }
   // Exit the application by: send window close messages to camera 
   // windows(this causes threads to exit). Close comms, free image buffer 
   // memory, delete camera objects and unregister Window classes.
   return 0L;
}

Listing Three

void GLWindowPr(void *arg){ // per camera thread function
   .. ..
   // get thread specific data
   PROCESSDATA *pp = (PROCESSDATA *)arg; id=pp->id; // id=1(left) 2(right)
   .. ..
   // create the OpenGL display window
   hwnd = CreateWindow(.. ..);
   // store the window data
   SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pp);
   .. ..
   // set up & run a DirectShow filter graph to grab video into image buffer
   hr = pp->pP->InitDShowRenderer(id);
   // if drawing into this window make it visible
   if(id ==1 || (id == 2 && !bStereoDraw))ShowWindow(hwnd,SW_SHOW);
   // set timer that will generate message to render to the display
   uTimerID = (UINT) SetTimer(hwnd, TIMER_ID, timer_rate, NULL); 
   .. ..
   MSG msg;
   while(GetMessage(&msg,NULL,0,0)){ .. ..} // put thread into message loop
   KillTimer(hwnd, TIMER_ID);
   .. ..
   pp->pP->Cleanup();
   _endthread();
   return;
}


Listing Four

LRESULT WINAPI GLProcessWndProc( HWND hWnd, UINT msg, 
                                           WPARAM wParam, LPARAM lParam ){
   switch( msg ) {
      case WM_CREATE:
        // standard windows stuff to configure OpenGL drawing in client area
        initializeGL(hWnd); // and stereoscopic capable display context.
        break;
     case WM_DESTROY:
       // standard windows stuff to release the drawing resources
       PostQuitMessage( 0 ); return 0;
     case WM_TIMER: // A timer goes off 15-30 times a second to render video
     case WM_PAINT:{
       PROCESSDATA *p=(PROCESSDATA *)GetWindowLongPtr( hWnd, GWLP_USERDATA);
       // choose the appropriate rendering function if these
       // messages are for the left or right window or a stereoscopic display
       if(p){
          if(p->pP){
            if(p->id == 1) p->pP->Render(hWnd,p->id,p->copyID);
            else if(!bStereoDraw)p->pP->Render2(hWnd,p->id,p->copyID);
          }
       }
    }
    break;
  default:
    break;
  }
  return DefWindowProc( hWnd, msg, wParam, lParam );
}
HRESULT CVideoCamera::Render(HWND hWnd, long id, long copyID){
   CAutoLock lock(&g_cs); // to prevent camera grabbing threads from writing
   .. ..
   hDC = BeginPaint(hWnd, &ps);
   if(ScreenL != NULL){ // draw the left eye image
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      if(bStereoDraw)glDrawBuffer (GL_BACK_LEFT);
      glDrawPixels((GLsizei)X,(GLsizei)Y, 
                               GL_RGB,GL_UNSIGNED_BYTE, (GLvoid *)ScreenL);
      if(bStereoDraw && ScreenR != NULL){ // draw the right eye image
         glFlush ();
         glClear (GL_DEPTH_BUFFER_BIT);
         glDrawBuffer (GL_BACK_RIGHT);
         glDrawPixels((GLsizei)X,(GLsizei)Y, 
                                GL_RGB,GL_UNSIGNED_BYTE, (GLvoid *)ScreenR);
      }
   }
   glFinish();
   hDC1 = wglGetCurrentDC();
   SwapBuffers(hDC1);
   EndPaint(hWnd, &ps);
   return S_OK;
}


Listing Five

HRESULT CVideoCamera::InitDShowRenderer(long id){
   .. ..
   CComPtr<IBaseFilter> pRenderer; // smart ATL pointer to renderer
   // Create the filter graph
   hr = m_pGB.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC);
   // Get the graph's media control and media event interfaces
   m_pGB.QueryInterface(&m_pMC);
   m_pGB.QueryInterface(&m_pME);
   // Get the graph's streams Interface
   hr = m_pGB.QueryInterface(&m_pGS);
   // create an instance of our custom OpenGL rendering filter.
   pRenderer = new CRenderer(this, id, NULL, &hr);
   // Initialize the video capture devices to render the incoming video
   // to our filter which will capture the images and display them.
   hr = CaptureVideo( pRenderer, id);
   // Try to sync the video sources
   hr = m_pGS->SyncUsingStreamOffset(TRUE);
   hr = m_pGS>SetMaxGraphLatency((REFERENCE_TIME)100);
   // Start the graph running
   hr = m_pMC->Run();
return hr;
}



5


