home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / graphics / video / aviview / aviview.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-10-05  |  64.6 KB  |  2,108 lines

  1. /**************************************************************************
  2.  *
  3.  *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  4.  *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  5.  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  6.  *  PURPOSE.
  7.  *
  8.  *  Copyright (C) 1993 - 1997  Microsoft Corporation.  All Rights Reserved.
  9.  *
  10.  **************************************************************************/
  11.  
  12. #define INC_OLE2
  13. #include <windows.h>
  14. #include <windowsx.h>
  15. #include <mmsystem.h>
  16. #include <memory.h>
  17. #include <commdlg.h>
  18. #include <vfw.h>
  19.  
  20. #include "muldiv32.h"
  21.  
  22. #include <mmreg.h>
  23. #include <msacm.h>
  24. #include "aviview.h"
  25. #include "audplay.h"
  26. #include "aviball.h"
  27.  
  28. #define GlobalSizePtr(lp)   GlobalSize(GlobalPtrHandle(lp))
  29.  
  30. #define FIXCC(fcc)  if (fcc == 0)       fcc = mmioFOURCC('N', 'o', 'n', 'e'); \
  31.                     if (fcc == BI_RLE8) fcc = mmioFOURCC('R', 'l', 'e', '8');
  32.  
  33. /*----------------------------------------------------------------------------*\
  34. \*----------------------------------------------------------------------------*/
  35. typedef LONG (PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure
  36.  
  37. /*----------------------------------------------------------------------------*\
  38. \*----------------------------------------------------------------------------*/
  39. static  TCHAR   gszAppName[]=TEXT("AVIView");
  40. static  TCHAR    gachFilter[512] = TEXT("");
  41.  
  42.  
  43. static  HANDLE    ghInstApp;
  44. static  HWND    ghwndApp;
  45. static  HACCEL    ghAccel;
  46. static  HANDLE  ghLib;                 // Handle to palmap32.dll
  47. static  BOOL    gbCanPalMap = FALSE;   // Is palmap32.dll available?
  48.  
  49. #define SCROLLRANGE  10000
  50.  
  51. #define MAXNUMSTREAMS   50
  52. PAVIFILE        gpfile;            // the current file
  53. PAVISTREAM          gapavi[MAXNUMSTREAMS];    // the current streams
  54. PGETFRAME        gapgf[MAXNUMSTREAMS];    // data for decompressing
  55.                         // video
  56. HDRAWDIB        ghdd[MAXNUMSTREAMS];    // drawdib handles
  57. int            gcpavi;            // # of streams
  58.  
  59. BOOL            gfPlaying = FALSE;        // Are we playing right now?
  60. LONG            glPlayStartTime;        // When did we start playing?
  61. LONG             glPlayStartPos;        // From what position?
  62.  
  63. PAVISTREAM          gpaviAudio;                 // 1st audio stream found
  64. PAVISTREAM          gpaviVideo;                 // 1st video stream found
  65. int                 giFirstVideo;                // index of gapavi for 1st Video stream
  66.  
  67. #define             gfVideoFound (gpaviVideo != NULL)
  68. #define             gfAudioFound (gpaviAudio != NULL)
  69.  
  70. LONG                timeStart;            // cached start, end, length
  71. LONG                timeEnd;
  72. LONG                timeLength;
  73. LONG            timehscroll;                // how much arrows scroll HORZ bar
  74. LONG            vertSBLen;
  75. LONG            vertHeight;
  76.  
  77.  
  78. DWORD            gdwMicroSecPerPixel = 1000L;// scale for video
  79.  
  80. TCHAR               gachFileName[MAX_PATH] = TEXT("");
  81. TCHAR               gachSaveFileName[MAX_PATH] = TEXT("");
  82. UINT            gwZoom = 2;            // one half zoom (divide by 4)
  83. AVICOMPRESSOPTIONS  gaAVIOptions[MAXNUMSTREAMS];
  84. LPAVICOMPRESSOPTIONS  galpAVIOptions[MAXNUMSTREAMS];
  85.  
  86. HFONT               hfontApp;
  87. TEXTMETRIC          tm;
  88. BYTE            abFormat[1024];
  89. LPVOID            lpAudio;        // buffer for painting
  90. int                 gnColours;          // No of colours to remap palette to
  91.  
  92.                             // !!! constants for painting
  93. #define VSPACE  8                    // some vertical spacing
  94. #define HSPACE  4                   // space between frames for video streams
  95. #define TSPACE  20                    // space for text area about each stream
  96. #define AUDIOVSPACE  64                    // height of an audio stream at X1 zoom
  97.  
  98. /*----------------------------------------------------------------------------*\
  99. \*----------------------------------------------------------------------------*/
  100.  
  101. #define GetScrollTime(hwnd) \
  102.     (LONG)(timeStart + muldiv32(GetScrollPos(hwnd, SB_HORZ), timeLength, SCROLLRANGE))
  103.  
  104. #define SetScrollTime(hwnd, time) SetScrollPos(hwnd, SB_HORZ, \
  105.     (int)muldiv32((time) - timeStart, SCROLLRANGE, timeLength), TRUE)
  106.  
  107. /*----------------------------------------------------------------------------*\
  108. \*----------------------------------------------------------------------------*/
  109. long             PaintStuff(HDC hdc, HWND hwnd, BOOL fDrawEverything);
  110. LONG WINAPI      AppWndProc (HWND hwnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
  111. int              ErrMsg (LPTSTR sz,...);
  112. LONG PASCAL      AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
  113. BOOL CALLBACK    AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
  114. BOOL CALLBACK    GetNumberOfColorsDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
  115. LONG             GetNumberOfColors(PAVISTREAM ps);
  116. /*----------------------------------------------------------------------------*\
  117. \*----------------------------------------------------------------------------*/
  118.  
  119. HCURSOR hcurSave;
  120. int     fWait = 0;
  121.  
  122. /*----------------------------------------------------------------------------*\
  123. |    StartWait()
  124. |
  125. |    Start a wait operation... put up the hourglass if it's the first call.
  126. \*----------------------------------------------------------------------------*/
  127. void StartWait()
  128. {
  129.     if (fWait++ == 0) {
  130.         SetCursor(LoadCursor(NULL,IDC_WAIT));
  131.     }
  132. }
  133.  
  134. /*----------------------------------------------------------------------------*\
  135. |    EndWait()
  136. |
  137. |    Once every one who started a wait is finished, go back to regular cursor.
  138. \*----------------------------------------------------------------------------*/
  139. void EndWait()
  140. {
  141.     if (--fWait == 0) {
  142.         SetCursor(LoadCursor(NULL,IDC_ARROW));
  143.         InvalidateRect(ghwndApp, NULL, TRUE);
  144.     }
  145. }
  146.  
  147. /*----------------------------------------------------------------------------*\
  148. |    WinYield()
  149. |
  150. |    Code to yield while we're not calling GetMessage.
  151. |    Dispatch all messages.  Pressing ESC or closing aborts.
  152. \*----------------------------------------------------------------------------*/
  153. BOOL WinYield()
  154. {
  155.     MSG msg;
  156.     BOOL fAbort=FALSE;
  157.  
  158.     while(fWait > 0 && PeekMessage(&msg,NULL,0,0,PM_REMOVE))
  159.     {
  160.     if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
  161.             fAbort = TRUE;
  162.     if (msg.message == WM_SYSCOMMAND && (msg.wParam & 0xFFF0) == SC_CLOSE)
  163.         fAbort = TRUE;
  164.     TranslateMessage(&msg);
  165.     DispatchMessage(&msg);
  166.     }
  167.     return fAbort;
  168. }
  169.  
  170.  
  171. /*----------------------------------------------------------------------------*\
  172. \*----------------------------------------------------------------------------*/
  173.  
  174. /*----------------------------------------------------------------------------*\
  175. |    FixScrollbars()
  176. |
  177. |    When we load a file or zoom changes, we re-set the scrollbars.
  178. \*----------------------------------------------------------------------------*/
  179. void FixScrollbars(HWND hwnd)
  180. {
  181.     LONG                lHeight = 0;
  182.     RECT                rc;
  183.     HDC                 hdc;
  184.  
  185.     //
  186.     // Pretend we're painting and count how many lines we needed
  187.     //
  188.     hdc = GetDC(NULL);
  189.     ExcludeClipRect(hdc, 0, 0, 32767, 32767);   // don't actually draw
  190.     lHeight = PaintStuff(hdc, hwnd, TRUE);
  191.     ReleaseDC(NULL, hdc);
  192.  
  193.     //
  194.     // Set vertical scrollbar for scrolling the visible area
  195.     //
  196.     GetClientRect(hwnd, &rc);
  197.     vertHeight = lHeight;    // total height in pixels of entire display
  198.  
  199.     //
  200.     // We won't fit in the window... need scrollbars
  201.     //
  202.     if (lHeight > rc.bottom) {
  203.     vertSBLen = lHeight - rc.bottom;
  204.     SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);
  205.     SetScrollPos(hwnd, SB_VERT, 0, TRUE);
  206.  
  207.     //
  208.     // We will fit in the window!  No scrollbars necessary
  209.     //
  210.     } else {
  211.     vertSBLen = 0;
  212.     SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
  213.     }
  214. }
  215.  
  216. /*----------------------------------------------------------------------------*\
  217. |    InitStreams()
  218. |
  219. |    Initialize the streams of a loaded file -- the compression options, the
  220. |    DrawDIB handles, and the scroll bars.
  221. \*----------------------------------------------------------------------------*/
  222. void InitStreams(HWND hwnd)
  223. {
  224.     AVISTREAMINFO     avis;
  225.     LONG    lTemp;
  226.     int        i;
  227.     DWORD    dw;
  228.  
  229.     //
  230.     // Start with bogus times
  231.     //
  232.     timeStart = 0x7FFFFFFF;
  233.     timeEnd   = 0;
  234.  
  235.     //
  236.     // Walk through and init all streams loaded
  237.     //
  238.     for (i = 0; i < gcpavi; i++) {
  239.  
  240.         AVIStreamInfo(gapavi[i], &avis, sizeof(avis));
  241.  
  242.     //
  243.     // Save and SaveOptions code takes a pointer to our compression opts
  244.     //
  245.     galpAVIOptions[i] = &gaAVIOptions[i];
  246.  
  247.     //
  248.     // clear options structure to zeroes
  249.     //
  250.     _fmemset(galpAVIOptions[i], 0, sizeof(AVICOMPRESSOPTIONS));
  251.  
  252.     //
  253.      // Initialize the compression options to some default stuff
  254.     // !!! Pick something better
  255.     //
  256.     galpAVIOptions[i]->fccType = avis.fccType;
  257.  
  258.     switch(avis.fccType) {
  259.  
  260.         case streamtypeVIDEO:        
  261.         galpAVIOptions[i]->dwFlags = AVICOMPRESSF_VALID |
  262.             AVICOMPRESSF_KEYFRAMES | AVICOMPRESSF_DATARATE;
  263.         galpAVIOptions[i]->fccHandler = 0;
  264.         galpAVIOptions[i]->dwQuality = (DWORD)ICQUALITY_DEFAULT;
  265.         galpAVIOptions[i]->dwKeyFrameEvery = (DWORD)-1; // Default
  266.         galpAVIOptions[i]->dwBytesPerSecond = 0;
  267.         galpAVIOptions[i]->dwInterleaveEvery = 1;
  268.         break;
  269.  
  270.         case streamtypeAUDIO:
  271.         galpAVIOptions[i]->dwFlags |= AVICOMPRESSF_VALID;
  272.         galpAVIOptions[i]->dwInterleaveEvery = 1;
  273.                 AVIStreamReadFormat(gapavi[i], AVIStreamStart(gapavi[i]),
  274.                                     NULL, &lTemp);
  275.                 galpAVIOptions[i]->cbFormat = lTemp;
  276.                 if (lTemp)
  277.                     galpAVIOptions[i]->lpFormat = GlobalAllocPtr(GHND, lTemp);
  278.                 // Use current format as default format
  279.                 if (galpAVIOptions[i]->lpFormat)
  280.                     AVIStreamReadFormat(gapavi[i],
  281.                                     AVIStreamStart(gapavi[i]),
  282.                     galpAVIOptions[i]->lpFormat,
  283.                     &lTemp);
  284.         break;
  285.  
  286.         default:
  287.         break;
  288.     }
  289.  
  290.     //
  291.     // We're finding the earliest and latest start and end points for
  292.     // our scrollbar.
  293.     //
  294.         timeStart = min(timeStart, AVIStreamStartTime(gapavi[i]));
  295.         timeEnd   = max(timeEnd, AVIStreamEndTime(gapavi[i]));
  296.  
  297.     //
  298.     // Initialize video streams for getting decompressed frames to display
  299.     //
  300.         if (avis.fccType == streamtypeVIDEO) {
  301.  
  302.         gapgf[i] = AVIStreamGetFrameOpen(gapavi[i], NULL);
  303.         if (gapgf[i] == NULL)
  304.         continue;
  305.     
  306.         ghdd[i] = DrawDibOpen();
  307.         // !!! DrawDibBegin?
  308.     
  309.         if (gpaviVideo == NULL) {
  310.  
  311.         //
  312.         // Remember the first video stream --- treat it specially
  313.         //
  314.                 gpaviVideo = gapavi[i];
  315.                 giFirstVideo = i;
  316.  
  317.                 //
  318.                 // Set the horizontal scrollbar scale to show every frame
  319.                 // of the first video stream exactly once
  320.                 //
  321.                 dw = (avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4 + HSPACE;
  322.                 gdwMicroSecPerPixel = muldiv32(1000000,
  323.                                                avis.dwScale,
  324.                                                dw * avis.dwRate);
  325.  
  326.                 // Move one frame on the top video screen for each HSCROLL
  327.                 timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
  328.             }
  329.  
  330.         } else if (avis.fccType == streamtypeAUDIO) {
  331.  
  332.             //
  333.             // If there are no video streams, we base everything on this
  334.             // audio stream.
  335.             //
  336.             if (gpaviAudio == NULL && gpaviVideo == NULL) {
  337.  
  338.                 // Show one sample per pixel
  339.                 gdwMicroSecPerPixel = muldiv32(1000000,
  340.                                                avis.dwScale,
  341.                                                avis.dwRate);
  342.                 // Move one sample per HSCROLL
  343.                 // Move at least enough to show movement
  344.                 timehscroll = muldiv32(1000, avis.dwScale, avis.dwRate);
  345.             }
  346.  
  347.         //
  348.         // Remember the first audio stream --- treat it specially
  349.         //
  350.         if (gpaviAudio == NULL)
  351.             gpaviAudio = gapavi[i];
  352.  
  353.     }
  354.  
  355.     }
  356.  
  357.     timeLength = timeEnd - timeStart;
  358.  
  359.     if (timeLength == 0)
  360.         timeLength = 1;
  361.  
  362.     // Make sure HSCROLL scrolls enough to be noticeable.
  363.     timehscroll = max(timehscroll, timeLength / SCROLLRANGE + 2);
  364.  
  365.     SetScrollRange(hwnd, SB_HORZ, 0, SCROLLRANGE, TRUE);
  366.     SetScrollTime(hwnd, timeStart);
  367.  
  368.     FixScrollbars(hwnd);
  369. }
  370.  
  371. /*----------------------------------------------------------------------------*\
  372. |    FixWindowTitle()
  373. |
  374. |    Update the window title to reflect what's loaded.
  375. \*----------------------------------------------------------------------------*/
  376. void FixWindowTitle(HWND hwnd)
  377. {
  378.     TCHAR ach[80];
  379.  
  380.     wsprintf(ach, TEXT("%s %s"),
  381.             (LPTSTR)gszAppName,
  382.             (LPTSTR)gachFileName);
  383.  
  384.     SetWindowText(hwnd, ach);
  385.  
  386.     InvalidateRect(hwnd, NULL, TRUE);
  387. }
  388.  
  389. /*----------------------------------------------------------------------------*\
  390. |    FreeDrawStuff()
  391. |
  392. | Free up the resources associated with DrawDIB
  393. \*----------------------------------------------------------------------------*/
  394. void FreeDrawStuff(HWND hwnd)
  395. {
  396.     int    i;
  397.  
  398.     aviaudioStop();
  399.  
  400.     for (i = 0; i < gcpavi; i++) {
  401.     if (gapgf[i]) {
  402.         AVIStreamGetFrameClose(gapgf[i]);
  403.         gapgf[i] = NULL;
  404.     }
  405.     if (ghdd[i]) {
  406.         DrawDibClose(ghdd[i]);
  407.         ghdd[i] = 0;
  408.     }
  409.     }
  410.     SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
  411.     gpaviVideo = gpaviAudio = NULL;
  412. }
  413.  
  414. /*----------------------------------------------------------------------------*\
  415. |    FreeAvi()
  416. |
  417. |    Free the resources associated with an open file.
  418. \*----------------------------------------------------------------------------*/
  419. void FreeAvi(HWND hwnd)
  420. {
  421.     int    i;
  422.  
  423.     FreeDrawStuff(hwnd);
  424.  
  425.     AVISaveOptionsFree(gcpavi, galpAVIOptions);
  426.  
  427.     for (i = 0; i < gcpavi; i++) {
  428.     AVIStreamRelease(gapavi[i]);
  429.     }
  430.  
  431.     if (gpfile)
  432.     AVIFileRelease(gpfile);
  433.  
  434.     gpfile = NULL;
  435.  
  436.     // Good a place as any to make sure audio data gets freed
  437.     if (lpAudio)
  438.         GlobalFreePtr(lpAudio);
  439.     lpAudio = NULL;
  440.  
  441.     gcpavi = 0;
  442. }
  443.  
  444. /*----------------------------------------------------------------------------*\
  445. |    InitBall()
  446. |
  447. |    Open up our fake "ball" file as an installible stream hander
  448. \*----------------------------------------------------------------------------*/
  449. void InitBall(HWND hwnd)
  450. {
  451.     // close everything down
  452.     FreeAvi(hwnd);
  453.  
  454.     // The NewBall() function creates a PAVISTREAM we can use as if it was
  455.     // an AVI file.
  456.     gapavi[0] = NewBall();
  457.  
  458.     if (gapavi[0])
  459.     gcpavi = 1;
  460.  
  461.     lstrcpy(gachFileName, TEXT("BALL"));
  462.     InitStreams(hwnd);
  463.     FixWindowTitle(hwnd);
  464. }
  465.  
  466. /*----------------------------------------------------------------------------*\
  467. |    InsertAVIFile()
  468. |
  469. |    Does most of the work of opening an AVI file.
  470. \*----------------------------------------------------------------------------*/
  471. void InsertAVIFile(PAVIFILE pfile, HWND hwnd, LPTSTR lpszFile)
  472. {
  473.     int        i;
  474.     PAVISTREAM    pavi;
  475.  
  476.     //
  477.     // Get all the streams from the new file
  478.     //
  479.     for (i = gcpavi; i <= MAXNUMSTREAMS; i++) {
  480.     if (AVIFileGetStream(pfile, &pavi, 0L, i - gcpavi) != AVIERR_OK)
  481.         break;
  482.         if (i == MAXNUMSTREAMS) {
  483.             AVIStreamRelease(pavi);
  484.             ErrMsg("Exceeded maximum number of streams");
  485.             break;
  486.         }
  487.     gapavi[i] = pavi;
  488.     }
  489.  
  490.     //
  491.     // Couldn't get any streams out of this file
  492.     //
  493.     if (gcpavi == i && i != MAXNUMSTREAMS)
  494.     {
  495.         ErrMsg(TEXT("Unable to open %s"), lpszFile);
  496.     if (pfile)
  497.         AVIFileRelease(pfile);
  498.     return;
  499.     }
  500.  
  501.     gcpavi = i;
  502.  
  503.     if (gpfile) {
  504.     AVIFileRelease(pfile);
  505.     } else
  506.     gpfile = pfile;
  507.  
  508.     FreeDrawStuff(hwnd);
  509.     InitStreams(hwnd);
  510.     FixWindowTitle(hwnd);
  511. }
  512.  
  513. /*----------------------------------------------------------------------------*\
  514. |    InitAvi()
  515. |
  516. |    Open up a file through the AVIFile handlers.
  517. \*----------------------------------------------------------------------------*/
  518. void InitAvi(HWND hwnd, LPTSTR szFile, UINT wMenu)
  519. {
  520.     HRESULT    hr;
  521.     PAVIFILE    pfile;
  522.  
  523.     hr = AVIFileOpen(&pfile, szFile, 0, 0L);
  524.  
  525.     if (hr != 0)
  526.     {
  527.         ErrMsg(TEXT("Unable to open %s"), szFile);
  528.         return;
  529.     }
  530.  
  531.     if (wMenu == MENU_OPEN)
  532.     FreeAvi(hwnd);
  533.  
  534.     InsertAVIFile(pfile, hwnd, szFile);
  535. }
  536.  
  537. /*----------------------------------------------------------------------------*\
  538. |   AppInit( hInst, hPrev)                               |
  539. |                                           |
  540. |   Description:                                   |
  541. |    This is called when the application is first loaded into           |
  542. |    memory.  It performs all initialization that doesn't need to be done   |
  543. |    once per instance.                               |
  544. |                                           |
  545. |   Arguments:                                       |
  546. |    hInstance    instance handle of current instance               |
  547. |    hPrev        instance handle of previous instance               |
  548. |                                           |
  549. |   Returns:                                       |
  550. |    TRUE if successful, FALSE if not                       |
  551. |                                           |
  552. \*----------------------------------------------------------------------------*/
  553. BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw,LPSTR szCmdLine)
  554. {
  555.     WNDCLASS    cls;
  556.     HDC        hdc;
  557.  
  558. #ifdef BIDI
  559.     const DWORD  dwExStyle = WS_EX_BIDI_SCROLL  | WS_EX_BIDI_MENU |WS_EX_BIDI_NOICON;
  560. #else
  561.     const DWORD  dwExStyle = 0;
  562. #endif
  563.  
  564.     /* Save instance handle for DialogBoxs */
  565.     ghInstApp = hInst;
  566.  
  567.     ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));
  568.  
  569.     if (szCmdLine && szCmdLine[0]) {
  570. #ifdef UNICODE
  571.     // convert to unicode
  572.     lstrcpy(gachFileName, GetCommandLine());
  573. #else
  574.         lstrcpy(gachFileName, szCmdLine);
  575. #endif
  576.     }
  577.  
  578.     if (!hPrev) {
  579.     /*
  580.      *  Register a class for the main application window
  581.      */
  582.         cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
  583.         cls.hIcon          = LoadIcon(hInst,MAKEINTATOM(ID_APP));
  584.         cls.lpszMenuName   = MAKEINTATOM(ID_APP);
  585.         cls.lpszClassName  = MAKEINTATOM(ID_APP);
  586.         cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
  587.         cls.hInstance      = hInst;
  588.         cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
  589.         cls.lpfnWndProc    = (LPWNDPROC)AppWndProc;
  590.         cls.cbWndExtra     = 0;
  591.         cls.cbClsExtra     = 0;
  592.  
  593.         if (!RegisterClass(&cls))
  594.         return FALSE;
  595.     }
  596.  
  597.     //
  598.     // Must be called before using any of the AVIFile routines
  599.     //
  600.     AVIFileInit();
  601.  
  602.     hfontApp = GetStockObject(ANSI_VAR_FONT);
  603.     hdc = GetDC(NULL);
  604.     SelectObject(hdc, hfontApp);
  605.     GetTextMetrics(hdc, &tm);
  606.     ReleaseDC(NULL, hdc);
  607.  
  608.     ghwndApp=CreateWindowEx(dwExStyle,
  609.                 MAKEINTATOM(ID_APP),    // Class name
  610.                             gszAppName,             // Caption
  611.                             WS_OVERLAPPEDWINDOW,    // Style bits
  612.                             CW_USEDEFAULT, 0,       // Position
  613.                             320,300,                // Size
  614.                             (HWND)NULL,             // Parent window (no parent)
  615.                             (HMENU)NULL,            // use class menu
  616.                             (HANDLE)hInst,          // handle to window instance
  617.                             (LPSTR)NULL             // no params to pass on
  618.                            );
  619.     ShowWindow(ghwndApp,sw);
  620.  
  621.     ghLib = LoadLibrary(TEXT("palmap32.dll"));
  622.     if (ghLib == NULL)
  623.         gbCanPalMap = FALSE;
  624.     else
  625.         gbCanPalMap = TRUE;
  626.  
  627.     return TRUE;
  628. }
  629.  
  630. /*----------------------------------------------------------------------------*\
  631. |   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )                   |
  632. |                                                                              |
  633. |   Description:                                                               |
  634. |       The main procedure for the App.  After initializing, it just goes      |
  635. |       into a message-processing loop until it gets a WM_QUIT message         |
  636. |       (meaning the app was closed).                                          |
  637. |                                                                              |
  638. |   Arguments:                                                                 |
  639. |    hInst        instance handle of this instance of the app           |
  640. |    hPrev        instance handle of previous instance, NULL if first    |
  641. |       szCmdLine       ->null-terminated command line                         |
  642. |       cmdShow         specifies how the window is initially displayed        |
  643. |                                                                              |
  644. |   Returns:                                                                   |
  645. |       The exit code as specified in the WM_QUIT message.                     |
  646. |                                                                              |
  647. \*----------------------------------------------------------------------------*/
  648. int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
  649. {
  650.     MSG     msg;
  651.  
  652.     /* Call initialization procedure */
  653.     if (!AppInit(hInst,hPrev,sw,szCmdLine))
  654.         return FALSE;
  655.  
  656.     /*
  657.      * Polling messages from event queue
  658.      */
  659.     for (;;)
  660.     {
  661.         while (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE))
  662.         {
  663.             if (msg.message == WM_QUIT)
  664.                 return msg.wParam;
  665.  
  666.         if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
  667.         continue;
  668.     
  669.             TranslateMessage(&msg);
  670.             DispatchMessage(&msg);
  671.         }
  672.  
  673.     //
  674.     // If we have no messages to dispatch, we do our background task...
  675.     // If we're playing a file, we set the scroll bar to show the video
  676.     // frames corresponding with the current playing audio sample
  677.     //
  678.         if (gfPlaying) {
  679.         LONG    l;
  680.  
  681.         //
  682.         // Use the audio clock to tell how long we've been playing.  To
  683.         // maintain sync, it's important we use this clock.
  684.         //
  685.         l = aviaudioTime();     // returns -1 if no audio playing
  686.  
  687.         //
  688.         // If we can't use the audio clock to tell us how long we've been
  689.         // playing, calculate it ourself
  690.         //
  691.         if (l == -1)
  692.         l = timeGetTime() - glPlayStartTime + glPlayStartPos;
  693.  
  694.         if (l != GetScrollTime(ghwndApp)) {
  695.             if (l < timeStart)    // make sure number isn't out of bounds
  696.             l = timeStart;
  697.             if (l > timeEnd)    // looks like we're all done!
  698.                     FORWARD_WM_COMMAND(ghwndApp, MENU_STOP, NULL, 0, SendMessage);
  699.  
  700.         SetScrollTime(ghwndApp, l);
  701.         InvalidateRect(ghwndApp, NULL, FALSE);
  702.         UpdateWindow(ghwndApp);
  703.         continue;
  704.         }
  705.     }
  706.     
  707.     WaitMessage();
  708.     }
  709.  
  710.     // not reached
  711.     return msg.wParam;
  712. }
  713.  
  714. typedef BYTE * HPBYTE;
  715. typedef UNALIGNED short * HPSHORT;
  716.  
  717. /*----------------------------------------------------------------------------*\
  718. |    PaintVideo()
  719. |
  720. |    Draw a video frame in the specified rect.
  721. \*----------------------------------------------------------------------------*/
  722. void PaintVideo(HDC hdc, RECT rcFrame, int iStream, LPBITMAPINFOHEADER lpbi, LONG lCurFrame, LONG lPos)
  723. {
  724.     int        iLen;
  725.     char    ach[200];
  726.     RECT    rc;
  727.     COLORREF    nCol;
  728.  
  729.     //
  730.     // If we have a picture, draw it
  731.     //
  732.     if (lpbi)
  733.     {
  734.         //
  735.         // use the palette of the first video stream
  736.         //
  737.         DrawDibDraw(ghdd[iStream],hdc,
  738.         rcFrame.left, rcFrame.top,
  739.         rcFrame.right - rcFrame.left,
  740.         rcFrame.bottom - rcFrame.top,
  741.         lpbi, NULL,
  742.         0, 0, -1, -1,
  743.         gapavi[iStream] == gpaviVideo ? 0 : DDF_BACKGROUNDPAL);
  744.  
  745.         iLen = wsprintf(ach, TEXT("%ld %ld.%03lds"),
  746.         lCurFrame, lPos/1000, lPos%1000);
  747.     }
  748.  
  749.     //
  750.     // Before or after the movie (or read error) draw GRAY
  751.     //
  752.     else {
  753.         if (gapgf[iStream])
  754.         SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));
  755.         else
  756.         SelectObject(hdc,GetStockObject(LTGRAY_BRUSH));
  757.  
  758.         PatBlt(hdc,
  759.         rcFrame.left, rcFrame.top,
  760.         rcFrame.right - rcFrame.left,
  761.         rcFrame.bottom - rcFrame.top,
  762.         PATCOPY);
  763.         iLen = 0;
  764.         ach[0] = TEXT('\0');
  765.     }
  766.  
  767.     //
  768.     // print something meaningful under the frame
  769.     //
  770.     rc.left = rcFrame.left;
  771.     rc.right = rcFrame.right + HSPACE;
  772.     rc.top = rcFrame.bottom + VSPACE;
  773.     rc.bottom = rc.top + TSPACE;
  774.     nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
  775.     ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE,
  776.            &rc, ach, iLen, NULL);
  777.     SetBkColor(hdc, nCol);
  778. }
  779.  
  780. /*----------------------------------------------------------------------------*\
  781. |    PaintAudio()
  782. |
  783. |    Draw some samples of audio inside the given rectangle.
  784. \*----------------------------------------------------------------------------*/
  785. void PaintAudio(HDC hdc, PRECT prc, PAVISTREAM pavi, LONG lStart, LONG lLen)
  786. {
  787.     PCMWAVEFORMAT wf;
  788.     int i;
  789.     int x,y;
  790.     int w,h;
  791.     BYTE b;
  792.     HBRUSH hbr;
  793.     RECT rc = *prc;
  794.     LONG    lBytes;
  795.     LONG    l, lLenOrig = lLen;
  796.     LONG    lWaveBeginTime = AVIStreamStartTime(pavi);
  797.     LONG    lWaveEndTime   = AVIStreamEndTime(pavi);
  798.  
  799.     //
  800.     // We can't draw before the beginning of the stream - adjust
  801.     //
  802.     if (lStart < lWaveBeginTime) {
  803.     lLen -= lWaveBeginTime - lStart;
  804.     lStart = lWaveBeginTime;
  805.     // right justify the legal samples in the rectange - don't stretch
  806.     rc.left = rc.right - (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
  807.     }
  808.  
  809.     //
  810.     // We can't draw past the end of the stream
  811.     //
  812.     if (lStart + lLen > lWaveEndTime) {
  813.     lLenOrig = lLen;
  814.     lLen = max(0, lWaveEndTime - lStart);    // maybe nothing to draw!
  815.     // left justify the legal samples in the rectange - don't stretch
  816.     rc.right = rc.left + (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
  817.     }
  818.  
  819.     // Now change and work with samples, not time.
  820.     l = lStart;
  821.     lStart = AVIStreamTimeToSample(pavi, lStart);
  822.     lLen = AVIStreamTimeToSample(pavi, l + lLen) - lStart;
  823.  
  824.     //
  825.     // Get the format of the wave data
  826.     //
  827.     l = sizeof(wf);
  828.     AVIStreamReadFormat(pavi, lStart, &wf, &l);
  829.     if (!l)
  830.         return;
  831.  
  832.     w = rc.right - rc.left;
  833.     h = rc.bottom - rc.top;
  834.  
  835.     //
  836.     // We were starting before the beginning or continuing past the end.
  837.     // We're not painting in the whole original rect --- use a dark background
  838.     //
  839.     if (rc.left > prc->left) {
  840.         SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
  841.      PatBlt(hdc, prc->left, rc.top, rc.left - prc->left,
  842.             rc.bottom - rc.top, PATCOPY);
  843.     }
  844.  
  845.     if (rc.right < prc->right) {
  846.         SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
  847.  PatBlt(hdc, rc.right, rc.top, prc->right - rc.right,
  848.             rc.bottom - rc.top, PATCOPY);
  849.     }
  850.  
  851. #define BACKBRUSH  (GetSysColor(COLOR_BTNFACE))
  852. #define MONOBRUSH  (GetSysColor(COLOR_BTNSHADOW))
  853. #define LEFTBRUSH  (RGB(0,0,255))
  854. #define RIGHTBRUSH (RGB(0,255,0))
  855. #define HPOSBRUSH  (RGB(255,0,0))
  856.  
  857.     //
  858.     // Paint the background
  859.     //
  860.     hbr = SelectObject(hdc, CreateSolidBrush(BACKBRUSH));
  861.     PatBlt(hdc, rc.left, rc.top, w, h, PATCOPY);
  862.     DeleteObject(SelectObject(hdc, hbr));
  863.  
  864.     //
  865.     // !!! we can only paint PCM data right now.  Sorry!
  866.     //
  867.     if (wf.wf.wFormatTag != WAVE_FORMAT_PCM)
  868.         return;
  869.  
  870.     //
  871.     // How many bytes are we painting? Alloc some space for them
  872.     //
  873.     lBytes = lLen * wf.wf.nChannels * wf.wBitsPerSample / 8;
  874.     if (!lpAudio)
  875.         lpAudio = GlobalAllocPtr (GHND, lBytes);
  876.     else if ((LONG)GlobalSizePtr(lpAudio) < lBytes)
  877.         lpAudio = GlobalReAllocPtr(lpAudio, lBytes, GMEM_MOVEABLE);
  878.  
  879.     if (!lpAudio)
  880.         return;
  881.  
  882.     //
  883.     // Read in the wave data
  884.     //
  885.     AVIStreamRead(pavi, lStart, lLen, lpAudio, lBytes, NULL, &l);
  886.  
  887.     if (l != lLen)
  888.     return;
  889.  
  890.     //
  891.     // !!! Flickers less painting it NOW or LATER?
  892.     // First show the current position as a bar
  893.     //
  894.     hbr = SelectObject(hdc, CreateSolidBrush(HPOSBRUSH));
  895.     PatBlt(hdc, prc->right / 2, prc->top, 1, prc->bottom - prc->top, PATCOPY);
  896.     DeleteObject(SelectObject(hdc, hbr));
  897.  
  898.     //
  899.     // Paint monochrome wave data
  900.     //
  901.     if (wf.wf.nChannels == 1) {
  902.  
  903.     //
  904.     // Draw the x-axis
  905.     //
  906.         hbr = SelectObject(hdc, CreateSolidBrush(MONOBRUSH));
  907.         y = rc.top + h/2;
  908.         PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  909.  
  910.     //
  911.     // 8 bit data is centred around 0x80
  912.     //
  913.         if (wf.wBitsPerSample == 8) {
  914.             for (x=0; x<w; x++) {
  915.  
  916.         // which byte of audio data belongs at this pixel?
  917.                 b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w));
  918.  
  919.                 if (b > 0x80) {
  920.                     i = y - (int)muldiv32(b-0x80,(h/2),128);
  921.                     PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  922.                 }
  923.                 else {
  924.                     i = y + (int)muldiv32(0x80-b,(h/2),128);
  925.                     PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
  926.                 }
  927.             }
  928.         }
  929.  
  930.     //
  931.     // 16 bit data is centred around 0x00
  932.     //
  933.         else if (wf.wBitsPerSample == 16) {
  934.             for (x=0; x<w; x++) {
  935.  
  936.         // Don't make any assumptions about INT size !
  937.         // which byte of audio data belongs at this pixel?
  938.                 i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w));
  939.                 if (i > 0) {
  940.                    i = y - (int) ((LONG)i * (h/2) / 32768);
  941.                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  942.                 }
  943.                 else {
  944.                    i = (int) ((LONG)i * (h/2) / 32768);
  945.                    PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
  946.                 }
  947.             }
  948.         }
  949.         DeleteObject(SelectObject(hdc, hbr));
  950.     } // endif mono
  951.  
  952.     //
  953.     // Draw stereo waveform data
  954.     //
  955.     else if (wf.wf.nChannels == 2) {
  956.  
  957.     //
  958.     // 8 bit data is centred around 0x80
  959.     //
  960.         if (wf.wBitsPerSample == 8) {
  961.  
  962.             // Left channel
  963.             hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
  964.             y = rc.top + h/4;
  965.             PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  966.  
  967.             for (x=0; x<w; x++) {
  968.                 b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2);
  969.  
  970.                 if (b > 0x80) {
  971.                     i = y - (int)muldiv32(b-0x80,(h/4),128);
  972.                     PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  973.                 }
  974.                 else {
  975.                     i = y + (int)muldiv32(0x80-b,(h/4),128);
  976.                     PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
  977.                 }
  978.             }
  979.             DeleteObject(SelectObject(hdc, hbr));
  980.  
  981.             // Right channel
  982.             hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
  983.             y = rc.top + h * 3 / 4;
  984.             PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  985.  
  986.             for (x=0; x<w; x++) {
  987.                 b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
  988.  
  989.                 if (b > 0x80) {
  990.                     i = y - (int)muldiv32(b-0x80,(h/4),128);
  991.                     PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  992.                 }
  993.                 else {
  994.                     i = y + (int)muldiv32(0x80-b,(h/4),128);
  995.                     PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
  996.                 }
  997.             }
  998.             DeleteObject(SelectObject(hdc, hbr));
  999.         }
  1000.  
  1001.     //
  1002.     // 16 bit data is centred around 0x00
  1003.     //
  1004.         else if (wf.wBitsPerSample == 16) {
  1005.  
  1006.             // Left channel
  1007.             hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
  1008.             y = rc.top + h/4;
  1009.             PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  1010.  
  1011.             for (x=0; x<w; x++) {
  1012.         // Don't make any assumptions about INT size !
  1013.                 i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2);
  1014.                 if (i > 0) {
  1015.                     i = y - (int) ((LONG)i * (h/4) / 32768);
  1016.                     PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  1017.                 }
  1018.                 else {
  1019.                     i = (int) ((LONG)i * (h/4) / 32768);
  1020.                     PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
  1021.                 }
  1022.             }
  1023.             DeleteObject(SelectObject(hdc, hbr));
  1024.  
  1025.             // Right channel
  1026.             hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
  1027.             y = rc.top + h * 3 / 4;
  1028.             PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  1029.  
  1030.             for (x=0; x<w; x++) {
  1031.         // Don't make any assumptions about INT size !
  1032.                 i = *((HPSHORT)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
  1033.                 if (i > 0) {
  1034.                    i = y - (int) ((LONG)i * (h/4) / 32768);
  1035.                    PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  1036.                 }
  1037.                 else {
  1038.                    i = (int) ((LONG)i * (h/4) / 32768);
  1039.                    PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
  1040.                 }
  1041.             }
  1042.             DeleteObject(SelectObject(hdc, hbr));
  1043.         }
  1044.     } // endif stereo
  1045. }
  1046.  
  1047. /*----------------------------------------------------------------------------*\
  1048. |    PaintStuff()
  1049. |
  1050. |    Paint the screen with what we plan to show them.
  1051. \*----------------------------------------------------------------------------*/
  1052. long PaintStuff(HDC hdc, HWND hwnd, BOOL fDrawEverything)
  1053. {
  1054.     int         yStreamTop;
  1055.     TCHAR       ach[400];
  1056.     int         iFrameWidth, iLen;
  1057.     int         n;
  1058.     int         nFrames;
  1059.     LPBITMAPINFOHEADER lpbi;
  1060.     LONG        lTime;
  1061.     LONG        lSize = 0;
  1062.     LONG        lAudioStart;
  1063.     LONG        lAudioLen;
  1064.     RECT        rcFrame, rcC;
  1065.     int         i;
  1066.     LONG        lFrame;
  1067.     LONG        lCurFrame;
  1068.     HBRUSH      hbr;
  1069.     RECT        rc;
  1070.     COLORREF    nCol;
  1071.  
  1072.     SelectObject(hdc, hfontApp);
  1073.  
  1074.     #define PRINT(sz) \
  1075.         (nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW)), \
  1076.     TextOut(hdc, TSPACE, yStreamTop, sz, lstrlen(sz)), \
  1077.         SetBkColor(hdc, nCol),                   \
  1078.     yStreamTop += tm.tmHeight+1)
  1079.  
  1080.     #define PF1(sz,a)                   (wsprintf(ach, sz, a), PRINT(ach))
  1081.     #define PF2(sz,a,b)                 (wsprintf(ach, sz, a, b), PRINT(ach))
  1082.     #define PF3(sz,a,b,c)               (wsprintf(ach, sz, a, b, c), PRINT(ach))
  1083.     #define PF4(sz,a,b,c,d)             (wsprintf(ach, sz, a, b, c, d), PRINT(ach))
  1084.     #define PF5(sz,a,b,c,d,e)           (wsprintf(ach, sz, a, b, c, d, e), PRINT(ach))
  1085.     #define PF6(sz,a,b,c,d,e,f)         (wsprintf(ach, sz, a, b, c, d, e, f), PRINT(ach))
  1086.     #define PF7(sz,a,b,c,d,e,f,g)       (wsprintf(ach, sz, a, b, c, d, e, f, g), PRINT(ach))
  1087.     #define PF8(sz,a,b,c,d,e,f,g,h)     (wsprintf(ach, sz, a, b, c, d, e, f, g, h), PRINT(ach))
  1088.     #define PF9(sz,a,b,c,d,e,f,g,h,i)   (wsprintf(ach, sz, a, b, c, d, e, f, g, h, i), PRINT(ach))
  1089.  
  1090.     GetClientRect(hwnd, &rcC);
  1091.  
  1092.     //
  1093.     // Look at scrollbars to find current position
  1094.     //
  1095.     lTime = GetScrollTime(hwnd);
  1096.     yStreamTop = -GetScrollPos(hwnd, SB_VERT);
  1097.  
  1098.     //
  1099.     // Walk through all streams and draw something
  1100.     //
  1101.     for (i=0; i<gcpavi; i++) {
  1102.     AVISTREAMINFO    avis;
  1103.     LONG    lEnd, lEndTime, lNextFmt, lPrevFmt, l;
  1104.     LONG    lPos, lNextKey, lPrevKey, lNextAny, lPrevAny;
  1105.  
  1106.     AVIStreamInfo(gapavi[i], &avis, sizeof(avis));
  1107.     FIXCC(avis.fccHandler);
  1108.     FIXCC(avis.fccType);
  1109.  
  1110.     l = sizeof(abFormat);
  1111.     AVIStreamReadFormat(gapavi[i],0, &abFormat, &l);
  1112.  
  1113.     PF7(TEXT("Stream%d [%4.4hs/%4.4hs] Start: %ld Length: %ld (%ld.%03ld sec)             "),
  1114.             i,
  1115.             (LPSTR)&avis.fccType,
  1116.             (LPSTR)&avis.fccHandler,
  1117.             AVIStreamStart(gapavi[i]),
  1118.             AVIStreamLength(gapavi[i]),
  1119.             AVIStreamLengthTime(gapavi[i]) / 1000,
  1120.             AVIStreamLengthTime(gapavi[i]) % 1000);
  1121.  
  1122.     lPos = AVIStreamTimeToSample(gapavi[i], lTime);
  1123.     AVIStreamSampleSize(gapavi[i], lPos, &lSize);
  1124.  
  1125.     lPrevKey = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_KEY);
  1126.     lPrevAny = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_ANY);
  1127.     lPrevFmt = AVIStreamFindSample(gapavi[i], lPos, FIND_PREV|FIND_FORMAT);
  1128.  
  1129.     lNextKey = AVIStreamFindSample(gapavi[i], lPos, FIND_NEXT|FIND_KEY);
  1130.     lNextAny = AVIStreamFindSample(gapavi[i], lPos, FIND_NEXT|FIND_ANY);
  1131.     lNextFmt = AVIStreamFindSample(gapavi[i], lPos, FIND_NEXT|FIND_FORMAT);
  1132.  
  1133.     PF5(TEXT("Pos:%ld Time:%ld.%03ld sec Size:%ld bytes %s                                 "),
  1134.         lPos, lTime/1000, lTime%1000, lSize,
  1135.         (LPTSTR)(lPos == lPrevKey ? TEXT("Key") : TEXT("")));
  1136.  
  1137.     PF6(TEXT("PrevKey=%ld NextKey=%ld, PrevAny=%ld NextAny=%ld, PrevFmt=%ld NextFmt=%ld                      "),
  1138.         lPrevKey, lNextKey,
  1139.         lPrevAny, lNextAny,
  1140.         lPrevFmt, lNextFmt);
  1141.  
  1142.     //
  1143.     // Draw a VIDEO stream
  1144.     //
  1145.     if (avis.fccType == streamtypeVIDEO) {
  1146.         if (gapgf[i] == NULL)
  1147.         continue;
  1148.  
  1149.         lpbi = (LPBITMAPINFOHEADER)abFormat;
  1150.         FIXCC(lpbi->biCompression);
  1151.  
  1152.         //
  1153.         // display video format
  1154.         //
  1155.         //  Video: 160x120x8 (cram)
  1156.         //
  1157.         PF4(TEXT("Format: %dx%dx%d (%4.4hs)"),
  1158.         (int)lpbi->biWidth,
  1159.         (int)lpbi->biHeight,
  1160.         (int)lpbi->biBitCount,
  1161.         (LPSTR)&lpbi->biCompression);
  1162.  
  1163.             //
  1164.         // Which frame belongs at this time?
  1165.         //
  1166.         lEndTime = AVIStreamEndTime(gapavi[i]);
  1167.         if (lTime <= lEndTime)
  1168.         lFrame = AVIStreamTimeToSample(gapavi[i], lTime);
  1169.         else {    // we've scrolled past the end of this stream
  1170.         lEnd = AVIStreamEnd(gapavi[i]);
  1171.         lFrame = lEnd + AVIStreamTimeToSample(
  1172.             gapavi[i], lTime - lEndTime);
  1173.         }
  1174.  
  1175.         //
  1176.         // how wide is each frame to paint?
  1177.         //
  1178.         iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
  1179.         gwZoom / 4 + HSPACE;
  1180.  
  1181.         //
  1182.         // how many frames can we fit on each half of the screen?
  1183.         //
  1184.         nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
  1185.         if (nFrames < 0)
  1186.         nFrames = 0;    // at least draw *something*
  1187.  
  1188.         //
  1189.         // Step through all the frames we'll draw
  1190.         //
  1191.         for (n=-nFrames; n<=nFrames; n++)
  1192.         {
  1193.  
  1194.                 //
  1195.                 // Each video stream is drawn as a horizontal line of
  1196.                 // frames, very close together.
  1197.                 // The first video stream shows a different frame in
  1198.                 // each square. Thus the scale of time is determined
  1199.                 // by the first video stream.
  1200.                 // Every other video stream shows whatever
  1201.                 // frame belongs at the time corresponding to the mid-
  1202.                 // point of each square.
  1203.                 //
  1204.         if (gapavi[i] == gpaviVideo) {
  1205.  
  1206.                     //
  1207.                     // by definition, we know what frame we're drawing..
  1208.                     // (lFrame-n), (lFrame-(n-1)), ..., (lFrame), ...,
  1209.                     // (lFrame+(n-1)), (lFrame+n)
  1210.                     //
  1211.             lCurFrame = lFrame + n;
  1212.  
  1213.             //
  1214.             // what time is it at that frame?  This number will be
  1215.             // printed underneath the frame
  1216.             //
  1217.             l = AVIStreamSampleToTime(gapavi[i], lCurFrame);
  1218.  
  1219.         } else {    // NOT the first video stream
  1220.  
  1221.             //
  1222.             // What time is it at the left edge of the square
  1223.             // we'll draw?  That's what frame we use.
  1224.             //
  1225.                     if (n<0) {
  1226.                         l = lTime - muldiv32(-n * iFrameWidth + HSPACE,
  1227.                     gdwMicroSecPerPixel, 1000);
  1228.                     }
  1229.                     else {
  1230.                 l = lTime + muldiv32(n * iFrameWidth,
  1231.                     gdwMicroSecPerPixel, 1000);
  1232.                     }
  1233.  
  1234.                     //
  1235.             // What frame belongs to that time?
  1236.             //
  1237.             lCurFrame = AVIStreamTimeToSample(gapavi[i], l);
  1238.  
  1239.                     //
  1240.                     // what time is it at that frame? This number will
  1241.                     // be printed underneath the frame
  1242.                     //
  1243.                     l = AVIStreamSampleToTime(gapavi[i], lCurFrame);
  1244.         }
  1245.         
  1246.         // !!!
  1247.         // Could actually return an LPBI for invalid frames
  1248.         // so we better force it to NULL.
  1249.         //
  1250.         if (gapgf[i] && lCurFrame >= AVIStreamStart(gapavi[i]))
  1251.             lpbi = AVIStreamGetFrame(gapgf[i], lCurFrame);
  1252.         else
  1253.             lpbi = NULL;
  1254.  
  1255.         //
  1256.         // Figure out where to draw this frame
  1257.         //
  1258.         rcFrame.left   = rcC.right / 2 -
  1259.             ((avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4)
  1260.             / 2 + (n * iFrameWidth);
  1261.         rcFrame.top    = yStreamTop;
  1262.         rcFrame.right  = rcFrame.left +
  1263.             (avis.rcFrame.right - avis.rcFrame.left)*gwZoom/4;
  1264.         rcFrame.bottom = rcFrame.top +
  1265.             (avis.rcFrame.bottom - avis.rcFrame.top)*gwZoom/4;
  1266.  
  1267.         //
  1268.         // draw a border around the current frame.  Make the
  1269.         // one around the centre frame a special colour.
  1270.         //
  1271.         if (n == 0)
  1272.             hbr = CreateSolidBrush(RGB(255,0,0));
  1273.         else
  1274.             hbr = CreateSolidBrush(RGB(255,255,255));
  1275.  
  1276.         InflateRect(&rcFrame, 1, 1);
  1277.         FrameRect(hdc, &rcFrame, hbr);
  1278.         InflateRect(&rcFrame, -1, -1);
  1279.         DeleteObject (hbr);
  1280.  
  1281.         //
  1282.         // Now draw the video frame in the computed rectangle
  1283.         //
  1284.         PaintVideo(hdc, rcFrame, i, lpbi, lCurFrame, l);
  1285.  
  1286.         }
  1287.     
  1288.         //
  1289.         // Move down to where we can draw the next stream
  1290.         //
  1291.         yStreamTop += (rcFrame.bottom - rcFrame.top) +
  1292.             TSPACE;
  1293.     }
  1294.  
  1295.     //
  1296.     // Draw an AUDIO stream
  1297.     //
  1298.     else if (avis.fccType == streamtypeAUDIO) {
  1299.         LPWAVEFORMAT pwf = (LPWAVEFORMAT)abFormat;
  1300.         TCHAR *szFmt;
  1301.  
  1302.         if (pwf->wFormatTag == 1) {  // PCM
  1303.         if (pwf->nChannels == 1)
  1304.             szFmt = TEXT("Format: Mono %ldHz %dbit");
  1305.         else
  1306.             szFmt = TEXT("Format: Stereo %ldHz %dbit");
  1307.         }
  1308.         else if (pwf->wFormatTag == 2) {  // ADPCM
  1309.         if (pwf->nChannels == 1)
  1310.             szFmt = TEXT("Format: ADPCM Mono %ldHz %dbit");
  1311.         else
  1312.             szFmt = TEXT("Format: ADPCM Stereo %ldHz %dbit");
  1313.         }
  1314.         else {
  1315.         if (pwf->nChannels == 1)
  1316.             szFmt = TEXT("Format: Compressed Mono %ldHz %dbit");
  1317.         else
  1318.             szFmt = TEXT("Format: Compressed Stereo %ldHz %dbit");
  1319.         }
  1320.  
  1321.         PF2(szFmt,(LONG)pwf->nSamplesPerSec,
  1322.         (int)((LONG)pwf->nAvgBytesPerSec / pwf->nSamplesPerSec /
  1323.               pwf->nChannels) * 8);
  1324.  
  1325.         //
  1326.         // Figure out which samples are visible
  1327.         //
  1328.         lAudioStart = lTime - muldiv32(rcC.right / 2,
  1329.                     gdwMicroSecPerPixel, 1000);
  1330.         lAudioLen = 2 * (lTime - lAudioStart);
  1331.  
  1332.         //
  1333.         // Make rectangle to draw audio into
  1334.         //
  1335.         rc.left = rcC.left;
  1336.         rc.right = rcC.right;
  1337.         rc.top = yStreamTop;
  1338.         rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;
  1339.  
  1340.         //
  1341.         // Actually paint the audio
  1342.         //
  1343.         PaintAudio(hdc, &rc, gapavi[i], lAudioStart, lAudioLen);
  1344.  
  1345.             //
  1346.             // Move down to where we can draw the next stream
  1347.             //
  1348.             yStreamTop += AUDIOVSPACE * gwZoom / 4;
  1349.  
  1350.     }
  1351.     else if (avis.fccType == streamtypeTEXT) {
  1352.         LONG    lPos;
  1353.         int        iLeft;
  1354.  
  1355.         lPos = AVIStreamTimeToSample(gapavi[i],
  1356.                      lTime -
  1357.                      muldiv32((rcC.right - rcC.left),
  1358.                         gdwMicroSecPerPixel,
  1359.                         1000));
  1360.  
  1361.         if (lPos < 0)
  1362.         lPos = 0;
  1363.  
  1364.         PatBlt(hdc, rcC.left, yStreamTop,
  1365.            rcC.right - rcC.left, TSPACE + TSPACE,
  1366.            WHITENESS);
  1367.     
  1368.         while (lPos < AVIStreamEnd(gapavi[i]) - 1) {
  1369.  
  1370.                 LONG    lStreamTime = AVIStreamSampleToTime(gapavi[i], lPos);
  1371.  
  1372.                 // What pixel is it at this time?
  1373.  
  1374.                 if (lStreamTime < lTime) {              // pixel will be left of centre
  1375.                     iLeft = (rcC.right + rcC.left) / 2 -
  1376.                 (int) muldiv32(lTime - lStreamTime, 1000,  gdwMicroSecPerPixel);
  1377.                 }
  1378.                 else {                                  // pixel is at, or right of, centre
  1379.                     iLeft = (rcC.right + rcC.left) / 2 +
  1380.                 (int) muldiv32(lStreamTime - lTime, 1000,  gdwMicroSecPerPixel);
  1381.                 }
  1382.         if (iLeft >= rcC.right)
  1383.             break;
  1384.  
  1385.         AVIStreamRead(gapavi[i], lPos, 1, ach, sizeof(ach), &l, NULL);
  1386.  
  1387.         nCol = SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
  1388.         if (l)
  1389.             TextOut(hdc, iLeft, yStreamTop, ach, (int) l - 1);
  1390.  
  1391.         iLen = wsprintf(ach, TEXT("%ld"), lPos);
  1392.         TextOut(hdc, iLeft, yStreamTop + TSPACE, ach, iLen);
  1393.         SetBkColor(hdc, nCol);
  1394.  
  1395.         lPos += 1;
  1396.         }
  1397.  
  1398.         yStreamTop += TSPACE + TSPACE;
  1399.     }
  1400.  
  1401.     yStreamTop += VSPACE;
  1402.  
  1403.         //
  1404.         // Give up once we're painting below the bottom of the window
  1405.         //
  1406.         if (!fDrawEverything && yStreamTop >= rcC.bottom)
  1407.             break;
  1408.     }
  1409.  
  1410.     //
  1411.     // How many lines did we draw?
  1412.     //
  1413.     return yStreamTop + GetScrollPos(hwnd, SB_VERT);
  1414. }
  1415.  
  1416. /*----------------------------------------------------------------------------*\
  1417. |   AppWndProc( hwnd, uiMessage, wParam, lParam )                   |
  1418. |                                                                              |
  1419. |   Description:                                                               |
  1420. |       The window proc for the app's main (tiled) window.  This processes all |
  1421. |       of the parent window's messages.                                       |
  1422. |                                                                              |
  1423. |   Arguments:                                                                 |
  1424. |    hwnd        window handle for the window                   |
  1425. |       msg             message number                                         |
  1426. |       wParam          message-dependent                                      |
  1427. |       lParam          message-dependent                                      |
  1428. |                                                                              |
  1429. |   Returns:                                                                   |
  1430. |       0 if processed, nonzero if ignored                                     |
  1431. |                                                                              |
  1432. \*----------------------------------------------------------------------------*/
  1433. LONG WINAPI  AppWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  1434. {
  1435.     PAINTSTRUCT ps;
  1436.     BOOL        f;
  1437.     HDC        hdc;
  1438.     RECT    rc;
  1439.     LONG    l;
  1440.  
  1441.     switch (msg) {
  1442.  
  1443.         //
  1444.         // If we passed a command line filename, open it
  1445.         //
  1446.         case WM_CREATE:
  1447.             if (gachFileName[0])
  1448.                 InitAvi(hwnd, gachFileName, MENU_OPEN);
  1449.         break;
  1450.  
  1451.         case WM_COMMAND:
  1452.             return AppCommand(hwnd,msg,wParam,lParam);
  1453.  
  1454.         case WM_INITMENU:
  1455.             f = gfVideoFound || gfAudioFound;
  1456.             EnableMenuItem((HMENU)wParam, MENU_OPTIONS,f ? MF_ENABLED :
  1457.             MF_GRAYED);
  1458.  
  1459.             f = gcpavi > 0;
  1460.             EnableMenuItem((HMENU)wParam, MENU_SAVEAS, f ? MF_ENABLED :
  1461.             MF_GRAYED);
  1462.             EnableMenuItem((HMENU)wParam, MENU_NEW,    f ? MF_ENABLED :
  1463.             MF_GRAYED);
  1464.             EnableMenuItem((HMENU)wParam, MENU_ADD,    f ? MF_ENABLED :
  1465.             MF_GRAYED);
  1466.     
  1467.             f = (gpfile != 0) && gbCanPalMap && gfVideoFound;
  1468.             EnableMenuItem((HMENU)wParam, MENU_NEWPALETTE, f ? MF_ENABLED :
  1469.                         MF_GRAYED);
  1470.  
  1471.         f = gfVideoFound || gfAudioFound;
  1472.             EnableMenuItem((HMENU)wParam, MENU_PLAY,
  1473.             (f && !gfPlaying) ? MF_ENABLED : MF_GRAYED);
  1474.             EnableMenuItem((HMENU)wParam, MENU_STOP,
  1475.                         (f && gfPlaying) ? MF_ENABLED : MF_GRAYED);
  1476.  
  1477.         CheckMenuItem((HMENU)wParam, MENU_ZOOMQUARTER,
  1478.             (gwZoom == 1) ? MF_CHECKED : MF_UNCHECKED);
  1479.         CheckMenuItem((HMENU)wParam, MENU_ZOOMHALF,
  1480.             (gwZoom == 2) ? MF_CHECKED : MF_UNCHECKED);
  1481.         CheckMenuItem((HMENU)wParam, MENU_ZOOM1,
  1482.             (gwZoom == 4) ? MF_CHECKED : MF_UNCHECKED);
  1483.         CheckMenuItem((HMENU)wParam, MENU_ZOOM2,
  1484.             (gwZoom == 8) ? MF_CHECKED : MF_UNCHECKED);
  1485.         CheckMenuItem((HMENU)wParam, MENU_ZOOM4,
  1486.             (gwZoom == 16) ? MF_CHECKED : MF_UNCHECKED);
  1487.             
  1488.             break;
  1489.  
  1490.         //
  1491.         // During a wait state (eg saving) don't let us choose any menus
  1492.         //
  1493.     case WM_NCHITTEST:
  1494.         if (fWait) {
  1495.  
  1496.         // Let windows tell us where the cursor is
  1497.         lParam = DefWindowProc(hwnd,msg,wParam,lParam);
  1498.  
  1499.         // If it's over a menu, pretend it's in the client (force
  1500.         // an hourglass)
  1501.         if (lParam == HTMENU)
  1502.             lParam = HTCLIENT;
  1503.  
  1504.         return lParam;
  1505.         }
  1506.         break;
  1507.  
  1508.     //
  1509.     // Set vertical scrollbar for scrolling streams
  1510.     //
  1511.     case WM_SIZE:
  1512.         GetClientRect(hwnd, &rc);
  1513.  
  1514.             //
  1515.             // There is not enough vertical room to show all streams. Scrollbars
  1516.             // are required.
  1517.             //
  1518.         if (vertHeight > rc.bottom) {
  1519.             vertSBLen = vertHeight - rc.bottom;
  1520.             SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);
  1521.  
  1522.             //
  1523.             // Everything fits vertically. No scrollbar necessary.
  1524.             //
  1525.         } else {
  1526.             vertSBLen = 0;
  1527.             SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
  1528.         }
  1529.         break;
  1530.  
  1531.         //
  1532.         // During a wait state, show an hourglass over our client area
  1533.         //
  1534.         case WM_SETCURSOR:
  1535.             if (fWait && LOWORD(lParam) == HTCLIENT) {
  1536.                 SetCursor(LoadCursor(NULL, IDC_WAIT));
  1537.                 return TRUE;
  1538.             }
  1539.             break;
  1540.  
  1541.     //
  1542.     // We're out of here!
  1543.     //
  1544.         case WM_DESTROY:
  1545.             FreeAvi(hwnd);    // close all open streams
  1546.         AVIFileExit();    // shuts down the AVIFile system
  1547.         if (ghLib)
  1548.                 FreeLibrary(ghLib);
  1549.         PostQuitMessage(0);
  1550.         break;
  1551.  
  1552.     //
  1553.     // Don't let us close ourselves in a wait state (eg saving)
  1554.     //
  1555.         case WM_CLOSE:
  1556.         if (fWait)
  1557.         return 0;
  1558.             break;
  1559.  
  1560.     //
  1561.     // Block keyboard access to menus if waiting
  1562.     //
  1563.     case WM_SYSCOMMAND:
  1564.         switch (wParam & 0xFFF0) {
  1565.         case SC_KEYMENU:
  1566.             if (fWait)
  1567.             return 0;
  1568.             break;
  1569.         }
  1570.         break;
  1571.  
  1572.         case WM_PALETTECHANGED:
  1573.         // It came from us.  Ignore it
  1574.             if ((HWND)wParam == hwnd)
  1575.                 break;
  1576.  
  1577.     case WM_QUERYNEWPALETTE:
  1578.             hdc = GetDC(hwnd);
  1579.  
  1580.             //
  1581.             // Realize the palette of the first video stream
  1582.             // !!! Assumes the first stream is video
  1583.             //
  1584.             if (ghdd[0] && (f = DrawDibRealize(ghdd[0], hdc, FALSE)))
  1585.                 InvalidateRect(hwnd,NULL,TRUE);
  1586.  
  1587.             ReleaseDC(hwnd,hdc);
  1588.  
  1589.             return f;
  1590.     
  1591.         case WM_ERASEBKGND:
  1592.             break;
  1593.  
  1594.         case WM_PAINT:
  1595.             hdc = BeginPaint(hwnd,&ps);
  1596.  
  1597.         PaintStuff(hdc, hwnd, FALSE);
  1598.  
  1599.             EndPaint(hwnd,&ps);
  1600.             break;
  1601.  
  1602.     //
  1603.     // handle the keyboard interface
  1604.     //
  1605.     case WM_KEYDOWN:
  1606.             switch (wParam)
  1607.             {
  1608.                 case VK_UP:    PostMessage (hwnd,WM_VSCROLL,SB_LINEUP,0L);   break;
  1609.                 case VK_DOWN:  PostMessage (hwnd,WM_VSCROLL,SB_LINEDOWN,0L); break;
  1610.                 case VK_PRIOR: PostMessage (hwnd,WM_HSCROLL,SB_PAGEUP,0L);   break;
  1611.                 case VK_NEXT:  PostMessage (hwnd,WM_HSCROLL,SB_PAGEDOWN,0L); break;
  1612.                 case VK_HOME:  PostMessage (hwnd,WM_HSCROLL,SB_THUMBPOSITION,0L);     break;
  1613.                 case VK_END:   PostMessage (hwnd,WM_HSCROLL,SB_THUMBPOSITION,0x7FFF); break;
  1614.                 case VK_LEFT:  PostMessage (hwnd,WM_HSCROLL,SB_LINEUP,0L);   break;
  1615.                 case VK_RIGHT: PostMessage (hwnd,WM_HSCROLL,SB_LINEDOWN,0L); break;
  1616.         }
  1617.         break;
  1618.  
  1619.         case WM_HSCROLL:
  1620.             l = GetScrollTime(hwnd);
  1621.  
  1622.             switch (GET_WM_HSCROLL_CODE(wParam, lParam)) {
  1623.                 case SB_LINEDOWN:      l += timehscroll;  break;
  1624.                 case SB_LINEUP:        l -= timehscroll;  break;
  1625.                 case SB_PAGEDOWN:      l += timeLength/10; break;
  1626.                 case SB_PAGEUP:        l -= timeLength/10; break;
  1627.                 case SB_THUMBTRACK:
  1628.                 case SB_THUMBPOSITION:
  1629.             l = GET_WM_HSCROLL_POS(wParam, lParam);
  1630.             l = timeStart + muldiv32(l, timeLength, SCROLLRANGE);
  1631.             break;
  1632.             }
  1633.  
  1634.         if (l < timeStart)
  1635.         l = timeStart;
  1636.  
  1637.         if (l > timeEnd)
  1638.         l = timeEnd;
  1639.  
  1640.         if (l == (LONG)GetScrollTime(hwnd))
  1641.         break;
  1642.     
  1643.         SetScrollTime(hwnd, l);
  1644.             InvalidateRect(hwnd, NULL, FALSE);
  1645.             UpdateWindow(hwnd);
  1646.             break;
  1647.  
  1648.         case WM_VSCROLL:
  1649.             l = GetScrollPos(hwnd, SB_VERT);
  1650.         GetClientRect(hwnd, &rc);
  1651.  
  1652.             switch (GET_WM_VSCROLL_CODE(wParam, lParam)) {
  1653.                 case SB_LINEDOWN:      l += 10;  break;
  1654.                 case SB_LINEUP:        l -= 10;  break;
  1655.                 case SB_PAGEDOWN:      l += rc.bottom; break;
  1656.                 case SB_PAGEUP:        l -= rc.bottom; break;
  1657.                 case SB_THUMBTRACK:
  1658.                 case SB_THUMBPOSITION:
  1659.             l = GET_WM_VSCROLL_POS(wParam, lParam);
  1660.             break;
  1661.             }
  1662.  
  1663.         if (l < 0)
  1664.         l = 0;
  1665.  
  1666.         if (l > vertSBLen)
  1667.         l = vertSBLen;
  1668.  
  1669.         if (l == GetScrollPos(hwnd, SB_VERT))
  1670.         break;
  1671.     
  1672.         SetScrollPos(hwnd, SB_VERT, (int)l, TRUE);
  1673.             InvalidateRect(hwnd, NULL, TRUE);
  1674.             UpdateWindow(hwnd);
  1675.             break;
  1676.  
  1677.     //
  1678.     // Wave driver wants to tell us something.  Pass it on.
  1679.     //
  1680.     case MM_WOM_OPEN:
  1681.     case MM_WOM_DONE:
  1682.     case MM_WOM_CLOSE:
  1683.         aviaudioMessage(hwnd, msg, wParam, lParam);
  1684.         break;
  1685.     }
  1686.     return DefWindowProc(hwnd,msg,wParam,lParam);
  1687. }
  1688.  
  1689. /*----------------------------------------------------------------------------*\
  1690. |    SaveCallback()
  1691. |
  1692. |    Our save callback that prints our progress in our window title bar.
  1693. \*----------------------------------------------------------------------------*/
  1694. BOOL PASCAL  SaveCallback(int iProgress)
  1695. {
  1696.     TCHAR    ach[128];
  1697.  
  1698.     wsprintf(ach, TEXT("%s - Saving %s: %d%%"),
  1699.         (LPTSTR) gszAppName, (LPTSTR) gachSaveFileName, iProgress);
  1700.  
  1701.     SetWindowText(ghwndApp, ach);
  1702.  
  1703.     //
  1704.     // Give ourselves a chance to abort
  1705.     //
  1706.     return WinYield();
  1707. }
  1708.  
  1709. /*----------------------------------------------------------------------------*\
  1710. |    AppCommand()
  1711. |
  1712. |    Process all of our WM_COMMAND messages.
  1713. \*----------------------------------------------------------------------------*/
  1714. LONG PASCAL AppCommand (HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
  1715. {
  1716.     switch(GET_WM_COMMAND_ID(wParam, lParam))
  1717.     {
  1718.     //
  1719.     // Our about box
  1720.     //
  1721.         case MENU_ABOUT:
  1722.             DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd, AboutDlgProc);
  1723.             break;
  1724.  
  1725.     //
  1726.     // We want out of here!
  1727.     //
  1728.     case MENU_EXIT:
  1729.         PostMessage(hwnd,WM_CLOSE,0,0L);
  1730.             break;
  1731.  
  1732.     //
  1733.     // Set the compression options for each stream - pass an array of
  1734.     // streams and an array of compression options structures
  1735.     //
  1736.         case MENU_OPTIONS:
  1737.             AVISaveOptions(hwnd, ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE
  1738.             | ICMF_CHOOSE_PREVIEW,
  1739.         gcpavi, gapavi, galpAVIOptions);
  1740.         break;
  1741.  
  1742.         //
  1743.         // Use palmap to set the no of colours to save with
  1744.         //
  1745.         case MENU_NEWPALETTE:
  1746.             {
  1747.            HDC          hdc;
  1748.                PAVISTREAM     psMappedStream;
  1749.                PAVISTREAM     psCurrent;
  1750.                AVISTREAMINFO  avis;
  1751.                FARPROC        lpfnAVICreateMappedStream;
  1752.            LPBITMAPINFOHEADER lpbi;
  1753. #define AVICreateMappedStream 11       //Cardinal for procedure in palmap32.dll
  1754.  
  1755.                lpfnAVICreateMappedStream = GetProcAddress(ghLib,MAKEINTRESOURCE(AVICreateMappedStream));
  1756.                if (lpfnAVICreateMappedStream == NULL) {
  1757.                    ErrMsg(TEXT("Unable to find AVICreateMappedStream in palmap32.dll"));
  1758.                    gbCanPalMap = FALSE;
  1759.                }
  1760.  
  1761.                psCurrent = gapavi[giFirstVideo];
  1762.  
  1763.                AVIStreamInfo(psCurrent, &avis, sizeof(avis));
  1764.  
  1765.                if (avis.fccType != streamtypeVIDEO) {
  1766.                    ErrMsg("Stream is not video!");
  1767.                    break;
  1768.                }
  1769.  
  1770.                if (DialogBoxParam(
  1771.                              ghInstApp,
  1772.                              MAKEINTRESOURCE(IDD_NCOLORS),
  1773.                              hwnd,
  1774.                              GetNumberOfColorsDlgProc,
  1775.                              (LPARAM)GetNumberOfColors(psCurrent))
  1776.                    ) {
  1777.                       StartWait();
  1778.                       //
  1779.                       //  Find out how many colours they want to map it to
  1780.                       //
  1781.  
  1782.                       if (0 != (*lpfnAVICreateMappedStream)(&psMappedStream,
  1783.                                               psCurrent,
  1784.                                               gnColours)) {
  1785.                            ErrMsg(TEXT("Can't map stream palette"));
  1786.                       }
  1787.                       else {
  1788.                            gapavi[giFirstVideo] = psMappedStream;
  1789.  
  1790.                // We've just mapped this stream to a new palette
  1791.                // so we have to re-init the GetFrame stuff used
  1792.                // to draw this stream.
  1793.                // Kick start DrawDib to realize the new palette.
  1794.                //
  1795.                if (gapgf[giFirstVideo])
  1796.                 AVIStreamGetFrameClose(gapgf[giFirstVideo]);
  1797.                gapgf[giFirstVideo] =
  1798.                 AVIStreamGetFrameOpen(gapavi[giFirstVideo], 0);
  1799.                lpbi = AVIStreamGetFrame(gapgf[giFirstVideo], 0);
  1800.                DrawDibBegin(ghdd[giFirstVideo], NULL,
  1801.                 lpbi->biWidth, lpbi->biHeight,
  1802.                 lpbi, lpbi->biWidth, lpbi->biHeight, 0);
  1803.                hdc = GetDC(hwnd);
  1804.                DrawDibRealize(ghdd[giFirstVideo], hdc, FALSE);
  1805.                ReleaseDC(hwnd, hdc);
  1806.                       }
  1807.                       EndWait();
  1808.                    }
  1809.  
  1810.             }
  1811.             break;
  1812.  
  1813.     
  1814.     //
  1815.     // Save all the open streams into a file
  1816.     //
  1817.         case MENU_SAVEAS:
  1818.             {
  1819.                 OPENFILENAME ofn;
  1820.  
  1821.                 gachSaveFileName[0] = 0;
  1822.  
  1823.                 //
  1824.                 // prompt user for file to save
  1825.                 //
  1826.                 ofn.lStructSize = sizeof(OPENFILENAME);
  1827.                 ofn.hwndOwner = hwnd;
  1828.                 ofn.hInstance = NULL;
  1829.  
  1830.                 AVIBuildFilter(gachFilter, sizeof(gachFilter)/sizeof(TCHAR), TRUE);
  1831.  
  1832.                 ofn.lpstrFilter = gachFilter;
  1833.                 ofn.lpstrCustomFilter = NULL;
  1834.                 ofn.nMaxCustFilter = 0;
  1835.                 ofn.nFilterIndex = 0;
  1836.                 ofn.lpstrFile = gachSaveFileName;
  1837.                 ofn.nMaxFile = sizeof(gachSaveFileName)/sizeof(TCHAR);
  1838.                 ofn.lpstrFileTitle = NULL;
  1839.                 ofn.nMaxFileTitle = 0;
  1840.                 ofn.lpstrInitialDir = NULL;
  1841.                 ofn.lpstrTitle = TEXT("Save AVI File");
  1842.                 ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
  1843.                     OFN_OVERWRITEPROMPT;
  1844.                 ofn.nFileOffset = 0;
  1845.                 ofn.nFileExtension = 0;
  1846.                 ofn.lpstrDefExt = TEXT("avi");
  1847.                 ofn.lCustData = 0;
  1848.                 ofn.lpfnHook = NULL;
  1849.                 ofn.lpTemplateName = NULL;
  1850.  
  1851.              //
  1852.              // If we get a filename, save it
  1853.              //
  1854.                 if (GetSaveFileName(&ofn)) {
  1855.                     DWORD   fccHandler[MAXNUMSTREAMS];
  1856.                 int        i;
  1857.                 HRESULT hr;
  1858.             
  1859.                 StartWait();
  1860.  
  1861.                 for (i = 0; i < gcpavi; i++)
  1862.                     fccHandler[i] = galpAVIOptions[i]->fccHandler;
  1863.  
  1864.                 hr = AVISaveV(gachSaveFileName,
  1865.                               NULL,
  1866.                               (AVISAVECALLBACK) SaveCallback,
  1867.                               gcpavi,
  1868.                               gapavi,
  1869.                               galpAVIOptions);
  1870.                 if (hr != AVIERR_OK) {
  1871.                         switch (hr) {
  1872.                         case AVIERR_FILEOPEN:
  1873.                             ErrMsg(TEXT("Overwriting an open AVI file is not possible"));
  1874.                             break;
  1875.                         default:
  1876.                             ErrMsg(TEXT("Error saving AVI file"));
  1877.                         }
  1878.                     }
  1879.  
  1880.                 // Now put the video compressors back that we stole
  1881.                 for (i = 0; i < gcpavi; i++)
  1882.                      galpAVIOptions[i]->fccHandler = fccHandler[i];
  1883.             
  1884.                 EndWait();
  1885.                 FixWindowTitle(hwnd);
  1886.                 }
  1887.          }
  1888.         break;
  1889.  
  1890.     //
  1891.     // Close everything
  1892.     //
  1893.     case MENU_NEW:
  1894.         FreeAvi(hwnd);
  1895.         gachFileName[0] = TEXT('\0');
  1896.         FixWindowTitle(hwnd);
  1897.         break;
  1898.     
  1899.     //
  1900.     // Open a new file, or merge streams with a new file
  1901.     //
  1902.         case MENU_OPEN:
  1903.         case MENU_ADD:
  1904.             {
  1905.                 OPENFILENAME ofn;
  1906.  
  1907.                 gachFileName[0] = 0;
  1908.  
  1909.                 //
  1910.                 // prompt user for file to open
  1911.                 //
  1912.                 ofn.lStructSize = sizeof(OPENFILENAME);
  1913.                 ofn.hwndOwner   = hwnd;
  1914.                 ofn.hInstance   = NULL;
  1915.                 if (wParam == MENU_ADD)
  1916.                 ofn.lpstrTitle = TEXT("Merge With");
  1917.                 else
  1918.                     ofn.lpstrTitle = TEXT("Open AVI");
  1919.  
  1920.                 if (gachFilter[0] == TEXT('\0'))
  1921.                 AVIBuildFilter(gachFilter, sizeof(gachFilter)/sizeof(TCHAR), FALSE);
  1922.         
  1923.                 ofn.lpstrFilter       = gachFilter;
  1924.                 ofn.lpstrCustomFilter = NULL;
  1925.                 ofn.nMaxCustFilter    = 0;
  1926.                 ofn.nFilterIndex      = 0;
  1927.                 ofn.lpstrFile         = gachFileName;
  1928.                 ofn.nMaxFile          = sizeof(gachFileName)/sizeof(TCHAR);
  1929.                 ofn.lpstrFileTitle    = NULL;
  1930.                 ofn.nMaxFileTitle     = 0;
  1931.                 ofn.lpstrInitialDir   = NULL;
  1932.                 ofn.Flags             = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
  1933.                 ofn.nFileOffset       = 0;
  1934.                 ofn.nFileExtension    = 0;
  1935.                 ofn.lpstrDefExt       = NULL;
  1936.                 ofn.lCustData         = 0;
  1937.                 ofn.lpfnHook          = NULL;
  1938.                 ofn.lpTemplateName    = NULL;
  1939.  
  1940.                 //
  1941.                 // If we've got a filename, go open it
  1942.                 //
  1943.                 if (GetOpenFileNamePreview(&ofn))
  1944.                 InitAvi(hwnd, gachFileName, wParam);
  1945.             }
  1946.             break;
  1947.  
  1948.     //
  1949.     // Open the "fake" ball file as our current file
  1950.     //
  1951.     case MENU_BALL:
  1952.         InitBall(hwnd);
  1953.         break;
  1954.     
  1955.     case MENU_ZOOMQUARTER:
  1956.         gwZoom = 1;
  1957.         FixScrollbars(hwnd);
  1958.             InvalidateRect(hwnd, NULL, TRUE);
  1959.         break;
  1960.     
  1961.     case MENU_ZOOMHALF:
  1962.         gwZoom = 2;
  1963.         FixScrollbars(hwnd);
  1964.             InvalidateRect(hwnd, NULL, TRUE);
  1965.         break;
  1966.     
  1967.     case MENU_ZOOM1:
  1968.         gwZoom = 4;
  1969.         FixScrollbars(hwnd);
  1970.             InvalidateRect(hwnd, NULL, TRUE);
  1971.         break;
  1972.     
  1973.     case MENU_ZOOM2:
  1974.         gwZoom = 8;
  1975.         FixScrollbars(hwnd);
  1976.             InvalidateRect(hwnd, NULL, TRUE);
  1977.         break;
  1978.     
  1979.     case MENU_ZOOM4:
  1980.         gwZoom = 16;
  1981.         FixScrollbars(hwnd);
  1982.             InvalidateRect(hwnd, NULL, TRUE);
  1983.             break;
  1984.  
  1985.     //
  1986.     // Simulate playing the file.  We just play the 1st audio stream and let
  1987.     // our main message loop scroll the video by whenever it's bored.
  1988.     //
  1989.     case MENU_PLAY:
  1990.         if (gfAudioFound)
  1991.             aviaudioPlay(hwnd,
  1992.              gpaviAudio,
  1993.              AVIStreamTimeToSample(gpaviAudio, GetScrollTime(hwnd)),
  1994.              AVIStreamEnd(gpaviAudio),
  1995.              FALSE);
  1996.             gfPlaying = TRUE;
  1997.             glPlayStartTime = timeGetTime();
  1998.             glPlayStartPos = GetScrollTime(hwnd);
  1999.             break;
  2000.  
  2001.     //
  2002.     // Stop the play preview
  2003.     //
  2004.     case MENU_STOP:
  2005.         if (gfAudioFound)
  2006.             aviaudioStop();
  2007.         gfPlaying = FALSE;
  2008.         break;
  2009.  
  2010.     }
  2011.     return 0L;
  2012. }
  2013.  
  2014. /*----------------------------------------------------------------------------*\
  2015. |   ErrMsg - Opens a Message box with a error message in it.  The user can     |
  2016. |            select the OK button to continue                                  |
  2017. \*----------------------------------------------------------------------------*/
  2018. int ErrMsg (LPTSTR sz,...)
  2019. {
  2020.     static TCHAR ach[2000];
  2021.     va_list va;
  2022.  
  2023.     va_start(va, sz);
  2024.     wvsprintf (ach,sz, va);
  2025.     va_end(va);
  2026.     MessageBox(NULL,ach,NULL,
  2027. #ifdef BIDI
  2028.         MB_RTL_READING |
  2029. #endif
  2030.     MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
  2031.     return FALSE;
  2032. }
  2033.  
  2034.  
  2035. // Find the number of colours present in the stream palette
  2036. LONG GetNumberOfColors(PAVISTREAM ps)
  2037. {
  2038.     BITMAPINFOHEADER bmih;
  2039.     LONG             cbFormat = sizeof(bmih);
  2040.  
  2041.     bmih.biClrUsed = 0;
  2042.  
  2043.     AVIStreamReadFormat(ps, 0, (LPVOID)&bmih, &cbFormat);
  2044.  
  2045.     return (LONG)bmih.biClrUsed;
  2046. }
  2047.  
  2048. /* GetNumberOfColorsDlgProc()
  2049.  *
  2050.  * Dialog Procedure for getting the no of colours the user wants in the palette
  2051.  *
  2052.  */
  2053.  
  2054. BOOL CALLBACK GetNumberOfColorsDlgProc(
  2055.     HWND    hwnd,
  2056.     UINT    msg,
  2057.     WPARAM    wParam,
  2058.     LPARAM    lParam)
  2059. {
  2060.         BOOL Translated;
  2061.     switch (msg) {
  2062.     case WM_COMMAND:
  2063.             switch (GET_WM_COMMAND_ID(wParam, lParam)) {
  2064.             case IDOK:
  2065.         gnColours = GetDlgItemInt(hwnd, IDC_NCOLORS, &Translated, FALSE);
  2066.                 EndDialog(hwnd, TRUE);
  2067.                 return TRUE;
  2068.             case IDCANCEL:
  2069.                 EndDialog(hwnd, FALSE);
  2070.             }
  2071.             break;
  2072.  
  2073.     case WM_INITDIALOG:
  2074.             {
  2075.                 SetDlgItemInt(hwnd, IDC_NCOLORS, lParam, FALSE);
  2076.         return TRUE;
  2077.             }
  2078.     }
  2079.     return FALSE;
  2080. }
  2081.  
  2082.  
  2083. /* AboutDlgProc()
  2084.  *
  2085.  * Dialog Procedure for the "about" dialog box.
  2086.  *
  2087.  */
  2088.  
  2089. BOOL CALLBACK AboutDlgProc(
  2090.     HWND    hwnd,
  2091.     UINT    msg,
  2092.     WPARAM    wParam,
  2093.     LPARAM    lParam)
  2094. {
  2095.     switch (msg) {
  2096.     case WM_COMMAND:
  2097.         EndDialog(hwnd, TRUE);
  2098.         return TRUE;
  2099.         case WM_INITDIALOG:
  2100.                 if (gbCanPalMap)                                         // Provide the user with
  2101.                     ShowWindow(GetDlgItem(hwnd, IDS_PALETTE), SW_HIDE);  // some information about
  2102.                 else                                                     // whether palmap32.dll was
  2103.                     ShowWindow(GetDlgItem(hwnd, IDS_PALETTE), SW_SHOW);  // found.
  2104.                 return TRUE;
  2105.     }
  2106.     return FALSE;
  2107. }
  2108.